@gajae-code/coding-agent 0.11.9 → 0.11.11
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 +56 -1
- package/dist/types/cli.d.ts +8 -1
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/model-profile-contract.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +57 -2
- package/dist/types/config/settings.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +21 -0
- package/dist/types/gjc-runtime/linux-proc.d.ts +22 -14
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +60 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +3 -1
- package/dist/types/gjc-runtime/state-writer.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +14 -9
- package/dist/types/modes/components/queue-pane.d.ts +8 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
- package/dist/types/modes/components/welcome.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +16 -8
- package/dist/types/modes/interactive-mode.d.ts +5 -1
- package/dist/types/runtime/memory-domain.d.ts +9 -0
- package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
- package/dist/types/runtime/memory-guard.d.ts +39 -0
- package/dist/types/runtime/memory-limit.d.ts +11 -0
- package/dist/types/runtime-mcp/manager.d.ts +8 -0
- package/dist/types/sdk/acp/final-text.d.ts +19 -0
- package/dist/types/sdk/acp/mcp.d.ts +23 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +2 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +11 -2
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +14 -7
- package/dist/types/sdk/bus/telegram-daemon.d.ts +97 -8
- package/dist/types/sdk/host/control/operations.d.ts +1 -0
- package/dist/types/sdk/lifecycle-session.d.ts +10 -1
- package/dist/types/sdk/session.d.ts +7 -0
- package/dist/types/sdk/startup-capability.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +11 -11
- package/dist/types/session/fallback-chain-controller.d.ts +18 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +28 -1
- package/dist/types/session/session-manager.d.ts +34 -0
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/provider-retry-status.d.ts +13 -0
- package/dist/types/task/types.d.ts +39 -0
- package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/tools/resource-gc.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/scripts/run-test-manifest.ts +6 -4
- package/src/cli/args.ts +14 -8
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/notify-cli.ts +1 -0
- package/src/cli.ts +48 -2
- package/src/commands/sdk.ts +91 -7
- package/src/config/model-profile-activation.ts +1 -0
- package/src/config/model-profile-contract.ts +13 -0
- package/src/config/model-profiles.ts +31 -7
- package/src/config/model-registry.ts +4 -2
- package/src/config/settings-schema.ts +61 -2
- package/src/config/settings.ts +8 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
- package/src/eval/js/tool-bridge.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +14 -0
- package/src/gjc-runtime/deep-interview-ambiguity.ts +16 -2
- package/src/gjc-runtime/deep-interview-draft.ts +2 -1
- package/src/gjc-runtime/deep-interview-recorder.ts +16 -1
- package/src/gjc-runtime/deep-interview-state.ts +25 -3
- package/src/gjc-runtime/linux-proc.ts +70 -36
- package/src/gjc-runtime/ralplan-runtime.ts +252 -1
- package/src/gjc-runtime/state-runtime.ts +48 -11
- package/src/gjc-runtime/state-writer.ts +1 -1
- package/src/hooks/skill-state.ts +23 -1
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/local-protocol.ts +23 -4
- package/src/modes/acp/acp-agent.ts +554 -60
- package/src/modes/acp/acp-event-mapper.ts +93 -6
- package/src/modes/components/model-selector.ts +15 -1
- package/src/modes/components/notifications-settings-editor.ts +1 -1
- package/src/modes/components/queue-pane.ts +55 -9
- package/src/modes/components/queued-message-selector.ts +53 -10
- package/src/modes/components/tool-status-header.ts +1 -4
- package/src/modes/components/welcome.ts +18 -3
- package/src/modes/controllers/command-controller.ts +4 -0
- package/src/modes/controllers/extension-ui-controller.ts +4 -10
- package/src/modes/controllers/input-controller.ts +44 -3
- package/src/modes/controllers/selector-controller.ts +38 -8
- package/src/modes/interactive-mode.ts +65 -26
- package/src/modes/runtime-init.ts +1 -0
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/architect.md +8 -1
- package/src/prompts/agents/critic.md +8 -1
- package/src/prompts/agents/executor.md +1 -1
- package/src/prompts/agents/planner.md +8 -1
- package/src/runtime/memory-domain.ts +66 -0
- package/src/runtime/memory-guard-contract.ts +73 -0
- package/src/runtime/memory-guard.ts +292 -0
- package/src/runtime/memory-limit.ts +50 -0
- package/src/runtime-mcp/manager.ts +26 -6
- package/src/sdk/acp/adapter.ts +8 -1
- package/src/sdk/acp/final-text.ts +40 -0
- package/src/sdk/acp/mcp.ts +26 -0
- package/src/sdk/broker/identity.ts +22 -12
- package/src/sdk/broker/lifecycle.ts +82 -1
- package/src/sdk/bus/chat-daemon-cli.ts +2 -2
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/config.ts +1 -1
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/index.ts +222 -27
- package/src/sdk/bus/notification-service.ts +19 -5
- package/src/sdk/bus/operator-runtime.ts +31 -5
- package/src/sdk/bus/rate-limit-pool.ts +8 -0
- package/src/sdk/bus/telegram-cli.ts +6 -13
- package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +15 -7
- package/src/sdk/bus/telegram-daemon-control.ts +14 -5
- package/src/sdk/bus/telegram-daemon.ts +1191 -164
- package/src/sdk/host/control/dispatch.ts +2 -0
- package/src/sdk/host/control/operations.ts +1 -0
- package/src/sdk/host/host.ts +2 -2
- package/src/sdk/lifecycle-session.ts +19 -3
- package/src/sdk/protocol/operation-inventory.generated.json +58 -0
- package/src/sdk/protocol/operation-registry.ts +1 -0
- package/src/sdk/session.ts +24 -1
- package/src/sdk/startup-capability.ts +6 -0
- package/src/session/agent-session.ts +290 -132
- package/src/session/fallback-chain-controller.ts +47 -8
- package/src/session/internal/managed-session-scope.ts +173 -127
- package/src/session/internal/native-publish-outcome.ts +8 -3
- package/src/session/session-manager.ts +608 -25
- package/src/skill-state/workflow-mutation-guard.ts +41 -7
- package/src/task/executor.ts +152 -42
- package/src/task/index.ts +2 -0
- package/src/task/provider-retry-status.ts +88 -0
- package/src/task/render.ts +45 -26
- package/src/task/types.ts +25 -11
- package/src/task/ultragoal-redteam-activation.ts +75 -0
- package/src/tools/bash-allowed-prefixes.ts +45 -1
- package/src/tools/index.ts +3 -1
- package/src/tools/resource-gc.ts +496 -120
- package/src/tools/subagent-render.ts +74 -16
- package/src/tools/subagent.ts +2 -0
- package/src/workflow/workflow-intent-diff.ts +4 -1
- package/vendor/insane-search/MANIFEST.json +4 -1
- package/vendor/insane-search/engine/bias_check.py +5 -0
- package/vendor/insane-search/engine/fetch_chain.py +67 -1
- package/vendor/insane-search/engine/transport.py +13 -2
- package/vendor/insane-search/engine/validators.py +4 -1
|
@@ -30,6 +30,7 @@ export class FallbackChainController {
|
|
|
30
30
|
|
|
31
31
|
#attemptStarted = false;
|
|
32
32
|
#totalAttemptsUsed = 0;
|
|
33
|
+
#restoredEntryIndices = new Set<number>();
|
|
33
34
|
skips: Array<{ selector: string; reason: string }> = [];
|
|
34
35
|
exhaustedForTurn = false;
|
|
35
36
|
|
|
@@ -46,6 +47,10 @@ export class FallbackChainController {
|
|
|
46
47
|
return this.#totalAttemptsUsed;
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
#maxTotalAttempts(): number {
|
|
51
|
+
return this.maxAttempts * this.chain.entries.length + this.#restoredEntryIndices.size;
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
currentSelector(): string | undefined {
|
|
50
55
|
return this.chain.entries[this.activeIndex];
|
|
51
56
|
}
|
|
@@ -59,7 +64,7 @@ export class FallbackChainController {
|
|
|
59
64
|
/** Charge an upstream request at its concrete transport boundary. */
|
|
60
65
|
onAttemptStarted(): void {
|
|
61
66
|
if (!this.currentSelector() || this.exhaustedForTurn) return;
|
|
62
|
-
if (this.#totalAttemptsUsed >= this
|
|
67
|
+
if (this.#totalAttemptsUsed >= this.#maxTotalAttempts()) {
|
|
63
68
|
this.activeIndex = this.chain.entries.length;
|
|
64
69
|
this.exhaustedForTurn = true;
|
|
65
70
|
return;
|
|
@@ -82,6 +87,7 @@ export class FallbackChainController {
|
|
|
82
87
|
this.attemptsUsed = 0;
|
|
83
88
|
this.#totalAttemptsUsed = 0;
|
|
84
89
|
this.tried = [];
|
|
90
|
+
this.#restoredEntryIndices.clear();
|
|
85
91
|
this.#attemptStarted = false;
|
|
86
92
|
}
|
|
87
93
|
|
|
@@ -91,6 +97,7 @@ export class FallbackChainController {
|
|
|
91
97
|
this.skips = [...skips];
|
|
92
98
|
this.attemptsUsed = 0;
|
|
93
99
|
this.#totalAttemptsUsed = 0;
|
|
100
|
+
this.#restoredEntryIndices.clear();
|
|
94
101
|
this.#attemptStarted = false;
|
|
95
102
|
this.exhaustedForTurn = this.activeIndex >= this.chain.entries.length;
|
|
96
103
|
}
|
|
@@ -104,7 +111,7 @@ export class FallbackChainController {
|
|
|
104
111
|
}
|
|
105
112
|
this.#attemptStarted = false;
|
|
106
113
|
this.tried.push({ selector, triggerClass, reason });
|
|
107
|
-
if (this.#totalAttemptsUsed >= this
|
|
114
|
+
if (this.#totalAttemptsUsed >= this.#maxTotalAttempts()) {
|
|
108
115
|
this.activeIndex = this.chain.entries.length;
|
|
109
116
|
this.exhaustedForTurn = true;
|
|
110
117
|
return "exhausted";
|
|
@@ -126,19 +133,23 @@ export class FallbackChainController {
|
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
/**
|
|
129
|
-
* Restore the entry that just advanced
|
|
130
|
-
*
|
|
136
|
+
* Restore the entry that just advanced for one attempt with a rotated credential.
|
|
137
|
+
* Each non-terminal entry may be restored once, so credential rotation remains
|
|
138
|
+
* bounded and cannot consume the attempts reserved for downstream entries.
|
|
131
139
|
*/
|
|
132
140
|
restorePreviousEntryForRetry(): boolean {
|
|
141
|
+
const previousIndex = this.activeIndex - 1;
|
|
133
142
|
if (
|
|
134
|
-
|
|
143
|
+
previousIndex < 0 ||
|
|
135
144
|
this.exhaustedForTurn ||
|
|
136
|
-
this.#
|
|
145
|
+
this.#restoredEntryIndices.has(previousIndex) ||
|
|
146
|
+
this.#totalAttemptsUsed >= this.#maxTotalAttempts()
|
|
137
147
|
) {
|
|
138
148
|
return false;
|
|
139
149
|
}
|
|
140
|
-
this.
|
|
141
|
-
this.
|
|
150
|
+
this.#restoredEntryIndices.add(previousIndex);
|
|
151
|
+
this.activeIndex = previousIndex;
|
|
152
|
+
this.attemptsUsed = this.maxAttempts - 1;
|
|
142
153
|
this.exhaustedForTurn = false;
|
|
143
154
|
this.#attemptStarted = false;
|
|
144
155
|
return true;
|
|
@@ -157,6 +168,7 @@ export class FallbackChainController {
|
|
|
157
168
|
this.attemptsUsed = 0;
|
|
158
169
|
this.#totalAttemptsUsed = 0;
|
|
159
170
|
this.tried = [];
|
|
171
|
+
this.#restoredEntryIndices.clear();
|
|
160
172
|
this.skips = [];
|
|
161
173
|
this.exhaustedForTurn = this.chain.entries.length === 0;
|
|
162
174
|
this.#attemptStarted = false;
|
|
@@ -174,6 +186,33 @@ export function cappedExponentialWithFullJitter(
|
|
|
174
186
|
return Math.floor(Math.max(0, cap) * Math.max(0, Math.min(1, random())));
|
|
175
187
|
}
|
|
176
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Legacy auto-compaction retry delay.
|
|
191
|
+
*
|
|
192
|
+
* Deliberately the mirror image of `effectiveFallbackDelay`: this path recovers
|
|
193
|
+
* Retry-After by regex over provider error prose (`#parseRetryAfterMsFromError`),
|
|
194
|
+
* so it follows the documented legacy rule — `retry.maxDelayMs` caps every
|
|
195
|
+
* legacy session retry delay, including provider retry-after hints. Managed
|
|
196
|
+
* fallback stays uncapped because it retries within its own per-entry budget;
|
|
197
|
+
* compaction has no such budget, so the final candidate would otherwise sleep
|
|
198
|
+
* for the full server-suggested duration.
|
|
199
|
+
*
|
|
200
|
+
* `maxDelayMs <= 0` means "no cap", matching `cappedExponentialWithFullJitter`.
|
|
201
|
+
* A missing, NaN, or infinite hint collapses to "no usable hint".
|
|
202
|
+
*/
|
|
203
|
+
export function compactionRetryDelay(
|
|
204
|
+
baseDelayMs: number,
|
|
205
|
+
maxDelayMs: number,
|
|
206
|
+
attempt: number,
|
|
207
|
+
retryAfterMs: number | undefined,
|
|
208
|
+
): number {
|
|
209
|
+
const exponential = baseDelayMs * 2 ** Math.max(0, attempt);
|
|
210
|
+
const hint = Number.isFinite(retryAfterMs) ? Math.max(0, retryAfterMs as number) : 0;
|
|
211
|
+
const hinted = Math.max(exponential, hint);
|
|
212
|
+
const bounded = maxDelayMs > 0 ? Math.min(hinted, maxDelayMs) : hinted;
|
|
213
|
+
return Math.max(0, bounded);
|
|
214
|
+
}
|
|
215
|
+
|
|
177
216
|
/** Retry-After is intentionally uncapped. */
|
|
178
217
|
export function effectiveFallbackDelay(
|
|
179
218
|
baseDelayMs: number,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
verifyOwnerOnlyPathSecurity,
|
|
9
9
|
verifyOwnerOnlyPathSecurityExpected,
|
|
10
10
|
} from "@gajae-code/natives";
|
|
11
|
-
import { pathIsWithin } from "@gajae-code/utils";
|
|
11
|
+
import { logger, pathIsWithin } from "@gajae-code/utils";
|
|
12
12
|
import type { ResumeSessionIdentity } from "../session-manager";
|
|
13
13
|
import {
|
|
14
14
|
FileSessionStorage,
|
|
@@ -2400,7 +2400,11 @@ type SourceArtifactCleanup = {
|
|
|
2400
2400
|
tree: NativeDirectoryTreeSnapshot;
|
|
2401
2401
|
};
|
|
2402
2402
|
|
|
2403
|
-
function cleanupAuthorityMatches(
|
|
2403
|
+
export function cleanupAuthorityMatches(
|
|
2404
|
+
cleanup: SourceArtifactCleanup,
|
|
2405
|
+
parent: string,
|
|
2406
|
+
platform: NodeJS.Platform = process.platform,
|
|
2407
|
+
): boolean {
|
|
2404
2408
|
try {
|
|
2405
2409
|
const stat = fs.lstatSync(cleanup.retainedPath, { bigint: true });
|
|
2406
2410
|
if (
|
|
@@ -2408,15 +2412,26 @@ function cleanupAuthorityMatches(cleanup: SourceArtifactCleanup, parent: string)
|
|
|
2408
2412
|
!stat.isDirectory() ||
|
|
2409
2413
|
stat.isSymbolicLink() ||
|
|
2410
2414
|
stat.dev !== cleanup.identity.dev ||
|
|
2411
|
-
stat.ino !== cleanup.identity.ino
|
|
2412
|
-
stat.size !== cleanup.identity.size ||
|
|
2413
|
-
stat.mtimeNs !== cleanup.identity.mtimeNs
|
|
2415
|
+
stat.ino !== cleanup.identity.ino
|
|
2414
2416
|
)
|
|
2415
2417
|
return false;
|
|
2416
2418
|
const snapshot = native.snapshotDirectoryTree(cleanup.retainedPath);
|
|
2419
|
+
const observedRoot = snapshot.snapshot?.entries.find(
|
|
2420
|
+
entry => entry.relativePath === "" && entry.kind === "directory",
|
|
2421
|
+
);
|
|
2422
|
+
if (
|
|
2423
|
+
!snapshot.ok ||
|
|
2424
|
+
!snapshot.snapshot ||
|
|
2425
|
+
!observedRoot ||
|
|
2426
|
+
(platform === "win32"
|
|
2427
|
+
? observedRoot.dev !== cleanup.identity.dev.toString() ||
|
|
2428
|
+
observedRoot.ino !== cleanup.identity.ino.toString() ||
|
|
2429
|
+
BigInt(observedRoot.size) !== cleanup.identity.size ||
|
|
2430
|
+
BigInt(observedRoot.mtimeNs) !== cleanup.identity.mtimeNs
|
|
2431
|
+
: stat.size !== cleanup.identity.size || stat.mtimeNs !== cleanup.identity.mtimeNs)
|
|
2432
|
+
)
|
|
2433
|
+
return false;
|
|
2417
2434
|
return (
|
|
2418
|
-
!!snapshot.ok &&
|
|
2419
|
-
!!snapshot.snapshot &&
|
|
2420
2435
|
sameArtifactTree(snapshot.snapshot, cleanup.tree) &&
|
|
2421
2436
|
cleanup.tree.entries.length === 1 &&
|
|
2422
2437
|
cleanup.tree.entries[0]?.relativePath === "" &&
|
|
@@ -2427,8 +2442,9 @@ function cleanupAuthorityMatches(cleanup: SourceArtifactCleanup, parent: string)
|
|
|
2427
2442
|
}
|
|
2428
2443
|
}
|
|
2429
2444
|
|
|
2430
|
-
function detachArtifactRootForMigration(
|
|
2445
|
+
export function detachArtifactRootForMigration(
|
|
2431
2446
|
plan: DetachedArtifactRoot,
|
|
2447
|
+
platform: NodeJS.Platform = process.platform,
|
|
2432
2448
|
):
|
|
2433
2449
|
| { detached: DetachedArtifactRoot; detachOutcome: "clean" }
|
|
2434
2450
|
| { detached: DetachedArtifactRoot; detachOutcome: "cleanup_pending"; cleanup: SourceArtifactCleanup } {
|
|
@@ -2464,14 +2480,27 @@ function detachArtifactRootForMigration(
|
|
|
2464
2480
|
if (!stat.isDirectory() || stat.isSymbolicLink() || path.dirname(placeholder) !== path.dirname(plan.originalPath))
|
|
2465
2481
|
throw new Error("durability_failed");
|
|
2466
2482
|
const snapshot = native.snapshotDirectoryTree(placeholder);
|
|
2483
|
+
if (!snapshot.ok || !snapshot.snapshot) throw new Error("durability_failed");
|
|
2484
|
+
// Windows directory size/mtime authority is the native tree root, never Bun's
|
|
2485
|
+
// zero-valued directory lstat. Capturing Bun values here would guarantee a
|
|
2486
|
+
// mismatch against the native-authoritative check below.
|
|
2487
|
+
const placeholderRoot = snapshot.snapshot.entries.find(
|
|
2488
|
+
entry => entry.relativePath === "" && entry.kind === "directory",
|
|
2489
|
+
);
|
|
2490
|
+
if (!placeholderRoot) throw new Error("durability_failed");
|
|
2467
2491
|
const cleanup: SourceArtifactCleanup = {
|
|
2468
2492
|
state: "cleanup_pending",
|
|
2469
2493
|
role: "exchange_placeholder",
|
|
2470
2494
|
retainedPath: placeholder,
|
|
2471
|
-
identity: {
|
|
2472
|
-
|
|
2495
|
+
identity: {
|
|
2496
|
+
dev: stat.dev,
|
|
2497
|
+
ino: stat.ino,
|
|
2498
|
+
size: platform === "win32" ? BigInt(placeholderRoot.size) : stat.size,
|
|
2499
|
+
mtimeNs: platform === "win32" ? BigInt(placeholderRoot.mtimeNs) : stat.mtimeNs,
|
|
2500
|
+
},
|
|
2501
|
+
tree: snapshot.snapshot,
|
|
2473
2502
|
};
|
|
2474
|
-
if (!
|
|
2503
|
+
if (!cleanupAuthorityMatches(cleanup, path.dirname(plan.originalPath), platform))
|
|
2475
2504
|
throw new Error("durability_failed");
|
|
2476
2505
|
return { detached, detachOutcome: "cleanup_pending", cleanup };
|
|
2477
2506
|
}
|
|
@@ -2789,12 +2818,16 @@ function validateCandidateForScope(scope: ManagedScope, candidate: ManagedCandid
|
|
|
2789
2818
|
}
|
|
2790
2819
|
|
|
2791
2820
|
/** Resume tombstoned cleanup under its original operation lease without restoring retired candidates. */
|
|
2792
|
-
export async function reconcileManagedTombstones(
|
|
2821
|
+
export async function reconcileManagedTombstones(
|
|
2822
|
+
scope: ManagedScope,
|
|
2823
|
+
expectedCandidate?: ManagedCandidate,
|
|
2824
|
+
): Promise<void> {
|
|
2793
2825
|
const directory = path.join(managedInternalDirectory(scope), MANAGED_TOMBSTONES_DIRECTORY);
|
|
2794
2826
|
for (const name of fs.readdirSync(directory)) {
|
|
2795
2827
|
const tombstone = path.join(directory, name);
|
|
2796
2828
|
const targets = retiredTargets(scope, tombstone);
|
|
2797
2829
|
if (!targets) continue;
|
|
2830
|
+
if (targets.every(target => cleanupCompleted(scope, tombstone, target))) continue;
|
|
2798
2831
|
let lock: ManagedStorageLock | undefined;
|
|
2799
2832
|
try {
|
|
2800
2833
|
lock = await acquireManagedLock(
|
|
@@ -2807,135 +2840,148 @@ export async function reconcileManagedTombstones(scope: ManagedScope): Promise<v
|
|
|
2807
2840
|
for (const target of lockedTargets) {
|
|
2808
2841
|
lock.assertOwned();
|
|
2809
2842
|
if (cleanupCompleted(scope, tombstone, target)) continue;
|
|
2810
|
-
const pending = pendingCleanupReceipt(scope, tombstone, target);
|
|
2811
|
-
const observedPending = pending ? probePlannedCleanupDetach(target, pending) : undefined;
|
|
2812
2843
|
try {
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
(
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
cwd: target.cwd,
|
|
2844
|
-
transcriptIdentity: target.identity,
|
|
2845
|
-
expectedArtifactsIdentity: active.expectedArtifactsIdentity,
|
|
2846
|
-
expectedArtifactsTree: active.expectedArtifactsTree,
|
|
2847
|
-
detachedArtifactsPath:
|
|
2848
|
-
active.detachedArtifactsPath ??
|
|
2849
|
-
observedPending?.detachedArtifactsPath ??
|
|
2850
|
-
(fs.existsSync(active.plannedArtifactsPath) ? active.plannedArtifactsPath : undefined),
|
|
2851
|
-
detachedTranscriptPath:
|
|
2852
|
-
active.detachedTranscriptPath ??
|
|
2853
|
-
observedPending?.detachedTranscriptPath ??
|
|
2854
|
-
(pending && fs.existsSync(pending.plannedTranscriptPath)
|
|
2855
|
-
? pending.plannedTranscriptPath
|
|
2856
|
-
: undefined) ??
|
|
2857
|
-
(fs.existsSync(active.plannedTranscriptPath) ? active.plannedTranscriptPath : undefined),
|
|
2858
|
-
retainedArtifactsSuccessorPath: active.retainedArtifactsSuccessorPath,
|
|
2859
|
-
retainedArtifactsPlaceholderPath: active.retainedArtifactsPlaceholderPath,
|
|
2860
|
-
retainedArtifactsUnknownPath: active.retainedArtifactsUnknownPath,
|
|
2861
|
-
retainedTranscriptSuccessorPath: active.retainedTranscriptSuccessorPath,
|
|
2862
|
-
retainedTranscriptPlaceholderPath: active.retainedTranscriptPlaceholderPath,
|
|
2863
|
-
retainedTranscriptUnknownPath: active.retainedTranscriptUnknownPath,
|
|
2864
|
-
plannedArtifactsPath: active.plannedArtifactsPath,
|
|
2865
|
-
plannedTranscriptPath: active.plannedTranscriptPath,
|
|
2866
|
-
...(cleanupArtifactsRemoved(scope, tombstone, target, pending?.attempt ?? active.attempt)
|
|
2867
|
-
? { artifactsRemoved: true as const }
|
|
2868
|
-
: {}),
|
|
2869
|
-
});
|
|
2870
|
-
if (deletion.kind === "artifacts_removed") {
|
|
2871
|
-
await publishCleanupArtifactsRemoved(scope, tombstone, active, lock);
|
|
2872
|
-
deletion = await new FileSessionStorage().deleteSessionVerified({
|
|
2844
|
+
const pending = pendingCleanupReceipt(scope, tombstone, target);
|
|
2845
|
+
const observedPending = pending ? probePlannedCleanupDetach(target, pending) : undefined;
|
|
2846
|
+
try {
|
|
2847
|
+
fs.lstatSync(target.path);
|
|
2848
|
+
} catch (error) {
|
|
2849
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
2850
|
+
if (!observedPending) continue;
|
|
2851
|
+
if (
|
|
2852
|
+
cleanupArtifactsRemoved(scope, tombstone, target, observedPending.attempt) &&
|
|
2853
|
+
cleanupRootsAbsent(tombstone, target, observedPending)
|
|
2854
|
+
) {
|
|
2855
|
+
fsyncManagedParent(target.path);
|
|
2856
|
+
await publishCleanupCompleted(scope, tombstone, target, lock);
|
|
2857
|
+
continue;
|
|
2858
|
+
}
|
|
2859
|
+
} else throw error;
|
|
2860
|
+
}
|
|
2861
|
+
const verified = observedPending ? target : validateCandidateForScope(scope, target);
|
|
2862
|
+
if (!verified || !sameCandidate(verified, target)) throw new Error("source_changed");
|
|
2863
|
+
const discoveredDetach =
|
|
2864
|
+
!!observedPending &&
|
|
2865
|
+
(observedPending.detachedArtifactsPath !== pending?.detachedArtifactsPath ||
|
|
2866
|
+
observedPending.detachedTranscriptPath !== pending?.detachedTranscriptPath);
|
|
2867
|
+
const active =
|
|
2868
|
+
discoveredDetach || (observedPending && requiresFreshCleanupPlan(observedPending))
|
|
2869
|
+
? nextCleanupReceipt(target, observedPending)
|
|
2870
|
+
: (observedPending ?? nextCleanupReceipt(target, undefined));
|
|
2871
|
+
if (!observedPending || discoveredDetach || requiresFreshCleanupPlan(observedPending))
|
|
2872
|
+
await publishCleanupPending(scope, tombstone, active, lock);
|
|
2873
|
+
let deletion = await new FileSessionStorage().deleteSessionVerified({
|
|
2873
2874
|
sessionsRoot: scope.sessionsRoot,
|
|
2874
2875
|
transcriptPath: target.path,
|
|
2875
2876
|
sessionId: target.sessionId,
|
|
2876
2877
|
cwd: target.cwd,
|
|
2877
2878
|
transcriptIdentity: target.identity,
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2879
|
+
expectedArtifactsIdentity: active.expectedArtifactsIdentity,
|
|
2880
|
+
expectedArtifactsTree: active.expectedArtifactsTree,
|
|
2881
|
+
detachedArtifactsPath:
|
|
2882
|
+
active.detachedArtifactsPath ??
|
|
2883
|
+
observedPending?.detachedArtifactsPath ??
|
|
2884
|
+
(fs.existsSync(active.plannedArtifactsPath) ? active.plannedArtifactsPath : undefined),
|
|
2885
|
+
detachedTranscriptPath:
|
|
2886
|
+
active.detachedTranscriptPath ??
|
|
2887
|
+
observedPending?.detachedTranscriptPath ??
|
|
2888
|
+
(pending && fs.existsSync(pending.plannedTranscriptPath)
|
|
2889
|
+
? pending.plannedTranscriptPath
|
|
2890
|
+
: undefined) ??
|
|
2891
|
+
(fs.existsSync(active.plannedTranscriptPath) ? active.plannedTranscriptPath : undefined),
|
|
2882
2892
|
retainedArtifactsSuccessorPath: active.retainedArtifactsSuccessorPath,
|
|
2883
2893
|
retainedArtifactsPlaceholderPath: active.retainedArtifactsPlaceholderPath,
|
|
2884
2894
|
retainedArtifactsUnknownPath: active.retainedArtifactsUnknownPath,
|
|
2885
2895
|
retainedTranscriptSuccessorPath: active.retainedTranscriptSuccessorPath,
|
|
2886
2896
|
retainedTranscriptPlaceholderPath: active.retainedTranscriptPlaceholderPath,
|
|
2887
2897
|
retainedTranscriptUnknownPath: active.retainedTranscriptUnknownPath,
|
|
2888
|
-
|
|
2898
|
+
plannedArtifactsPath: active.plannedArtifactsPath,
|
|
2899
|
+
plannedTranscriptPath: active.plannedTranscriptPath,
|
|
2900
|
+
...(cleanupArtifactsRemoved(scope, tombstone, target, pending?.attempt ?? active.attempt)
|
|
2901
|
+
? { artifactsRemoved: true as const }
|
|
2902
|
+
: {}),
|
|
2889
2903
|
});
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2904
|
+
if (deletion.kind === "artifacts_removed") {
|
|
2905
|
+
await publishCleanupArtifactsRemoved(scope, tombstone, active, lock);
|
|
2906
|
+
deletion = await new FileSessionStorage().deleteSessionVerified({
|
|
2907
|
+
sessionsRoot: scope.sessionsRoot,
|
|
2908
|
+
transcriptPath: target.path,
|
|
2909
|
+
sessionId: target.sessionId,
|
|
2910
|
+
cwd: target.cwd,
|
|
2911
|
+
transcriptIdentity: target.identity,
|
|
2912
|
+
plannedArtifactsPath: active.plannedArtifactsPath,
|
|
2913
|
+
plannedTranscriptPath: active.plannedTranscriptPath,
|
|
2914
|
+
detachedTranscriptPath: active.detachedTranscriptPath ?? observedPending?.detachedTranscriptPath,
|
|
2915
|
+
|
|
2916
|
+
retainedArtifactsSuccessorPath: active.retainedArtifactsSuccessorPath,
|
|
2917
|
+
retainedArtifactsPlaceholderPath: active.retainedArtifactsPlaceholderPath,
|
|
2918
|
+
retainedArtifactsUnknownPath: active.retainedArtifactsUnknownPath,
|
|
2919
|
+
retainedTranscriptSuccessorPath: active.retainedTranscriptSuccessorPath,
|
|
2920
|
+
retainedTranscriptPlaceholderPath: active.retainedTranscriptPlaceholderPath,
|
|
2921
|
+
retainedTranscriptUnknownPath: active.retainedTranscriptUnknownPath,
|
|
2922
|
+
artifactsRemoved: true,
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2925
|
+
if (deletion.kind === "cleanup_pending") {
|
|
2926
|
+
const retry = nextCleanupReceipt(target, active);
|
|
2927
|
+
await publishCleanupPending(
|
|
2928
|
+
scope,
|
|
2929
|
+
tombstone,
|
|
2930
|
+
{
|
|
2931
|
+
...retry,
|
|
2932
|
+
expectedArtifactsIdentity:
|
|
2933
|
+
deletion.phase === "artifacts"
|
|
2934
|
+
? deletion.artifactsIdentity
|
|
2935
|
+
: active.expectedArtifactsIdentity,
|
|
2936
|
+
expectedArtifactsTree:
|
|
2937
|
+
deletion.phase === "artifacts" ? deletion.artifactsTree : active.expectedArtifactsTree,
|
|
2938
|
+
detachedArtifactsPath:
|
|
2939
|
+
deletion.phase === "artifacts"
|
|
2940
|
+
? deletion.detachedArtifactsPath
|
|
2941
|
+
: active.detachedArtifactsPath,
|
|
2942
|
+
detachedTranscriptPath:
|
|
2943
|
+
deletion.phase === "transcript"
|
|
2944
|
+
? deletion.detachedTranscriptPath
|
|
2945
|
+
: active.detachedTranscriptPath,
|
|
2946
|
+
retainedArtifactsSuccessorPath:
|
|
2947
|
+
deletion.phase === "artifacts"
|
|
2948
|
+
? deletion.retainedSuccessorPath
|
|
2949
|
+
: active.retainedArtifactsSuccessorPath,
|
|
2950
|
+
retainedArtifactsPlaceholderPath:
|
|
2951
|
+
deletion.phase === "artifacts"
|
|
2952
|
+
? deletion.retainedPlaceholderPath
|
|
2953
|
+
: active.retainedArtifactsPlaceholderPath,
|
|
2954
|
+
retainedArtifactsUnknownPath:
|
|
2955
|
+
deletion.phase === "artifacts"
|
|
2956
|
+
? deletion.retainedUnknownPath
|
|
2957
|
+
: active.retainedArtifactsUnknownPath,
|
|
2958
|
+
retainedTranscriptSuccessorPath:
|
|
2959
|
+
deletion.phase === "transcript"
|
|
2960
|
+
? deletion.retainedSuccessorPath
|
|
2961
|
+
: active.retainedTranscriptSuccessorPath,
|
|
2962
|
+
retainedTranscriptPlaceholderPath:
|
|
2963
|
+
deletion.phase === "transcript"
|
|
2964
|
+
? deletion.retainedPlaceholderPath
|
|
2965
|
+
: active.retainedTranscriptPlaceholderPath,
|
|
2966
|
+
retainedTranscriptUnknownPath:
|
|
2967
|
+
deletion.phase === "transcript"
|
|
2968
|
+
? deletion.retainedUnknownPath
|
|
2969
|
+
: active.retainedTranscriptUnknownPath,
|
|
2970
|
+
},
|
|
2971
|
+
lock,
|
|
2972
|
+
);
|
|
2973
|
+
continue;
|
|
2974
|
+
}
|
|
2975
|
+
fsyncManagedParent(target.path);
|
|
2976
|
+
await publishCleanupCompleted(scope, tombstone, target, lock);
|
|
2977
|
+
} catch (error) {
|
|
2978
|
+
if (!expectedCandidate || target.sessionId === expectedCandidate.sessionId) throw error;
|
|
2979
|
+
logger.warn("Tombstone reconciliation failed for one target; will retry on a future scope open", {
|
|
2895
2980
|
tombstone,
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
deletion.phase === "artifacts" ? deletion.artifactsIdentity : active.expectedArtifactsIdentity,
|
|
2900
|
-
expectedArtifactsTree:
|
|
2901
|
-
deletion.phase === "artifacts" ? deletion.artifactsTree : active.expectedArtifactsTree,
|
|
2902
|
-
detachedArtifactsPath:
|
|
2903
|
-
deletion.phase === "artifacts" ? deletion.detachedArtifactsPath : active.detachedArtifactsPath,
|
|
2904
|
-
detachedTranscriptPath:
|
|
2905
|
-
deletion.phase === "transcript"
|
|
2906
|
-
? deletion.detachedTranscriptPath
|
|
2907
|
-
: active.detachedTranscriptPath,
|
|
2908
|
-
retainedArtifactsSuccessorPath:
|
|
2909
|
-
deletion.phase === "artifacts"
|
|
2910
|
-
? deletion.retainedSuccessorPath
|
|
2911
|
-
: active.retainedArtifactsSuccessorPath,
|
|
2912
|
-
retainedArtifactsPlaceholderPath:
|
|
2913
|
-
deletion.phase === "artifacts"
|
|
2914
|
-
? deletion.retainedPlaceholderPath
|
|
2915
|
-
: active.retainedArtifactsPlaceholderPath,
|
|
2916
|
-
retainedArtifactsUnknownPath:
|
|
2917
|
-
deletion.phase === "artifacts"
|
|
2918
|
-
? deletion.retainedUnknownPath
|
|
2919
|
-
: active.retainedArtifactsUnknownPath,
|
|
2920
|
-
retainedTranscriptSuccessorPath:
|
|
2921
|
-
deletion.phase === "transcript"
|
|
2922
|
-
? deletion.retainedSuccessorPath
|
|
2923
|
-
: active.retainedTranscriptSuccessorPath,
|
|
2924
|
-
retainedTranscriptPlaceholderPath:
|
|
2925
|
-
deletion.phase === "transcript"
|
|
2926
|
-
? deletion.retainedPlaceholderPath
|
|
2927
|
-
: active.retainedTranscriptPlaceholderPath,
|
|
2928
|
-
retainedTranscriptUnknownPath:
|
|
2929
|
-
deletion.phase === "transcript"
|
|
2930
|
-
? deletion.retainedUnknownPath
|
|
2931
|
-
: active.retainedTranscriptUnknownPath,
|
|
2932
|
-
},
|
|
2933
|
-
lock,
|
|
2934
|
-
);
|
|
2935
|
-
continue;
|
|
2981
|
+
sessionId: target.sessionId,
|
|
2982
|
+
error: String(error),
|
|
2983
|
+
});
|
|
2936
2984
|
}
|
|
2937
|
-
fsyncManagedParent(target.path);
|
|
2938
|
-
await publishCleanupCompleted(scope, tombstone, target, lock);
|
|
2939
2985
|
}
|
|
2940
2986
|
} finally {
|
|
2941
2987
|
if (lock) await lock.release().catch(() => undefined);
|
|
@@ -2962,7 +3008,7 @@ export async function prepareManagedSessionScopeForWrite(
|
|
|
2962
3008
|
ensureManagedDirectory(path.join(internal, MANAGED_LOCKS_DIRECTORY), root, policy);
|
|
2963
3009
|
ensureManagedDirectory(path.join(internal, MANAGED_RECEIPTS_DIRECTORY), root, policy);
|
|
2964
3010
|
ensureManagedDirectory(path.join(internal, MANAGED_TOMBSTONES_DIRECTORY), root, policy);
|
|
2965
|
-
await reconcileManagedTombstones(scope);
|
|
3011
|
+
await reconcileManagedTombstones(scope, expectedCandidate);
|
|
2966
3012
|
return { kind: "resolved", scope };
|
|
2967
3013
|
} catch (error) {
|
|
2968
3014
|
const publication = error instanceof ManagedPublishError ? error : undefined;
|
|
@@ -171,9 +171,14 @@ function legalOutcome(outcome: NativePublishOutcome): boolean {
|
|
|
171
171
|
outcome.durabilityState === "not_attempted" &&
|
|
172
172
|
preMutationReasons.has(outcome.reason) &&
|
|
173
173
|
((outcome.phase === "rename" &&
|
|
174
|
-
[
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
[
|
|
175
|
+
"destination_exists",
|
|
176
|
+
"atomic_unavailable",
|
|
177
|
+
"cross_device",
|
|
178
|
+
"permission_denied",
|
|
179
|
+
"io_failure",
|
|
180
|
+
"interrupted",
|
|
181
|
+
].includes(outcome.reason)) ||
|
|
177
182
|
(outcome.phase === "preflight" &&
|
|
178
183
|
!["atomic_unavailable", "cross_device", "interrupted"].includes(outcome.reason)) ||
|
|
179
184
|
(outcome.phase === "file_sync" && outcome.reason === "io_failure"))
|