@gajae-code/coding-agent 0.12.18 → 0.12.20

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.
Files changed (100) hide show
  1. package/CHANGELOG.md +26 -9
  2. package/dist/types/commands/gc.d.ts +4 -0
  3. package/dist/types/commands/sdk.d.ts +66 -0
  4. package/dist/types/config/settings-schema.d.ts +34 -0
  5. package/dist/types/gjc-runtime/gc-runtime.d.ts +97 -1
  6. package/dist/types/modes/acp/acp-agent.d.ts +33 -1
  7. package/dist/types/modes/components/bash-execution.d.ts +7 -0
  8. package/dist/types/modes/components/eval-execution.d.ts +7 -0
  9. package/dist/types/modes/utils/ui-helpers.d.ts +10 -1
  10. package/dist/types/sdk/acp/adapter.d.ts +9 -17
  11. package/dist/types/sdk/acp/index.d.ts +1 -1
  12. package/dist/types/sdk/broker/broker.d.ts +93 -1
  13. package/dist/types/sdk/broker/ensure.d.ts +17 -0
  14. package/dist/types/sdk/broker/index.d.ts +1 -0
  15. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +10 -1
  16. package/dist/types/sdk/broker/lifecycle.d.ts +81 -2
  17. package/dist/types/sdk/broker/startup-budget.d.ts +44 -0
  18. package/dist/types/sdk/bus/chat-daemon-control.d.ts +7 -1
  19. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +11 -1
  20. package/dist/types/sdk/bus/discord-daemon.d.ts +1 -0
  21. package/dist/types/sdk/bus/index.d.ts +20 -9
  22. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -6
  23. package/dist/types/sdk/bus/slack-daemon.d.ts +2 -2
  24. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -2
  25. package/dist/types/sdk/host/query/revision-store.d.ts +5 -0
  26. package/dist/types/sdk/host/session-runtime.d.ts +4 -0
  27. package/dist/types/sdk/lifecycle-session.d.ts +13 -2
  28. package/dist/types/sdk/prompt-failure.d.ts +13 -0
  29. package/dist/types/sdk/prompt-watchdog.d.ts +82 -0
  30. package/dist/types/sdk/session-reconnect.d.ts +20 -0
  31. package/dist/types/sdk/startup-capability.d.ts +1 -1
  32. package/dist/types/session/agent-session.d.ts +14 -4
  33. package/dist/types/session/blob-store.d.ts +32 -0
  34. package/dist/types/session/internal/managed-session-storage.d.ts +40 -2
  35. package/dist/types/session/internal/session-memory-sidecar.d.ts +881 -0
  36. package/dist/types/session/session-manager.d.ts +301 -27
  37. package/dist/types/session/session-storage.d.ts +170 -0
  38. package/dist/types/task/executor.d.ts +2 -1
  39. package/dist/types/tools/bash.d.ts +1 -0
  40. package/dist/types/tui/output-block.d.ts +6 -0
  41. package/package.json +8 -8
  42. package/src/cli/args.ts +75 -34
  43. package/src/commands/acp.ts +7 -1
  44. package/src/commands/gc.ts +31 -2
  45. package/src/commands/sdk.ts +141 -7
  46. package/src/config/settings-schema.ts +43 -0
  47. package/src/coordinator-mcp/server.ts +6 -1
  48. package/src/export/html/index.ts +83 -39
  49. package/src/gjc-runtime/gc-runtime.ts +1283 -4
  50. package/src/internal-urls/docs-index.generated.ts +1 -1
  51. package/src/internal-urls/local-protocol.ts +14 -0
  52. package/src/main.ts +40 -9
  53. package/src/modes/acp/acp-agent.ts +564 -37
  54. package/src/modes/acp/terminal-auth.ts +2 -2
  55. package/src/modes/components/bash-execution.ts +14 -0
  56. package/src/modes/components/eval-execution.ts +14 -0
  57. package/src/modes/controllers/command-controller.ts +63 -8
  58. package/src/modes/utils/ui-helpers.ts +81 -11
  59. package/src/sdk/acp/adapter.ts +46 -45
  60. package/src/sdk/acp/index.ts +1 -1
  61. package/src/sdk/broker/broker.ts +392 -11
  62. package/src/sdk/broker/ensure.ts +131 -61
  63. package/src/sdk/broker/index.ts +1 -0
  64. package/src/sdk/broker/lifecycle-ledger.ts +59 -5
  65. package/src/sdk/broker/lifecycle.ts +278 -63
  66. package/src/sdk/broker/startup-budget.ts +103 -0
  67. package/src/sdk/bus/chat-daemon-control.ts +9 -3
  68. package/src/sdk/bus/chat-daemon-runtime.ts +565 -29
  69. package/src/sdk/bus/conversation-store.ts +1 -1
  70. package/src/sdk/bus/discord-daemon.ts +2 -1
  71. package/src/sdk/bus/discord-live-provider.ts +34 -6
  72. package/src/sdk/bus/index.ts +118 -20
  73. package/src/sdk/bus/kind-aware-reconciliation.ts +2 -1
  74. package/src/sdk/bus/prompt-reconciliation.ts +4 -9
  75. package/src/sdk/bus/slack-daemon.ts +91 -18
  76. package/src/sdk/bus/telegram-daemon-contract.ts +4 -2
  77. package/src/sdk/bus/topic-registry.ts +7 -1
  78. package/src/sdk/cli/session-cli.ts +8 -1
  79. package/src/sdk/host/query/handlers.ts +1 -1
  80. package/src/sdk/host/query/revision-store.ts +48 -12
  81. package/src/sdk/host/session-runtime.test.ts +50 -0
  82. package/src/sdk/host/session-runtime.ts +12 -2
  83. package/src/sdk/lifecycle-session.ts +26 -3
  84. package/src/sdk/mcp/server.ts +6 -1
  85. package/src/sdk/prompt-failure.ts +42 -0
  86. package/src/sdk/prompt-watchdog.ts +162 -0
  87. package/src/sdk/session-reconnect.ts +43 -0
  88. package/src/sdk/startup-capability.ts +13 -4
  89. package/src/session/agent-session.ts +525 -222
  90. package/src/session/blob-store.ts +89 -0
  91. package/src/session/internal/managed-session-scope.ts +36 -26
  92. package/src/session/internal/managed-session-storage.ts +303 -32
  93. package/src/session/internal/session-memory-sidecar.ts +2160 -0
  94. package/src/session/session-manager.ts +7027 -394
  95. package/src/session/session-storage.ts +1134 -11
  96. package/src/task/executor.ts +17 -4
  97. package/src/tools/bash.ts +109 -7
  98. package/src/tools/find.ts +55 -26
  99. package/src/tools/todo-contract.ts +40 -8
  100. package/src/tui/output-block.ts +11 -1
