gsd-pi 2.79.0 → 2.80.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/README.md +94 -47
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/contracts.js +1 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +146 -0
- package/dist/resources/extensions/gsd/auto/phases.js +61 -7
- package/dist/resources/extensions/gsd/auto/session.js +8 -0
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +2 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +2 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +52 -29
- package/dist/resources/extensions/gsd/auto-recovery.js +63 -55
- package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -0
- package/dist/resources/extensions/gsd/auto-start.js +3 -2
- package/dist/resources/extensions/gsd/auto.js +159 -2
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +9 -1
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +41 -45
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +8 -8
- package/dist/resources/extensions/gsd/commands/context.js +1 -1
- package/dist/resources/extensions/gsd/gsd-db.js +34 -1
- package/dist/resources/extensions/gsd/guided-flow.js +40 -0
- package/dist/resources/extensions/gsd/paths.js +5 -1
- package/dist/resources/extensions/gsd/post-execution-checks.js +25 -6
- package/dist/resources/extensions/gsd/preferences-types.js +20 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +3 -3
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +82 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +32 -0
- package/dist/resources/extensions/gsd/unit-context-manifest.js +21 -0
- package/dist/resources/extensions/gsd/uok/audit.js +23 -9
- package/dist/resources/extensions/gsd/uok/contracts.js +69 -1
- package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +3 -0
- package/dist/resources/extensions/gsd/uok/loop-adapter.js +48 -33
- package/dist/resources/extensions/gsd/uok/timeline.js +125 -0
- package/dist/resources/extensions/shared/gsd-phase-state.js +45 -3
- package/dist/resources/extensions/shared/interview-ui.js +15 -4
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/package.json +1 -1
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/dist/workflow-tools.d.ts +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +53 -0
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/workflow-tools.test.ts +129 -2
- package/packages/mcp-server/src/workflow-tools.ts +81 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/rpc-client/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/contracts.ts +87 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +10 -3
- package/src/resources/extensions/gsd/auto/orchestrator.ts +161 -0
- package/src/resources/extensions/gsd/auto/phases.ts +88 -9
- package/src/resources/extensions/gsd/auto/session.ts +11 -0
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +2 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +106 -28
- package/src/resources/extensions/gsd/auto-recovery.ts +59 -53
- package/src/resources/extensions/gsd/auto-runtime-state.ts +7 -0
- package/src/resources/extensions/gsd/auto-start.ts +3 -2
- package/src/resources/extensions/gsd/auto.ts +167 -1
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +14 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -2
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +49 -46
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-shouldblock-basepath.test.ts +97 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +8 -4
- package/src/resources/extensions/gsd/commands/context.ts +1 -1
- package/src/resources/extensions/gsd/gsd-db.ts +35 -1
- package/src/resources/extensions/gsd/guided-flow.ts +47 -0
- package/src/resources/extensions/gsd/interrupted-session.ts +1 -0
- package/src/resources/extensions/gsd/paths.ts +6 -1
- package/src/resources/extensions/gsd/post-execution-checks.ts +31 -6
- package/src/resources/extensions/gsd/preferences-types.ts +23 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-abort-pause-regression.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +108 -1
- package/src/resources/extensions/gsd/tests/auto-runtime-state.test.ts +39 -0
- package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/check-auto-start-pending-gate.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/check-auto-start-ready-guard.test.ts +148 -0
- package/src/resources/extensions/gsd/tests/current-directory-root-homedir-fallback.test.ts +63 -0
- package/src/resources/extensions/gsd/tests/deep-planning-mode-dispatch.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/deep-project-auto-loop.test.ts +63 -2
- package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +109 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +95 -0
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +8 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/pre-exec-gate-loop.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +109 -1
- package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +132 -3
- package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +3 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +84 -1
- package/src/resources/extensions/gsd/unit-context-composer.ts +49 -0
- package/src/resources/extensions/gsd/unit-context-manifest.ts +34 -0
- package/src/resources/extensions/gsd/uok/audit.ts +25 -9
- package/src/resources/extensions/gsd/uok/contracts.ts +105 -0
- package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +4 -0
- package/src/resources/extensions/gsd/uok/loop-adapter.ts +60 -45
- package/src/resources/extensions/gsd/uok/timeline.ts +158 -0
- package/src/resources/extensions/shared/gsd-phase-state.ts +56 -3
- package/src/resources/extensions/shared/interview-ui.ts +18 -5
- package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +43 -1
- package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +41 -0
- /package/dist/web/standalone/.next/static/{J-CU-p_sp45CJHT3R9TJS → V-3Ehy4B24f9FCGiLPWIM}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{J-CU-p_sp45CJHT3R9TJS → V-3Ehy4B24f9FCGiLPWIM}/_ssgManifest.js +0 -0
package/pkg/package.json
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { GSDState } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export interface AutoSessionContext {
|
|
4
|
+
basePath: string;
|
|
5
|
+
trigger: "guided-flow" | "resume" | "auto-loop" | "manual";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface AutoStatus {
|
|
9
|
+
phase: "idle" | "running" | "paused" | "stopped" | "error";
|
|
10
|
+
activeUnit?: {
|
|
11
|
+
unitType: string;
|
|
12
|
+
unitId: string;
|
|
13
|
+
};
|
|
14
|
+
lastTransitionAt?: number;
|
|
15
|
+
transitionCount: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AutoAdvanceResult {
|
|
19
|
+
kind: "advanced" | "blocked" | "paused" | "stopped" | "error";
|
|
20
|
+
reason?: string;
|
|
21
|
+
stateSnapshot?: GSDState;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AutoOrchestrationModule {
|
|
25
|
+
start(sessionContext: AutoSessionContext): Promise<AutoAdvanceResult>;
|
|
26
|
+
advance(): Promise<AutoAdvanceResult>;
|
|
27
|
+
resume(): Promise<AutoAdvanceResult>;
|
|
28
|
+
stop(reason: string): Promise<AutoAdvanceResult>;
|
|
29
|
+
getStatus(): AutoStatus;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface DispatchAdapter {
|
|
33
|
+
decideNextUnit(): Promise<{
|
|
34
|
+
unitType: string;
|
|
35
|
+
unitId: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
preconditions: string[];
|
|
38
|
+
} | null>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RecoveryAdapter {
|
|
42
|
+
classifyAndRecover(input: {
|
|
43
|
+
error: unknown;
|
|
44
|
+
unitType?: string;
|
|
45
|
+
unitId?: string;
|
|
46
|
+
}): Promise<{
|
|
47
|
+
action: "retry" | "escalate" | "stop";
|
|
48
|
+
reason: string;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface WorktreeAdapter {
|
|
53
|
+
prepareForUnit(unitType: string, unitId: string): Promise<void>;
|
|
54
|
+
syncAfterUnit(unitType: string, unitId: string): Promise<void>;
|
|
55
|
+
cleanupOnStop(reason: string): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface HealthAdapter {
|
|
59
|
+
preAdvanceGate(): Promise<{ allow: boolean; reason?: string }>;
|
|
60
|
+
postAdvanceRecord(result: AutoAdvanceResult): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface RuntimePersistenceAdapter {
|
|
64
|
+
ensureLockOwnership(): Promise<void>;
|
|
65
|
+
journalTransition(event: {
|
|
66
|
+
name: string;
|
|
67
|
+
reason?: string;
|
|
68
|
+
unitType?: string;
|
|
69
|
+
unitId?: string;
|
|
70
|
+
}): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface NotificationAdapter {
|
|
74
|
+
notifyLifecycle(event: {
|
|
75
|
+
name: string;
|
|
76
|
+
detail?: string;
|
|
77
|
+
}): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface AutoOrchestratorDeps {
|
|
81
|
+
dispatch: DispatchAdapter;
|
|
82
|
+
recovery: RecoveryAdapter;
|
|
83
|
+
worktree: WorktreeAdapter;
|
|
84
|
+
health: HealthAdapter;
|
|
85
|
+
runtime: RuntimePersistenceAdapter;
|
|
86
|
+
notifications: NotificationAdapter;
|
|
87
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
8
8
|
|
|
9
9
|
import type { AutoSession } from "./session.js";
|
|
10
|
+
import type { ErrorContext } from "./types.js";
|
|
10
11
|
import type { GSDPreferences } from "../preferences.js";
|
|
11
12
|
import type { GSDState } from "../types.js";
|
|
12
13
|
import type { SessionLockStatus } from "../session-lock.js";
|
|
@@ -24,6 +25,12 @@ import type { MergeReconcileResult } from "../auto-recovery.js";
|
|
|
24
25
|
import type { UokTurnObserver } from "../uok/contracts.js";
|
|
25
26
|
import type { PreflightResult } from "../clean-root-preflight.js";
|
|
26
27
|
|
|
28
|
+
type PauseAutoFn = (
|
|
29
|
+
ctx?: ExtensionContext,
|
|
30
|
+
pi?: ExtensionAPI,
|
|
31
|
+
errorContext?: ErrorContext,
|
|
32
|
+
) => Promise<void>;
|
|
33
|
+
|
|
27
34
|
/**
|
|
28
35
|
* Dependencies injected by the caller (auto.ts startAuto) so autoLoop
|
|
29
36
|
* can access private functions from auto.ts without exporting them.
|
|
@@ -39,7 +46,7 @@ export interface LoopDeps {
|
|
|
39
46
|
pi?: ExtensionAPI,
|
|
40
47
|
reason?: string,
|
|
41
48
|
) => Promise<void>;
|
|
42
|
-
pauseAuto:
|
|
49
|
+
pauseAuto: PauseAutoFn;
|
|
43
50
|
clearUnitTimeout: () => void;
|
|
44
51
|
updateProgressWidget: (
|
|
45
52
|
ctx: ExtensionContext,
|
|
@@ -245,7 +252,7 @@ export interface LoopDeps {
|
|
|
245
252
|
prefs: GSDPreferences | undefined;
|
|
246
253
|
buildSnapshotOpts: () => CloseoutOptions & Record<string, unknown>;
|
|
247
254
|
buildRecoveryContext: () => unknown;
|
|
248
|
-
pauseAuto:
|
|
255
|
+
pauseAuto: PauseAutoFn;
|
|
249
256
|
}) => void;
|
|
250
257
|
|
|
251
258
|
// Prompt helpers
|
|
@@ -271,7 +278,7 @@ export interface LoopDeps {
|
|
|
271
278
|
) => Promise<"dispatched" | "continue" | "retry">;
|
|
272
279
|
runPostUnitVerification: (
|
|
273
280
|
vctx: VerificationContext,
|
|
274
|
-
pauseAuto:
|
|
281
|
+
pauseAuto: PauseAutoFn,
|
|
275
282
|
) => Promise<VerificationResult>;
|
|
276
283
|
postUnitPostVerification: (
|
|
277
284
|
pctx: PostUnitContext,
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { AutoAdvanceResult, AutoOrchestrationModule, AutoOrchestratorDeps, AutoSessionContext, AutoStatus } from "./contracts.js";
|
|
2
|
+
|
|
3
|
+
function now(): number {
|
|
4
|
+
return Date.now();
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class AutoOrchestrator implements AutoOrchestrationModule {
|
|
8
|
+
private status: AutoStatus = {
|
|
9
|
+
phase: "idle",
|
|
10
|
+
transitionCount: 0,
|
|
11
|
+
};
|
|
12
|
+
private readonly deps: AutoOrchestratorDeps;
|
|
13
|
+
private lastAdvanceKey: string | null = null;
|
|
14
|
+
|
|
15
|
+
public constructor(deps: AutoOrchestratorDeps) {
|
|
16
|
+
this.deps = deps;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async start(_sessionContext: AutoSessionContext): Promise<AutoAdvanceResult> {
|
|
20
|
+
this.lastAdvanceKey = null;
|
|
21
|
+
this.status.phase = "running";
|
|
22
|
+
this.bumpTransition();
|
|
23
|
+
await this.deps.runtime.journalTransition({ name: "start" });
|
|
24
|
+
await this.deps.notifications.notifyLifecycle({ name: "start" });
|
|
25
|
+
return this.advance();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async advance(): Promise<AutoAdvanceResult> {
|
|
29
|
+
try {
|
|
30
|
+
await this.deps.runtime.ensureLockOwnership();
|
|
31
|
+
const gate = await this.deps.health.preAdvanceGate();
|
|
32
|
+
if (!gate.allow) {
|
|
33
|
+
const blocked: AutoAdvanceResult = { kind: "blocked", reason: gate.reason ?? "health gate blocked" };
|
|
34
|
+
await this.deps.runtime.journalTransition({ name: "advance-blocked", reason: blocked.reason });
|
|
35
|
+
await this.deps.health.postAdvanceRecord(blocked);
|
|
36
|
+
return blocked;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const decision = await this.deps.dispatch.decideNextUnit();
|
|
40
|
+
if (!decision) {
|
|
41
|
+
const stopped: AutoAdvanceResult = { kind: "stopped", reason: "no remaining units" };
|
|
42
|
+
this.status.phase = "stopped";
|
|
43
|
+
this.status.activeUnit = undefined;
|
|
44
|
+
this.lastAdvanceKey = null;
|
|
45
|
+
this.bumpTransition();
|
|
46
|
+
await this.deps.runtime.journalTransition({ name: "advance-stopped", reason: stopped.reason });
|
|
47
|
+
await this.deps.health.postAdvanceRecord(stopped);
|
|
48
|
+
return stopped;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const nextKey = `${decision.unitType}:${decision.unitId}`;
|
|
52
|
+
if (this.lastAdvanceKey === nextKey) {
|
|
53
|
+
const blocked: AutoAdvanceResult = { kind: "blocked", reason: "idempotent advance: unit already active" };
|
|
54
|
+
await this.deps.runtime.journalTransition({
|
|
55
|
+
name: "advance-blocked",
|
|
56
|
+
reason: blocked.reason,
|
|
57
|
+
unitType: decision.unitType,
|
|
58
|
+
unitId: decision.unitId,
|
|
59
|
+
});
|
|
60
|
+
await this.deps.health.postAdvanceRecord(blocked);
|
|
61
|
+
return blocked;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.status.activeUnit = { unitType: decision.unitType, unitId: decision.unitId };
|
|
65
|
+
this.status.phase = "running";
|
|
66
|
+
this.lastAdvanceKey = nextKey;
|
|
67
|
+
this.bumpTransition();
|
|
68
|
+
|
|
69
|
+
await this.deps.runtime.journalTransition({
|
|
70
|
+
name: "advance",
|
|
71
|
+
reason: decision.reason,
|
|
72
|
+
unitType: decision.unitType,
|
|
73
|
+
unitId: decision.unitId,
|
|
74
|
+
});
|
|
75
|
+
await this.deps.worktree.prepareForUnit(decision.unitType, decision.unitId);
|
|
76
|
+
await this.deps.worktree.syncAfterUnit(decision.unitType, decision.unitId);
|
|
77
|
+
|
|
78
|
+
const advanced: AutoAdvanceResult = { kind: "advanced" };
|
|
79
|
+
await this.deps.health.postAdvanceRecord(advanced);
|
|
80
|
+
return advanced;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const recovery = await this.deps.recovery.classifyAndRecover({
|
|
83
|
+
error,
|
|
84
|
+
unitType: this.status.activeUnit?.unitType,
|
|
85
|
+
unitId: this.status.activeUnit?.unitId,
|
|
86
|
+
});
|
|
87
|
+
const result: AutoAdvanceResult = recovery.action === "retry"
|
|
88
|
+
? { kind: "paused", reason: recovery.reason }
|
|
89
|
+
: recovery.action === "escalate"
|
|
90
|
+
? { kind: "error", reason: recovery.reason }
|
|
91
|
+
: { kind: "stopped", reason: recovery.reason };
|
|
92
|
+
|
|
93
|
+
if (result.kind === "paused") {
|
|
94
|
+
this.status.phase = "paused";
|
|
95
|
+
} else if (result.kind === "stopped") {
|
|
96
|
+
this.status.phase = "stopped";
|
|
97
|
+
} else {
|
|
98
|
+
this.status.phase = "error";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (result.kind === "stopped") {
|
|
102
|
+
this.lastAdvanceKey = null;
|
|
103
|
+
this.status.activeUnit = undefined;
|
|
104
|
+
}
|
|
105
|
+
this.bumpTransition();
|
|
106
|
+
|
|
107
|
+
const journalName = result.kind === "paused"
|
|
108
|
+
? "advance-paused"
|
|
109
|
+
: result.kind === "stopped"
|
|
110
|
+
? "advance-stopped"
|
|
111
|
+
: "advance-error";
|
|
112
|
+
await this.deps.runtime.journalTransition({ name: journalName, reason: recovery.reason });
|
|
113
|
+
|
|
114
|
+
if (result.kind === "paused") {
|
|
115
|
+
await this.deps.notifications.notifyLifecycle({ name: "pause", detail: recovery.reason });
|
|
116
|
+
} else if (result.kind === "stopped") {
|
|
117
|
+
await this.deps.notifications.notifyLifecycle({ name: "stopped", detail: recovery.reason });
|
|
118
|
+
} else if (result.kind === "error") {
|
|
119
|
+
await this.deps.notifications.notifyLifecycle({ name: "error", detail: recovery.reason });
|
|
120
|
+
}
|
|
121
|
+
await this.deps.health.postAdvanceRecord(result);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public async resume(): Promise<AutoAdvanceResult> {
|
|
127
|
+
this.lastAdvanceKey = null;
|
|
128
|
+
this.status.phase = "running";
|
|
129
|
+
this.bumpTransition();
|
|
130
|
+
await this.deps.runtime.journalTransition({ name: "resume" });
|
|
131
|
+
await this.deps.notifications.notifyLifecycle({ name: "resume" });
|
|
132
|
+
return this.advance();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public async stop(reason: string): Promise<AutoAdvanceResult> {
|
|
136
|
+
if (this.status.phase === "stopped") {
|
|
137
|
+
return { kind: "stopped", reason };
|
|
138
|
+
}
|
|
139
|
+
await this.deps.worktree.cleanupOnStop(reason);
|
|
140
|
+
this.status.phase = "stopped";
|
|
141
|
+
this.status.activeUnit = undefined;
|
|
142
|
+
this.lastAdvanceKey = null;
|
|
143
|
+
this.bumpTransition();
|
|
144
|
+
await this.deps.runtime.journalTransition({ name: "stop", reason });
|
|
145
|
+
await this.deps.notifications.notifyLifecycle({ name: "stop", detail: reason });
|
|
146
|
+
return { kind: "stopped", reason };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public getStatus(): AutoStatus {
|
|
150
|
+
return { ...this.status, activeUnit: this.status.activeUnit ? { ...this.status.activeUnit } : undefined };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private bumpTransition(): void {
|
|
154
|
+
this.status.transitionCount += 1;
|
|
155
|
+
this.status.lastTransitionAt = now();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function createAutoOrchestrator(deps: AutoOrchestratorDeps): AutoOrchestrationModule {
|
|
160
|
+
return new AutoOrchestrator(deps);
|
|
161
|
+
}
|
|
@@ -55,7 +55,7 @@ import { writeUnitRuntimeRecord } from "../unit-runtime.js";
|
|
|
55
55
|
import { withTimeout, FINALIZE_PRE_TIMEOUT_MS, FINALIZE_POST_TIMEOUT_MS } from "./finalize-timeout.js";
|
|
56
56
|
import { getEligibleSlices } from "../slice-parallel-eligibility.js";
|
|
57
57
|
import { startSliceParallel } from "../slice-parallel-orchestrator.js";
|
|
58
|
-
import { isDbAvailable, getMilestoneSlices } from "../gsd-db.js";
|
|
58
|
+
import { isDbAvailable, getMilestoneSlices, refreshOpenDatabaseFromDisk } from "../gsd-db.js";
|
|
59
59
|
import type { MinimalModelRegistry } from "../context-budget.js";
|
|
60
60
|
import { ensurePlanV2Graph, isEmptyPlanV2GraphResult, isMissingFinalizedContextResult } from "../uok/plan-v2.js";
|
|
61
61
|
import { resolveUokFlags } from "../uok/flags.js";
|
|
@@ -76,6 +76,12 @@ function isSamePathLocal(a: string, b: string): boolean {
|
|
|
76
76
|
return normalizeWorktreePathForCompare(a) === normalizeWorktreePathForCompare(b);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function refreshPlanSliceRecoveryDbIfNeeded(unitType: string): boolean {
|
|
80
|
+
if (unitType !== "plan-slice") return true;
|
|
81
|
+
if (!isDbAvailable()) return true;
|
|
82
|
+
return refreshOpenDatabaseFromDisk();
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
// ─── Session timeout auto-resume state ────────────────────────────────────────
|
|
80
86
|
|
|
81
87
|
let consecutiveSessionTimeouts = 0;
|
|
@@ -232,6 +238,33 @@ async function emitCancelledUnitEnd(
|
|
|
232
238
|
});
|
|
233
239
|
}
|
|
234
240
|
|
|
241
|
+
export function _buildCancelledUnitStopReason(
|
|
242
|
+
unitType: string,
|
|
243
|
+
unitId: string,
|
|
244
|
+
errorContext?: { message: string; category: string },
|
|
245
|
+
): {
|
|
246
|
+
notifyMessage: string;
|
|
247
|
+
stopReason: string;
|
|
248
|
+
loopReason: "session-failed" | "unit-aborted";
|
|
249
|
+
} {
|
|
250
|
+
const cancellationMessage = errorContext?.message ?? "unknown";
|
|
251
|
+
const isSessionCreationFailure = errorContext?.category === "session-failed";
|
|
252
|
+
|
|
253
|
+
if (isSessionCreationFailure) {
|
|
254
|
+
return {
|
|
255
|
+
notifyMessage: `Session creation failed for ${unitType} ${unitId}: ${cancellationMessage}. Stopping auto-mode.`,
|
|
256
|
+
stopReason: `Session creation failed: ${cancellationMessage}`,
|
|
257
|
+
loopReason: "session-failed",
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
notifyMessage: `Unit ${unitType} ${unitId} aborted after dispatch: ${cancellationMessage}. Stopping auto-mode.`,
|
|
263
|
+
stopReason: `Unit aborted: ${cancellationMessage}`,
|
|
264
|
+
loopReason: "unit-aborted",
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
235
268
|
async function failClosedOnFinalizeTimeout(
|
|
236
269
|
ic: IterationContext,
|
|
237
270
|
iterData: IterationData,
|
|
@@ -994,7 +1027,10 @@ export async function runDispatch(
|
|
|
994
1027
|
// See: https://github.com/gsd-build/gsd-2/issues/2474
|
|
995
1028
|
if (dispatchResult.level === "warning") {
|
|
996
1029
|
ctx.ui.notify(dispatchResult.reason, "warning");
|
|
997
|
-
await deps.pauseAuto(ctx, pi
|
|
1030
|
+
await deps.pauseAuto(ctx, pi, {
|
|
1031
|
+
message: dispatchResult.reason,
|
|
1032
|
+
category: "unknown",
|
|
1033
|
+
});
|
|
998
1034
|
} else {
|
|
999
1035
|
await closeoutAndStop(ctx, pi, s, deps, dispatchResult.reason);
|
|
1000
1036
|
}
|
|
@@ -1065,7 +1101,16 @@ export async function runDispatch(
|
|
|
1065
1101
|
`Stuck recovery: artifact for ${unitType} ${unitId} found on disk. Invalidating caches.`,
|
|
1066
1102
|
"info",
|
|
1067
1103
|
);
|
|
1104
|
+
if (!refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
|
|
1105
|
+
ctx.ui.notify(
|
|
1106
|
+
`Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed. Keeping stuck state for retry.`,
|
|
1107
|
+
"warning",
|
|
1108
|
+
);
|
|
1109
|
+
return { action: "continue" };
|
|
1110
|
+
}
|
|
1068
1111
|
deps.invalidateAllCaches();
|
|
1112
|
+
loopState.recentUnits.length = 0;
|
|
1113
|
+
loopState.stuckRecoveryAttempts = 0;
|
|
1069
1114
|
return { action: "continue" };
|
|
1070
1115
|
}
|
|
1071
1116
|
ctx.ui.notify(
|
|
@@ -1075,6 +1120,32 @@ export async function runDispatch(
|
|
|
1075
1120
|
deps.invalidateAllCaches();
|
|
1076
1121
|
} else {
|
|
1077
1122
|
// Level 2: hard stop — genuinely stuck
|
|
1123
|
+
deps.invalidateAllCaches();
|
|
1124
|
+
const artifactExists = verifyExpectedArtifact(
|
|
1125
|
+
unitType,
|
|
1126
|
+
unitId,
|
|
1127
|
+
s.basePath,
|
|
1128
|
+
);
|
|
1129
|
+
if (artifactExists && unitType !== "complete-milestone") {
|
|
1130
|
+
debugLog("autoLoop", {
|
|
1131
|
+
phase: "stuck-recovery",
|
|
1132
|
+
level: 2,
|
|
1133
|
+
action: "artifact-found",
|
|
1134
|
+
});
|
|
1135
|
+
ctx.ui.notify(
|
|
1136
|
+
`Stuck recovery: artifact for ${unitType} ${unitId} found on disk after cache invalidation. Continuing.`,
|
|
1137
|
+
"info",
|
|
1138
|
+
);
|
|
1139
|
+
if (refreshPlanSliceRecoveryDbIfNeeded(unitType)) {
|
|
1140
|
+
loopState.recentUnits.length = 0;
|
|
1141
|
+
loopState.stuckRecoveryAttempts = 0;
|
|
1142
|
+
return { action: "continue" };
|
|
1143
|
+
}
|
|
1144
|
+
ctx.ui.notify(
|
|
1145
|
+
`Stuck recovery found ${unitType} ${unitId} artifacts, but the DB refresh failed. Stopping for manual recovery.`,
|
|
1146
|
+
"warning",
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1078
1149
|
debugLog("autoLoop", {
|
|
1079
1150
|
phase: "stuck-detected",
|
|
1080
1151
|
unitType,
|
|
@@ -1470,7 +1541,12 @@ export async function runUnitPhase(
|
|
|
1470
1541
|
s.lastGitActionFailure = null;
|
|
1471
1542
|
s.lastGitActionStatus = null;
|
|
1472
1543
|
s.lastUnitAgentEndMessages = null;
|
|
1473
|
-
setCurrentPhase(unitType
|
|
1544
|
+
setCurrentPhase(unitType, {
|
|
1545
|
+
basePath: s.basePath,
|
|
1546
|
+
traceId: ic.flowId,
|
|
1547
|
+
turnId: `iter-${ic.iteration}`,
|
|
1548
|
+
causedBy: "unit-start",
|
|
1549
|
+
});
|
|
1474
1550
|
s.lastToolInvocationError = null; // #2883: clear stale error from previous unit
|
|
1475
1551
|
const unitStartSeq = ic.nextSeq();
|
|
1476
1552
|
deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
|
|
@@ -1866,13 +1942,16 @@ export async function runUnitPhase(
|
|
|
1866
1942
|
}
|
|
1867
1943
|
await deps.autoCommitUnit?.(s.basePath, unitType, unitId, ctx);
|
|
1868
1944
|
await emitCancelledUnitEnd(ic, unitType, unitId, unitStartSeq, unitResult.errorContext);
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1945
|
+
|
|
1946
|
+
const cancelledStop = _buildCancelledUnitStopReason(
|
|
1947
|
+
unitType,
|
|
1948
|
+
unitId,
|
|
1949
|
+
unitResult.errorContext,
|
|
1872
1950
|
);
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1951
|
+
ctx.ui.notify(cancelledStop.notifyMessage, "warning");
|
|
1952
|
+
await deps.stopAuto(ctx, pi, cancelledStop.stopReason);
|
|
1953
|
+
debugLog("autoLoop", { phase: "exit", reason: cancelledStop.loopReason });
|
|
1954
|
+
return { action: "break", reason: cancelledStop.loopReason };
|
|
1876
1955
|
}
|
|
1877
1956
|
|
|
1878
1957
|
// ── Immediate unit closeout (metrics, activity log, memory) ────────
|
|
@@ -21,6 +21,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
|
|
|
21
21
|
import type { GitServiceImpl } from "../git-service.js";
|
|
22
22
|
import type { CaptureEntry } from "../captures.js";
|
|
23
23
|
import type { BudgetAlertLevel } from "../auto-budget.js";
|
|
24
|
+
import type { AutoOrchestrationModule } from "./contracts.js";
|
|
24
25
|
import { resolveWorktreeProjectRoot } from "../worktree-root.js";
|
|
25
26
|
import { normalizeRealPath } from "../paths.js";
|
|
26
27
|
import type { MilestoneScope } from "../workspace.js";
|
|
@@ -229,6 +230,9 @@ export class AutoSession {
|
|
|
229
230
|
/** Cleanup function returned by startCommandPolling(); null when not running. */
|
|
230
231
|
commandPollingCleanup: (() => void) | null = null;
|
|
231
232
|
|
|
233
|
+
// ── Orchestration seam ───────────────────────────────────────────────────
|
|
234
|
+
orchestration: AutoOrchestrationModule | null = null;
|
|
235
|
+
|
|
232
236
|
// ── Loop promise state ──────────────────────────────────────────────────
|
|
233
237
|
// Per-unit resolve function and session-switch guard live at module level
|
|
234
238
|
// in auto-loop.ts (_currentResolve, _sessionSwitchInFlight).
|
|
@@ -354,10 +358,14 @@ export class AutoSession {
|
|
|
354
358
|
// Remote command polling — cleanup must be called before reset (auto.ts stopAuto)
|
|
355
359
|
this.commandPollingCleanup = null;
|
|
356
360
|
|
|
361
|
+
// Orchestration seam
|
|
362
|
+
this.orchestration = null;
|
|
363
|
+
|
|
357
364
|
// Loop promise state lives in auto-loop.ts module scope
|
|
358
365
|
}
|
|
359
366
|
|
|
360
367
|
toJSON(): Record<string, unknown> {
|
|
368
|
+
const orchestrationStatus = this.orchestration?.getStatus();
|
|
361
369
|
return {
|
|
362
370
|
active: this.active,
|
|
363
371
|
paused: this.paused,
|
|
@@ -367,6 +375,9 @@ export class AutoSession {
|
|
|
367
375
|
activeRunDir: this.activeRunDir,
|
|
368
376
|
currentMilestoneId: this.currentMilestoneId,
|
|
369
377
|
currentUnit: this.currentUnit,
|
|
378
|
+
orchestrationPhase: orchestrationStatus?.phase,
|
|
379
|
+
orchestrationTransitionCount: orchestrationStatus?.transitionCount,
|
|
380
|
+
orchestrationLastTransitionAt: orchestrationStatus?.lastTransitionAt,
|
|
370
381
|
unitDispatchCount: Object.fromEntries(this.unitDispatchCount),
|
|
371
382
|
};
|
|
372
383
|
}
|
|
@@ -159,9 +159,9 @@ export function diagnoseExpectedArtifact(
|
|
|
159
159
|
}
|
|
160
160
|
return `${relSliceFile(base, mid, sid!, "RESEARCH")} (slice research)`;
|
|
161
161
|
case "plan-slice":
|
|
162
|
-
return `${relSliceFile(base, mid, sid!, "PLAN")} (slice plan)`;
|
|
162
|
+
return `${relSliceFile(base, mid, sid!, "PLAN")} plus tasks/T##-PLAN.md files (slice plan and task plans)`;
|
|
163
163
|
case "refine-slice":
|
|
164
|
-
return `${relSliceFile(base, mid, sid!, "PLAN")} (refined slice plan
|
|
164
|
+
return `${relSliceFile(base, mid, sid!, "PLAN")} plus tasks/T##-PLAN.md files (refined slice plan and task plans)`;
|
|
165
165
|
case "execute-task": {
|
|
166
166
|
return `Task ${tid} marked [x] in ${relSliceFile(base, mid, sid!, "PLAN")} + summary written`;
|
|
167
167
|
}
|
|
@@ -692,6 +692,7 @@ export const DISPATCH_RULES: DispatchRule[] = [
|
|
|
692
692
|
const contextFile = resolveMilestoneFile(basePath, mid, "CONTEXT");
|
|
693
693
|
const hasContext = !!(contextFile && (await loadFile(contextFile)));
|
|
694
694
|
if (hasContext) return null; // fall through to next rule
|
|
695
|
+
if (prefs?.planning_depth === "deep") return null;
|
|
695
696
|
// H6 fix (#4973): keep the non-deep auto-mode bypass, but do not
|
|
696
697
|
// pre-verify deep planning's user-facing milestone approval gate.
|
|
697
698
|
if (shouldBypassMilestoneDepthGateInAuto(prefs)) {
|