@opengsd/gsd-core 1.7.0-rc.5 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-executor.md +2 -1
- package/agents/gsd-security-auditor.md +13 -15
- package/agents/gsd-ui-checker.md +2 -0
- package/agents/gsd-ui-researcher.md +1 -0
- package/bin/install.js +975 -196
- package/commands/gsd/mempalace-capture.md +27 -1
- package/commands/gsd/surface.md +6 -6
- package/gsd-core/bin/gsd-tools.cjs +63 -2
- package/gsd-core/bin/lib/api-coverage.cjs +3 -4
- package/gsd-core/bin/lib/audit.cjs +7 -6
- package/gsd-core/bin/lib/capability-registry.cjs +503 -87
- package/gsd-core/bin/lib/capability-validator.cjs +56 -18
- package/gsd-core/bin/lib/check-command-router.cjs +1 -1
- package/gsd-core/bin/lib/clock.cjs +19 -0
- package/gsd-core/bin/lib/commands.cjs +48 -9
- package/gsd-core/bin/lib/config-loader.cjs +6 -2
- package/gsd-core/bin/lib/config.cjs +12 -0
- package/gsd-core/bin/lib/core-utils.cjs +8 -2
- package/gsd-core/bin/lib/drift.cjs +4 -4
- package/gsd-core/bin/lib/frontmatter.cjs +22 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +2 -1
- package/gsd-core/bin/lib/host-integration.cjs +33 -8
- package/gsd-core/bin/lib/init.cjs +60 -53
- package/gsd-core/bin/lib/install-engine.cjs +93 -22
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +2 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +342 -0
- package/gsd-core/bin/lib/markdown-table.cjs +698 -0
- package/gsd-core/bin/lib/mcp-server.cjs +18 -7
- package/gsd-core/bin/lib/milestone.cjs +217 -31
- package/gsd-core/bin/lib/phase-command-router.cjs +50 -2
- package/gsd-core/bin/lib/phase-lifecycle.cjs +62 -36
- package/gsd-core/bin/lib/phase-locator.cjs +23 -2
- package/gsd-core/bin/lib/phase.cjs +436 -61
- package/gsd-core/bin/lib/plan-scan.cjs +3 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +24 -7
- package/gsd-core/bin/lib/roadmap-parser.cjs +218 -13
- package/gsd-core/bin/lib/roadmap.cjs +100 -49
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +242 -44
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +24 -14
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +19 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +22 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +526 -29
- package/gsd-core/bin/lib/runtime-name-policy.cjs +63 -5
- package/gsd-core/bin/lib/schema-detect.cjs +2 -1
- package/gsd-core/bin/lib/security.cjs +7 -37
- package/gsd-core/bin/lib/shell-command-projection.cjs +176 -27
- package/gsd-core/bin/lib/smart-entry.cjs +4 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +30 -10
- package/gsd-core/bin/lib/state-transition.cjs +100 -45
- package/gsd-core/bin/lib/state.cjs +391 -126
- package/gsd-core/bin/lib/surface.cjs +12 -8
- package/gsd-core/bin/lib/template.cjs +2 -1
- package/gsd-core/bin/lib/uat.cjs +54 -8
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +249 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +23 -1
- package/gsd-core/bin/lib/verify.cjs +4 -3
- package/gsd-core/bin/lib/workstream.cjs +3 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +1 -1
- package/gsd-core/bin/lib/write-set.cjs +38 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -3
- package/gsd-core/references/checkpoints.md +12 -0
- package/gsd-core/references/ui-consideration-probe.md +73 -0
- package/gsd-core/templates/UI-SPEC.md +25 -0
- package/gsd-core/templates/VALIDATION.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +7 -4
- package/gsd-core/workflows/debug.md +2 -0
- package/gsd-core/workflows/execute-phase.md +5 -3
- package/gsd-core/workflows/fast.md +8 -22
- package/gsd-core/workflows/plan-phase.md +6 -0
- package/gsd-core/workflows/progress.md +2 -2
- package/gsd-core/workflows/quick.md +2 -0
- package/gsd-core/workflows/review.md +42 -3
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +7 -4
- package/gsd-core/workflows/ship.md +8 -2
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +146 -1
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/hooks/dist/gsd-statusline.js +164 -14
- package/hooks/dist/gsd-windsurf-pre-command.js +275 -0
- package/hooks/dist/gsd-windsurf-pre-write.js +132 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-statusline.js +164 -14
- package/hooks/gsd-windsurf-pre-command.js +275 -0
- package/hooks/gsd-windsurf-pre-write.js +132 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +10 -4
- package/pi/gsd.cjs +354 -0
- package/scripts/build-hooks.js +3 -0
- package/scripts/ci-test-scope.cjs +39 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +35 -35
- package/scripts/gen-install-tree-fixtures.cjs +75 -0
- package/scripts/gen-registry.cjs +128 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-table-schema-drift.cjs +157 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/registry-schema.cjs +565 -0
- package/scripts/validate-registry.cjs +117 -0
- package/skills/gsd-mempalace-capture/SKILL.md +27 -1
- package/skills/gsd-surface/SKILL.md +6 -6
- package/vscode/browser.js +197 -0
- package/vscode/extension.js +383 -0
- package/vscode/host-binding.js +113 -0
- package/vscode/package.json +96 -0
|
@@ -55,7 +55,33 @@ This step is `onError: skip` at `discuss:post` / `plan:post` / `verify:post` --
|
|
|
55
55
|
On any error or timeout, stop and let the phase continue -- capture is best-effort.
|
|
56
56
|
|
|
57
57
|
1. **Dedup first.** Interactive: `mempalace_check_duplicate` on the artifact's deterministic drawer id. Headless: rely on `mempalace mine`'s content-hash idempotency.
|
|
58
|
-
2. **Add the drawer (verbatim).** File the exact artifact text into `room: <room>` of `wing: <wing>` with provenance (`source_file`, phase id). Interactive: `mempalace_add_drawer`. Headless:
|
|
58
|
+
2. **Add the drawer (verbatim).** File the exact artifact text into `room: <room>` of `wing: <wing>` with provenance (`source_file`, phase id). Interactive: `mempalace_add_drawer`. Headless: see below.
|
|
59
|
+
|
|
60
|
+
**`mempalace mine` has no `--room` flag** — only `search` accepts `--room` ([CLI reference: https://mempalaceofficial.com/reference/cli.html](https://mempalaceofficial.com/reference/cli.html)). Room assignment is driven by `detect_room()` matching folder-path segments against the `rooms:` list in `mempalace.yaml` ([mining guide: https://mempalaceofficial.com/guide/mining.html](https://mempalaceofficial.com/guide/mining.html) — "Rooms are auto-detected from your folder structure"; [config reference: https://mempalaceofficial.com/guide/configuration.html](https://mempalaceofficial.com/guide/configuration.html)). Stage the artifact under a room-named folder so `detect_room()` assigns it correctly:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
STAGE=".planning/.mempalace-stage"
|
|
64
|
+
# One-time: declare the GSD room taxonomy so detect_room() recognizes these folders
|
|
65
|
+
mkdir -p "$STAGE"
|
|
66
|
+
[ -f "$STAGE/mempalace.yaml" ] || cat > "$STAGE/mempalace.yaml" <<'YAML'
|
|
67
|
+
rooms:
|
|
68
|
+
- decisions
|
|
69
|
+
- planning
|
|
70
|
+
- milestones
|
|
71
|
+
- problems
|
|
72
|
+
- general
|
|
73
|
+
YAML
|
|
74
|
+
# Suppress MemPalace cache artifacts written into the scanned tree
|
|
75
|
+
[ -f "$STAGE/.gitignore" ] || echo "mempalace_embedder.json" > "$STAGE/.gitignore"
|
|
76
|
+
# Stage under <room>/<phase-id>/<basename> — stable path so mine's content-hash
|
|
77
|
+
# idempotency (file_already_mined keys on absolute source_file + mtime) deduplicates
|
|
78
|
+
# instead of creating duplicate drawers on re-runs.
|
|
79
|
+
ROOM_DIR="$STAGE/<room>/<phase-id>"
|
|
80
|
+
mkdir -p "$ROOM_DIR"
|
|
81
|
+
cp "<artifact-path>" "$ROOM_DIR/<basename>"
|
|
82
|
+
# Mine with --wing only — no --room flag; detect_room() assigns from the folder path
|
|
83
|
+
mempalace mine "$STAGE" --wing <wing>
|
|
84
|
+
```
|
|
59
85
|
3. **Mirror KG facts** when `config.mempalace.mirror_kg` is true: extract decision/delivery facts and `mempalace_kg_add` them with `valid_from` = the phase date (e.g. `(<project>, decided, <decision>)` from CONTEXT; `(<phase>, delivered, <capability>)` from SUMMARY). Under `augment` these are an *additive* mirror of GSD's native `.planning/graphs/`. Under `kg_backend`/`replace` the palace KG is the *authoritative* fact store — GSD still produces `.planning/graphs/` through its normal graphify, so an unreachable palace never loses a fact.
|
|
60
86
|
4. Re-running a phase MUST NOT create duplicate drawers (deterministic ids + `check_duplicate`).
|
|
61
87
|
|
|
@@ -37,9 +37,9 @@ Parse the first token of $ARGUMENTS:
|
|
|
37
37
|
## list / status
|
|
38
38
|
|
|
39
39
|
Load the capability registry and call `listSurface(runtimeConfigDir, manifest, CLUSTERS, registry)` from
|
|
40
|
-
|
|
40
|
+
the engine module at `${runtimeConfigDir}/gsd-core/bin/lib/surface.cjs`. The registry is loaded via:
|
|
41
41
|
```js
|
|
42
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
42
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
43
43
|
```
|
|
44
44
|
Display:
|
|
45
45
|
|
|
@@ -71,7 +71,7 @@ Install profile: standard (from .gsd-profile)
|
|
|
71
71
|
3. `writeSurface(runtimeConfigDir, surfaceState)`.
|
|
72
72
|
4. Resolve and re-apply:
|
|
73
73
|
```js
|
|
74
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
74
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
75
75
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
76
76
|
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
77
77
|
```
|
|
@@ -89,7 +89,7 @@ Valid cluster names: `core_loop`, `audit_review`, `milestone`, `research_ideate`
|
|
|
89
89
|
3. Add cluster to `surfaceState.disabledClusters` (deduplicate).
|
|
90
90
|
4. `writeSurface` → resolve layout → `applySurface`:
|
|
91
91
|
```js
|
|
92
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
92
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
93
93
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
94
94
|
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
95
95
|
```
|
|
@@ -103,7 +103,7 @@ Valid cluster names: `core_loop`, `audit_review`, `milestone`, `research_ideate`
|
|
|
103
103
|
2. Remove cluster from `surfaceState.disabledClusters`.
|
|
104
104
|
3. `writeSurface` → resolve layout → `applySurface`:
|
|
105
105
|
```js
|
|
106
|
-
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
106
|
+
const registry = require(runtimeConfigDir + '/gsd-core/bin/lib/capability-registry.cjs');
|
|
107
107
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
108
108
|
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
109
109
|
```
|
|
@@ -151,7 +151,7 @@ All paths can be overridden by reading the `CLAUDE_CONFIG_DIR` env var if set.
|
|
|
151
151
|
|
|
152
152
|
- Unknown cluster name → list valid cluster names, exit without writing.
|
|
153
153
|
- Unknown profile name → list known profiles (`core`, `standard`, `full`), exit.
|
|
154
|
-
- Missing `surface.cjs` → prompt: "Run `npm i -g gsd-core` to reinstall GSD."
|
|
154
|
+
- Missing `surface.cjs` → prompt: "Run `npm i -g @opengsd/gsd-core` to reinstall GSD."
|
|
155
155
|
|
|
156
156
|
<execution_context>
|
|
157
157
|
Surface state file: `~/.claude/.gsd-surface.json`
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GSD extension for VS Code — WEB (browser) entry, #2103.
|
|
5
|
+
*
|
|
6
|
+
* This is the `browser` entry point (vscode/package.json `"browser": "./browser.js"`),
|
|
7
|
+
* loaded by VS Code Web / vscode.dev in a webworker context. It has ZERO Node
|
|
8
|
+
* APIs: no `require('fs')`, `require('path')`, or `require('child_process')`,
|
|
9
|
+
* and no Node globals (`process`, `Buffer`, `__dirname`, `__filename`). This is
|
|
10
|
+
* a HARD constraint, not a style preference — a Web Extension host does not
|
|
11
|
+
* have Node's core modules available at all; requiring one throws immediately
|
|
12
|
+
* at load time and breaks activation.
|
|
13
|
+
*
|
|
14
|
+
* WHY THIS FILE DOES NOT REQUIRE `./host-binding.js` OR `./extension.js`
|
|
15
|
+
* (a deliberate deviation from "compose the seams via bindGsdToVscode" — see
|
|
16
|
+
* the #2103 dispatch-crux note below):
|
|
17
|
+
*
|
|
18
|
+
* `host-binding.js`'s `bindGsdToVscode` is NOT actually web-safe once its
|
|
19
|
+
* transitive dependencies are checked — three of its four required engine-lib
|
|
20
|
+
* modules pull in Node's `fs`/`os`/`path` at module-load time (eagerly, on
|
|
21
|
+
* every `require()`, regardless of which code path runs):
|
|
22
|
+
* - gsd-core/bin/lib/state-io.cjs → requires 'node:fs' directly.
|
|
23
|
+
* - gsd-core/bin/lib/adapter-imperative.cjs → requires install-engine.cjs +
|
|
24
|
+
* capability-loader.cjs (fs/os/path).
|
|
25
|
+
* - gsd-core/bin/lib/model-adapter.cjs → requires model-resolver.cjs →
|
|
26
|
+
* config-loader.cjs (fs/os/path) +
|
|
27
|
+
* configuration.cjs (fs/path).
|
|
28
|
+
* (gsd-core/bin/lib/hook-bus.cjs alone has no requires and is genuinely
|
|
29
|
+
* web-safe.) Requiring `host-binding.js` here would transitively pull in
|
|
30
|
+
* `node:fs` and throw at web-worker load time — the opposite of "zero Node
|
|
31
|
+
* APIs". See host-binding.js's own header comment for the full chain. Fixing
|
|
32
|
+
* those engine-lib modules to be fs-free is a separate, much larger
|
|
33
|
+
* engine-wide refactor (config/capability loading genuinely reads files from
|
|
34
|
+
* disk for every OTHER host) — out of scope here; flagged rather than routed
|
|
35
|
+
* around silently.
|
|
36
|
+
*
|
|
37
|
+
* So this file implements its OWN minimal, independently-verified-zero-Node-API
|
|
38
|
+
* composition directly against `vscode.lm` — no engine-lib requires at all.
|
|
39
|
+
*
|
|
40
|
+
* DISPATCH STORY ON WEB (per the #2103 dispatch-crux design): full GSD engine
|
|
41
|
+
* dispatch (the gsd-tools.cjs subprocess-shim `dispatchGsdCommand` used by the
|
|
42
|
+
* desktop `extension.js`) is fundamentally a Node `child_process.spawnSync`
|
|
43
|
+
* call — there is no web-worker equivalent. On web, GSD command dispatch is
|
|
44
|
+
* available through VS Code's NATIVE MCP client connecting to the GSD
|
|
45
|
+
* companion MCP server (gsd-core/bin/lib/mcp-server.cjs, `gsd-mcp-server`
|
|
46
|
+
* bin entry — a separate, already-existing surface; this file does NOT
|
|
47
|
+
* implement an MCP client itself, it only points the user at that story).
|
|
48
|
+
* The chat participant and Language Model Tools registered below are
|
|
49
|
+
* therefore intentionally limited on web: they register (so the surface is
|
|
50
|
+
* discoverable and `#runSubagent`-eligible per VS Code's chat engine) but
|
|
51
|
+
* their handlers return an honest "configure the GSD MCP server for full
|
|
52
|
+
* dispatch on web" message rather than silently failing or faking success.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Tokenizes a raw chat/free-form prompt string. Kept local (not shared with
|
|
57
|
+
* extension.js) so this file has zero requires of any kind beyond `vscode`.
|
|
58
|
+
* @param {string} rawPrompt
|
|
59
|
+
* @returns {{family: string, subcommand: string|undefined, args: string[]}}
|
|
60
|
+
*/
|
|
61
|
+
function parseChatPrompt(rawPrompt) {
|
|
62
|
+
const tokens = String(rawPrompt || '').trim().split(/\s+/).filter(Boolean);
|
|
63
|
+
return {
|
|
64
|
+
family: tokens[0] || '--help',
|
|
65
|
+
subcommand: tokens[1],
|
|
66
|
+
args: tokens.slice(2),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The honest "web mode" message every web-surface handler returns instead of
|
|
72
|
+
* attempting Node-only engine dispatch.
|
|
73
|
+
* @param {string} family
|
|
74
|
+
*/
|
|
75
|
+
function webDispatchUnavailableMessage(family) {
|
|
76
|
+
return (
|
|
77
|
+
`GSD web mode: full engine dispatch for "${family}" is not available in the browser ` +
|
|
78
|
+
'(the VS Code Web/webworker host has no Node runtime, so the gsd-tools.cjs ' +
|
|
79
|
+
'subprocess dispatch used on desktop cannot run here). Configure the GSD MCP ' +
|
|
80
|
+
'server (gsd-mcp-server) as a VS Code MCP server for full command dispatch on web, ' +
|
|
81
|
+
'or use the desktop GSD Core extension.'
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Registers the `@gsd` chat participant in web mode (#2103). Its handler is
|
|
87
|
+
* honest about the web dispatch limitation — see webDispatchUnavailableMessage.
|
|
88
|
+
* Exported separately so it is testable with a mock `vscode.chat`.
|
|
89
|
+
* @param {object} vscode
|
|
90
|
+
* @param {import('vscode').ExtensionContext} context
|
|
91
|
+
* @returns {object|null} the created participant, or null if vscode.chat is absent.
|
|
92
|
+
*/
|
|
93
|
+
function registerChatParticipant(vscode, context) {
|
|
94
|
+
if (!vscode || !vscode.chat || typeof vscode.chat.createChatParticipant !== 'function') {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const participant = vscode.chat.createChatParticipant('gsd', async (request, _chatContext, stream, _token) => {
|
|
98
|
+
const { family } = parseChatPrompt(request && request.prompt);
|
|
99
|
+
if (stream && typeof stream.markdown === 'function') {
|
|
100
|
+
stream.markdown(webDispatchUnavailableMessage(family));
|
|
101
|
+
}
|
|
102
|
+
return { metadata: { command: family, mode: 'web' } };
|
|
103
|
+
});
|
|
104
|
+
if (context && Array.isArray(context.subscriptions)) context.subscriptions.push(participant);
|
|
105
|
+
return participant;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The same representative LM tool NAMES as the desktop extension (must match
|
|
110
|
+
* package.json's contributes.languageModelTools[].name — underscored, the
|
|
111
|
+
* vscode.lm.registerTool registration name — so the manifest is identical
|
|
112
|
+
* across both entry points), but with web-mode invoke() handlers.
|
|
113
|
+
*/
|
|
114
|
+
const LM_TOOL_NAMES = ['gsd_progress', 'gsd_workstreams', 'gsd_plan_phase'];
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Registers web-mode LM tools via vscode.lm.registerTool (#2103). Each
|
|
118
|
+
* invoke() returns the honest web-dispatch-unavailable message — no engine-lib
|
|
119
|
+
* requires, no Node APIs.
|
|
120
|
+
* @param {object} vscode
|
|
121
|
+
* @param {import('vscode').ExtensionContext} context
|
|
122
|
+
* @returns {number} count of tools registered (0 if vscode.lm is absent — fail-soft).
|
|
123
|
+
*/
|
|
124
|
+
function registerLanguageModelTools(vscode, context) {
|
|
125
|
+
if (!vscode || !vscode.lm || typeof vscode.lm.registerTool !== 'function') return 0;
|
|
126
|
+
let count = 0;
|
|
127
|
+
for (const name of LM_TOOL_NAMES) {
|
|
128
|
+
const impl = {
|
|
129
|
+
async invoke(_options, _token) {
|
|
130
|
+
return new vscode.LanguageModelToolResult([
|
|
131
|
+
new vscode.LanguageModelTextPart(webDispatchUnavailableMessage(name)),
|
|
132
|
+
]);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
const disposable = vscode.lm.registerTool(name, impl);
|
|
136
|
+
if (context && Array.isArray(context.subscriptions)) context.subscriptions.push(disposable);
|
|
137
|
+
count++;
|
|
138
|
+
}
|
|
139
|
+
return count;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Detects `#runSubagent` feature availability (`chat.subagents.allowInvocationsFromSubagents`,
|
|
144
|
+
* VS Code 1.105+). Fail-soft: never throws. Identical detection logic to the
|
|
145
|
+
* desktop extension.js (duplicated, not shared, to keep this file at zero
|
|
146
|
+
* requires) — see extension.js's registerSubagentDispatch for the rationale
|
|
147
|
+
* that VS Code's chat engine itself surfaces registered participants/tools to
|
|
148
|
+
* `#runSubagent`, with no separate registration API.
|
|
149
|
+
* @param {object} vscode
|
|
150
|
+
* @returns {{available: boolean}}
|
|
151
|
+
*/
|
|
152
|
+
function detectSubagentSupport(vscode) {
|
|
153
|
+
let available = false;
|
|
154
|
+
try {
|
|
155
|
+
const cfg = vscode && vscode.workspace && typeof vscode.workspace.getConfiguration === 'function'
|
|
156
|
+
? vscode.workspace.getConfiguration('chat.subagents')
|
|
157
|
+
: null;
|
|
158
|
+
available = !!(cfg && typeof cfg.get === 'function' && cfg.get('allowInvocationsFromSubagents') !== undefined);
|
|
159
|
+
} catch {
|
|
160
|
+
available = false;
|
|
161
|
+
}
|
|
162
|
+
return { available };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* VS Code Web extension activation. Registers the chat participant + Language
|
|
167
|
+
* Model Tools in web mode. Does NOT register the `gsd.invoke` command with a
|
|
168
|
+
* real-dispatch handler (there is no Node dispatch on web) — the command is
|
|
169
|
+
* still contributed (contributes.commands in package.json is shared across
|
|
170
|
+
* desktop/web), so it is registered here too, but its handler returns the
|
|
171
|
+
* same honest web-mode message.
|
|
172
|
+
* @param {import('vscode').ExtensionContext} context
|
|
173
|
+
*/
|
|
174
|
+
function activate(context) {
|
|
175
|
+
const vscode = require('vscode');
|
|
176
|
+
|
|
177
|
+
const gsdCommand = vscode.commands.registerCommand('gsd.invoke', async (args) => {
|
|
178
|
+
const a = (args && typeof args === 'object') ? args : {};
|
|
179
|
+
const family = (typeof a.family === 'string' && a.family) ? a.family : '--help';
|
|
180
|
+
return JSON.stringify({ ok: false, stdout: '', stderr: webDispatchUnavailableMessage(family), code: null, timedOut: false });
|
|
181
|
+
});
|
|
182
|
+
context.subscriptions.push(gsdCommand);
|
|
183
|
+
|
|
184
|
+
registerChatParticipant(vscode, context);
|
|
185
|
+
registerLanguageModelTools(vscode, context);
|
|
186
|
+
detectSubagentSupport(vscode);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
activate,
|
|
191
|
+
parseChatPrompt,
|
|
192
|
+
webDispatchUnavailableMessage,
|
|
193
|
+
registerChatParticipant,
|
|
194
|
+
registerLanguageModelTools,
|
|
195
|
+
detectSubagentSupport,
|
|
196
|
+
LM_TOOL_NAMES,
|
|
197
|
+
};
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GSD extension for VS Code — ADR-1239 Phase D / #1942, dispatch fixed +
|
|
5
|
+
* extension surface (chat participant, Language Model Tools, #runSubagent
|
|
6
|
+
* wiring) added #2103.
|
|
7
|
+
*
|
|
8
|
+
* This is the DESKTOP (Node) `main` entry — see browser.js for the Web
|
|
9
|
+
* Extension `browser` entry, which is intentionally a SEPARATE, much more
|
|
10
|
+
* minimal file (zero Node APIs; does not require this file or host-binding.js).
|
|
11
|
+
*
|
|
12
|
+
* VS Code is the IDE-profile reference host. This extension binds GSD's command
|
|
13
|
+
* surface to VS Code's Command Palette + Chat participant via the imperative
|
|
14
|
+
* adapter path. Engine entry: in-process CJS require (the extension host runs
|
|
15
|
+
* Node). The engine seams (active model via vscode.lm, engine-owned hook bus,
|
|
16
|
+
* sandboxed-storage stateIO) are composed in activate() via host-binding.js
|
|
17
|
+
* per the #1933 binding.
|
|
18
|
+
*
|
|
19
|
+
* Installation: Marketplace/VSIX extension (see capabilities/vscode/capability.json
|
|
20
|
+
* — installSurface:'none', it is never CLI-installed by bin/install.js).
|
|
21
|
+
*
|
|
22
|
+
* Engine entry: dispatch is SUBPROCESS-REUSE to gsd-tools.cjs (bounded,
|
|
23
|
+
* no-throw — the shared `dispatchGsdCommand` in
|
|
24
|
+
* gsd-core/bin/lib/shell-command-projection.cjs), NOT an in-process
|
|
25
|
+
* command-routing hub. No fully-populated hub factory exists anywhere in
|
|
26
|
+
* gsd-core — every createHub() caller builds a single-family hub for its own
|
|
27
|
+
* narrow purpose — so calling createHub() with no args (the original #1942
|
|
28
|
+
* cut) always answered UnknownCommand. This mirrors the fix already applied
|
|
29
|
+
* to the pi extension (pi/gsd.cjs) and the companion MCP server
|
|
30
|
+
* (gsd-core/bin/lib/mcp-server.cjs), which dispatch through the SAME shared
|
|
31
|
+
* helper (#2102 Stage 2 / #2103).
|
|
32
|
+
*
|
|
33
|
+
* Extension surface (#2103):
|
|
34
|
+
* - Chat participant `@gsd` (contributes.chatParticipants) — dispatches free-form
|
|
35
|
+
* prompts through the same dispatchGsdCommand.
|
|
36
|
+
* - Language Model Tools (contributes.languageModelTools) — a representative
|
|
37
|
+
* set of GSD skills exposed as vscode.lm tools, each dispatching through the
|
|
38
|
+
* same shared helper (UPGRADE 1).
|
|
39
|
+
* - #runSubagent wiring — VS Code 1.105+ lets the primary chat agent invoke
|
|
40
|
+
* registered chat participants / languageModelTools as a nested agent turn
|
|
41
|
+
* via `#runSubagent`, gated by the `chat.subagents.allowInvocationsFromSubagents`
|
|
42
|
+
* setting. There is no separate "subagent contribution" registration API
|
|
43
|
+
* beyond the participant + tools already registered above — VS Code's own
|
|
44
|
+
* chat engine surfaces them. This extension's own contribution is a
|
|
45
|
+
* belt-and-suspenders depth cap (dispatchAsSubagent, GSD_MAX_SUBAGENT_DEPTH)
|
|
46
|
+
* mirroring capabilities/vscode/capability.json's
|
|
47
|
+
* hostIntegration.dispatch.maxDepth:5, independent of whatever VS Code
|
|
48
|
+
* itself enforces natively (UPGRADE 2).
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
const path = require('path');
|
|
52
|
+
const fs = require('fs');
|
|
53
|
+
|
|
54
|
+
// Resolve the GSD engine tree (walk up to find gsd-core/).
|
|
55
|
+
function resolveEngineRoot(startDir) {
|
|
56
|
+
let dir = startDir;
|
|
57
|
+
for (let i = 0; i < 6; i++) {
|
|
58
|
+
if (fs.existsSync(path.join(dir, 'gsd-core'))) return dir;
|
|
59
|
+
const parent = path.dirname(dir);
|
|
60
|
+
if (parent === dir) break;
|
|
61
|
+
dir = parent;
|
|
62
|
+
}
|
|
63
|
+
return path.resolve(startDir, '..');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const ENGINE_ROOT = resolveEngineRoot(__dirname);
|
|
67
|
+
const GSD_CORE = path.join(ENGINE_ROOT, 'gsd-core');
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Pure command handler — dispatches through the SHARED subprocess-shim
|
|
71
|
+
* `dispatchGsdCommand` (gsd-core/bin/lib/shell-command-projection.cjs), the
|
|
72
|
+
* same helper the pi extension (pi/gsd.cjs) and the companion MCP server
|
|
73
|
+
* (gsd-core/bin/lib/mcp-server.cjs) dispatch through.
|
|
74
|
+
*
|
|
75
|
+
* Exported separately from activate() so it is testable WITHOUT a VS Code
|
|
76
|
+
* host. Preserves the original return CONTRACT — a JSON-stringified result
|
|
77
|
+
* object — but now backed by a REAL dispatch instead of an unconfigured
|
|
78
|
+
* `createHub()` that always answered UnknownCommand (#2103 fix).
|
|
79
|
+
*
|
|
80
|
+
* Empty/omitted args default to `--help` (a real, working, ok:true
|
|
81
|
+
* gsd-tools.cjs command) — NOT the `'query'`/`'help'` pairing the original
|
|
82
|
+
* cut used, which is not a valid gsd-tools.cjs command and always produced
|
|
83
|
+
* UnknownCommand (mirrors the same fix already applied to
|
|
84
|
+
* pi/gsd.cjs's parseGsdCommandArgs).
|
|
85
|
+
*
|
|
86
|
+
* @returns {Promise<string>} JSON-stringified dispatch result:
|
|
87
|
+
* `{ok, stdout, stderr, code, timedOut}` on a normal dispatch, or
|
|
88
|
+
* `{ok:false, stdout:'', stderr:'GSD engine unavailable: ...', code:null,
|
|
89
|
+
* timedOut:false}` if the shared helper itself cannot be loaded (e.g.
|
|
90
|
+
* gsd-core/ missing from the tree).
|
|
91
|
+
*/
|
|
92
|
+
async function dispatchGsdCommand(args) {
|
|
93
|
+
const a = (args && typeof args === 'object') ? args : {};
|
|
94
|
+
const family = (typeof a.family === 'string' && a.family) ? a.family : '--help';
|
|
95
|
+
const subcommand = (typeof a.subcommand === 'string' && a.subcommand) ? a.subcommand : undefined;
|
|
96
|
+
const rest = Array.isArray(a.args) ? a.args : [];
|
|
97
|
+
const cwd = a.cwd || process.cwd();
|
|
98
|
+
|
|
99
|
+
let dispatchViaShim;
|
|
100
|
+
try {
|
|
101
|
+
({ dispatchGsdCommand: dispatchViaShim } = require(path.join(GSD_CORE, 'bin', 'lib', 'shell-command-projection.cjs')));
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return JSON.stringify({
|
|
104
|
+
ok: false,
|
|
105
|
+
stdout: '',
|
|
106
|
+
stderr: `GSD engine unavailable: ${e && e.message ? e.message : String(e)}`,
|
|
107
|
+
code: null,
|
|
108
|
+
timedOut: false,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const result = dispatchViaShim({ family, subcommand, args: rest, cwd });
|
|
112
|
+
return JSON.stringify(result);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolves the current workspace's root directory, per-invocation (never
|
|
117
|
+
* cached at activate() time), so GSD commands dispatch against the user's
|
|
118
|
+
* actual project instead of the extension host's own `process.cwd()` (#2103
|
|
119
|
+
* FIX — adversarial review: all three desktop dispatch surfaces previously
|
|
120
|
+
* omitted `cwd` entirely when calling dispatchGsdCommand, silently defaulting
|
|
121
|
+
* to the wrong directory). Falls back to `process.cwd()` only when no
|
|
122
|
+
* workspace folder is open (e.g. an empty window) — mirrors VS Code's own
|
|
123
|
+
* single-root convention of reading `workspaceFolders[0]`.
|
|
124
|
+
* @param {object} vscode
|
|
125
|
+
* @returns {string}
|
|
126
|
+
*/
|
|
127
|
+
function resolveWorkspaceCwd(vscode) {
|
|
128
|
+
const folders = vscode && vscode.workspace && vscode.workspace.workspaceFolders;
|
|
129
|
+
const first = Array.isArray(folders) ? folders[0] : undefined;
|
|
130
|
+
const fsPath = first && first.uri && first.uri.fsPath;
|
|
131
|
+
return (typeof fsPath === 'string' && fsPath) ? fsPath : process.cwd();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Tokenizes a raw chat/free-form prompt string into {family, subcommand, args}.
|
|
136
|
+
* Mirrors pi/gsd.cjs's parseGsdCommandArgs (simple whitespace split — no shell
|
|
137
|
+
* quoting support needed for a chat prompt). Empty input defaults to `--help`
|
|
138
|
+
* (a real, working gsd-tools.cjs command), matching dispatchGsdCommand's own
|
|
139
|
+
* default so the two surfaces (palette vs. chat) never diverge on "no input".
|
|
140
|
+
* @param {string} rawPrompt
|
|
141
|
+
* @returns {{family: string, subcommand: string|undefined, args: string[]}}
|
|
142
|
+
*/
|
|
143
|
+
function parseChatPrompt(rawPrompt) {
|
|
144
|
+
const tokens = String(rawPrompt || '').trim().split(/\s+/).filter(Boolean);
|
|
145
|
+
return {
|
|
146
|
+
family: tokens[0] || '--help',
|
|
147
|
+
subcommand: tokens[1],
|
|
148
|
+
args: tokens.slice(2),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Registers the `@gsd` chat participant (#2103). Its handler dispatches the
|
|
154
|
+
* user's free-form prompt through the SAME dispatchGsdCommand as gsd.invoke —
|
|
155
|
+
* one dispatch path for every command surface (palette / chat / LM tools).
|
|
156
|
+
* Exported separately so it is testable with a mock `vscode.chat`.
|
|
157
|
+
* @param {object} vscode
|
|
158
|
+
* @param {import('vscode').ExtensionContext} context
|
|
159
|
+
* @returns {object|null} the created participant, or null if vscode.chat is absent
|
|
160
|
+
* (older VS Code — fail-soft, never throws).
|
|
161
|
+
*/
|
|
162
|
+
function registerChatParticipant(vscode, context) {
|
|
163
|
+
if (!vscode || !vscode.chat || typeof vscode.chat.createChatParticipant !== 'function') {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const participant = vscode.chat.createChatParticipant('gsd', async (request, _chatContext, stream, _token) => {
|
|
167
|
+
// #2103 FIX: resolve the user's actual workspace, not the extension host's
|
|
168
|
+
// process.cwd() — computed per-invocation so it always reflects the
|
|
169
|
+
// CURRENT workspace (a chat request has no cwd field of its own; VS Code's
|
|
170
|
+
// ChatContext does not carry one).
|
|
171
|
+
const cwd = resolveWorkspaceCwd(vscode);
|
|
172
|
+
const { family, subcommand, args } = parseChatPrompt(request && request.prompt);
|
|
173
|
+
const result = JSON.parse(await dispatchGsdCommand({ family, subcommand, args, cwd }));
|
|
174
|
+
if (stream && typeof stream.markdown === 'function') {
|
|
175
|
+
stream.markdown(result.ok ? result.stdout : `GSD error: ${result.stderr || result.stdout || 'dispatch failed'}`);
|
|
176
|
+
}
|
|
177
|
+
return { metadata: { command: family } };
|
|
178
|
+
});
|
|
179
|
+
if (context && Array.isArray(context.subscriptions)) context.subscriptions.push(participant);
|
|
180
|
+
return participant;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* #2103 UPGRADE 1 — Language Model Tools.
|
|
185
|
+
*
|
|
186
|
+
* A representative set of GSD skills (see skills/gsd-progress, skills/gsd-workstreams,
|
|
187
|
+
* skills/gsd-plan-phase) exposed as vscode.lm tools, matching the
|
|
188
|
+
* contributes.languageModelTools manifest entries in package.json. Each tool's
|
|
189
|
+
* invoke() dispatches through the SAME shared dispatchGsdCommand as gsd.invoke —
|
|
190
|
+
* the tool name maps to a real, verified family/subcommand pair (verified by
|
|
191
|
+
* direct `gsd-tools.cjs <family> [subcommand] --raw --json-errors` invocation;
|
|
192
|
+
* see the #2103 CORE-stage precedent for `progress json`).
|
|
193
|
+
*
|
|
194
|
+
* Kept to a small, curated set rather than all 71 shipped skills: these three
|
|
195
|
+
* both name a real skill AND map cleanly onto a single, safe, read-only
|
|
196
|
+
* gsd-tools.cjs command (the rest are multi-step agent workflows that do not
|
|
197
|
+
* reduce to one non-interactive CLI call, and stay slash-command-native).
|
|
198
|
+
*/
|
|
199
|
+
// `name` uses underscores (vscode.lm.registerTool's registration name — MUST
|
|
200
|
+
// match contributes.languageModelTools[].name in package.json exactly); the
|
|
201
|
+
// hyphenated `#gsd-progress`-style mention alias is package.json's separate
|
|
202
|
+
// `toolReferenceName` field (VS Code owns that mapping internally).
|
|
203
|
+
const LM_TOOLS = [
|
|
204
|
+
{
|
|
205
|
+
name: 'gsd_progress',
|
|
206
|
+
// skills/gsd-progress — real, verified: `gsd-tools.cjs progress json`.
|
|
207
|
+
resolveCommand: () => ({ family: 'progress', subcommand: 'json', args: [] }),
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'gsd_workstreams',
|
|
211
|
+
// skills/gsd-workstreams — real, verified: `gsd-tools.cjs workstream list`.
|
|
212
|
+
resolveCommand: () => ({ family: 'workstream', subcommand: 'list', args: [] }),
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'gsd_plan_phase',
|
|
216
|
+
// skills/gsd-plan-phase — real, verified: `gsd-tools.cjs phase-plan-index <phase>`.
|
|
217
|
+
// (The full multi-step planning workflow stays slash-command-native; this
|
|
218
|
+
// tool exposes the read-only plan-index lookup the workflow itself queries first.)
|
|
219
|
+
resolveCommand: (input) => ({
|
|
220
|
+
family: 'phase-plan-index',
|
|
221
|
+
subcommand: undefined,
|
|
222
|
+
args: [input && input.phase ? String(input.phase) : ''],
|
|
223
|
+
}),
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Builds a vscode.lm tool implementation for one LM_TOOLS entry.
|
|
229
|
+
* @param {{name:string, resolveCommand:(input:object)=>{family:string,subcommand:string|undefined,args:string[]}}} toolDef
|
|
230
|
+
* @param {object} vscode
|
|
231
|
+
*/
|
|
232
|
+
function createLanguageModelTool(toolDef, vscode) {
|
|
233
|
+
return {
|
|
234
|
+
async invoke(options, _token) {
|
|
235
|
+
const input = (options && options.input) || {};
|
|
236
|
+
// #2103 FIX: resolve the user's actual workspace, not the extension
|
|
237
|
+
// host's process.cwd() — computed per-invocation. LanguageModelToolInvocationOptions
|
|
238
|
+
// has no cwd field of its own.
|
|
239
|
+
const cwd = resolveWorkspaceCwd(vscode);
|
|
240
|
+
const { family, subcommand, args } = toolDef.resolveCommand(input);
|
|
241
|
+
const result = JSON.parse(await dispatchGsdCommand({ family, subcommand, args, cwd }));
|
|
242
|
+
const text = result.ok ? result.stdout : `GSD error: ${result.stderr || result.stdout || 'dispatch failed'}`;
|
|
243
|
+
return new vscode.LanguageModelToolResult([new vscode.LanguageModelTextPart(text)]);
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Registers the LM_TOOLS set via vscode.lm.registerTool (#2103 UPGRADE 1).
|
|
250
|
+
* Exported separately so it is testable with a mock `vscode.lm`.
|
|
251
|
+
* @param {object} vscode
|
|
252
|
+
* @param {import('vscode').ExtensionContext} context
|
|
253
|
+
* @returns {number} count of tools registered (0 if vscode.lm is absent — fail-soft).
|
|
254
|
+
*/
|
|
255
|
+
function registerLanguageModelTools(vscode, context) {
|
|
256
|
+
if (!vscode || !vscode.lm || typeof vscode.lm.registerTool !== 'function') return 0;
|
|
257
|
+
let count = 0;
|
|
258
|
+
for (const toolDef of LM_TOOLS) {
|
|
259
|
+
const disposable = vscode.lm.registerTool(toolDef.name, createLanguageModelTool(toolDef, vscode));
|
|
260
|
+
if (context && Array.isArray(context.subscriptions)) context.subscriptions.push(disposable);
|
|
261
|
+
count++;
|
|
262
|
+
}
|
|
263
|
+
return count;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Mirrors capabilities/vscode/capability.json's hostIntegration.dispatch.maxDepth. */
|
|
267
|
+
const GSD_MAX_SUBAGENT_DEPTH = 5;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* #2103 UPGRADE 2 — native subagent dispatch (#runSubagent).
|
|
271
|
+
*
|
|
272
|
+
* Dispatches a command as a (possibly nested) subagent turn, enforcing GSD's
|
|
273
|
+
* own maxDepth:5 ceiling (capabilities/vscode/capability.json) independent of
|
|
274
|
+
* whatever VS Code's chat engine enforces natively for `#runSubagent` /
|
|
275
|
+
* `chat.subagents.allowInvocationsFromSubagents` — belt-and-suspenders, never
|
|
276
|
+
* silently trusts the host's own depth accounting.
|
|
277
|
+
* @param {{family?:string, subcommand?:string, args?:string[], cwd?:string, depth?:number}} args
|
|
278
|
+
* @returns {Promise<string>} same JSON-stringified contract as dispatchGsdCommand.
|
|
279
|
+
*/
|
|
280
|
+
async function dispatchAsSubagent(args) {
|
|
281
|
+
const a = (args && typeof args === 'object') ? args : {};
|
|
282
|
+
const depth = Number.isInteger(a.depth) ? a.depth : 0;
|
|
283
|
+
if (depth > GSD_MAX_SUBAGENT_DEPTH) {
|
|
284
|
+
return JSON.stringify({
|
|
285
|
+
ok: false,
|
|
286
|
+
stdout: '',
|
|
287
|
+
stderr: `GSD subagent dispatch refused: depth ${depth} exceeds maxDepth ${GSD_MAX_SUBAGENT_DEPTH}`,
|
|
288
|
+
code: null,
|
|
289
|
+
timedOut: false,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return dispatchGsdCommand(a);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Detects whether the host VS Code build exposes the `#runSubagent` feature
|
|
297
|
+
* surface (`chat.subagents.allowInvocationsFromSubagents`, VS Code 1.105+).
|
|
298
|
+
* Fail-soft: any missing/older API surface (including an Insiders-gated build
|
|
299
|
+
* where the setting does not exist yet) resolves `available:false` — never
|
|
300
|
+
* throws. There is no separate registration call: VS Code's chat engine
|
|
301
|
+
* surfaces the already-registered chat participant + languageModelTools to
|
|
302
|
+
* `#runSubagent` on its own; this function only reports/confirms availability.
|
|
303
|
+
* @param {object} vscode
|
|
304
|
+
* @returns {{available: boolean, dispatchAsSubagent: typeof dispatchAsSubagent, maxDepth: number}}
|
|
305
|
+
*/
|
|
306
|
+
function registerSubagentDispatch(vscode) {
|
|
307
|
+
let available = false;
|
|
308
|
+
try {
|
|
309
|
+
const cfg = vscode && vscode.workspace && typeof vscode.workspace.getConfiguration === 'function'
|
|
310
|
+
? vscode.workspace.getConfiguration('chat.subagents')
|
|
311
|
+
: null;
|
|
312
|
+
available = !!(cfg && typeof cfg.get === 'function' && cfg.get('allowInvocationsFromSubagents') !== undefined);
|
|
313
|
+
} catch {
|
|
314
|
+
available = false;
|
|
315
|
+
}
|
|
316
|
+
return { available, dispatchAsSubagent, maxDepth: GSD_MAX_SUBAGENT_DEPTH };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* VS Code extension activation. Composes the IDE-profile seams (per the #1933
|
|
321
|
+
* reference binding) + registers the full command surface: palette
|
|
322
|
+
* (gsd.invoke), chat participant (@gsd), Language Model Tools, and the
|
|
323
|
+
* #runSubagent depth-cap wiring.
|
|
324
|
+
* @param {import('vscode').ExtensionContext} context
|
|
325
|
+
*/
|
|
326
|
+
function activate(context) {
|
|
327
|
+
const vscode = require('vscode');
|
|
328
|
+
|
|
329
|
+
// ── Command surface: palette ──────────────────────────────────────────────
|
|
330
|
+
// #2103 FIX: wrap dispatchGsdCommand (rather than registering it directly as
|
|
331
|
+
// the handler) so a palette invocation that omits `cwd` resolves the user's
|
|
332
|
+
// actual workspace instead of silently defaulting to the extension host's
|
|
333
|
+
// own process.cwd(). An explicit `args.cwd` (e.g. from a programmatic
|
|
334
|
+
// vscode.commands.executeCommand('gsd.invoke', {..., cwd}) caller) still
|
|
335
|
+
// takes precedence.
|
|
336
|
+
const gsdCommand = vscode.commands.registerCommand('gsd.invoke', (args) => {
|
|
337
|
+
const a = (args && typeof args === 'object') ? args : {};
|
|
338
|
+
const cwd = a.cwd || resolveWorkspaceCwd(vscode);
|
|
339
|
+
return dispatchGsdCommand({ ...a, cwd });
|
|
340
|
+
});
|
|
341
|
+
context.subscriptions.push(gsdCommand);
|
|
342
|
+
|
|
343
|
+
// ── IDE-profile host binding (#1933 reference binding; desktop/Node only —
|
|
344
|
+
// see host-binding.js's header for why browser.js does NOT use this path).
|
|
345
|
+
// On a real desktop VS Code host this SUCCEEDS (#2103 fix: host-binding.js's
|
|
346
|
+
// guard checks vscode.lm.selectChatModels — the real API — not the
|
|
347
|
+
// nonexistent vscode.lm.sendRequest the pre-#2103 code assumed). Still
|
|
348
|
+
// wrapped fail-open so a genuinely older VS Code build (no vscode.lm at all)
|
|
349
|
+
// degrades to the palette command only, rather than blocking activation.
|
|
350
|
+
try {
|
|
351
|
+
const bindGsdToVscode = require('./host-binding.js');
|
|
352
|
+
const hostStorage = {
|
|
353
|
+
read: (key) => context.globalState.get(key),
|
|
354
|
+
write: (key, value) => context.globalState.update(key, value),
|
|
355
|
+
};
|
|
356
|
+
bindGsdToVscode(vscode, hostStorage);
|
|
357
|
+
} catch {
|
|
358
|
+
// fail-open — see doc comment above.
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ── Chat participant (@gsd) ───────────────────────────────────────────────
|
|
362
|
+
registerChatParticipant(vscode, context);
|
|
363
|
+
|
|
364
|
+
// ── Language Model Tools (#2103 UPGRADE 1) ────────────────────────────────
|
|
365
|
+
registerLanguageModelTools(vscode, context);
|
|
366
|
+
|
|
367
|
+
// ── #runSubagent wiring (#2103 UPGRADE 2) ─────────────────────────────────
|
|
368
|
+
registerSubagentDispatch(vscode);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
module.exports = {
|
|
372
|
+
activate,
|
|
373
|
+
dispatchGsdCommand,
|
|
374
|
+
resolveEngineRoot,
|
|
375
|
+
resolveWorkspaceCwd,
|
|
376
|
+
parseChatPrompt,
|
|
377
|
+
registerChatParticipant,
|
|
378
|
+
registerLanguageModelTools,
|
|
379
|
+
registerSubagentDispatch,
|
|
380
|
+
dispatchAsSubagent,
|
|
381
|
+
LM_TOOLS,
|
|
382
|
+
GSD_MAX_SUBAGENT_DEPTH,
|
|
383
|
+
};
|