diff --git a/.all-contributorsrc b/.all-contributorsrc index bf2e668c..53f76239 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3569,6 +3569,15 @@ "contributions": [ "content" ] + }, + { + "login": "thesurenk", + "name": "Suren K", + "avatar_url": "https://avatars.githubusercontent.com/u/902972?v=4", + "profile": "https://surenk.com", + "contributions": [ + "doc" + ] } ] } diff --git a/README.md b/README.md index c3c5e4c1..9c735c9f 100644 --- a/README.md +++ b/README.md @@ -535,6 +535,7 @@ Thanks goes to these wonderful people ([emoji key](./CONTRIBUTING.md#contributor
Lovy Jain

kimtth

Akash Dwivedi
+
Suren K
diff --git a/agents/workshop-ta.agent.md b/agents/workshop-ta.agent.md index 03738c6a..9dbaead7 100644 --- a/agents/workshop-ta.agent.md +++ b/agents/workshop-ta.agent.md @@ -106,14 +106,23 @@ Use `signal-write` when something needs the operator's attention: ### Viewing signals -If the Workshop's canvas extension (🪨 Cairn) is installed — it -ships bundled with the-workshop plugin — the operator can open a -live dashboard showing every desk's signals, score bars, and -escalations. The canvas reads `desks/*/.signals/` for the latest -signal JSON per desk. +The Workshop has a canvas extension — **🪨 Cairn** — that shows a live dashboard +of every desk's signals, score bars, and escalations. It reads +`desks/*/.signals/` for the latest signal JSON per desk. -Without the canvas, you can still read signals by scanning the -`.signals/` directories directly and summarizing for the operator. +The canvas does **not** auto-load when the plugin is installed. To see the live +board, install and register the `signals-dashboard` extension separately. If the +operator asks you to "run cairn" / "open the dashboard" and it isn't already +showing: + +1. Install the `signals-dashboard` canvas extension. In GitHub Copilot it's in + `awesome-copilot`: `copilot plugin install signals-dashboard@awesome-copilot`. + (It also ships in the the-workshop repo at + `.github/extensions/signals-dashboard/` for other setups.) +2. Open the **🪨 Cairn** canvas once it's registered. + +Without the canvas, you can still read signals by scanning the `.signals/` +directories directly and summarizing for the operator. ### Partnership signals diff --git a/cookbook/cookbook.yml b/cookbook/cookbook.yml index 0134ebb7..cc1542d9 100644 --- a/cookbook/cookbook.yml +++ b/cookbook/cookbook.yml @@ -103,3 +103,17 @@ cookbooks: - copilot-sdk - web-app - community + - id: copilot-sdk-java-examples + name: Copilot SDK Java Examples + description: A web-based chat application built with the GitHub Copilot Java SDK, Jetty, with auth status, JSON API, chat, and CLI connectivity examples + external: true + url: https://github.com/thesurenk/github-copilot-java-examples + author: + name: thesurenk + url: https://github.com/thesurenk + tags: + - java + - copilot-sdk + - web-app + - cli + - community diff --git a/docs/README.skills.md b/docs/README.skills.md index b7dcac28..3f7f8cad 100644 --- a/docs/README.skills.md +++ b/docs/README.skills.md @@ -40,6 +40,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to | [ai-prompt-engineering-safety-review](../skills/ai-prompt-engineering-safety-review/SKILL.md)
`gh skills install github/awesome-copilot ai-prompt-engineering-safety-review` | Comprehensive AI prompt engineering safety review and improvement prompt. Analyzes prompts for safety, bias, security vulnerabilities, and effectiveness while providing detailed improvement recommendations with extensive frameworks, testing methodologies, and educational content. | None | | [ai-ready](../skills/ai-ready/SKILL.md)
`gh skills install github/awesome-copilot ai-ready` | Make any repo AI-ready — analyzes your codebase and generates AGENTS.md, copilot-instructions.md, CI workflows, issue templates, and more. Mines your PR review patterns and creates files customized to your stack. USE THIS SKILL when the user asks to "make this repo ai-ready", "set up AI config", or "prepare this repo for AI contributions". | None | | [ai-team-orchestration](../skills/ai-team-orchestration/SKILL.md)
`gh skills install github/awesome-copilot ai-team-orchestration` | Bootstrap and run a multi-agent AI development team. Use when: starting a new software project with AI agents, setting up parallel dev/QA teams, creating sprint plans, writing brainstorm prompts with distinct agent voices, recovering a project workflow, or planning sprints. | `references/anti-patterns.md`
`references/brainstorm-format.md`
`references/project-brief-template.md`
`references/sprint-plan-template.md` | +| [anti-ui-slop](../skills/anti-ui-slop/SKILL.md)
`gh skills install github/awesome-copilot anti-ui-slop` | Stop Codex, GitHub Copilot, Claude Code, and Cursor from shipping generic UI. Use UIZZE’s public catalogue of 800,000+ real web and iOS screens to extract product-specific design decisions and enforce a hard finish gate for web and iOS interfaces. | None | | [appinsights-instrumentation](../skills/appinsights-instrumentation/SKILL.md)
`gh skills install github/awesome-copilot appinsights-instrumentation` | Instrument a webapp to send useful telemetry data to Azure App Insights | `LICENSE.txt`
`examples`
`references/ASPNETCORE.md`
`references/AUTO.md`
`references/NODEJS.md`
`references/PYTHON.md`
`scripts/appinsights.ps1` | | [apple-appstore-reviewer](../skills/apple-appstore-reviewer/SKILL.md)
`gh skills install github/awesome-copilot apple-appstore-reviewer` | Serves as a reviewer of the codebase with instructions on looking for Apple App Store optimizations or rejection reasons. | None | | [arch-linux-triage](../skills/arch-linux-triage/SKILL.md)
`gh skills install github/awesome-copilot arch-linux-triage` | Triage and resolve Arch Linux issues with pacman, systemd, and rolling-release best practices. | None | @@ -92,6 +93,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to | [cloud-design-patterns](../skills/cloud-design-patterns/SKILL.md)
`gh skills install github/awesome-copilot cloud-design-patterns` | Cloud design patterns for distributed systems architecture covering 42 industry-standard patterns across reliability, performance, messaging, security, and deployment categories. Use when designing, reviewing, or implementing distributed system architectures. | `references/architecture-design.md`
`references/azure-service-mappings.md`
`references/best-practices.md`
`references/deployment-operational.md`
`references/event-driven.md`
`references/messaging-integration.md`
`references/performance.md`
`references/reliability-resilience.md`
`references/security.md` | | [code-exemplars-blueprint-generator](../skills/code-exemplars-blueprint-generator/SKILL.md)
`gh skills install github/awesome-copilot code-exemplars-blueprint-generator` | Technology-agnostic prompt generator that creates customizable AI prompts for scanning codebases and identifying high-quality code exemplars. Supports multiple programming languages (.NET, Java, JavaScript, TypeScript, React, Angular, Python) with configurable analysis depth, categorization methods, and documentation formats to establish coding standards and maintain consistency across development teams. | None | | [code-tour](../skills/code-tour/SKILL.md)
`gh skills install github/awesome-copilot code-tour` | Use this skill to create CodeTour .tour files — persona-targeted, step-by-step walkthroughs that link to real files and line numbers. Trigger for: "create a tour", "make a code tour", "generate a tour", "onboarding tour", "tour for this PR", "tour for this bug", "RCA tour", "architecture tour", "explain how X works", "vibe check", "PR review tour", "contributor guide", "help someone ramp up", or any request for a structured walkthrough through code. Supports 20 developer personas (new joiner, bug fixer, architect, PR reviewer, vibecoder, security reviewer, and more), all CodeTour step types (file/line, selection, pattern, uri, commands, view), and tour-level fields (ref, isPrimary, nextTour). Works with any repository in any language. | `references/codetour-schema.json`
`references/examples.md`
`scripts/generate_from_docs.py`
`scripts/validate_tour.py` | +| [codebase-memory-mcp](../skills/codebase-memory-mcp/SKILL.md)
`gh skills install github/awesome-copilot codebase-memory-mcp` | Use when a configured codebase-memory-mcp server can assist with graph-backed code discovery, architecture orientation, symbol lookup, callers and callees, dependency or data-flow tracing, impact analysis, unfamiliar modules, or an explicit Codebase Memory request. | None | | [codeql](../skills/codeql/SKILL.md)
`gh skills install github/awesome-copilot codeql` | Comprehensive guide for setting up and configuring CodeQL code scanning via GitHub Actions workflows and the CodeQL CLI. This skill should be used when users need help with code scanning configuration, CodeQL workflow files, CodeQL CLI commands, SARIF output, security analysis setup, or troubleshooting CodeQL analysis. | `references/alert-management.md`
`references/cli-commands.md`
`references/compiled-languages.md`
`references/sarif-output.md`
`references/troubleshooting.md`
`references/workflow-configuration.md` | | [comment-code-generate-a-tutorial](../skills/comment-code-generate-a-tutorial/SKILL.md)
`gh skills install github/awesome-copilot comment-code-generate-a-tutorial` | Transform this Python script into a polished, beginner-friendly project by refactoring the code, adding clear instructional comments, and generating a complete markdown tutorial. | None | | [commit-message-storyteller](../skills/commit-message-storyteller/SKILL.md)
`gh skills install github/awesome-copilot commit-message-storyteller` | Analyzes git diffs or staged changes and generates narrative commit messages that explain WHY a change was made, not just what changed — following Conventional Commits format. Use when asked to "write a commit message", "generate a commit", "describe my changes", "what should I commit this as", "commit this", "summarize my diff", or "help me commit". Works with git diff output, staged files, or plain descriptions of changes. | `references/conventional-commits-guide.md` | @@ -226,6 +228,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to | [issue-fields-migration](../skills/issue-fields-migration/SKILL.md)
`gh skills install github/awesome-copilot issue-fields-migration` | Bulk-migrate metadata to GitHub issue fields from two sources: repo labels (e.g. priority labels to a Priority field) and Project V2 fields. Use when users say "migrate my labels to issue fields", "migrate project fields to issue fields", "convert labels to issue fields", "copy project field values to issue fields", or ask about adopting issue fields. Issue fields are org-level typed metadata (single select, text, number, date) that replace label-based workarounds with structured, searchable, cross-repo fields. | `references/issue-fields-api.md`
`references/labels-api.md`
`references/projects-api.md` | | [java-add-graalvm-native-image-support](../skills/java-add-graalvm-native-image-support/SKILL.md)
`gh skills install github/awesome-copilot java-add-graalvm-native-image-support` | GraalVM Native Image expert that adds native image support to Java applications, builds the project, analyzes build errors, applies fixes, and iterates until successful compilation using Oracle best practices. | None | | [java-docs](../skills/java-docs/SKILL.md)
`gh skills install github/awesome-copilot java-docs` | Ensure that Java types are documented with Javadoc comments and follow best practices for documentation. | None | +| [java-helidon](../skills/java-helidon/SKILL.md)
`gh skills install github/awesome-copilot java-helidon` | Get best practices for developing applications with Helidon 4 (SE and MP). Use when working with Helidon SE or Helidon MP, HttpService routing, Helidon DB Client, MicroProfile Config, Helidon Security, or Helidon testing in Java 21+ projects. | None | | [java-junit](../skills/java-junit/SKILL.md)
`gh skills install github/awesome-copilot java-junit` | Get best practices for JUnit 5 unit testing, including data-driven tests | None | | [java-mcp-server-generator](../skills/java-mcp-server-generator/SKILL.md)
`gh skills install github/awesome-copilot java-mcp-server-generator` | Generate a complete Model Context Protocol server project in Java using the official MCP Java SDK with reactive streams and optional Spring Boot integration. | None | | [java-refactoring-extract-method](../skills/java-refactoring-extract-method/SKILL.md)
`gh skills install github/awesome-copilot java-refactoring-extract-method` | Refactoring using Extract Methods in Java Language | None | @@ -240,6 +243,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to | [lsp-setup](../skills/lsp-setup/SKILL.md)
`gh skills install github/awesome-copilot lsp-setup` | Enable code intelligence (go-to-definition, find-references, hover, type info) for any programming language by installing and configuring an LSP server for Copilot CLI. Detects the OS, installs the right server, and generates the JSON configuration (user-level or repo-level). Use when you need deeper code understanding and no LSP server is configured, or when the user asks to set up, install, or configure an LSP server. | `references/lsp-servers.md` | | [make-repo-contribution](../skills/make-repo-contribution/SKILL.md)
`gh skills install github/awesome-copilot make-repo-contribution` | All changes to code must follow the guidance documented in the repository. Before any issue is filed, branch is made, commits generated, or pull request (or PR) created, a search must be done to ensure the right steps are followed. Whenever asked to create an issue, commit messages, to push code, or create a PR, use this skill so everything is done correctly. | `assets/issue-template.md`
`assets/pr-template.md` | | [markdown-to-html](../skills/markdown-to-html/SKILL.md)
`gh skills install github/awesome-copilot markdown-to-html` | Convert Markdown files to HTML similar to `marked.js`, `pandoc`, `gomarkdown/markdown`, or similar tools; or writing custom script to convert markdown to html and/or working on web template systems like `jekyll/jekyll`, `gohugoio/hugo`, or similar web templating systems that utilize markdown documents, converting them to html. Use when asked to "convert markdown to html", "transform md to html", "render markdown", "generate html from markdown", or when working with .md files and/or web a templating system that converts markdown to HTML output. Supports CLI and Node.js workflows with GFM, CommonMark, and standard Markdown flavors. | `references/basic-markdown-to-html.md`
`references/basic-markdown.md`
`references/code-blocks-to-html.md`
`references/code-blocks.md`
`references/collapsed-sections-to-html.md`
`references/collapsed-sections.md`
`references/gomarkdown.md`
`references/hugo.md`
`references/jekyll.md`
`references/marked.md`
`references/pandoc.md`
`references/tables-to-html.md`
`references/tables.md`
`references/writing-mathematical-expressions-to-html.md`
`references/writing-mathematical-expressions.md` | +| [markstream-install](../skills/markstream-install/SKILL.md)
`gh skills install github/awesome-copilot markstream-install` | Install and configure Markstream streaming Markdown renderers for Vue, React, Svelte, Angular, Nuxt, and Vue 2 applications. Use for package selection, minimal peer dependencies, CSS order, SSR boundaries, streaming mode, and renderer setup. | `references/scenarios.md` | | [mcp-cli](../skills/mcp-cli/SKILL.md)
`gh skills install github/awesome-copilot mcp-cli` | Interface for MCP (Model Context Protocol) servers via CLI. Use when you need to interact with external tools, APIs, or data sources through MCP servers, list available MCP servers/tools, or call MCP tools from command line. | None | | [mcp-copilot-studio-server-generator](../skills/mcp-copilot-studio-server-generator/SKILL.md)
`gh skills install github/awesome-copilot mcp-copilot-studio-server-generator` | Generate a complete MCP server implementation optimized for Copilot Studio integration with proper schema constraints and streamable HTTP support | None | | [mcp-create-adaptive-cards](../skills/mcp-create-adaptive-cards/SKILL.md)
`gh skills install github/awesome-copilot mcp-create-adaptive-cards` | Skill converted from mcp-create-adaptive-cards.prompt.md | None | @@ -397,6 +401,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to | [update-markdown-file-index](../skills/update-markdown-file-index/SKILL.md)
`gh skills install github/awesome-copilot update-markdown-file-index` | Update a markdown file section with an index/table of files from a specified folder. | None | | [update-specification](../skills/update-specification/SKILL.md)
`gh skills install github/awesome-copilot update-specification` | Update an existing specification file for the solution, optimized for Generative AI consumption based on new requirements or updates to any existing code. | None | | [vardoger-analyze](../skills/vardoger-analyze/SKILL.md)
`gh skills install github/awesome-copilot vardoger-analyze` | Use when the user asks to personalize the GitHub Copilot CLI assistant, adapt Copilot to their style, use vardoger, or analyze their Copilot CLI conversation history. Reads the local session directory at `~/.copilot/session-state/`, extracts recurring preferences and conventions, and writes a fenced personalization block into `~/.copilot/copilot-instructions.md`. Runs entirely on the user's machine via the local `vardoger` CLI (`pipx install vardoger`); no network calls and no uploads. Triggers: 'personalize my copilot', 'analyze my copilot history', 'tailor copilot to me', 'run vardoger', 'update my copilot instructions from history', 'make copilot learn my style'. | None | +| [vcpkg](../skills/vcpkg/SKILL.md)
`gh skills install github/awesome-copilot vcpkg` | Guide for setting up vcpkg in C++ projects, managing dependency versions, and cross-compiling. Covers manifest initialization, CMake and Visual Studio integration, classic-to-manifest migration, version pinning, baselines, overrides, triplets, and cross-compilation. Use when a user is working with vcpkg project setup, installation, version management, or cross-platform builds. For specialized tasks, additional references cover custom registries and overlay ports (references/registries.md), CI/CD and binary caching (references/ci.md), and troubleshooting and dependency lifecycle (references/troubleshooting.md). | `references/ci.md`
`references/registries.md`
`references/troubleshooting.md` | | [vscode-ext-commands](../skills/vscode-ext-commands/SKILL.md)
`gh skills install github/awesome-copilot vscode-ext-commands` | Guidelines for contributing commands in VS Code extensions. Indicates naming convention, visibility, localization and other relevant attributes, following VS Code extension development guidelines, libraries and good practices | None | | [vscode-ext-localization](../skills/vscode-ext-localization/SKILL.md)
`gh skills install github/awesome-copilot vscode-ext-localization` | Guidelines for proper localization of VS Code extensions, following VS Code extension development guidelines, libraries and good practices | None | | [web-design-reviewer](../skills/web-design-reviewer/SKILL.md)
`gh skills install github/awesome-copilot web-design-reviewer` | This skill enables visual inspection of websites running locally or remotely to identify and fix design issues. Triggers on requests like "review website design", "check the UI", "fix the layout", "find design problems". Detects issues with responsive design, accessibility, visual consistency, and layout breakage, then performs fixes at the source code level. | `references/framework-fixes.md`
`references/visual-checklist.md` | diff --git a/extensions/signals-dashboard/extension.mjs b/extensions/signals-dashboard/extension.mjs index 65d043a0..f45b6431 100644 --- a/extensions/signals-dashboard/extension.mjs +++ b/extensions/signals-dashboard/extension.mjs @@ -4,8 +4,11 @@ // Supports stashing desks (48hr hold) and restoring them. import { createServer } from "node:http"; +import { statSync, accessSync, realpathSync, constants as fsConstants } from "node:fs"; import { readdir, readFile, writeFile, stat } from "node:fs/promises"; -import { join } from "node:path"; +import { join, delimiter, sep } from "node:path"; +import { spawn } from "node:child_process"; +import { randomBytes } from "node:crypto"; import { joinSession, createCanvas } from "@github/copilot-sdk/extension"; const servers = new Map(); @@ -31,6 +34,198 @@ function isValidDeskName(name) { name !== "." && name !== ".."; } +// Launch a desk as an in-place Copilot CLI session — the canvas counterpart to +// WorkshopRoom's ConsoleLauncher. A desk is a seat that independent sessions +// pick up over time, so "open" starts a fresh copilot in the desk's own folder, +// oriented to read the journal and continue. This keeps every desk inside the +// one workshop repo (coordinated through journals + .signals + Cairn) instead +// of spinning off an isolated worktree elsewhere on disk. +// +// deskPath has already been confirmed to exist by the caller. Before launching +// we re-resolve it with realpath and require it to stay inside the workshop root +// (isInsideRoot), which defeats a planted desks/foo -> /outside symlink; the +// path is then only ever passed as a spawn cwd, an argv element, or a +// single-quoted literal inside the macOS Terminal command — never concatenated +// raw onto a command line — so no character filtering of the path is required. +function deskOrientPrompt(deskName) { + return `You are sitting down at the ${deskName} desk in this workshop. ` + + `Read journal.md in this folder first to pick up where the last session ` + + `left off, then continue the desk's work. Write your journal before you stop.`; +} + +// Spawn detached and resolve true only once the OS confirms the process +// started ('spawn'), false on failure ('error', e.g. the binary is missing) so +// the caller can fall back. Node guarantees exactly one of those events fires +// for a spawn attempt, so we resolve solely from them — no timeout that could +// report an unconfirmed launch as success and skip the clipboard fallback. +function trySpawn(cmd, args, opts = {}) { + return new Promise((resolve) => { + let settled = false; + const done = (v, child) => { + if (settled) return; + settled = true; + if (v && child) { try { child.unref(); } catch {} } + resolve(v); + }; + try { + const child = spawn(cmd, args, { detached: true, stdio: "ignore", ...opts }); + child.on("error", () => done(false)); + child.on("spawn", () => done(true, child)); + } catch { resolve(false); } + }); +} + +// Resolve an executable on PATH (honoring PATHEXT on Windows), mirroring +// WorkshopRoom's AgentClis.IsOnPath. Used to prefer Agency when the machine has +// it installed, falling back to vanilla Copilot. +// A PATH hit only counts if it resolves to a real, runnable file. existsSync +// alone would treat a directory or a non-executable file named `agency` as a +// match, so auto-detection would pick the wrapper and the terminal would then +// fail to run it with no fallback. +function isExecutableFile(p) { + try { + if (!statSync(p).isFile()) return false; + if (process.platform !== "win32") accessSync(p, fsConstants.X_OK); + return true; + } catch { return false; } +} + +function isOnPath(command) { + try { + const dirs = (process.env.PATH || "").split(delimiter); + const exts = process.platform === "win32" + ? (process.env.PATHEXT || ".EXE;.CMD;.BAT").split(";").filter(Boolean) + : []; + for (const dir of dirs) { + if (!dir) continue; + // On Windows only a PATHEXT match is runnable; on POSIX check the bare + // name, and isExecutableFile confirms the execute bit either way. + if (exts.length) { + for (const ext of exts) if (isExecutableFile(join(dir, command + ext))) return true; + } else if (isExecutableFile(join(dir, command))) { + return true; + } + } + } catch {} + return false; +} + +// The agent argv a desk opens with. Default: prefer Agency (the internal +// wrapper around Copilot) when it's installed, so a desk comes up with its +// MCPs/plugin already configured instead of bare GHCP; otherwise vanilla +// Copilot. Agency can't take Copilot's --name (it clashes with Agency's own +// --resume), matching AgentClis. Override with WORKSHOP_DESK_AGENT=copilot to +// force vanilla, or =agency to insist on the wrapper. +function deskAgentArgv(deskName) { + const pref = (process.env.WORKSHOP_DESK_AGENT || "").trim().toLowerCase(); + // An explicit override is authoritative: =agency insists on the wrapper even + // when it isn't detected on PATH, and =copilot forces vanilla. Only when the + // override is unset do we auto-detect and prefer Agency if it's installed. + const useAgency = pref === "agency" ? true + : pref === "copilot" ? false + : isOnPath("agency"); + return useAgency ? ["agency", "copilot"] : ["copilot", "--name", deskName]; +} + +// A desk name flows onto a command line, and on the no-wt Windows fallback +// through cmd.exe. isValidDeskName still allows shell metacharacters such as +// & | > % ^, so the launcher additionally requires a conservative slug before +// any shell can see the name; anything else refuses to launch and the caller +// falls back to copying the path. Combined with the quote-free orientation +// prompt, no untrusted text ever reaches a shell parser. +function isSafeDeskNameForLaunch(name) { + return isValidDeskName(name) && /^[A-Za-z0-9._-]+$/.test(name); +} + +// POSIX single-quote a value for the macOS `do script` command line, escaping +// any embedded single quotes. +function shSingleQuote(s) { + return "'" + String(s).replace(/'/g, "'\\''") + "'"; +} + +// AppleScript string literal: escape backslashes, double quotes, and line breaks +// (a raw CR/LF in a path would otherwise terminate the literal and fail to +// compile, while osascript still spawns and trySpawn would report success). +function osaStringLiteral(s) { + return '"' + String(s) + .replace(/\\/g, "\\\\") + .replace(/"/g, '\\"') + .replace(/\r/g, "\\r") + .replace(/\n/g, "\\n") + '"'; +} + +// Resolve symlinks on both sides and confirm the target is the workshop root +// itself or lives beneath it. The callers locate a desk with stat(), which +// follows symlinks, so a committed desks/foo -> /outside symlink would otherwise +// launch the agent with an external working directory, breaking the inside-repo +// guarantee. +function isInsideRoot(root, target) { + try { + const r = realpathSync(root); + const t = realpathSync(target); + if (t === r) return true; + // A filesystem root ("/" or "C:\") already ends with the separator, so + // don't append a second one or every desk below it would fail the prefix + // test and opening would always fall back. + const prefix = r.endsWith(sep) ? r : r + sep; + return t.startsWith(prefix); + } catch { return false; } +} + +async function launchDeskConsole(deskPath, deskName, workshopDir) { + // deskName must be a plain slug so it is safe on every command line and shell + // below, and the resolved desk must still live inside the workshop root + // (which defeats a symlinked desk that escapes the repo). deskPath itself is + // only ever passed as an argv element / spawn cwd (Windows via -d plus cwd, + // Linux via cwd) or as a single-quoted literal inside the macOS Terminal + // command, so an empty-path guard is all that is needed — a quote in the path + // can't break out of any of those. + if (!deskPath) return false; + if (!isSafeDeskNameForLaunch(deskName)) return false; + if (!isInsideRoot(workshopDir, deskPath)) return false; + const run = [...deskAgentArgv(deskName), "-i", deskOrientPrompt(deskName)]; + if (process.platform === "win32") { + // Run the agent through cmd.exe (/k) so PATHEXT is applied: globally + // installed CLIs like `copilot`/`agency` are usually .cmd shims that + // Windows Terminal or a bare CreateProcess would fail to launch (they + // expect a literal executable, not a PATHEXT name). Windows Terminal is a + // GUI app, so it still surfaces its own window from the windowless host. + // Each element of run is its own argv token — deskName is a slug and the + // orientation prompt has no cmd metacharacters — and the desk path is + // passed via -d/cwd, so nothing untrusted is reparsed by a shell. + if (await trySpawn("wt.exe", ["-d", deskPath, "cmd", "/k", ...run])) return true; + // Fallback when wt.exe is absent: a fresh console window via `start`, + // still through cmd /k for the same PATHEXT resolution. + return await trySpawn("cmd.exe", ["/c", "start", "", "cmd", "/k", ...run], { cwd: deskPath }); + } + if (process.platform === "darwin") { + // macOS: `open` can't inject a command, so drive Terminal via AppleScript + // to cd into the desk and exec the agent. Each argv element is POSIX + // single-quoted so the shell can't reinterpret it, and osascript itself + // is spawned via argv (no shell). + const line = "cd " + shSingleQuote(deskPath) + " && exec " + + run.map(shSingleQuote).join(" "); + const script = 'tell application "Terminal"\n' + + " activate\n" + + " do script " + osaStringLiteral(line) + "\n" + + "end tell"; + return await trySpawn("osascript", ["-e", script]); + } + // Linux/other: best-effort across common terminal emulators. Each is spawned + // via argv (no shell) with the agent command after the emulator's exec flag, + // so the desk actually comes up running its agent instead of a bare shell. + const linuxTerms = [ + ["x-terminal-emulator", ["-e", ...run]], + ["gnome-terminal", ["--", ...run]], + ["konsole", ["-e", ...run]], + ["xterm", ["-e", ...run]], + ]; + for (const [term, args] of linuxTerms) { + if (await trySpawn(term, args, { cwd: deskPath })) return true; + } + return false; +} + // Signal JSON is agent-produced and unvalidated. Coerce numeric fields before // they reach the renderer so a nonnumeric value cannot inject markup or break // layout. toScore clamps self-assessment/quality scores to 0..max; toCount @@ -75,6 +270,24 @@ function isCrossSiteRequest(req) { return site === "cross-site" || site === "same-site"; } +// Pin the Host header to the exact loopback authority we bound. A DNS-rebinding +// page reaches us under its own hostname (Host: attacker.example:), so an +// exact match against 127.0.0.1: refuses those requests before any state +// change — Origin/Host equality alone doesn't, since the attacker controls both. +function isCanonicalHost(req, canonicalHost) { + return String(req.headers.host || "").toLowerCase() === String(canonicalHost || "").toLowerCase(); +} + +// Capability check for the per-server token minted at startup and embedded in +// the page we serve. Only the loopback document we rendered knows it, so a blind +// cross-origin/rebinding caller can't forge a mutating request even if it +// reached the socket. +function hasCapabilityToken(req, token) { + const header = req.headers["x-workshop-token"]; + const provided = Array.isArray(header) ? header[0] : header; + return typeof provided === "string" && provided.length > 0 && provided === token; +} + // --- Stash management --- async function readStash(workshopDir) { @@ -399,11 +612,11 @@ function renderSignalCard(sig) { const openBtnStyle = isEscalation ? "background:#7f1d1d;border:1px solid #dc2626;color:#fca5a5;padding:2px 10px;border-radius:4px;font-size:11px;cursor:pointer;font-weight:600;transition:all .15s;" : "background:none;border:1px solid #1e3a5f;color:#7dd3fc;padding:2px 8px;border-radius:4px;font-size:11px;cursor:pointer;transition:all .15s;"; - const openBtn = noSignal ? "" : ``; + title="Open this desk as a Copilot CLI session in its folder">open`; let escalationBlock = ""; if (isEscalation && sig.escalationReason) { @@ -531,7 +744,7 @@ function renderStashedCard(entry) { `; } -function renderDashboard(signals, stashed) { +function renderDashboard(signals, stashed, capabilityToken) { const activeSignals = sortSignals(signals.filter(s => !stashed.some(e => e.name === s.deskName))); const cards = activeSignals.length > 0 @@ -603,12 +816,17 @@ function renderDashboard(signals, stashed) {