@gajae-code/coding-agent 0.11.11 → 0.12.1
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 +131 -4
- package/dist/types/async/job-manager.d.ts +17 -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 +83 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +38 -4
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- 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/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +70 -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 +27 -1
- package/dist/types/gjc-runtime/team-store.d.ts +12 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +17 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
- 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 +10 -19
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/session-observer-overlay.d.ts +22 -0
- package/dist/types/modes/components/settings-selector.d.ts +11 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- 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/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/index.d.ts +44 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +44 -0
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
- package/dist/types/sdk/bus/reconciliation-store.d.ts +69 -0
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- 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/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +82 -12
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +48 -19
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +96 -12
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -4
- package/dist/types/skill-state/workflow-hud.d.ts +9 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +7 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +17 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/output-meta.d.ts +44 -3
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -7
- package/dist/types/tools/resource-gc.d.ts +4 -0
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +11 -13
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/generate-sdk-operation-inventory.ts +5 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +285 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/async/job-manager.ts +78 -20
- package/src/capability/index.ts +8 -0
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- 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 +20 -2
- package/src/config/file-lock.ts +11 -2
- 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 +74 -1
- package/src/config/settings.ts +246 -108
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +230 -82
- package/src/defaults/gjc/skills/ralplan/SKILL.md +77 -41
- package/src/defaults/gjc/skills/team/SKILL.md +3 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +62 -39
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/eval/py/executor.ts +7 -2
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +46 -5
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- 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/launch-tmux.ts +25 -6
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/gjc-runtime/ralplan-runtime.ts +1149 -98
- 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 +42 -5
- package/src/gjc-runtime/team-runtime.ts +1085 -40
- package/src/gjc-runtime/team-store.ts +32 -0
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -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 +28 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +414 -618
- package/src/gjc-runtime/workflow-manifest.generated.json +105 -449
- package/src/gjc-runtime/workflow-manifest.ts +38 -247
- 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 +21 -22
- 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 +98 -0
- package/src/modes/acp/acp-agent.ts +339 -191
- 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/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +61 -4
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +96 -28
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/session-observer-overlay.ts +457 -83
- package/src/modes/components/settings-selector.ts +116 -23
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +3 -14
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/event-controller.ts +109 -27
- package/src/modes/controllers/extension-ui-controller.ts +26 -2
- package/src/modes/controllers/input-controller.ts +18 -3
- package/src/modes/controllers/selector-controller.ts +127 -36
- package/src/modes/interactive-mode.ts +62 -3
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/runtime-init.ts +22 -2
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +94 -1
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/agents/architect.md +9 -1
- package/src/prompts/agents/critic.md +9 -0
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -5
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/read.md +4 -2
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/lifecycle.ts +25 -6
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +6 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/index.ts +984 -146
- package/src/sdk/bus/kind-aware-reconciliation.ts +317 -0
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/prompt-reconciliation.ts +2 -1
- package/src/sdk/bus/reconciliation-store.ts +303 -0
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -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/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +51 -0
- package/src/sdk/protocol/operation-registry.ts +7 -2
- package/src/sdk/session.ts +101 -10
- package/src/session/agent-session.ts +1286 -220
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +44 -6
- package/src/session/internal/managed-session-storage.ts +48 -6
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +2364 -438
- package/src/session/session-storage.ts +9 -1
- package/src/session/streaming-output.ts +500 -111
- package/src/setup/credential-import.ts +75 -13
- package/src/setup/provider-onboarding.ts +19 -4
- package/src/skill-state/active-state.ts +32 -10
- package/src/skill-state/workflow-hud.ts +42 -2
- package/src/skill-state/workflow-mutation-guard.ts +560 -8
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +23 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +23 -7
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +169 -118
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/fetch.ts +114 -23
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/output-meta.ts +281 -20
- package/src/tools/path-utils.ts +8 -3
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1560 -310
- package/src/tools/resource-gc.ts +182 -18
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/tools/tool-result.ts +14 -1
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- 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/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- 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
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
|
@@ -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,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime-owned `gjc team` worker heartbeat.
|
|
3
|
+
*
|
|
4
|
+
* Worker liveness used to be published only when the model remembered to call
|
|
5
|
+
* `gjc team api update-worker-heartbeat` between turns. A worker inside a
|
|
6
|
+
* single tool call longer than `GJC_TEAM_HEARTBEAT_STALE_MS` (default 120s) was
|
|
7
|
+
* therefore reported as stale and had its task claim requeued mid-flight — even
|
|
8
|
+
* though the claim lease it invalidated is 30 minutes long. This module makes
|
|
9
|
+
* the worker process itself publish liveness while a turn is in flight, so the
|
|
10
|
+
* heartbeat records what the process is doing instead of what the model
|
|
11
|
+
* remembered to report.
|
|
12
|
+
*
|
|
13
|
+
* The published record carries the writer's real pid, so leader-side diagnostics
|
|
14
|
+
* name the process that is actually working rather than a placeholder.
|
|
15
|
+
*/
|
|
16
|
+
import { logger } from "@gajae-code/utils";
|
|
17
|
+
import { parseHeartbeatStaleMs, refreshGjcWorkerHeartbeat, type WorkerHeartbeatFile } from "./team-runtime";
|
|
18
|
+
|
|
19
|
+
/** Publish several times per stale window so one missed tick cannot cross it. */
|
|
20
|
+
const HEARTBEAT_INTERVAL_DIVISOR = 3;
|
|
21
|
+
const MIN_HEARTBEAT_INTERVAL_MS = 1;
|
|
22
|
+
const MAX_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
23
|
+
|
|
24
|
+
export interface GjcTeamWorkerIdentity {
|
|
25
|
+
teamName: string;
|
|
26
|
+
workerId: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Resolves the team identity injected into a worker pane by `gjc team` startup. */
|
|
30
|
+
export function resolveGjcTeamWorkerIdentity(env: NodeJS.ProcessEnv = process.env): GjcTeamWorkerIdentity | undefined {
|
|
31
|
+
const teamName = env.GJC_TEAM_NAME?.trim();
|
|
32
|
+
const workerId = env.GJC_TEAM_WORKER_ID?.trim() || env.GJC_TEAM_INTERNAL_WORKER?.split("/").pop()?.trim();
|
|
33
|
+
return teamName && workerId ? { teamName, workerId } : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Refresh cadence derived from the leader's stale window; `0` disables publishing. */
|
|
37
|
+
export function resolveGjcTeamWorkerHeartbeatIntervalMs(env: NodeJS.ProcessEnv = process.env): number {
|
|
38
|
+
const staleMs = parseHeartbeatStaleMs(env);
|
|
39
|
+
if (staleMs <= 0) return 0;
|
|
40
|
+
return Math.min(
|
|
41
|
+
MAX_HEARTBEAT_INTERVAL_MS,
|
|
42
|
+
Math.max(MIN_HEARTBEAT_INTERVAL_MS, Math.floor(staleMs / HEARTBEAT_INTERVAL_DIVISOR)),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Publish one runtime-owned heartbeat for the worker this process is running as.
|
|
48
|
+
* Returns `undefined` when the process is not a team worker.
|
|
49
|
+
*
|
|
50
|
+
* `turn_count` and process-incarnation metadata are merged under the same
|
|
51
|
+
* mutation fence used by CLI heartbeat updates.
|
|
52
|
+
*/
|
|
53
|
+
export async function writeGjcTeamWorkerRuntimeHeartbeat(
|
|
54
|
+
cwd: string = process.cwd(),
|
|
55
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
56
|
+
): Promise<WorkerHeartbeatFile | undefined> {
|
|
57
|
+
const identity = resolveGjcTeamWorkerIdentity(env);
|
|
58
|
+
if (!identity) return undefined;
|
|
59
|
+
return refreshGjcWorkerHeartbeat(identity.teamName, identity.workerId, process.pid, cwd, env);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface GjcTeamWorkerHeartbeatReporterOptions {
|
|
63
|
+
write: () => Promise<void>;
|
|
64
|
+
intervalMs: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Publishes a worker heartbeat while a turn is in flight.
|
|
69
|
+
*
|
|
70
|
+
* Writes never overlap; a slow or failing write is logged and skipped rather
|
|
71
|
+
* than queued, because only the most recent record matters. The timer is
|
|
72
|
+
* unreferenced so it can never hold the process open.
|
|
73
|
+
*/
|
|
74
|
+
export class GjcTeamWorkerHeartbeatReporter {
|
|
75
|
+
readonly #write: () => Promise<void>;
|
|
76
|
+
readonly #intervalMs: number;
|
|
77
|
+
#timer: NodeJS.Timeout | undefined;
|
|
78
|
+
#inFlight: Promise<void> | undefined;
|
|
79
|
+
#disposed = false;
|
|
80
|
+
|
|
81
|
+
constructor(options: GjcTeamWorkerHeartbeatReporterOptions) {
|
|
82
|
+
this.#write = options.write;
|
|
83
|
+
this.#intervalMs = options.intervalMs;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Reporter for the current process, or `undefined` when it is not a team worker. */
|
|
87
|
+
static forProcess(
|
|
88
|
+
cwd: () => string,
|
|
89
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
90
|
+
): GjcTeamWorkerHeartbeatReporter | undefined {
|
|
91
|
+
if (!resolveGjcTeamWorkerIdentity(env)) return undefined;
|
|
92
|
+
const intervalMs = resolveGjcTeamWorkerHeartbeatIntervalMs(env);
|
|
93
|
+
if (intervalMs <= 0) return undefined;
|
|
94
|
+
return new GjcTeamWorkerHeartbeatReporter({
|
|
95
|
+
intervalMs,
|
|
96
|
+
write: async () => {
|
|
97
|
+
await writeGjcTeamWorkerRuntimeHeartbeat(cwd(), env);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get isRunning(): boolean {
|
|
103
|
+
return this.#timer !== undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Publish immediately, then keep publishing until {@link stop} or {@link dispose}. */
|
|
107
|
+
start(): void {
|
|
108
|
+
if (this.#disposed || this.#timer) return;
|
|
109
|
+
this.#publish();
|
|
110
|
+
this.#timer = setInterval(() => this.#publish(), this.#intervalMs);
|
|
111
|
+
this.#timer.unref?.();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Stop periodic publishing after one final record, so the idle stamp is fresh. */
|
|
115
|
+
stop(): void {
|
|
116
|
+
if (!this.#timer) return;
|
|
117
|
+
clearInterval(this.#timer);
|
|
118
|
+
this.#timer = undefined;
|
|
119
|
+
this.#publish();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Await the write in flight, if any. */
|
|
123
|
+
async flush(): Promise<void> {
|
|
124
|
+
while (this.#inFlight) {
|
|
125
|
+
await this.#inFlight;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Permanently stop publishing. Idempotent. */
|
|
130
|
+
dispose(): void {
|
|
131
|
+
this.#disposed = true;
|
|
132
|
+
if (this.#timer) {
|
|
133
|
+
clearInterval(this.#timer);
|
|
134
|
+
this.#timer = undefined;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#publish(): void {
|
|
139
|
+
if (this.#disposed || this.#inFlight) return;
|
|
140
|
+
const run = this.#write()
|
|
141
|
+
.catch((error: unknown) => {
|
|
142
|
+
logger.warn("GJC team worker heartbeat publish failed", { error: String(error) });
|
|
143
|
+
})
|
|
144
|
+
.finally(() => {
|
|
145
|
+
if (this.#inFlight === run) this.#inFlight = undefined;
|
|
146
|
+
});
|
|
147
|
+
this.#inFlight = run;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -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);
|
|
@@ -110,6 +110,16 @@ export interface TmuxServerProof {
|
|
|
110
110
|
startTime?: string;
|
|
111
111
|
cgroup?: CgroupInfo;
|
|
112
112
|
sessionNames?: string[];
|
|
113
|
+
/**
|
|
114
|
+
* Whether `pid` is kernel-proved evidence about the live tmux server.
|
|
115
|
+
*
|
|
116
|
+
* Only Linux can prove a server PID (`/proc` + cgroup classification), so
|
|
117
|
+
* probes on other platforms may report a placeholder PID with this flag set
|
|
118
|
+
* to `false`. Guards must not build a `#{pid}` predicate from an unproven
|
|
119
|
+
* PID: no live tmux server can ever satisfy it. Omitted means proven, which
|
|
120
|
+
* keeps every probe that reads a real `#{pid}` unchanged.
|
|
121
|
+
*/
|
|
122
|
+
pidProven?: boolean;
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
export interface PlanRequest {
|
|
@@ -960,6 +970,24 @@ export function lifecyclePaths(stateDir: string, sessionId: string, generation:
|
|
|
960
970
|
};
|
|
961
971
|
}
|
|
962
972
|
|
|
973
|
+
export interface MemoryGuardClaimPaths {
|
|
974
|
+
root: string;
|
|
975
|
+
databaseFile: string;
|
|
976
|
+
walFile: string;
|
|
977
|
+
shmFile: string;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
export function memoryGuardClaimPaths(stateDir: string, sessionId: string): MemoryGuardClaimPaths {
|
|
981
|
+
const root = path.join(stateDir, "memory-guard-claims", sessionId);
|
|
982
|
+
const databaseFile = path.join(root, "claims.sqlite");
|
|
983
|
+
return {
|
|
984
|
+
root,
|
|
985
|
+
databaseFile,
|
|
986
|
+
walFile: `${databaseFile}-wal`,
|
|
987
|
+
shmFile: `${databaseFile}-shm`,
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
|
|
963
991
|
export async function replaceOwnerGeneration(
|
|
964
992
|
stateDir: string,
|
|
965
993
|
sessionId: string,
|