@@ -1,3 +1,4 @@
1
+ import * as crypto from "node:crypto";
1
2
  import { type AgentMessage } from "@gajae-code/agent-core";
2
3
  import type { ConfiguredModelChainEntry as SharedConfiguredModelChainEntry } from "@gajae-code/agent-core/compaction";
3
4
  import type { ImageContent, Message, MessageAttribution, ServiceTier, TextContent } from "@gajae-code/ai/core";
@@ -7,7 +8,8 @@ import type { ManagedLegacyLocalMigrationSource } from "../internal-urls/local-p
7
8
  import { ArtifactManager } from "./artifacts";
8
9
  import { type BlobPutResult, BlobStore } from "./blob-store";
9
10
  import { type ManagedMigrationPolicy } from "./internal/managed-session-scope";
10
- import { type ManagedDirectoryRoot, ManagedSessionDescendantStore } from "./internal/managed-session-storage";
11
+ import { type ManagedBoundedAppendExpectation, type ManagedDirectoryRoot, ManagedSessionDescendantStore } from "./internal/managed-session-storage";
12
+ import { type BaseAnchor, BoundedLabelsPinsStore, type CommittedTail, type DescriptorSnapshot, type DictionaryPartitionCommit, FixedCacheAccount, type MetadataDeltaValue, type ParentBucketCommit, type ReducerState, type ReopenClassification, SessionMemoryAccountant } from "./internal/session-memory-sidecar";
11
13
  import { type MemoryGuardCreateCheckpointInput, type MemoryGuardParticipantIngressLease, type MemoryGuardRestoreInput, type MemoryGuardRestoreResult, type MemoryGuardSessionManagerCheckpointV1 } from "./memory-guard-checkpoint-participant";
12
14
  import { type BashExecutionMessage, type CustomMessage, type FileMentionMessage, type HookMessage, type PythonExecutionMessage } from "./messages";
13
15
  import { type SessionManagerReadAccess, sessionManagerReadCapability } from "./session-manager-internal";
@@ -84,6 +86,48 @@ export interface SessionManagerObservabilityStats {
84
86
  materializedCacheDemotedCount: number;
85
87
  pathOnlyContextBuildCount: number;
86
88
  }
