@gajae-code/coding-agent 0.18.6 → 0.18.8
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 +318 -1
- package/dist/types/async/job-manager.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/capability/skill.d.ts +2 -0
- package/dist/types/capability/types.d.ts +6 -0
- package/dist/types/cli/public-command-errors.d.ts +3 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/autorouting-tier-map.d.ts +348 -9
- package/dist/types/config/file-lock.d.ts +1 -0
- package/dist/types/config/model-bindings-applier.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -3
- package/dist/types/config/model-registry.d.ts +4 -9
- package/dist/types/config/settings.d.ts +4 -0
- package/dist/types/config/skill-settings-defaults.d.ts +2 -0
- package/dist/types/cursor-delete-path.d.ts +1 -0
- package/dist/types/cursor-mcp-name.d.ts +8 -0
- package/dist/types/customization/inventory.d.ts +2 -0
- package/dist/types/debug/report-bundle-parent.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
- package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
- package/dist/types/discovery/helpers.d.ts +8 -3
- package/dist/types/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
- package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
- package/dist/types/extensibility/skill-management.d.ts +3 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
- package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
- package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +11 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
- package/dist/types/internal-urls/skill-path.d.ts +1 -0
- package/dist/types/lsp/workspace-path.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
- package/dist/types/modes/controllers/input-controller.d.ts +9 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
- package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
- package/dist/types/progress/collect-project-progress.d.ts +38 -0
- package/dist/types/progress/progress-contract.d.ts +154 -0
- package/dist/types/progress/progress-snapshot.d.ts +11 -0
- package/dist/types/progress/project-progress.d.ts +141 -0
- package/dist/types/progress/render-progress.d.ts +28 -0
- package/dist/types/runtime-mcp/elicitation.d.ts +12 -0
- package/dist/types/runtime-mcp/oauth-public-url.d.ts +1 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk/acp/adapter.d.ts +1 -1
- package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
- package/dist/types/sdk/broker/broker.d.ts +18 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +9 -0
- package/dist/types/sdk/broker/session-index.d.ts +12 -6
- package/dist/types/sdk/broker/task-artifact-owner-validation.d.ts +55 -0
- package/dist/types/sdk/bus/index.d.ts +12 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +3 -2
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/host/host.d.ts +6 -0
- package/dist/types/sdk/host/query/handlers.d.ts +3 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +9 -0
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +38 -9
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +36 -1
- package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +19 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +11 -0
- package/dist/types/session/session-manager.d.ts +31 -5
- package/dist/types/session/session-retirement.d.ts +71 -0
- package/dist/types/session/session-storage.d.ts +62 -48
- package/dist/types/session/session-transcript-header.d.ts +2 -0
- package/dist/types/session/task-artifact-owner-codec.d.ts +109 -0
- package/dist/types/session/task-artifact-owner-retirement.d.ts +7 -0
- package/dist/types/session/task-artifact-owner.d.ts +10 -0
- package/dist/types/setup/credential-auto-import.d.ts +1 -1
- package/dist/types/setup/credential-import.d.ts +6 -4
- package/dist/types/skill-state/active-state.d.ts +38 -0
- package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/python.d.ts +9 -1
- package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
- package/dist/types/tools/restricted-cwd.d.ts +1 -0
- package/dist/types/tools/todo-write.d.ts +6 -0
- package/dist/types/utils/tools-checksum.d.ts +7 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +21 -0
- package/scripts/resident-memory-bench.ts +174 -20
- package/scripts/verify-managed-empty-stop.ts +28 -0
- package/src/async/job-manager.ts +228 -122
- package/src/capability/index.ts +18 -0
- package/src/capability/mcp.ts +2 -0
- package/src/capability/skill.ts +2 -0
- package/src/capability/types.ts +6 -0
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/config-cli.ts +8 -18
- package/src/cli/customize-doctor.ts +15 -2
- package/src/cli/public-command-errors.ts +21 -1
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/skills-cli.ts +5 -0
- package/src/cli-main.ts +7 -8
- package/src/commands/deep-interview.ts +47 -8
- package/src/commands/sdk.ts +140 -29
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +307 -68
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-bindings-applier.ts +20 -0
- package/src/config/model-profile-activation.ts +240 -46
- package/src/config/model-registry.ts +47 -153
- package/src/config/settings.ts +44 -4
- package/src/config/skill-settings-defaults.ts +7 -0
- package/src/coordinator-mcp/policy.ts +3 -2
- package/src/cursor-delete-path.ts +10 -0
- package/src/cursor-mcp-name.ts +15 -0
- package/src/cursor.ts +13 -2
- package/src/customization/inventory.ts +3 -0
- package/src/debug/report-bundle-parent.ts +22 -0
- package/src/debug/report-bundle.ts +3 -0
- package/src/debug/system-info.ts +13 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
- package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
- package/src/defaults/gjc-defaults.test.ts +18 -1
- package/src/defaults/gjc-defaults.ts +3 -0
- package/src/defaults/gjc-skills.generated.ts +58 -36
- package/src/discovery/agents.ts +6 -1
- package/src/discovery/builtin.ts +10 -0
- package/src/discovery/helpers.ts +168 -23
- package/src/discovery/mcp-json.ts +6 -0
- package/src/discovery/ssh.ts +9 -4
- package/src/discovery/substitute-plugin-root.ts +3 -1
- package/src/edit/file-read-cache.ts +1 -1
- package/src/eval/py/executor.ts +380 -120
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/index.ts +5 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -8
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/plugins/marketplace/cache.ts +15 -0
- package/src/extensibility/plugins/marketplace/manager.ts +11 -3
- package/src/extensibility/plugins/plugin-link-path.ts +12 -0
- package/src/extensibility/runtime-skill-discovery.ts +20 -2
- package/src/extensibility/skill-management.ts +6 -2
- package/src/extensibility/skills.ts +6 -1
- package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
- package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
- package/src/gjc-runtime/deep-interview-stage.ts +98 -21
- package/src/gjc-runtime/gc-runtime.ts +206 -8
- package/src/gjc-runtime/launch-worktree.ts +14 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- package/src/gjc-runtime/state-argv.ts +2 -0
- package/src/gjc-runtime/state-migrations.ts +91 -29
- package/src/gjc-runtime/state-runtime.ts +3716 -135
- package/src/gjc-runtime/state-writer.ts +83 -8
- package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
- package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
- package/src/gjc-runtime/workflow-manifest.ts +54 -21
- package/src/harness-control-plane/operate.ts +12 -4
- package/src/harness-control-plane/owner.ts +5 -1
- package/src/hashline/parser.ts +48 -9
- package/src/hashline/recovery.ts +13 -4
- package/src/hindsight/config.ts +9 -3
- package/src/internal-urls/docs-index.generated.ts +19 -18
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- package/src/modes/acp/acp-agent.ts +163 -7
- package/src/modes/components/custom-editor.ts +14 -0
- package/src/modes/components/customization/customization-dashboard.ts +12 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -2
- package/src/modes/components/extensions/state-manager.ts +15 -5
- package/src/modes/components/model-selector.ts +1 -4
- package/src/modes/components/provider-onboarding-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +4 -0
- package/src/modes/controllers/event-controller.ts +11 -4
- package/src/modes/controllers/input-controller.ts +74 -29
- package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
- package/src/modes/controllers/selector-controller.ts +28 -15
- package/src/modes/interactive-mode.ts +4 -0
- package/src/modes/runtime-init.ts +4 -0
- package/src/modes/utils/abort-message.ts +3 -1
- package/src/modes/utils/interrupt-ownership.ts +12 -0
- package/src/modes/utils/keybinding-matchers.ts +4 -0
- package/src/modes/utils/macos-option-forwarding.ts +80 -0
- package/src/progress/collect-project-progress.ts +153 -0
- package/src/progress/progress-contract.ts +172 -0
- package/src/progress/progress-snapshot.ts +140 -0
- package/src/progress/project-progress.ts +446 -0
- package/src/progress/render-progress.ts +209 -0
- package/src/prompts/system/master-mode.md +1 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/runtime-mcp/client.ts +13 -4
- package/src/runtime-mcp/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/elicitation.ts +142 -0
- package/src/runtime-mcp/oauth-flow.ts +5 -0
- package/src/runtime-mcp/oauth-public-url.ts +8 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +2 -2
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +93 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +83 -7
- package/src/sdk/broker/ensure.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +820 -73
- package/src/sdk/broker/session-index.ts +191 -81
- package/src/sdk/broker/task-artifact-owner-validation.ts +259 -0
- package/src/sdk/bus/chat-daemon-cli.ts +38 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +3 -0
- package/src/sdk/bus/index.ts +241 -46
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
- package/src/sdk/bus/telegram-daemon.ts +4 -2
- package/src/sdk/cli/session-cli.ts +17 -3
- package/src/sdk/client/client.ts +28 -8
- package/src/sdk/host/host.ts +6 -0
- package/src/sdk/host/query/handlers.ts +9 -1
- package/src/sdk/host/session-runtime.test.ts +2805 -411
- package/src/sdk/host/session-runtime.ts +428 -111
- package/src/sdk/host/surface-policy.ts +1 -0
- package/src/sdk/index.ts +8 -0
- package/src/sdk/lifecycle-session.ts +3 -1
- package/src/sdk/mcp/server.ts +15 -3
- package/src/sdk/prompt-deadline-manager.ts +15 -0
- package/src/sdk/protocol/operation-inventory.generated.json +146 -0
- package/src/sdk/protocol/operation-registry.ts +9 -2
- package/src/sdk/router/session-router.ts +94 -14
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +81 -38
- package/src/session/agent-session.ts +674 -224
- package/src/session/artifacts.ts +174 -70
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3457 -462
- package/src/session/internal/managed-session-storage.ts +482 -61
- package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
- package/src/session/internal/task-artifact-owner-access.ts +245 -0
- package/src/session/internal/task-artifact-owner-transcript.ts +614 -0
- package/src/session/session-manager.ts +939 -133
- package/src/session/session-retirement.ts +714 -0
- package/src/session/session-storage.ts +418 -174
- package/src/session/session-transcript-header.ts +17 -0
- package/src/session/task-artifact-owner-codec.ts +641 -0
- package/src/session/task-artifact-owner-retirement.ts +477 -0
- package/src/session/task-artifact-owner.ts +289 -0
- package/src/setup/credential-auto-import.ts +12 -3
- package/src/setup/credential-import.ts +176 -5
- package/src/setup/paseo/announce.ts +6 -1
- package/src/skill-state/active-state.ts +132 -2
- package/src/slash-commands/builtin-registry.ts +39 -9
- package/src/slash-commands/helpers/progress-report.ts +23 -0
- package/src/stt/setup.ts +3 -5
- package/src/task/commands.ts +3 -1
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +6 -0
- package/src/tools/ask.ts +205 -6
- package/src/tools/ast-edit.ts +4 -0
- package/src/tools/bash-allowed-prefixes.ts +15 -0
- package/src/tools/bash-skill-urls.ts +56 -3
- package/src/tools/bash.ts +48 -79
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/python.ts +187 -66
- package/src/tools/recipe/index.ts +9 -0
- package/src/tools/recipe/recipe-guard.ts +19 -0
- package/src/tools/restricted-cwd.ts +10 -0
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +28 -1
- package/src/tools/todo-write.ts +10 -1
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/write.ts +1 -0
- package/src/utils/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/search/providers/codex.ts +3 -2
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "./deep-interview-state";
|
|
15
15
|
import { sessionStateDir } from "./session-layout";
|
|
16
16
|
import { resolveGjcSessionForWrite, SessionResolutionError, writeSessionActivityMarker } from "./session-resolution";
|
|
17
|
+
import { migrateWorkflowState } from "./state-migrations";
|
|
17
18
|
import { runNativeStateCommand } from "./state-runtime";
|
|
18
19
|
import {
|
|
19
20
|
persistedStateRevision,
|
|
@@ -184,7 +185,12 @@ const RUNTIME_OWNED_ENVELOPE_KEYS = [
|
|
|
184
185
|
* `invalid intent contract`, bricking the interview until a destructive
|
|
185
186
|
* `clear --force`. The recorder is the only writer that can lock intent.
|
|
186
187
|
*/
|
|
187
|
-
const
|
|
188
|
+
const RUNTIME_OWNED_STATE_KEYS = [
|
|
189
|
+
"intent_contract",
|
|
190
|
+
"intent_review",
|
|
191
|
+
"execution_approval",
|
|
192
|
+
"execution_approval_receipt",
|
|
193
|
+
] as const;
|
|
188
194
|
|
|
189
195
|
/**
|
|
190
196
|
* Remove sanitizer-owned keys from a staged payload and classify the
|
|
@@ -213,7 +219,7 @@ function sanitizeStagedPayload(payload: Record<string, unknown>): {
|
|
|
213
219
|
}
|
|
214
220
|
if (isPlainObject(next.state)) {
|
|
215
221
|
const state = { ...(next.state as Record<string, unknown>) };
|
|
216
|
-
for (const key of
|
|
222
|
+
for (const key of RUNTIME_OWNED_STATE_KEYS) {
|
|
217
223
|
if (key in state) {
|
|
218
224
|
delete state[key];
|
|
219
225
|
ignoredKeys.push(`state.${key}`);
|
|
@@ -231,11 +237,11 @@ function sanitizeStagedPayload(payload: Record<string, unknown>): {
|
|
|
231
237
|
}
|
|
232
238
|
|
|
233
239
|
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
240
|
+
* Validate a persisted `state.intent_contract`: if it fails canonical validation
|
|
241
|
+
* and `intent_contract_required === true`, the contract is locked and must be valid.
|
|
242
|
+
* Fail closed with an integrity error to preserve the invalid contract for recovery/audit.
|
|
243
|
+
* When intent_contract_required is absent or false, healing may occur for backward
|
|
244
|
+
* compatibility with pre-guard poisoned writes, but only after explicit authorization.
|
|
239
245
|
*/
|
|
240
246
|
function healPoisonedIntentContract(base: Record<string, unknown>): {
|
|
241
247
|
base: Record<string, unknown>;
|
|
@@ -247,7 +253,16 @@ function healPoisonedIntentContract(base: Record<string, unknown>): {
|
|
|
247
253
|
try {
|
|
248
254
|
assertDeepInterviewIntentManifest(state.intent_contract);
|
|
249
255
|
return { base, healed: false };
|
|
250
|
-
} catch {
|
|
256
|
+
} catch (error) {
|
|
257
|
+
// If intent_contract_required is set (locked contract), fail closed
|
|
258
|
+
if (state.intent_contract_required === true) {
|
|
259
|
+
throw new DeepInterviewStageError(
|
|
260
|
+
"DI_STAGE_STATE_CORRUPT",
|
|
261
|
+
`locked intent contract is malformed or tampered: ${error instanceof Error ? error.message : String(error)}`,
|
|
262
|
+
"preserve the state for recovery; contact support with the session state for audit",
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
// For unlocked contracts, continue with healing for backward compatibility
|
|
251
266
|
const healedState = { ...state };
|
|
252
267
|
delete healedState.intent_contract;
|
|
253
268
|
delete healedState.intent_review;
|
|
@@ -371,7 +386,7 @@ async function readCurrentState(cwd: string, sessionId: string): Promise<Current
|
|
|
371
386
|
if (read.kind === "absent")
|
|
372
387
|
return { value: {}, revision: 0, sha256: workflowEnvelopeContentSha256({}), exists: false };
|
|
373
388
|
return {
|
|
374
|
-
value: read.value,
|
|
389
|
+
value: migrateWorkflowState(read.value, "deep-interview").state,
|
|
375
390
|
revision: persistedStateRevision(read.value),
|
|
376
391
|
sha256: workflowEnvelopeContentSha256(read.value),
|
|
377
392
|
exists: true,
|
|
@@ -430,6 +445,11 @@ function computeMergedEnvelope(
|
|
|
430
445
|
draft: DeepInterviewStageDraft,
|
|
431
446
|
nowIso: string,
|
|
432
447
|
): Record<string, unknown> {
|
|
448
|
+
if (current.active === false)
|
|
449
|
+
throw new DeepInterviewStageError(
|
|
450
|
+
"DI_STAGE_MERGE_REJECTED",
|
|
451
|
+
"cannot stage after deep-interview handoff or completion",
|
|
452
|
+
);
|
|
433
453
|
// A poisoned (unverifiable) intent contract in the persisted base would make
|
|
434
454
|
// every merge throw forever; heal it instead of bricking the interview.
|
|
435
455
|
const { base: healedCurrent, healed } = healPoisonedIntentContract(current);
|
|
@@ -454,6 +474,39 @@ function computeMergedEnvelope(
|
|
|
454
474
|
// one-fact patch cannot erase confirmed/disputed history (#3387 finding 2).
|
|
455
475
|
const mergedState = isPlainObject(merged.state) ? (merged.state as Record<string, unknown>) : undefined;
|
|
456
476
|
const priorState = isPlainObject(current.state) ? (current.state as Record<string, unknown>) : undefined;
|
|
477
|
+
if (priorState?.crystal === undefined && mergedState?.crystal !== undefined)
|
|
478
|
+
throw new DeepInterviewStageError(
|
|
479
|
+
"DI_STAGE_MERGE_REJECTED",
|
|
480
|
+
"staged apply cannot introduce canonical Crystal state",
|
|
481
|
+
);
|
|
482
|
+
if (priorState?.crystal !== undefined) {
|
|
483
|
+
if (!mergedState || JSON.stringify(mergedState.crystal) !== JSON.stringify(priorState.crystal))
|
|
484
|
+
throw new DeepInterviewStageError(
|
|
485
|
+
"DI_STAGE_MERGE_REJECTED",
|
|
486
|
+
"canonical crystallized state cannot be replaced or deleted through staged apply",
|
|
487
|
+
);
|
|
488
|
+
if (mergedState.execution_approval !== priorState.execution_approval)
|
|
489
|
+
throw new DeepInterviewStageError(
|
|
490
|
+
"DI_STAGE_MERGE_REJECTED",
|
|
491
|
+
"crystallized execution approval is immutable through staged apply",
|
|
492
|
+
);
|
|
493
|
+
if (
|
|
494
|
+
JSON.stringify(mergedState.execution_approval_receipt) !==
|
|
495
|
+
JSON.stringify(priorState.execution_approval_receipt)
|
|
496
|
+
)
|
|
497
|
+
throw new DeepInterviewStageError(
|
|
498
|
+
"DI_STAGE_MERGE_REJECTED",
|
|
499
|
+
"crystallized execution approval provenance is immutable through staged apply",
|
|
500
|
+
);
|
|
501
|
+
for (const field of ["spec_path", "spec_sha256", "spec_slug", "spec_stage"] as const)
|
|
502
|
+
if (merged[field] !== current[field])
|
|
503
|
+
throw new DeepInterviewStageError(
|
|
504
|
+
"DI_STAGE_MERGE_REJECTED",
|
|
505
|
+
`crystallized ${field} is immutable through staged apply`,
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
if (mergedState?.crystal && isPlainObject(mergedState.crystal) && mergedState.crystal.lifecycle !== "ready")
|
|
509
|
+
merged.current_phase = "interviewing";
|
|
457
510
|
if (mergedState && priorState && Array.isArray(priorState.established_facts)) {
|
|
458
511
|
mergedState.established_facts = mergeEstablishedFacts(
|
|
459
512
|
priorState.established_facts,
|
|
@@ -461,6 +514,24 @@ function computeMergedEnvelope(
|
|
|
461
514
|
);
|
|
462
515
|
}
|
|
463
516
|
merged = deriveRuntimeAmbiguity(merged, current);
|
|
517
|
+
if (isPlainObject(priorState?.crystal) && priorState.crystal.lifecycle === "ready") {
|
|
518
|
+
for (const field of [
|
|
519
|
+
"rounds",
|
|
520
|
+
"established_facts",
|
|
521
|
+
"intent_review",
|
|
522
|
+
"current_ambiguity",
|
|
523
|
+
"topology",
|
|
524
|
+
"auto_answered_rounds",
|
|
525
|
+
] as const)
|
|
526
|
+
if (
|
|
527
|
+
JSON.stringify((merged.state as Record<string, unknown> | undefined)?.[field]) !==
|
|
528
|
+
JSON.stringify(priorState[field])
|
|
529
|
+
)
|
|
530
|
+
throw new DeepInterviewStageError(
|
|
531
|
+
"DI_STAGE_MERGE_REJECTED",
|
|
532
|
+
"ready Crystal evidence is immutable through staged apply",
|
|
533
|
+
);
|
|
534
|
+
}
|
|
464
535
|
try {
|
|
465
536
|
assertDeepInterviewEnvelopeInputLimits(merged);
|
|
466
537
|
} catch (error) {
|
|
@@ -851,6 +922,14 @@ async function handleWrite(args: readonly string[], cwd: string): Promise<Record
|
|
|
851
922
|
);
|
|
852
923
|
}
|
|
853
924
|
const current = await readCurrentState(cwd, sessionId);
|
|
925
|
+
const currentInner = isPlainObject(current.value.state) ? current.value.state : {};
|
|
926
|
+
if (reset && currentInner.crystal !== undefined) {
|
|
927
|
+
throw new DeepInterviewStageError(
|
|
928
|
+
"DI_STAGE_MERGE_REJECTED",
|
|
929
|
+
"reset cannot rewrite canonical Crystal state",
|
|
930
|
+
"clear the workflow explicitly or continue through the approval and handoff lifecycle",
|
|
931
|
+
);
|
|
932
|
+
}
|
|
854
933
|
const nowIso = new Date().toISOString();
|
|
855
934
|
const syntheticDraft: DeepInterviewStageDraft = {
|
|
856
935
|
version: DRAFT_VERSION,
|
|
@@ -862,17 +941,15 @@ async function handleWrite(args: readonly string[], cwd: string): Promise<Record
|
|
|
862
941
|
payload,
|
|
863
942
|
created_at: nowIso,
|
|
864
943
|
};
|
|
865
|
-
//
|
|
866
|
-
//
|
|
867
|
-
const base = reset
|
|
868
|
-
?
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
:
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
: {};
|
|
875
|
-
})()
|
|
944
|
+
// Canonical Crystal resets are rejected above. Ordinary resets retain only
|
|
945
|
+
// locked intent, never legacy handoff/spec metadata or execution authority.
|
|
946
|
+
const base: Record<string, unknown> = reset
|
|
947
|
+
? {
|
|
948
|
+
state: {
|
|
949
|
+
intent_contract: currentInner.intent_contract,
|
|
950
|
+
intent_contract_required: currentInner.intent_contract_required,
|
|
951
|
+
},
|
|
952
|
+
}
|
|
876
953
|
: current.value;
|
|
877
954
|
const merged = computeMergedEnvelope(base as Record<string, unknown>, syntheticDraft, nowIso);
|
|
878
955
|
merged.last_applied_draft_id = syntheticDraft.draft_id;
|
|
@@ -930,7 +1007,7 @@ async function syncStageHud(cwd: string, sessionId: string, envelope: Record<str
|
|
|
930
1007
|
await syncSkillActiveState({
|
|
931
1008
|
cwd,
|
|
932
1009
|
skill: "deep-interview",
|
|
933
|
-
active: phase !== "complete",
|
|
1010
|
+
active: envelope.active !== false && phase !== "complete",
|
|
934
1011
|
phase,
|
|
935
1012
|
sessionId,
|
|
936
1013
|
source: "gjc-deep-interview-native",
|
|
@@ -30,7 +30,22 @@ import {
|
|
|
30
30
|
listCanonicalBlobs,
|
|
31
31
|
removeCanonicalBlob,
|
|
32
32
|
} from "../session/blob-store";
|
|
33
|
-
import {
|
|
33
|
+
import type { ManagedGcSessionRetirementReceipt } from "../session/internal/managed-gc-retirement-codec";
|
|
34
|
+
import {
|
|
35
|
+
discoverManagedGcSessionRetirementReceipts,
|
|
36
|
+
type ManagedScope,
|
|
37
|
+
resolveManagedGcScopeForRead,
|
|
38
|
+
} from "../session/internal/managed-session-scope";
|
|
39
|
+
import { taskArtifactOwnerLocatorFromTranscriptBytes } from "../session/internal/task-artifact-owner-transcript";
|
|
40
|
+
import {
|
|
41
|
+
probeSessionRetirement,
|
|
42
|
+
retireOrphanSessionTranscript,
|
|
43
|
+
retireSessionTranscript,
|
|
44
|
+
type SessionRetirementContinuation,
|
|
45
|
+
type SessionRetirementOutcome,
|
|
46
|
+
} from "../session/session-retirement";
|
|
47
|
+
import { FileSessionStorage, type SessionStorageSnapshot } from "../session/session-storage";
|
|
48
|
+
import { parseFirstJsonlLine } from "../session/session-transcript-header";
|
|
34
49
|
import {
|
|
35
50
|
collectEmptyDeleteReceipts,
|
|
36
51
|
type EmptyDeleteGcRecord,
|
|
@@ -756,6 +771,11 @@ export interface GcDiskRecord {
|
|
|
756
771
|
action: GcDiskAction;
|
|
757
772
|
reason: string;
|
|
758
773
|
error?: string;
|
|
774
|
+
/** Exact incomplete retirement phase surfaced by the owning session adapter. */
|
|
775
|
+
phase?: "artifacts" | "transcript";
|
|
776
|
+
task_artifact_owner_retired?: true;
|
|
777
|
+
task_artifact_owner_payload_retired?: true;
|
|
778
|
+
task_artifact_owner_namespace_retained?: true;
|
|
759
779
|
/** Set when `bytes` is a floor because a walk was capped or partially unreadable. */
|
|
760
780
|
partial?: true;
|
|
761
781
|
/** Set when this entry was a reclaim candidate that its surface withheld on incomplete evidence. */
|
|
@@ -1201,6 +1221,59 @@ async function discoverGcDiskTranscripts(sessionsRoot: string, errors: GcDiskErr
|
|
|
1201
1221
|
* Classify (and optionally retire) session transcripts. Returns the transcripts
|
|
1202
1222
|
* that survived, which is exactly the mark set for the blob sweep.
|
|
1203
1223
|
*/
|
|
1224
|
+
function sessionRetirementContinuation(
|
|
1225
|
+
storage: FileSessionStorage,
|
|
1226
|
+
sessionsRoot: string,
|
|
1227
|
+
transcriptPath: string,
|
|
1228
|
+
agentDir: string,
|
|
1229
|
+
): SessionRetirementContinuation | undefined {
|
|
1230
|
+
let snapshot: SessionStorageSnapshot;
|
|
1231
|
+
try {
|
|
1232
|
+
snapshot = storage.readSnapshotSync(transcriptPath);
|
|
1233
|
+
} catch {
|
|
1234
|
+
return undefined;
|
|
1235
|
+
}
|
|
1236
|
+
const header = parseFirstJsonlLine(snapshot.bytes);
|
|
1237
|
+
if (header?.type !== "session" || typeof header.id !== "string" || typeof header.cwd !== "string") return undefined;
|
|
1238
|
+
try {
|
|
1239
|
+
if (!taskArtifactOwnerLocatorFromTranscriptBytes(snapshot.bytes, header.id)) return undefined;
|
|
1240
|
+
} catch {
|
|
1241
|
+
return undefined;
|
|
1242
|
+
}
|
|
1243
|
+
const resolved = resolveManagedGcScopeForRead({ cwd: header.cwd, agentDir, sessionsRoot });
|
|
1244
|
+
if (
|
|
1245
|
+
resolved.kind !== "resolved" ||
|
|
1246
|
+
path.resolve(resolved.scope.sessionsRoot) !== path.resolve(sessionsRoot) ||
|
|
1247
|
+
path.resolve(resolved.scope.directoryPath) !== path.resolve(path.dirname(transcriptPath))
|
|
1248
|
+
)
|
|
1249
|
+
return undefined;
|
|
1250
|
+
return { managedScope: resolved.scope };
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function orphanRetirementRecord(
|
|
1254
|
+
receipt: ManagedGcSessionRetirementReceipt,
|
|
1255
|
+
now: number,
|
|
1256
|
+
reason: string,
|
|
1257
|
+
withheld = false,
|
|
1258
|
+
): GcDiskRecord {
|
|
1259
|
+
const mtimeMs = Number(receipt.transcriptIdentity.mtimeNs / 1_000_000n);
|
|
1260
|
+
const outcome = receipt.taskArtifactOwnerRetirementOutcome;
|
|
1261
|
+
return {
|
|
1262
|
+
surface: "sessions",
|
|
1263
|
+
id: receipt.sessionId,
|
|
1264
|
+
path: receipt.transcriptPath,
|
|
1265
|
+
bytes: 0,
|
|
1266
|
+
age_days: gcDiskAgeDays(now, mtimeMs),
|
|
1267
|
+
action: "keep",
|
|
1268
|
+
reason,
|
|
1269
|
+
...(withheld ? { withheld: true as const } : {}),
|
|
1270
|
+
...(receipt.state === "owner_retired" ? { task_artifact_owner_retired: true as const } : {}),
|
|
1271
|
+
...(outcome?.kind === "payload_retired"
|
|
1272
|
+
? { task_artifact_owner_payload_retired: true as const, task_artifact_owner_namespace_retained: true as const }
|
|
1273
|
+
: {}),
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1204
1277
|
async function runGcDiskSessions(input: {
|
|
1205
1278
|
surface: GcDiskSurfaceReport;
|
|
1206
1279
|
transcripts: GcDiskTranscript[];
|
|
@@ -1208,8 +1281,35 @@ async function runGcDiskSessions(input: {
|
|
|
1208
1281
|
policy: GcDiskPolicy;
|
|
1209
1282
|
now: number;
|
|
1210
1283
|
prune: boolean;
|
|
1284
|
+
agentDir: string;
|
|
1285
|
+
errors: GcDiskError[];
|
|
1211
1286
|
}): Promise<GcDiskTranscript[]> {
|
|
1212
|
-
const { surface, transcripts, references, policy, now, prune } = input;
|
|
1287
|
+
const { surface, transcripts, references, policy, now, prune, agentDir, errors } = input;
|
|
1288
|
+
const storage = new FileSessionStorage();
|
|
1289
|
+
let orphanReceipts: readonly {
|
|
1290
|
+
readonly scope: ManagedScope;
|
|
1291
|
+
readonly receipt: ManagedGcSessionRetirementReceipt;
|
|
1292
|
+
}[] = [];
|
|
1293
|
+
let receiptDiscoveryError: string | undefined;
|
|
1294
|
+
try {
|
|
1295
|
+
const entries = await fsp.readdir(surface.root, { withFileTypes: true });
|
|
1296
|
+
if (entries.some(entry => entry.name.startsWith("v2-"))) {
|
|
1297
|
+
orphanReceipts = (
|
|
1298
|
+
await discoverManagedGcSessionRetirementReceipts({
|
|
1299
|
+
agentDir,
|
|
1300
|
+
sessionsRoot: surface.root,
|
|
1301
|
+
})
|
|
1302
|
+
).filter(
|
|
1303
|
+
item =>
|
|
1304
|
+
!storage.existsSync(item.receipt.transcriptPath) &&
|
|
1305
|
+
!transcripts.some(
|
|
1306
|
+
transcript => path.resolve(transcript.path) === path.resolve(item.receipt.transcriptPath),
|
|
1307
|
+
),
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
if (!isEnoent(error)) receiptDiscoveryError = gcDiskErrorText(error);
|
|
1312
|
+
}
|
|
1213
1313
|
const maxAgeMs = policy.sessions_max_age_days * GC_DISK_DAY_MS;
|
|
1214
1314
|
|
|
1215
1315
|
// The newest transcript in each project directory is the `--continue` resume
|
|
@@ -1278,18 +1378,54 @@ async function runGcDiskSessions(input: {
|
|
|
1278
1378
|
}
|
|
1279
1379
|
}
|
|
1280
1380
|
|
|
1281
|
-
const storage = new FileSessionStorage();
|
|
1282
1381
|
if (!prune) {
|
|
1283
1382
|
// Dry run must project the prune verdict, not just the policy verdict: the
|
|
1284
1383
|
// retention pass re-checks its own preconditions before it may call the
|
|
1285
1384
|
// delete authority, and a candidate that fails them is never removable.
|
|
1286
1385
|
for (const item of classified) {
|
|
1287
1386
|
if (item.record.action !== "would_reclaim") continue;
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1387
|
+
try {
|
|
1388
|
+
const continuation = sessionRetirementContinuation(storage, surface.root, item.transcript.path, agentDir);
|
|
1389
|
+
const probe = await probeSessionRetirement(storage, surface.root, item.transcript.path, continuation);
|
|
1390
|
+
if (probe.kind === "retirable") {
|
|
1391
|
+
if (probe.taskArtifactOwnerRetired) item.record.task_artifact_owner_retired = true;
|
|
1392
|
+
if (probe.taskArtifactOwnerPayloadRetired) item.record.task_artifact_owner_payload_retired = true;
|
|
1393
|
+
if (probe.taskArtifactOwnerNamespaceRetained) item.record.task_artifact_owner_namespace_retained = true;
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
if (probe.kind === "cleanup_pending") {
|
|
1397
|
+
item.record.action = "reclaim_failed";
|
|
1398
|
+
item.record.reason = `retention_incomplete: ${probe.reason}`;
|
|
1399
|
+
item.record.error = probe.reason;
|
|
1400
|
+
if (probe.phase) item.record.phase = probe.phase;
|
|
1401
|
+
if (probe.taskArtifactOwnerRetired) item.record.task_artifact_owner_retired = true;
|
|
1402
|
+
if (probe.taskArtifactOwnerPayloadRetired) item.record.task_artifact_owner_payload_retired = true;
|
|
1403
|
+
if (probe.taskArtifactOwnerNamespaceRetained) item.record.task_artifact_owner_namespace_retained = true;
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
item.record.action = "keep";
|
|
1407
|
+
item.record.reason = `retention_declined: ${probe.reason}`;
|
|
1408
|
+
if (probe.reason.includes("task_artifact_owner_")) item.record.error = probe.reason;
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
const message = gcDiskErrorText(error);
|
|
1411
|
+
item.record.action = "keep";
|
|
1412
|
+
item.record.reason = `retention_declined: ${message}`;
|
|
1413
|
+
item.record.error = message;
|
|
1414
|
+
item.record.withheld = true;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
for (const { receipt } of orphanReceipts) {
|
|
1418
|
+
let reason = "transcript_missing_without_verified_retirement";
|
|
1419
|
+
if (!references.complete) reason = `reference_scan_incomplete: ${references.notes.join(", ")}`;
|
|
1420
|
+
else if (references.ids.has(receipt.sessionId)) reason = "referenced_by_live_surface";
|
|
1421
|
+
surface.records.push(orphanRetirementRecord(receipt, now, reason, true));
|
|
1292
1422
|
}
|
|
1423
|
+
if (receiptDiscoveryError)
|
|
1424
|
+
errors.push({
|
|
1425
|
+
surface: "sessions",
|
|
1426
|
+
scope: surface.root,
|
|
1427
|
+
message: `managed_gc_retirement_discovery_incomplete: ${receiptDiscoveryError}`,
|
|
1428
|
+
});
|
|
1293
1429
|
return classified.filter(item => item.record.action !== "would_reclaim").map(item => item.transcript);
|
|
1294
1430
|
}
|
|
1295
1431
|
|
|
@@ -1303,9 +1439,21 @@ async function runGcDiskSessions(input: {
|
|
|
1303
1439
|
survivors.push(item.transcript);
|
|
1304
1440
|
continue;
|
|
1305
1441
|
}
|
|
1306
|
-
|
|
1442
|
+
let outcome: SessionRetirementOutcome;
|
|
1443
|
+
try {
|
|
1444
|
+
const continuation = sessionRetirementContinuation(storage, surface.root, item.transcript.path, agentDir);
|
|
1445
|
+
outcome = await retireSessionTranscript(storage, surface.root, item.transcript.path, continuation);
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
const message = gcDiskErrorText(error);
|
|
1448
|
+
item.record.action = "reclaim_failed";
|
|
1449
|
+
item.record.reason = `retention_failed: ${message}`;
|
|
1450
|
+
item.record.error = message;
|
|
1451
|
+
survivors.push(item.transcript);
|
|
1452
|
+
continue;
|
|
1453
|
+
}
|
|
1307
1454
|
if (outcome.kind === "retired") {
|
|
1308
1455
|
item.record.action = "reclaimed";
|
|
1456
|
+
if (outcome.taskArtifactOwnerRetired) item.record.task_artifact_owner_retired = true;
|
|
1309
1457
|
continue;
|
|
1310
1458
|
}
|
|
1311
1459
|
if (outcome.kind === "cleanup_pending") {
|
|
@@ -1315,13 +1463,61 @@ async function runGcDiskSessions(input: {
|
|
|
1315
1463
|
item.record.action = "reclaim_failed";
|
|
1316
1464
|
item.record.reason = `retention_incomplete: ${outcome.reason}`;
|
|
1317
1465
|
item.record.error = outcome.reason;
|
|
1466
|
+
if (outcome.phase) item.record.phase = outcome.phase;
|
|
1467
|
+
if (outcome.taskArtifactOwnerRetired) item.record.task_artifact_owner_retired = true;
|
|
1468
|
+
if (outcome.taskArtifactOwnerPayloadRetired) item.record.task_artifact_owner_payload_retired = true;
|
|
1469
|
+
if (outcome.taskArtifactOwnerNamespaceRetained) item.record.task_artifact_owner_namespace_retained = true;
|
|
1318
1470
|
survivors.push(item.transcript);
|
|
1319
1471
|
continue;
|
|
1320
1472
|
}
|
|
1321
1473
|
item.record.action = "keep";
|
|
1322
1474
|
item.record.reason = `retention_declined: ${outcome.reason}`;
|
|
1475
|
+
if (outcome.reason.includes("task_artifact_owner_")) item.record.error = outcome.reason;
|
|
1323
1476
|
survivors.push(item.transcript);
|
|
1324
1477
|
}
|
|
1478
|
+
for (const { scope, receipt } of orphanReceipts) {
|
|
1479
|
+
const record = orphanRetirementRecord(
|
|
1480
|
+
receipt,
|
|
1481
|
+
now,
|
|
1482
|
+
!references.complete
|
|
1483
|
+
? `reference_scan_incomplete: ${references.notes.join(", ")}`
|
|
1484
|
+
: references.ids.has(receipt.sessionId)
|
|
1485
|
+
? "referenced_by_live_surface"
|
|
1486
|
+
: "transcript_missing_without_verified_retirement",
|
|
1487
|
+
true,
|
|
1488
|
+
);
|
|
1489
|
+
if (!references.complete || references.ids.has(receipt.sessionId)) {
|
|
1490
|
+
surface.records.push(record);
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
try {
|
|
1494
|
+
const outcome = await retireOrphanSessionTranscript(storage, surface.root, { managedScope: scope }, receipt);
|
|
1495
|
+
if (outcome.kind === "cleanup_pending") {
|
|
1496
|
+
record.action = "reclaim_failed";
|
|
1497
|
+
record.reason = `retention_incomplete: ${outcome.reason}`;
|
|
1498
|
+
record.error = outcome.reason;
|
|
1499
|
+
if (outcome.phase) record.phase = outcome.phase;
|
|
1500
|
+
if (outcome.taskArtifactOwnerRetired) record.task_artifact_owner_retired = true;
|
|
1501
|
+
if (outcome.taskArtifactOwnerPayloadRetired) record.task_artifact_owner_payload_retired = true;
|
|
1502
|
+
if (outcome.taskArtifactOwnerNamespaceRetained) record.task_artifact_owner_namespace_retained = true;
|
|
1503
|
+
} else {
|
|
1504
|
+
record.reason = outcome.kind === "kept" ? outcome.reason : "transcript_missing_without_verified_retirement";
|
|
1505
|
+
if (outcome.kind === "kept") record.error = outcome.reason;
|
|
1506
|
+
}
|
|
1507
|
+
} catch (error) {
|
|
1508
|
+
const message = gcDiskErrorText(error);
|
|
1509
|
+
record.action = "reclaim_failed";
|
|
1510
|
+
record.reason = `retention_failed: ${message}`;
|
|
1511
|
+
record.error = message;
|
|
1512
|
+
}
|
|
1513
|
+
surface.records.push(record);
|
|
1514
|
+
}
|
|
1515
|
+
if (receiptDiscoveryError)
|
|
1516
|
+
errors.push({
|
|
1517
|
+
surface: "sessions",
|
|
1518
|
+
scope: surface.root,
|
|
1519
|
+
message: `managed_gc_retirement_discovery_incomplete: ${receiptDiscoveryError}`,
|
|
1520
|
+
});
|
|
1325
1521
|
return survivors;
|
|
1326
1522
|
}
|
|
1327
1523
|
|
|
@@ -2203,6 +2399,8 @@ export async function collectGcDiskReport(input: {
|
|
|
2203
2399
|
policy,
|
|
2204
2400
|
now,
|
|
2205
2401
|
prune,
|
|
2402
|
+
agentDir,
|
|
2403
|
+
errors,
|
|
2206
2404
|
});
|
|
2207
2405
|
await runGcDiskBlobs({
|
|
2208
2406
|
surface: surfaces.blobs,
|
|
@@ -205,7 +205,8 @@ export function resolveWorktreeBucketForPath(
|
|
|
205
205
|
const template = expandHomePrefix(configured || DEFAULT_WORKTREE_BUCKET, home, pathApi);
|
|
206
206
|
return pathApi.resolve(
|
|
207
207
|
pathApi.dirname(repoRoot),
|
|
208
|
-
|
|
208
|
+
// Function replacer so a `$` in the repository directory name is kept verbatim.
|
|
209
|
+
template.replaceAll(REPO_NAME_PLACEHOLDER, () => pathApi.basename(repoRoot)),
|
|
209
210
|
);
|
|
210
211
|
}
|
|
211
212
|
|
|
@@ -456,9 +457,18 @@ function isWorktreeDirty(worktreePath: string): boolean {
|
|
|
456
457
|
function resolveOptionalWorktreeName(args: string[], index: number): { name: string | null; nextIndex: number } {
|
|
457
458
|
const next = args[index + 1];
|
|
458
459
|
if (!next) return { name: null, nextIndex: index };
|
|
459
|
-
if (next === "--") return { name: null, nextIndex: index };
|
|
460
|
-
|
|
461
|
-
|
|
460
|
+
if (next === "--" || next.startsWith("-")) return { name: null, nextIndex: index };
|
|
461
|
+
// A bare --worktree is the detached selector. Only consume the following
|
|
462
|
+
// positional token as a branch name when it is a valid ref; otherwise it is
|
|
463
|
+
// the launch prompt and must remain in the forwarded arguments.
|
|
464
|
+
const trimmed = next.trim();
|
|
465
|
+
if (!trimmed || /\s/.test(trimmed)) return { name: null, nextIndex: index };
|
|
466
|
+
const valid =
|
|
467
|
+
Bun.spawnSync(["git", "check-ref-format", "--branch", trimmed], {
|
|
468
|
+
stdout: "ignore",
|
|
469
|
+
stderr: "ignore",
|
|
470
|
+
}).exitCode === 0;
|
|
471
|
+
return valid ? { name: trimmed, nextIndex: index + 1 } : { name: null, nextIndex: index };
|
|
462
472
|
}
|
|
463
473
|
|
|
464
474
|
export function parseLaunchWorktreeMode(args: string[]): ParsedLaunchWorktreeMode {
|