@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.
Files changed (182) hide show
  1. package/CHANGELOG.md +49 -3
  2. package/dist/types/cli/read-cli.d.ts +1 -0
  3. package/dist/types/commands/deep-interview.d.ts +9 -0
  4. package/dist/types/commands/harness.d.ts +22 -0
  5. package/dist/types/commands/read.d.ts +6 -0
  6. package/dist/types/config/model-registry.d.ts +4 -0
  7. package/dist/types/config/provider-auth-health.d.ts +14 -0
  8. package/dist/types/config/provider-ranking.d.ts +59 -0
  9. package/dist/types/config/settings-schema.d.ts +29 -1
  10. package/dist/types/config/settings.d.ts +2 -0
  11. package/dist/types/extensibility/extensions/types.d.ts +22 -2
  12. package/dist/types/extensibility/shared-events.d.ts +6 -0
  13. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  14. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  15. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  16. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  17. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  18. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  19. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +52 -1
  20. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  21. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  22. package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
  23. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  24. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  25. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
  26. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  27. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  28. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  29. package/dist/types/main.d.ts +1 -1
  30. package/dist/types/memories/index.d.ts +2 -0
  31. package/dist/types/modes/acp/acp-agent.d.ts +8 -0
  32. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  33. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  34. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  35. package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
  36. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  37. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  38. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  39. package/dist/types/modes/interactive-mode.d.ts +2 -0
  40. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  41. package/dist/types/modes/types.d.ts +2 -0
  42. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  43. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  44. package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
  45. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  46. package/dist/types/sdk/bus/index.d.ts +15 -1
  47. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
  48. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  49. package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
  50. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  51. package/dist/types/sdk/host/host.d.ts +2 -0
  52. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  53. package/dist/types/session/agent-session.d.ts +44 -10
  54. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  55. package/dist/types/session/session-manager.d.ts +25 -13
  56. package/dist/types/session/streaming-output.d.ts +60 -5
  57. package/dist/types/skill-state/active-state.d.ts +0 -4
  58. package/dist/types/skill-state/workflow-hud.d.ts +3 -0
  59. package/dist/types/tools/browser/launch.d.ts +7 -0
  60. package/dist/types/tools/fetch.d.ts +17 -1
  61. package/dist/types/tools/output-meta.d.ts +21 -1
  62. package/dist/types/tools/read-internals.d.ts +12 -0
  63. package/dist/types/tools/read.d.ts +45 -1
  64. package/dist/types/tools/resource-gc.d.ts +4 -0
  65. package/dist/types/tools/tool-result.d.ts +7 -1
  66. package/package.json +9 -13
  67. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  68. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  69. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  70. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  71. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  72. package/src/capability/index.ts +8 -0
  73. package/src/cli/read-cli.ts +2 -1
  74. package/src/cli.ts +1 -0
  75. package/src/commands/deep-interview.ts +29 -9
  76. package/src/commands/harness.ts +35 -12
  77. package/src/commands/read.ts +11 -2
  78. package/src/commands/team.ts +1 -1
  79. package/src/config/model-registry.ts +21 -2
  80. package/src/config/provider-auth-health.ts +42 -0
  81. package/src/config/provider-ranking.ts +119 -0
  82. package/src/config/settings-schema.ts +23 -1
  83. package/src/config/settings.ts +246 -108
  84. package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
  85. package/src/defaults/gjc/skills/ralplan/SKILL.md +58 -25
  86. package/src/defaults/gjc/skills/team/SKILL.md +1 -1
  87. package/src/defaults/gjc/skills/ultragoal/SKILL.md +25 -15
  88. package/src/eval/py/executor.ts +7 -2
  89. package/src/extensibility/extensions/types.ts +28 -4
  90. package/src/extensibility/shared-events.ts +5 -0
  91. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
  92. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  93. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  94. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  95. package/src/gjc-runtime/deep-interview-state.ts +12 -943
  96. package/src/gjc-runtime/gc-render.ts +1 -0
  97. package/src/gjc-runtime/gc-runtime.ts +11 -1
  98. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  99. package/src/gjc-runtime/ralplan-runtime.ts +742 -85
  100. package/src/gjc-runtime/state-runtime.ts +2 -1
  101. package/src/gjc-runtime/state-writer.ts +70 -222
  102. package/src/gjc-runtime/team-launch.ts +34 -5
  103. package/src/gjc-runtime/team-runtime.ts +970 -33
  104. package/src/gjc-runtime/team-store.ts +32 -0
  105. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  106. package/src/gjc-runtime/team-workers.ts +1 -0
  107. package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
  108. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  109. package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
  110. package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
  111. package/src/gjc-runtime/workflow-manifest.ts +34 -179
  112. package/src/hooks/native-skill-hook.ts +17 -4
  113. package/src/hooks/skill-state.ts +1 -23
  114. package/src/internal-urls/docs-index.generated.ts +8 -9
  115. package/src/internal-urls/local-root-gc.ts +127 -0
  116. package/src/main.ts +9 -5
  117. package/src/memories/index.ts +10 -0
  118. package/src/modes/DESIGN.md +47 -0
  119. package/src/modes/acp/acp-agent.ts +43 -7
  120. package/src/modes/acp/acp-event-mapper.ts +14 -7
  121. package/src/modes/acp/acp-mode.ts +39 -3
  122. package/src/modes/components/assistant-message.ts +123 -22
  123. package/src/modes/components/irc-sidebar.ts +176 -62
  124. package/src/modes/components/model-selector.ts +61 -4
  125. package/src/modes/components/oauth-selector.ts +42 -12
  126. package/src/modes/components/read-tool-group.ts +82 -14
  127. package/src/modes/components/session-observer-overlay.ts +187 -32
  128. package/src/modes/components/settings-selector.ts +60 -18
  129. package/src/modes/components/tool-execution.ts +138 -27
  130. package/src/modes/controllers/event-controller.ts +109 -27
  131. package/src/modes/controllers/extension-ui-controller.ts +19 -2
  132. package/src/modes/controllers/input-controller.ts +18 -3
  133. package/src/modes/controllers/selector-controller.ts +83 -37
  134. package/src/modes/interactive-mode.ts +62 -3
  135. package/src/modes/irc-observation-ledger.ts +71 -15
  136. package/src/modes/runtime-init.ts +19 -2
  137. package/src/modes/types.ts +2 -0
  138. package/src/modes/utils/ui-helpers.ts +94 -1
  139. package/src/prompts/agents/architect.md +9 -1
  140. package/src/prompts/agents/critic.md +9 -0
  141. package/src/prompts/system/system-prompt.md +0 -1
  142. package/src/prompts/tools/read.md +4 -2
  143. package/src/runtime-mcp/smithery-auth.ts +17 -2
  144. package/src/runtime-mcp/smithery-connect.ts +11 -1
  145. package/src/sdk/broker/lifecycle.ts +25 -6
  146. package/src/sdk/bus/control-drain-lease.ts +104 -0
  147. package/src/sdk/bus/index.ts +341 -38
  148. package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
  149. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  150. package/src/sdk/bus/reconciliation-store.ts +224 -0
  151. package/src/sdk/host/control/dispatch.ts +5 -1
  152. package/src/sdk/host/control/operations.ts +1 -1
  153. package/src/sdk/host/host.ts +23 -1
  154. package/src/sdk/host/query/handlers.ts +55 -0
  155. package/src/sdk/mcp/server.ts +46 -18
  156. package/src/sdk/protocol/operation-inventory.generated.json +29 -0
  157. package/src/sdk/protocol/operation-registry.ts +7 -2
  158. package/src/sdk/session.ts +13 -1
  159. package/src/session/agent-session.ts +466 -50
  160. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  161. package/src/session/session-manager.ts +764 -17
  162. package/src/session/streaming-output.ts +262 -43
  163. package/src/setup/provider-onboarding.ts +19 -4
  164. package/src/skill-state/active-state.ts +0 -10
  165. package/src/skill-state/workflow-hud.ts +25 -2
  166. package/src/skill-state/workflow-mutation-guard.ts +65 -7
  167. package/src/tools/ask.ts +89 -69
  168. package/src/tools/browser/launch.ts +41 -6
  169. package/src/tools/fetch.ts +114 -23
  170. package/src/tools/output-meta.ts +176 -9
  171. package/src/tools/path-utils.ts +5 -1
  172. package/src/tools/read-internals.ts +44 -0
  173. package/src/tools/read.ts +1542 -310
  174. package/src/tools/resource-gc.ts +182 -18
  175. package/src/tools/tool-result.ts +14 -1
  176. package/src/web/search/providers/codex.ts +1 -1
  177. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  178. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  179. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  180. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  181. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  182. 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 userDir = process.env.GJC_CONFIG_DIR?.trim() || path.join(os.homedir(), ".gjc");
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 mutated goal row keeps the
3095
- // fail-loud tamper handling in the idempotent branch below.
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 &&