gsd-pi 2.23.0 → 2.24.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/dist/cli.js +12 -3
- package/dist/headless.d.ts +4 -0
- package/dist/headless.js +118 -10
- package/dist/help-text.js +22 -7
- package/dist/resource-loader.js +64 -9
- package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +41 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/dist/resources/extensions/gsd/auto.ts +123 -41
- package/dist/resources/extensions/gsd/commands.ts +176 -10
- package/dist/resources/extensions/gsd/complexity.ts +1 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +38 -0
- package/dist/resources/extensions/gsd/doctor.ts +56 -11
- package/dist/resources/extensions/gsd/exit-command.ts +2 -2
- package/dist/resources/extensions/gsd/gitignore.ts +1 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +75 -0
- package/dist/resources/extensions/gsd/index.ts +34 -1
- package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/dist/resources/extensions/gsd/preferences.ts +65 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
- package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
- package/dist/resources/extensions/gsd/state.ts +72 -30
- package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +202 -2
- package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/dist/resources/extensions/gsd/types.ts +15 -1
- package/dist/resources/extensions/subagent/index.ts +5 -0
- package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
- package/dist/update-check.d.ts +9 -0
- package/dist/update-check.js +97 -0
- package/package.json +6 -1
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +16 -7
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
- package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +21 -8
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
- package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
- package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
- package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
- package/scripts/postinstall.js +7 -109
- package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +73 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +41 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +15 -3
- package/src/resources/extensions/gsd/auto.ts +123 -41
- package/src/resources/extensions/gsd/commands.ts +176 -10
- package/src/resources/extensions/gsd/complexity.ts +1 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +38 -0
- package/src/resources/extensions/gsd/doctor.ts +56 -11
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +75 -0
- package/src/resources/extensions/gsd/index.ts +34 -1
- package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
- package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
- package/src/resources/extensions/gsd/preferences.ts +65 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
- package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
- package/src/resources/extensions/gsd/session-status-io.ts +197 -0
- package/src/resources/extensions/gsd/state.ts +72 -30
- package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
- package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +202 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
- package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
- package/src/resources/extensions/gsd/types.ts +15 -1
- package/src/resources/extensions/subagent/index.ts +5 -0
- package/src/resources/extensions/subagent/worker-registry.ts +99 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the parallel worker registry used by the dashboard overlay.
|
|
3
|
+
*
|
|
4
|
+
* Verifies worker lifecycle (register → update → cleanup), batch grouping,
|
|
5
|
+
* and the hasActiveWorkers() status check.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createTestContext } from './test-helpers.ts';
|
|
9
|
+
import {
|
|
10
|
+
registerWorker,
|
|
11
|
+
updateWorker,
|
|
12
|
+
getActiveWorkers,
|
|
13
|
+
getWorkerBatches,
|
|
14
|
+
hasActiveWorkers,
|
|
15
|
+
resetWorkerRegistry,
|
|
16
|
+
} from '../../subagent/worker-registry.ts';
|
|
17
|
+
|
|
18
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
19
|
+
|
|
20
|
+
// ─── Setup ────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
resetWorkerRegistry();
|
|
23
|
+
|
|
24
|
+
// ─── Registration ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
console.log("\n=== Worker Registration ===");
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
resetWorkerRegistry();
|
|
30
|
+
const id = registerWorker("scout", "Explore codebase", 0, 3, "batch-1");
|
|
31
|
+
assertTrue(id.startsWith("worker-"), "worker ID has correct prefix");
|
|
32
|
+
const workers = getActiveWorkers();
|
|
33
|
+
assertEq(workers.length, 1, "one worker registered");
|
|
34
|
+
assertEq(workers[0].agent, "scout", "worker agent name correct");
|
|
35
|
+
assertEq(workers[0].task, "Explore codebase", "worker task correct");
|
|
36
|
+
assertEq(workers[0].status, "running", "worker starts as running");
|
|
37
|
+
assertEq(workers[0].index, 0, "worker index correct");
|
|
38
|
+
assertEq(workers[0].batchSize, 3, "worker batch size correct");
|
|
39
|
+
assertEq(workers[0].batchId, "batch-1", "worker batch ID correct");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── Multiple workers in a batch ──────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
console.log("\n=== Multiple Workers in a Batch ===");
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
resetWorkerRegistry();
|
|
48
|
+
const id1 = registerWorker("scout", "Task A", 0, 3, "batch-2");
|
|
49
|
+
const id2 = registerWorker("researcher", "Task B", 1, 3, "batch-2");
|
|
50
|
+
const id3 = registerWorker("worker", "Task C", 2, 3, "batch-2");
|
|
51
|
+
|
|
52
|
+
const workers = getActiveWorkers();
|
|
53
|
+
assertEq(workers.length, 3, "three workers registered");
|
|
54
|
+
assertTrue(hasActiveWorkers(), "has active workers");
|
|
55
|
+
|
|
56
|
+
const batches = getWorkerBatches();
|
|
57
|
+
assertEq(batches.size, 1, "one batch");
|
|
58
|
+
const batch = batches.get("batch-2");
|
|
59
|
+
assertTrue(batch !== undefined, "batch-2 exists");
|
|
60
|
+
assertEq(batch!.length, 3, "batch has 3 workers");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ─── Worker status updates ────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
console.log("\n=== Worker Status Updates ===");
|
|
66
|
+
|
|
67
|
+
{
|
|
68
|
+
resetWorkerRegistry();
|
|
69
|
+
const id1 = registerWorker("scout", "Task A", 0, 2, "batch-3");
|
|
70
|
+
const id2 = registerWorker("worker", "Task B", 1, 2, "batch-3");
|
|
71
|
+
|
|
72
|
+
updateWorker(id1, "completed");
|
|
73
|
+
const workers = getActiveWorkers();
|
|
74
|
+
const w1 = workers.find(w => w.id === id1);
|
|
75
|
+
assertEq(w1?.status, "completed", "worker 1 marked completed");
|
|
76
|
+
|
|
77
|
+
const w2 = workers.find(w => w.id === id2);
|
|
78
|
+
assertEq(w2?.status, "running", "worker 2 still running");
|
|
79
|
+
assertTrue(hasActiveWorkers(), "still has active workers (worker 2 running)");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── Failed worker ────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
console.log("\n=== Failed Worker ===");
|
|
85
|
+
|
|
86
|
+
{
|
|
87
|
+
resetWorkerRegistry();
|
|
88
|
+
const id = registerWorker("scout", "Task A", 0, 1, "batch-4");
|
|
89
|
+
updateWorker(id, "failed");
|
|
90
|
+
const workers = getActiveWorkers();
|
|
91
|
+
assertEq(workers[0].status, "failed", "worker marked failed");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Multiple batches ─────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
console.log("\n=== Multiple Batches ===");
|
|
97
|
+
|
|
98
|
+
{
|
|
99
|
+
resetWorkerRegistry();
|
|
100
|
+
registerWorker("scout", "Task A", 0, 2, "batch-5");
|
|
101
|
+
registerWorker("worker", "Task B", 1, 2, "batch-5");
|
|
102
|
+
registerWorker("researcher", "Task C", 0, 1, "batch-6");
|
|
103
|
+
|
|
104
|
+
const batches = getWorkerBatches();
|
|
105
|
+
assertEq(batches.size, 2, "two batches");
|
|
106
|
+
assertEq(batches.get("batch-5")!.length, 2, "batch-5 has 2 workers");
|
|
107
|
+
assertEq(batches.get("batch-6")!.length, 1, "batch-6 has 1 worker");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─── hasActiveWorkers with all completed ──────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
console.log("\n=== hasActiveWorkers — all completed ===");
|
|
113
|
+
|
|
114
|
+
{
|
|
115
|
+
resetWorkerRegistry();
|
|
116
|
+
const id1 = registerWorker("scout", "Task A", 0, 2, "batch-7");
|
|
117
|
+
const id2 = registerWorker("worker", "Task B", 1, 2, "batch-7");
|
|
118
|
+
updateWorker(id1, "completed");
|
|
119
|
+
updateWorker(id2, "completed");
|
|
120
|
+
assertTrue(!hasActiveWorkers(), "no active workers when all completed");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ─── Reset clears everything ─────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
console.log("\n=== Reset ===");
|
|
126
|
+
|
|
127
|
+
{
|
|
128
|
+
registerWorker("scout", "Task", 0, 1, "batch-8");
|
|
129
|
+
assertTrue(getActiveWorkers().length > 0, "workers exist before reset");
|
|
130
|
+
resetWorkerRegistry();
|
|
131
|
+
assertEq(getActiveWorkers().length, 0, "no workers after reset");
|
|
132
|
+
assertTrue(!hasActiveWorkers(), "hasActiveWorkers false after reset");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── Update non-existent worker is no-op ──────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
console.log("\n=== Update non-existent worker ===");
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
resetWorkerRegistry();
|
|
141
|
+
// Should not throw
|
|
142
|
+
updateWorker("nonexistent-id", "completed");
|
|
143
|
+
assertEq(getActiveWorkers().length, 0, "no workers created by updating nonexistent");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Summary ──────────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
report();
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// ─── Enums & Literal Unions ────────────────────────────────────────────────
|
|
6
6
|
|
|
7
7
|
export type RiskLevel = 'low' | 'medium' | 'high';
|
|
8
|
-
export type Phase = 'pre-planning' | 'needs-discussion' | 'discussing' | 'researching' | 'planning' | 'executing' | 'verifying' | 'summarizing' | 'advancing' | 'completing-milestone' | 'replanning-slice' | 'complete' | 'paused' | 'blocked';
|
|
8
|
+
export type Phase = 'pre-planning' | 'needs-discussion' | 'discussing' | 'researching' | 'planning' | 'executing' | 'verifying' | 'summarizing' | 'advancing' | 'validating-milestone' | 'completing-milestone' | 'replanning-slice' | 'complete' | 'paused' | 'blocked';
|
|
9
9
|
export type ContinueStatus = 'in_progress' | 'interrupted' | 'compacted';
|
|
10
10
|
|
|
11
11
|
// ─── Roadmap (Milestone-level) ─────────────────────────────────────────────
|
|
@@ -264,6 +264,7 @@ export interface PhaseSkipPreferences {
|
|
|
264
264
|
skip_research?: boolean;
|
|
265
265
|
skip_reassess?: boolean;
|
|
266
266
|
skip_slice_research?: boolean;
|
|
267
|
+
skip_milestone_validation?: boolean;
|
|
267
268
|
}
|
|
268
269
|
|
|
269
270
|
export interface NotificationPreferences {
|
|
@@ -363,3 +364,16 @@ export interface Requirement {
|
|
|
363
364
|
full_content: string; // full requirement text
|
|
364
365
|
superseded_by: string | null; // ID of superseding requirement, or null
|
|
365
366
|
}
|
|
367
|
+
|
|
368
|
+
// ─── Parallel Orchestration Types ────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
export type MergeStrategy = "per-slice" | "per-milestone";
|
|
371
|
+
export type AutoMergeMode = "auto" | "confirm" | "manual";
|
|
372
|
+
|
|
373
|
+
export interface ParallelConfig {
|
|
374
|
+
enabled: boolean;
|
|
375
|
+
max_workers: number;
|
|
376
|
+
budget_ceiling?: number;
|
|
377
|
+
merge_strategy: MergeStrategy;
|
|
378
|
+
auto_merge: AutoMergeMode;
|
|
379
|
+
}
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
mergeDeltaPatches,
|
|
34
34
|
readIsolationMode,
|
|
35
35
|
} from "./isolation.js";
|
|
36
|
+
import { registerWorker, updateWorker } from "./worker-registry.js";
|
|
36
37
|
|
|
37
38
|
const MAX_PARALLEL_TASKS = 8;
|
|
38
39
|
const MAX_CONCURRENCY = 4;
|
|
@@ -626,7 +627,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
626
627
|
};
|
|
627
628
|
|
|
628
629
|
const MAX_RETRIES = 1; // Retry failed tasks once
|
|
630
|
+
const batchId = crypto.randomUUID();
|
|
631
|
+
const batchSize = params.tasks.length;
|
|
629
632
|
const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
|
|
633
|
+
const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
|
|
630
634
|
let result = await runSingleAgent(
|
|
631
635
|
ctx.cwd,
|
|
632
636
|
agents,
|
|
@@ -666,6 +670,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
666
670
|
);
|
|
667
671
|
}
|
|
668
672
|
|
|
673
|
+
updateWorker(workerId, result.exitCode === 0 ? "completed" : "failed");
|
|
669
674
|
allResults[index] = result;
|
|
670
675
|
emitParallelUpdate();
|
|
671
676
|
return result;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker Registry — Tracks active subagent sessions for dashboard visibility.
|
|
3
|
+
*
|
|
4
|
+
* Provides a global registry of currently-running parallel workers so the
|
|
5
|
+
* GSD dashboard overlay can display real-time worker status.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface WorkerEntry {
|
|
9
|
+
id: string;
|
|
10
|
+
agent: string;
|
|
11
|
+
task: string;
|
|
12
|
+
startedAt: number;
|
|
13
|
+
status: "running" | "completed" | "failed";
|
|
14
|
+
/** Index within a parallel batch (0-based) */
|
|
15
|
+
index: number;
|
|
16
|
+
/** Total workers in the parallel batch */
|
|
17
|
+
batchSize: number;
|
|
18
|
+
/** Unique batch identifier for grouping parallel runs */
|
|
19
|
+
batchId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const activeWorkers = new Map<string, WorkerEntry>();
|
|
23
|
+
let workerIdCounter = 0;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Register a new worker. Returns the worker ID for later updates.
|
|
27
|
+
*/
|
|
28
|
+
export function registerWorker(
|
|
29
|
+
agent: string,
|
|
30
|
+
task: string,
|
|
31
|
+
index: number,
|
|
32
|
+
batchSize: number,
|
|
33
|
+
batchId: string,
|
|
34
|
+
): string {
|
|
35
|
+
const id = `worker-${++workerIdCounter}`;
|
|
36
|
+
activeWorkers.set(id, {
|
|
37
|
+
id,
|
|
38
|
+
agent,
|
|
39
|
+
task,
|
|
40
|
+
startedAt: Date.now(),
|
|
41
|
+
status: "running",
|
|
42
|
+
index,
|
|
43
|
+
batchSize,
|
|
44
|
+
batchId,
|
|
45
|
+
});
|
|
46
|
+
return id;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Update worker status when it completes or fails.
|
|
51
|
+
*/
|
|
52
|
+
export function updateWorker(id: string, status: "completed" | "failed"): void {
|
|
53
|
+
const entry = activeWorkers.get(id);
|
|
54
|
+
if (entry) {
|
|
55
|
+
entry.status = status;
|
|
56
|
+
// Remove after a brief display window (5 seconds)
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
activeWorkers.delete(id);
|
|
59
|
+
}, 5000);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get all currently-tracked workers (running + recently completed).
|
|
65
|
+
*/
|
|
66
|
+
export function getActiveWorkers(): WorkerEntry[] {
|
|
67
|
+
return Array.from(activeWorkers.values());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get workers grouped by batch.
|
|
72
|
+
*/
|
|
73
|
+
export function getWorkerBatches(): Map<string, WorkerEntry[]> {
|
|
74
|
+
const batches = new Map<string, WorkerEntry[]>();
|
|
75
|
+
for (const worker of activeWorkers.values()) {
|
|
76
|
+
const batch = batches.get(worker.batchId) ?? [];
|
|
77
|
+
batch.push(worker);
|
|
78
|
+
batches.set(worker.batchId, batch);
|
|
79
|
+
}
|
|
80
|
+
return batches;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if any parallel workers are currently running.
|
|
85
|
+
*/
|
|
86
|
+
export function hasActiveWorkers(): boolean {
|
|
87
|
+
for (const worker of activeWorkers.values()) {
|
|
88
|
+
if (worker.status === "running") return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Reset registry state. Used for testing.
|
|
95
|
+
*/
|
|
96
|
+
export function resetWorkerRegistry(): void {
|
|
97
|
+
activeWorkers.clear();
|
|
98
|
+
workerIdCounter = 0;
|
|
99
|
+
}
|
package/dist/update-check.d.ts
CHANGED
|
@@ -21,4 +21,13 @@ export interface UpdateCheckOptions {
|
|
|
21
21
|
* caches the result, and prints a banner if a newer version is available.
|
|
22
22
|
*/
|
|
23
23
|
export declare function checkForUpdates(options?: UpdateCheckOptions): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Interactive update prompt shown at startup when a newer version is available.
|
|
26
|
+
* Fetches the latest version (with cache), then asks the user whether to
|
|
27
|
+
* update now or skip. Runs at most once per 24 hours (same cache as checkForUpdates).
|
|
28
|
+
* Defaults to skip after 30 seconds of inactivity.
|
|
29
|
+
*
|
|
30
|
+
* Returns true if an update was performed, false otherwise.
|
|
31
|
+
*/
|
|
32
|
+
export declare function checkAndPromptForUpdates(options?: UpdateCheckOptions): Promise<boolean>;
|
|
24
33
|
export {};
|
package/dist/update-check.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { appRoot } from './app-paths.js';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
5
6
|
const CACHE_FILE = join(appRoot, '.update-check');
|
|
6
7
|
const NPM_PACKAGE_NAME = 'gsd-pi';
|
|
7
8
|
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
@@ -88,3 +89,99 @@ export async function checkForUpdates(options = {}) {
|
|
|
88
89
|
clearTimeout(timeout);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
92
|
+
const PROMPT_TIMEOUT_MS = 30_000;
|
|
93
|
+
/**
|
|
94
|
+
* Interactive update prompt shown at startup when a newer version is available.
|
|
95
|
+
* Fetches the latest version (with cache), then asks the user whether to
|
|
96
|
+
* update now or skip. Runs at most once per 24 hours (same cache as checkForUpdates).
|
|
97
|
+
* Defaults to skip after 30 seconds of inactivity.
|
|
98
|
+
*
|
|
99
|
+
* Returns true if an update was performed, false otherwise.
|
|
100
|
+
*/
|
|
101
|
+
export async function checkAndPromptForUpdates(options = {}) {
|
|
102
|
+
const currentVersion = options.currentVersion || process.env.GSD_VERSION || '0.0.0';
|
|
103
|
+
const cachePath = options.cachePath || CACHE_FILE;
|
|
104
|
+
const registryUrl = options.registryUrl || `https://registry.npmjs.org/${NPM_PACKAGE_NAME}/latest`;
|
|
105
|
+
const checkIntervalMs = options.checkIntervalMs ?? CHECK_INTERVAL_MS;
|
|
106
|
+
const fetchTimeoutMs = options.fetchTimeoutMs ?? FETCH_TIMEOUT_MS;
|
|
107
|
+
// Determine latest version (from cache or network)
|
|
108
|
+
let latestVersion = null;
|
|
109
|
+
const cache = readUpdateCache(cachePath);
|
|
110
|
+
if (cache && Date.now() - cache.lastCheck < checkIntervalMs) {
|
|
111
|
+
latestVersion = cache.latestVersion;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const controller = new AbortController();
|
|
115
|
+
const timeout = setTimeout(() => controller.abort(), fetchTimeoutMs);
|
|
116
|
+
try {
|
|
117
|
+
const res = await fetch(registryUrl, { signal: controller.signal });
|
|
118
|
+
clearTimeout(timeout);
|
|
119
|
+
if (res.ok) {
|
|
120
|
+
const data = (await res.json());
|
|
121
|
+
if (data.version) {
|
|
122
|
+
latestVersion = data.version;
|
|
123
|
+
writeUpdateCache({ lastCheck: Date.now(), latestVersion }, cachePath);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Network unavailable — silently skip
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!latestVersion || compareSemver(latestVersion, currentVersion) <= 0) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
// Update available — show interactive prompt
|
|
138
|
+
// Measure visible (ANSI-free) width to size the box, then render with chalk.
|
|
139
|
+
const midContent = ` ${chalk.bold('Update available!')} ${chalk.dim(`v${currentVersion}`)} → ${chalk.bold.green(`v${latestVersion}`)} `;
|
|
140
|
+
const midVisible = ` Update available! v${currentVersion} → v${latestVersion} `;
|
|
141
|
+
const innerWidth = midVisible.length;
|
|
142
|
+
const top = '╔' + '═'.repeat(innerWidth) + '╗';
|
|
143
|
+
const bot = '╚' + '═'.repeat(innerWidth) + '╝';
|
|
144
|
+
process.stderr.write('\n');
|
|
145
|
+
process.stderr.write(` ${chalk.yellow(top)}\n` +
|
|
146
|
+
` ${chalk.yellow('║')}${midContent}${chalk.yellow('║')}\n` +
|
|
147
|
+
` ${chalk.yellow(bot)}\n\n`);
|
|
148
|
+
// Use readline for a simple two-option prompt that works without @clack/prompts
|
|
149
|
+
const readline = await import('node:readline');
|
|
150
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
151
|
+
const choice = await new Promise((resolve) => {
|
|
152
|
+
process.stderr.write(` ${chalk.bold('[1]')} Update now ${chalk.dim(`npm install -g ${NPM_PACKAGE_NAME}@latest`)}\n` +
|
|
153
|
+
` ${chalk.bold('[2]')} Skip\n\n`);
|
|
154
|
+
// Default to skip if the user doesn't respond within PROMPT_TIMEOUT_MS
|
|
155
|
+
const timer = setTimeout(() => {
|
|
156
|
+
process.stderr.write('\n');
|
|
157
|
+
rl.close();
|
|
158
|
+
resolve('2');
|
|
159
|
+
}, PROMPT_TIMEOUT_MS);
|
|
160
|
+
rl.question(` ${chalk.bold('Choose [1/2]:')} `, (answer) => {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
resolve(answer.trim());
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
rl.close();
|
|
166
|
+
// Clean up stdin state so the TUI can start with a clean slate
|
|
167
|
+
process.stdin.removeAllListeners('data');
|
|
168
|
+
process.stdin.removeAllListeners('keypress');
|
|
169
|
+
if (process.stdin.setRawMode)
|
|
170
|
+
process.stdin.setRawMode(false);
|
|
171
|
+
process.stdin.pause();
|
|
172
|
+
if (choice === '1') {
|
|
173
|
+
process.stderr.write(`\n ${chalk.dim('Running:')} npm install -g ${NPM_PACKAGE_NAME}@latest\n\n`);
|
|
174
|
+
try {
|
|
175
|
+
execSync(`npm install -g ${NPM_PACKAGE_NAME}@latest`, { stdio: 'inherit' });
|
|
176
|
+
process.stderr.write(`\n ${chalk.green.bold(`✓ Updated to v${latestVersion}`)}\n\n`);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
process.stderr.write(`\n ${chalk.yellow(`Update failed. You can run: npm install -g ${NPM_PACKAGE_NAME}@latest`)}\n\n`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
process.stderr.write(` ${chalk.dim('Skipped. Run')} gsd update ${chalk.dim('anytime to upgrade.')}\n\n`);
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-pi",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.0",
|
|
4
4
|
"description": "GSD — Get Shit Done coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -56,6 +56,11 @@
|
|
|
56
56
|
"build:native": "node native/scripts/build.js",
|
|
57
57
|
"build:native:dev": "node native/scripts/build.js --dev",
|
|
58
58
|
"dev": "node scripts/dev.js",
|
|
59
|
+
"dev:symlink": "bash scripts/dev-symlink.sh",
|
|
60
|
+
"dev:symlink:remove": "bash scripts/dev-symlink.sh --remove",
|
|
61
|
+
"dev:symlink:status": "bash scripts/dev-symlink.sh --status",
|
|
62
|
+
"dev:clean": "rm -rf ~/.gsd/agent/{extensions,skills,agents,AGENTS.md} && npm run build && npm run pi:install-global",
|
|
63
|
+
"dev:full": "npm run dev:clean && node dist/loader.js",
|
|
59
64
|
"postinstall": "node scripts/link-workspace-packages.cjs && node scripts/postinstall.js",
|
|
60
65
|
"pi:install-global": "node scripts/install-pi-global.js",
|
|
61
66
|
"pi:uninstall-global": "node scripts/uninstall-pi-global.js",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAQX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AA0IrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AAEhE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACtE;AAYD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG;IAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,SAAS,SAAK,GAAG,MAAM,GAAG,SAAS,CA8B/H;AAED,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAiPlF,CAAC;AAoCF,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAuC3F,CAAC"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import Anthropic from "@anthropic-ai/sdk";
|
|
2
1
|
import { getEnvApiKey } from "../env-api-keys.js";
|
|
3
2
|
import { calculateCost } from "../models.js";
|
|
4
3
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
@@ -7,6 +6,14 @@ import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
|
7
6
|
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
8
7
|
import { adjustMaxTokensForThinking, buildBaseOptions } from "./simple-options.js";
|
|
9
8
|
import { transformMessages } from "./transform-messages.js";
|
|
9
|
+
let _AnthropicClass;
|
|
10
|
+
async function getAnthropicClass() {
|
|
11
|
+
if (!_AnthropicClass) {
|
|
12
|
+
const mod = await import("@anthropic-ai/sdk");
|
|
13
|
+
_AnthropicClass = mod.default;
|
|
14
|
+
}
|
|
15
|
+
return _AnthropicClass;
|
|
16
|
+
}
|
|
10
17
|
/**
|
|
11
18
|
* Resolve cache retention preference.
|
|
12
19
|
* Defaults to "short" and uses PI_CACHE_RETENTION for backward compatibility.
|
|
@@ -178,7 +185,7 @@ export const streamAnthropic = (model, context, options) => {
|
|
|
178
185
|
hasImages,
|
|
179
186
|
});
|
|
180
187
|
}
|
|
181
|
-
const { client, isOAuthToken } = createClient(model, apiKey, options?.interleavedThinking ?? true, options?.headers, copilotDynamicHeaders);
|
|
188
|
+
const { client, isOAuthToken } = await createClient(model, apiKey, options?.interleavedThinking ?? true, options?.headers, copilotDynamicHeaders);
|
|
182
189
|
let params = buildParams(model, context, isOAuthToken, options);
|
|
183
190
|
const nextParams = await options?.onPayload?.(params, model);
|
|
184
191
|
if (nextParams !== undefined) {
|
|
@@ -370,7 +377,8 @@ export const streamAnthropic = (model, context, options) => {
|
|
|
370
377
|
if (model.provider === "alibaba-coding-plan") {
|
|
371
378
|
output.errorMessage = `[alibaba-coding-plan] ${output.errorMessage}`;
|
|
372
379
|
}
|
|
373
|
-
|
|
380
|
+
const AnthropicSdk = _AnthropicClass;
|
|
381
|
+
if (AnthropicSdk && error instanceof AnthropicSdk.APIError && error.headers) {
|
|
374
382
|
const retryAfterMs = extractRetryAfterMs(error.headers, error.message);
|
|
375
383
|
if (retryAfterMs !== undefined) {
|
|
376
384
|
output.retryAfterMs = retryAfterMs;
|
|
@@ -442,7 +450,8 @@ export const streamSimpleAnthropic = (model, context, options) => {
|
|
|
442
450
|
function isOAuthToken(apiKey) {
|
|
443
451
|
return apiKey.includes("sk-ant-oat");
|
|
444
452
|
}
|
|
445
|
-
function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynamicHeaders) {
|
|
453
|
+
async function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynamicHeaders) {
|
|
454
|
+
const AnthropicClass = await getAnthropicClass();
|
|
446
455
|
// Adaptive thinking models (Opus 4.6, Sonnet 4.6) have interleaved thinking built-in.
|
|
447
456
|
// The beta header is deprecated on Opus 4.6 and redundant on Sonnet 4.6, so skip it.
|
|
448
457
|
const needsInterleavedBeta = interleavedThinking && !supportsAdaptiveThinking(model.id);
|
|
@@ -452,7 +461,7 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
|
|
|
452
461
|
if (needsInterleavedBeta) {
|
|
453
462
|
betaFeatures.push("interleaved-thinking-2025-05-14");
|
|
454
463
|
}
|
|
455
|
-
const client = new
|
|
464
|
+
const client = new AnthropicClass({
|
|
456
465
|
apiKey: null,
|
|
457
466
|
authToken: apiKey,
|
|
458
467
|
baseURL: model.baseUrl,
|
|
@@ -473,7 +482,7 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
|
|
|
473
482
|
}
|
|
474
483
|
// OAuth: Bearer auth, Claude Code identity headers
|
|
475
484
|
if (isOAuthToken(apiKey)) {
|
|
476
|
-
const client = new
|
|
485
|
+
const client = new AnthropicClass({
|
|
477
486
|
apiKey: null,
|
|
478
487
|
authToken: apiKey,
|
|
479
488
|
baseURL: model.baseUrl,
|
|
@@ -491,7 +500,7 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
|
|
|
491
500
|
// API key auth
|
|
492
501
|
// Alibaba Coding Plan uses Bearer token auth instead of x-api-key
|
|
493
502
|
const isAlibabaProvider = model.provider === "alibaba-coding-plan";
|
|
494
|
-
const client = new
|
|
503
|
+
const client = new AnthropicClass({
|
|
495
504
|
apiKey: isAlibabaProvider ? null : apiKey,
|
|
496
505
|
authToken: isAlibabaProvider ? apiKey : undefined,
|
|
497
506
|
baseURL: model.baseUrl,
|