diff --git a/extensions/accessibility-kanban/extension.mjs b/extensions/accessibility-kanban/extension.mjs index be66adac..9b5edb57 100644 --- a/extensions/accessibility-kanban/extension.mjs +++ b/extensions/accessibility-kanban/extension.mjs @@ -14,10 +14,26 @@ const VALID_COLUMNS = new Set(COLUMNS); let repoInfoCache = null; let githubTokenCache; -let sessionRef = null; +let workspaceCwd = null; + +// The canvas request context reports the active session's *working directory* +// (i.e. the repo checkout). Note this is different from `session.workspacePath`, +// which points at the session-state folder (~/.copilot/session-state/) and is +// NOT a git repo — using it for git resolution is what caused the board to fail +// with "Unable to detect the current repository from this workspace." +function setWorkspaceCwd(dir) { + if (typeof dir !== "string" || !dir.trim() || dir === workspaceCwd) return; + workspaceCwd = dir; + repoInfoCache = null; + githubTokenCache = undefined; +} + +function captureCwd(ctx) { + setWorkspaceCwd(ctx?.session?.workingDirectory); +} function getWorkspaceCwd() { - return sessionRef?.workspacePath || process.cwd(); + return workspaceCwd || process.cwd(); } // ─── Repo resolution ─── @@ -61,12 +77,12 @@ function parseRepoFromRemoteUrl(remoteUrl) { function candidateCwds(preferredCwd) { const candidates = [ preferredCwd, - sessionRef?.workspacePath, + workspaceCwd, + process.cwd(), __dirname, path.dirname(__dirname), path.dirname(path.dirname(__dirname)), path.dirname(path.dirname(path.dirname(__dirname))), - process.cwd(), ].filter(Boolean); return [...new Set(candidates)]; } @@ -564,7 +580,8 @@ const canvas = createCanvas({ name: "get_state", description: "Get the current board state including open repository issues and selected label filters.", inputSchema: { type: "object", properties: {}, additionalProperties: false }, - async handler() { + async handler(ctx) { + captureCwd(ctx); return refreshIssuesSafe(); }, }, @@ -580,8 +597,9 @@ const canvas = createCanvas({ required: ["issue_number", "column"], additionalProperties: false, }, - handler({ input }) { - const { issue } = moveIssue(input.issue_number, input.column); + handler(ctx) { + captureCwd(ctx); + const { issue } = moveIssue(ctx.input.issue_number, ctx.input.column); return { issue, state: currentState() }; }, }, @@ -620,8 +638,9 @@ const canvas = createCanvas({ required: ["issues"], additionalProperties: false, }, - handler({ input }) { - return replaceIssues(input.issues); + handler(ctx) { + captureCwd(ctx); + return replaceIssues(ctx.input.issues); }, }, { @@ -635,21 +654,24 @@ const canvas = createCanvas({ required: ["labels"], additionalProperties: false, }, - handler({ input }) { - return setSelectedLabels(input.labels); + handler(ctx) { + captureCwd(ctx); + return setSelectedLabels(ctx.input.labels); }, }, { name: "reset_state", description: "Reset all cards to backlog and clear label filters, then refresh from live repo issues.", inputSchema: { type: "object", properties: {}, additionalProperties: false }, - async handler() { + async handler(ctx) { + captureCwd(ctx); resetBoard(); return refreshIssuesSafe(); }, }, ], - async open() { + async open(ctx) { + captureCwd(ctx); const state = await refreshIssuesSafe(); broadcast("state", state); return { @@ -662,7 +684,7 @@ const canvas = createCanvas({ // ─── Join session (tools + canvas) ─── -const session = await joinSession({ +await joinSession({ canvases: [canvas], tools: [ { @@ -702,5 +724,3 @@ const session = await joinSession({ }, ], }); - -sessionRef = session; diff --git a/extensions/arcade-canvas/assets/preview.png b/extensions/arcade-canvas/assets/preview.png index feb5404b..65d9bd8b 100644 Binary files a/extensions/arcade-canvas/assets/preview.png and b/extensions/arcade-canvas/assets/preview.png differ diff --git a/extensions/backlog-swipe-triage/assets/preview.png b/extensions/backlog-swipe-triage/assets/preview.png index ee411be0..23a7e4f0 100644 Binary files a/extensions/backlog-swipe-triage/assets/preview.png and b/extensions/backlog-swipe-triage/assets/preview.png differ diff --git a/extensions/chromium-control-canvas/assets/preview.png b/extensions/chromium-control-canvas/assets/preview.png index 9ee5501a..f87b051a 100644 Binary files a/extensions/chromium-control-canvas/assets/preview.png and b/extensions/chromium-control-canvas/assets/preview.png differ diff --git a/extensions/release-notes-showcase/.github/plugin/plugin.json b/extensions/release-notes-showcase/.github/plugin/plugin.json index e4088e3e..f87e6d7b 100644 --- a/extensions/release-notes-showcase/.github/plugin/plugin.json +++ b/extensions/release-notes-showcase/.github/plugin/plugin.json @@ -3,8 +3,8 @@ "description": "Compose and refine launch-ready release notes with contributor callouts and export-friendly output.", "version": "1.0.0", "author": { - "name": "James Montemagno", - "url": "https://github.com/jamesmontemagno" + "name": "Kayla Cinnamon", + "url": "https://github.com/cinnamon-msft" }, "keywords": [ "changelog", diff --git a/extensions/release-notes-showcase/assets/preview.png b/extensions/release-notes-showcase/assets/preview.png index c1818140..33f6b4ab 100644 Binary files a/extensions/release-notes-showcase/assets/preview.png and b/extensions/release-notes-showcase/assets/preview.png differ diff --git a/extensions/site-studio/assets/preview.png b/extensions/site-studio/assets/preview.png index 8b64b79a..d0570aaf 100644 Binary files a/extensions/site-studio/assets/preview.png and b/extensions/site-studio/assets/preview.png differ diff --git a/extensions/token-pacman/assets/preview.png b/extensions/token-pacman/assets/preview.png index 4e2d215e..a37a0b54 100644 Binary files a/extensions/token-pacman/assets/preview.png and b/extensions/token-pacman/assets/preview.png differ diff --git a/extensions/where-was-i/extension.mjs b/extensions/where-was-i/extension.mjs index 0d6b7be7..60e75b54 100644 --- a/extensions/where-was-i/extension.mjs +++ b/extensions/where-was-i/extension.mjs @@ -14,11 +14,28 @@ const contextCache = new Map(); // instanceId → contextData const isWindows = process.platform === "win32"; -// Derive repo root from extension location (.github/extensions/where-was-i/) +// Fallback repo root derived from extension location. Only used when the +// session's real working directory is unavailable (see captureCwd below). const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const REPO_ROOT = join(__dirname, "..", "..", ".."); +// The canvas request context reports the active session's working directory — +// the actual repo checkout or worktree the user opened the canvas in. This is +// what git commands must run against; REPO_ROOT (the extension's install dir) +// and session.workspacePath (the session-state folder) are NOT the repo, which +// is why the board previously showed an empty branch as "detached HEAD". +let workspaceCwd = null; + +function captureCwd(ctx) { + const dir = ctx?.session?.workingDirectory; + if (typeof dir === "string" && dir.trim()) workspaceCwd = dir; +} + +function repoCwd() { + return workspaceCwd || REPO_ROOT; +} + // --- Shell helpers --- function run(cmd, cwd) { @@ -34,7 +51,7 @@ function run(cmd, cwd) { } async function gatherContext(cwd) { - cwd = cwd || REPO_ROOT; + cwd = cwd || repoCwd(); const authorCmd = isWindows ? 'git log --oneline -5 --format="%h %s" --author="$(git config user.name)"' : 'git log --oneline -5 --format="%h %s" --author="$(git config user.name)"'; @@ -605,7 +622,7 @@ async function startServer(instanceId, sessionRef, cwd, workspacePath) { } if (url.pathname === "/refresh" && req.method === "POST") { - const data = await gatherContext(cwd); + const data = await gatherContext(repoCwd()); contextCache.set(instanceId, data); await saveContext(workspacePath, data); broadcast(instanceId, data); @@ -672,7 +689,8 @@ const session = await joinSession({ name: "refresh", description: "Re-gather all git/project context and push updates to the canvas", handler: async (ctx) => { - const data = await gatherContext(REPO_ROOT); + captureCwd(ctx); + const data = await gatherContext(repoCwd()); contextCache.set(ctx.instanceId, data); if (sessionRef) await saveContext(sessionRef.workspacePath, data); broadcast(ctx.instanceId, data); @@ -713,16 +731,20 @@ const session = await joinSession({ }, ], open: async (ctx) => { + captureCwd(ctx); let entry = servers.get(ctx.instanceId); if (!entry) { - entry = await startServer(ctx.instanceId, sessionRef, REPO_ROOT, sessionRef?.workspacePath); + entry = await startServer(ctx.instanceId, sessionRef, repoCwd(), sessionRef?.workspacePath); servers.set(ctx.instanceId, entry); } - // Load persisted context or gather fresh + // Load persisted context or gather fresh. Re-gather when the + // saved context is missing or has no branch (e.g. it was saved + // before the working directory was known), so the board never + // opens stuck on a stale "detached HEAD". let data = await loadContext(sessionRef?.workspacePath); - if (!data) { - data = await gatherContext(REPO_ROOT); + if (!data || !data.branch) { + data = await gatherContext(repoCwd()); await saveContext(sessionRef?.workspacePath, data); } contextCache.set(ctx.instanceId, data);