@opengsd/gsd-pi 1.0.2-dev.d55d5c9 → 1.0.2-dev.dbfb371

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.
Files changed (94) hide show
  1. package/README.md +63 -12
  2. package/dist/resource-loader.js +1 -1
  3. package/dist/resources/.managed-resources-content-hash +1 -1
  4. package/dist/resources/extensions/gsd/auto/loop.js +19 -0
  5. package/dist/resources/extensions/gsd/auto/phases.js +1 -1
  6. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -15
  7. package/dist/resources/extensions/gsd/commands-handlers.js +3 -0
  8. package/dist/resources/shared/package-manager-detection.js +36 -0
  9. package/dist/update-check.d.ts +6 -2
  10. package/dist/update-check.js +7 -3
  11. package/dist/web/standalone/.next/BUILD_ID +1 -1
  12. package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
  13. package/dist/web/standalone/.next/build-manifest.json +2 -2
  14. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  15. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  32. package/dist/web/standalone/.next/server/app/index.html +1 -1
  33. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app-paths-manifest.json +6 -6
  40. package/dist/web/standalone/.next/server/chunks/1834.js +1 -1
  41. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  42. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  43. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  44. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  45. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  46. package/package.json +5 -2
  47. package/packages/cloud-mcp-gateway/package.json +2 -2
  48. package/packages/contracts/package.json +1 -1
  49. package/packages/daemon/package.json +4 -4
  50. package/packages/gsd-agent-core/package.json +5 -5
  51. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  52. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +3 -1
  53. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  54. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  55. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
  56. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  57. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts +1 -0
  58. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.d.ts.map +1 -1
  59. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js +1 -0
  60. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-class-constants.js.map +1 -1
  61. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  62. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +2 -1
  63. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
  64. package/packages/gsd-agent-modes/package.json +7 -7
  65. package/packages/mcp-server/package.json +3 -3
  66. package/packages/native/package.json +1 -1
  67. package/packages/pi-agent-core/dist/agent-loop.js +13 -13
  68. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  69. package/packages/pi-agent-core/package.json +1 -1
  70. package/packages/pi-ai/dist/models.generated.d.ts +40 -17
  71. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  72. package/packages/pi-ai/dist/models.generated.js +36 -17
  73. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  74. package/packages/pi-ai/package.json +1 -1
  75. package/packages/pi-coding-agent/dist/core/tools/read.d.ts +2 -2
  76. package/packages/pi-coding-agent/dist/core/tools/read.d.ts.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/tools/read.js +5 -3
  78. package/packages/pi-coding-agent/dist/core/tools/read.js.map +1 -1
  79. package/packages/pi-coding-agent/package.json +8 -8
  80. package/packages/pi-tui/package.json +1 -1
  81. package/packages/rpc-client/package.json +2 -2
  82. package/pkg/package.json +1 -1
  83. package/scripts/install/detect-existing.js +17 -3
  84. package/scripts/install/npm-global.js +103 -33
  85. package/scripts/install.js +1 -0
  86. package/src/resources/extensions/gsd/auto/loop.ts +22 -0
  87. package/src/resources/extensions/gsd/auto/phases.ts +1 -1
  88. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -15
  89. package/src/resources/extensions/gsd/commands-handlers.ts +2 -0
  90. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +64 -0
  91. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +60 -0
  92. package/src/resources/shared/package-manager-detection.ts +39 -0
  93. /package/dist/web/standalone/.next/static/{l2HoM3Fqnb0IDfjVs_Umt → BVrLsL82ynrLee5zxeihC}/_buildManifest.js +0 -0
  94. /package/dist/web/standalone/.next/static/{l2HoM3Fqnb0IDfjVs_Umt → BVrLsL82ynrLee5zxeihC}/_ssgManifest.js +0 -0
@@ -507,6 +507,21 @@ function initSessionNotifications(ctx: ExtensionContext): void {
507
507
  initNotificationWidget(ctx);
508
508
  }
509
509
 
