@gajae-code/coding-agent 0.12.10 → 0.12.12
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 +47 -2
- package/dist/types/cli/notify-cli.d.ts +42 -1
- package/dist/types/cli/update-cli.d.ts +9 -3
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +3 -1
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/ouroboros-ooo-bridge.d.ts +13 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -1
- package/dist/types/extensibility/extensions/types.d.ts +2 -0
- package/dist/types/main.d.ts +6 -2
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/utils/host-status.d.ts +23 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +12 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -0
- package/dist/types/sdk/bus/chat-daemon-command-channel.d.ts +244 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +13 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +7 -0
- package/dist/types/sdk/bus/control-plane-frames.d.ts +43 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +18 -0
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +71 -0
- package/dist/types/sdk/bus/notification-service.d.ts +10 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +27 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +11 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +14 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +140 -0
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +25 -7
- package/dist/types/sdk/bus/telegram-daemon.d.ts +104 -16
- package/dist/types/sdk/bus/telegram-reference.d.ts +10 -1
- package/dist/types/sdk/bus/topic-registry.d.ts +130 -85
- package/dist/types/sdk/host/host.d.ts +52 -0
- package/dist/types/sdk/lifecycle-session.d.ts +6 -0
- package/dist/types/sdk/session-activation.d.ts +41 -0
- package/dist/types/sdk/session.d.ts +18 -0
- package/dist/types/sdk/startup-capability.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +7 -0
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +12 -2
- package/dist/types/session/session-manager.d.ts +40 -5
- package/dist/types/session/session-storage.d.ts +1 -0
- package/dist/types/tools/computer.d.ts +35 -2
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/todo-write.d.ts +2 -0
- package/dist/types/utils/npm-registry.d.ts +71 -0
- package/examples/extensions/README.md +38 -4
- package/examples/extensions/ooo-bridge.ts +15 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/src/async/job-manager.ts +9 -2
- package/src/cli/notify-cli.ts +226 -2
- package/src/cli/update-cli.ts +90 -23
- package/src/commands/notify.ts +27 -3
- package/src/commands/sdk.ts +1 -0
- package/src/config/file-lock-gc.ts +18 -0
- package/src/config/file-lock.ts +61 -16
- package/src/config/model-profiles.ts +31 -15
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +2 -0
- package/src/coordinator-mcp/server.ts +358 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -0
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +235 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +4 -9
- package/src/extensibility/extensions/runner.ts +15 -2
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/gjc-runtime/launch-tmux.ts +53 -1
- package/src/internal-urls/docs-index.generated.ts +13 -12
- package/src/main.ts +203 -67
- package/src/modes/DESIGN.md +8 -5
- package/src/modes/components/footer.ts +10 -18
- package/src/modes/components/session-selector.ts +26 -3
- package/src/modes/components/status-line/gh.ts +23 -2
- package/src/modes/controllers/event-controller.ts +3 -0
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +11 -0
- package/src/modes/controllers/selector-controller.ts +8 -2
- package/src/modes/interactive-mode.ts +7 -1
- package/src/modes/utils/host-status.ts +38 -0
- package/src/prompts/tools/computer.md +6 -1
- package/src/runtime-mcp/types.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +60 -3
- package/src/sdk/bus/chat-daemon-cli.ts +42 -0
- package/src/sdk/bus/chat-daemon-command-channel.ts +921 -0
- package/src/sdk/bus/chat-daemon-control.ts +18 -3
- package/src/sdk/bus/chat-daemon-runtime.ts +257 -21
- package/src/sdk/bus/control-plane-frames.ts +51 -0
- package/src/sdk/bus/conversation-store.ts +29 -1
- package/src/sdk/bus/existing-thread-readiness.ts +155 -0
- package/src/sdk/bus/index.ts +70 -0
- package/src/sdk/bus/notification-service.ts +28 -1
- package/src/sdk/bus/rich-render.ts +3 -2
- package/src/sdk/bus/slack-daemon.ts +99 -4
- package/src/sdk/bus/slack-live-provider.ts +32 -2
- package/src/sdk/bus/slack-provider.ts +11 -0
- package/src/sdk/bus/slack-thread-binding.ts +641 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +11 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +25 -7
- package/src/sdk/bus/telegram-daemon.ts +1961 -497
- package/src/sdk/bus/telegram-reference.ts +42 -6
- package/src/sdk/bus/topic-registry.ts +674 -181
- package/src/sdk/host/host.ts +180 -1
- package/src/sdk/lifecycle-session.ts +8 -2
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session-activation.ts +127 -0
- package/src/sdk/session.ts +139 -28
- package/src/sdk/startup-capability.ts +10 -1
- package/src/session/agent-session.ts +331 -30
- package/src/session/artifacts.ts +56 -13
- package/src/session/internal/managed-session-storage.ts +440 -243
- package/src/session/session-manager.ts +288 -44
- package/src/session/session-storage.ts +3 -0
- package/src/setup/provider-presets.json +1 -1
- package/src/task/index.ts +92 -44
- package/src/tools/computer.ts +322 -45
- package/src/tools/index.ts +8 -0
- package/src/tools/todo-write.ts +29 -11
- package/src/utils/npm-registry.ts +660 -0
|
@@ -899,6 +899,7 @@ export interface ResumeSessionIdentity {
|
|
|
899
899
|
size: number;
|
|
900
900
|
mtimeMs: number;
|
|
901
901
|
mtimeNs: bigint;
|
|
902
|
+
ctimeNs?: bigint;
|
|
902
903
|
sha256: string;
|
|
903
904
|
}
|
|
904
905
|
|
|
@@ -918,6 +919,21 @@ export interface ResumeTailError {
|
|
|
918
919
|
}
|
|
919
920
|
|
|
920
921
|
export type SessionDirectoryMigrationPolicy = ManagedMigrationPolicy;
|
|
922
|
+
export type SessionAppendPersistenceFailurePhase = "current_append" | "prior_failure";
|
|
923
|
+
|
|
924
|
+
export class SessionAppendPersistenceError extends Error {
|
|
925
|
+
readonly phase: SessionAppendPersistenceFailurePhase;
|
|
926
|
+
readonly entryId: string;
|
|
927
|
+
readonly persistenceError: Error;
|
|
928
|
+
|
|
929
|
+
constructor(phase: SessionAppendPersistenceFailurePhase, entryId: string, persistenceError: Error) {
|
|
930
|
+
super(persistenceError.message, { cause: persistenceError });
|
|
931
|
+
this.name = "SessionAppendPersistenceError";
|
|
932
|
+
this.phase = phase;
|
|
933
|
+
this.entryId = entryId;
|
|
934
|
+
this.persistenceError = persistenceError;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
921
937
|
|
|
922
938
|
export class SessionManagedStorageError extends Error {
|
|
923
939
|
readonly code = "managed_storage_unsupported";
|
|
@@ -2161,6 +2177,7 @@ function sameResumeIdentity(left: ResumeSessionIdentity, right: ResumeSessionIde
|
|
|
2161
2177
|
left.size === right.size &&
|
|
2162
2178
|
left.mtimeMs === right.mtimeMs &&
|
|
2163
2179
|
left.mtimeNs === right.mtimeNs &&
|
|
2180
|
+
(left.ctimeNs === undefined || right.ctimeNs === undefined || left.ctimeNs === right.ctimeNs) &&
|
|
2164
2181
|
left.sha256 === right.sha256
|
|
2165
2182
|
);
|
|
2166
2183
|
}
|
|
@@ -2174,7 +2191,8 @@ function sameResumeStat(left: SessionStorageStat, right: SessionStorageStat): bo
|
|
|
2174
2191
|
left.nlink === right.nlink &&
|
|
2175
2192
|
left.size === right.size &&
|
|
2176
2193
|
left.mtimeMs === right.mtimeMs &&
|
|
2177
|
-
left.mtimeNs === right.mtimeNs
|
|
2194
|
+
left.mtimeNs === right.mtimeNs &&
|
|
2195
|
+
left.ctimeNs === right.ctimeNs
|
|
2178
2196
|
);
|
|
2179
2197
|
}
|
|
2180
2198
|
|
|
@@ -2372,6 +2390,7 @@ function inspectResumeSessionFile(
|
|
|
2372
2390
|
size: snapshot.size,
|
|
2373
2391
|
mtimeMs: snapshot.mtimeMs,
|
|
2374
2392
|
mtimeNs: snapshot.mtimeNs,
|
|
2393
|
+
ctimeNs: snapshot.ctimeNs,
|
|
2375
2394
|
sha256: crypto.createHash("sha256").update(bytes).digest("hex"),
|
|
2376
2395
|
};
|
|
2377
2396
|
return { identity, content: bytes, entries, context, migrationApplied };
|
|
@@ -2380,6 +2399,46 @@ function inspectResumeSessionFile(
|
|
|
2380
2399
|
}
|
|
2381
2400
|
}
|
|
2382
2401
|
|
|
2402
|
+
function fsyncResumeSessionIdentity(expected: ResumeSessionIdentity): void {
|
|
2403
|
+
const fd = fs.openSync(expected.canonicalPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
2404
|
+
try {
|
|
2405
|
+
const before = fs.fstatSync(fd, { bigint: true });
|
|
2406
|
+
if (
|
|
2407
|
+
before.dev !== expected.dev ||
|
|
2408
|
+
before.ino !== expected.ino ||
|
|
2409
|
+
(expected.nlink !== undefined && before.nlink !== expected.nlink) ||
|
|
2410
|
+
Number(before.size) !== expected.size ||
|
|
2411
|
+
before.mtimeNs !== expected.mtimeNs ||
|
|
2412
|
+
(expected.ctimeNs !== undefined && before.ctimeNs !== expected.ctimeNs)
|
|
2413
|
+
) {
|
|
2414
|
+
throw new Error("session_persistence_recovery_identity_mismatch");
|
|
2415
|
+
}
|
|
2416
|
+
fs.fsyncSync(fd);
|
|
2417
|
+
const after = fs.fstatSync(fd, { bigint: true });
|
|
2418
|
+
const named = fs.lstatSync(expected.canonicalPath, { bigint: true });
|
|
2419
|
+
if (
|
|
2420
|
+
after.dev !== before.dev ||
|
|
2421
|
+
after.ino !== before.ino ||
|
|
2422
|
+
after.nlink !== before.nlink ||
|
|
2423
|
+
after.size !== before.size ||
|
|
2424
|
+
after.mtimeNs !== before.mtimeNs ||
|
|
2425
|
+
after.ctimeNs !== before.ctimeNs ||
|
|
2426
|
+
named.dev !== after.dev ||
|
|
2427
|
+
named.ino !== after.ino ||
|
|
2428
|
+
named.nlink !== after.nlink
|
|
2429
|
+
) {
|
|
2430
|
+
throw new Error("session_persistence_recovery_identity_mismatch");
|
|
2431
|
+
}
|
|
2432
|
+
} finally {
|
|
2433
|
+
fs.closeSync(fd);
|
|
2434
|
+
}
|
|
2435
|
+
const parentFd = fs.openSync(path.dirname(expected.canonicalPath), fs.constants.O_RDONLY | fs.constants.O_DIRECTORY);
|
|
2436
|
+
try {
|
|
2437
|
+
fs.fsyncSync(parentFd);
|
|
2438
|
+
} finally {
|
|
2439
|
+
fs.closeSync(parentFd);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2383
2442
|
/** Revalidate previously parsed authority without another decode, parse, migration, or context build. */
|
|
2384
2443
|
function revalidateResumeSessionIdentity(
|
|
2385
2444
|
filePath: string,
|
|
@@ -2410,6 +2469,7 @@ function revalidateResumeSessionIdentity(
|
|
|
2410
2469
|
size: snapshot.stat.size,
|
|
2411
2470
|
mtimeMs: snapshot.stat.mtimeMs,
|
|
2412
2471
|
mtimeNs: snapshot.stat.mtimeNs,
|
|
2472
|
+
ctimeNs: snapshot.stat.ctimeNs,
|
|
2413
2473
|
sha256: crypto.createHash("sha256").update(snapshot.bytes).digest("hex"),
|
|
2414
2474
|
};
|
|
2415
2475
|
return sameResumeIdentity(expected, observed) ? { kind: "valid" } : { kind: "error", reason: "identity-mismatch" };
|
|
@@ -4705,6 +4765,7 @@ interface SessionManagerStateSnapshot {
|
|
|
4705
4765
|
needsFullRewriteOnNextPersist: boolean;
|
|
4706
4766
|
fileEntries: FileEntry[];
|
|
4707
4767
|
materializedFileEntries: FileEntry[];
|
|
4768
|
+
adoptedArtifactManager: ArtifactManager | null;
|
|
4708
4769
|
}
|
|
4709
4770
|
|
|
4710
4771
|
/** Benchmark-derived cap for strong materialized session snapshots. */
|
|
@@ -4715,6 +4776,7 @@ export const SessionManagerTestHooks: {
|
|
|
4715
4776
|
materializedCacheMaxBytesOverride?: number;
|
|
4716
4777
|
beforeResidentTransitionIndexBuild?: () => void;
|
|
4717
4778
|
afterForkSnapshot?: () => void | Promise<void>;
|
|
4779
|
+
beforeEphemeralArtifactManagerInstall?: (dir: string) => void | Promise<void>;
|
|
4718
4780
|
} = {};
|
|
4719
4781
|
|
|
4720
4782
|
function materializedCacheMaxBytes(): number {
|
|
@@ -4751,7 +4813,9 @@ export class SessionManager {
|
|
|
4751
4813
|
#recoveryPromotionTranscriptPath: string | undefined;
|
|
4752
4814
|
#memoryGuardParticipantIngressToken: symbol | undefined;
|
|
4753
4815
|
#fileEntries: FileEntry[] = [];
|
|
4754
|
-
#pendingStrictAdoption:
|
|
4816
|
+
#pendingStrictAdoption:
|
|
4817
|
+
| { canonicalPath: string; identity: ResumeSessionIdentity; inspection?: ResumeInspectionSnapshot }
|
|
4818
|
+
| undefined;
|
|
4755
4819
|
#byId: Map<string, SessionEntry> = new Map();
|
|
4756
4820
|
#labelsById: Map<string, string> = new Map();
|
|
4757
4821
|
#leafId: string | null = null;
|
|
@@ -4779,10 +4843,13 @@ export class SessionManager {
|
|
|
4779
4843
|
// Subagents adopt the parent's manager so artifact IDs are unique across the
|
|
4780
4844
|
// whole agent tree and all files land in the parent's artifacts dir.
|
|
4781
4845
|
#adoptedArtifactManager: ArtifactManager | null = null;
|
|
4782
|
-
//
|
|
4783
|
-
//
|
|
4784
|
-
|
|
4785
|
-
#
|
|
4846
|
+
// Filesystem-backed artifact fallback for non-persistent sessions (persist=false).
|
|
4847
|
+
// The directory is created lazily on first save so artifact content is read back
|
|
4848
|
+
// from disk on demand instead of being retained in memory for the session lifetime.
|
|
4849
|
+
#ephemeralArtifactManager: ArtifactManager | null = null;
|
|
4850
|
+
#ephemeralArtifactDir: string | null = null;
|
|
4851
|
+
#ephemeralArtifactInit: Promise<ArtifactManager | null> | null = null;
|
|
4852
|
+
#ephemeralArtifactCleanups = new Set<Promise<void>>();
|
|
4786
4853
|
readonly #blobStore: BlobStore;
|
|
4787
4854
|
#residentTextBlobStore: BlobStore = new MemoryBlobStore();
|
|
4788
4855
|
#residentImageBlobStore: BlobStore;
|
|
@@ -5298,6 +5365,7 @@ export class SessionManager {
|
|
|
5298
5365
|
// Rollback snapshots must own resident data before another session reset disposes
|
|
5299
5366
|
// the ephemeral store backing the resident sentinels above.
|
|
5300
5367
|
materializedFileEntries,
|
|
5368
|
+
adoptedArtifactManager: this.#adoptedArtifactManager,
|
|
5301
5369
|
};
|
|
5302
5370
|
}
|
|
5303
5371
|
|
|
@@ -5335,7 +5403,7 @@ export class SessionManager {
|
|
|
5335
5403
|
this.#persistErrorReported = false;
|
|
5336
5404
|
this.#artifactManager = null;
|
|
5337
5405
|
this.#artifactManagerSessionFile = null;
|
|
5338
|
-
this.#adoptedArtifactManager =
|
|
5406
|
+
this.#adoptedArtifactManager = snapshot.adoptedArtifactManager;
|
|
5339
5407
|
this.#commitResidentTextStoreTransition(prepared);
|
|
5340
5408
|
if (this.#sessionFile) writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
|
|
5341
5409
|
}
|
|
@@ -5376,8 +5444,7 @@ export class SessionManager {
|
|
|
5376
5444
|
this.#flushed = false;
|
|
5377
5445
|
this.#needsFullRewriteOnNextPersist = false;
|
|
5378
5446
|
this.#ensuredOnDisk = false;
|
|
5379
|
-
|
|
5380
|
-
this.#inMemoryArtifactCounter = 0;
|
|
5447
|
+
|
|
5381
5448
|
this.#artifactManager = null;
|
|
5382
5449
|
this.#artifactManagerSessionFile = null;
|
|
5383
5450
|
this.#adoptedArtifactManager = null;
|
|
@@ -5409,6 +5476,7 @@ export class SessionManager {
|
|
|
5409
5476
|
const prepared = this.#prepareFreshSessionTransition(fresh, "memory-fallback");
|
|
5410
5477
|
this.#applyFreshSessionMetadata(fresh);
|
|
5411
5478
|
this.#commitResidentTextStoreTransition(prepared);
|
|
5479
|
+
this.#retireEphemeralArtifacts();
|
|
5412
5480
|
writeTerminalBreadcrumb(this.cwd, resolvedSessionFile);
|
|
5413
5481
|
await this.#rewriteFile();
|
|
5414
5482
|
this.#flushed = true;
|
|
@@ -5482,7 +5550,7 @@ export class SessionManager {
|
|
|
5482
5550
|
}
|
|
5483
5551
|
|
|
5484
5552
|
/** Switch to a different session file (used for resume and branching). */
|
|
5485
|
-
async setSessionFile(sessionFile: string): Promise<void> {
|
|
5553
|
+
async setSessionFile(sessionFile: string, options?: { deferEphemeralArtifactRetirement?: boolean }): Promise<void> {
|
|
5486
5554
|
this.#assertRecoveryHydrationWritable();
|
|
5487
5555
|
const resolvedSessionFile = this.storage instanceof FileSessionStorage ? path.resolve(sessionFile) : sessionFile;
|
|
5488
5556
|
const strictAdoption = this.#pendingStrictAdoption;
|
|
@@ -5501,11 +5569,14 @@ export class SessionManager {
|
|
|
5501
5569
|
if (strictAdoption && resolvedSessionFile === strictAdoption.canonicalPath) {
|
|
5502
5570
|
const inspected = inspectResumeSessionFile(resolvedSessionFile, this.storage);
|
|
5503
5571
|
if ("kind" in inspected || !sameResumeIdentity(strictAdoption.identity, inspected.identity))
|
|
5504
|
-
throw new Error("Prepared
|
|
5572
|
+
throw new Error("Prepared session changed before strict adoption.");
|
|
5505
5573
|
}
|
|
5506
5574
|
let entries: FileEntry[];
|
|
5507
5575
|
let candidateMigrationApplied = false;
|
|
5508
|
-
if (
|
|
5576
|
+
if (strictAdoption?.inspection && resolvedSessionFile === strictAdoption.canonicalPath) {
|
|
5577
|
+
entries = strictAdoption.inspection.entries;
|
|
5578
|
+
candidateMigrationApplied = strictAdoption.inspection.migrationApplied;
|
|
5579
|
+
} else if (this.storage.existsSync(resolvedSessionFile)) {
|
|
5509
5580
|
const inspected = inspectResumeSessionFile(resolvedSessionFile, this.storage);
|
|
5510
5581
|
if ("kind" in inspected) throw new Error(`Could not switch session: ${inspected.reason}`);
|
|
5511
5582
|
entries = inspected.entries;
|
|
@@ -5516,7 +5587,7 @@ export class SessionManager {
|
|
|
5516
5587
|
if (strictAdoption) {
|
|
5517
5588
|
const inspected = inspectResumeSessionFile(resolvedSessionFile, this.storage);
|
|
5518
5589
|
if ("kind" in inspected || !sameResumeIdentity(strictAdoption.identity, inspected.identity))
|
|
5519
|
-
throw new Error("Prepared
|
|
5590
|
+
throw new Error("Prepared session changed during strict adoption.");
|
|
5520
5591
|
}
|
|
5521
5592
|
if (entries.length > 0) {
|
|
5522
5593
|
const header = entries.find(entry => entry.type === "session") as SessionHeader | undefined;
|
|
@@ -5541,6 +5612,13 @@ export class SessionManager {
|
|
|
5541
5612
|
prepared.dispose();
|
|
5542
5613
|
throw error;
|
|
5543
5614
|
}
|
|
5615
|
+
if (strictAdoption) {
|
|
5616
|
+
const inspected = inspectResumeSessionFile(resolvedSessionFile, this.storage);
|
|
5617
|
+
if ("kind" in inspected || !sameResumeIdentity(strictAdoption.identity, inspected.identity)) {
|
|
5618
|
+
prepared.dispose();
|
|
5619
|
+
throw new Error("Prepared session changed before strict adoption commit.");
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5544
5622
|
const previous = {
|
|
5545
5623
|
sessionId: this.#sessionId,
|
|
5546
5624
|
sessionName: this.#sessionName,
|
|
@@ -5569,6 +5647,7 @@ export class SessionManager {
|
|
|
5569
5647
|
prepared.dispose();
|
|
5570
5648
|
throw error;
|
|
5571
5649
|
}
|
|
5650
|
+
if (!options?.deferEphemeralArtifactRetirement) this.#retireEphemeralArtifacts();
|
|
5572
5651
|
managedTransition?.settle();
|
|
5573
5652
|
this.#pendingStrictAdoption = undefined;
|
|
5574
5653
|
this.#flushed = true;
|
|
@@ -5596,8 +5675,6 @@ export class SessionManager {
|
|
|
5596
5675
|
flushed: this.#flushed,
|
|
5597
5676
|
needsFullRewriteOnNextPersist: this.#needsFullRewriteOnNextPersist,
|
|
5598
5677
|
ensuredOnDisk: this.#ensuredOnDisk,
|
|
5599
|
-
inMemoryArtifacts: this.#inMemoryArtifacts,
|
|
5600
|
-
inMemoryArtifactCounter: this.#inMemoryArtifactCounter,
|
|
5601
5678
|
artifactManager: this.#artifactManager,
|
|
5602
5679
|
artifactManagerSessionFile: this.#artifactManagerSessionFile,
|
|
5603
5680
|
adoptedArtifactManager: this.#adoptedArtifactManager,
|
|
@@ -5607,6 +5684,7 @@ export class SessionManager {
|
|
|
5607
5684
|
managedTransition?.adopt();
|
|
5608
5685
|
writeTerminalBreadcrumb(this.cwd, resolvedSessionFile);
|
|
5609
5686
|
this.#commitResidentTextStoreTransition(prepared);
|
|
5687
|
+
if (!options?.deferEphemeralArtifactRetirement) this.#retireEphemeralArtifacts();
|
|
5610
5688
|
} catch (error) {
|
|
5611
5689
|
managedTransition?.rollback();
|
|
5612
5690
|
this.#lifecycleIdAdopted = previous.lifecycleIdAdopted;
|
|
@@ -5620,8 +5698,6 @@ export class SessionManager {
|
|
|
5620
5698
|
this.#flushed = previous.flushed;
|
|
5621
5699
|
this.#needsFullRewriteOnNextPersist = previous.needsFullRewriteOnNextPersist;
|
|
5622
5700
|
this.#ensuredOnDisk = previous.ensuredOnDisk;
|
|
5623
|
-
this.#inMemoryArtifacts = previous.inMemoryArtifacts;
|
|
5624
|
-
this.#inMemoryArtifactCounter = previous.inMemoryArtifactCounter;
|
|
5625
5701
|
this.#artifactManager = previous.artifactManager;
|
|
5626
5702
|
this.#artifactManagerSessionFile = previous.artifactManagerSessionFile;
|
|
5627
5703
|
this.#adoptedArtifactManager = previous.adoptedArtifactManager;
|
|
@@ -6067,12 +6143,12 @@ export class SessionManager {
|
|
|
6067
6143
|
this.#flushed = stage.flushed;
|
|
6068
6144
|
this.#needsFullRewriteOnNextPersist = false;
|
|
6069
6145
|
this.#ensuredOnDisk = stage.flushed;
|
|
6070
|
-
|
|
6071
|
-
this.#inMemoryArtifactCounter = 0;
|
|
6146
|
+
|
|
6072
6147
|
this.#artifactManager = null;
|
|
6073
6148
|
this.#artifactManagerSessionFile = null;
|
|
6074
6149
|
this.#adoptedArtifactManager = null;
|
|
6075
6150
|
this.#commitResidentTextStoreTransition(transition);
|
|
6151
|
+
this.#retireEphemeralArtifacts();
|
|
6076
6152
|
stage.committed = true;
|
|
6077
6153
|
this.#preparedNewSessions.delete(stage);
|
|
6078
6154
|
}
|
|
@@ -6883,6 +6959,7 @@ export class SessionManager {
|
|
|
6883
6959
|
const prepared = this.#prepareFreshSessionTransition(fresh, "memory-fallback");
|
|
6884
6960
|
this.#applyFreshSessionMetadata(fresh);
|
|
6885
6961
|
this.#commitResidentTextStoreTransition(prepared);
|
|
6962
|
+
this.#retireEphemeralArtifacts();
|
|
6886
6963
|
if (writeBreadcrumb && fresh.sessionFile) writeTerminalBreadcrumb(this.cwd, fresh.sessionFile);
|
|
6887
6964
|
return fresh.sessionFile;
|
|
6888
6965
|
}
|
|
@@ -7605,6 +7682,8 @@ export class SessionManager {
|
|
|
7605
7682
|
this.#flushed = true;
|
|
7606
7683
|
}
|
|
7607
7684
|
});
|
|
7685
|
+
this.#retireEphemeralArtifacts();
|
|
7686
|
+
await this.#drainEphemeralArtifactCleanups();
|
|
7608
7687
|
this.#releaseResidentTextStore();
|
|
7609
7688
|
this.#releaseOwnedManagedAuthority();
|
|
7610
7689
|
if (this.#persistError) throw this.#persistError;
|
|
@@ -7664,6 +7743,12 @@ export class SessionManager {
|
|
|
7664
7743
|
// close leaves the session live for a genuine retry.
|
|
7665
7744
|
if (!this.#persistWriter) {
|
|
7666
7745
|
this.#releaseResidentTextStore();
|
|
7746
|
+
this.#retireEphemeralArtifacts();
|
|
7747
|
+
try {
|
|
7748
|
+
await this.#drainEphemeralArtifactCleanups();
|
|
7749
|
+
} catch (error) {
|
|
7750
|
+
outcome = { kind: "close_unknown", error: toError(error) };
|
|
7751
|
+
}
|
|
7667
7752
|
}
|
|
7668
7753
|
return outcome;
|
|
7669
7754
|
}
|
|
@@ -7952,14 +8037,40 @@ export class SessionManager {
|
|
|
7952
8037
|
this.#adoptedArtifactManager = manager;
|
|
7953
8038
|
}
|
|
7954
8039
|
|
|
8040
|
+
/** Release only the matching externally adopted manager. */
|
|
8041
|
+
releaseArtifactManager(manager: ArtifactManager): void {
|
|
8042
|
+
if (this.#adoptedArtifactManager === manager) this.#adoptedArtifactManager = null;
|
|
8043
|
+
}
|
|
8044
|
+
|
|
8045
|
+
/** Temporarily release adopted authority while an outer transition validates its successor. */
|
|
8046
|
+
stageAdoptedArtifactManagerForTransition(): void {
|
|
8047
|
+
this.#adoptedArtifactManager = null;
|
|
8048
|
+
}
|
|
8049
|
+
|
|
8050
|
+
/** Prove manager authority by exact object identity, never by pathname shape. */
|
|
8051
|
+
isArtifactManagerAuthorized(manager: ArtifactManager): boolean {
|
|
8052
|
+
return (
|
|
8053
|
+
manager === this.#adoptedArtifactManager ||
|
|
8054
|
+
manager === this.#artifactManager ||
|
|
8055
|
+
manager === this.#ephemeralArtifactManager
|
|
8056
|
+
);
|
|
8057
|
+
}
|
|
8058
|
+
|
|
7955
8059
|
/**
|
|
7956
8060
|
* Returns the ArtifactManager this session writes through. Lazily creates
|
|
7957
8061
|
* one bound to the current session file unless an external manager was
|
|
7958
|
-
* adopted via `adoptArtifactManager`.
|
|
7959
|
-
*
|
|
8062
|
+
* adopted via `adoptArtifactManager`. Falls back to the lazily created
|
|
8063
|
+
* ephemeral filesystem store once a non-persistent session has saved an
|
|
8064
|
+
* artifact, so `artifact://` stays resolvable. Returns null only when no
|
|
8065
|
+
* store has been established yet.
|
|
7960
8066
|
*/
|
|
7961
8067
|
getArtifactManager(): ArtifactManager | null {
|
|
7962
|
-
return this.#getOrCreateArtifactManager();
|
|
8068
|
+
return this.#getOrCreateArtifactManager() ?? this.#ephemeralArtifactManager;
|
|
8069
|
+
}
|
|
8070
|
+
|
|
8071
|
+
/** Linearizably establish this session's persistent or ephemeral artifact manager. */
|
|
8072
|
+
async ensureArtifactManager(): Promise<ArtifactManager | null> {
|
|
8073
|
+
return this.#getOrCreateArtifactManager() ?? (await this.#ensureEphemeralArtifactManager());
|
|
7963
8074
|
}
|
|
7964
8075
|
|
|
7965
8076
|
/**
|
|
@@ -8002,28 +8113,89 @@ export class SessionManager {
|
|
|
8002
8113
|
|
|
8003
8114
|
/**
|
|
8004
8115
|
* Save artifact content under the current session and return artifact ID.
|
|
8005
|
-
*
|
|
8116
|
+
* Persistent sessions write into the session artifact directory; non-persistent
|
|
8117
|
+
* sessions write into a lazily created temporary directory so the content is
|
|
8118
|
+
* read back from the filesystem instead of being retained in memory.
|
|
8006
8119
|
*/
|
|
8007
8120
|
async saveArtifact(content: string, toolType: string): Promise<string | undefined> {
|
|
8008
|
-
const manager = this.#getOrCreateArtifactManager();
|
|
8009
|
-
|
|
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;
|
|
8121
|
+
const manager = this.#getOrCreateArtifactManager() ?? (await this.#ensureEphemeralArtifactManager());
|
|
8122
|
+
return manager ? manager.save(content, toolType) : undefined;
|
|
8015
8123
|
}
|
|
8016
8124
|
|
|
8017
8125
|
/**
|
|
8018
8126
|
* Resolve an artifact ID to an on-disk path for the current session.
|
|
8019
|
-
* Returns null when
|
|
8127
|
+
* Returns null when the artifact is missing.
|
|
8020
8128
|
*/
|
|
8021
8129
|
async getArtifactPath(id: string): Promise<string | null> {
|
|
8022
|
-
const manager = this
|
|
8130
|
+
const manager = this.getArtifactManager();
|
|
8023
8131
|
if (!manager) return null;
|
|
8024
8132
|
return manager.getPath(id);
|
|
8025
8133
|
}
|
|
8026
8134
|
|
|
8135
|
+
/**
|
|
8136
|
+
* Create the non-persistent session's temporary artifact directory on first use.
|
|
8137
|
+
* Returns null when the directory cannot be created; callers then report no artifact.
|
|
8138
|
+
*/
|
|
8139
|
+
async #ensureEphemeralArtifactManager(): Promise<ArtifactManager | null> {
|
|
8140
|
+
if (!this.#ephemeralArtifactInit) {
|
|
8141
|
+
let init: Promise<ArtifactManager | null>;
|
|
8142
|
+
init = fs.promises
|
|
8143
|
+
.mkdtemp(path.join(os.tmpdir(), "gjc-session-artifacts-"))
|
|
8144
|
+
.then(async dir => {
|
|
8145
|
+
try {
|
|
8146
|
+
await SessionManagerTestHooks.beforeEphemeralArtifactManagerInstall?.(dir);
|
|
8147
|
+
} catch (error) {
|
|
8148
|
+
await fs.promises.rm(dir, { recursive: true, force: true });
|
|
8149
|
+
throw error;
|
|
8150
|
+
}
|
|
8151
|
+
const manager = new ArtifactManager(dir);
|
|
8152
|
+
if (this.#ephemeralArtifactInit !== init) {
|
|
8153
|
+
await fs.promises.rm(dir, { recursive: true, force: true });
|
|
8154
|
+
return null;
|
|
8155
|
+
}
|
|
8156
|
+
this.#ephemeralArtifactDir = dir;
|
|
8157
|
+
this.#ephemeralArtifactManager = manager;
|
|
8158
|
+
return manager;
|
|
8159
|
+
})
|
|
8160
|
+
.catch(() => null);
|
|
8161
|
+
this.#ephemeralArtifactInit = init;
|
|
8162
|
+
}
|
|
8163
|
+
const init = this.#ephemeralArtifactInit;
|
|
8164
|
+
const manager = await init;
|
|
8165
|
+
if (!manager && this.#ephemeralArtifactInit === init) this.#ephemeralArtifactInit = null;
|
|
8166
|
+
return manager;
|
|
8167
|
+
}
|
|
8168
|
+
|
|
8169
|
+
/** Unbind the active ephemeral root only after a successor transition commits. */
|
|
8170
|
+
#retireEphemeralArtifacts(): void {
|
|
8171
|
+
const dir = this.#ephemeralArtifactDir;
|
|
8172
|
+
const init = this.#ephemeralArtifactInit;
|
|
8173
|
+
if (!dir && !init) return;
|
|
8174
|
+
this.#ephemeralArtifactManager = null;
|
|
8175
|
+
this.#ephemeralArtifactDir = null;
|
|
8176
|
+
this.#ephemeralArtifactInit = null;
|
|
8177
|
+
const cleanup = (async () => {
|
|
8178
|
+
const initialized = await init;
|
|
8179
|
+
const cleanupDir = dir ?? initialized?.dir;
|
|
8180
|
+
if (cleanupDir) await fs.promises.rm(cleanupDir, { recursive: true, force: true });
|
|
8181
|
+
})();
|
|
8182
|
+
void cleanup.catch(() => {});
|
|
8183
|
+
this.#ephemeralArtifactCleanups.add(cleanup);
|
|
8184
|
+
}
|
|
8185
|
+
|
|
8186
|
+
/** Retire predecessor ephemeral artifacts after an outer logical transition commits. */
|
|
8187
|
+
retireEphemeralArtifactsAfterTransition(): void {
|
|
8188
|
+
this.#retireEphemeralArtifacts();
|
|
8189
|
+
}
|
|
8190
|
+
|
|
8191
|
+
async #drainEphemeralArtifactCleanups(): Promise<void> {
|
|
8192
|
+
while (this.#ephemeralArtifactCleanups.size > 0) {
|
|
8193
|
+
const pending = Array.from(this.#ephemeralArtifactCleanups);
|
|
8194
|
+
this.#ephemeralArtifactCleanups.clear();
|
|
8195
|
+
await Promise.all(pending);
|
|
8196
|
+
}
|
|
8197
|
+
}
|
|
8198
|
+
|
|
8027
8199
|
/**
|
|
8028
8200
|
* Path to the unsent-input draft sidecar for an explicit session. It lives
|
|
8029
8201
|
* inside the artifacts directory so `dropSession` removes it with its owner.
|
|
@@ -8157,20 +8329,26 @@ export class SessionManager {
|
|
|
8157
8329
|
await this.#persistPatch({ type: "header_patch", patch });
|
|
8158
8330
|
}
|
|
8159
8331
|
|
|
8160
|
-
#
|
|
8332
|
+
#appendManagedRecordsSync(records: readonly (FileEntry | SessionPatchRecord)[]): void {
|
|
8161
8333
|
if (!this.#sessionFile) throw new Error("Managed transcript path is unavailable");
|
|
8162
8334
|
const store = this.#managedTranscriptStore();
|
|
8163
8335
|
const relativePath = path.basename(this.#sessionFile);
|
|
8164
|
-
|
|
8336
|
+
const bytes = Buffer.from(`${records.map(record => JSON.stringify(record)).join("\n")}\n`, "utf8");
|
|
8337
|
+
store.appendSync(relativePath, bytes);
|
|
8165
8338
|
}
|
|
8166
8339
|
|
|
8167
8340
|
async #persistPatch(record: SessionPatchRecord): Promise<void> {
|
|
8168
|
-
|
|
8341
|
+
await this.#persistPatches([record]);
|
|
8342
|
+
}
|
|
8343
|
+
|
|
8344
|
+
async #persistPatches(records: readonly SessionPatchRecord[]): Promise<void> {
|
|
8345
|
+
if (records.length === 0 || !this.persist || !this.#sessionFile || !this.storage.existsSync(this.#sessionFile))
|
|
8346
|
+
return;
|
|
8169
8347
|
await this.#queuePersistTask(async () => {
|
|
8170
8348
|
const header = this.#fileEntries.find(entry => entry.type === "session") as SessionHeader | undefined;
|
|
8171
8349
|
if (this.#needsFullRewriteOnNextPersist || !this.#flushed || (header?.version ?? 1) < CURRENT_SESSION_VERSION)
|
|
8172
8350
|
return this.#rewriteFileContents();
|
|
8173
|
-
const
|
|
8351
|
+
const persistedRecords = records.map(record =>
|
|
8174
8352
|
record.type === "entry_patch"
|
|
8175
8353
|
? (prepareEntryForPersistenceSync(
|
|
8176
8354
|
materializeResidentEntryForPersistenceSync(
|
|
@@ -8180,14 +8358,17 @@ export class SessionManager {
|
|
|
8180
8358
|
),
|
|
8181
8359
|
this.#blobStore,
|
|
8182
8360
|
) as unknown as SessionPatchRecord)
|
|
8183
|
-
: record
|
|
8361
|
+
: record,
|
|
8362
|
+
);
|
|
8184
8363
|
if (this.destination.kind === "managed") {
|
|
8185
|
-
this.#
|
|
8364
|
+
this.#appendManagedRecordsSync(persistedRecords);
|
|
8186
8365
|
return;
|
|
8187
8366
|
}
|
|
8188
8367
|
const writer = this.#ensurePersistWriter();
|
|
8189
8368
|
if (!writer) return this.#rewriteFileContents();
|
|
8190
|
-
|
|
8369
|
+
for (const persistedRecord of persistedRecords) {
|
|
8370
|
+
await writer.write(persistedRecord);
|
|
8371
|
+
}
|
|
8191
8372
|
});
|
|
8192
8373
|
}
|
|
8193
8374
|
_persist(entry: SessionEntry): void {
|
|
@@ -8236,7 +8417,7 @@ export class SessionManager {
|
|
|
8236
8417
|
new Map(),
|
|
8237
8418
|
);
|
|
8238
8419
|
const persistedEntry = prepareEntryForPersistenceSync(materializedEntry, this.#blobStore);
|
|
8239
|
-
this.#
|
|
8420
|
+
this.#appendManagedRecordsSync([persistedEntry]);
|
|
8240
8421
|
return;
|
|
8241
8422
|
}
|
|
8242
8423
|
const writer = this.#ensurePersistWriter();
|
|
@@ -8265,13 +8446,31 @@ export class SessionManager {
|
|
|
8265
8446
|
this.#assertRecoveryHydrationWritable();
|
|
8266
8447
|
const normalizedEntry = normalizeSessionEntryForStorage(entry);
|
|
8267
8448
|
const residentEntry = this.#prepareEntryForCurrentResidentStore(normalizedEntry) as SessionEntry;
|
|
8449
|
+
const previousLeafId = this.#leafId;
|
|
8450
|
+
const priorPersistenceError = this.#persistError;
|
|
8268
8451
|
this.#fileEntries.push(residentEntry);
|
|
8269
8452
|
this.#byId.set(residentEntry.id, residentEntry);
|
|
8270
8453
|
this.#leafId = residentEntry.id;
|
|
8271
8454
|
this.#bumpEntryRevision();
|
|
8272
8455
|
this.#leafRevision++;
|
|
8273
8456
|
if (entry.type === "label") this.#labelRevision++;
|
|
8274
|
-
|
|
8457
|
+
try {
|
|
8458
|
+
this._persist(residentEntry);
|
|
8459
|
+
} catch (error) {
|
|
8460
|
+
const removed = this.#fileEntries.pop();
|
|
8461
|
+
if (removed !== residentEntry)
|
|
8462
|
+
throw new Error("Session append rollback lost resident ordering.", { cause: error });
|
|
8463
|
+
this.#byId.delete(residentEntry.id);
|
|
8464
|
+
this.#leafId = previousLeafId;
|
|
8465
|
+
this.#bumpEntryRevision();
|
|
8466
|
+
this.#leafRevision++;
|
|
8467
|
+
if (entry.type === "label") this.#labelRevision++;
|
|
8468
|
+
throw new SessionAppendPersistenceError(
|
|
8469
|
+
priorPersistenceError ? "prior_failure" : "current_append",
|
|
8470
|
+
residentEntry.id,
|
|
8471
|
+
this.#persistError ?? toError(error),
|
|
8472
|
+
);
|
|
8473
|
+
}
|
|
8275
8474
|
// Same validated, overflow-guarded aggregation as the resume path (#buildIndex): a
|
|
8276
8475
|
// malformed task-result or assistant usage shape reaching the append path must not
|
|
8277
8476
|
// crash or poison getUsageStatistics() either.
|
|
@@ -8513,6 +8712,50 @@ export class SessionManager {
|
|
|
8513
8712
|
this.#replayMetadataRevision++;
|
|
8514
8713
|
}
|
|
8515
8714
|
|
|
8715
|
+
/**
|
|
8716
|
+
* Rehydrate the canonical transcript after a synchronous persistence failure.
|
|
8717
|
+
*
|
|
8718
|
+
* The failed append may have committed before reporting an uncertain outcome,
|
|
8719
|
+
* so callers must not clear the sticky error or retry against the resident
|
|
8720
|
+
* branch. Reloading the exact session file is the only supported recovery
|
|
8721
|
+
* boundary for both managed and explicit persistent destinations.
|
|
8722
|
+
*/
|
|
8723
|
+
async recoverPersistenceFailure(): Promise<void> {
|
|
8724
|
+
const persistenceError = this.#persistError;
|
|
8725
|
+
if (!persistenceError) return;
|
|
8726
|
+
if (!this.#sessionFile) throw persistenceError;
|
|
8727
|
+
const sessionFile = this.#sessionFile;
|
|
8728
|
+
try {
|
|
8729
|
+
await this.#closePersistWriter();
|
|
8730
|
+
} catch {
|
|
8731
|
+
// A writer reports its prior write failure after a confirmed close. A
|
|
8732
|
+
// second close clears that terminal writer; retryable close failures are
|
|
8733
|
+
// genuinely re-dispatched and still fail if the descriptor cannot settle.
|
|
8734
|
+
}
|
|
8735
|
+
await this.#closePersistWriter();
|
|
8736
|
+
const inspected = inspectResumeSessionFile(sessionFile, this.storage);
|
|
8737
|
+
if ("kind" in inspected) throw persistenceError;
|
|
8738
|
+
try {
|
|
8739
|
+
fsyncResumeSessionIdentity(inspected.identity);
|
|
8740
|
+
} catch {
|
|
8741
|
+
throw persistenceError;
|
|
8742
|
+
}
|
|
8743
|
+
const durable = inspectResumeSessionFile(sessionFile, this.storage);
|
|
8744
|
+
if ("kind" in durable || !sameResumeIdentity(inspected.identity, durable.identity)) {
|
|
8745
|
+
throw persistenceError;
|
|
8746
|
+
}
|
|
8747
|
+
const adoption = {
|
|
8748
|
+
canonicalPath: durable.identity.canonicalPath,
|
|
8749
|
+
identity: durable.identity,
|
|
8750
|
+
inspection: durable,
|
|
8751
|
+
};
|
|
8752
|
+
this.#pendingStrictAdoption = adoption;
|
|
8753
|
+
try {
|
|
8754
|
+
await this.setSessionFile(durable.identity.canonicalPath);
|
|
8755
|
+
} finally {
|
|
8756
|
+
if (this.#pendingStrictAdoption === adoption) this.#pendingStrictAdoption = undefined;
|
|
8757
|
+
}
|
|
8758
|
+
}
|
|
8516
8759
|
/**
|
|
8517
8760
|
* Rewrite the session file after in-place entry updates.
|
|
8518
8761
|
* Use sparingly (e.g., pruning old tool outputs).
|
|
@@ -9094,9 +9337,7 @@ export class SessionManager {
|
|
|
9094
9337
|
|
|
9095
9338
|
async #sanitizeLoadedOpenAIResponsesReplayMetadataAndPersist(): Promise<boolean> {
|
|
9096
9339
|
const patches = this.#sanitizeLoadedOpenAIResponsesReplayMetadata();
|
|
9097
|
-
|
|
9098
|
-
await this.#persistPatch(patch);
|
|
9099
|
-
}
|
|
9340
|
+
await this.#persistPatches(patches);
|
|
9100
9341
|
return patches.length > 0;
|
|
9101
9342
|
}
|
|
9102
9343
|
|
|
@@ -9669,6 +9910,7 @@ export class SessionManager {
|
|
|
9669
9910
|
);
|
|
9670
9911
|
manager.#applyFreshSessionMetadata(fresh);
|
|
9671
9912
|
manager.#commitResidentTextStoreTransition(transition);
|
|
9913
|
+
manager.#retireEphemeralArtifacts();
|
|
9672
9914
|
if (fresh.sessionFile) writeTerminalBreadcrumb(manager.cwd, fresh.sessionFile);
|
|
9673
9915
|
manager.sanitizeLoadedOpenAIResponsesReplayMetadata();
|
|
9674
9916
|
await manager.#rewriteFile();
|
|
@@ -9771,6 +10013,7 @@ export class SessionManager {
|
|
|
9771
10013
|
const transition = manager.#prepareFreshSessionTransition(fresh, "memory-fallback");
|
|
9772
10014
|
manager.#applyFreshSessionMetadata(fresh);
|
|
9773
10015
|
manager.#commitResidentTextStoreTransition(transition);
|
|
10016
|
+
manager.#retireEphemeralArtifacts();
|
|
9774
10017
|
writeTerminalBreadcrumb(manager.cwd, resolved);
|
|
9775
10018
|
await manager.#rewriteFile();
|
|
9776
10019
|
manager.#flushed = true;
|
|
@@ -9962,6 +10205,7 @@ export class SessionManager {
|
|
|
9962
10205
|
);
|
|
9963
10206
|
manager.#applyFreshSessionMetadata(fresh);
|
|
9964
10207
|
manager.#commitResidentTextStoreTransition(transition);
|
|
10208
|
+
manager.#retireEphemeralArtifacts();
|
|
9965
10209
|
manager.sanitizeLoadedOpenAIResponsesReplayMetadata();
|
|
9966
10210
|
const beforeWrite = inspectResumeSessionFile(snapshot.sourcePath, snapshot.storage);
|
|
9967
10211
|
if ("kind" in beforeWrite) {
|
|
@@ -28,6 +28,7 @@ export interface SessionStorageStat {
|
|
|
28
28
|
size: number;
|
|
29
29
|
mtimeMs: number;
|
|
30
30
|
mtimeNs: bigint;
|
|
31
|
+
ctimeNs: bigint;
|
|
31
32
|
mtime: Date;
|
|
32
33
|
isFile: boolean;
|
|
33
34
|
}
|
|
@@ -47,6 +48,7 @@ function statFromNode(stats: fs.BigIntStats): SessionStorageStat {
|
|
|
47
48
|
size: Number(stats.size),
|
|
48
49
|
mtimeMs: Number(stats.mtimeMs),
|
|
49
50
|
mtimeNs: stats.mtimeNs,
|
|
51
|
+
ctimeNs: stats.ctimeNs,
|
|
50
52
|
mtime: stats.mtime,
|
|
51
53
|
isFile: stats.isFile(),
|
|
52
54
|
};
|
|
@@ -1624,6 +1626,7 @@ export class MemorySessionStorage implements SessionStorage {
|
|
|
1624
1626
|
size: entry.content.byteLength,
|
|
1625
1627
|
mtimeMs: entry.mtimeMs,
|
|
1626
1628
|
mtimeNs: BigInt(entry.mtimeMs) * 1_000_000n,
|
|
1629
|
+
ctimeNs: BigInt(entry.mtimeMs) * 1_000_000n,
|
|
1627
1630
|
mtime: new Date(entry.mtimeMs),
|
|
1628
1631
|
isFile: true,
|
|
1629
1632
|
};
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
{"id":"minimax","aliases":["minimax-code"],"name":"MiniMax Coding Plan","description":"OpenAI-compatible MiniMax Coding Plan endpoint","compatibility":"openai","api":"openai-completions","providerId":"minimax-code","baseUrl":"https://api.minimax.io/v1","apiKeyEnv":"MINIMAX_CODE_API_KEY","models":["minimax-m3"],"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"reasoningContentField":"reasoning_content"}},
|
|
3
3
|
{"id":"minimax-cn","aliases":["minimax-code-cn","minimaxi"],"name":"MiniMax Coding Plan (China)","description":"OpenAI-compatible MiniMax China endpoint","compatibility":"openai","api":"openai-completions","providerId":"minimax-code-cn","baseUrl":"https://api.minimaxi.com/v1","apiKeyEnv":"MINIMAX_CODE_CN_API_KEY","models":["minimax-m3"],"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"reasoningContentField":"reasoning_content"}},
|
|
4
4
|
{"id":"glm","aliases":["zai","z-ai","bigmodel"],"name":"GLM / zAI","description":"OpenAI-compatible GLM endpoint from zAI/BigModel","compatibility":"openai","api":"openai-completions","providerId":"glm-proxy","baseUrl":"https://api.z.ai/api/paas/v4","apiKeyEnv":"ZAI_API_KEY","models":["glm-4.6"],"compat":{"supportsDeveloperRole":false,"supportsReasoningEffort":false,"thinkingFormat":"zai","reasoningContentField":"reasoning_content"}},
|
|
5
|
-
{"id":"alibaba-token-plan","aliases":["alibaba","token-plan"],"name":"Alibaba Token Plan","description":"OpenAI-compatible Alibaba Token Plan endpoint","compatibility":"openai","api":"openai-completions","providerId":"alibaba-token-plan","baseUrl":"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1","apiKeyEnv":"ALIBABA_TOKEN_PLAN_API_KEY","models":["qwen3.8-max-preview","glm-5.2","deepseek-v4-pro"],"modelApi":{"qwen3.8-max-preview":"openai-responses","glm-5.2":"openai-completions","deepseek-v4-pro":"openai-completions"},"compat":{"supportsDeveloperRole":false}}
|
|
5
|
+
{"id":"alibaba-token-plan","aliases":["alibaba","token-plan"],"name":"Alibaba Token Plan","description":"OpenAI-compatible Alibaba Token Plan endpoint","compatibility":"openai","api":"openai-completions","providerId":"alibaba-token-plan","baseUrl":"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1","apiKeyEnv":"ALIBABA_TOKEN_PLAN_API_KEY","models":["qwen3.8-max-preview","qwen-3.8-max","glm-5.2","deepseek-v4-pro"],"modelApi":{"qwen3.8-max-preview":"openai-responses","qwen-3.8-max":"openai-responses","glm-5.2":"openai-completions","deepseek-v4-pro":"openai-completions"},"compat":{"supportsDeveloperRole":false}}
|
|
6
6
|
]
|