@gajae-code/coding-agent 0.12.6 → 0.12.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 +22 -1
- package/dist/types/async/job-manager.d.ts +36 -1
- package/dist/types/commands/notify.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-discovery-manager.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/config/resolve-config-value.d.ts +2 -1
- package/dist/types/config/settings-schema.d.ts +17 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +8 -10
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
- package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +20 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
- package/dist/types/sdk/broker/broker.d.ts +5 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -3
- package/dist/types/sdk/bus/config.d.ts +75 -113
- package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
- package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
- package/dist/types/sdk/bus/index.d.ts +1 -1
- package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
- package/dist/types/sdk/bus/notification-service.d.ts +31 -4
- package/dist/types/sdk/bus/session-control.d.ts +8 -3
- package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
- package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
- package/dist/types/sdk/client/discovery.d.ts +2 -1
- package/dist/types/sdk/host/host.d.ts +2 -23
- package/dist/types/sdk/host/query/handlers.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/providers.d.ts +22 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/session/artifacts.d.ts +5 -8
- package/dist/types/session/client-bridge.d.ts +0 -14
- package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +11 -4
- package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
- package/dist/types/session/internal/session-open-errors.d.ts +5 -0
- package/dist/types/session/messages.d.ts +1 -4
- package/dist/types/session/session-manager.d.ts +7 -6
- package/dist/types/session/session-storage.d.ts +2 -0
- package/dist/types/session/streaming-output.d.ts +0 -32
- package/dist/types/task/receipt.d.ts +1 -2
- package/dist/types/task/types.d.ts +43 -178
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +0 -3
- package/dist/types/tools/output-meta.d.ts +5 -37
- package/dist/types/tools/subagent.d.ts +15 -0
- package/dist/types/tools/todo-write.d.ts +3 -0
- package/dist/types/tools/tool-result.d.ts +0 -1
- package/package.json +10 -8
- package/scripts/build-sdk-package-smoke.ts +51 -0
- package/scripts/capture-notifications-settings-showcase.ts +1 -1
- package/src/async/job-manager.ts +430 -46
- package/src/cli/notify-cli.ts +204 -94
- package/src/commands/notify.ts +19 -8
- package/src/config/file-lock.ts +6 -0
- package/src/config/model-discovery-manager.ts +50 -8
- package/src/config/model-registry.ts +862 -75
- package/src/config/resolve-config-value.ts +10 -8
- package/src/config/settings-schema.ts +25 -15
- package/src/config/settings.ts +15 -15
- package/src/cursor.ts +13 -20
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +151 -425
- package/src/gjc-runtime/session-state-sidecar.ts +12 -2
- package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
- package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
- package/src/gjc-runtime/workflow-manifest.ts +0 -12
- package/src/internal-urls/agent-protocol.ts +38 -98
- package/src/internal-urls/artifact-protocol.ts +17 -28
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/registry-helpers.ts +0 -62
- package/src/main.ts +43 -7
- package/src/modes/DESIGN.md +11 -11
- package/src/modes/acp/acp-agent.ts +0 -24
- package/src/modes/acp/acp-client-bridge.ts +11 -154
- package/src/modes/components/hook-selector.ts +155 -44
- package/src/modes/components/model-selector.ts +242 -49
- package/src/modes/components/notifications-settings-editor.ts +631 -85
- package/src/modes/controllers/extension-ui-controller.ts +114 -15
- package/src/modes/controllers/selector-controller.ts +464 -50
- package/src/prompts/tools/bash.md +2 -2
- package/src/sdk/acp/adapter.ts +9 -200
- package/src/sdk/broker/broker.ts +54 -10
- package/src/sdk/broker/lifecycle-ledger.ts +34 -1
- package/src/sdk/broker/lifecycle.ts +47 -50
- package/src/sdk/bus/chat-daemon-cli.ts +12 -5
- package/src/sdk/bus/chat-daemon-control.ts +35 -8
- package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
- package/src/sdk/bus/config.ts +534 -286
- package/src/sdk/bus/discord-live-provider.ts +57 -2
- package/src/sdk/bus/discord-provider.ts +21 -0
- package/src/sdk/bus/index.ts +105 -359
- package/src/sdk/bus/notification-orchestration.ts +371 -68
- package/src/sdk/bus/notification-service.ts +431 -130
- package/src/sdk/bus/session-control.ts +119 -44
- package/src/sdk/bus/slack-live-provider.ts +78 -2
- package/src/sdk/bus/slack-provider.ts +24 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
- package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
- package/src/sdk/bus/telegram-daemon-control.ts +3 -3
- package/src/sdk/bus/telegram-daemon.ts +23 -15
- package/src/sdk/client/discovery.ts +10 -4
- package/src/sdk/host/host.ts +9 -187
- package/src/sdk/host/query/handlers.ts +20 -1
- package/src/sdk/host/reverse-leases.ts +42 -249
- package/src/sdk/index.ts +1 -0
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +5 -2
- package/src/sdk/providers.ts +51 -0
- package/src/sdk/session-directory.ts +1 -0
- package/src/sdk/session.ts +10 -7
- package/src/session/agent-session.ts +59 -202
- package/src/session/artifacts.ts +74 -211
- package/src/session/client-bridge.ts +0 -15
- package/src/session/internal/managed-session-scope.ts +981 -450
- package/src/session/internal/managed-session-storage.ts +362 -124
- package/src/session/internal/native-publish-outcome.ts +3 -1
- package/src/session/internal/session-open-errors.ts +11 -0
- package/src/session/messages.ts +7 -32
- package/src/session/session-manager.ts +48 -15
- package/src/session/session-storage.ts +72 -5
- package/src/session/streaming-output.ts +40 -198
- package/src/slash-commands/builtin-registry.ts +82 -4
- package/src/task/index.ts +237 -65
- package/src/task/receipt.ts +2 -4
- package/src/task/render.ts +0 -30
- package/src/task/types.ts +10 -34
- package/src/tools/ask.ts +1 -1
- package/src/tools/bash.ts +239 -818
- package/src/tools/eval.ts +4 -99
- package/src/tools/fetch.ts +15 -77
- package/src/tools/find.ts +3 -7
- package/src/tools/output-meta.ts +114 -696
- package/src/tools/search.ts +4 -9
- package/src/tools/subagent.ts +97 -75
- package/src/tools/telegram-send.ts +4 -2
- package/src/tools/todo-write.ts +47 -0
- package/src/tools/tool-result.ts +0 -1
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
- package/dist/types/utils/artifact-id.d.ts +0 -2
- package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
- package/src/utils/artifact-id.ts +0 -6
|
@@ -48,6 +48,7 @@ export type NativePublishIdentity = {
|
|
|
48
48
|
readonly dev: string;
|
|
49
49
|
readonly ino: string;
|
|
50
50
|
readonly size: string;
|
|
51
|
+
readonly nlink?: string;
|
|
51
52
|
readonly mtimeNs: string;
|
|
52
53
|
readonly ctimeNs: string;
|
|
53
54
|
readonly sha256?: string;
|
|
@@ -126,12 +127,13 @@ const malformed: NativePublishOutcome = Object.freeze({
|
|
|
126
127
|
|
|
127
128
|
function validIdentity(value: unknown): boolean {
|
|
128
129
|
if (value === undefined) return true;
|
|
129
|
-
if (!ownPlainRecord(value) || !exactKeys(value, ["dev", "ino", "size", "mtimeNs", "ctimeNs", "sha256"]))
|
|
130
|
+
if (!ownPlainRecord(value) || !exactKeys(value, ["dev", "ino", "nlink", "size", "mtimeNs", "ctimeNs", "sha256"]))
|
|
130
131
|
return false;
|
|
131
132
|
const decimal = (field: unknown) => typeof field === "string" && /^-?[0-9]{1,32}$/.test(field);
|
|
132
133
|
return (
|
|
133
134
|
decimal(value.dev) &&
|
|
134
135
|
decimal(value.ino) &&
|
|
136
|
+
(value.nlink === undefined || decimal(value.nlink)) &&
|
|
135
137
|
decimal(value.size) &&
|
|
136
138
|
decimal(value.mtimeNs) &&
|
|
137
139
|
decimal(value.ctimeNs) &&
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const SESSION_MIGRATION_BUSY_MESSAGE =
|
|
2
|
+
"Another session migration is still active. Wait for it to finish, then retry.";
|
|
3
|
+
|
|
4
|
+
export class SessionMigrationBusyError extends Error {
|
|
5
|
+
readonly code = "migration_busy";
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
super(SESSION_MIGRATION_BUSY_MESSAGE);
|
|
9
|
+
this.name = "SessionMigrationBusyError";
|
|
10
|
+
}
|
|
11
|
+
}
|
package/src/session/messages.ts
CHANGED
|
@@ -194,39 +194,14 @@ function utf8Suffix(text: string, maxBytes: number): string {
|
|
|
194
194
|
* next provider context is constructed. Byte boundaries never split UTF-8
|
|
195
195
|
* code points, so head/tail recovery remains readable for emoji and CJK text.
|
|
196
196
|
*/
|
|
197
|
-
export function createPreAdmissionArtifactSpillPreview(
|
|
198
|
-
fullText: string,
|
|
199
|
-
artifactId: string,
|
|
200
|
-
digest: string,
|
|
201
|
-
options: { maxInlineBytes?: number; fullOutput?: boolean } = {},
|
|
202
|
-
): string {
|
|
197
|
+
export function createPreAdmissionArtifactSpillPreview(fullText: string, artifactId: string, digest: string): string {
|
|
203
198
|
const totalBytes = Buffer.byteLength(fullText, "utf-8");
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
`\n\n[${totalBytes} bytes omitted; sha256:${digest}; ${referenceKind}: artifact://${artifactId}]\n\n`,
|
|
211
|
-
"utf-8",
|
|
212
|
-
);
|
|
213
|
-
const contentBudget = Math.max(0, maxInlineBytes - receiptReserve);
|
|
214
|
-
headBudget = Math.min(headBudget, Math.ceil(contentBudget / 2));
|
|
215
|
-
tailBudget = Math.min(tailBudget, Math.floor(contentBudget / 2));
|
|
216
|
-
}
|
|
217
|
-
for (;;) {
|
|
218
|
-
const head = utf8Prefix(fullText, headBudget);
|
|
219
|
-
const tail = utf8Suffix(fullText, tailBudget);
|
|
220
|
-
const retainedBytes = Buffer.byteLength(head, "utf-8") + Buffer.byteLength(tail, "utf-8");
|
|
221
|
-
const omittedBytes = Math.max(0, totalBytes - retainedBytes);
|
|
222
|
-
const receipt = `[${omittedBytes} bytes omitted; sha256:${digest}; ${referenceKind}: artifact://${artifactId}]`;
|
|
223
|
-
const preview = `${head}\n\n${receipt}\n\n${tail}`;
|
|
224
|
-
if (maxInlineBytes === undefined || Buffer.byteLength(preview, "utf-8") <= maxInlineBytes) return preview;
|
|
225
|
-
if (headBudget === 0 && tailBudget === 0) return utf8Prefix(receipt, maxInlineBytes);
|
|
226
|
-
const overflow = Buffer.byteLength(preview, "utf-8") - maxInlineBytes;
|
|
227
|
-
if (tailBudget >= headBudget) tailBudget = Math.max(0, tailBudget - overflow);
|
|
228
|
-
else headBudget = Math.max(0, headBudget - overflow);
|
|
229
|
-
}
|
|
199
|
+
const head = utf8Prefix(fullText, PRE_ADMISSION_ARTIFACT_SPILL_HEAD_BYTES);
|
|
200
|
+
const tail = utf8Suffix(fullText, PRE_ADMISSION_ARTIFACT_SPILL_TAIL_BYTES);
|
|
201
|
+
const retainedBytes = Buffer.byteLength(head, "utf-8") + Buffer.byteLength(tail, "utf-8");
|
|
202
|
+
const omittedBytes = Math.max(0, totalBytes - retainedBytes);
|
|
203
|
+
const receipt = `[${omittedBytes} bytes omitted; sha256:${digest}; full output: artifact://${artifactId}]`;
|
|
204
|
+
return `${head}\n\n${receipt}\n\n${tail}`;
|
|
230
205
|
}
|
|
231
206
|
|
|
232
207
|
/**
|
|
@@ -37,7 +37,7 @@ import { assertSafePathComponent } from "../gjc-runtime/session-layout";
|
|
|
37
37
|
import { writeTextAtomic } from "../gjc-runtime/state-writer";
|
|
38
38
|
import type { ManagedLegacyLocalMigrationSource } from "../internal-urls/local-protocol";
|
|
39
39
|
import * as git from "../utils/git";
|
|
40
|
-
import { ArtifactManager
|
|
40
|
+
import { ArtifactManager } from "./artifacts";
|
|
41
41
|
import {
|
|
42
42
|
type BlobPutResult,
|
|
43
43
|
BlobStore,
|
|
@@ -83,8 +83,8 @@ import {
|
|
|
83
83
|
mayCleanManagedTreeStaging,
|
|
84
84
|
retainManagedDirectoryAuthority,
|
|
85
85
|
} from "./internal/managed-session-storage";
|
|
86
|
-
|
|
87
86
|
import { classifyNativePublishOutcome, formatNativePublishDiagnostic } from "./internal/native-publish-outcome";
|
|
87
|
+
import { SessionMigrationBusyError } from "./internal/session-open-errors";
|
|
88
88
|
import {
|
|
89
89
|
hasOnlyKeys as hasOnlyMemoryGuardKeys,
|
|
90
90
|
isMemoryGuardDecimalString,
|
|
@@ -940,6 +940,8 @@ export class SessionMigrationPolicyError extends Error {
|
|
|
940
940
|
}
|
|
941
941
|
|
|
942
942
|
export class SessionArtifactCapacityError extends Error {
|
|
943
|
+
readonly code = "artifact_capacity_exceeded";
|
|
944
|
+
|
|
943
945
|
constructor(message: string) {
|
|
944
946
|
super(message);
|
|
945
947
|
this.name = "SessionArtifactCapacityError";
|
|
@@ -954,7 +956,12 @@ export interface StrictSessionOpenSuccess {
|
|
|
954
956
|
|
|
955
957
|
export interface StrictSessionOpenFailure {
|
|
956
958
|
kind: "error";
|
|
957
|
-
reason:
|
|
959
|
+
reason:
|
|
960
|
+
| ResumeTailError["reason"]
|
|
961
|
+
| "identity-mismatch"
|
|
962
|
+
| "migration-required"
|
|
963
|
+
| "artifact_capacity_exceeded"
|
|
964
|
+
| "migration_busy";
|
|
958
965
|
message?: string;
|
|
959
966
|
}
|
|
960
967
|
|
|
@@ -1133,7 +1140,7 @@ export function createReadonlySessionManager(manager: SessionManager): ReadonlyS
|
|
|
1133
1140
|
|
|
1134
1141
|
/** Internal artifact-writing capability. Read-only facades expose it only through a private weak-map lookup. */
|
|
1135
1142
|
export type SessionArtifactCapability = Readonly<
|
|
1136
|
-
Pick<SessionManager, "allocateArtifactPath" | "saveArtifact" | "
|
|
1143
|
+
Pick<SessionManager, "allocateArtifactPath" | "saveArtifact" | "putBlob">
|
|
1137
1144
|
>;
|
|
1138
1145
|
|
|
1139
1146
|
const sessionArtifactCapabilities = new WeakMap<SessionManager, SessionArtifactCapability>();
|
|
@@ -1150,7 +1157,6 @@ export function sessionArtifactCapability(value: unknown): SessionArtifactCapabi
|
|
|
1150
1157
|
capability = Object.freeze({
|
|
1151
1158
|
allocateArtifactPath: value.allocateArtifactPath.bind(value),
|
|
1152
1159
|
saveArtifact: value.saveArtifact.bind(value),
|
|
1153
|
-
getArtifactPath: value.getArtifactPath.bind(value),
|
|
1154
1160
|
putBlob: value.putBlob.bind(value),
|
|
1155
1161
|
});
|
|
1156
1162
|
sessionArtifactCapabilities.set(value, capability);
|
|
@@ -4708,6 +4714,7 @@ export const MATERIALIZED_CACHE_MAX_BYTES = 32 * 1024 * 1024;
|
|
|
4708
4714
|
export const SessionManagerTestHooks: {
|
|
4709
4715
|
materializedCacheMaxBytesOverride?: number;
|
|
4710
4716
|
beforeResidentTransitionIndexBuild?: () => void;
|
|
4717
|
+
afterForkSnapshot?: () => void | Promise<void>;
|
|
4711
4718
|
} = {};
|
|
4712
4719
|
|
|
4713
4720
|
function materializedCacheMaxBytes(): number {
|
|
@@ -4772,6 +4779,10 @@ export class SessionManager {
|
|
|
4772
4779
|
// Subagents adopt the parent's manager so artifact IDs are unique across the
|
|
4773
4780
|
// whole agent tree and all files land in the parent's artifacts dir.
|
|
4774
4781
|
#adoptedArtifactManager: ArtifactManager | null = null;
|
|
4782
|
+
// In-memory artifact fallback for non-persistent sessions (persist=false).
|
|
4783
|
+
// Keyed by sequential numeric ID string; mirrors the file-based ArtifactManager ID scheme.
|
|
4784
|
+
#inMemoryArtifacts: Map<string, string> | null = null;
|
|
4785
|
+
#inMemoryArtifactCounter = 0;
|
|
4775
4786
|
readonly #blobStore: BlobStore;
|
|
4776
4787
|
#residentTextBlobStore: BlobStore = new MemoryBlobStore();
|
|
4777
4788
|
#residentImageBlobStore: BlobStore;
|
|
@@ -5365,6 +5376,8 @@ export class SessionManager {
|
|
|
5365
5376
|
this.#flushed = false;
|
|
5366
5377
|
this.#needsFullRewriteOnNextPersist = false;
|
|
5367
5378
|
this.#ensuredOnDisk = false;
|
|
5379
|
+
this.#inMemoryArtifacts = null;
|
|
5380
|
+
this.#inMemoryArtifactCounter = 0;
|
|
5368
5381
|
this.#artifactManager = null;
|
|
5369
5382
|
this.#artifactManagerSessionFile = null;
|
|
5370
5383
|
this.#adoptedArtifactManager = null;
|
|
@@ -5583,6 +5596,8 @@ export class SessionManager {
|
|
|
5583
5596
|
flushed: this.#flushed,
|
|
5584
5597
|
needsFullRewriteOnNextPersist: this.#needsFullRewriteOnNextPersist,
|
|
5585
5598
|
ensuredOnDisk: this.#ensuredOnDisk,
|
|
5599
|
+
inMemoryArtifacts: this.#inMemoryArtifacts,
|
|
5600
|
+
inMemoryArtifactCounter: this.#inMemoryArtifactCounter,
|
|
5586
5601
|
artifactManager: this.#artifactManager,
|
|
5587
5602
|
artifactManagerSessionFile: this.#artifactManagerSessionFile,
|
|
5588
5603
|
adoptedArtifactManager: this.#adoptedArtifactManager,
|
|
@@ -5605,6 +5620,8 @@ export class SessionManager {
|
|
|
5605
5620
|
this.#flushed = previous.flushed;
|
|
5606
5621
|
this.#needsFullRewriteOnNextPersist = previous.needsFullRewriteOnNextPersist;
|
|
5607
5622
|
this.#ensuredOnDisk = previous.ensuredOnDisk;
|
|
5623
|
+
this.#inMemoryArtifacts = previous.inMemoryArtifacts;
|
|
5624
|
+
this.#inMemoryArtifactCounter = previous.inMemoryArtifactCounter;
|
|
5608
5625
|
this.#artifactManager = previous.artifactManager;
|
|
5609
5626
|
this.#artifactManagerSessionFile = previous.artifactManagerSessionFile;
|
|
5610
5627
|
this.#adoptedArtifactManager = previous.adoptedArtifactManager;
|
|
@@ -6050,6 +6067,8 @@ export class SessionManager {
|
|
|
6050
6067
|
this.#flushed = stage.flushed;
|
|
6051
6068
|
this.#needsFullRewriteOnNextPersist = false;
|
|
6052
6069
|
this.#ensuredOnDisk = stage.flushed;
|
|
6070
|
+
this.#inMemoryArtifacts = null;
|
|
6071
|
+
this.#inMemoryArtifactCounter = 0;
|
|
6053
6072
|
this.#artifactManager = null;
|
|
6054
6073
|
this.#artifactManagerSessionFile = null;
|
|
6055
6074
|
this.#adoptedArtifactManager = null;
|
|
@@ -6154,6 +6173,7 @@ export class SessionManager {
|
|
|
6154
6173
|
const candidate = listing.owned.find(candidate => path.resolve(candidate.path) === requestedPath);
|
|
6155
6174
|
if (!candidate) throw new Error("Managed session deletion requires exact logical authorization.");
|
|
6156
6175
|
const deleted = await deleteManagedSessionCandidate(resolved.scope, candidate);
|
|
6176
|
+
if (deleted.kind === "error" && deleted.code === "migration_busy") throw new SessionMigrationBusyError();
|
|
6157
6177
|
if (deleted.kind !== "deleted" && deleted.kind !== "already_deleted")
|
|
6158
6178
|
throw new Error(`Could not delete managed session: ${deleted.message}`);
|
|
6159
6179
|
} else {
|
|
@@ -6237,6 +6257,8 @@ export class SessionManager {
|
|
|
6237
6257
|
newHeader,
|
|
6238
6258
|
...materializedEntries.filter((entry): entry is SessionEntry => entry.type !== "session"),
|
|
6239
6259
|
];
|
|
6260
|
+
const forkSnapshotHook = SessionManagerTestHooks.afterForkSnapshot;
|
|
6261
|
+
if (forkSnapshotHook) await forkSnapshotHook();
|
|
6240
6262
|
|
|
6241
6263
|
await this.#closePersistWriter();
|
|
6242
6264
|
this.#persistChain = Promise.resolve();
|
|
@@ -7979,19 +8001,17 @@ export class SessionManager {
|
|
|
7979
8001
|
}
|
|
7980
8002
|
|
|
7981
8003
|
/**
|
|
7982
|
-
* Save artifact content under the current
|
|
7983
|
-
*
|
|
8004
|
+
* Save artifact content under the current session and return artifact ID.
|
|
8005
|
+
* Returns an artifact ID for all sessions (file-backed for persistent, in-memory fallback otherwise).
|
|
7984
8006
|
*/
|
|
7985
8007
|
async saveArtifact(content: string, toolType: string): Promise<string | undefined> {
|
|
7986
8008
|
const manager = this.#getOrCreateArtifactManager();
|
|
7987
8009
|
if (manager) return manager.save(content, toolType);
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
if (!manager) return undefined;
|
|
7994
|
-
return manager.saveWithReceipt(content, toolType);
|
|
8010
|
+
// Non-persistent session: store in memory so spill truncation can proceed.
|
|
8011
|
+
if (!this.#inMemoryArtifacts) this.#inMemoryArtifacts = new Map();
|
|
8012
|
+
const id = String(this.#inMemoryArtifactCounter++);
|
|
8013
|
+
this.#inMemoryArtifacts.set(id, content);
|
|
8014
|
+
return id;
|
|
7995
8015
|
}
|
|
7996
8016
|
|
|
7997
8017
|
/**
|
|
@@ -9594,6 +9614,7 @@ export class SessionManager {
|
|
|
9594
9614
|
authority,
|
|
9595
9615
|
);
|
|
9596
9616
|
} catch (error) {
|
|
9617
|
+
if (error instanceof Error && error.message === "migration_busy") throw new SessionMigrationBusyError();
|
|
9597
9618
|
if (error instanceof Error && error.message.startsWith("Managed root authority changed"))
|
|
9598
9619
|
managedDestinationStore.assertBound();
|
|
9599
9620
|
throw error;
|
|
@@ -9602,6 +9623,7 @@ export class SessionManager {
|
|
|
9602
9623
|
managedDestinationStore.assertBound();
|
|
9603
9624
|
if (opened.code === "legacy_migration_disabled") throw new SessionMigrationPolicyError();
|
|
9604
9625
|
if (opened.code === "artifact_capacity_exceeded") throw new SessionArtifactCapacityError(opened.message);
|
|
9626
|
+
if (opened.code === "migration_busy") throw new SessionMigrationBusyError();
|
|
9605
9627
|
throw new Error(`Could not open managed session: ${opened.message}`);
|
|
9606
9628
|
}
|
|
9607
9629
|
assertManagedDestinationBound();
|
|
@@ -9697,7 +9719,15 @@ export class SessionManager {
|
|
|
9697
9719
|
throw new Error(`Could not open session: ${inspected.reason}`);
|
|
9698
9720
|
}
|
|
9699
9721
|
const opened = await SessionManager.openExistingStrict(inspected.identity, destination, storage, migrationPolicy);
|
|
9700
|
-
if (opened.kind === "error")
|
|
9722
|
+
if (opened.kind === "error") {
|
|
9723
|
+
if (opened.reason === "legacy_migration_disabled") throw new SessionMigrationPolicyError();
|
|
9724
|
+
if (opened.reason === "artifact_capacity_exceeded")
|
|
9725
|
+
throw new SessionArtifactCapacityError(
|
|
9726
|
+
opened.message ?? "Session artifacts exceed the migration capacity.",
|
|
9727
|
+
);
|
|
9728
|
+
if (opened.reason === "migration_busy") throw new SessionMigrationBusyError();
|
|
9729
|
+
throw new Error(`Could not open session: ${opened.reason}`);
|
|
9730
|
+
}
|
|
9701
9731
|
return opened.manager;
|
|
9702
9732
|
}
|
|
9703
9733
|
|
|
@@ -9835,6 +9865,7 @@ export class SessionManager {
|
|
|
9835
9865
|
const candidate = listing.owned.find(item => path.resolve(item.path) === path.resolve(sessionPath));
|
|
9836
9866
|
if (!candidate) throw new Error("Session is not an authorized managed candidate.");
|
|
9837
9867
|
const deleted = await deleteManagedSessionCandidate(resolved.scope, candidate);
|
|
9868
|
+
if (deleted.kind === "error" && deleted.code === "migration_busy") throw new SessionMigrationBusyError();
|
|
9838
9869
|
if (deleted.kind !== "deleted" && deleted.kind !== "already_deleted")
|
|
9839
9870
|
throw new Error(`Could not delete managed session: ${deleted.message}`);
|
|
9840
9871
|
}
|
|
@@ -10380,6 +10411,7 @@ export class SessionManager {
|
|
|
10380
10411
|
return { kind: "error", reason: "legacy_migration_disabled" };
|
|
10381
10412
|
if (error instanceof SessionArtifactCapacityError)
|
|
10382
10413
|
return { kind: "error", reason: "artifact_capacity_exceeded", message: error.message };
|
|
10414
|
+
if (error instanceof SessionMigrationBusyError) return { kind: "error", reason: "migration_busy" };
|
|
10383
10415
|
if (
|
|
10384
10416
|
error instanceof Error &&
|
|
10385
10417
|
(error.message.includes("source_changed") || error.message.includes("changed before migration"))
|
|
@@ -10442,6 +10474,7 @@ export class SessionManager {
|
|
|
10442
10474
|
throw new SessionArtifactCapacityError(
|
|
10443
10475
|
opened.message ?? "Session artifacts exceed the migration capacity.",
|
|
10444
10476
|
);
|
|
10477
|
+
if (opened.reason === "migration_busy") throw new SessionMigrationBusyError();
|
|
10445
10478
|
return undefined;
|
|
10446
10479
|
}
|
|
10447
10480
|
return opened.manager;
|
|
@@ -312,6 +312,7 @@ export type VerifiedSessionDeleteResult =
|
|
|
312
312
|
artifactsIdentity: SessionStorageFileIdentity | undefined;
|
|
313
313
|
/** Identity-bound quarantine path retained when recursive cleanup failed. */
|
|
314
314
|
detachedArtifactsPath: string;
|
|
315
|
+
artifactsPayloadDurable?: true;
|
|
315
316
|
/** Native snapshot required for an identity-bound recursive retry. */
|
|
316
317
|
artifactsTree: NativeDirectoryTreeSnapshot;
|
|
317
318
|
/** Transcript identity (unchanged) for retry binding. */
|
|
@@ -328,6 +329,7 @@ export type VerifiedSessionDeleteResult =
|
|
|
328
329
|
transcriptIdentity: SessionStorageFileIdentity;
|
|
329
330
|
/** Optional identity-bound transcript quarantine path for restart cleanup. */
|
|
330
331
|
detachedTranscriptPath?: string;
|
|
332
|
+
transcriptPayloadDurable?: true;
|
|
331
333
|
retainedSuccessorPath?: string;
|
|
332
334
|
retainedPlaceholderPath?: string;
|
|
333
335
|
retainedUnknownPath?: string;
|
|
@@ -423,6 +425,7 @@ type NativeDirectoryTreeApi = {
|
|
|
423
425
|
function nativeDirectoryTreeApi(): NativeDirectoryTreeApi {
|
|
424
426
|
return native as unknown as NativeDirectoryTreeApi;
|
|
425
427
|
}
|
|
428
|
+
|
|
426
429
|
function snapshotDirectoryTree(pathname: string): NativeDirectoryTreeSnapshot {
|
|
427
430
|
const result = nativeDirectoryTreeApi().snapshotDirectoryTree(pathname);
|
|
428
431
|
if (!result.ok || !result.snapshot)
|
|
@@ -438,10 +441,21 @@ function retainedTreeDoesNotExpandAuthority(
|
|
|
438
441
|
): boolean {
|
|
439
442
|
if (expected.rootDev !== retained.rootDev || expected.rootIno !== retained.rootIno) return false;
|
|
440
443
|
const expectedEntries = new Map(expected.entries.map(entry => [entry.relativePath, entry]));
|
|
444
|
+
if (retained.entries.length > expected.entries.length) return false;
|
|
441
445
|
return retained.entries.every(entry => {
|
|
442
446
|
if (entry.relativePath === "") return entry.kind === "directory";
|
|
443
447
|
const authorized = expectedEntries.get(entry.relativePath);
|
|
444
|
-
|
|
448
|
+
if (
|
|
449
|
+
authorized === undefined ||
|
|
450
|
+
authorized.kind !== entry.kind ||
|
|
451
|
+
authorized.dev !== entry.dev ||
|
|
452
|
+
authorized.ino !== entry.ino ||
|
|
453
|
+
authorized.nlink !== entry.nlink
|
|
454
|
+
)
|
|
455
|
+
return false;
|
|
456
|
+
if (entry.kind !== "file") return entry.size === authorized.size;
|
|
457
|
+
const scrubbed = entry.size === "0" && entry.sha256 === createHash("sha256").update("").digest("hex");
|
|
458
|
+
return scrubbed || (entry.size === authorized.size && entry.sha256 === authorized.sha256);
|
|
445
459
|
});
|
|
446
460
|
}
|
|
447
461
|
|
|
@@ -938,7 +952,7 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
938
952
|
"Transcript parent identity does not match authorization",
|
|
939
953
|
);
|
|
940
954
|
const authorizedTranscriptParentIdentity = transcriptParentIdentity ?? parentIdentity;
|
|
941
|
-
if (detachedArtifactsPath) {
|
|
955
|
+
if (detachedArtifactsPath && !artifactsRemoved) {
|
|
942
956
|
if (
|
|
943
957
|
!expectedArtifactsIdentity ||
|
|
944
958
|
path.dirname(detachedArtifactsPath) !== path.dirname(transcriptPath) ||
|
|
@@ -997,6 +1011,9 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
997
1011
|
artifactsIdentity: expectedArtifactsIdentity,
|
|
998
1012
|
detachedArtifactsPath: retainedRoot,
|
|
999
1013
|
artifactsTree: retainedTree,
|
|
1014
|
+
...((removal as typeof removal & { payloadDurable?: boolean }).payloadDurable === true
|
|
1015
|
+
? { artifactsPayloadDurable: true as const }
|
|
1016
|
+
: {}),
|
|
1000
1017
|
...((removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath)
|
|
1001
1018
|
? { retainedSuccessorPath: removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath }
|
|
1002
1019
|
: {}),
|
|
@@ -1045,6 +1062,24 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1045
1062
|
"Artifact path reappeared after durable artifact-phase completion",
|
|
1046
1063
|
);
|
|
1047
1064
|
}
|
|
1065
|
+
if (artifactsRemoved && detachedArtifactsPath && expectedArtifactsIdentity && expectedArtifactsTree) {
|
|
1066
|
+
const retainedIdentity = this.#optionalDirectoryIdentity(detachedArtifactsPath);
|
|
1067
|
+
if (
|
|
1068
|
+
!retainedIdentity ||
|
|
1069
|
+
retainedIdentity.dev !== expectedArtifactsIdentity.dev ||
|
|
1070
|
+
retainedIdentity.ino !== expectedArtifactsIdentity.ino
|
|
1071
|
+
)
|
|
1072
|
+
throw new SessionDeleteVerificationError(
|
|
1073
|
+
"artifacts",
|
|
1074
|
+
"Retained artifact root identity changed before transcript cleanup",
|
|
1075
|
+
);
|
|
1076
|
+
const retainedTree = snapshotDirectoryTree(detachedArtifactsPath);
|
|
1077
|
+
if (!retainedTreeDoesNotExpandAuthority(expectedArtifactsTree, retainedTree))
|
|
1078
|
+
throw new SessionDeleteVerificationError(
|
|
1079
|
+
"artifacts",
|
|
1080
|
+
"Partial artifact cleanup expanded retained tree authority",
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1048
1083
|
if (!artifactsIdentity && expectedArtifactsIdentity && !detachedArtifactsPath && !artifactsRemoved) {
|
|
1049
1084
|
// Absence at the original path alone is not completion: native recursive removal
|
|
1050
1085
|
// may retain the planned root or its deterministic `.removing` final-stage root.
|
|
@@ -1116,12 +1151,22 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1116
1151
|
throw new Error("parent_changed");
|
|
1117
1152
|
fs.fsyncSync(descriptor);
|
|
1118
1153
|
} catch (error) {
|
|
1119
|
-
|
|
1154
|
+
return {
|
|
1155
|
+
kind: "cleanup_pending",
|
|
1156
|
+
phase: "artifacts",
|
|
1157
|
+
error: new SessionDeleteVerificationError("artifacts", "durability_failed", {
|
|
1158
|
+
cause: toError(error),
|
|
1159
|
+
}),
|
|
1160
|
+
artifactsIdentity,
|
|
1161
|
+
detachedArtifactsPath: detach.detachedPath,
|
|
1162
|
+
artifactsTree,
|
|
1163
|
+
transcriptIdentity,
|
|
1164
|
+
};
|
|
1120
1165
|
} finally {
|
|
1121
1166
|
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
1122
1167
|
}
|
|
1123
1168
|
}
|
|
1124
|
-
if (!detach.ok) {
|
|
1169
|
+
if (!detach.ok && process.platform === "win32") {
|
|
1125
1170
|
return {
|
|
1126
1171
|
kind: "cleanup_pending",
|
|
1127
1172
|
phase: "artifacts",
|
|
@@ -1132,7 +1177,6 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1132
1177
|
...(detach.retainedSuccessorPath ? { retainedSuccessorPath: detach.retainedSuccessorPath } : {}),
|
|
1133
1178
|
...(detach.retainedPlaceholderPath ? { retainedPlaceholderPath: detach.retainedPlaceholderPath } : {}),
|
|
1134
1179
|
...(detach.retainedUnknownPath ? { retainedUnknownPath: detach.retainedUnknownPath } : {}),
|
|
1135
|
-
|
|
1136
1180
|
transcriptIdentity,
|
|
1137
1181
|
};
|
|
1138
1182
|
}
|
|
@@ -1173,6 +1217,9 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1173
1217
|
artifactsIdentity,
|
|
1174
1218
|
detachedArtifactsPath: retainedRoot,
|
|
1175
1219
|
artifactsTree: retainedTree,
|
|
1220
|
+
...((removal as typeof removal & { payloadDurable?: boolean }).payloadDurable === true
|
|
1221
|
+
? { artifactsPayloadDurable: true as const }
|
|
1222
|
+
: {}),
|
|
1176
1223
|
...((removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath)
|
|
1177
1224
|
? { retainedSuccessorPath: removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath }
|
|
1178
1225
|
: {}),
|
|
@@ -1223,6 +1270,13 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1223
1270
|
quarantineName: path.basename(plannedTranscriptPath),
|
|
1224
1271
|
});
|
|
1225
1272
|
if (!deletion.ok) {
|
|
1273
|
+
if (
|
|
1274
|
+
deletion.code === "cleanup_pending" &&
|
|
1275
|
+
(deletion as typeof deletion & { payloadDurable?: boolean }).payloadDurable === true &&
|
|
1276
|
+
deletion.retainedSuccessorPath === undefined &&
|
|
1277
|
+
deletion.retainedUnknownPath === undefined
|
|
1278
|
+
)
|
|
1279
|
+
return { kind: "deleted" };
|
|
1226
1280
|
const error = exactUnlinkFailure(deletion);
|
|
1227
1281
|
const retainedAuthority =
|
|
1228
1282
|
deletion.detachedPath ||
|
|
@@ -1234,6 +1288,9 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1234
1288
|
kind: "cleanup_pending",
|
|
1235
1289
|
phase: "transcript",
|
|
1236
1290
|
error,
|
|
1291
|
+
...((deletion as typeof deletion & { payloadDurable?: boolean }).payloadDurable === true
|
|
1292
|
+
? { transcriptPayloadDurable: true as const }
|
|
1293
|
+
: {}),
|
|
1237
1294
|
transcriptIdentity,
|
|
1238
1295
|
detachedTranscriptPath: deletion.detachedPath ?? detachedTranscriptPath,
|
|
1239
1296
|
...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
|
|
@@ -1288,6 +1345,13 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1288
1345
|
quarantineName: path.basename(plannedTranscriptPath),
|
|
1289
1346
|
});
|
|
1290
1347
|
if (!deletion.ok) {
|
|
1348
|
+
if (
|
|
1349
|
+
deletion.code === "cleanup_pending" &&
|
|
1350
|
+
(deletion as typeof deletion & { payloadDurable?: boolean }).payloadDurable === true &&
|
|
1351
|
+
deletion.retainedSuccessorPath === undefined &&
|
|
1352
|
+
deletion.retainedUnknownPath === undefined
|
|
1353
|
+
)
|
|
1354
|
+
return { kind: "deleted" };
|
|
1291
1355
|
const error = exactUnlinkFailure(deletion);
|
|
1292
1356
|
const retainedAuthority =
|
|
1293
1357
|
deletion.detachedPath ||
|
|
@@ -1299,6 +1363,9 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1299
1363
|
kind: "cleanup_pending",
|
|
1300
1364
|
phase: "transcript",
|
|
1301
1365
|
error,
|
|
1366
|
+
...((deletion as typeof deletion & { payloadDurable?: boolean }).payloadDurable === true
|
|
1367
|
+
? { transcriptPayloadDurable: true as const }
|
|
1368
|
+
: {}),
|
|
1302
1369
|
transcriptIdentity,
|
|
1303
1370
|
detachedTranscriptPath: deletion.detachedPath,
|
|
1304
1371
|
...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
|