@gajae-code/coding-agent 0.11.7 → 0.11.9
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/CHANGELOG.md +73 -1
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/config/keybindings.d.ts +33 -3
- package/dist/types/config/model-profile-activation.d.ts +4 -3
- package/dist/types/config/model-profile-contract.d.ts +38 -0
- package/dist/types/config/model-profiles.d.ts +2 -3
- package/dist/types/config/settings-schema.d.ts +15 -2
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +12 -5
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +41 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +32 -17
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +17 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +135 -4
- package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +44 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +1 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +24 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +5 -3
- package/dist/types/main.d.ts +8 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/tool-status-header.d.ts +3 -2
- package/dist/types/modes/components/welcome.d.ts +4 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +5 -2
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/runtime-mcp/redaction.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +7 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +25 -0
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +15 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +128 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +1 -1
- package/dist/types/sdk/host/query/handlers.d.ts +9 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/startup-capability.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +22 -3
- package/dist/types/session/internal/managed-session-storage.d.ts +24 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-manager.d.ts +5 -1
- package/dist/types/session/session-storage.d.ts +31 -0
- package/dist/types/skill-state/active-state.d.ts +4 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +94 -0
- package/dist/types/tools/ask.d.ts +454 -174
- package/dist/types/tools/image-gen.d.ts +47 -1
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/package.json +8 -8
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-test-manifest.ts +4 -4
- package/src/cli/args.ts +2 -92
- package/src/cli/mcp-cli.ts +4 -21
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli.ts +50 -0
- package/src/commands/deep-interview.ts +9 -4
- package/src/commands/sdk.ts +2 -2
- package/src/commit/agentic/index.ts +3 -3
- package/src/commit/map-reduce/index.ts +2 -2
- package/src/config/keybindings.ts +157 -24
- package/src/config/model-profile-activation.ts +7 -18
- package/src/config/model-profile-contract.ts +161 -0
- package/src/config/model-profiles.ts +2 -3
- package/src/config/model-registry.ts +11 -1
- package/src/config/settings-schema.ts +17 -2
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +80 -165
- package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/py/executor.ts +30 -11
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
- package/src/gjc-runtime/deep-interview-ambiguity.ts +68 -47
- package/src/gjc-runtime/deep-interview-draft.ts +842 -0
- package/src/gjc-runtime/deep-interview-payload.ts +262 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +294 -226
- package/src/gjc-runtime/deep-interview-repair.ts +1674 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +264 -103
- package/src/gjc-runtime/deep-interview-state.ts +916 -4
- package/src/gjc-runtime/launch-tmux.ts +1 -1
- package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/gjc-runtime/ralplan-runtime.ts +78 -4
- package/src/gjc-runtime/repository-binding.ts +267 -0
- package/src/gjc-runtime/session-state-sidecar.ts +25 -9
- package/src/gjc-runtime/state-writer.ts +166 -2
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
- package/src/gjc-runtime/tmux-sessions.ts +45 -6
- package/src/gjc-runtime/ultragoal-guard.ts +5 -5
- package/src/gjc-runtime/ultragoal-runtime.ts +181 -19
- package/src/gjc-runtime/workflow-manifest.generated.json +386 -1
- package/src/gjc-runtime/workflow-manifest.ts +191 -5
- package/src/internal-urls/docs-index.generated.ts +8 -6
- package/src/internal-urls/local-protocol.ts +33 -16
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +25 -10
- package/src/modes/DESIGN.md +5 -0
- package/src/modes/acp/acp-agent.ts +50 -16
- package/src/modes/components/model-selector.ts +42 -8
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/tool-status-header.ts +20 -4
- package/src/modes/components/welcome.ts +32 -20
- package/src/modes/controllers/command-controller.ts +97 -54
- package/src/modes/controllers/event-controller.ts +10 -0
- package/src/modes/controllers/input-controller.ts +7 -2
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +46 -54
- package/src/modes/interactive-mode.ts +70 -36
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +5 -3
- package/src/prompts/agents/architect.md +0 -1
- package/src/prompts/agents/critic.md +0 -1
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +7 -4
- package/src/prompts/tools/cron.md +3 -1
- package/src/runtime-mcp/json-rpc.ts +7 -4
- package/src/runtime-mcp/manager.ts +51 -53
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/redaction.ts +30 -0
- package/src/runtime-mcp/transports/http.ts +13 -6
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/lifecycle.ts +185 -36
- package/src/sdk/broker/session-index.ts +11 -1
- package/src/sdk/bus/chat-daemon-control.ts +20 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +9 -1
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/index.ts +240 -65
- package/src/sdk/bus/lifecycle-commands.ts +47 -3
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +27 -2
- package/src/sdk/bus/prompt-reconciliation.ts +239 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/telegram-daemon-contract.ts +15 -3
- package/src/sdk/bus/telegram-daemon-control.ts +145 -33
- package/src/sdk/bus/telegram-daemon.ts +853 -112
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/host/control/dispatch.ts +1 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +2 -6
- package/src/sdk/host/query/handlers.ts +70 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +47 -0
- package/src/sdk/protocol/operation-registry.ts +18 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +2 -0
- package/src/sdk/startup-capability.ts +9 -1
- package/src/session/agent-session.ts +138 -5
- package/src/session/blob-store.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +998 -133
- package/src/session/internal/managed-session-storage.ts +268 -203
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +406 -360
- package/src/session/session-storage.ts +151 -11
- package/src/skill-state/active-state.ts +10 -0
- package/src/task/executor.ts +13 -5
- package/src/task/gjc-command.ts +2 -2
- package/src/task/index.ts +121 -15
- package/src/task/receipt.ts +3 -0
- package/src/task/types.ts +34 -3
- package/src/tools/ask.ts +199 -88
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/image-gen.ts +178 -6
- package/src/tools/index.ts +4 -0
- package/src/tools/render-utils.ts +7 -1
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/url-guard.ts +3 -1
- package/src/web/scrapers/docs-rs.ts +30 -5
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type WorkflowStateMutationOwner,
|
|
12
12
|
type WorkflowStateReceipt,
|
|
13
13
|
} from "../skill-state/workflow-state-contract";
|
|
14
|
+
import { validateDeepInterviewV1Envelope } from "./deep-interview-state";
|
|
14
15
|
import {
|
|
15
16
|
activeEntryPath as layoutActiveEntryPath,
|
|
16
17
|
activeSnapshotPath as layoutActiveSnapshotPath,
|
|
@@ -77,6 +78,47 @@ export interface WorkflowEnvelopeIntegrityMismatch {
|
|
|
77
78
|
expected: string;
|
|
78
79
|
actual: string;
|
|
79
80
|
}
|
|
81
|
+
export type WorkflowReceiptVerification =
|
|
82
|
+
| "legacy"
|
|
83
|
+
| "native-valid"
|
|
84
|
+
| "receipt-malformed"
|
|
85
|
+
| "receipt-missing"
|
|
86
|
+
| "checksum-mismatch";
|
|
87
|
+
|
|
88
|
+
export interface GuardedWorkflowEnvelopeTransformOptions extends StateWriterOptions {
|
|
89
|
+
expectedRevision: number;
|
|
90
|
+
/** Additional native schema validation applied to both current and candidate envelopes. */
|
|
91
|
+
validate?: (value: Record<string, unknown>, legacy: boolean) => void;
|
|
92
|
+
transform: (
|
|
93
|
+
current: Record<string, unknown>,
|
|
94
|
+
context: { revision: number; legacy: boolean },
|
|
95
|
+
) =>
|
|
96
|
+
| { kind: "noop" }
|
|
97
|
+
| { kind: "write"; value: Record<string, unknown> }
|
|
98
|
+
| Promise<{ kind: "noop" } | { kind: "write"; value: Record<string, unknown> }>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface GuardedWorkflowEnvelopeWarning {
|
|
102
|
+
code: "DI_POST_COMMIT_AUDIT_FAILED";
|
|
103
|
+
message: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type GuardedWorkflowEnvelopeTransformResult =
|
|
107
|
+
| { path: string; written: false; reason: "noop"; revision: number; warnings: [] }
|
|
108
|
+
| {
|
|
109
|
+
path: string;
|
|
110
|
+
written: true;
|
|
111
|
+
revision: number;
|
|
112
|
+
stamped: Record<string, unknown>;
|
|
113
|
+
warnings: GuardedWorkflowEnvelopeWarning[];
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export class GuardedWorkflowEnvelopeError extends Error {
|
|
117
|
+
constructor(public readonly code: string) {
|
|
118
|
+
super(code);
|
|
119
|
+
this.name = "GuardedWorkflowEnvelopeError";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
80
122
|
|
|
81
123
|
export interface WorkflowTransactionJournal {
|
|
82
124
|
version: 1;
|
|
@@ -108,6 +150,7 @@ export interface StateWriterOptions {
|
|
|
108
150
|
receipt?: StateWriterReceiptContext;
|
|
109
151
|
audit?: StateWriterAuditContext;
|
|
110
152
|
sourceRevision?: number;
|
|
153
|
+
orderingRevision?: number;
|
|
111
154
|
/**
|
|
112
155
|
* Cross-process lock tuning for read-modify-write paths that route through
|
|
113
156
|
* `withWorkflowStateLock` / `updateJsonAtomic`. Omit for the hardened
|
|
@@ -255,6 +298,49 @@ export function workflowEnvelopeContentSha256(value: unknown): string {
|
|
|
255
298
|
.update(JSON.stringify(canonicalizeJson(withoutReceiptChecksum(value))))
|
|
256
299
|
.digest("hex");
|
|
257
300
|
}
|
|
301
|
+
function isNativeDeepInterviewV1(value: Record<string, unknown>): boolean {
|
|
302
|
+
if (value.skill !== "deep-interview") return false;
|
|
303
|
+
return value.schema_version === 1 || (isPlainObject(value.state) && value.state.schema_version === 1);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function verifyWorkflowEnvelopeReceiptValue(value: unknown, filePath: string): WorkflowReceiptVerification {
|
|
307
|
+
if (!isPlainObject(value)) return "receipt-malformed";
|
|
308
|
+
const native = isNativeDeepInterviewV1(value);
|
|
309
|
+
if (!Object.hasOwn(value, "receipt")) return native ? "receipt-missing" : "legacy";
|
|
310
|
+
if (!isPlainObject(value.receipt) || !isPlainObject(value.receipt.content_sha256)) return "receipt-malformed";
|
|
311
|
+
|
|
312
|
+
const receipt = value.receipt as Record<string, unknown>;
|
|
313
|
+
const checksum = receipt.content_sha256 as Record<string, unknown>;
|
|
314
|
+
if (
|
|
315
|
+
typeof value.skill !== "string" ||
|
|
316
|
+
receipt.version !== 1 ||
|
|
317
|
+
receipt.skill !== value.skill ||
|
|
318
|
+
(receipt.skill !== "deep-interview" &&
|
|
319
|
+
receipt.skill !== "ralplan" &&
|
|
320
|
+
receipt.skill !== "ultragoal" &&
|
|
321
|
+
receipt.skill !== "team") ||
|
|
322
|
+
(receipt.owner !== "gjc-state-cli" && receipt.owner !== "gjc-runtime" && receipt.owner !== "gjc-hook") ||
|
|
323
|
+
typeof receipt.command !== "string" ||
|
|
324
|
+
typeof receipt.state_path !== "string" ||
|
|
325
|
+
typeof receipt.storage_path !== "string" ||
|
|
326
|
+
typeof receipt.mutated_at !== "string" ||
|
|
327
|
+
!Number.isFinite(Date.parse(receipt.mutated_at)) ||
|
|
328
|
+
typeof receipt.fresh_until !== "string" ||
|
|
329
|
+
!Number.isFinite(Date.parse(receipt.fresh_until)) ||
|
|
330
|
+
(receipt.status !== "fresh" && receipt.status !== "stale") ||
|
|
331
|
+
typeof receipt.mutation_id !== "string" ||
|
|
332
|
+
checksum.algorithm !== "sha256" ||
|
|
333
|
+
typeof checksum.value !== "string" ||
|
|
334
|
+
!/^[0-9a-f]{64}$/.test(checksum.value) ||
|
|
335
|
+
typeof checksum.covered_path !== "string" ||
|
|
336
|
+
path.resolve(checksum.covered_path) !== path.resolve(filePath) ||
|
|
337
|
+
typeof checksum.computed_at !== "string" ||
|
|
338
|
+
!Number.isFinite(Date.parse(checksum.computed_at))
|
|
339
|
+
) {
|
|
340
|
+
return "receipt-malformed";
|
|
341
|
+
}
|
|
342
|
+
return workflowEnvelopeContentSha256(value) === checksum.value ? "native-valid" : "checksum-mismatch";
|
|
343
|
+
}
|
|
258
344
|
|
|
259
345
|
export function stampWorkflowEnvelopeChecksum<T>(value: T, filePath: string, computedAt = new Date().toISOString()): T {
|
|
260
346
|
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
@@ -421,6 +507,11 @@ function persistedSourceRevision(value: unknown): number {
|
|
|
421
507
|
const revision = value.source_state_revision;
|
|
422
508
|
return typeof revision === "number" && Number.isFinite(revision) ? revision : persistedStateRevision(value);
|
|
423
509
|
}
|
|
510
|
+
function persistedCommittedModeRevision(value: unknown): number {
|
|
511
|
+
if (!isPlainObject(value)) return 0;
|
|
512
|
+
const revision = value.committed_mode_state_revision;
|
|
513
|
+
return typeof revision === "number" && Number.isFinite(revision) ? revision : 0;
|
|
514
|
+
}
|
|
424
515
|
|
|
425
516
|
function withoutCandidateRevision(value: unknown): unknown {
|
|
426
517
|
if (!isPlainObject(value)) return value;
|
|
@@ -529,7 +620,12 @@ async function writeGuardedResolvedJsonAtomic(
|
|
|
529
620
|
|
|
530
621
|
const incomingSourceRevision =
|
|
531
622
|
options.sourceRevision ?? (isPlainObject(value) ? persistedStateRevision(value) : 0);
|
|
532
|
-
if (
|
|
623
|
+
if (
|
|
624
|
+
current !== undefined &&
|
|
625
|
+
(options.orderingRevision !== undefined
|
|
626
|
+
? options.orderingRevision <= persistedCommittedModeRevision(current)
|
|
627
|
+
: incomingSourceRevision <= persistedSourceRevision(current))
|
|
628
|
+
) {
|
|
533
629
|
return { path: filePath, written: false, reason: "stale-skip", revision: currentRevision };
|
|
534
630
|
}
|
|
535
631
|
const next = stampStateRevision(
|
|
@@ -563,6 +659,7 @@ export async function writeGuardedWorkflowEnvelopeAtomic(
|
|
|
563
659
|
const write = async (): Promise<GuardedWriteResult> => {
|
|
564
660
|
const current = await readJsonIfPresentTolerant(filePath);
|
|
565
661
|
const currentRevision = persistedStateRevision(current);
|
|
662
|
+
|
|
566
663
|
if (options.policy === "source") {
|
|
567
664
|
if (options.expectedRevision !== undefined && options.expectedRevision !== currentRevision) {
|
|
568
665
|
throw new StateWriteConflictError(filePath, options.expectedRevision, currentRevision);
|
|
@@ -586,9 +683,15 @@ export async function writeGuardedWorkflowEnvelopeAtomic(
|
|
|
586
683
|
await maybeAudit(filePath, options);
|
|
587
684
|
return { path: filePath, written: true, revision: currentRevision + 1, stamped: next };
|
|
588
685
|
}
|
|
686
|
+
|
|
589
687
|
const incomingSourceRevision =
|
|
590
688
|
options.sourceRevision ?? (isPlainObject(value) ? persistedStateRevision(value) : 0);
|
|
591
|
-
if (
|
|
689
|
+
if (
|
|
690
|
+
current !== undefined &&
|
|
691
|
+
(options.orderingRevision !== undefined
|
|
692
|
+
? options.orderingRevision <= persistedCommittedModeRevision(current)
|
|
693
|
+
: incomingSourceRevision <= persistedSourceRevision(current))
|
|
694
|
+
) {
|
|
592
695
|
return { path: filePath, written: false, reason: "stale-skip", revision: currentRevision };
|
|
593
696
|
}
|
|
594
697
|
const next = stampWorkflowEnvelopeRevisionAndChecksum(
|
|
@@ -612,6 +715,67 @@ export async function writeGuardedWorkflowEnvelopeAtomic(
|
|
|
612
715
|
};
|
|
613
716
|
return options.lockHeld ? write() : lockResolvedWorkflowTarget(filePath, write, options.lock);
|
|
614
717
|
}
|
|
718
|
+
export async function transformGuardedWorkflowEnvelopeAtomic(
|
|
719
|
+
targetPath: string,
|
|
720
|
+
options: GuardedWorkflowEnvelopeTransformOptions,
|
|
721
|
+
): Promise<GuardedWorkflowEnvelopeTransformResult> {
|
|
722
|
+
const filePath = resolveGjcTarget(targetPath, cwdForOptions(options));
|
|
723
|
+
return lockResolvedWorkflowTarget(
|
|
724
|
+
filePath,
|
|
725
|
+
async () => {
|
|
726
|
+
const read = await readExistingStateForMutation(filePath);
|
|
727
|
+
if (read.kind === "absent") throw new GuardedWorkflowEnvelopeError("DI_STATE_ABSENT");
|
|
728
|
+
if (read.kind === "corrupt") throw new GuardedWorkflowEnvelopeError("DI_STATE_CORRUPT");
|
|
729
|
+
const receipt = verifyWorkflowEnvelopeReceiptValue(read.value, filePath);
|
|
730
|
+
if (receipt === "receipt-malformed") throw new GuardedWorkflowEnvelopeError("DI_RECEIPT_MALFORMED");
|
|
731
|
+
if (receipt === "receipt-missing") throw new GuardedWorkflowEnvelopeError("DI_RECEIPT_MISSING");
|
|
732
|
+
if (receipt === "checksum-mismatch") throw new GuardedWorkflowEnvelopeError("DI_RECEIPT_CHECKSUM_MISMATCH");
|
|
733
|
+
const legacy = receipt === "legacy";
|
|
734
|
+
try {
|
|
735
|
+
if (isNativeDeepInterviewV1(read.value)) validateDeepInterviewV1Envelope(read.value);
|
|
736
|
+
options.validate?.(read.value, legacy);
|
|
737
|
+
} catch {
|
|
738
|
+
throw new GuardedWorkflowEnvelopeError("DI_STATE_SCHEMA_INVALID");
|
|
739
|
+
}
|
|
740
|
+
const phase = read.value.current_phase;
|
|
741
|
+
if (phase === "complete" || phase === "handoff" || read.value.active === false) {
|
|
742
|
+
throw new GuardedWorkflowEnvelopeError("DI_PHASE_NOT_REPAIRABLE");
|
|
743
|
+
}
|
|
744
|
+
const revision = persistedStateRevision(read.value);
|
|
745
|
+
if (revision !== options.expectedRevision) throw new GuardedWorkflowEnvelopeError("DI_REVISION_CONFLICT");
|
|
746
|
+
const transformed = await options.transform(read.value, { revision, legacy });
|
|
747
|
+
if (transformed.kind === "noop")
|
|
748
|
+
return { path: filePath, written: false, reason: "noop", revision, warnings: [] };
|
|
749
|
+
try {
|
|
750
|
+
if (isNativeDeepInterviewV1(transformed.value)) validateDeepInterviewV1Envelope(transformed.value);
|
|
751
|
+
options.validate?.(transformed.value, false);
|
|
752
|
+
} catch {
|
|
753
|
+
throw new GuardedWorkflowEnvelopeError("DI_STATE_SCHEMA_INVALID");
|
|
754
|
+
}
|
|
755
|
+
const stamped = stampWorkflowEnvelopeRevisionAndChecksum(
|
|
756
|
+
transformed.value,
|
|
757
|
+
filePath,
|
|
758
|
+
revision + 1,
|
|
759
|
+
undefined,
|
|
760
|
+
options,
|
|
761
|
+
) as Record<string, unknown>;
|
|
762
|
+
const parsed = RequiredOnWriteEnvelopeSchema.safeParse(stamped);
|
|
763
|
+
if (!parsed.success) throw new GuardedWorkflowEnvelopeError("DI_STATE_SCHEMA_INVALID");
|
|
764
|
+
await atomicWrite(filePath, jsonText(stamped));
|
|
765
|
+
const warnings: GuardedWorkflowEnvelopeWarning[] = [];
|
|
766
|
+
try {
|
|
767
|
+
await maybeAudit(filePath, options);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
warnings.push({
|
|
770
|
+
code: "DI_POST_COMMIT_AUDIT_FAILED",
|
|
771
|
+
message: error instanceof Error ? error.message : String(error),
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
return { path: filePath, written: true, revision: revision + 1, stamped, warnings };
|
|
775
|
+
},
|
|
776
|
+
options.lock,
|
|
777
|
+
);
|
|
778
|
+
}
|
|
615
779
|
|
|
616
780
|
export async function writeJsonAtomic(
|
|
617
781
|
targetPath: string,
|
|
@@ -1646,7 +1646,8 @@ export function isValidOwnerIntent(intent: unknown, request?: ObserveTerminalReq
|
|
|
1646
1646
|
intent.generation === request.owner_generation &&
|
|
1647
1647
|
intent.session_id === request.session_id &&
|
|
1648
1648
|
intent.server_key === request.socket_key &&
|
|
1649
|
-
|
|
1649
|
+
request.operator_dispatch_id !== undefined &&
|
|
1650
|
+
intent.dispatch_id === request.operator_dispatch_id &&
|
|
1650
1651
|
request.signal === "SIGTERM"
|
|
1651
1652
|
);
|
|
1652
1653
|
}
|
|
@@ -3,6 +3,7 @@ import * as crypto from "node:crypto";
|
|
|
3
3
|
import * as fsSync from "node:fs";
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
+
import { Process } from "@gajae-code/natives";
|
|
6
7
|
import { readLinuxProcStartTime, readLinuxProcStartTimeSync } from "./linux-proc";
|
|
7
8
|
import { resolveGjcTmuxBinary } from "./psmux-detect";
|
|
8
9
|
import { tmuxRuntimeSessionPath } from "./session-layout";
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
lifecyclePaths,
|
|
44
45
|
type OwnerIsolationProbeSync,
|
|
45
46
|
type OwnerVerdict,
|
|
47
|
+
observeOwnerTerminal,
|
|
46
48
|
type PlanResponse,
|
|
47
49
|
planTmuxOwnerIsolationSync,
|
|
48
50
|
replaceOwnerGenerationSync,
|
|
@@ -963,6 +965,14 @@ async function readProcessStartTime(pid: number): Promise<string | null> {
|
|
|
963
965
|
return readLinuxProcStartTime(pid);
|
|
964
966
|
}
|
|
965
967
|
|
|
968
|
+
function exactManagedOwnerSupervisor(supervisorPid: number, supervisorStartTime: string): Process {
|
|
969
|
+
const supervisor = Process.fromPid(supervisorPid);
|
|
970
|
+
if (!supervisor) throw new Error("managed_owner_supervisor_unverifiable");
|
|
971
|
+
if (process.platform === "linux" && supervisor.incarnation !== `linux:${supervisorStartTime}`)
|
|
972
|
+
throw new Error("managed_owner_supervisor_incarnation_mismatch");
|
|
973
|
+
return supervisor;
|
|
974
|
+
}
|
|
975
|
+
|
|
966
976
|
async function readCurrentGeneration(stateDir: string, sessionId: string): Promise<string | null> {
|
|
967
977
|
try {
|
|
968
978
|
const value: unknown = JSON.parse(
|
|
@@ -1016,12 +1026,12 @@ async function requireUnchangedOwnerForCompatibilityCleanup(
|
|
|
1016
1026
|
initialServer: { pid: number; startTime: string },
|
|
1017
1027
|
listPanePids: (sessionName: string, env: NodeJS.ProcessEnv) => number[],
|
|
1018
1028
|
readStartTime: (pid: number) => Promise<string | null>,
|
|
1019
|
-
): Promise<
|
|
1029
|
+
): Promise<boolean> {
|
|
1020
1030
|
try {
|
|
1021
1031
|
const currentNativeSessionId = readNativeTmuxSessionId(nativeSessionId, env);
|
|
1022
1032
|
if (!currentNativeSessionId) {
|
|
1023
1033
|
if (readNativeTmuxSessionId(sessionName, env)) throw new Error(`gjc_tmux_owner_changed:${sessionName}`);
|
|
1024
|
-
return;
|
|
1034
|
+
return false;
|
|
1025
1035
|
}
|
|
1026
1036
|
const currentServer = requireSafeTmuxServerForMutation(resolveGjcTmuxCommand(env), env);
|
|
1027
1037
|
const panePids = listPanePids(nativeSessionId, env);
|
|
@@ -1054,10 +1064,11 @@ async function requireUnchangedOwnerForCompatibilityCleanup(
|
|
|
1054
1064
|
: null,
|
|
1055
1065
|
].filter((value): value is string => value !== null);
|
|
1056
1066
|
if (mismatches.length > 0) throw new Error(`gjc_tmux_owner_changed:${sessionName}:${mismatches.join(",")}`);
|
|
1067
|
+
return true;
|
|
1057
1068
|
} catch (error) {
|
|
1058
1069
|
if (!readNativeTmuxSessionId(nativeSessionId, env)) {
|
|
1059
1070
|
if (readNativeTmuxSessionId(sessionName, env)) throw new Error(`gjc_tmux_owner_changed:${sessionName}`);
|
|
1060
|
-
return;
|
|
1071
|
+
return false;
|
|
1061
1072
|
}
|
|
1062
1073
|
if (error instanceof Error && error.message.startsWith(`gjc_tmux_owner_changed:${sessionName}`))
|
|
1063
1074
|
throw new Error(`gjc_tmux_owner_changed:${sessionName}`);
|
|
@@ -1152,6 +1163,7 @@ export async function forceCloseGjcTmuxSession(
|
|
|
1152
1163
|
const now = deps.now ?? (() => new Date());
|
|
1153
1164
|
const sleep = deps.sleep ?? (ms => Bun.sleep(ms));
|
|
1154
1165
|
const dispatchId = crypto.randomUUID();
|
|
1166
|
+
let operatorVerdict: Promise<OwnerVerdict> | null = null;
|
|
1155
1167
|
await closeExactTmuxOwner(
|
|
1156
1168
|
{
|
|
1157
1169
|
stateDir: identity.stateDir,
|
|
@@ -1169,12 +1181,37 @@ export async function forceCloseGjcTmuxSession(
|
|
|
1169
1181
|
sendSigterm: async pid => {
|
|
1170
1182
|
if ((await (deps.readProcessStartTime ?? readProcessStartTime)(pid)) !== identity.startTime)
|
|
1171
1183
|
throw new Error("owner_pid_identity_mismatch");
|
|
1172
|
-
(deps.signalTerm
|
|
1184
|
+
if (deps.signalTerm) {
|
|
1185
|
+
deps.signalTerm(pid);
|
|
1186
|
+
} else {
|
|
1187
|
+
const supervisor = exactManagedOwnerSupervisor(pid, identity.startTime);
|
|
1188
|
+
if (!supervisor.signalRoot(15)) throw new Error("managed_owner_supervisor_signal_failed");
|
|
1189
|
+
operatorVerdict = supervisor
|
|
1190
|
+
.waitForExit({ timeoutMs: FORCE_CLOSE_VERDICT_TIMEOUT_MS - 500 })
|
|
1191
|
+
.then(async exited => {
|
|
1192
|
+
if (!exited) throw new Error("managed_owner_supervisor_exit_timeout");
|
|
1193
|
+
return await observeOwnerTerminal({
|
|
1194
|
+
schema_version: 1,
|
|
1195
|
+
op: "observe_terminal",
|
|
1196
|
+
session_id: identity.sessionId,
|
|
1197
|
+
owner_generation: identity.generation,
|
|
1198
|
+
state_dir: identity.stateDir,
|
|
1199
|
+
socket_key: identity.socketKey,
|
|
1200
|
+
observer: "raw_monitor",
|
|
1201
|
+
observed_at: now().toISOString(),
|
|
1202
|
+
signal: "SIGTERM",
|
|
1203
|
+
exit_code: null,
|
|
1204
|
+
exit_kind: "exact_owner_exit_observed",
|
|
1205
|
+
reason: "operator_observed_owner_exit",
|
|
1206
|
+
operator_dispatch_id: dispatchId,
|
|
1207
|
+
});
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1173
1210
|
},
|
|
1174
1211
|
|
|
1175
|
-
waitForVerdict: () => waitForExpectedVerdict(identity, sleep, now),
|
|
1212
|
+
waitForVerdict: () => operatorVerdict ?? waitForExpectedVerdict(identity, sleep, now),
|
|
1176
1213
|
cleanupSession: async () => {
|
|
1177
|
-
await requireUnchangedOwnerForCompatibilityCleanup(
|
|
1214
|
+
const cleanupRequired = await requireUnchangedOwnerForCompatibilityCleanup(
|
|
1178
1215
|
session.name,
|
|
1179
1216
|
nativeSessionId,
|
|
1180
1217
|
env,
|
|
@@ -1184,6 +1221,7 @@ export async function forceCloseGjcTmuxSession(
|
|
|
1184
1221
|
deps.listPanePids ?? readExactSessionPanePids,
|
|
1185
1222
|
deps.readProcessStartTime ?? readProcessStartTime,
|
|
1186
1223
|
);
|
|
1224
|
+
if (!cleanupRequired) return;
|
|
1187
1225
|
if (deps.cleanupSession) deps.cleanupSession(nativeSessionId, env);
|
|
1188
1226
|
else
|
|
1189
1227
|
runGuardedTmuxSessionCommand(
|
|
@@ -1192,6 +1230,7 @@ export async function forceCloseGjcTmuxSession(
|
|
|
1192
1230
|
initialServer.pid,
|
|
1193
1231
|
env,
|
|
1194
1232
|
`kill-session -t '${nativeSessionId}'`,
|
|
1233
|
+
identity.generation,
|
|
1195
1234
|
);
|
|
1196
1235
|
},
|
|
1197
1236
|
},
|
|
@@ -76,12 +76,12 @@ function objectiveMatches(currentGoal: CurrentGoalLike, plan: UltragoalPlan, ses
|
|
|
76
76
|
return plan.goals.some(goal => goal.objective === normalized);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
function isKnownUltragoalObjective(currentObjective: string): boolean {
|
|
79
|
+
export function isKnownUltragoalObjective(currentObjective: string): boolean {
|
|
80
80
|
const normalized = currentObjective.trim();
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
// Exact default aggregate objective only. Substring path sniffing previously
|
|
82
|
+
// treated arbitrary objectives that merely mentioned goals.json/ledger paths
|
|
83
|
+
// as Ultragoal-owned, which could mis-arm guards. Prefer fail-closed.
|
|
84
|
+
return normalized === DEFAULT_ULTRAGOAL_OBJECTIVE;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
async function ultragoalReadPaths(
|
|
@@ -5,6 +5,12 @@ import type { WorkflowHudSummary } from "../skill-state/active-state";
|
|
|
5
5
|
import { buildUltragoalHudSummary as buildWorkflowUltragoalHudSummary } from "../skill-state/workflow-hud";
|
|
6
6
|
import { renderCliWriteReceipt } from "./cli-write-receipt";
|
|
7
7
|
import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
|
|
8
|
+
import {
|
|
9
|
+
assertCwdMatchesRepositoryBinding,
|
|
10
|
+
captureRepositoryBinding,
|
|
11
|
+
parseRepositoryBinding,
|
|
12
|
+
type RepositoryBinding,
|
|
13
|
+
} from "./repository-binding";
|
|
8
14
|
import {
|
|
9
15
|
CRITIC_GATE_HARD_STOP_EVENT,
|
|
10
16
|
CRITIC_GATE_OVERRIDE_EVENT,
|
|
@@ -192,6 +198,8 @@ export interface UltragoalPlan {
|
|
|
192
198
|
gjcObjective: string;
|
|
193
199
|
gjcObjectiveAliases?: string[];
|
|
194
200
|
goals: UltragoalGoal[];
|
|
201
|
+
/** Authoritative repository identity for multi-repo fail-closed spawn (#2901). */
|
|
202
|
+
repositoryBinding?: RepositoryBinding;
|
|
195
203
|
createdAt: string;
|
|
196
204
|
updatedAt: string;
|
|
197
205
|
[key: string]: unknown;
|
|
@@ -1349,6 +1357,10 @@ function normalizePlan(raw: unknown): UltragoalPlan {
|
|
|
1349
1357
|
(value): value is string => typeof value === "string" && value.trim().length > 0,
|
|
1350
1358
|
)
|
|
1351
1359
|
: undefined;
|
|
1360
|
+
let repositoryBinding: RepositoryBinding | undefined;
|
|
1361
|
+
if (record.repositoryBinding !== undefined) {
|
|
1362
|
+
repositoryBinding = parseRepositoryBinding(record.repositoryBinding);
|
|
1363
|
+
}
|
|
1352
1364
|
return {
|
|
1353
1365
|
version: 1,
|
|
1354
1366
|
brief,
|
|
@@ -1358,6 +1370,7 @@ function normalizePlan(raw: unknown): UltragoalPlan {
|
|
|
1358
1370
|
goals,
|
|
1359
1371
|
createdAt,
|
|
1360
1372
|
updatedAt,
|
|
1373
|
+
...(repositoryBinding ? { repositoryBinding } : {}),
|
|
1361
1374
|
...(typeof record.state_revision === "number" && Number.isFinite(record.state_revision)
|
|
1362
1375
|
? { state_revision: record.state_revision }
|
|
1363
1376
|
: {}),
|
|
@@ -1558,12 +1571,16 @@ export async function createUltragoalPlan(input: {
|
|
|
1558
1571
|
goal.validationBatch = validationBatchByGoalId.get(goal.id);
|
|
1559
1572
|
validateValidationBatchPipelineExclusion(goal);
|
|
1560
1573
|
}
|
|
1574
|
+
const repositoryBinding = await captureRepositoryBinding(input.cwd, {
|
|
1575
|
+
displayPath: input.cwd,
|
|
1576
|
+
});
|
|
1561
1577
|
const plan: UltragoalPlan = {
|
|
1562
1578
|
version: 1,
|
|
1563
1579
|
brief,
|
|
1564
1580
|
gjcGoalMode: input.gjcGoalMode ?? "aggregate",
|
|
1565
1581
|
gjcObjective: DEFAULT_ULTRAGOAL_OBJECTIVE,
|
|
1566
1582
|
goals,
|
|
1583
|
+
repositoryBinding,
|
|
1567
1584
|
createdAt: now,
|
|
1568
1585
|
updatedAt: now,
|
|
1569
1586
|
};
|
|
@@ -1639,6 +1656,50 @@ export function getUltragoalRunCompletionState(
|
|
|
1639
1656
|
};
|
|
1640
1657
|
}
|
|
1641
1658
|
|
|
1659
|
+
/**
|
|
1660
|
+
* Discriminated next-action for `complete-goals` handoff (#2903).
|
|
1661
|
+
* `none` is reserved for genuine completion; `execute-goal` always carries a goal.
|
|
1662
|
+
*/
|
|
1663
|
+
export type UltragoalCompleteNextActionKind =
|
|
1664
|
+
| "none"
|
|
1665
|
+
| "execute-goal"
|
|
1666
|
+
| "retry-failed"
|
|
1667
|
+
| "resolve-blockers"
|
|
1668
|
+
| "final-aggregate-receipt";
|
|
1669
|
+
|
|
1670
|
+
export type UltragoalCompleteNextAction = {
|
|
1671
|
+
kind: UltragoalCompleteNextActionKind;
|
|
1672
|
+
goal?: UltragoalGoal;
|
|
1673
|
+
blockedGoals?: UltragoalGoal[];
|
|
1674
|
+
failedGoals?: UltragoalGoal[];
|
|
1675
|
+
};
|
|
1676
|
+
|
|
1677
|
+
/**
|
|
1678
|
+
* Resolve the actionable next step after scheduling / complete-goals.
|
|
1679
|
+
* Blocked and review_blocked goals remain unschedulable; they surface as
|
|
1680
|
+
* `resolve-blockers` instead of a contradictory `execute-goal` without goal_id.
|
|
1681
|
+
*/
|
|
1682
|
+
export function resolveUltragoalCompleteNextAction(
|
|
1683
|
+
plan: UltragoalPlan,
|
|
1684
|
+
options: { retryFailed?: boolean; selectedGoal?: UltragoalGoal } = {},
|
|
1685
|
+
): UltragoalCompleteNextAction {
|
|
1686
|
+
const state = getUltragoalRunCompletionState(plan, { retryFailed: options.retryFailed });
|
|
1687
|
+
// Genuine completion keeps next_action=`none` (historical complete-goals contract).
|
|
1688
|
+
// final-aggregate-receipt is reserved for a future dedicated handoff; do not remap
|
|
1689
|
+
// allComplete here so aggregate runs still finish with `none` / complete text.
|
|
1690
|
+
if (state.allComplete) return { kind: "none" };
|
|
1691
|
+
const goal = options.selectedGoal ?? state.nextGoal;
|
|
1692
|
+
if (goal) return { kind: "execute-goal", goal };
|
|
1693
|
+
const blockedGoals = state.incompleteGoals.filter(
|
|
1694
|
+
item => item.status === "blocked" || item.status === "review_blocked",
|
|
1695
|
+
);
|
|
1696
|
+
if (blockedGoals.length > 0) return { kind: "resolve-blockers", blockedGoals };
|
|
1697
|
+
const failedGoals = state.incompleteGoals.filter(item => item.status === "failed");
|
|
1698
|
+
if (failedGoals.length > 0) return { kind: "retry-failed", failedGoals };
|
|
1699
|
+
// Incomplete but not schedulable (unexpected statuses): still actionable, not "none".
|
|
1700
|
+
return { kind: "resolve-blockers", blockedGoals: state.incompleteGoals };
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1642
1703
|
export async function startNextUltragoalGoal(input: {
|
|
1643
1704
|
cwd: string;
|
|
1644
1705
|
retryFailed?: boolean;
|
|
@@ -1647,11 +1708,27 @@ export async function startNextUltragoalGoal(input: {
|
|
|
1647
1708
|
plan: UltragoalPlan;
|
|
1648
1709
|
goal?: UltragoalGoal;
|
|
1649
1710
|
allComplete: boolean;
|
|
1711
|
+
nextAction: UltragoalCompleteNextAction;
|
|
1650
1712
|
}> {
|
|
1651
1713
|
const plan = await readUltragoalPlan(input.cwd, input.sessionId);
|
|
1652
1714
|
if (!plan) throw new Error("No ultragoal plan found. Run `gjc ultragoal create-goals --brief ...` first.");
|
|
1653
|
-
|
|
1654
|
-
if (!
|
|
1715
|
+
// Fail closed: delegated execution requires stamped repository authority (#2901).
|
|
1716
|
+
if (!plan.repositoryBinding) {
|
|
1717
|
+
throw new Error(
|
|
1718
|
+
"Ultragoal plan is missing repositoryBinding; recreate goals so the plan is bound to an authoritative repository identity.",
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
await assertCwdMatchesRepositoryBinding(input.cwd, plan.repositoryBinding);
|
|
1722
|
+
const retryFailed = input.retryFailed === true;
|
|
1723
|
+
const goal = chooseNextGoal(plan, retryFailed);
|
|
1724
|
+
if (!goal) {
|
|
1725
|
+
const state = getUltragoalRunCompletionState(plan, { retryFailed });
|
|
1726
|
+
return {
|
|
1727
|
+
plan,
|
|
1728
|
+
allComplete: state.allComplete,
|
|
1729
|
+
nextAction: resolveUltragoalCompleteNextAction(plan, { retryFailed }),
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1655
1732
|
if (goal.status !== "active") {
|
|
1656
1733
|
const now = new Date().toISOString();
|
|
1657
1734
|
goal.status = "active";
|
|
@@ -1661,7 +1738,12 @@ export async function startNextUltragoalGoal(input: {
|
|
|
1661
1738
|
await writePlan(input.cwd, plan, input.sessionId);
|
|
1662
1739
|
await appendLedger(input.cwd, { event: "goal_started", goalId: goal.id }, input.sessionId);
|
|
1663
1740
|
}
|
|
1664
|
-
return {
|
|
1741
|
+
return {
|
|
1742
|
+
plan,
|
|
1743
|
+
goal,
|
|
1744
|
+
allComplete: false,
|
|
1745
|
+
nextAction: { kind: "execute-goal", goal },
|
|
1746
|
+
};
|
|
1665
1747
|
}
|
|
1666
1748
|
|
|
1667
1749
|
async function readStructuredValue(cwd: string, value: string): Promise<unknown> {
|
|
@@ -4237,31 +4319,111 @@ function renderStatus(summary: UltragoalStatusSummary, json: boolean): string {
|
|
|
4237
4319
|
return renderUltragoalStatusMarkdown(summary);
|
|
4238
4320
|
}
|
|
4239
4321
|
|
|
4322
|
+
function summarizeBlockedGoalForHandoff(goal: UltragoalGoal): {
|
|
4323
|
+
id: string;
|
|
4324
|
+
status: UltragoalGoalStatus;
|
|
4325
|
+
evidence?: string;
|
|
4326
|
+
} {
|
|
4327
|
+
const evidence = typeof goal.evidence === "string" && goal.evidence.trim() ? goal.evidence.trim() : undefined;
|
|
4328
|
+
return {
|
|
4329
|
+
id: goal.id,
|
|
4330
|
+
status: goal.status,
|
|
4331
|
+
...(evidence ? { evidence } : {}),
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
|
|
4240
4335
|
function renderCompleteHandoff(
|
|
4241
|
-
result: {
|
|
4336
|
+
result: {
|
|
4337
|
+
plan: UltragoalPlan;
|
|
4338
|
+
goal?: UltragoalGoal;
|
|
4339
|
+
allComplete: boolean;
|
|
4340
|
+
nextAction?: UltragoalCompleteNextAction;
|
|
4341
|
+
},
|
|
4242
4342
|
json: boolean,
|
|
4243
4343
|
cwd: string,
|
|
4244
4344
|
): string {
|
|
4345
|
+
const nextAction =
|
|
4346
|
+
result.nextAction ??
|
|
4347
|
+
resolveUltragoalCompleteNextAction(result.plan, {
|
|
4348
|
+
selectedGoal: result.goal,
|
|
4349
|
+
});
|
|
4350
|
+
const goalsPath = getUltragoalPaths(cwd, currentUltragoalSessionId(cwd)).goalsPath;
|
|
4351
|
+
|
|
4245
4352
|
if (json) {
|
|
4246
|
-
|
|
4353
|
+
const receipt: Record<string, unknown> = {
|
|
4247
4354
|
ok: true,
|
|
4248
4355
|
all_complete: result.allComplete,
|
|
4249
|
-
next_action:
|
|
4250
|
-
goal_id: result.goal?.id,
|
|
4251
|
-
goal_status: result.goal?.status,
|
|
4356
|
+
next_action: nextAction.kind,
|
|
4252
4357
|
gjc_objective: result.plan.gjcObjective,
|
|
4253
|
-
goals_path:
|
|
4254
|
-
}
|
|
4358
|
+
goals_path: goalsPath,
|
|
4359
|
+
};
|
|
4360
|
+
if (nextAction.kind === "execute-goal" && nextAction.goal) {
|
|
4361
|
+
receipt.goal_id = nextAction.goal.id;
|
|
4362
|
+
receipt.goal_status = nextAction.goal.status;
|
|
4363
|
+
}
|
|
4364
|
+
if (nextAction.kind === "resolve-blockers" && nextAction.blockedGoals) {
|
|
4365
|
+
receipt.blocked_goals = nextAction.blockedGoals.map(summarizeBlockedGoalForHandoff);
|
|
4366
|
+
receipt.blocked_goal_ids = nextAction.blockedGoals.map(goal => goal.id);
|
|
4367
|
+
receipt.recovery_hints = [
|
|
4368
|
+
"gjc ultragoal classify-blocker --help",
|
|
4369
|
+
"gjc ultragoal record-review-blockers --help",
|
|
4370
|
+
"gjc ultragoal steer --kind add_subgoal --help",
|
|
4371
|
+
"gjc ultragoal steer --kind mark_blocked_superseded --help",
|
|
4372
|
+
];
|
|
4373
|
+
}
|
|
4374
|
+
if (nextAction.kind === "retry-failed" && nextAction.failedGoals) {
|
|
4375
|
+
receipt.failed_goal_ids = nextAction.failedGoals.map(goal => goal.id);
|
|
4376
|
+
receipt.recovery_hints = ["gjc ultragoal complete-goals --retry-failed"];
|
|
4377
|
+
}
|
|
4378
|
+
if (nextAction.kind === "final-aggregate-receipt") {
|
|
4379
|
+
receipt.recovery_hints = [
|
|
4380
|
+
"Finalize the aggregate completion receipt before treating the ultragoal run as closed.",
|
|
4381
|
+
];
|
|
4382
|
+
}
|
|
4383
|
+
return renderCliWriteReceipt(receipt);
|
|
4255
4384
|
}
|
|
4256
|
-
|
|
4257
|
-
if (
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4385
|
+
|
|
4386
|
+
if (nextAction.kind === "none" || (result.allComplete && nextAction.kind !== "final-aggregate-receipt")) {
|
|
4387
|
+
return "ultragoal complete all=true\n";
|
|
4388
|
+
}
|
|
4389
|
+
if (nextAction.kind === "final-aggregate-receipt") {
|
|
4390
|
+
return [
|
|
4391
|
+
"ultragoal next-action=final-aggregate-receipt",
|
|
4392
|
+
"hint=finalize the aggregate completion receipt before treating the run as closed",
|
|
4393
|
+
"",
|
|
4394
|
+
].join("\n");
|
|
4395
|
+
}
|
|
4396
|
+
if (nextAction.kind === "execute-goal" && nextAction.goal) {
|
|
4397
|
+
return [
|
|
4398
|
+
`ultragoal next-action=execute-goal goal-id=${nextAction.goal.id}`,
|
|
4399
|
+
`objective=${nextAction.goal.objective}`,
|
|
4400
|
+
`gjc-objective=${result.plan.gjcObjective}`,
|
|
4401
|
+
"checkpoint requires=architectReview:CLEAR+APPROVE,executorQa:passed",
|
|
4402
|
+
"",
|
|
4403
|
+
].join("\n");
|
|
4404
|
+
}
|
|
4405
|
+
if (nextAction.kind === "resolve-blockers" && nextAction.blockedGoals && nextAction.blockedGoals.length > 0) {
|
|
4406
|
+
const ids = nextAction.blockedGoals.map(goal => goal.id).join(",");
|
|
4407
|
+
const statuses = nextAction.blockedGoals.map(goal => `${goal.id}:${goal.status}`).join(",");
|
|
4408
|
+
return [
|
|
4409
|
+
"ultragoal next-action=resolve-blockers",
|
|
4410
|
+
`blocked-goal-ids=${ids}`,
|
|
4411
|
+
`blocked-statuses=${statuses}`,
|
|
4412
|
+
"hint=resolve blockers via classify-blocker / record-review-blockers / steer --kind add_subgoal (or audited mark_blocked_superseded); blocked goals stay unschedulable",
|
|
4413
|
+
"",
|
|
4414
|
+
].join("\n");
|
|
4415
|
+
}
|
|
4416
|
+
if (nextAction.kind === "retry-failed" && nextAction.failedGoals && nextAction.failedGoals.length > 0) {
|
|
4417
|
+
const ids = nextAction.failedGoals.map(goal => goal.id).join(",");
|
|
4418
|
+
return [
|
|
4419
|
+
"ultragoal next-action=retry-failed",
|
|
4420
|
+
`failed-goal-ids=${ids}`,
|
|
4421
|
+
"hint=run `gjc ultragoal complete-goals --retry-failed` after the failure is addressed",
|
|
4422
|
+
"",
|
|
4423
|
+
].join("\n");
|
|
4424
|
+
}
|
|
4425
|
+
// Fail closed: never claim complete or execute-goal without a goal id.
|
|
4426
|
+
return "ultragoal next-action=resolve-blockers\nhint=no schedulable goal; inspect goals.json and ledger\n";
|
|
4265
4427
|
}
|
|
4266
4428
|
function renderCheckpointContinuation(
|
|
4267
4429
|
result: UltragoalCheckpointContinuation,
|