510
+ async function prepareWorkflowMcpForHookContext(
511
+ ctx: ExtensionContext,
512
+ basePath: string,
513
+ ): Promise<void> {
514
+ // Skip MCP auto-prep when running inside an auto-worktree. The worktree
515
+ // already has .mcp.json from createAutoWorktree, and re-running the writer
516
+ // post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
517
+ // CLI path resolution), dirtying the tree and breaking the milestone merge.
518
+ const { isInAutoWorktree } = await import("../auto-worktree.js");
519
+ if (isInAutoWorktree(basePath)) return;
520
+
521
+ const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
522
+ prepareWorkflowMcpForProject(ctx, basePath);
523
+ }
524
+
510
525
  export function registerHooks(
511
526
  pi: ExtensionAPI,
512
527
  ecosystemHandlers: GSDEcosystemBeforeAgentStartHandler[],
@@ -532,12 +547,7 @@ export function registerHooks(
532
547
  await syncServiceTierStatus(ctx);
533
548
  await applyDisabledModelProviderPolicy(ctx);
534
549
  await applyCompactionThresholdOverride(ctx);
535
- // Skip MCP auto-prep when running inside an auto-worktree (see session_switch below).
536
- const { isInAutoWorktree } = await import("../auto-worktree.js");
537
- if (!isInAutoWorktree(basePath)) {
538
- const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
539
- prepareWorkflowMcpForProject(ctx, basePath);
540
- }
550
+ await prepareWorkflowMcpForHookContext(ctx, basePath);
541
551
 
542
552
  // Apply show_token_cost preference (#1515)
543
553
  try {
@@ -563,15 +573,7 @@ export function registerHooks(
563
573
  await syncServiceTierStatus(ctx);
564
574
  await applyDisabledModelProviderPolicy(ctx);
565
575
  await applyCompactionThresholdOverride(ctx);
566
- // Skip MCP auto-prep when running inside an auto-worktree. The worktree
567
- // already has .mcp.json from createAutoWorktree, and re-running the writer
568
- // post-chdir rewrites the file mid-run (non-idempotent due to cwd-relative
569
- // CLI path resolution), dirtying the tree and breaking the milestone merge.
570
- const { isInAutoWorktree } = await import("../auto-worktree.js");
571
- if (!isInAutoWorktree(basePath)) {
572
- const { prepareWorkflowMcpForProject } = await import("../workflow-mcp-auto-prep.js");
573
- prepareWorkflowMcpForProject(ctx, basePath);
574
- }
576
+ await prepareWorkflowMcpForHookContext(ctx, basePath);
575
577
  await loadToolApiKeysForSession();
576
578
  if (!isAutoActive()) {
577
579
  ctx.ui.setWidget("gsd-progress", undefined);
@@ -608,6 +610,11 @@ export function registerHooks(
608
610
  }
609
611
  clearDeferredApprovalGate(beforeAgentBasePath);
610
612
 
613
+ // session_start can fire before the active provider has settled. By
614
+ // before_agent_start, Claude Code CLI sessions should get the same
615
+ // project MCP config that /gsd mcp init would write.
616
+ await prepareWorkflowMcpForHookContext(ctx, beforeAgentBasePath);
617
+
611
618
  // GSD's own context injection (existing behavior — unchanged).
612
619
  const { buildBeforeAgentStartResult } = await import("./system-context.js");
613
620
  const gsdResult = await buildBeforeAgentStartResult(event, ctx);
@@ -26,6 +26,7 @@ import { isAutoActive, checkRemoteAutoSession } from "./auto.js";
26
26
  import { getAutoWorktreePath } from "./auto-worktree.js";
27
27
  import { currentDirectoryRoot, projectRoot } from "./commands/context.js";
28
28
  import { loadPrompt } from "./prompt-loader.js";
29
+ import { isPnpmInstall } from "../../shared/package-manager-detection.js";
29
30
  import {
30
31
  buildDoctorHealIssuePayload,
31
32
  buildDoctorHealSummary,
@@ -57,6 +58,7 @@ function isBunInstall(argv1: string | undefined = process.argv[1]): boolean {
57
58
 
58
59
  function resolveInstallCommand(pkg: string): string {
59
60
  if (isBunInstall()) return `bun add -g ${pkg}`;
61
+ if (isPnpmInstall()) return `pnpm add -g ${pkg}`;
60
62
  return `npm install -g ${pkg}`;
61
63
  }
62
64
 
@@ -344,6 +344,70 @@ describe("Custom engine loop integration", () => {
344
344
  );
345
345
  });
346
346
 
347
+ it("step mode stops after one custom workflow step", async () => {
348
+ _resetPendingResolve();
349
+
350
+ const runDir = makeTmpDir();
351
+ const graph = makeGraph([
352
+ makeStep({ id: "step-a" }),
353
+ makeStep({ id: "step-b", dependsOn: ["step-a"] }),
354
+ makeStep({ id: "step-c", dependsOn: ["step-b"] }),
355
+ ], "step-mode-custom");
356
+ writeGraph(runDir, graph);
357
+ writeDefinition(runDir, graph.steps, "step-mode-custom");
358
+
359
+ const ctx = makeMockCtx();
360
+ const pi = makeMockPi();
361
+ const s = makeLoopSession({
362
+ activeEngineId: "custom",
363
+ activeRunDir: runDir,
364
+ basePath: runDir,
365
+ stepMode: true,
366
+ });
367
+
368
+ const deps = makeMockDeps({
369
+ stopAuto: async (_ctx, _pi, reason) => {
370
+ deps.callLog.push(`stopAuto:${reason ?? "no-reason"}`);
371
+ s.active = false;
372
+ },
373
+ });
374
+
375
+ const loopPromise = autoLoop(ctx, pi, s, deps);
376
+ await resolveNextAgentEnd();
377
+
378
+ let timeout: NodeJS.Timeout | undefined;
379
+ try {
380
+ await Promise.race([
381
+ loopPromise,
382
+ new Promise((_, reject) =>
383
+ timeout = setTimeout(() => {
384
+ s.active = false;
385
+ if (_hasPendingResolveForTest()) {
386
+ resolveAgentEnd({ messages: [{ role: "assistant" }] });
387
+ }
388
+ reject(new Error(
389
+ `step mode did not stop after one custom workflow step; calls=${pi.calls.length}; log=${deps.callLog.join(",")}`,
390
+ ));
391
+ }, 1_000),
392
+ ),
393
+ ]);
394
+ } finally {
395
+ if (timeout) clearTimeout(timeout);
396
+ }
397
+
398
+ const finalGraph = readGraph(runDir);
399
+ assert.equal(pi.calls.length, 1, "step mode should dispatch exactly one custom step");
400
+ assert.equal(finalGraph.steps[0]?.status, "complete", "first step should complete");
401
+ assert.equal(finalGraph.steps[1]?.status, "pending", "second step should wait for the next /gsd next");
402
+ assert.equal(finalGraph.steps[2]?.status, "pending", "third step should wait for a later step");
403
+ assert.equal(
404
+ deps.callLog.some((e: string) => e.startsWith("stopAuto:")),
405
+ false,
406
+ "step-mode pause should not complete or stop the whole workflow",
407
+ );
408
+ assert.equal(s.preserveStepSurfaceAfterLoopExit, true);
409
+ });
410
+
347
411
  it("stops when engine reports isComplete on first derive", async () => {
348
412
  _resetPendingResolve();
349
413
 
@@ -1,6 +1,11 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
3
6
 
7
+ import { registerHooks } from "../bootstrap/register-hooks.ts";
8
+ import { GSD_WORKFLOW_MCP_SERVER_NAME } from "../mcp-project-config.ts";
4
9
  import { prepareWorkflowMcpForProject, shouldAutoPrepareWorkflowMcp } from "../workflow-mcp-auto-prep.ts";
5
10
 
6
11
  test("shouldAutoPrepareWorkflowMcp enables prep for externalCli local transport", () => {
@@ -74,3 +79,58 @@ test("prepareWorkflowMcpForProject warns with /gsd mcp init guidance when prep f
74
79
  assert.equal(notifications[0].level, "warning");
75
80
  assert.match(notifications[0].message, /Please run \/gsd mcp init \./);
76
81
  });
82
+
83
+ test("before_agent_start auto-prepares project workflow MCP for Claude Code CLI", async (t) => {
84
+ const projectRoot = mkdtempSync(join(tmpdir(), "gsd-mcp-before-agent-"));
85
+ const originalCwd = process.cwd();
86
+ const notifications: string[] = [];
87
+ const handlers = new Map<string, Array<(event: any, ctx?: any) => Promise<any> | any>>();
88
+ const pi = {
89
+ on(event: string, handler: (event: any, ctx?: any) => Promise<any> | any) {
90
+ const existing = handlers.get(event) ?? [];
91
+ existing.push(handler);
92
+ handlers.set(event, existing);
93
+ },
94
+ getActiveTools: () => [],
95
+ getAllTools: () => [],
96
+ setActiveTools() {},
97
+ };
98
+
99
+ t.after(() => {
100
+ process.chdir(originalCwd);
101
+ rmSync(projectRoot, { recursive: true, force: true });
102
+ });
103
+
104
+ process.chdir(projectRoot);
105
+ registerHooks(pi as any, []);
106
+
107
+ const beforeAgentStart = handlers.get("before_agent_start")?.[0];
108
+ assert.ok(beforeAgentStart, "before_agent_start hook should be registered");
109
+
110
+ await beforeAgentStart(
111
+ { prompt: "hello", systemPrompt: "base" },
112
+ {
113
+ cwd: projectRoot,
114
+ model: { provider: "claude-code", baseUrl: "local://claude-code" },
115
+ modelRegistry: {
116
+ getProviderAuthMode: () => "externalCli",
117
+ isProviderRequestReady: () => true,
118
+ },
119
+ ui: {
120
+ notify(message: string) {
121
+ notifications.push(message);
122
+ },
123
+ setWidget() {},
124
+ },
125
+ },
126
+ );
127
+
128
+ const configPath = join(projectRoot, ".mcp.json");
129
+ assert.equal(existsSync(configPath), true, "Claude Code CLI turns should create project MCP config");
130
+
131
+ const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as {
132
+ mcpServers?: Record<string, unknown>;
133
+ };
134
+ assert.ok(parsed.mcpServers?.[GSD_WORKFLOW_MCP_SERVER_NAME]);
135
+ assert.match(notifications.join("\n"), /Claude Code MCP prepared/);
136
+ });
@@ -0,0 +1,39 @@
1
+ import { homedir } from 'node:os'
2
+ import { join, resolve as resolvePath, sep } from 'node:path'
3
+
4
+ function hasPnpmPath(value: string | undefined): boolean {
5
+ if (!value) return false
6
+ const normalized = value.replace(/\\/g, '/').toLowerCase()
7
+ return (
8
+ normalized.includes('/.pnpm/') ||
9
+ normalized.endsWith('/pnpm') ||
10
+ normalized.endsWith('/pnpm.cjs') ||
11
+ normalized.endsWith('/pnpm.js')
12
+ )
13
+ }
14
+
15
+ function pathStartsWith(pathValue: string | undefined, dir: string): boolean {
16
+ if (!pathValue) return false
17
+ const resolvedPath = resolvePath(pathValue)
18
+ const resolvedDir = resolvePath(dir)
19
+ return resolvedPath === resolvedDir || resolvedPath.startsWith(resolvedDir + sep)
20
+ }
21
+
22
+ // Shared by update-check.ts and gsd command handlers. The JS installer keeps a
23
+ // parallel copy because it runs before TypeScript output exists.
24
+ export function isPnpmInstall(
25
+ argv1: string | undefined = process.argv[1],
26
+ env: NodeJS.ProcessEnv = process.env,
27
+ ): boolean {
28
+ if (env.npm_config_user_agent?.startsWith('pnpm/')) return true
29
+ if (hasPnpmPath(env.npm_execpath)) return true
30
+ if (hasPnpmPath(argv1)) return true
31
+ if (!argv1) return false
32
+
33
+ const pnpmBinDirs: string[] = []
34
+ if (env.PNPM_HOME) pnpmBinDirs.push(env.PNPM_HOME)
35
+ pnpmBinDirs.push(join(homedir(), 'Library', 'pnpm'))
36
+ pnpmBinDirs.push(join(homedir(), '.local', 'share', 'pnpm'))
37
+
38
+ return pnpmBinDirs.some((dir) => pathStartsWith(argv1, dir) || pathStartsWith(env.npm_execpath, dir))
39
+ }