89
+ export interface SessionMemoryStats {
90
+ sidecarEnabled: boolean;
91
+ coldRetirementActive: boolean;
92
+ sidecarIneligible: boolean;
93
+ hotRegionBytes: number;
94
+ metaDescriptorBytes: number;
95
+ totalAccountedBytes: number;
96
+ lastReopenTransition: ReopenClassification | undefined;
97
+ currentCommitTransition: ReopenClassification | undefined;
98
+ lazyReopenAttempted: boolean;
99
+ lazyReopenSucceeded: boolean;
100
+ lazyReopenFallbackReason: string | undefined;
101
+ retirementFallbackReason: string | undefined;
102
+ autoDisabledReason: string | undefined;
103
+ consecutiveBuildFailures: number;
104
+ /** Persistent bounded parent→children artifact is adopted and usable. */
105
+ parentArtifactEnabled: boolean;
106
+ /** Persistent bounded dictionary artifact is adopted and usable. */
107
+ dictionaryArtifactEnabled: boolean;
108
+ /** Reducer-bucket bytes retained by metadata-delta descriptors (fixed accounting). */
109
+ metadataDeltaDescriptorBytes: number;
110
+ /** Live cold index bytes (descriptor size when proven, else 0). */
111
+ coldIndexBytes: number;
112
+ /** Live cold block-cache allocated bytes. */
113
+ coldIndexBlockCacheBytes: number;
114
+ /** Live cold entry-cache allocated bytes. */
115
+ coldEntryCacheBytes: number;
116
+ /** Live observability counters (P7 contract). */
117
+ coldEntriesRetired: number;
118
+ coldEntriesReloaded: number;
119
+ rangeReadCount: number;
120
+ rangeReadGenerationMismatchCount: number;
121
+ sidecarRebuildCount: number;
122
+ coldMutationPromotions: number;
123
+ hotOverflowTransitions: number;
124
+ labelDiskFallbackCount: number;
125
+ /** Shadow-mode eager-vs-sidecar parity mismatches observed at build time (AC10 telemetry). */
126
+ shadowParityMismatchCount: number;
127
+ /** Shadow-mode parity comparisons performed at build time (AC10 telemetry). */
128
+ shadowParityCheckCount: number;
129
+ transcriptGeneration: number;
130
+ }
87
131
  export interface SessionMessageEntry extends SessionEntryBase {
88
132
  type: "message";
89
133
  message: AgentMessage;
@@ -256,14 +300,179 @@ export interface EntryPatchRecord {
256
300
  export type SessionPatchRecord = HeaderPatchRecord | EntryPatchRecord;
257
301
  /** Resolved file entries; patch records are applied by parseSessionEntries(). */
258
302
  export type FileEntry = SessionHeader | SessionEntry;
303
+ /**
304
+ * Cold-region placeholder envelope for a retired entry. The heavy payload was moved
305
+ * out of RAM into the disposable `.spill.idx`/`.spill.tail` cold region; the exact
306
+ * transcript byte range is recorded so the entry can be lazily resolved by
307
+ * `ordinal`/`id`. Never persisted; used only while a cold sidecar region is active.
308
+ */
309
+ export interface SessionColdRefEntry extends SessionEntryBase {
310
+ type: "session_cold_ref";
311
+ ordinal: number;
312
+ seq: number;
313
+ byteOffset: number;
314
+ byteLength: number;
315
+ }
316
+ /** One cold entry's transcript location, keyed by entry id. */
317
+ export interface ColdEntryIndex {
318
+ ordinal: number;
319
+ seq: number;
320
+ byteOffset: number;
321
+ byteLength: number;
322
+ recordDigest: string;
323
+ parentId?: string | null;
324
+ entryType?: string;
325
+ }
326
+ export interface SessionMemorySidecarRuntime {
327
+ /** Transcript v5 remains authoritative; sidecars are disposable/rebuildable. */
328
+ enabled: boolean;
329
+ /** Set when duplicate record IDs were detected; session stays eager. */
330
+ sidecarIneligible: boolean;
331
+ base: BaseAnchor;
332
+ tail: CommittedTail;
333
+ /** Bounded hot index cache; authoritative lookup falls back to the disk index. */
334
+ coldEntries: Map<string, ColdEntryIndex>;
335
+ indexPath: string;
336
+ tailPath: string;
337
+ commitPath: string;
338
+ /** Directory prefix of the persistent `.spill.parent-<bucket>` bucket files. */
339
+ parentPathPrefix: string;
340
+ /** Directory prefix of the persistent `.spill.dict-part-<partition>` partition files. */
341
+ dictionaryPathPrefix: string;
342
+ /** Path of the persistent `.spill.dict-meta` finalization file. */
343
+ dictionaryMetaPath: string;
344
+ /** Path of the persistent `.spill.metadata-delta` section. */
345
+ metadataDeltaPath: string;
346
+ retirementFirstKeptEntryId: string | undefined;
347
+ /** Hot-suffix byte total (accountant-bounded to ≤ 16 MiB). */
348
+ hotSuffixBytes: number;
349
+ accountant: SessionMemoryAccountant;
350
+ reducer: ReducerState;
351
+ /** Resident inline provider-affecting entries (merged order minus demoted slots). */
352
+ providerStateEntries: SessionEntry[];
353
+ /** Merged provider-list order as keys (inline + demoted); reopened/appended slots stay exact. */
354
+ providerStateOrder: string[];
355
+ blockCache: FixedCacheAccount;
356
+ parentChildrenCache: Map<string, {
357
+ ids: readonly string[];
358
+ bytes: number;
359
+ descriptor: SessionStorageStat;
360
+ bucketDescriptor?: SessionStorageStat;
361
+ bucketIndex?: number;
362
+ }>;
363
+ entryCache: FixedCacheAccount;
364
+ tailCache: FixedCacheAccount;
365
+ labelsPins: BoundedLabelsPinsStore;
366
+ terminalTransition: ReopenClassification | undefined;
367
+ reopenTransition: ReopenClassification | undefined;
368
+ /** SHA-256 over the exact `.spill.idx` bytes this runtime has written/adopted ("" until proven). */
369
+ indexDigest: string;
370
+ /** Descriptor of the exact index bytes whose digest was validated for cache use. */
371
+ validatedIndexDescriptor?: SessionStorageStat;
372
+ /** Live running hash of the index bytes; updated on every index append. */
373
+ indexHash: crypto.Hash;
374
+ /** Persistent bounded parent→children artifact state; absent = parent lookups fail closed. */
375
+ parentArtifact?: ParentArtifactRuntimeState;
376
+ /** Persistent bounded dictionary artifact state; absent = dictionary lookups fail closed to the cold scan. */
377
+ dictionary?: DictionaryArtifactRuntimeState;
378
+ /** Persistent metadata-delta section state; absent = no demoted provider values. */
379
+ metadataDelta?: MetadataDeltaArtifactRuntimeState;
380
+ /** Lazily built 24-bit fingerprints for O(1) fail-closed ID generation when no persistent dictionary exists. */
381
+ coldIdHashes?: BoundedColdIdHashSet;
382
+ coldIdHashesDescriptor?: SessionStorageStat;
383
+ /** Live observability counters (P7 live-only contract). */
384
+ coldEntriesRetired: number;
385
+ coldEntriesReloaded: number;
386
+ rangeReadCount: number;
387
+ rangeReadGenerationMismatchCount: number;
388
+ sidecarRebuildCount: number;
389
+ coldMutationPromotions: number;
390
+ hotOverflowTransitions: number;
391
+ labelDiskFallbackCount: number;
392
+ /** Shadow-mode eager-vs-sidecar parity mismatches observed at build time (AC10 telemetry). */
393
+ shadowParityMismatchCount: number;
394
+ /** Shadow-mode parity comparisons performed at build time (AC10 telemetry). */
395
+ shadowParityCheckCount: number;
396
+ transcriptGeneration: number;
397
+ }
398
+ /** Retained runtime state of the disposable parent→children artifact (block-cache charged). */
399
+ interface ParentArtifactRuntimeState {
400
+ /** Exact `.spill.idx` digest the artifact covers; artifact lookups require `indexDigest === runtime.indexDigest`. */
401
+ indexDigest: string;
402
+ /** Committed per-bucket exact-byte size + sha256, updated incrementally on append. */
403
+ buckets: ParentBucketCommit[];
404
+ /** Total artifact bytes (sum of bucket sizes). */
405
+ totalBytes: number;
406
+ /** Block-cache bytes charged for this retained state; released on invalidation/wholesale release. */
407
+ chargedBytes: number;
408
+ /** On-disk byte cap enforced on append; fixed at `PARENT_CHILDREN_BUDGET_BYTES` unless a test shrinks it. */
409
+ budgetBytes: number;
410
+ }
411
+ /** Retained runtime state of the disposable dictionary artifact (block-cache charged). */
412
+ interface DictionaryArtifactRuntimeState {
413
+ /** Exact `.spill.idx` digest the artifact covers; lookups require `indexDigest === runtime.indexDigest`. */
414
+ indexDigest: string;
415
+ /** Committed per-partition exact-byte size + sha256, updated incrementally on append. */
416
+ partitions: DictionaryPartitionCommit[];
417
+ /** Exact `.spill.dict-meta` bytes (size + digest). */
418
+ metaSize: number;
419
+ metaDigest: string;
420
+ recordCount: number;
421
+ uniqueTerms: number;
422
+ totalBytes: number;
423
+ /** Bounded duplicate-id diagnostics; non-empty ⇒ the artifact is never adopted. */
424
+ duplicateIds: readonly string[];
425
+ sidecarIneligible: boolean;
426
+ /** Block-cache bytes charged for this retained state; released on invalidation. */
427
+ chargedBytes: number;
428
+ /** On-disk byte cap for a single partition append; fixed unless a test shrinks it. */
429
+ budgetBytes: number;
430
+ /** Running per-partition hashes/sizes/records for append-time rebinding. */
431
+ partitionHashes: crypto.Hash[];
432
+ partitionSizes: number[];
433
+ partitionRecords: number[];
434
+ /** Descriptor of the exact meta bytes whose digest was validated for artifact use. */
435
+ validatedDescriptor?: SessionStorageStat;
436
+ }
437
+ /** Retained runtime state of the disposable metadata-delta section (reducer-bucket accounting). */
438
+ interface MetadataDeltaArtifactRuntimeState {
439
+ /** Exact `.spill.idx` digest the sidecar set was built with. */
440
+ indexDigest: string;
441
+ /** Exact bytes of the `.spill.metadata-delta` file. */
442
+ size: number;
443
+ sha256: string;
444
+ /** Live running hash of the delta bytes; updated on every value append. */
445
+ hash: crypto.Hash;
446
+ /** Demoted value descriptors keyed by provider key (positions derived at marker time). */
447
+ byKey: Map<string, Omit<MetadataDeltaValue, "key" | "position">>;
448
+ /** Reducer-bucket bytes retained by the descriptors (fixed accounting, reported in stats). */
449
+ descriptorBytes: number;
450
+ /** Fixed on-disk byte cap for the section. */
451
+ budgetBytes: number;
452
+ /** Descriptor of the exact delta bytes whose digest was validated. */
453
+ validatedDescriptor?: SessionStorageStat;
454
+ }
455
+ declare class BoundedColdIdHashSet {
456
+ #private;
457
+ has(value: string): boolean;
458
+ add(value: string): boolean;
459
+ addUnique(value: string): boolean;
460
+ }
259
461
  export type DefaultModelSelectionStage = {
260
462
  readonly entryRevision: number;
463
+ readonly leafRevision: number;
261
464
  readonly headerExportRevision: number;
262
465
  readonly sessionId: string;
263
466
  readonly sessionFile: string | undefined;
264
467
  readonly entries: readonly FileEntry[];
265
468
  readonly tempPath: string | undefined;
266
469
  readonly persistsToExistingFile: boolean;
470
+ readonly boundedCold: boolean;
471
+ readonly appendEntries: readonly SessionEntry[];
472
+ readonly sourceDescriptor: DescriptorSnapshot | undefined;
473
+ readonly sourceStat: SessionStorageStat | undefined;
474
+ readonly sourceSha256: string | undefined;
475
+ readonly managedAppendExpectation: ManagedBoundedAppendExpectation | undefined;
267
476
  };
268
477
  export interface SessionManagerRevisionSnapshot {
269
478
  entry: number;
@@ -346,18 +555,12 @@ export interface ResumeTailTerminal {
346
555
  }
347
556
  export interface ResumeTailError {
348
557
  kind: "error";
349
- reason: "missing" | "malformed" | "unstable" | "read-failed" | "legacy_migration_disabled" | "oversized";
558
+ reason: "missing" | "malformed" | "unstable" | "read-failed" | "legacy_migration_disabled" | "oversized" | "context_too_large";
350
559
  size?: number;
351
560
  }
352
561
  export type SessionDirectoryMigrationPolicy = ManagedMigrationPolicy;
353
562
  export type SessionAppendPersistenceFailurePhase = "current_append" | "prior_failure";
354
- /**
355
- * Safety bound for resuming a single transcript file. Matches the managed-storage
356
- * per-file artifact limit (`MANAGED_ARTIFACT_MAX_FILE_BYTES`): transcripts at or above
357
- * this size are not loaded into memory wholesale because the decode + per-line JSON
358
- * parse + identity hash would OOM or stall the process (#3851). The bound is a
359
- * load-time guard only; it does not raise the on-disk managed-storage limit.
360
- */
563
+ /** Safety bound for eager resume compatibility and managed per-file artifacts. */
361
564
  export declare const RESUME_TRANSCRIPT_MAX_BYTES: number;
362
565
  export declare const SESSION_OVERSIZED_RECOVERY_MESSAGE = "The selected session transcript is too large to resume safely. Use `gjc export <session-file>` to export its content into a new session, or remove/archive it after confirming its content is no longer needed.";
363
566
  export declare class SessionAppendPersistenceError extends Error {
@@ -383,6 +586,21 @@ export declare class SessionTranscriptOversizedError extends Error {
383
586
  readonly size: number;
384
587
  constructor(size: number);
385
588
  }
589
+ /** Operation-peak budget for one synchronous session-context materialization. */
590
+ export declare const SESSION_CONTEXT_MATERIALIZATION_BUDGET_BYTES: number;
591
+ /**
592
+ * Thrown by the synchronous session-context builders when the materialized graph
593
+ * exceeds the operation budget. `instanceof`-stable across module boundaries:
594
+ * consumers map with `error instanceof SessionContextTooLargeError`, never by name
595
+ * string. The over-budget graph is never retained — the builder releases scratch
596
+ * before throwing and public synchronous signatures are unchanged.
597
+ */
598
+ export declare class SessionContextTooLargeError extends Error {
599
+ readonly code: "context_too_large";
600
+ readonly measuredBytes: number;
601
+ readonly budgetBytes: number;
602
+ constructor(measuredBytes: number, budgetBytes?: number, options?: ErrorOptions);
603
+ }
386
604
  export type ResumeTailInspection = ResumeTailResumable | ResumeTailTerminal | ResumeTailError;
387
605
  export interface StrictSessionOpenSuccess {
388
606
  kind: "opened";
@@ -394,16 +612,42 @@ export interface StrictSessionOpenFailure {
394
612
  message?: string;
395
613
  size?: number;
396
614
  }
397
- /** Exact transcript bytes and authority captured from one descriptor-bound read. */
398
- export interface CapturedSessionTranscriptSnapshot {
399
- sourcePath: string;
400
- identity: ResumeSessionIdentity;
401
- content: Uint8Array;
402
- storage: SessionStorage;
615
+ /**
616
+ * Descriptor-bound strict-capture handle. Bounded recorded-length range reads
617
+ * revalidate the live source against the captured identity on every pass; no
618
+ * whole-transcript buffer is ever materialized by capture or fork.
619
+ */
620
+ export interface TranscriptSnapshotHandle {
621
+ readonly sourcePath: string;
622
+ readonly identity: ResumeSessionIdentity;
623
+ readonly storage: SessionStorage;
624
+ /**
625
+ * Iterate every transcript line in bounded recorded-length reads. Each line
626
+ * is delivered without its trailing newline; returning `false` aborts the
627
+ * pass. A completed pass re-validates the running content hash against the
628
+ * captured identity and throws `identity-mismatch` on divergence.
629
+ */
630
+ forEachLine(callback: (line: Uint8Array) => boolean | undefined): boolean;
631
+ /** Descriptor captured after the most recent complete line pass, when supported. */
632
+ getLastReadStat(): SessionStorageStat | undefined;
633
+ /** Revalidate the live source against the captured identity (bounded). */
634
+ revalidate(): {
635
+ kind: "valid";
636
+ } | StrictSessionOpenFailure;
637
+ /** Idempotent close; subsequent reads throw. */
638
+ close(): void;
639
+ /**
640
+ * Rehydrate the full transcript bytes for bounded-full-return consumers.
641
+ * This is an explicit compatibility escape hatch, never used by fork/capture
642
+ * publication itself.
643
+ */
644
+ materialize(): Uint8Array;
403
645
  }
646
+ /** @deprecated Use {@link TranscriptSnapshotHandle}; retained for call-site compatibility. */
647
+ export type CapturedSessionTranscriptSnapshot = TranscriptSnapshotHandle;
404
648
  export type StrictSessionCaptureResult = {
405
649
  kind: "captured";
406
- snapshot: CapturedSessionTranscriptSnapshot;
650
+ snapshot: TranscriptSnapshotHandle;
407
651
  } | ResumeTailError;
408
652
  export type StrictSessionForkResult = {
409
653
  kind: "forked";
@@ -521,11 +765,6 @@ export declare function migrateSessionEntries(entries: FileEntry[]): void;
521
765
  /** Exported for compaction.test.ts */
522
766
  export declare function parseSessionEntries(content: string): FileEntry[];
523
767
  export declare function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null;
524
- /**
525
- * Build the session context from entries using tree traversal.
526
- * If leafId is provided, walks from that entry to root.
527
- * Handles compaction and branch summaries along the path.
528
- */
529
768
  export declare function buildSessionContext(entries: SessionEntry[], leafId?: string | null, byId?: Map<string, SessionEntry>, sessionIdentityNamespace?: string): SessionContext;
530
769
  /** A session directory's authority is distinct from its string path. */
531
770
  export type SessionDestination = {
@@ -550,6 +789,7 @@ type ForkArtifactPublication = {
550
789
  };
551
790
  /** Exported for testing */
552
791
  export declare function loadEntriesFromFile(filePath: string, storage?: SessionStorage): Promise<FileEntry[]>;
792
+ export declare const TRANSCRIPT_CAPTURE_CHUNK_BYTES: number;
553
793
  declare class RecentSessionInfo {
554
794
  #private;
555
795
  readonly path: string;
@@ -641,7 +881,27 @@ export declare const SessionManagerTestHooks: {
641
881
  beforeResidentTransitionIndexBuild?: () => void;
642
882
  afterForkSnapshot?: () => void | Promise<void>;
643
883
  beforeEphemeralArtifactManagerInstall?: (dir: string) => void | Promise<void>;
884
+ beforePersistPatchFence?: (attempt: number) => void;
644
885
  };
886
+ /**
887
+ * Freshness snapshot captured with every async whole-session persistence
888
+ * preparation. Immediately before the synchronous persistence transaction, the
889
+ * live values are compared: a `sessionFile`/`lifecycleId` change aborts (lifecycle
890
+ * switch); a revision change discards the prepared bytes and re-prepares (bounded),
891
+ * so a stale snapshot is never published.
892
+ */
893
+ export interface PersistenceInputToken {
894
+ /** Canonical session file path the prepared bytes target. */
895
+ sessionFile: string;
896
+ /** Per-manager lifecycle identity (`sessionId@sessionFile`); changes on switch/open/reset. */
897
+ lifecycleId: string;
898
+ /** Revision of #fileEntries affecting persisted bytes. */
899
+ entryRevision: number;
900
+ /** Header/version revision affecting persisted bytes. */
901
+ headerRevision: number;
902
+ /** Resident/blob store revision affecting persisted bytes. */
903
+ residentBlobRevision: number;
904
+ }
645
905
  export declare class SessionManager {
646
906
  #private;
647
907
  private cwd;
@@ -753,6 +1013,8 @@ export declare class SessionManager {
753
1013
  listForResumePickerReadOnly(): Promise<SessionInfo[]>;
754
1014
  getSessionId(): string;
755
1015
  getSessionFile(): string | undefined;
1016
+ getSessionMemoryStats(): SessionMemoryStats;
1017
+ setSessionMemoryMode(mode: "off" | "shadow" | "enabled"): void;
756
1018
  acquireMemoryGuardParticipantIngressLease(): MemoryGuardParticipantIngressLease;
757
1019
  createMemoryGuardCheckpoint(input: MemoryGuardCreateCheckpointInput): Promise<MemoryGuardSessionManagerCheckpointV1>;
758
1020
  /**
@@ -938,10 +1200,21 @@ export declare class SessionManager {
938
1200
  /**
939
1201
  * Get the most recent model role from the current session path.
940
1202
  * Returns undefined if no model change has been recorded.
1203
+ *
1204
+ * R1: keyed ONLY on the nearest `model_change` on the leaf→root path — never on
1205
+ * `hasExplicitDefaultModel`, which gates only legacy assistant inference into
1206
+ * `models.default` inside `buildSessionContext`. Six parity cases (D1): reviewer-
1207
+ * only → "reviewer"; temporary-only → "temporary"; interleaved → nearest;
1208
+ * no model_change → undefined; legacy-only inference → undefined; explicit
1209
+ * default then legacy inference → "default".
941
1210
  */
942
1211
  getLastModelChangeRole(): string | undefined;
943
1212
  evictCompactedContent(firstKeptEntryId: string, compactionEntryId: string): EvictCompactedContentResult;
944
1213
  getObservabilityStatsForTests(): SessionManagerObservabilityStats;
1214
+ setSidecarHotSuffixBudgetForTests(bytes: number): void;
1215
+ parentChildrenCacheKeysForTests(): string[];
1216
+ parentArtifactEnabledForTests(): boolean;
1217
+ setParentArtifactBudgetForTests(bytes: number): void;
945
1218
  hotRetainedMessageCharsForTests(): number;
946
1219
  getCanonicalEntryForTests(id: string): SessionEntry | undefined;
947
1220
  getEntryForFidelity(id: string): SessionEntry | undefined;
@@ -952,6 +1225,7 @@ export declare class SessionManager {
952
1225
  * sync; callers must not mutate returned entries.
953
1226
  */
954
1227
  getActivePathEntriesCanonical(fromId?: string): SessionEntry[];
1228
+ visitEntriesForExport(visitor: (entry: SessionEntry) => void): void;
955
1229
  getEntriesForExport(): SessionEntry[];
956
1230
  getEntry(id: string): SessionEntry | undefined;
957
1231
  /**
@@ -1046,14 +1320,14 @@ export declare class SessionManager {
1046
1320
  * Fork a session into the current project directory.
1047
1321
  * Copies history from another session file while creating a new session file in the current sessionDir.
1048
1322
  */
1049
- static forkFrom(sourcePath: string, cwd: string, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy): Promise<SessionManager>;
1323
+ static forkFrom(sourcePath: string, cwd: string, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy, sessionMemoryMode?: "off" | "shadow" | "enabled"): Promise<SessionManager>;
1050
1324
  /**
1051
1325
  * Open a specific session file.
1052
1326
  * @param path Path to session file
1053
1327
  * @param sessionDir Optional session directory for /new or /branch. If omitted, derives from file's parent.
1054
1328
  */
1055
- static open(filePath: string, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy): Promise<SessionManager>;
1056
- static openNestedManaged(filePath: string, destination: SessionDestination, store: ManagedSessionDescendantStore, storage?: SessionStorage, cwdOverride?: string): Promise<SessionManager>;
1329
+ static open(filePath: string, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy, sessionMemoryMode?: "off" | "shadow" | "enabled"): Promise<SessionManager>;
1330
+ static openNestedManaged(filePath: string, destination: SessionDestination, store: ManagedSessionDescendantStore, storage?: SessionStorage, cwdOverride?: string, sessionMemoryMode?: "off" | "shadow" | "enabled"): Promise<SessionManager>;
1057
1331
  /**
1058
1332
  * List default-managed sessions for the resume picker without recovery or other
1059
1333
  * maintenance writes. This is the only picker inventory that includes legacy
@@ -1075,7 +1349,7 @@ export declare class SessionManager {
1075
1349
  * revalidate captured authority before destination initialization and transcript
1076
1350
  * persistence; destination history always comes from the captured bytes.
1077
1351
  */
1078
- static forkFromCaptured(snapshot: CapturedSessionTranscriptSnapshot, cwd: string, destinationInput?: SessionDestinationInput, _migrationPolicy?: SessionDirectoryMigrationPolicy): Promise<StrictSessionForkResult>;
1352
+ static forkFromCaptured(snapshot: CapturedSessionTranscriptSnapshot, cwd: string, destinationInput?: SessionDestinationInput, _migrationPolicy?: SessionDirectoryMigrationPolicy, sessionMemoryMode?: "off" | "shadow" | "enabled"): Promise<StrictSessionForkResult>;
1079
1353
  static restoreMemoryGuardCheckpoint(input: MemoryGuardRestoreInput): Promise<MemoryGuardRestoreResult>;
1080
1354
  /** Inspect a selected session without acquiring write-capable ownership. */
1081
1355
  static inspectSessionTailReadOnly(filePath: string, storage?: SessionStorage): Promise<ResumeTailInspection>;
@@ -1096,13 +1370,13 @@ export declare class SessionManager {
1096
1370
  * the returned discriminant; an error result never creates, rewrites, or adopts
1097
1371
  * the selected path. Breadcrumb ownership begins only after `kind: "opened"`.
1098
1372
  */
1099
- static openExistingStrict(identity: ResumeSessionIdentity, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy): Promise<StrictSessionOpenResult>;
1373
+ static openExistingStrict(identity: ResumeSessionIdentity, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy, sessionMemoryMode?: "off" | "shadow" | "enabled"): Promise<StrictSessionOpenResult>;
1100
1374
  /**
1101
1375
  * Continue the most recent session, or create new if none.
1102
1376
  * @param cwd Working directory
1103
1377
  * @param sessionDir Optional session directory. If omitted, uses default (~/.gjc/agent/sessions/<encoded-cwd>/).
1104
1378
  */
1105
- static continueRecent(cwd: string, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy): Promise<SessionManager>;
1379
+ static continueRecent(cwd: string, destinationInput?: SessionDestinationInput, storage?: SessionStorage, migrationPolicy?: SessionDirectoryMigrationPolicy, sessionMemoryMode?: "off" | "shadow" | "enabled"): Promise<SessionManager>;
1106
1380
  /** Create an in-memory session (no file persistence) */
1107
1381
  static inMemory(cwd?: string, storage?: SessionStorage): SessionManager;
1108
1382
  /**