@gajae-code/coding-agent 0.11.11 → 0.12.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/CHANGELOG.md +49 -3
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +22 -2
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
- package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +52 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/team-store.d.ts +12 -0
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +8 -0
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/index.d.ts +15 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
- package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +44 -10
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +25 -13
- package/dist/types/session/streaming-output.d.ts +60 -5
- package/dist/types/skill-state/active-state.d.ts +0 -4
- package/dist/types/skill-state/workflow-hud.d.ts +3 -0
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +17 -1
- package/dist/types/tools/output-meta.d.ts +21 -1
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -1
- package/dist/types/tools/resource-gc.d.ts +4 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/package.json +9 -13
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/capability/index.ts +8 -0
- package/src/cli/read-cli.ts +2 -1
- package/src/cli.ts +1 -0
- package/src/commands/deep-interview.ts +29 -9
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/team.ts +1 -1
- package/src/config/model-registry.ts +21 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +23 -1
- package/src/config/settings.ts +246 -108
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
- package/src/defaults/gjc/skills/ralplan/SKILL.md +58 -25
- package/src/defaults/gjc/skills/team/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +25 -15
- package/src/eval/py/executor.ts +7 -2
- package/src/extensibility/extensions/types.ts +28 -4
- package/src/extensibility/shared-events.ts +5 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
- package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
- package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
- package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
- package/src/gjc-runtime/deep-interview-state.ts +12 -943
- package/src/gjc-runtime/gc-render.ts +1 -0
- package/src/gjc-runtime/gc-runtime.ts +11 -1
- package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/gjc-runtime/ralplan-runtime.ts +742 -85
- package/src/gjc-runtime/state-runtime.ts +2 -1
- package/src/gjc-runtime/state-writer.ts +70 -222
- package/src/gjc-runtime/team-launch.ts +34 -5
- package/src/gjc-runtime/team-runtime.ts +970 -33
- package/src/gjc-runtime/team-store.ts +32 -0
- package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/gjc-runtime/team-workers.ts +1 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
- package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
- package/src/gjc-runtime/workflow-manifest.ts +34 -179
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/hooks/skill-state.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +8 -9
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/main.ts +9 -5
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +47 -0
- package/src/modes/acp/acp-agent.ts +43 -7
- package/src/modes/acp/acp-event-mapper.ts +14 -7
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +61 -4
- package/src/modes/components/oauth-selector.ts +42 -12
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/session-observer-overlay.ts +187 -32
- package/src/modes/components/settings-selector.ts +60 -18
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/controllers/event-controller.ts +109 -27
- package/src/modes/controllers/extension-ui-controller.ts +19 -2
- package/src/modes/controllers/input-controller.ts +18 -3
- package/src/modes/controllers/selector-controller.ts +83 -37
- package/src/modes/interactive-mode.ts +62 -3
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/runtime-init.ts +19 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +94 -1
- package/src/prompts/agents/architect.md +9 -1
- package/src/prompts/agents/critic.md +9 -0
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/read.md +4 -2
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/sdk/broker/lifecycle.ts +25 -6
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/index.ts +341 -38
- package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
- package/src/sdk/bus/prompt-reconciliation.ts +2 -1
- package/src/sdk/bus/reconciliation-store.ts +224 -0
- package/src/sdk/host/control/dispatch.ts +5 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +23 -1
- package/src/sdk/host/query/handlers.ts +55 -0
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/protocol/operation-inventory.generated.json +29 -0
- package/src/sdk/protocol/operation-registry.ts +7 -2
- package/src/sdk/session.ts +13 -1
- package/src/session/agent-session.ts +466 -50
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +764 -17
- package/src/session/streaming-output.ts +262 -43
- package/src/setup/provider-onboarding.ts +19 -4
- package/src/skill-state/active-state.ts +0 -10
- package/src/skill-state/workflow-hud.ts +25 -2
- package/src/skill-state/workflow-mutation-guard.ts +65 -7
- package/src/tools/ask.ts +89 -69
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/fetch.ts +114 -23
- package/src/tools/output-meta.ts +176 -9
- package/src/tools/path-utils.ts +5 -1
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1542 -310
- package/src/tools/resource-gc.ts +182 -18
- package/src/tools/tool-result.ts +14 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
- package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
- package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
- package/src/gjc-runtime/deep-interview-draft.ts +0 -843
- package/src/gjc-runtime/deep-interview-payload.ts +0 -262
- package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
|
@@ -126,6 +126,38 @@ export interface GjcTeamApiClaimResult {
|
|
|
126
126
|
claim_token?: string;
|
|
127
127
|
reason?: string;
|
|
128
128
|
}
|
|
129
|
+
export type GjcTeamWorkerClaimSelection =
|
|
130
|
+
| { kind: "none" }
|
|
131
|
+
| { kind: "exact"; task: GjcTeamTask; claim: GjcTeamTaskClaim }
|
|
132
|
+
| { kind: "ambiguous"; tasks: GjcTeamTask[] };
|
|
133
|
+
|
|
134
|
+
export function selectCurrentClaimedTaskForWorker(
|
|
135
|
+
tasks: readonly GjcTeamTask[],
|
|
136
|
+
workerId: string,
|
|
137
|
+
): GjcTeamWorkerClaimSelection {
|
|
138
|
+
const claimed = tasks.filter(
|
|
139
|
+
task =>
|
|
140
|
+
task.status === "in_progress" &&
|
|
141
|
+
task.assignee === workerId &&
|
|
142
|
+
task.claim?.owner === workerId &&
|
|
143
|
+
task.owner === workerId,
|
|
144
|
+
);
|
|
145
|
+
if (claimed.length === 0) return { kind: "none" };
|
|
146
|
+
if (claimed.length === 1) {
|
|
147
|
+
const task = claimed[0]!;
|
|
148
|
+
return { kind: "exact", task, claim: task.claim! };
|
|
149
|
+
}
|
|
150
|
+
return { kind: "ambiguous", tasks: claimed };
|
|
151
|
+
}
|
|
152
|
+
export function findGjcTeamClaimedTaskForWorker(
|
|
153
|
+
tasks: readonly GjcTeamTask[],
|
|
154
|
+
workerId: string,
|
|
155
|
+
): GjcTeamTask | undefined {
|
|
156
|
+
const active = selectCurrentClaimedTaskForWorker(tasks, workerId);
|
|
157
|
+
if (active.kind === "exact") return active.task;
|
|
158
|
+
if (active.kind === "ambiguous") return undefined;
|
|
159
|
+
return tasks.find(task => task.status === "blocked" && task.assignee === workerId && task.claim?.owner === workerId);
|
|
160
|
+
}
|
|
129
161
|
|
|
130
162
|
type EventAppender = (event: {
|
|
131
163
|
type: string;
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { appendJsonl, type StateWriterOptions } from "./state-writer";
|
|
4
|
+
|
|
5
|
+
export type GjcTeamWorkerMemoryGuardState = "idle" | "advisory" | "retrying" | "checkpointed" | "replaced" | "blocked";
|
|
6
|
+
|
|
7
|
+
export type GjcTeamWorkerMemoryGuardCheckpointKind =
|
|
8
|
+
| "clean"
|
|
9
|
+
| "eligible"
|
|
10
|
+
| "protected_only"
|
|
11
|
+
| "conflicted"
|
|
12
|
+
| "git_error";
|
|
13
|
+
|
|
14
|
+
export type GjcTeamWorkerMemoryGuardPidProbe =
|
|
15
|
+
| { kind: "live"; start_time: string }
|
|
16
|
+
| { kind: "absent" }
|
|
17
|
+
| { kind: "unverifiable"; reason: string };
|
|
18
|
+
|
|
19
|
+
export interface GjcTeamWorkerMemoryGuardCheckpoint {
|
|
20
|
+
kind: GjcTeamWorkerMemoryGuardCheckpointKind;
|
|
21
|
+
files: string[];
|
|
22
|
+
head?: string | null;
|
|
23
|
+
commit?: string | null;
|
|
24
|
+
recorded_at: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GjcTeamWorkerMemoryGuardReplacement {
|
|
28
|
+
old_pane_id?: string;
|
|
29
|
+
new_pane_id?: string;
|
|
30
|
+
recorded_at: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GjcTeamWorkerMemoryGuardLedger {
|
|
34
|
+
schema_version: 1;
|
|
35
|
+
worker_id: string;
|
|
36
|
+
platform: string;
|
|
37
|
+
state: GjcTeamWorkerMemoryGuardState;
|
|
38
|
+
automatic_action_allowed: boolean;
|
|
39
|
+
retry_count: number;
|
|
40
|
+
retry_limit: number;
|
|
41
|
+
current_task_id?: string;
|
|
42
|
+
last_incident_id?: string;
|
|
43
|
+
last_reason?: string;
|
|
44
|
+
last_pid_probe?: GjcTeamWorkerMemoryGuardPidProbe;
|
|
45
|
+
last_checkpoint?: GjcTeamWorkerMemoryGuardCheckpoint;
|
|
46
|
+
last_replacement?: GjcTeamWorkerMemoryGuardReplacement;
|
|
47
|
+
updated_at: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface GjcTeamWorkerMemoryGuardSelectionCandidate {
|
|
51
|
+
worker_id: string;
|
|
52
|
+
platform: string;
|
|
53
|
+
excess_bytes: number;
|
|
54
|
+
retry_count: number;
|
|
55
|
+
retry_limit: number;
|
|
56
|
+
blocked?: boolean;
|
|
57
|
+
current_task_id?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface GjcTeamWorkerMemoryGuardSelection {
|
|
61
|
+
worker_id: string;
|
|
62
|
+
excess_bytes: number;
|
|
63
|
+
retry_count: number;
|
|
64
|
+
current_task_id?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const ledgerKeys = new Set([
|
|
68
|
+
"schema_version",
|
|
69
|
+
"worker_id",
|
|
70
|
+
"platform",
|
|
71
|
+
"state",
|
|
72
|
+
"automatic_action_allowed",
|
|
73
|
+
"retry_count",
|
|
74
|
+
"retry_limit",
|
|
75
|
+
"current_task_id",
|
|
76
|
+
"last_incident_id",
|
|
77
|
+
"last_reason",
|
|
78
|
+
"last_pid_probe",
|
|
79
|
+
"last_checkpoint",
|
|
80
|
+
"last_replacement",
|
|
81
|
+
"updated_at",
|
|
82
|
+
]);
|
|
83
|
+
const checkpointKeys = new Set(["kind", "files", "head", "commit", "recorded_at"]);
|
|
84
|
+
const replacementKeys = new Set(["old_pane_id", "new_pane_id", "recorded_at"]);
|
|
85
|
+
const absentPidProbeKeys = new Set(["kind"]);
|
|
86
|
+
const livePidProbeKeys = new Set(["kind", "start_time"]);
|
|
87
|
+
const unverifiablePidProbeKeys = new Set(["kind", "reason"]);
|
|
88
|
+
|
|
89
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
90
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function hasExactKeys(value: Record<string, unknown>, keys: Set<string>): boolean {
|
|
94
|
+
return Object.keys(value).every(key => keys.has(key));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
98
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isTimestamp(value: unknown): value is string {
|
|
102
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isRetryCount(value: unknown): value is number {
|
|
106
|
+
return Number.isInteger(value) && (value as number) >= 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isWorkerMemoryGuardState(value: unknown): value is GjcTeamWorkerMemoryGuardState {
|
|
110
|
+
return ["idle", "advisory", "retrying", "checkpointed", "replaced", "blocked"].includes(String(value));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isCheckpointKind(value: unknown): value is GjcTeamWorkerMemoryGuardCheckpointKind {
|
|
114
|
+
return ["clean", "eligible", "protected_only", "conflicted", "git_error"].includes(String(value));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function workerMemoryGuardLedgerPath(dir: string, workerId: string): string {
|
|
118
|
+
return path.join(dir, "workers", workerId, "memory-guard.json");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function normalizeGjcTeamWorkerMemoryGuardPidProbe(
|
|
122
|
+
value: unknown,
|
|
123
|
+
): GjcTeamWorkerMemoryGuardPidProbe | undefined {
|
|
124
|
+
if (value === undefined) return undefined;
|
|
125
|
+
if (!isRecord(value)) throw new Error("invalid_worker_memory_guard_pid_probe");
|
|
126
|
+
const kind = typeof value.kind === "string" ? value.kind : "";
|
|
127
|
+
if (kind === "absent") {
|
|
128
|
+
if (!hasExactKeys(value, absentPidProbeKeys)) throw new Error("invalid_worker_memory_guard_pid_probe:absent");
|
|
129
|
+
return { kind: "absent" };
|
|
130
|
+
}
|
|
131
|
+
if (kind === "live") {
|
|
132
|
+
if (!hasExactKeys(value, livePidProbeKeys) || !isNonEmptyString(value.start_time))
|
|
133
|
+
throw new Error("invalid_worker_memory_guard_pid_probe:live");
|
|
134
|
+
return { kind: "live", start_time: value.start_time.trim() };
|
|
135
|
+
}
|
|
136
|
+
if (kind === "unverifiable") {
|
|
137
|
+
if (!hasExactKeys(value, unverifiablePidProbeKeys) || !isNonEmptyString(value.reason))
|
|
138
|
+
throw new Error("invalid_worker_memory_guard_pid_probe:unverifiable");
|
|
139
|
+
return { kind: "unverifiable", reason: value.reason.trim() };
|
|
140
|
+
}
|
|
141
|
+
throw new Error("invalid_worker_memory_guard_pid_probe");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function isCanonicalGjcTeamWorkerMemoryGuardCheckpoint(
|
|
145
|
+
value: unknown,
|
|
146
|
+
): value is GjcTeamWorkerMemoryGuardCheckpoint {
|
|
147
|
+
return (
|
|
148
|
+
isRecord(value) &&
|
|
149
|
+
hasExactKeys(value, checkpointKeys) &&
|
|
150
|
+
isCheckpointKind(value.kind) &&
|
|
151
|
+
Array.isArray(value.files) &&
|
|
152
|
+
value.files.every(file => isNonEmptyString(file)) &&
|
|
153
|
+
(value.head === undefined || value.head === null || isNonEmptyString(value.head)) &&
|
|
154
|
+
(value.commit === undefined || value.commit === null || isNonEmptyString(value.commit)) &&
|
|
155
|
+
isTimestamp(value.recorded_at)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function isCanonicalGjcTeamWorkerMemoryGuardReplacement(
|
|
160
|
+
value: unknown,
|
|
161
|
+
): value is GjcTeamWorkerMemoryGuardReplacement {
|
|
162
|
+
return (
|
|
163
|
+
isRecord(value) &&
|
|
164
|
+
hasExactKeys(value, replacementKeys) &&
|
|
165
|
+
(value.old_pane_id === undefined || isNonEmptyString(value.old_pane_id)) &&
|
|
166
|
+
(value.new_pane_id === undefined || isNonEmptyString(value.new_pane_id)) &&
|
|
167
|
+
isTimestamp(value.recorded_at)
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function isCanonicalGjcTeamWorkerMemoryGuardPidProbe(value: unknown): value is GjcTeamWorkerMemoryGuardPidProbe {
|
|
172
|
+
try {
|
|
173
|
+
return normalizeGjcTeamWorkerMemoryGuardPidProbe(value) !== undefined;
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function isCanonicalGjcTeamWorkerMemoryGuardLedger(value: unknown): value is GjcTeamWorkerMemoryGuardLedger {
|
|
180
|
+
return (
|
|
181
|
+
isRecord(value) &&
|
|
182
|
+
hasExactKeys(value, ledgerKeys) &&
|
|
183
|
+
value.schema_version === 1 &&
|
|
184
|
+
isNonEmptyString(value.worker_id) &&
|
|
185
|
+
isNonEmptyString(value.platform) &&
|
|
186
|
+
isWorkerMemoryGuardState(value.state) &&
|
|
187
|
+
typeof value.automatic_action_allowed === "boolean" &&
|
|
188
|
+
isRetryCount(value.retry_count) &&
|
|
189
|
+
isRetryCount(value.retry_limit) &&
|
|
190
|
+
(value.current_task_id === undefined || isNonEmptyString(value.current_task_id)) &&
|
|
191
|
+
(value.last_incident_id === undefined || isNonEmptyString(value.last_incident_id)) &&
|
|
192
|
+
(value.last_reason === undefined || isNonEmptyString(value.last_reason)) &&
|
|
193
|
+
(value.last_pid_probe === undefined || isCanonicalGjcTeamWorkerMemoryGuardPidProbe(value.last_pid_probe)) &&
|
|
194
|
+
(value.last_checkpoint === undefined || isCanonicalGjcTeamWorkerMemoryGuardCheckpoint(value.last_checkpoint)) &&
|
|
195
|
+
(value.last_replacement === undefined ||
|
|
196
|
+
isCanonicalGjcTeamWorkerMemoryGuardReplacement(value.last_replacement)) &&
|
|
197
|
+
isTimestamp(value.updated_at)
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function createInitialGjcTeamWorkerMemoryGuardLedger(input: {
|
|
202
|
+
workerId: string;
|
|
203
|
+
platform: string;
|
|
204
|
+
now: string;
|
|
205
|
+
retryLimit?: number;
|
|
206
|
+
}): GjcTeamWorkerMemoryGuardLedger {
|
|
207
|
+
const retryLimit = Number.isInteger(input.retryLimit) && (input.retryLimit ?? 0) > 0 ? input.retryLimit! : 2;
|
|
208
|
+
return {
|
|
209
|
+
schema_version: 1,
|
|
210
|
+
worker_id: input.workerId,
|
|
211
|
+
platform: input.platform.trim() || "unknown",
|
|
212
|
+
state: "idle",
|
|
213
|
+
automatic_action_allowed: false,
|
|
214
|
+
retry_count: 0,
|
|
215
|
+
retry_limit: retryLimit,
|
|
216
|
+
updated_at: input.now,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function workerIndex(workerId: string): number {
|
|
221
|
+
const match = /(?:^|[^0-9])(\d+)$/.exec(workerId);
|
|
222
|
+
return match ? Number.parseInt(match[1]!, 10) : Number.MAX_SAFE_INTEGER;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function selectGjcTeamWorkerMemoryGuardCandidate(
|
|
226
|
+
candidates: readonly GjcTeamWorkerMemoryGuardSelectionCandidate[],
|
|
227
|
+
): GjcTeamWorkerMemoryGuardSelection | undefined {
|
|
228
|
+
const eligible = candidates
|
|
229
|
+
.filter(candidate => candidate.platform === "linux")
|
|
230
|
+
.filter(candidate => Number.isFinite(candidate.excess_bytes) && candidate.excess_bytes > 0)
|
|
231
|
+
.filter(candidate => !candidate.blocked)
|
|
232
|
+
.filter(candidate => candidate.retry_count < candidate.retry_limit)
|
|
233
|
+
.sort((left, right) => {
|
|
234
|
+
if (right.excess_bytes !== left.excess_bytes) return right.excess_bytes - left.excess_bytes;
|
|
235
|
+
if (left.retry_count !== right.retry_count) return left.retry_count - right.retry_count;
|
|
236
|
+
const leftIndex = workerIndex(left.worker_id);
|
|
237
|
+
const rightIndex = workerIndex(right.worker_id);
|
|
238
|
+
if (leftIndex !== rightIndex) return leftIndex - rightIndex;
|
|
239
|
+
return left.worker_id.localeCompare(right.worker_id);
|
|
240
|
+
});
|
|
241
|
+
const first = eligible[0];
|
|
242
|
+
return first
|
|
243
|
+
? {
|
|
244
|
+
worker_id: first.worker_id,
|
|
245
|
+
excess_bytes: first.excess_bytes,
|
|
246
|
+
retry_count: first.retry_count,
|
|
247
|
+
current_task_id: first.current_task_id,
|
|
248
|
+
}
|
|
249
|
+
: undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export type TeamWorkerMemoryGuardAction = "advisory" | "replace" | "blocked";
|
|
253
|
+
export type TeamWorkerMemoryGuardResult = "noop" | "scheduled" | "succeeded" | "failed" | "blocked";
|
|
254
|
+
|
|
255
|
+
export interface TeamWorkerMemoryGuardLedgerEntry {
|
|
256
|
+
schema_version: 1;
|
|
257
|
+
recorded_at: string;
|
|
258
|
+
incident_id: string;
|
|
259
|
+
team_name: string;
|
|
260
|
+
worker_id: string;
|
|
261
|
+
task_id: string;
|
|
262
|
+
claim_token: string;
|
|
263
|
+
attempt: number;
|
|
264
|
+
platform: NodeJS.Platform;
|
|
265
|
+
action: TeamWorkerMemoryGuardAction;
|
|
266
|
+
result: TeamWorkerMemoryGuardResult;
|
|
267
|
+
reason: string;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const teamWorkerMemoryGuardActions = new Set<TeamWorkerMemoryGuardAction>(["advisory", "replace", "blocked"]);
|
|
271
|
+
const teamWorkerMemoryGuardResults = new Set<TeamWorkerMemoryGuardResult>([
|
|
272
|
+
"noop",
|
|
273
|
+
"scheduled",
|
|
274
|
+
"succeeded",
|
|
275
|
+
"failed",
|
|
276
|
+
"blocked",
|
|
277
|
+
]);
|
|
278
|
+
|
|
279
|
+
export function isCanonicalTeamWorkerMemoryGuardLedgerEntry(value: unknown): value is TeamWorkerMemoryGuardLedgerEntry {
|
|
280
|
+
if (!isRecord(value)) return false;
|
|
281
|
+
const requiredKeys = [
|
|
282
|
+
"schema_version",
|
|
283
|
+
"recorded_at",
|
|
284
|
+
"incident_id",
|
|
285
|
+
"team_name",
|
|
286
|
+
"worker_id",
|
|
287
|
+
"task_id",
|
|
288
|
+
"claim_token",
|
|
289
|
+
"attempt",
|
|
290
|
+
"platform",
|
|
291
|
+
"action",
|
|
292
|
+
"result",
|
|
293
|
+
"reason",
|
|
294
|
+
];
|
|
295
|
+
return (
|
|
296
|
+
Object.keys(value).length === requiredKeys.length &&
|
|
297
|
+
requiredKeys.every(key => Object.hasOwn(value, key)) &&
|
|
298
|
+
value.schema_version === 1 &&
|
|
299
|
+
isTimestamp(value.recorded_at) &&
|
|
300
|
+
isNonEmptyString(value.incident_id) &&
|
|
301
|
+
isNonEmptyString(value.team_name) &&
|
|
302
|
+
isNonEmptyString(value.worker_id) &&
|
|
303
|
+
isNonEmptyString(value.task_id) &&
|
|
304
|
+
isNonEmptyString(value.claim_token) &&
|
|
305
|
+
Number.isInteger(value.attempt) &&
|
|
306
|
+
(value.attempt as number) > 0 &&
|
|
307
|
+
isNonEmptyString(value.platform) &&
|
|
308
|
+
teamWorkerMemoryGuardActions.has(value.action as TeamWorkerMemoryGuardAction) &&
|
|
309
|
+
teamWorkerMemoryGuardResults.has(value.result as TeamWorkerMemoryGuardResult) &&
|
|
310
|
+
isNonEmptyString(value.reason)
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function teamWorkerMemoryGuardDir(workerDirPath: string): string {
|
|
315
|
+
return path.join(workerDirPath, "memory-guard");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function teamWorkerMemoryGuardLedgerPath(workerDirPath: string): string {
|
|
319
|
+
return path.join(teamWorkerMemoryGuardDir(workerDirPath), "ledger.jsonl");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function canMutateTeamWorkerMemoryGuard(platform: NodeJS.Platform): boolean {
|
|
323
|
+
return platform === "linux";
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function advisoryReasonForTeamWorkerMemoryGuard(platform: NodeJS.Platform): string | undefined {
|
|
327
|
+
return canMutateTeamWorkerMemoryGuard(platform) ? undefined : `unsupported_platform:${platform}`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export async function appendTeamWorkerMemoryGuardLedgerEntry(
|
|
331
|
+
workerDirPath: string,
|
|
332
|
+
entry: TeamWorkerMemoryGuardLedgerEntry,
|
|
333
|
+
options?: StateWriterOptions,
|
|
334
|
+
): Promise<string> {
|
|
335
|
+
await fs.mkdir(teamWorkerMemoryGuardDir(workerDirPath), { recursive: true });
|
|
336
|
+
return appendJsonl(teamWorkerMemoryGuardLedgerPath(workerDirPath), entry, options);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export async function readTeamWorkerMemoryGuardLedger(
|
|
340
|
+
workerDirPath: string,
|
|
341
|
+
): Promise<TeamWorkerMemoryGuardLedgerEntry[]> {
|
|
342
|
+
const ledgerPath = teamWorkerMemoryGuardLedgerPath(workerDirPath);
|
|
343
|
+
if (!(await Bun.file(ledgerPath).exists())) return [];
|
|
344
|
+
const entries: TeamWorkerMemoryGuardLedgerEntry[] = [];
|
|
345
|
+
for (const [index, line] of (await Bun.file(ledgerPath).text()).split(/\r?\n/).filter(Boolean).entries()) {
|
|
346
|
+
const parsed = JSON.parse(line) as unknown;
|
|
347
|
+
if (!isCanonicalTeamWorkerMemoryGuardLedgerEntry(parsed))
|
|
348
|
+
throw new Error(`invalid_team_worker_memory_guard_ledger:${ledgerPath}:${index + 1}`);
|
|
349
|
+
entries.push(parsed);
|
|
350
|
+
}
|
|
351
|
+
return entries;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function nextTeamWorkerMemoryGuardAttempt(
|
|
355
|
+
entries: readonly TeamWorkerMemoryGuardLedgerEntry[],
|
|
356
|
+
incidentId: string,
|
|
357
|
+
): number {
|
|
358
|
+
let attempt = 0;
|
|
359
|
+
for (const entry of entries) {
|
|
360
|
+
if (entry.incident_id === incidentId && entry.attempt > attempt) attempt = entry.attempt;
|
|
361
|
+
}
|
|
362
|
+
return attempt + 1;
|
|
363
|
+
}
|
|
@@ -352,6 +352,7 @@ export async function writeGjcWorkerStartupAck(
|
|
|
352
352
|
pid: typeof input.pid === "number" ? input.pid : undefined,
|
|
353
353
|
session: typeof input.session === "string" ? input.session : undefined,
|
|
354
354
|
protocol_version: String(input.protocol_version ?? "1"),
|
|
355
|
+
replacement_token: typeof input.replacement_token === "string" ? input.replacement_token : undefined,
|
|
355
356
|
ack_at: runtime.now(),
|
|
356
357
|
};
|
|
357
358
|
await runtime.writeJson(path.join(runtime.workerDir(dir, worker), "startup-ack.json"), ack);
|
|
@@ -960,6 +960,24 @@ export function lifecyclePaths(stateDir: string, sessionId: string, generation:
|
|
|
960
960
|
};
|
|
961
961
|
}
|
|
962
962
|
|
|
963
|
+
export interface MemoryGuardClaimPaths {
|
|
964
|
+
root: string;
|
|
965
|
+
databaseFile: string;
|
|
966
|
+
walFile: string;
|
|
967
|
+
shmFile: string;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
export function memoryGuardClaimPaths(stateDir: string, sessionId: string): MemoryGuardClaimPaths {
|
|
971
|
+
const root = path.join(stateDir, "memory-guard-claims", sessionId);
|
|
972
|
+
const databaseFile = path.join(root, "claims.sqlite");
|
|
973
|
+
return {
|
|
974
|
+
root,
|
|
975
|
+
databaseFile,
|
|
976
|
+
walFile: `${databaseFile}-wal`,
|
|
977
|
+
shmFile: `${databaseFile}-shm`,
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
|
|
963
981
|
export async function replaceOwnerGeneration(
|
|
964
982
|
stateDir: string,
|
|
965
983
|
sessionId: string,
|
|
@@ -290,6 +290,26 @@ export function findLedgerReceiptEvent(
|
|
|
290
290
|
}) ?? null
|
|
291
291
|
);
|
|
292
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* A final-aggregate receipt whose recorded ledger checkpoint quality gate is
|
|
295
|
+
* missing a clean `criticReview` OKAY can never satisfy the completion guard,
|
|
296
|
+
* yet is not "stale" under {@link validateReceiptFreshBase}. Detect it so an
|
|
297
|
+
* identical-evidence complete replay can re-verify and re-mint with a
|
|
298
|
+
* corrected gate instead of no-opping into a permanently blocked run.
|
|
299
|
+
*/
|
|
300
|
+
export function finalAggregateReceiptMissingCriticOkay(
|
|
301
|
+
ledger: readonly UltragoalLedgerEvent[],
|
|
302
|
+
receipt: UltragoalCompletionVerification,
|
|
303
|
+
): boolean {
|
|
304
|
+
if (receipt.receiptKind !== "final-aggregate") return false;
|
|
305
|
+
const event = findLedgerReceiptEvent(ledger, receipt);
|
|
306
|
+
if (!event) return false;
|
|
307
|
+
const gate = event.qualityGateJson;
|
|
308
|
+
if (typeof gate !== "object" || gate === null || Array.isArray(gate)) return true;
|
|
309
|
+
const criticReview = (gate as Record<string, unknown>).criticReview;
|
|
310
|
+
if (typeof criticReview !== "object" || criticReview === null || Array.isArray(criticReview)) return true;
|
|
311
|
+
return (criticReview as Record<string, unknown>).verdict !== "OKAY";
|
|
312
|
+
}
|
|
293
313
|
|
|
294
314
|
export function validateReceiptFreshBase(input: {
|
|
295
315
|
plan: UltragoalPlan;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
|
-
import * as os from "node:os";
|
|
3
2
|
import * as path from "node:path";
|
|
3
|
+
import { getConfigRootDir } from "@gajae-code/utils";
|
|
4
4
|
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";
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
computeCriticVerdictPlanGeneration,
|
|
20
20
|
computeUltragoalPlanGeneration,
|
|
21
21
|
countNonOkayTerminalCriticVerdicts,
|
|
22
|
+
finalAggregateReceiptMissingCriticOkay,
|
|
22
23
|
findFreshBatchCloseReceipt,
|
|
23
24
|
findLedgerReceiptEvent,
|
|
24
25
|
isCleanPauseCriticVerdictShape,
|
|
@@ -490,8 +491,7 @@ export async function resolveUltragoalNudgeBudget(cwd: string): Promise<{ budget
|
|
|
490
491
|
const projectPath = path.join(gjcRoot(cwd), "settings.json");
|
|
491
492
|
const project = await readSettingsNudgeBudget(projectPath);
|
|
492
493
|
if (project !== null) return { budget: project, source: projectPath };
|
|
493
|
-
const
|
|
494
|
-
const userPath = path.join(userDir, "settings.json");
|
|
494
|
+
const userPath = path.join(getConfigRootDir(), "settings.json");
|
|
495
495
|
const user = await readSettingsNudgeBudget(userPath);
|
|
496
496
|
if (user !== null) return { budget: user, source: userPath };
|
|
497
497
|
return { budget: DEFAULT_ULTRAGOAL_NUDGE_BUDGET, source: "default" };
|
|
@@ -3091,8 +3091,13 @@ export async function checkpointUltragoalGoal(input: {
|
|
|
3091
3091
|
// receipt, the replay is a genuine re-verification: it must run the full
|
|
3092
3092
|
// quality gate and mint a fresh receipt, otherwise a completed goal with a
|
|
3093
3093
|
// context-staled receipt can never be repaired (different evidence is
|
|
3094
|
-
// rejected on complete goals by design). A
|
|
3095
|
-
//
|
|
3094
|
+
// rejected on complete goals by design). A final-aggregate receipt whose
|
|
3095
|
+
// recorded checkpoint gate lacks a clean criticReview OKAY is likewise
|
|
3096
|
+
// repair-eligible: it is not "stale", but the completion guard rejects it
|
|
3097
|
+
// forever (active_missing_critic_verdict), so a no-op replay would leave
|
|
3098
|
+
// the run permanently unable to complete even after the terminal critic
|
|
3099
|
+
// records OKAY. A mutated goal row keeps the fail-loud tamper handling in
|
|
3100
|
+
// the idempotent branch below.
|
|
3096
3101
|
const staleCompleteReceiptReplay =
|
|
3097
3102
|
input.status === "complete" &&
|
|
3098
3103
|
goal.status === "complete" &&
|
|
@@ -3100,13 +3105,14 @@ export async function checkpointUltragoalGoal(input: {
|
|
|
3100
3105
|
Boolean(matchingIdempotentEvent) &&
|
|
3101
3106
|
(!goal.completionVerification ||
|
|
3102
3107
|
(goal.completionVerification.verifiedAt === goal.updatedAt &&
|
|
3103
|
-
validateReceiptFreshBase({
|
|
3108
|
+
(validateReceiptFreshBase({
|
|
3104
3109
|
plan,
|
|
3105
3110
|
ledger: ledgerBefore,
|
|
3106
3111
|
goal,
|
|
3107
3112
|
receipt: goal.completionVerification,
|
|
3108
3113
|
receiptKind: goal.completionVerification.receiptKind,
|
|
3109
|
-
}) !== null
|
|
3114
|
+
}) !== null ||
|
|
3115
|
+
finalAggregateReceiptMissingCriticOkay(ledgerBefore, goal.completionVerification))));
|
|
3110
3116
|
if (
|
|
3111
3117
|
goal.status === input.status &&
|
|
3112
3118
|
goal.evidence === evidence &&
|