laohuang 0.9.2 → 0.9.3

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 (3) hide show
  1. package/dist/bin.js +1732 -1696
  2. package/dist/bin.js.map +4 -4
  3. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -7368,342 +7368,156 @@ var require_dist = __commonJS({
7368
7368
  }
7369
7369
  });
7370
7370
 
7371
+ // src/sqlite-warning-filter.ts
7372
+ var sqliteWarning = "SQLite is an experimental feature and might change at any time";
7373
+ process.emit = new Proxy(process.emit, {
7374
+ apply(target, receiver, args) {
7375
+ const [event, warning] = args;
7376
+ if (event === "warning" && warning instanceof Error && warning.name === "ExperimentalWarning" && warning.message === sqliteWarning && !("code" in warning)) {
7377
+ return false;
7378
+ }
7379
+ return Reflect.apply(target, receiver, args);
7380
+ }
7381
+ });
7382
+
7371
7383
  // src/main.ts
7372
7384
  import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
7373
7385
  import { dirname as dirname13, join as join20 } from "node:path";
7374
7386
  import { fileURLToPath } from "node:url";
7375
7387
 
7376
7388
  // ../../packages/session/session-context/dist/conversation-history.js
7377
- import { randomUUID } from "node:crypto";
7389
+ import { randomUUID as randomUUID2 } from "node:crypto";
7378
7390
 
7379
7391
  // ../../packages/session/session-context/dist/token-estimator.js
7380
- import { createHash } from "node:crypto";
7381
- var TEXT_BYTES_PER_TOKEN = 4;
7382
- var BLOCK_OVERHEAD = 4;
7383
- var MESSAGE_OVERHEAD = 4;
7384
- var DefaultTokenEstimator = class {
7385
- estimateMessages(messages) {
7386
- return messages.reduce((total, message) => total + this.estimateMessage(message), 0);
7387
- }
7388
- estimateTools(tools) {
7389
- if (tools.length === 0) {
7390
- return 0;
7391
- }
7392
- return this.estimateText(canonicalJson(tools.map(({ name, description, parameters }) => ({ name, description, parameters })))) + BLOCK_OVERHEAD;
7393
- }
7394
- estimateText(text3) {
7395
- return Math.ceil(Buffer.byteLength(text3, "utf8") / TEXT_BYTES_PER_TOKEN);
7396
- }
7397
- measure(input) {
7398
- const anchor = input.anchor ?? null;
7399
- if (anchor !== null && this.isValidAnchor(input, anchor)) {
7400
- const trailingEntries = input.entries.filter((entry) => entry.seq > anchor.throughSeq);
7401
- const trailingTokens = this.estimateMessages(messagesForEntries(trailingEntries));
7402
- return {
7403
- totalTokens: anchor.contextTokens + trailingTokens,
7404
- anchorTokens: anchor.contextTokens,
7405
- trailingTokens,
7406
- source: "provider_usage"
7407
- };
7408
- }
7409
- const totalTokens = this.estimateMessages(input.messages) + this.estimateTools(input.tools);
7410
- return {
7411
- totalTokens,
7412
- anchorTokens: 0,
7413
- trailingTokens: totalTokens,
7414
- source: "estimated"
7415
- };
7392
+ import { createHash as createHash2 } from "node:crypto";
7393
+
7394
+ // ../../packages/attachment/attachment/dist/errors.js
7395
+ var AttachmentError = class extends Error {
7396
+ code;
7397
+ constructor(code, message, options) {
7398
+ super(message, options);
7399
+ this.name = "AttachmentError";
7400
+ this.code = code;
7416
7401
  }
7417
- estimateMessage(message) {
7418
- const attachmentTokens = message.role === "user" || message.role === "tool-result" ? (message.attachments ?? []).reduce((sum, block, index) => sum + (block.type === "image" && !message.omittedImageIndexes?.includes(index) ? 85 + 170 * Math.ceil(block.ref.width / 512) * Math.ceil(block.ref.height / 512) : 32), 0) : 0;
7419
- if (message.role === "system" && message.toolDefinitions !== void 0)
7420
- return 0;
7421
- if (message.role === "system" || message.role === "user") {
7422
- return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content) + attachmentTokens;
7423
- }
7424
- if (message.role === "tool-result") {
7425
- return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content) + attachmentTokens;
7402
+ };
7403
+
7404
+ // ../../packages/attachment/attachment/dist/types.js
7405
+ var DEFAULT_IMAGE_LIMITS = Object.freeze({
7406
+ maxBytes: 20 * 1024 ** 2,
7407
+ maxImages: 20,
7408
+ maxBatchBytes: 200 * 1024 ** 2,
7409
+ maxPixels: 64e6,
7410
+ maxDimension: 8192
7411
+ });
7412
+ var RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
7413
+ var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
7414
+ function assertAttachmentContent(value) {
7415
+ if (!value || typeof value !== "object")
7416
+ throw new AttachmentError("INVALID_REFERENCE", "Invalid attachment content");
7417
+ const block = value;
7418
+ if (block.type !== "image" && block.type !== "file")
7419
+ throw new AttachmentError("INVALID_REFERENCE", "Invalid attachment kind");
7420
+ assertFileRef(block.ref);
7421
+ if (block.type === "image") {
7422
+ const ref = block.ref;
7423
+ if (!["image/png", "image/jpeg", "image/webp", "image/gif"].includes(String(ref.mimeType)) || !Number.isSafeInteger(ref.width) || Number(ref.width) < 1 || !Number.isSafeInteger(ref.height) || Number(ref.height) < 1 || typeof ref.animated !== "boolean") {
7424
+ throw new AttachmentError("INVALID_REFERENCE", "Invalid image reference");
7426
7425
  }
7427
- return MESSAGE_OVERHEAD + message.content.reduce((total, block) => {
7428
- if (block.type === "text" || block.type === "reasoning") {
7429
- return total + BLOCK_OVERHEAD + this.estimateText(block.text);
7430
- }
7431
- return total + BLOCK_OVERHEAD + this.estimateText(`${block.call.name}${canonicalJson(block.call.arguments)}`);
7432
- }, 0);
7433
- }
7434
- isValidAnchor(input, anchor) {
7435
- return anchor.provider === input.provider && anchor.model === input.model && anchor.systemFingerprint === input.systemFingerprint && anchor.projectInstructionsFingerprint === input.projectInstructionsFingerprint && anchor.toolsFingerprint === input.toolsFingerprint && input.entries.some((entry) => entry.id === anchor.throughEntryId && entry.seq === anchor.throughSeq);
7426
+ if (block.view !== void 0)
7427
+ assertImageView(block.view);
7436
7428
  }
7437
- };
7438
- function normalizeProviderUsage(usage) {
7439
- return usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0) + usage.outputTokens;
7440
- }
7441
- function fingerprintContextPart(value) {
7442
- return createHash("sha256").update(canonicalJson(value)).digest("hex");
7443
- }
7444
- function canonicalJson(value) {
7445
- return JSON.stringify(sortStable(value));
7446
7429
  }
7447
- function sortStable(value) {
7448
- if (Array.isArray(value)) {
7449
- return value.map(sortStable);
7450
- }
7451
- if (value !== null && typeof value === "object") {
7452
- const record = value;
7453
- const sorted = {};
7454
- for (const key of Object.keys(record).sort()) {
7455
- sorted[key] = sortStable(record[key]);
7456
- }
7457
- return sorted;
7430
+ function assertFileRef(value) {
7431
+ const ref = value;
7432
+ if (!ref || typeof ref !== "object" || typeof ref.id !== "string" || !/^sha256:[a-f0-9]{64}$/.test(ref.id) || !Number.isSafeInteger(ref.bytes) || Number(ref.bytes) < 0 || typeof ref.name !== "string") {
7433
+ throw new AttachmentError("INVALID_REFERENCE", "Invalid attachment reference");
7458
7434
  }
7459
- return value;
7460
7435
  }
7461
- function messagesForEntries(entries) {
7462
- return entries.flatMap((entry) => {
7463
- if (entry.entryType === "tool_definitions" || entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
7464
- return [entry.payload.message];
7465
- if (entry.entryType === "system_context") {
7466
- return [entry.payload.message];
7467
- }
7468
- if (entry.entryType === "project_instructions" || entry.entryType === "user_message" || entry.entryType === "reminder") {
7469
- return [entry.payload.message];
7470
- }
7471
- if (entry.entryType === "assistant_message") {
7472
- return [entry.payload.message];
7473
- }
7474
- if (entry.entryType === "tool_result") {
7475
- return [entry.payload.message];
7436
+ function assertImageView(value) {
7437
+ if (!value || typeof value !== "object" || Array.isArray(value))
7438
+ throw new AttachmentError("INVALID_REFERENCE", "Invalid image view");
7439
+ const view = value;
7440
+ if (view.fullResolution !== void 0 && typeof view.fullResolution !== "boolean")
7441
+ throw new AttachmentError("INVALID_REFERENCE", "Invalid fullResolution");
7442
+ if (view.region !== void 0) {
7443
+ const r = view.region;
7444
+ if (!r || typeof r !== "object" || !["x", "y", "width", "height"].every((k) => Number.isSafeInteger(r[k])) || Number(r.x) < 0 || Number(r.y) < 0 || Number(r.width) < 1 || Number(r.height) < 1) {
7445
+ throw new AttachmentError("INVALID_REFERENCE", "Invalid crop region");
7476
7446
  }
7477
- return [];
7478
- });
7447
+ }
7479
7448
  }
7480
7449
 
7481
- // ../../packages/session/session-context/dist/context-usage.js
7482
- var ContextUsage = class {
7483
- #tokens = 0;
7484
- constructor(entries = []) {
7485
- for (const entry of entries)
7486
- this.append(entry);
7450
+ // ../../packages/attachment/attachment/dist/attachment-store.js
7451
+ var AttachmentStore = class {
7452
+ };
7453
+
7454
+ // ../../packages/session/session-store/dist/session-metadata.js
7455
+ function normalizeSessionTitle(input) {
7456
+ if (new RegExp("\\p{Cc}|\\u2028|\\u2029", "u").test(input)) {
7457
+ throw new Error("Session name must be a single line without control characters.");
7487
7458
  }
7488
- get tokens() {
7489
- return this.#tokens;
7459
+ const title = input.trim();
7460
+ if (title.length === 0) {
7461
+ throw new Error("Session name must not be empty.");
7490
7462
  }
7491
- /** Histories are append-only and ordered; process each committed entry once. */
7492
- append(entry) {
7493
- if (entry.entryType === "compaction" || entry.entryType === "image_offload") {
7494
- this.#tokens = null;
7495
- return;
7463
+ return title;
7464
+ }
7465
+ function sessionTitleAt(items, throughSeq = Infinity) {
7466
+ let title = null;
7467
+ for (const item of items) {
7468
+ if (item.seq > throughSeq) {
7469
+ break;
7496
7470
  }
7497
- if (entry.entryType === "assistant_message") {
7498
- const usage = validUsageTokens(entry.payload.usage);
7499
- if (usage !== null) {
7500
- this.#tokens = usage;
7501
- return;
7471
+ if (item.kind === "record" && item.recordType === "session_name_changed") {
7472
+ const value = item.payload["title"];
7473
+ if (typeof value !== "string") {
7474
+ throw new Error("Invalid session name record.");
7502
7475
  }
7503
- }
7504
- if (this.#tokens === null)
7505
- return;
7506
- if (entry.entryType === "user_message" || entry.entryType === "assistant_message" || entry.entryType === "tool_result" || entry.entryType === "reminder" || entry.entryType === "skill_context") {
7507
- this.#tokens += estimateMessage(entry.payload.message);
7476
+ title = normalizeSessionTitle(value);
7508
7477
  }
7509
7478
  }
7510
- };
7511
- function validUsageTokens(usage) {
7512
- if (usage === void 0 || usage === null)
7513
- return null;
7514
- const fields = [
7515
- usage.inputTokens,
7516
- usage.outputTokens,
7517
- usage.cacheReadTokens ?? 0,
7518
- usage.cacheWriteTokens ?? 0
7519
- ];
7520
- if (!fields.every((value) => Number.isSafeInteger(value) && value >= 0))
7521
- return null;
7522
- const total = normalizeProviderUsage(usage);
7523
- return Number.isSafeInteger(total) && total > 0 ? total : null;
7524
- }
7525
- function estimateMessage(message) {
7526
- if (message.role === "system")
7527
- return 0;
7528
- if (message.role !== "assistant")
7529
- return Math.ceil(message.content.length / 4);
7530
- const chars = message.content.reduce((sum, block) => {
7531
- if (block.type === "tool-call") {
7532
- return sum + block.call.name.length + JSON.stringify(block.call.arguments).length;
7533
- }
7534
- return sum + block.text.length;
7535
- }, 0);
7536
- return Math.ceil(chars / 4);
7479
+ return title;
7537
7480
  }
7538
7481
 
7539
- // ../../packages/session/session-context/dist/conversation-history.js
7540
- var ConversationHistory = class _ConversationHistory {
7541
- #journal;
7542
- #entries;
7543
- #staged = /* @__PURE__ */ new Map();
7544
- #contextUsage;
7545
- #listeners = /* @__PURE__ */ new Set();
7546
- constructor(entries, journal) {
7547
- this.#entries = [...entries];
7548
- this.#journal = journal;
7549
- this.#contextUsage = new ContextUsage(entries);
7550
- }
7551
- static fromReplay(replay, journal) {
7552
- const history = new _ConversationHistory(replay.items.filter((item) => item.kind === "entry"), journal);
7553
- for (const open6 of replay.openToolCalls) {
7554
- const entry = history.appendToolResults({
7555
- requestId: "recovered",
7556
- recovered: true,
7557
- messages: [{
7558
- role: "tool-result",
7559
- toolCallId: open6.toolCallId,
7560
- toolName: open6.toolName,
7561
- content: JSON.stringify({
7562
- ok: false,
7563
- status: "interrupted",
7564
- error: "Tool execution was interrupted before a result was recorded."
7565
- }),
7566
- isError: true
7567
- }]
7568
- })[0];
7569
- journal.appendRecord({
7570
- recordType: "error",
7571
- payload: {
7572
- type: "interrupted_tool_call",
7573
- toolCallId: open6.toolCallId,
7574
- repairedEntryId: entry?.id ?? null
7575
- }
7576
- });
7577
- }
7578
- return history;
7579
- }
7580
- appendSystemContext(input) {
7581
- return this.append({
7582
- entryType: "system_context",
7583
- payload: input
7584
- });
7585
- }
7586
- appendProjectInstructions(input) {
7587
- if (input.message.content === "") {
7588
- return null;
7589
- }
7590
- return this.append({
7591
- entryType: "project_instructions",
7592
- payload: {
7593
- message: input.message,
7594
- files: input.files,
7595
- supersedesEntryIds: input.supersedesEntryIds ?? []
7596
- }
7597
- });
7598
- }
7599
- appendUser(input) {
7600
- return this.append({
7601
- entryType: "user_message",
7602
- payload: input
7603
- });
7604
- }
7605
- stageUser(input) {
7606
- const token = randomUUID().replaceAll("-", "");
7607
- this.#staged.set(token, input);
7608
- return {
7609
- token,
7610
- message: input.message,
7611
- inputEventIds: input.inputEventIds
7612
- };
7613
- }
7614
- commitStagedUser(token) {
7615
- const staged = this.#staged.get(token);
7616
- if (staged === void 0) {
7617
- throw new Error(`unknown staged user message: ${token}`);
7618
- }
7619
- this.#staged.delete(token);
7620
- return this.appendUser(staged);
7621
- }
7622
- discardStagedUser(token) {
7623
- this.#staged.delete(token);
7624
- }
7625
- appendAssistant(input) {
7626
- return this.append({
7627
- entryType: "assistant_message",
7628
- payload: input
7629
- });
7630
- }
7631
- appendToolResults(input) {
7632
- return input.messages.map((message) => this.append({
7633
- entryType: "tool_result",
7634
- payload: {
7635
- message,
7636
- requestId: input.requestId,
7637
- recovered: input.recovered
7638
- }
7639
- }));
7640
- }
7641
- appendReminder(input) {
7642
- return this.append({
7643
- entryType: "reminder",
7644
- payload: input
7645
- });
7646
- }
7647
- appendToolDefinitions(input) {
7648
- return this.append({ entryType: "tool_definitions", payload: input });
7649
- }
7650
- appendToolCatalog(input) {
7651
- return this.append({ entryType: "tool_catalog", payload: input });
7652
- }
7653
- appendSkillContext(input) {
7654
- return this.append({ entryType: "skill_context", payload: input });
7655
- }
7656
- appendCompaction(input) {
7657
- return this.append({
7658
- entryType: "compaction",
7659
- payload: input
7660
- });
7661
- }
7662
- entries() {
7663
- return [...this.#entries];
7664
- }
7665
- offloadImages(keys2) {
7666
- if (keys2.length === 0)
7667
- return;
7668
- this.append({ entryType: "image_offload", payload: { keys: [...keys2] } });
7669
- this.#journal.flush();
7670
- }
7671
- get contextTokens() {
7672
- return this.#contextUsage.tokens;
7673
- }
7674
- onChange(listener) {
7675
- this.#listeners.add(listener);
7676
- return () => {
7677
- this.#listeners.delete(listener);
7678
- };
7679
- }
7680
- append(input) {
7681
- const entry = this.#journal.appendEntry(input);
7682
- this.#entries.push(entry);
7683
- this.#contextUsage.append(entry);
7684
- for (const listener of this.#listeners)
7685
- listener();
7686
- return entry;
7687
- }
7688
- };
7689
-
7690
- // ../../packages/llm/llm/dist/model-contracts.js
7691
- var ModelStreamError = class extends Error {
7692
- /** True when the stream had already delivered deltas before failing. */
7693
- hadDelta;
7694
- constructor(message, options = {}) {
7695
- super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
7696
- this.name = "ModelStreamError";
7697
- this.hadDelta = options.hadDelta ?? false;
7698
- }
7699
- };
7700
- var ModelStreamCancelled = class extends ModelStreamError {
7701
- constructor(message, options = {}) {
7702
- super(message, options);
7703
- this.name = "ModelStreamCancelled";
7704
- }
7705
- };
7706
- var REASONING_EFFORTS = [
7482
+ // ../../packages/session/session-store/dist/schema.js
7483
+ var ENTRY_TYPES = /* @__PURE__ */ new Set([
7484
+ "context_reset",
7485
+ "image_offload",
7486
+ "skill_context",
7487
+ "tool_definitions",
7488
+ "tool_catalog",
7489
+ "system_context",
7490
+ "project_instructions",
7491
+ "user_message",
7492
+ "assistant_message",
7493
+ "tool_result",
7494
+ "reminder",
7495
+ "compaction"
7496
+ ]);
7497
+ var RECORD_TYPES = /* @__PURE__ */ new Set([
7498
+ "turn_started",
7499
+ "turn_finished",
7500
+ "model_request",
7501
+ "model_response",
7502
+ "tool_started",
7503
+ "tool_finished",
7504
+ "provider_changed",
7505
+ "model_changed",
7506
+ "reasoning_effort_changed",
7507
+ "cwd_changed",
7508
+ "usage",
7509
+ "error",
7510
+ "cancelled",
7511
+ "queue_enqueued",
7512
+ "queue_started",
7513
+ "queue_finished",
7514
+ "compaction_started",
7515
+ "compaction_finished",
7516
+ "session_name_changed",
7517
+ "session_closed"
7518
+ ]);
7519
+ var ORIGINS = /* @__PURE__ */ new Set(["new", "fork", "clone", "import"]);
7520
+ var REASONING_EFFORTS = /* @__PURE__ */ new Set([
7707
7521
  "off",
7708
7522
  "minimal",
7709
7523
  "low",
@@ -7711,1590 +7525,1812 @@ var REASONING_EFFORTS = [
7711
7525
  "high",
7712
7526
  "xhigh",
7713
7527
  "max"
7714
- ];
7715
- function isReasoningEffort(value) {
7716
- return REASONING_EFFORTS.includes(value);
7717
- }
7718
- function clampReasoningEffort(supported, requested) {
7719
- const available = supported.length === 0 ? ["off"] : supported;
7720
- if (available.includes(requested)) {
7721
- return requested;
7528
+ ]);
7529
+ var SessionSchemaError = class extends Error {
7530
+ constructor(message) {
7531
+ super(message);
7532
+ this.name = "SessionSchemaError";
7722
7533
  }
7723
- const requestedIndex = REASONING_EFFORTS.indexOf(requested);
7724
- for (let index = requestedIndex; index < REASONING_EFFORTS.length; index += 1) {
7725
- const candidate = REASONING_EFFORTS[index];
7726
- if (available.includes(candidate)) {
7727
- return candidate;
7728
- }
7534
+ };
7535
+ function parseSessionHeader(value) {
7536
+ const input = objectOf(value, "session header");
7537
+ if (input["schemaVersion"] !== 1) {
7538
+ throw new SessionSchemaError("unsupported session schema version");
7729
7539
  }
7730
- for (let index = requestedIndex - 1; index >= 0; index -= 1) {
7731
- const candidate = REASONING_EFFORTS[index];
7732
- if (available.includes(candidate)) {
7733
- return candidate;
7734
- }
7540
+ if (input["type"] !== "session_header") {
7541
+ throw new SessionSchemaError("invalid session header type");
7735
7542
  }
7736
- return "off";
7737
- }
7738
- function projectImageOffloads(message, keys2) {
7739
- if (message.role !== "user" && message.role !== "tool-result" || !message.attachments?.length)
7740
- return message;
7741
- const omittedImageIndexes = message.attachments.flatMap((block, index) => block.type === "image" && keys2.has(`${message.attachmentKey}:${index}`) ? [index] : []);
7742
- if (!omittedImageIndexes.length)
7743
- return message;
7744
- return { ...message, omittedImageIndexes };
7745
- }
7746
- var ModelError = class extends ModelStreamError {
7747
- kind;
7748
- constructor(message, options) {
7749
- super(message, { hadDelta: options.hadDelta ?? false, cause: options.cause });
7750
- this.name = "ModelError";
7751
- this.kind = options.kind;
7543
+ requireString(input, "sessionId");
7544
+ requireString(input, "createdAt");
7545
+ requireString(input, "initialCwd");
7546
+ requireString(input, "projectRoot");
7547
+ requireString(input, "projectKey");
7548
+ requireString(input, "appVersion");
7549
+ requireString(input, "provider");
7550
+ requireString(input, "model");
7551
+ if (!REASONING_EFFORTS.has(stringValue(input, "reasoningEffort"))) {
7552
+ throw new SessionSchemaError("invalid reasoning effort");
7752
7553
  }
7753
- };
7754
- var CONTEXT_OVERFLOW_PATTERN = /context length|maximum context|context window|too many tokens|reduce the length/i;
7755
- var NETWORK_PATTERN = /fetch failed|network|econnreset|econnrefused|etimedout|socket hang up|timed out/i;
7756
- function readStatus(record) {
7757
- let status = record["status_code"];
7758
- if (status === null || status === void 0) {
7759
- const response = record["response"];
7760
- if (response !== null && typeof response === "object") {
7761
- status = response["status_code"];
7762
- }
7554
+ if (!ORIGINS.has(stringValue(input, "origin"))) {
7555
+ throw new SessionSchemaError("invalid session origin");
7763
7556
  }
7764
- if (status === null || status === void 0) {
7765
- status = record["status"];
7557
+ if (input["parentSessionId"] !== void 0) {
7558
+ requireString(input, "parentSessionId");
7766
7559
  }
7767
- return status;
7768
- }
7769
- function classifyModelError(error) {
7770
- let current = error;
7771
- const seen = /* @__PURE__ */ new Set();
7772
- while (current !== null && current !== void 0 && (typeof current === "object" || typeof current === "function") && !seen.has(current)) {
7773
- seen.add(current);
7774
- const record = current;
7775
- const status = readStatus(record);
7776
- if (status === 401 || status === 403 || record["name"] === "AuthenticationError") {
7777
- return "authentication";
7560
+ if (input["forkedFrom"] !== void 0) {
7561
+ const forkedFrom = objectOf(input["forkedFrom"], "forkedFrom");
7562
+ requireString(forkedFrom, "sessionId");
7563
+ if (forkedFrom["seq"] !== void 0) {
7564
+ requirePositiveInteger(forkedFrom, "seq");
7778
7565
  }
7779
- if (status === 429 || record["name"] === "RateLimitError") {
7780
- return "rate_limited";
7566
+ if (forkedFrom["entryId"] !== void 0) {
7567
+ requireString(forkedFrom, "entryId");
7781
7568
  }
7782
- const message = typeof record["message"] === "string" ? record["message"] : "";
7783
- if (status === 408 || status === 504 || record["name"] === "TimeoutError" || /\btimeout\b|\btimed out\b/i.test(message)) {
7784
- return "timeout";
7785
- }
7786
- if (CONTEXT_OVERFLOW_PATTERN.test(message)) {
7787
- return "context_overflow";
7788
- }
7789
- if (typeof status === "number" && status >= 500) {
7790
- return "server";
7791
- }
7792
- if (record["name"] === "APIConnectionError" || NETWORK_PATTERN.test(message)) {
7793
- return "retryable";
7569
+ const mode = stringValue(forkedFrom, "mode");
7570
+ if (mode !== "before" && mode !== "at" && mode !== "clone") {
7571
+ throw new SessionSchemaError("invalid fork mode");
7794
7572
  }
7795
- current = record["cause"];
7796
7573
  }
7797
- return "retryable";
7798
- }
7799
- function modelErrorKind(error) {
7800
- return error instanceof ModelError ? error.kind : classifyModelError(error);
7574
+ return input;
7801
7575
  }
7802
- function portableModelMessage(message) {
7803
- if (message.role !== "assistant") {
7804
- return message;
7576
+ function parseSessionItem(value, expectedSessionId) {
7577
+ const input = objectOf(value, "session item");
7578
+ if (input["schemaVersion"] !== 1) {
7579
+ throw new SessionSchemaError("unsupported session schema version");
7805
7580
  }
7806
- return {
7807
- role: "assistant",
7808
- provider: message.provider,
7809
- model: message.model,
7810
- content: message.content
7811
- };
7812
- }
7813
-
7814
- // ../../packages/llm/llm/dist/model-runtime.js
7815
- var ModelRuntime = class {
7816
- adapter;
7817
- retryDelaysMs;
7818
- sleep;
7819
- constructor(adapter, options = {}) {
7820
- this.adapter = adapter;
7821
- this.retryDelaysMs = validateRetryDelays(options.retryDelaysMs ?? [250, 1e3]);
7822
- this.sleep = options.sleep ?? defaultSleep;
7581
+ const sessionId = requireString(input, "sessionId");
7582
+ if (expectedSessionId !== void 0 && sessionId !== expectedSessionId) {
7583
+ throw new SessionSchemaError("session id does not match header");
7823
7584
  }
7824
- async complete(request) {
7825
- if (request.cancelToken?.isCancelled()) {
7826
- throw new ModelStreamCancelled(request.cancelToken.reason || "cancelled");
7585
+ requirePositiveInteger(input, "seq");
7586
+ requireString(input, "id");
7587
+ requireString(input, "timestamp");
7588
+ const kind = stringValue(input, "kind");
7589
+ if (kind === "entry") {
7590
+ const entryType = stringValue(input, "entryType");
7591
+ if (!ENTRY_TYPES.has(entryType)) {
7592
+ throw new SessionSchemaError("invalid session entry type");
7827
7593
  }
7828
- const maxAttempts = validateMaxAttempts(request.maxAttempts ?? 3);
7829
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
7830
- const adapterRequest = {
7831
- ...request.imageContext === void 0 ? {} : { imageContext: request.imageContext },
7832
- provider: request.provider,
7833
- model: request.model,
7834
- ...request.baseUrl === void 0 ? {} : { baseUrl: request.baseUrl },
7835
- messages: request.messages,
7836
- tools: request.tools,
7837
- ...request.reasoningEffort === void 0 ? {} : { reasoningEffort: request.reasoningEffort },
7838
- ...request.temperature === void 0 ? {} : { temperature: request.temperature },
7839
- ...request.maxOutputTokens === void 0 ? {} : { maxOutputTokens: request.maxOutputTokens },
7840
- ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs },
7841
- ...request.requestId === void 0 ? {} : { requestId: request.requestId },
7842
- ...request.cancelToken == null ? {} : { cancelToken: request.cancelToken },
7843
- ...request.isRequestActive == null ? {} : { isRequestActive: request.isRequestActive },
7844
- ...request.onDelta == null ? {} : { onEvent: (event) => forwardEvent(event, request.onDelta) },
7845
- ...request.onRequestOpened == null ? {} : { onRequestOpened: request.onRequestOpened }
7846
- };
7594
+ const entryPayload = objectOf(input["payload"], "entry payload");
7595
+ if (entryType === "context_reset") {
7596
+ const boundary = entryPayload.resetThroughSeq;
7597
+ if (typeof boundary !== "number" || !Number.isSafeInteger(boundary) || boundary < 0 || boundary >= input.seq || entryPayload.reason !== "user_clear" || Object.keys(entryPayload).some((key) => key !== "resetThroughSeq" && key !== "reason")) {
7598
+ throw new SessionSchemaError("invalid legacy context reset");
7599
+ }
7600
+ }
7601
+ if (entryType === "image_offload" && (!Array.isArray(entryPayload.keys) || entryPayload.keys.some((key) => typeof key !== "string"))) {
7602
+ throw new SessionSchemaError("invalid image offload keys");
7603
+ }
7604
+ if (entryPayload.message !== void 0) {
7605
+ const message = objectOf(entryPayload.message, "message");
7606
+ if (message.attachments !== void 0) {
7607
+ if (message.role !== "user" && message.role !== "tool-result" || !Array.isArray(message.attachments))
7608
+ throw new SessionSchemaError("invalid message attachments");
7609
+ for (const attachment of message.attachments)
7610
+ assertAttachmentContent(attachment);
7611
+ if (typeof message.attachmentKey !== "string" || !message.attachmentKey)
7612
+ throw new SessionSchemaError("missing attachment occurrence key");
7613
+ }
7614
+ }
7615
+ if (entryType === "skill_context") {
7616
+ const payload = objectOf(input["payload"], "skill payload");
7617
+ const message = objectOf(payload["message"], "skill message");
7618
+ const metadata2 = objectOf(message["skillContext"], "skill metadata");
7619
+ if (message["role"] !== "user" || typeof message["content"] !== "string" || !["catalog", "activation", "invalidation"].includes(String(metadata2["kind"])) || typeof metadata2["key"] !== "string")
7620
+ throw new SessionSchemaError("invalid skill context");
7621
+ }
7622
+ if (entryType === "tool_definitions" || entryType === "tool_catalog") {
7623
+ const payload = objectOf(input["payload"], "tool context payload");
7624
+ const message = objectOf(payload["message"], "tool context message");
7625
+ if (typeof message["content"] !== "string")
7626
+ throw new SessionSchemaError("tool context content must be text");
7627
+ if (entryType === "tool_definitions") {
7628
+ if (message["role"] !== "system" || !Array.isArray(message["toolDefinitions"]))
7629
+ throw new SessionSchemaError("invalid tool definitions message");
7630
+ for (const raw of message["toolDefinitions"]) {
7631
+ const definition = objectOf(raw, "loaded tool");
7632
+ requireString(definition, "version");
7633
+ const spec2 = objectOf(definition["spec"], "tool spec");
7634
+ requireString(spec2, "name");
7635
+ if (typeof spec2["description"] !== "string" || !Array.isArray(spec2["promptGuidelines"]) || spec2["promptGuidelines"].some((value2) => typeof value2 !== "string"))
7636
+ throw new SessionSchemaError("invalid tool spec");
7637
+ objectOf(spec2["parameters"], "tool parameters");
7638
+ if (spec2["catalog"] !== void 0) {
7639
+ const catalog = objectOf(spec2["catalog"], "tool catalog metadata");
7640
+ for (const key of ["source", "originalName", "binding"])
7641
+ requireString(catalog, key);
7642
+ if (catalog["exposure"] !== "direct" && catalog["exposure"] !== "deferred")
7643
+ throw new SessionSchemaError("invalid exposure");
7644
+ }
7645
+ }
7646
+ } else {
7647
+ if (message["role"] !== "user")
7648
+ throw new SessionSchemaError("invalid catalog message role");
7649
+ const catalog = objectOf(message["toolCatalog"], "tool catalog");
7650
+ if (catalog["mode"] !== "full" && catalog["mode"] !== "deferred")
7651
+ throw new SessionSchemaError("invalid tool mode");
7652
+ for (const version of Object.values(objectOf(catalog["tools"], "tool versions")))
7653
+ if (typeof version !== "string")
7654
+ throw new SessionSchemaError("invalid tool version");
7655
+ }
7656
+ }
7657
+ return input;
7658
+ }
7659
+ if (kind === "record") {
7660
+ const recordType = stringValue(input, "recordType");
7661
+ if (!RECORD_TYPES.has(recordType)) {
7662
+ throw new SessionSchemaError("invalid session record type");
7663
+ }
7664
+ const payload = objectOf(input["payload"], "record payload");
7665
+ if (recordType === "session_name_changed") {
7666
+ const title = payload["title"];
7667
+ if (typeof title !== "string") {
7668
+ throw new SessionSchemaError("invalid session name record");
7669
+ }
7847
7670
  try {
7848
- return await this.adapter.runAttempt(adapterRequest);
7671
+ if (normalizeSessionTitle(title) !== title) {
7672
+ throw new SessionSchemaError("session name must already be normalized");
7673
+ }
7849
7674
  } catch (error) {
7850
- const normalized = normalizeRuntimeError(error, request.cancelToken ?? null);
7851
- if (!shouldRetry(normalized, attempt, maxAttempts)) {
7852
- throw normalized;
7675
+ if (error instanceof SessionSchemaError) {
7676
+ throw error;
7853
7677
  }
7854
- const delayMs = this.retryDelaysMs[Math.min(attempt - 1, this.retryDelaysMs.length - 1)];
7855
- request.onDelta?.("model_retry_scheduled", {
7856
- attempt: attempt + 1,
7857
- max_attempts: maxAttempts,
7858
- delay_ms: delayMs,
7859
- error_kind: normalized.kind
7860
- });
7861
- await this.sleep(delayMs, request.cancelToken ?? null);
7678
+ throw new SessionSchemaError("invalid session name record");
7862
7679
  }
7863
7680
  }
7864
- throw new ModelError("model retry loop exhausted", { kind: "protocol" });
7865
- }
7866
- };
7867
- function forwardEvent(event, handler) {
7868
- if (event.type === "text-delta") {
7869
- handler?.("model_text_delta", { text: event.text });
7870
- } else if (event.type === "reasoning-delta") {
7871
- handler?.("model_reasoning_delta", { text: event.text });
7872
- } else if (event.type === "tool-call-delta") {
7873
- handler?.("model_tool_call_delta", {
7874
- index: event.index,
7875
- id: event.id,
7876
- name: event.name ?? null,
7877
- arguments: event.argumentsDelta
7878
- });
7879
- } else {
7880
- handler?.("model_response_validating", {});
7681
+ return input;
7881
7682
  }
7683
+ throw new SessionSchemaError("invalid session item kind");
7882
7684
  }
7883
- function normalizeRuntimeError(error, cancelToken) {
7884
- if (error instanceof ModelStreamCancelled) {
7885
- return error;
7886
- }
7887
- if (cancelToken?.isCancelled()) {
7888
- return new ModelStreamCancelled(cancelToken.reason || "cancelled", { cause: error });
7889
- }
7890
- if (error instanceof ModelError) {
7891
- return error;
7892
- }
7893
- if (error instanceof ModelStreamError) {
7894
- return new ModelError(error.message, {
7895
- kind: modelErrorKind(error),
7896
- hadDelta: error.hadDelta,
7897
- cause: error
7898
- });
7899
- }
7900
- return new ModelError(errorMessage(error), {
7901
- kind: modelErrorKind(error),
7902
- cause: error
7903
- });
7685
+ function contextResetBoundary(entries) {
7686
+ return entries.reduce((boundary, entry) => entry.entryType === "context_reset" ? Math.max(boundary, entry.payload.resetThroughSeq) : boundary, 0);
7904
7687
  }
7905
- function shouldRetry(error, attempt, maxAttempts) {
7906
- if (!(error instanceof ModelError) || attempt >= maxAttempts) {
7907
- return false;
7908
- }
7909
- if (error.hadDelta) {
7910
- return false;
7688
+ function objectOf(value, label) {
7689
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
7690
+ throw new SessionSchemaError(`${label} must be an object`);
7911
7691
  }
7912
- return error.kind === "timeout" || error.kind === "rate_limited" || error.kind === "server" || error.kind === "retryable";
7692
+ return value;
7913
7693
  }
7914
- function validateMaxAttempts(value) {
7915
- if (!Number.isInteger(value) || value < 1 || value > 10) {
7916
- throw new RangeError("maxAttempts must be an integer from 1 through 10");
7694
+ function requireString(input, key) {
7695
+ const value = input[key];
7696
+ if (typeof value !== "string" || value === "") {
7697
+ throw new SessionSchemaError(`${key} must be a non-empty string`);
7917
7698
  }
7918
7699
  return value;
7919
7700
  }
7920
- function validateRetryDelays(value) {
7921
- if (value.length === 0) {
7922
- throw new RangeError("retryDelaysMs must not be empty");
7923
- }
7924
- for (const delay2 of value) {
7925
- if (!Number.isInteger(delay2) || delay2 < 0 || !Number.isFinite(delay2)) {
7926
- throw new RangeError("retryDelaysMs must contain finite non-negative integers");
7927
- }
7928
- }
7929
- return [...value];
7701
+ function stringValue(input, key) {
7702
+ const value = requireString(input, key);
7703
+ return value;
7930
7704
  }
7931
- async function defaultSleep(delayMs, cancelToken) {
7932
- if (cancelToken?.isCancelled()) {
7933
- throw new ModelStreamCancelled(cancelToken.reason || "cancelled");
7934
- }
7935
- const cancelled = await cancelToken?.wait(delayMs);
7936
- if (cancelled) {
7937
- throw new ModelStreamCancelled(cancelToken?.reason || "cancelled");
7938
- }
7939
- if (cancelToken === null) {
7940
- await new Promise((resolve15) => setTimeout(resolve15, delayMs));
7705
+ function requirePositiveInteger(input, key) {
7706
+ const value = input[key];
7707
+ if (!Number.isInteger(value) || typeof value !== "number" || value < 1) {
7708
+ throw new SessionSchemaError(`${key} must be a positive integer`);
7941
7709
  }
7942
- }
7943
- function errorMessage(error) {
7944
- return error instanceof Error ? error.message : String(error);
7710
+ return value;
7945
7711
  }
7946
7712
 
7947
- // ../../packages/session/session-context/dist/context-builder.js
7948
- var ContextBuildError = class extends Error {
7949
- entryId;
7950
- constructor(entryId, message) {
7951
- super(`${entryId}: ${message}`);
7952
- this.name = "ContextBuildError";
7953
- this.entryId = entryId;
7713
+ // ../../packages/session/session-store/dist/attachment-references.js
7714
+ import { lstatSync, readdirSync } from "node:fs";
7715
+ import { join } from "node:path";
7716
+
7717
+ // ../../packages/session/session-store/dist/session-reader.js
7718
+ import { readFileSync } from "node:fs";
7719
+ var SessionCorruptError = class extends Error {
7720
+ path;
7721
+ line;
7722
+ constructor(path5, line2, message, options = {}) {
7723
+ super(`${path5}:${line2}: ${message}`, options.cause === void 0 ? void 0 : { cause: options.cause });
7724
+ this.name = "SessionCorruptError";
7725
+ this.path = path5;
7726
+ this.line = line2;
7954
7727
  }
7955
7728
  };
7956
- var ContextBuilder = class {
7957
- build(input) {
7958
- const sorted = [...input.entries].sort((left, right) => left.seq - right.seq);
7959
- const superseded = supersededEntryIds(sorted);
7960
- const activeCompaction = latestCompaction(sorted);
7961
- const selected = [];
7962
- const system = latestActive(sorted, "system_context", superseded);
7963
- if (system !== null) {
7964
- selected.push({ entryId: system.id, message: system.payload.message });
7965
- }
7966
- for (const entry of sorted) {
7967
- if (entry.entryType === "project_instructions" && !superseded.has(entry.id)) {
7968
- selected.push({ entryId: entry.id, message: entry.payload.message });
7969
- }
7970
- }
7971
- if (activeCompaction !== null) {
7972
- selected.push({
7973
- entryId: activeCompaction.id,
7974
- message: {
7975
- role: "user",
7976
- content: `<conversation_summary>
7977
- ${activeCompaction.payload.summary}
7978
- </conversation_summary>`
7979
- }
7980
- });
7981
- }
7982
- for (const entry of conversationTail(sorted, activeCompaction)) {
7983
- const message = modelMessageForEntry(entry, input.currentProvider, input.currentModel);
7984
- if (message !== null) {
7985
- selected.push({ entryId: entry.id, message });
7986
- }
7987
- }
7988
- validateToolPairs(selected);
7989
- const offloaded = new Set(sorted.flatMap((entry) => entry.entryType === "image_offload" ? entry.payload.keys : []));
7990
- return {
7991
- messages: selected.map((item) => projectImageOffloads(item.message, offloaded)),
7992
- sourceEntryIds: selected.map((item) => item.entryId),
7993
- activeCompactionId: activeCompaction?.id ?? null
7994
- };
7729
+ function readSessionFile(path5) {
7730
+ const text3 = readFileSync(path5, "utf8");
7731
+ const endsWithNewline = text3.endsWith("\n");
7732
+ const rawLines = text3.split("\n");
7733
+ if (endsWithNewline) {
7734
+ rawLines.pop();
7995
7735
  }
7996
- };
7997
- function supersededEntryIds(entries) {
7998
- const superseded = /* @__PURE__ */ new Set();
7999
- for (const entry of entries) {
8000
- if (entry.entryType === "system_context" && entry.payload.supersedesEntryId !== void 0) {
8001
- superseded.add(entry.payload.supersedesEntryId);
7736
+ if (rawLines.length === 0 || rawLines[0] === "") {
7737
+ throw new SessionCorruptError(path5, 1, "missing session header");
7738
+ }
7739
+ const header = parseLine(path5, 1, rawLines[0], parseSessionHeader);
7740
+ const items = [];
7741
+ let ignoredTornTail = false;
7742
+ let previousSeq = 0;
7743
+ for (let index = 1; index < rawLines.length; index += 1) {
7744
+ const lineNumber = index + 1;
7745
+ const line2 = rawLines[index];
7746
+ if (line2.trim() === "") {
7747
+ continue;
8002
7748
  }
8003
- if (entry.entryType === "project_instructions") {
8004
- for (const id of entry.payload.supersedesEntryIds) {
8005
- superseded.add(id);
7749
+ try {
7750
+ const item = parseSessionItem(JSON.parse(line2), header.sessionId);
7751
+ if (item.seq !== previousSeq + 1) {
7752
+ throw new SessionSchemaError("session item seq must be contiguous");
8006
7753
  }
7754
+ previousSeq = item.seq;
7755
+ items.push(item);
7756
+ } catch (error) {
7757
+ if (!endsWithNewline && index === rawLines.length - 1) {
7758
+ ignoredTornTail = true;
7759
+ break;
7760
+ }
7761
+ throw corrupt(path5, lineNumber, error);
8007
7762
  }
8008
7763
  }
8009
- return superseded;
8010
- }
8011
- function latestActive(entries, entryType, superseded) {
8012
- for (let index = entries.length - 1; index >= 0; index -= 1) {
8013
- const entry = entries[index];
8014
- if (entry.entryType === entryType && !superseded.has(entry.id)) {
8015
- return entry;
8016
- }
8017
- }
8018
- return null;
7764
+ return {
7765
+ header,
7766
+ items,
7767
+ lastSeq: previousSeq,
7768
+ ignoredTornTail,
7769
+ openToolCalls: findOpenToolCalls(items)
7770
+ };
8019
7771
  }
8020
- function latestCompaction(entries) {
8021
- for (let index = entries.length - 1; index >= 0; index -= 1) {
8022
- const entry = entries[index];
8023
- if (entry.entryType === "compaction") {
8024
- return entry;
8025
- }
7772
+ function parseLine(path5, line2, raw, parse3) {
7773
+ try {
7774
+ return parse3(JSON.parse(raw));
7775
+ } catch (error) {
7776
+ throw corrupt(path5, line2, error);
8026
7777
  }
8027
- return null;
8028
- }
8029
- function conversationTail(entries, activeCompaction) {
8030
- const boundary = activeCompaction?.payload.retainedFromSeq ?? 1;
8031
- return entries.filter((entry) => entry.seq >= boundary && entry.entryType !== "system_context" && entry.entryType !== "project_instructions" && entry.entryType !== "compaction");
8032
7778
  }
8033
- function modelMessageForEntry(entry, currentProvider, currentModel) {
8034
- if (entry.entryType === "tool_definitions" || entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
8035
- return entry.payload.message;
8036
- if (entry.entryType === "user_message" || entry.entryType === "reminder") {
8037
- return entry.payload.message;
8038
- }
8039
- if (entry.entryType === "assistant_message") {
8040
- const message = entry.payload.message;
8041
- if (message.provider !== currentProvider || message.model !== currentModel) {
8042
- return portableModelMessage(message);
8043
- }
8044
- return message;
8045
- }
8046
- if (entry.entryType === "tool_result") {
8047
- return entry.payload.message;
7779
+ function corrupt(path5, line2, error) {
7780
+ if (error instanceof SessionCorruptError) {
7781
+ return error;
8048
7782
  }
8049
- return null;
7783
+ return new SessionCorruptError(path5, line2, error instanceof Error ? error.message : "invalid session JSONL", { cause: error });
8050
7784
  }
8051
- function validateToolPairs(selected) {
8052
- let expected = [];
8053
- let ownerEntryId = null;
8054
- for (const item of selected) {
8055
- if (item.message.role === "assistant") {
8056
- if (expected.length > 0) {
8057
- throw new ContextBuildError(ownerEntryId ?? item.entryId, "missing tool result");
7785
+ function findOpenToolCalls(items) {
7786
+ const calls = /* @__PURE__ */ new Map();
7787
+ for (const item of items) {
7788
+ if (item.kind === "entry" && item.entryType === "context_reset") {
7789
+ for (const [id, call] of calls) {
7790
+ if (call.seq <= item.payload.resetThroughSeq)
7791
+ calls.delete(id);
8058
7792
  }
8059
- expected = item.message.content.filter((block) => block.type === "tool-call").map((block) => block.call.id);
8060
- ownerEntryId = expected.length === 0 ? null : item.entryId;
8061
- continue;
8062
7793
  }
8063
- if (item.message.role === "tool-result") {
8064
- const [next, ...rest] = expected;
8065
- if (next === void 0) {
8066
- throw new ContextBuildError(item.entryId, "orphan tool result");
8067
- }
8068
- if (item.message.toolCallId !== next) {
8069
- throw new ContextBuildError(item.entryId, "tool result order does not match tool call order");
7794
+ if (item.kind === "entry" && item.entryType === "assistant_message") {
7795
+ for (const block of item.payload.message.content) {
7796
+ if (block.type === "tool-call") {
7797
+ calls.set(block.call.id, {
7798
+ entryId: item.id,
7799
+ seq: item.seq,
7800
+ toolCallId: block.call.id,
7801
+ toolName: block.call.name
7802
+ });
7803
+ }
8070
7804
  }
8071
- expected = rest;
8072
- ownerEntryId = expected.length === 0 ? null : ownerEntryId;
8073
- continue;
8074
7805
  }
8075
- if (expected.length > 0) {
8076
- throw new ContextBuildError(ownerEntryId ?? item.entryId, "missing tool result");
7806
+ if (item.kind === "entry" && item.entryType === "tool_result") {
7807
+ calls.delete(item.payload.message.toolCallId);
8077
7808
  }
8078
7809
  }
8079
- if (expected.length > 0) {
8080
- throw new ContextBuildError(ownerEntryId ?? "unknown", "missing tool result");
8081
- }
7810
+ return [...calls.values()];
8082
7811
  }
8083
7812
 
8084
- // ../../packages/session/session-context/dist/summary-prompt.js
8085
- var COMPACTION_SYSTEM_PROMPT = "Summarize the conversation for continuation. Preserve user goals, explicit constraints, completed work, modified files, architectural decisions, tool outcomes, failures, current TODOs, and concrete state needed to continue. Do not include credentials. Do not invent results.";
8086
-
8087
- // ../../packages/session/session-context/dist/compaction.js
8088
- function selectCompactionPlan(input) {
8089
- const units = semanticUnits(input.entries);
8090
- const retained = [];
8091
- let tokens = 0;
8092
- for (let index = units.length - 1; index >= 0; index -= 1) {
8093
- const unit = units[index];
8094
- const unitTokens = input.estimator.estimateMessages(unit.messages);
8095
- retained.unshift(unit);
8096
- tokens += unitTokens;
8097
- if (tokens >= input.retainTokens) {
8098
- break;
7813
+ // ../../packages/session/session-store/dist/attachment-references.js
7814
+ function attachmentReferences(entries) {
7815
+ const result = /* @__PURE__ */ new Set();
7816
+ for (const entry of entries) {
7817
+ if (entry.entryType === "compaction" || entry.entryType === "image_offload" || entry.entryType === "context_reset")
7818
+ continue;
7819
+ if (!("message" in entry.payload) || !entry.payload.message || typeof entry.payload.message !== "object")
7820
+ throw new Error("Incomplete session message during attachment scan");
7821
+ const message = entry.payload.message;
7822
+ if (!("attachments" in message))
7823
+ continue;
7824
+ for (const block of message.attachments ?? []) {
7825
+ assertAttachmentContent(block);
7826
+ result.add(block.ref.id);
8099
7827
  }
8100
7828
  }
8101
- if (retained.length === 0 && units.length > 0) {
8102
- retained.push(units.at(-1));
8103
- }
8104
- const currentTurn = input.protectedFromSeq ?? [...input.entries].reverse().find((entry) => entry.entryType === "user_message")?.seq ?? 0;
8105
- const hasCurrentImages = input.entries.some((entry) => entry.seq >= currentTurn && "message" in entry.payload && "attachments" in entry.payload.message && entry.payload.message.attachments?.some((block) => block.type === "image"));
8106
- const retainedFirst = Math.min(retained[0]?.entries[0]?.seq ?? Number.MAX_SAFE_INTEGER, hasCurrentImages ? currentTurn : Number.MAX_SAFE_INTEGER);
8107
- const retainedUnits = units.filter((unit) => unit.entries[0].seq >= retainedFirst);
8108
- const summarized = units.filter((unit) => unit.entries[0].seq < retainedFirst);
8109
- return {
8110
- summarizedEntries: summarized.flatMap((unit) => unit.entries),
8111
- retainedEntries: retainedUnits.flatMap((unit) => unit.entries),
8112
- retainedFromSeq: retainedUnits[0]?.entries[0]?.seq ?? 1
7829
+ return result;
7830
+ }
7831
+ function scanAttachmentReferences(sessionsRoot) {
7832
+ const result = /* @__PURE__ */ new Set();
7833
+ const visit = (directory) => {
7834
+ for (const file of readdirSync(directory, { withFileTypes: true })) {
7835
+ const path5 = join(directory, file.name);
7836
+ if (file.isSymbolicLink())
7837
+ throw new Error(`Cannot scan symbolic session path: ${path5}`);
7838
+ if (file.isDirectory())
7839
+ visit(path5);
7840
+ else if (file.name.endsWith(".jsonl")) {
7841
+ if (!lstatSync(path5).isFile())
7842
+ throw new Error(`Invalid session file: ${path5}`);
7843
+ const replay = readSessionFile(path5);
7844
+ if (replay.ignoredTornTail)
7845
+ throw new Error(`Incomplete session journal: ${path5}`);
7846
+ for (const id of attachmentReferences(replay.items.filter((item) => item.kind === "entry")))
7847
+ result.add(id);
7848
+ }
7849
+ }
8113
7850
  };
7851
+ visit(sessionsRoot);
7852
+ return result;
8114
7853
  }
8115
- function serializeConversation(entries) {
8116
- const lines2 = ["<conversation>"];
8117
- for (const entry of entries) {
8118
- lines2.push(`<entry id="${entry.id}" seq="${entry.seq}" type="${entry.entryType}">`);
8119
- if (entry.entryType === "assistant_message") {
8120
- for (const block of entry.payload.message.content) {
8121
- if (block.type === "tool-call") {
8122
- lines2.push(`[assistant tool-call ${block.call.id} ${block.call.name}] ${JSON.stringify(block.call.arguments)}`);
8123
- } else {
8124
- lines2.push(`[assistant ${block.type}] ${block.text}`);
8125
- }
8126
- }
8127
- } else if (entry.entryType === "tool_result") {
8128
- lines2.push(`[tool-result ${entry.payload.message.toolCallId} ${entry.payload.message.toolName}] ${entry.payload.message.content}`);
8129
- } else if (entry.entryType === "tool_definitions") {
8130
- lines2.push(`[loaded tool definitions] ${(entry.payload.message.toolDefinitions ?? []).map((tool) => tool.spec.name).join(", ")}`);
8131
- } else if (entry.entryType === "user_message" || entry.entryType === "skill_context" || entry.entryType === "reminder" || entry.entryType === "project_instructions") {
8132
- lines2.push(`[${entry.payload.message.role}] ${entry.payload.message.content}`);
8133
- }
8134
- if ("message" in entry.payload && "attachments" in entry.payload.message) {
8135
- for (const block of entry.payload.message.attachments ?? [])
8136
- lines2.push(`[attachment metadata only; not visual interpretation] ${JSON.stringify(block)}`);
8137
- }
8138
- lines2.push("</entry>");
7854
+
7855
+ // ../../packages/session/session-store/dist/session-paths.js
7856
+ import { createHash } from "node:crypto";
7857
+ import { mkdirSync, realpathSync } from "node:fs";
7858
+ import { basename, join as join2, resolve } from "node:path";
7859
+ function canonicalProjectRoot(projectRoot) {
7860
+ try {
7861
+ return realpathSync.native(projectRoot);
7862
+ } catch {
7863
+ return resolve(projectRoot);
8139
7864
  }
8140
- lines2.push("</conversation>");
8141
- return lines2.join("\n");
8142
7865
  }
8143
- function semanticUnits(entries) {
8144
- const units = [];
8145
- const groupedDefinitions = /* @__PURE__ */ new Set();
8146
- for (let index = 0; index < entries.length; index += 1) {
8147
- const entry = entries[index];
8148
- if (groupedDefinitions.has(entry.id))
8149
- continue;
8150
- if (entry.entryType === "image_offload" || entry.entryType === "system_context" || entry.entryType === "project_instructions" || entry.entryType === "compaction") {
8151
- continue;
8152
- }
8153
- if (entry.entryType === "assistant_message") {
8154
- const toolCallIds = entry.payload.message.content.filter((block) => block.type === "tool-call").map((block) => block.call.id);
8155
- if (toolCallIds.length > 0) {
8156
- const grouped = [entry];
8157
- for (const id of toolCallIds) {
8158
- const resultIndex = entries.findIndex((candidate) => candidate.entryType === "tool_result" && candidate.payload.message.toolCallId === id);
8159
- if (resultIndex < 0) {
8160
- throw new Error(`cannot compact incomplete tool call: ${id}`);
8161
- }
8162
- grouped.push(entries[resultIndex]);
8163
- }
8164
- const lastResultIndex = entries.findIndex((candidate) => candidate.id === grouped.at(-1).id);
8165
- const definitions = entries[lastResultIndex + 1];
8166
- if (definitions?.entryType === "tool_definitions") {
8167
- grouped.push(definitions);
8168
- groupedDefinitions.add(definitions.id);
8169
- }
8170
- units.push({ entries: grouped, messages: grouped.map(messageForEntry) });
8171
- continue;
8172
- }
8173
- }
8174
- if (entry.entryType === "tool_result") {
8175
- continue;
8176
- }
8177
- units.push({ entries: [entry], messages: [messageForEntry(entry)] });
8178
- }
8179
- return units;
7866
+ function projectKeyForRoot(projectRoot) {
7867
+ const canonical = canonicalProjectRoot(projectRoot);
7868
+ const digest = createHash("sha256").update(canonical).digest("hex").slice(0, 12);
7869
+ return `${sanitizePathPart(basename(canonical) || "project")}-${digest}`;
8180
7870
  }
8181
- function messageForEntry(entry) {
8182
- if (entry.entryType === "tool_definitions" || entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
8183
- return entry.payload.message;
8184
- if (entry.entryType === "assistant_message") {
8185
- return entry.payload.message;
8186
- }
8187
- if (entry.entryType === "tool_result") {
8188
- return entry.payload.message;
8189
- }
8190
- if (entry.entryType === "user_message" || entry.entryType === "reminder" || entry.entryType === "project_instructions") {
8191
- return entry.payload.message;
8192
- }
8193
- throw new Error(`entry has no model message: ${entry.entryType}`);
7871
+ function ensureSessionDirectory(sessionsRoot, projectKey) {
7872
+ const directory = join2(sessionsRoot, projectKey);
7873
+ mkdirSync(directory, { recursive: true, mode: 448 });
7874
+ return directory;
7875
+ }
7876
+ function sessionPathForHeader(sessionsRoot, projectKey, createdAt, sessionId) {
7877
+ const safeCreatedAt = createdAt.replaceAll(":", "").replaceAll(".", "");
7878
+ return join2(ensureSessionDirectory(sessionsRoot, projectKey), `${safeCreatedAt}_${sessionId}.jsonl`);
7879
+ }
7880
+ function sanitizePathPart(value) {
7881
+ const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
7882
+ return sanitized || "project";
8194
7883
  }
8195
7884
 
8196
- // ../../packages/session/session-context/dist/context-governor.js
8197
- var ContextBudgetError = class extends Error {
7885
+ // ../../packages/session/session-store/dist/session-journal.js
7886
+ import { closeSync, existsSync, fsyncSync, openSync, readFileSync as readFileSync2, unlinkSync, writeSync } from "node:fs";
7887
+ import { randomUUID } from "node:crypto";
7888
+ var SessionJournalLockError = class extends Error {
8198
7889
  constructor(message) {
8199
7890
  super(message);
8200
- this.name = "ContextBudgetError";
7891
+ this.name = "SessionJournalLockError";
8201
7892
  }
8202
7893
  };
8203
- var ContextGovernor = class {
8204
- #builder;
8205
- #estimator;
8206
- #summarize;
8207
- #appendCompaction;
8208
- constructor(options) {
8209
- this.#builder = options.builder ?? new ContextBuilder();
8210
- this.#estimator = options.estimator ?? new DefaultTokenEstimator();
8211
- this.#summarize = options.summarize;
8212
- this.#appendCompaction = options.appendCompaction;
7894
+ var FileSessionJournal = class {
7895
+ attachments;
7896
+ header;
7897
+ path;
7898
+ #fd;
7899
+ #lockPath;
7900
+ #nextSeq;
7901
+ #closed = false;
7902
+ constructor(input) {
7903
+ this.attachments = input.attachments;
7904
+ this.header = input.header;
7905
+ this.path = input.path;
7906
+ this.#nextSeq = input.nextSeq;
7907
+ this.#fd = input.fd;
7908
+ this.#lockPath = `${input.path}.lock`;
8213
7909
  }
8214
- async prepare(input) {
8215
- let built = this.#builder.build(input);
8216
- let tokens = this.measure(input, built);
8217
- const calculated = calculateModelBudget({ budget: input.budget, policy: input.policy });
8218
- if (!input.policy.auto || tokens <= calculated.autoTrigger) {
8219
- if (tokens > calculated.hardInputLimit)
8220
- throw new ContextBudgetError("context exceeds hard input limit");
8221
- return { built, messages: built.messages, compacted: false, tokens };
7910
+ get nextSeq() {
7911
+ return this.#nextSeq;
7912
+ }
7913
+ appendEntry(input) {
7914
+ const item = this.makeBase(input);
7915
+ const entry = {
7916
+ ...item,
7917
+ kind: "entry",
7918
+ entryType: input.entryType,
7919
+ payload: input.payload
7920
+ };
7921
+ const message = "message" in entry.payload ? entry.payload.message : void 0;
7922
+ const attachments = message && "attachments" in message ? message.attachments : void 0;
7923
+ if (attachments?.length) {
7924
+ for (const attachment of attachments)
7925
+ assertAttachmentContent(attachment);
7926
+ if (!this.attachments)
7927
+ throw new Error("Attachment persistence requires an attachment store");
7928
+ if (!("attachmentKey" in message) || !message.attachmentKey)
7929
+ throw new Error("Attachment message requires an occurrence key");
7930
+ this.attachments.commit(attachments.map((block) => block.ref), () => {
7931
+ this.writeItem(entry);
7932
+ this.flush();
7933
+ });
7934
+ } else
7935
+ this.writeItem(entry);
7936
+ if (entry.entryType === "compaction") {
7937
+ this.flush();
8222
7938
  }
8223
- const result = await this.compact({ ...input, trigger: "automatic" });
8224
- const entries = [...input.entries, result.entry];
8225
- built = this.#builder.build({ ...input, entries });
8226
- tokens = this.measure({ ...input, entries }, built);
8227
- if (tokens > calculated.hardInputLimit) {
8228
- throw new ContextBudgetError("context remains over hard input limit after compaction");
7939
+ return entry;
7940
+ }
7941
+ appendRecord(input) {
7942
+ const record = {
7943
+ ...this.makeBase(input),
7944
+ kind: "record",
7945
+ recordType: input.recordType,
7946
+ payload: input.payload ?? {}
7947
+ };
7948
+ this.writeItem(record);
7949
+ if (record.recordType === "turn_finished" || record.recordType === "compaction_finished" || record.recordType === "session_closed") {
7950
+ this.flush();
8229
7951
  }
8230
- return { built, messages: built.messages, compacted: true, tokens };
7952
+ return record;
8231
7953
  }
8232
- async compact(input) {
8233
- const calculated = calculateModelBudget({ budget: input.budget, policy: input.policy });
8234
- const plan = selectCompactionPlan({
8235
- protectedFromSeq: input.protectedFromSeq,
8236
- entries: input.entries,
8237
- // A search preflight reserves the pending call/result/definition unit.
8238
- // Keep the latest completed unit and summarize older units to make room.
8239
- retainTokens: input.reserveTokens === void 0 ? calculated.retainTokens : 1,
8240
- estimator: this.#estimator
8241
- });
8242
- if (input.trigger === "manual" && plan.summarizedEntries.length === 0) {
8243
- throw new Error("No messages to compact in current history.");
7954
+ flush() {
7955
+ if (!this.#closed) {
7956
+ fsyncSync(this.#fd);
8244
7957
  }
8245
- const summarizedFromSeq = plan.summarizedEntries[0]?.seq ?? 1;
8246
- const summarizedThroughSeq = plan.summarizedEntries.at(-1)?.seq ?? Math.max(0, summarizedFromSeq - 1);
8247
- const activeCompaction = [...input.entries].reverse().find((entry) => entry.entryType === "compaction");
8248
- const messagesBefore = this.#builder.build(input).messages;
8249
- const tokensBefore = this.#estimator.estimateMessages(messagesBefore) + this.#estimator.estimateTools(input.projectTools?.(messagesBefore) ?? input.tools);
8250
- const summary = plan.summarizedEntries.length === 0 ? {
8251
- summary: "No prior conversation needed compaction.",
8252
- inputTokens: 0,
8253
- outputTokens: 0
8254
- } : await this.#summarize({
8255
- serialized: serializeConversation(plan.summarizedEntries),
8256
- maxSummaryTokens: input.policy.maxSummaryTokens
8257
- });
8258
- const payload = {
8259
- summary: summary.summary,
8260
- summarizedFromSeq,
8261
- summarizedThroughSeq,
8262
- retainedFromSeq: plan.retainedFromSeq,
8263
- ...activeCompaction === void 0 ? {} : { supersedesCompactionId: activeCompaction.id },
8264
- tokensBefore,
8265
- retainedTokens: calculated.retainTokens,
8266
- summaryInputTokens: summary.inputTokens,
8267
- summaryOutputTokens: summary.outputTokens,
8268
- provider: input.currentProvider,
8269
- model: input.currentModel,
8270
- trigger: input.trigger
8271
- };
8272
- return { entry: this.#appendCompaction(payload) };
8273
7958
  }
8274
- async completeWithOverflowRecovery(input, send) {
8275
- let prepared = await this.prepare(input);
8276
- try {
8277
- return await send(prepared);
8278
- } catch (error) {
8279
- if (!(error instanceof ModelError) || modelErrorKind(error) !== "context_overflow" || error.hadDelta) {
8280
- throw error;
8281
- }
7959
+ close() {
7960
+ if (this.#closed) {
7961
+ return;
7962
+ }
7963
+ this.flush();
7964
+ closeSync(this.#fd);
7965
+ this.#closed = true;
7966
+ if (existsSync(this.#lockPath)) {
7967
+ unlinkSync(this.#lockPath);
8282
7968
  }
8283
- const compaction = await this.compact({ ...input, trigger: "provider_overflow" });
8284
- prepared = await this.prepare({ ...input, entries: [...input.entries, compaction.entry] });
8285
- return await send(prepared);
8286
7969
  }
8287
- measure(input, built) {
8288
- const tools = input.projectTools?.(built.messages) ?? input.tools;
8289
- return this.#estimator.measure({
8290
- messages: built.messages,
8291
- tools,
8292
- entries: input.entries,
8293
- anchor: input.anchor ?? null,
8294
- provider: input.currentProvider,
8295
- model: input.currentModel,
8296
- systemFingerprint: fingerprintContextPart(built.messages.find((message) => message.role === "system") ?? null),
8297
- projectInstructionsFingerprint: fingerprintContextPart(built.messages.filter((message, index) => index > 0 && message.role === "user")),
8298
- toolsFingerprint: fingerprintContextPart(tools)
8299
- }).totalTokens + (input.reserveTokens ?? 0);
7970
+ makeBase(input) {
7971
+ return {
7972
+ schemaVersion: 1,
7973
+ sessionId: this.header.sessionId,
7974
+ seq: this.#nextSeq,
7975
+ id: randomUUID().replaceAll("-", ""),
7976
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7977
+ ...input.taskId === void 0 ? {} : { taskId: input.taskId },
7978
+ ...input.turnId === void 0 ? {} : { turnId: input.turnId },
7979
+ ...input.copiedFrom === void 0 ? {} : { copiedFrom: input.copiedFrom }
7980
+ };
8300
7981
  }
8301
- };
8302
- function calculateModelBudget(input) {
8303
- const hardInputLimit = input.budget.contextWindow - input.budget.maxOutputTokens;
8304
- const safetyTokens = Math.ceil(input.budget.contextWindow * input.policy.safetyRatio);
8305
- const autoTrigger = Math.min(Math.floor(input.budget.contextWindow * input.policy.thresholdRatio), hardInputLimit - safetyTokens);
8306
- const retainTokens = input.policy.retainTokens ?? Math.floor(input.budget.contextWindow * input.policy.retainRatio);
8307
- return { hardInputLimit, safetyTokens, autoTrigger, retainTokens };
8308
- }
8309
-
8310
- // ../../packages/attachment/attachment/dist/errors.js
8311
- var AttachmentError = class extends Error {
8312
- code;
8313
- constructor(code, message, options) {
8314
- super(message, options);
8315
- this.name = "AttachmentError";
8316
- this.code = code;
7982
+ writeItem(item) {
7983
+ if (this.#closed) {
7984
+ throw new Error("session journal is closed");
7985
+ }
7986
+ writeLine(this.#fd, item);
7987
+ this.#nextSeq += 1;
8317
7988
  }
8318
7989
  };
8319
-
8320
- // ../../packages/attachment/attachment/dist/types.js
8321
- var DEFAULT_IMAGE_LIMITS = Object.freeze({
8322
- maxBytes: 20 * 1024 ** 2,
8323
- maxImages: 20,
8324
- maxBatchBytes: 200 * 1024 ** 2,
8325
- maxPixels: 64e6,
8326
- maxDimension: 8192
8327
- });
8328
- var RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
8329
- var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
8330
- function assertAttachmentContent(value) {
8331
- if (!value || typeof value !== "object")
8332
- throw new AttachmentError("INVALID_REFERENCE", "Invalid attachment content");
8333
- const block = value;
8334
- if (block.type !== "image" && block.type !== "file")
8335
- throw new AttachmentError("INVALID_REFERENCE", "Invalid attachment kind");
8336
- assertFileRef(block.ref);
8337
- if (block.type === "image") {
8338
- const ref = block.ref;
8339
- if (!["image/png", "image/jpeg", "image/webp", "image/gif"].includes(String(ref.mimeType)) || !Number.isSafeInteger(ref.width) || Number(ref.width) < 1 || !Number.isSafeInteger(ref.height) || Number(ref.height) < 1 || typeof ref.animated !== "boolean") {
8340
- throw new AttachmentError("INVALID_REFERENCE", "Invalid image reference");
7990
+ function createSessionJournal(options) {
7991
+ const header = options.header ?? makeHeader(options);
7992
+ const path5 = options.path ?? sessionPathForHeader(options.sessionsRoot, header.projectKey, header.createdAt, header.sessionId);
7993
+ const existing = existsSync(path5);
7994
+ const lockPath = `${path5}.lock`;
7995
+ acquireLock(lockPath, header.sessionId);
7996
+ try {
7997
+ const fd = openSync(path5, existing ? "a" : "wx", 384);
7998
+ if (!existing) {
7999
+ writeLine(fd, header);
8000
+ fsyncSync(fd);
8001
+ return new FileSessionJournal({ header, path: path5, nextSeq: 1, fd, attachments: options.attachments });
8341
8002
  }
8342
- if (block.view !== void 0)
8343
- assertImageView(block.view);
8003
+ const replay = readSessionFile(path5);
8004
+ return new FileSessionJournal({
8005
+ header: replay.header,
8006
+ path: path5,
8007
+ nextSeq: replay.lastSeq + 1,
8008
+ fd,
8009
+ attachments: options.attachments
8010
+ });
8011
+ } catch (error) {
8012
+ releaseLock(lockPath);
8013
+ throw error;
8344
8014
  }
8345
8015
  }
8346
- function assertFileRef(value) {
8347
- const ref = value;
8348
- if (!ref || typeof ref !== "object" || typeof ref.id !== "string" || !/^sha256:[a-f0-9]{64}$/.test(ref.id) || !Number.isSafeInteger(ref.bytes) || Number(ref.bytes) < 0 || typeof ref.name !== "string") {
8349
- throw new AttachmentError("INVALID_REFERENCE", "Invalid attachment reference");
8016
+ function makeHeader(options) {
8017
+ const projectRoot = canonicalProjectRoot(required(options.projectRoot, "projectRoot"));
8018
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
8019
+ return {
8020
+ schemaVersion: 1,
8021
+ type: "session_header",
8022
+ sessionId: randomUUID().replaceAll("-", ""),
8023
+ createdAt,
8024
+ initialCwd: required(options.initialCwd, "initialCwd"),
8025
+ projectRoot,
8026
+ projectKey: projectKeyForRoot(projectRoot),
8027
+ appVersion: required(options.appVersion, "appVersion"),
8028
+ provider: required(options.provider, "provider"),
8029
+ model: required(options.model, "model"),
8030
+ reasoningEffort: options.reasoningEffort ?? "high",
8031
+ origin: options.origin ?? "new",
8032
+ ...options.parentSessionId === void 0 ? {} : { parentSessionId: options.parentSessionId },
8033
+ ...options.forkedFrom === void 0 ? {} : { forkedFrom: options.forkedFrom }
8034
+ };
8035
+ }
8036
+ function acquireLock(path5, sessionId) {
8037
+ try {
8038
+ const fd = openSync(path5, "wx", 384);
8039
+ writeLine(fd, { pid: process.pid, sessionId, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
8040
+ closeSync(fd);
8041
+ } catch (error) {
8042
+ if (!isLiveLock(path5)) {
8043
+ releaseLock(path5);
8044
+ const fd = openSync(path5, "wx", 384);
8045
+ writeLine(fd, { pid: process.pid, sessionId, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
8046
+ closeSync(fd);
8047
+ return;
8048
+ }
8049
+ throw new SessionJournalLockError(`session journal is locked: ${path5}`);
8350
8050
  }
8351
8051
  }
8352
- function assertImageView(value) {
8353
- if (!value || typeof value !== "object" || Array.isArray(value))
8354
- throw new AttachmentError("INVALID_REFERENCE", "Invalid image view");
8355
- const view = value;
8356
- if (view.fullResolution !== void 0 && typeof view.fullResolution !== "boolean")
8357
- throw new AttachmentError("INVALID_REFERENCE", "Invalid fullResolution");
8358
- if (view.region !== void 0) {
8359
- const r = view.region;
8360
- if (!r || typeof r !== "object" || !["x", "y", "width", "height"].every((k) => Number.isSafeInteger(r[k])) || Number(r.x) < 0 || Number(r.y) < 0 || Number(r.width) < 1 || Number(r.height) < 1) {
8361
- throw new AttachmentError("INVALID_REFERENCE", "Invalid crop region");
8052
+ function isLiveLock(path5) {
8053
+ if (!existsSync(path5)) {
8054
+ return false;
8055
+ }
8056
+ try {
8057
+ const parsed = JSON.parse(readFileSync2(path5, "utf8"));
8058
+ if (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid)) {
8059
+ return false;
8362
8060
  }
8061
+ process.kill(parsed.pid, 0);
8062
+ return true;
8063
+ } catch (error) {
8064
+ const code = error.code;
8065
+ return code === "EPERM";
8066
+ }
8067
+ }
8068
+ function releaseLock(path5) {
8069
+ if (existsSync(path5)) {
8070
+ unlinkSync(path5);
8071
+ }
8072
+ }
8073
+ function writeLine(fd, value) {
8074
+ writeSync(fd, `${JSON.stringify(value)}
8075
+ `);
8076
+ }
8077
+ function required(value, label) {
8078
+ if (value === void 0 || value === "") {
8079
+ throw new Error(`${label} is required`);
8363
8080
  }
8081
+ return value;
8364
8082
  }
8365
8083
 
8366
- // ../../packages/attachment/attachment/dist/attachment-store.js
8367
- var AttachmentStore = class {
8368
- };
8084
+ // ../../packages/session/session-store/dist/session-manager.js
8085
+ import { existsSync as existsSync2, readdirSync as readdirSync2, statSync } from "node:fs";
8086
+ import { join as join3 } from "node:path";
8369
8087
 
8370
- // ../../packages/session/session-store/dist/session-metadata.js
8371
- function normalizeSessionTitle(input) {
8372
- if (new RegExp("\\p{Cc}|\\u2028|\\u2029", "u").test(input)) {
8373
- throw new Error("Session name must be a single line without control characters.");
8088
+ // ../../packages/session/session-store/dist/session-tree.js
8089
+ function buildSessionTree(sessions) {
8090
+ const nodes = /* @__PURE__ */ new Map();
8091
+ for (const session of sessions) {
8092
+ nodes.set(session.sessionId, { session, children: [] });
8374
8093
  }
8375
- const title = input.trim();
8376
- if (title.length === 0) {
8377
- throw new Error("Session name must not be empty.");
8094
+ const roots = [];
8095
+ for (const session of sessions) {
8096
+ const node = nodes.get(session.sessionId);
8097
+ const parentId = session.parentSessionId;
8098
+ const parent = parentId === void 0 ? void 0 : nodes.get(parentId);
8099
+ if (parent === void 0) {
8100
+ roots.push(node);
8101
+ } else {
8102
+ parent.children.push(node);
8103
+ }
8378
8104
  }
8379
- return title;
8105
+ const sortNodes = (items) => items.sort((left, right) => right.session.createdAt.localeCompare(left.session.createdAt)).map((item) => ({
8106
+ session: item.session,
8107
+ children: sortNodes(item.children)
8108
+ }));
8109
+ return sortNodes(roots);
8380
8110
  }
8381
- function sessionTitleAt(items, throughSeq = Infinity) {
8382
- let title = null;
8383
- for (const item of items) {
8384
- if (item.seq > throughSeq) {
8385
- break;
8386
- }
8387
- if (item.kind === "record" && item.recordType === "session_name_changed") {
8388
- const value = item.payload["title"];
8389
- if (typeof value !== "string") {
8390
- throw new Error("Invalid session name record.");
8111
+
8112
+ // ../../packages/session/session-store/dist/session-manager.js
8113
+ var SessionManager = class {
8114
+ #attachments;
8115
+ #sessionsRoot;
8116
+ #appVersion;
8117
+ constructor(options) {
8118
+ this.#attachments = options.attachments;
8119
+ this.#sessionsRoot = options.sessionsRoot;
8120
+ this.#appVersion = options.appVersion;
8121
+ }
8122
+ create(options) {
8123
+ const journal = createSessionJournal({
8124
+ attachments: this.#attachments,
8125
+ sessionsRoot: this.#sessionsRoot,
8126
+ projectRoot: options.projectRoot,
8127
+ initialCwd: options.initialCwd,
8128
+ appVersion: this.#appVersion,
8129
+ provider: options.provider,
8130
+ model: options.model,
8131
+ reasoningEffort: options.reasoningEffort,
8132
+ origin: "new"
8133
+ });
8134
+ return {
8135
+ header: journal.header,
8136
+ journal,
8137
+ replay: {
8138
+ header: journal.header,
8139
+ items: [],
8140
+ lastSeq: 0,
8141
+ ignoredTornTail: false,
8142
+ openToolCalls: []
8391
8143
  }
8392
- title = normalizeSessionTitle(value);
8144
+ };
8145
+ }
8146
+ open(sessionId) {
8147
+ const summary = this.list().find((item) => item.sessionId === sessionId);
8148
+ if (summary === void 0 || summary.status === "corrupt") {
8149
+ throw new Error(`unknown session: ${sessionId}`);
8393
8150
  }
8151
+ const replay = readSessionFile(summary.path);
8152
+ const journal = createSessionJournal({
8153
+ attachments: this.#attachments,
8154
+ sessionsRoot: this.#sessionsRoot,
8155
+ header: replay.header,
8156
+ path: summary.path
8157
+ });
8158
+ return { header: replay.header, journal, replay };
8394
8159
  }
8395
- return title;
8396
- }
8397
-
8398
- // ../../packages/session/session-store/dist/schema.js
8399
- var ENTRY_TYPES = /* @__PURE__ */ new Set([
8400
- "image_offload",
8401
- "skill_context",
8402
- "tool_definitions",
8403
- "tool_catalog",
8404
- "system_context",
8405
- "project_instructions",
8406
- "user_message",
8407
- "assistant_message",
8408
- "tool_result",
8409
- "reminder",
8410
- "compaction"
8411
- ]);
8412
- var RECORD_TYPES = /* @__PURE__ */ new Set([
8413
- "turn_started",
8414
- "turn_finished",
8415
- "model_request",
8416
- "model_response",
8417
- "tool_started",
8418
- "tool_finished",
8419
- "provider_changed",
8420
- "model_changed",
8421
- "reasoning_effort_changed",
8422
- "cwd_changed",
8423
- "usage",
8424
- "error",
8425
- "cancelled",
8426
- "queue_enqueued",
8427
- "queue_started",
8428
- "queue_finished",
8429
- "compaction_started",
8430
- "compaction_finished",
8431
- "session_name_changed",
8432
- "session_closed"
8433
- ]);
8434
- var ORIGINS = /* @__PURE__ */ new Set(["new", "fork", "clone", "import"]);
8435
- var REASONING_EFFORTS2 = /* @__PURE__ */ new Set([
8436
- "off",
8437
- "minimal",
8438
- "low",
8439
- "medium",
8440
- "high",
8441
- "xhigh",
8442
- "max"
8443
- ]);
8444
- var SessionSchemaError = class extends Error {
8445
- constructor(message) {
8446
- super(message);
8447
- this.name = "SessionSchemaError";
8160
+ continueLatest(projectRoot) {
8161
+ const latest = this.list(projectRoot).find((item) => item.status !== "corrupt");
8162
+ return latest === void 0 ? null : this.open(latest.sessionId);
8448
8163
  }
8449
- };
8450
- function parseSessionHeader(value) {
8451
- const input = objectOf(value, "session header");
8452
- if (input["schemaVersion"] !== 1) {
8453
- throw new SessionSchemaError("unsupported session schema version");
8164
+ list(projectRoot) {
8165
+ const canonical = projectRoot === void 0 ? null : canonicalProjectRoot(projectRoot);
8166
+ return this.sessionPaths().map((path5) => summaryForPath(path5)).filter((summary) => canonical === null || summary.projectRoot === canonical).sort((left, right) => {
8167
+ const updated = right.updatedAt.localeCompare(left.updatedAt);
8168
+ return updated === 0 ? right.createdAt.localeCompare(left.createdAt) : updated;
8169
+ });
8454
8170
  }
8455
- if (input["type"] !== "session_header") {
8456
- throw new SessionSchemaError("invalid session header type");
8171
+ tree(projectRoot) {
8172
+ return buildSessionTree(this.list(projectRoot).filter((item) => item.status !== "corrupt"));
8457
8173
  }
8458
- requireString(input, "sessionId");
8459
- requireString(input, "createdAt");
8460
- requireString(input, "initialCwd");
8461
- requireString(input, "projectRoot");
8462
- requireString(input, "projectKey");
8463
- requireString(input, "appVersion");
8464
- requireString(input, "provider");
8465
- requireString(input, "model");
8466
- if (!REASONING_EFFORTS2.has(stringValue(input, "reasoningEffort"))) {
8467
- throw new SessionSchemaError("invalid reasoning effort");
8174
+ fork(options) {
8175
+ const parent = this.parentReplay(options.parentSessionId);
8176
+ const target = parent.items.find((item) => item.kind === "entry" && item.entryType === "user_message" && item.id === options.entryId);
8177
+ if (target === void 0) {
8178
+ throw new Error("fork target must be a user_message entry");
8179
+ }
8180
+ const child = createSessionJournal({
8181
+ attachments: this.#attachments,
8182
+ sessionsRoot: this.#sessionsRoot,
8183
+ projectRoot: parent.header.projectRoot,
8184
+ initialCwd: parent.header.initialCwd,
8185
+ appVersion: this.#appVersion,
8186
+ provider: parent.header.provider,
8187
+ model: parent.header.model,
8188
+ reasoningEffort: parent.header.reasoningEffort,
8189
+ origin: "fork",
8190
+ parentSessionId: parent.header.sessionId,
8191
+ forkedFrom: {
8192
+ sessionId: parent.header.sessionId,
8193
+ seq: target.seq,
8194
+ entryId: target.id,
8195
+ mode: options.mode
8196
+ }
8197
+ });
8198
+ const throughSeq = options.mode === "before" ? target.seq - 1 : target.seq;
8199
+ copyEntries(child, parent.items.filter(isEntry).filter((item) => item.seq <= throughSeq));
8200
+ appendTitleSnapshot(child, sessionTitleAt(parent.items, throughSeq));
8201
+ child.close();
8202
+ return {
8203
+ sessionId: child.header.sessionId,
8204
+ path: child.path,
8205
+ editorText: options.mode === "before" ? target.payload.message.skillContext?.input ?? target.payload.message.content : ""
8206
+ };
8468
8207
  }
8469
- if (!ORIGINS.has(stringValue(input, "origin"))) {
8470
- throw new SessionSchemaError("invalid session origin");
8208
+ clone(options) {
8209
+ const parent = this.parentReplay(options.parentSessionId);
8210
+ const lastEntry = [...parent.items].reverse().find((item) => item.kind === "entry");
8211
+ const child = createSessionJournal({
8212
+ attachments: this.#attachments,
8213
+ sessionsRoot: this.#sessionsRoot,
8214
+ projectRoot: parent.header.projectRoot,
8215
+ initialCwd: parent.header.initialCwd,
8216
+ appVersion: this.#appVersion,
8217
+ provider: parent.header.provider,
8218
+ model: parent.header.model,
8219
+ reasoningEffort: parent.header.reasoningEffort,
8220
+ origin: "clone",
8221
+ parentSessionId: parent.header.sessionId,
8222
+ forkedFrom: {
8223
+ sessionId: parent.header.sessionId,
8224
+ ...lastEntry === void 0 ? {} : { seq: lastEntry.seq, entryId: lastEntry.id },
8225
+ mode: "clone"
8226
+ }
8227
+ });
8228
+ copyEntries(child, parent.items.filter(isEntry));
8229
+ appendTitleSnapshot(child, sessionTitleAt(parent.items));
8230
+ child.close();
8231
+ return { sessionId: child.header.sessionId, path: child.path };
8471
8232
  }
8472
- if (input["parentSessionId"] !== void 0) {
8473
- requireString(input, "parentSessionId");
8233
+ parentReplay(sessionId) {
8234
+ const summary = this.list().find((item) => item.sessionId === sessionId);
8235
+ if (summary === void 0 || summary.status === "corrupt") {
8236
+ throw new Error(`unknown session: ${sessionId}`);
8237
+ }
8238
+ return readSessionFile(summary.path);
8474
8239
  }
8475
- if (input["forkedFrom"] !== void 0) {
8476
- const forkedFrom = objectOf(input["forkedFrom"], "forkedFrom");
8477
- requireString(forkedFrom, "sessionId");
8478
- if (forkedFrom["seq"] !== void 0) {
8479
- requirePositiveInteger(forkedFrom, "seq");
8240
+ sessionPaths() {
8241
+ if (!existsSync2(this.#sessionsRoot)) {
8242
+ return [];
8480
8243
  }
8481
- if (forkedFrom["entryId"] !== void 0) {
8482
- requireString(forkedFrom, "entryId");
8244
+ const paths = [];
8245
+ const visit = (directory) => {
8246
+ for (const entry of readdirSync2(directory)) {
8247
+ const absolute = join3(directory, entry);
8248
+ const stat6 = statSync(absolute);
8249
+ if (stat6.isDirectory()) {
8250
+ visit(absolute);
8251
+ } else if (absolute.endsWith(".jsonl")) {
8252
+ paths.push(absolute);
8253
+ }
8254
+ }
8255
+ };
8256
+ visit(this.#sessionsRoot);
8257
+ return paths.sort();
8258
+ }
8259
+ };
8260
+ function summaryForPath(path5) {
8261
+ try {
8262
+ const replay = readSessionFile(path5);
8263
+ const last = replay.items.at(-1);
8264
+ const title = sessionTitleAt(replay.items);
8265
+ return {
8266
+ sessionId: replay.header.sessionId,
8267
+ path: path5,
8268
+ createdAt: replay.header.createdAt,
8269
+ updatedAt: last?.timestamp ?? replay.header.createdAt,
8270
+ projectRoot: replay.header.projectRoot,
8271
+ cwd: cwdFrom(replay),
8272
+ provider: replay.header.provider,
8273
+ model: replay.header.model,
8274
+ origin: replay.header.origin,
8275
+ ...replay.header.parentSessionId === void 0 ? {} : { parentSessionId: replay.header.parentSessionId },
8276
+ ...title === null ? {} : { title },
8277
+ lastUserText: lastUserText(replay.items),
8278
+ status: last?.kind === "record" && last.recordType === "session_closed" ? "closed" : existsSync2(`${path5}.lock`) ? "open" : "interrupted"
8279
+ };
8280
+ } catch {
8281
+ return {
8282
+ sessionId: path5,
8283
+ path: path5,
8284
+ createdAt: "",
8285
+ updatedAt: "",
8286
+ projectRoot: "",
8287
+ cwd: "",
8288
+ provider: "",
8289
+ model: "",
8290
+ origin: "import",
8291
+ lastUserText: "",
8292
+ status: "corrupt"
8293
+ };
8294
+ }
8295
+ }
8296
+ function cwdFrom(replay) {
8297
+ for (const item of [...replay.items].reverse()) {
8298
+ if (item.kind === "entry" && item.entryType === "system_context") {
8299
+ return item.payload.cwd;
8483
8300
  }
8484
- const mode = stringValue(forkedFrom, "mode");
8485
- if (mode !== "before" && mode !== "at" && mode !== "clone") {
8486
- throw new SessionSchemaError("invalid fork mode");
8301
+ }
8302
+ return replay.header.initialCwd;
8303
+ }
8304
+ function lastUserText(items) {
8305
+ for (const item of [...items].reverse()) {
8306
+ if (item.kind === "entry" && item.entryType === "user_message") {
8307
+ return item.payload.message.skillContext?.input ?? item.payload.message.content;
8308
+ }
8309
+ }
8310
+ return "";
8311
+ }
8312
+ function copyEntries(journal, entries) {
8313
+ for (const entry of entries) {
8314
+ const input = {
8315
+ entryType: entry.entryType,
8316
+ payload: entry.entryType === "context_reset" ? { ...entry.payload, resetThroughSeq: entries.filter((candidate) => candidate.seq <= entry.payload.resetThroughSeq).length } : entry.payload,
8317
+ ...entry.taskId === void 0 ? {} : { taskId: entry.taskId },
8318
+ ...entry.turnId === void 0 ? {} : { turnId: entry.turnId },
8319
+ copiedFrom: {
8320
+ sessionId: entry.sessionId,
8321
+ itemId: entry.id,
8322
+ seq: entry.seq
8323
+ }
8324
+ };
8325
+ journal.appendEntry(input);
8326
+ }
8327
+ }
8328
+ function isEntry(item) {
8329
+ return item.kind === "entry";
8330
+ }
8331
+ function appendTitleSnapshot(journal, title) {
8332
+ if (title !== null) {
8333
+ journal.appendRecord({
8334
+ recordType: "session_name_changed",
8335
+ payload: { title }
8336
+ });
8337
+ }
8338
+ }
8339
+
8340
+ // ../../packages/session/session-store/dist/transcript-projector.js
8341
+ function projectTranscript(entries) {
8342
+ const items = [];
8343
+ const skillNames = /* @__PURE__ */ new Map();
8344
+ for (const entry of entries) {
8345
+ if (entry.entryType === "user_message") {
8346
+ items.push({ kind: "user", text: entry.payload.message.skillContext?.input ?? entry.payload.message.content });
8347
+ } else if (entry.entryType === "assistant_message") {
8348
+ for (const block of entry.payload.message.content) {
8349
+ if (block.type !== "tool-call" || block.call.name !== "skill")
8350
+ continue;
8351
+ try {
8352
+ const args = JSON.parse(block.call.arguments);
8353
+ if (args && typeof args === "object" && "name" in args && typeof args.name === "string") {
8354
+ skillNames.set(block.call.id, args.name);
8355
+ }
8356
+ } catch {
8357
+ }
8358
+ }
8359
+ const text3 = entry.payload.message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
8360
+ const reasoning = entry.payload.message.content.filter((block) => block.type === "reasoning").map((block) => block.text).join("");
8361
+ items.push({
8362
+ kind: "assistant",
8363
+ text: text3,
8364
+ ...reasoning === "" ? {} : { reasoning }
8365
+ });
8366
+ } else if (entry.entryType === "tool_result") {
8367
+ const message = entry.payload.message;
8368
+ let result = message.content;
8369
+ if (message.toolName === "skill") {
8370
+ try {
8371
+ const value = JSON.parse(result);
8372
+ if (value && typeof value === "object") {
8373
+ if ("content" in value && typeof value.content === "string")
8374
+ result = value.content;
8375
+ else if ("error" in value && typeof value.error === "string")
8376
+ result = value.error;
8377
+ }
8378
+ } catch {
8379
+ }
8380
+ }
8381
+ items.push({
8382
+ kind: "tool",
8383
+ callId: entry.payload.message.toolCallId,
8384
+ name: entry.payload.message.toolName,
8385
+ subject: message.toolName === "skill" ? skillNames.get(message.toolCallId) ?? "" : message.toolCallId,
8386
+ result,
8387
+ isError: entry.payload.message.isError
8388
+ });
8389
+ } else if (entry.entryType === "context_reset") {
8390
+ items.push({ kind: "notice", text: "Conversation context was cleared.", tone: "info" });
8391
+ } else if (entry.entryType === "compaction") {
8392
+ items.push({
8393
+ kind: "notice",
8394
+ text: "Conversation context was compacted.",
8395
+ tone: "info"
8396
+ });
8487
8397
  }
8488
8398
  }
8489
- return input;
8399
+ return items;
8490
8400
  }
8491
- function parseSessionItem(value, expectedSessionId) {
8492
- const input = objectOf(value, "session item");
8493
- if (input["schemaVersion"] !== 1) {
8494
- throw new SessionSchemaError("unsupported session schema version");
8495
- }
8496
- const sessionId = requireString(input, "sessionId");
8497
- if (expectedSessionId !== void 0 && sessionId !== expectedSessionId) {
8498
- throw new SessionSchemaError("session id does not match header");
8401
+
8402
+ // ../../packages/session/session-context/dist/token-estimator.js
8403
+ var TEXT_BYTES_PER_TOKEN = 4;
8404
+ var BLOCK_OVERHEAD = 4;
8405
+ var MESSAGE_OVERHEAD = 4;
8406
+ var DefaultTokenEstimator = class {
8407
+ estimateMessages(messages) {
8408
+ return messages.reduce((total, message) => total + this.estimateMessage(message), 0);
8499
8409
  }
8500
- requirePositiveInteger(input, "seq");
8501
- requireString(input, "id");
8502
- requireString(input, "timestamp");
8503
- const kind = stringValue(input, "kind");
8504
- if (kind === "entry") {
8505
- const entryType = stringValue(input, "entryType");
8506
- if (!ENTRY_TYPES.has(entryType)) {
8507
- throw new SessionSchemaError("invalid session entry type");
8508
- }
8509
- const entryPayload = objectOf(input["payload"], "entry payload");
8510
- if (entryType === "image_offload" && (!Array.isArray(entryPayload.keys) || entryPayload.keys.some((key) => typeof key !== "string"))) {
8511
- throw new SessionSchemaError("invalid image offload keys");
8512
- }
8513
- if (entryPayload.message !== void 0) {
8514
- const message = objectOf(entryPayload.message, "message");
8515
- if (message.attachments !== void 0) {
8516
- if (message.role !== "user" && message.role !== "tool-result" || !Array.isArray(message.attachments))
8517
- throw new SessionSchemaError("invalid message attachments");
8518
- for (const attachment of message.attachments)
8519
- assertAttachmentContent(attachment);
8520
- if (typeof message.attachmentKey !== "string" || !message.attachmentKey)
8521
- throw new SessionSchemaError("missing attachment occurrence key");
8522
- }
8523
- }
8524
- if (entryType === "skill_context") {
8525
- const payload = objectOf(input["payload"], "skill payload");
8526
- const message = objectOf(payload["message"], "skill message");
8527
- const metadata2 = objectOf(message["skillContext"], "skill metadata");
8528
- if (message["role"] !== "user" || typeof message["content"] !== "string" || !["catalog", "activation", "invalidation"].includes(String(metadata2["kind"])) || typeof metadata2["key"] !== "string")
8529
- throw new SessionSchemaError("invalid skill context");
8410
+ estimateTools(tools) {
8411
+ if (tools.length === 0) {
8412
+ return 0;
8530
8413
  }
8531
- if (entryType === "tool_definitions" || entryType === "tool_catalog") {
8532
- const payload = objectOf(input["payload"], "tool context payload");
8533
- const message = objectOf(payload["message"], "tool context message");
8534
- if (typeof message["content"] !== "string")
8535
- throw new SessionSchemaError("tool context content must be text");
8536
- if (entryType === "tool_definitions") {
8537
- if (message["role"] !== "system" || !Array.isArray(message["toolDefinitions"]))
8538
- throw new SessionSchemaError("invalid tool definitions message");
8539
- for (const raw of message["toolDefinitions"]) {
8540
- const definition = objectOf(raw, "loaded tool");
8541
- requireString(definition, "version");
8542
- const spec2 = objectOf(definition["spec"], "tool spec");
8543
- requireString(spec2, "name");
8544
- if (typeof spec2["description"] !== "string" || !Array.isArray(spec2["promptGuidelines"]) || spec2["promptGuidelines"].some((value2) => typeof value2 !== "string"))
8545
- throw new SessionSchemaError("invalid tool spec");
8546
- objectOf(spec2["parameters"], "tool parameters");
8547
- if (spec2["catalog"] !== void 0) {
8548
- const catalog = objectOf(spec2["catalog"], "tool catalog metadata");
8549
- for (const key of ["source", "originalName", "binding"])
8550
- requireString(catalog, key);
8551
- if (catalog["exposure"] !== "direct" && catalog["exposure"] !== "deferred")
8552
- throw new SessionSchemaError("invalid exposure");
8553
- }
8554
- }
8555
- } else {
8556
- if (message["role"] !== "user")
8557
- throw new SessionSchemaError("invalid catalog message role");
8558
- const catalog = objectOf(message["toolCatalog"], "tool catalog");
8559
- if (catalog["mode"] !== "full" && catalog["mode"] !== "deferred")
8560
- throw new SessionSchemaError("invalid tool mode");
8561
- for (const version of Object.values(objectOf(catalog["tools"], "tool versions")))
8562
- if (typeof version !== "string")
8563
- throw new SessionSchemaError("invalid tool version");
8564
- }
8414
+ return this.estimateText(canonicalJson(tools.map(({ name, description, parameters }) => ({ name, description, parameters })))) + BLOCK_OVERHEAD;
8415
+ }
8416
+ estimateText(text3) {
8417
+ return Math.ceil(Buffer.byteLength(text3, "utf8") / TEXT_BYTES_PER_TOKEN);
8418
+ }
8419
+ measure(input) {
8420
+ const anchor = input.anchor ?? null;
8421
+ if (anchor !== null && this.isValidAnchor(input, anchor)) {
8422
+ const trailingEntries = input.entries.filter((entry) => entry.seq > anchor.throughSeq);
8423
+ const trailingTokens = this.estimateMessages(messagesForEntries(trailingEntries));
8424
+ return {
8425
+ totalTokens: anchor.contextTokens + trailingTokens,
8426
+ anchorTokens: anchor.contextTokens,
8427
+ trailingTokens,
8428
+ source: "provider_usage"
8429
+ };
8565
8430
  }
8566
- return input;
8431
+ const totalTokens = this.estimateMessages(input.messages) + this.estimateTools(input.tools);
8432
+ return {
8433
+ totalTokens,
8434
+ anchorTokens: 0,
8435
+ trailingTokens: totalTokens,
8436
+ source: "estimated"
8437
+ };
8567
8438
  }
8568
- if (kind === "record") {
8569
- const recordType = stringValue(input, "recordType");
8570
- if (!RECORD_TYPES.has(recordType)) {
8571
- throw new SessionSchemaError("invalid session record type");
8439
+ estimateMessage(message) {
8440
+ const attachmentTokens = message.role === "user" || message.role === "tool-result" ? (message.attachments ?? []).reduce((sum, block, index) => sum + (block.type === "image" && !message.omittedImageIndexes?.includes(index) ? 85 + 170 * Math.ceil(block.ref.width / 512) * Math.ceil(block.ref.height / 512) : 32), 0) : 0;
8441
+ if (message.role === "system" && message.toolDefinitions !== void 0)
8442
+ return 0;
8443
+ if (message.role === "system" || message.role === "user") {
8444
+ return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content) + attachmentTokens;
8572
8445
  }
8573
- const payload = objectOf(input["payload"], "record payload");
8574
- if (recordType === "session_name_changed") {
8575
- const title = payload["title"];
8576
- if (typeof title !== "string") {
8577
- throw new SessionSchemaError("invalid session name record");
8578
- }
8579
- try {
8580
- if (normalizeSessionTitle(title) !== title) {
8581
- throw new SessionSchemaError("session name must already be normalized");
8582
- }
8583
- } catch (error) {
8584
- if (error instanceof SessionSchemaError) {
8585
- throw error;
8586
- }
8587
- throw new SessionSchemaError("invalid session name record");
8588
- }
8446
+ if (message.role === "tool-result") {
8447
+ return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content) + attachmentTokens;
8589
8448
  }
8590
- return input;
8449
+ return MESSAGE_OVERHEAD + message.content.reduce((total, block) => {
8450
+ if (block.type === "text" || block.type === "reasoning") {
8451
+ return total + BLOCK_OVERHEAD + this.estimateText(block.text);
8452
+ }
8453
+ return total + BLOCK_OVERHEAD + this.estimateText(`${block.call.name}${canonicalJson(block.call.arguments)}`);
8454
+ }, 0);
8591
8455
  }
8592
- throw new SessionSchemaError("invalid session item kind");
8593
- }
8594
- function objectOf(value, label) {
8595
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
8596
- throw new SessionSchemaError(`${label} must be an object`);
8456
+ isValidAnchor(input, anchor) {
8457
+ return anchor.provider === input.provider && anchor.throughSeq > contextResetBoundary(input.entries) && anchor.model === input.model && anchor.systemFingerprint === input.systemFingerprint && anchor.projectInstructionsFingerprint === input.projectInstructionsFingerprint && anchor.toolsFingerprint === input.toolsFingerprint && input.entries.some((entry) => entry.id === anchor.throughEntryId && entry.seq === anchor.throughSeq);
8597
8458
  }
8598
- return value;
8459
+ };
8460
+ function normalizeProviderUsage(usage) {
8461
+ return usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0) + usage.outputTokens;
8599
8462
  }
8600
- function requireString(input, key) {
8601
- const value = input[key];
8602
- if (typeof value !== "string" || value === "") {
8603
- throw new SessionSchemaError(`${key} must be a non-empty string`);
8604
- }
8605
- return value;
8463
+ function fingerprintContextPart(value) {
8464
+ return createHash2("sha256").update(canonicalJson(value)).digest("hex");
8606
8465
  }
8607
- function stringValue(input, key) {
8608
- const value = requireString(input, key);
8609
- return value;
8466
+ function canonicalJson(value) {
8467
+ return JSON.stringify(sortStable(value));
8610
8468
  }
8611
- function requirePositiveInteger(input, key) {
8612
- const value = input[key];
8613
- if (!Number.isInteger(value) || typeof value !== "number" || value < 1) {
8614
- throw new SessionSchemaError(`${key} must be a positive integer`);
8469
+ function sortStable(value) {
8470
+ if (Array.isArray(value)) {
8471
+ return value.map(sortStable);
8472
+ }
8473
+ if (value !== null && typeof value === "object") {
8474
+ const record = value;
8475
+ const sorted = {};
8476
+ for (const key of Object.keys(record).sort()) {
8477
+ sorted[key] = sortStable(record[key]);
8478
+ }
8479
+ return sorted;
8615
8480
  }
8616
8481
  return value;
8617
8482
  }
8483
+ function messagesForEntries(entries) {
8484
+ return entries.flatMap((entry) => {
8485
+ if (entry.entryType === "tool_definitions" || entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
8486
+ return [entry.payload.message];
8487
+ if (entry.entryType === "system_context") {
8488
+ return [entry.payload.message];
8489
+ }
8490
+ if (entry.entryType === "project_instructions" || entry.entryType === "user_message" || entry.entryType === "reminder") {
8491
+ return [entry.payload.message];
8492
+ }
8493
+ if (entry.entryType === "assistant_message") {
8494
+ return [entry.payload.message];
8495
+ }
8496
+ if (entry.entryType === "tool_result") {
8497
+ return [entry.payload.message];
8498
+ }
8499
+ return [];
8500
+ });
8501
+ }
8618
8502
 
8619
- // ../../packages/session/session-store/dist/attachment-references.js
8620
- import { lstatSync, readdirSync } from "node:fs";
8621
- import { join } from "node:path";
8622
-
8623
- // ../../packages/session/session-store/dist/session-reader.js
8624
- import { readFileSync } from "node:fs";
8625
- var SessionCorruptError = class extends Error {
8626
- path;
8627
- line;
8628
- constructor(path5, line2, message, options = {}) {
8629
- super(`${path5}:${line2}: ${message}`, options.cause === void 0 ? void 0 : { cause: options.cause });
8630
- this.name = "SessionCorruptError";
8631
- this.path = path5;
8632
- this.line = line2;
8633
- }
8634
- };
8635
- function readSessionFile(path5) {
8636
- const text3 = readFileSync(path5, "utf8");
8637
- const endsWithNewline = text3.endsWith("\n");
8638
- const rawLines = text3.split("\n");
8639
- if (endsWithNewline) {
8640
- rawLines.pop();
8503
+ // ../../packages/session/session-context/dist/context-usage.js
8504
+ var ContextUsage = class {
8505
+ #tokens = 0;
8506
+ constructor(entries = []) {
8507
+ for (const entry of entries)
8508
+ this.append(entry);
8641
8509
  }
8642
- if (rawLines.length === 0 || rawLines[0] === "") {
8643
- throw new SessionCorruptError(path5, 1, "missing session header");
8510
+ get tokens() {
8511
+ return this.#tokens;
8644
8512
  }
8645
- const header = parseLine(path5, 1, rawLines[0], parseSessionHeader);
8646
- const items = [];
8647
- let ignoredTornTail = false;
8648
- let previousSeq = 0;
8649
- for (let index = 1; index < rawLines.length; index += 1) {
8650
- const lineNumber = index + 1;
8651
- const line2 = rawLines[index];
8652
- if (line2.trim() === "") {
8653
- continue;
8513
+ /** Histories are append-only and ordered; process each committed entry once. */
8514
+ append(entry) {
8515
+ if (entry.entryType === "context_reset" || entry.entryType === "compaction" || entry.entryType === "image_offload") {
8516
+ this.#tokens = null;
8517
+ return;
8654
8518
  }
8655
- try {
8656
- const item = parseSessionItem(JSON.parse(line2), header.sessionId);
8657
- if (item.seq !== previousSeq + 1) {
8658
- throw new SessionSchemaError("session item seq must be contiguous");
8659
- }
8660
- previousSeq = item.seq;
8661
- items.push(item);
8662
- } catch (error) {
8663
- if (!endsWithNewline && index === rawLines.length - 1) {
8664
- ignoredTornTail = true;
8665
- break;
8519
+ if (entry.entryType === "assistant_message") {
8520
+ const usage = validUsageTokens(entry.payload.usage);
8521
+ if (usage !== null) {
8522
+ this.#tokens = usage;
8523
+ return;
8666
8524
  }
8667
- throw corrupt(path5, lineNumber, error);
8525
+ }
8526
+ if (this.#tokens === null)
8527
+ return;
8528
+ if (entry.entryType === "user_message" || entry.entryType === "assistant_message" || entry.entryType === "tool_result" || entry.entryType === "reminder" || entry.entryType === "skill_context") {
8529
+ this.#tokens += estimateMessage(entry.payload.message);
8668
8530
  }
8669
8531
  }
8670
- return {
8671
- header,
8672
- items,
8673
- lastSeq: previousSeq,
8674
- ignoredTornTail,
8675
- openToolCalls: findOpenToolCalls(items)
8676
- };
8532
+ };
8533
+ function validUsageTokens(usage) {
8534
+ if (usage === void 0 || usage === null)
8535
+ return null;
8536
+ const fields = [
8537
+ usage.inputTokens,
8538
+ usage.outputTokens,
8539
+ usage.cacheReadTokens ?? 0,
8540
+ usage.cacheWriteTokens ?? 0
8541
+ ];
8542
+ if (!fields.every((value) => Number.isSafeInteger(value) && value >= 0))
8543
+ return null;
8544
+ const total = normalizeProviderUsage(usage);
8545
+ return Number.isSafeInteger(total) && total > 0 ? total : null;
8677
8546
  }
8678
- function parseLine(path5, line2, raw, parse3) {
8679
- try {
8680
- return parse3(JSON.parse(raw));
8681
- } catch (error) {
8682
- throw corrupt(path5, line2, error);
8683
- }
8547
+ function estimateMessage(message) {
8548
+ if (message.role === "system")
8549
+ return 0;
8550
+ if (message.role !== "assistant")
8551
+ return Math.ceil(message.content.length / 4);
8552
+ const chars = message.content.reduce((sum, block) => {
8553
+ if (block.type === "tool-call") {
8554
+ return sum + block.call.name.length + JSON.stringify(block.call.arguments).length;
8555
+ }
8556
+ return sum + block.text.length;
8557
+ }, 0);
8558
+ return Math.ceil(chars / 4);
8684
8559
  }
8685
- function corrupt(path5, line2, error) {
8686
- if (error instanceof SessionCorruptError) {
8687
- return error;
8560
+
8561
+ // ../../packages/session/session-context/dist/conversation-history.js
8562
+ var ConversationHistory = class _ConversationHistory {
8563
+ #journal;
8564
+ #entries;
8565
+ #staged = /* @__PURE__ */ new Map();
8566
+ #contextUsage;
8567
+ #listeners = /* @__PURE__ */ new Set();
8568
+ constructor(entries, journal) {
8569
+ this.#entries = [...entries];
8570
+ this.#journal = journal;
8571
+ this.#contextUsage = new ContextUsage(entries);
8688
8572
  }
8689
- return new SessionCorruptError(path5, line2, error instanceof Error ? error.message : "invalid session JSONL", { cause: error });
8690
- }
8691
- function findOpenToolCalls(items) {
8692
- const calls = /* @__PURE__ */ new Map();
8693
- for (const item of items) {
8694
- if (item.kind === "entry" && item.entryType === "assistant_message") {
8695
- for (const block of item.payload.message.content) {
8696
- if (block.type === "tool-call") {
8697
- calls.set(block.call.id, {
8698
- entryId: item.id,
8699
- seq: item.seq,
8700
- toolCallId: block.call.id,
8701
- toolName: block.call.name
8702
- });
8573
+ static fromReplay(replay, journal) {
8574
+ const history = new _ConversationHistory(replay.items.filter((item) => item.kind === "entry"), journal);
8575
+ for (const open6 of replay.openToolCalls) {
8576
+ const entry = history.appendToolResults({
8577
+ requestId: "recovered",
8578
+ recovered: true,
8579
+ messages: [{
8580
+ role: "tool-result",
8581
+ toolCallId: open6.toolCallId,
8582
+ toolName: open6.toolName,
8583
+ content: JSON.stringify({
8584
+ ok: false,
8585
+ status: "interrupted",
8586
+ error: "Tool execution was interrupted before a result was recorded."
8587
+ }),
8588
+ isError: true
8589
+ }]
8590
+ })[0];
8591
+ journal.appendRecord({
8592
+ recordType: "error",
8593
+ payload: {
8594
+ type: "interrupted_tool_call",
8595
+ toolCallId: open6.toolCallId,
8596
+ repairedEntryId: entry?.id ?? null
8703
8597
  }
8704
- }
8598
+ });
8705
8599
  }
8706
- if (item.kind === "entry" && item.entryType === "tool_result") {
8707
- calls.delete(item.payload.message.toolCallId);
8600
+ return history;
8601
+ }
8602
+ appendSystemContext(input) {
8603
+ return this.append({
8604
+ entryType: "system_context",
8605
+ payload: input
8606
+ });
8607
+ }
8608
+ appendProjectInstructions(input) {
8609
+ if (input.message.content === "") {
8610
+ return null;
8708
8611
  }
8612
+ return this.append({
8613
+ entryType: "project_instructions",
8614
+ payload: {
8615
+ message: input.message,
8616
+ files: input.files,
8617
+ supersedesEntryIds: input.supersedesEntryIds ?? []
8618
+ }
8619
+ });
8709
8620
  }
8710
- return [...calls.values()];
8711
- }
8712
-
8713
- // ../../packages/session/session-store/dist/attachment-references.js
8714
- function attachmentReferences(entries) {
8715
- const result = /* @__PURE__ */ new Set();
8716
- for (const entry of entries) {
8717
- if (entry.entryType === "compaction" || entry.entryType === "image_offload")
8718
- continue;
8719
- if (!("message" in entry.payload) || !entry.payload.message || typeof entry.payload.message !== "object")
8720
- throw new Error("Incomplete session message during attachment scan");
8721
- const message = entry.payload.message;
8722
- if (!("attachments" in message))
8723
- continue;
8724
- for (const block of message.attachments ?? []) {
8725
- assertAttachmentContent(block);
8726
- result.add(block.ref.id);
8621
+ appendUser(input) {
8622
+ return this.append({
8623
+ entryType: "user_message",
8624
+ payload: input
8625
+ });
8626
+ }
8627
+ stageUser(input) {
8628
+ const token = randomUUID2().replaceAll("-", "");
8629
+ this.#staged.set(token, input);
8630
+ return {
8631
+ token,
8632
+ message: input.message,
8633
+ inputEventIds: input.inputEventIds
8634
+ };
8635
+ }
8636
+ commitStagedUser(token) {
8637
+ const staged = this.#staged.get(token);
8638
+ if (staged === void 0) {
8639
+ throw new Error(`unknown staged user message: ${token}`);
8727
8640
  }
8641
+ this.#staged.delete(token);
8642
+ return this.appendUser(staged);
8728
8643
  }
8729
- return result;
8730
- }
8731
- function scanAttachmentReferences(sessionsRoot) {
8732
- const result = /* @__PURE__ */ new Set();
8733
- const visit = (directory) => {
8734
- for (const file of readdirSync(directory, { withFileTypes: true })) {
8735
- const path5 = join(directory, file.name);
8736
- if (file.isSymbolicLink())
8737
- throw new Error(`Cannot scan symbolic session path: ${path5}`);
8738
- if (file.isDirectory())
8739
- visit(path5);
8740
- else if (file.name.endsWith(".jsonl")) {
8741
- if (!lstatSync(path5).isFile())
8742
- throw new Error(`Invalid session file: ${path5}`);
8743
- const replay = readSessionFile(path5);
8744
- if (replay.ignoredTornTail)
8745
- throw new Error(`Incomplete session journal: ${path5}`);
8746
- for (const id of attachmentReferences(replay.items.filter((item) => item.kind === "entry")))
8747
- result.add(id);
8644
+ discardStagedUser(token) {
8645
+ this.#staged.delete(token);
8646
+ }
8647
+ appendAssistant(input) {
8648
+ return this.append({
8649
+ entryType: "assistant_message",
8650
+ payload: input
8651
+ });
8652
+ }
8653
+ appendToolResults(input) {
8654
+ return input.messages.map((message) => this.append({
8655
+ entryType: "tool_result",
8656
+ payload: {
8657
+ message,
8658
+ requestId: input.requestId,
8659
+ recovered: input.recovered
8748
8660
  }
8749
- }
8750
- };
8751
- visit(sessionsRoot);
8752
- return result;
8753
- }
8754
-
8755
- // ../../packages/session/session-store/dist/session-paths.js
8756
- import { createHash as createHash2 } from "node:crypto";
8757
- import { mkdirSync, realpathSync } from "node:fs";
8758
- import { basename, join as join2, resolve } from "node:path";
8759
- function canonicalProjectRoot(projectRoot) {
8760
- try {
8761
- return realpathSync.native(projectRoot);
8762
- } catch {
8763
- return resolve(projectRoot);
8661
+ }));
8764
8662
  }
8765
- }
8766
- function projectKeyForRoot(projectRoot) {
8767
- const canonical = canonicalProjectRoot(projectRoot);
8768
- const digest = createHash2("sha256").update(canonical).digest("hex").slice(0, 12);
8769
- return `${sanitizePathPart(basename(canonical) || "project")}-${digest}`;
8770
- }
8771
- function ensureSessionDirectory(sessionsRoot, projectKey) {
8772
- const directory = join2(sessionsRoot, projectKey);
8773
- mkdirSync(directory, { recursive: true, mode: 448 });
8774
- return directory;
8775
- }
8776
- function sessionPathForHeader(sessionsRoot, projectKey, createdAt, sessionId) {
8777
- const safeCreatedAt = createdAt.replaceAll(":", "").replaceAll(".", "");
8778
- return join2(ensureSessionDirectory(sessionsRoot, projectKey), `${safeCreatedAt}_${sessionId}.jsonl`);
8779
- }
8780
- function sanitizePathPart(value) {
8781
- const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
8782
- return sanitized || "project";
8783
- }
8784
-
8785
- // ../../packages/session/session-store/dist/session-journal.js
8786
- import { closeSync, existsSync, fsyncSync, openSync, readFileSync as readFileSync2, unlinkSync, writeSync } from "node:fs";
8787
- import { randomUUID as randomUUID2 } from "node:crypto";
8788
- var SessionJournalLockError = class extends Error {
8789
- constructor(message) {
8790
- super(message);
8791
- this.name = "SessionJournalLockError";
8663
+ appendReminder(input) {
8664
+ return this.append({
8665
+ entryType: "reminder",
8666
+ payload: input
8667
+ });
8792
8668
  }
8793
- };
8794
- var FileSessionJournal = class {
8795
- attachments;
8796
- header;
8797
- path;
8798
- #fd;
8799
- #lockPath;
8800
- #nextSeq;
8801
- #closed = false;
8802
- constructor(input) {
8803
- this.attachments = input.attachments;
8804
- this.header = input.header;
8805
- this.path = input.path;
8806
- this.#nextSeq = input.nextSeq;
8807
- this.#fd = input.fd;
8808
- this.#lockPath = `${input.path}.lock`;
8669
+ appendToolDefinitions(input) {
8670
+ return this.append({ entryType: "tool_definitions", payload: input });
8809
8671
  }
8810
- get nextSeq() {
8811
- return this.#nextSeq;
8672
+ appendToolCatalog(input) {
8673
+ return this.append({ entryType: "tool_catalog", payload: input });
8812
8674
  }
8813
- appendEntry(input) {
8814
- const item = this.makeBase(input);
8815
- const entry = {
8816
- ...item,
8817
- kind: "entry",
8818
- entryType: input.entryType,
8819
- payload: input.payload
8820
- };
8821
- const message = "message" in entry.payload ? entry.payload.message : void 0;
8822
- const attachments = message && "attachments" in message ? message.attachments : void 0;
8823
- if (attachments?.length) {
8824
- for (const attachment of attachments)
8825
- assertAttachmentContent(attachment);
8826
- if (!this.attachments)
8827
- throw new Error("Attachment persistence requires an attachment store");
8828
- if (!("attachmentKey" in message) || !message.attachmentKey)
8829
- throw new Error("Attachment message requires an occurrence key");
8830
- this.attachments.commit(attachments.map((block) => block.ref), () => {
8831
- this.writeItem(entry);
8832
- this.flush();
8833
- });
8834
- } else
8835
- this.writeItem(entry);
8836
- if (entry.entryType === "compaction") {
8837
- this.flush();
8838
- }
8839
- return entry;
8675
+ appendSkillContext(input) {
8676
+ return this.append({ entryType: "skill_context", payload: input });
8840
8677
  }
8841
- appendRecord(input) {
8842
- const record = {
8843
- ...this.makeBase(input),
8844
- kind: "record",
8845
- recordType: input.recordType,
8846
- payload: input.payload ?? {}
8847
- };
8848
- this.writeItem(record);
8849
- if (record.recordType === "turn_finished" || record.recordType === "compaction_finished" || record.recordType === "session_closed") {
8850
- this.flush();
8851
- }
8852
- return record;
8678
+ appendCompaction(input) {
8679
+ return this.append({
8680
+ entryType: "compaction",
8681
+ payload: input
8682
+ });
8853
8683
  }
8854
- flush() {
8855
- if (!this.#closed) {
8856
- fsyncSync(this.#fd);
8857
- }
8684
+ entries() {
8685
+ return [...this.#entries];
8858
8686
  }
8859
- close() {
8860
- if (this.#closed) {
8687
+ offloadImages(keys2) {
8688
+ if (keys2.length === 0)
8861
8689
  return;
8862
- }
8863
- this.flush();
8864
- closeSync(this.#fd);
8865
- this.#closed = true;
8866
- if (existsSync(this.#lockPath)) {
8867
- unlinkSync(this.#lockPath);
8868
- }
8690
+ this.append({ entryType: "image_offload", payload: { keys: [...keys2] } });
8691
+ this.#journal.flush();
8869
8692
  }
8870
- makeBase(input) {
8871
- return {
8872
- schemaVersion: 1,
8873
- sessionId: this.header.sessionId,
8874
- seq: this.#nextSeq,
8875
- id: randomUUID2().replaceAll("-", ""),
8876
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8877
- ...input.taskId === void 0 ? {} : { taskId: input.taskId },
8878
- ...input.turnId === void 0 ? {} : { turnId: input.turnId },
8879
- ...input.copiedFrom === void 0 ? {} : { copiedFrom: input.copiedFrom }
8693
+ get contextTokens() {
8694
+ return this.#contextUsage.tokens;
8695
+ }
8696
+ onChange(listener) {
8697
+ this.#listeners.add(listener);
8698
+ return () => {
8699
+ this.#listeners.delete(listener);
8880
8700
  };
8881
8701
  }
8882
- writeItem(item) {
8883
- if (this.#closed) {
8884
- throw new Error("session journal is closed");
8885
- }
8886
- writeLine(this.#fd, item);
8887
- this.#nextSeq += 1;
8702
+ append(input) {
8703
+ const entry = this.#journal.appendEntry(input);
8704
+ this.#entries.push(entry);
8705
+ this.#contextUsage.append(entry);
8706
+ for (const listener of this.#listeners)
8707
+ listener();
8708
+ return entry;
8888
8709
  }
8889
8710
  };
8890
- function createSessionJournal(options) {
8891
- const header = options.header ?? makeHeader(options);
8892
- const path5 = options.path ?? sessionPathForHeader(options.sessionsRoot, header.projectKey, header.createdAt, header.sessionId);
8893
- const existing = existsSync(path5);
8894
- const lockPath = `${path5}.lock`;
8895
- acquireLock(lockPath, header.sessionId);
8896
- try {
8897
- const fd = openSync(path5, existing ? "a" : "wx", 384);
8898
- if (!existing) {
8899
- writeLine(fd, header);
8900
- fsyncSync(fd);
8901
- return new FileSessionJournal({ header, path: path5, nextSeq: 1, fd, attachments: options.attachments });
8711
+
8712
+ // ../../packages/llm/llm/dist/model-contracts.js
8713
+ var ModelStreamError = class extends Error {
8714
+ /** True when the stream had already delivered deltas before failing. */
8715
+ hadDelta;
8716
+ constructor(message, options = {}) {
8717
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
8718
+ this.name = "ModelStreamError";
8719
+ this.hadDelta = options.hadDelta ?? false;
8720
+ }
8721
+ };
8722
+ var ModelStreamCancelled = class extends ModelStreamError {
8723
+ constructor(message, options = {}) {
8724
+ super(message, options);
8725
+ this.name = "ModelStreamCancelled";
8726
+ }
8727
+ };
8728
+ var REASONING_EFFORTS2 = [
8729
+ "off",
8730
+ "minimal",
8731
+ "low",
8732
+ "medium",
8733
+ "high",
8734
+ "xhigh",
8735
+ "max"
8736
+ ];
8737
+ function isReasoningEffort(value) {
8738
+ return REASONING_EFFORTS2.includes(value);
8739
+ }
8740
+ function clampReasoningEffort(supported, requested) {
8741
+ const available = supported.length === 0 ? ["off"] : supported;
8742
+ if (available.includes(requested)) {
8743
+ return requested;
8744
+ }
8745
+ const requestedIndex = REASONING_EFFORTS2.indexOf(requested);
8746
+ for (let index = requestedIndex; index < REASONING_EFFORTS2.length; index += 1) {
8747
+ const candidate = REASONING_EFFORTS2[index];
8748
+ if (available.includes(candidate)) {
8749
+ return candidate;
8750
+ }
8751
+ }
8752
+ for (let index = requestedIndex - 1; index >= 0; index -= 1) {
8753
+ const candidate = REASONING_EFFORTS2[index];
8754
+ if (available.includes(candidate)) {
8755
+ return candidate;
8902
8756
  }
8903
- const replay = readSessionFile(path5);
8904
- return new FileSessionJournal({
8905
- header: replay.header,
8906
- path: path5,
8907
- nextSeq: replay.lastSeq + 1,
8908
- fd,
8909
- attachments: options.attachments
8910
- });
8911
- } catch (error) {
8912
- releaseLock(lockPath);
8913
- throw error;
8914
8757
  }
8758
+ return "off";
8915
8759
  }
8916
- function makeHeader(options) {
8917
- const projectRoot = canonicalProjectRoot(required(options.projectRoot, "projectRoot"));
8918
- const createdAt = (/* @__PURE__ */ new Date()).toISOString();
8919
- return {
8920
- schemaVersion: 1,
8921
- type: "session_header",
8922
- sessionId: randomUUID2().replaceAll("-", ""),
8923
- createdAt,
8924
- initialCwd: required(options.initialCwd, "initialCwd"),
8925
- projectRoot,
8926
- projectKey: projectKeyForRoot(projectRoot),
8927
- appVersion: required(options.appVersion, "appVersion"),
8928
- provider: required(options.provider, "provider"),
8929
- model: required(options.model, "model"),
8930
- reasoningEffort: options.reasoningEffort ?? "high",
8931
- origin: options.origin ?? "new",
8932
- ...options.parentSessionId === void 0 ? {} : { parentSessionId: options.parentSessionId },
8933
- ...options.forkedFrom === void 0 ? {} : { forkedFrom: options.forkedFrom }
8934
- };
8760
+ function projectImageOffloads(message, keys2) {
8761
+ if (message.role !== "user" && message.role !== "tool-result" || !message.attachments?.length)
8762
+ return message;
8763
+ const omittedImageIndexes = message.attachments.flatMap((block, index) => block.type === "image" && keys2.has(`${message.attachmentKey}:${index}`) ? [index] : []);
8764
+ if (!omittedImageIndexes.length)
8765
+ return message;
8766
+ return { ...message, omittedImageIndexes };
8935
8767
  }
8936
- function acquireLock(path5, sessionId) {
8937
- try {
8938
- const fd = openSync(path5, "wx", 384);
8939
- writeLine(fd, { pid: process.pid, sessionId, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
8940
- closeSync(fd);
8941
- } catch (error) {
8942
- if (!isLiveLock(path5)) {
8943
- releaseLock(path5);
8944
- const fd = openSync(path5, "wx", 384);
8945
- writeLine(fd, { pid: process.pid, sessionId, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
8946
- closeSync(fd);
8947
- return;
8768
+ var ModelError = class extends ModelStreamError {
8769
+ kind;
8770
+ constructor(message, options) {
8771
+ super(message, { hadDelta: options.hadDelta ?? false, cause: options.cause });
8772
+ this.name = "ModelError";
8773
+ this.kind = options.kind;
8774
+ }
8775
+ };
8776
+ var CONTEXT_OVERFLOW_PATTERN = /context length|maximum context|context window|too many tokens|reduce the length/i;
8777
+ var NETWORK_PATTERN = /fetch failed|network|econnreset|econnrefused|etimedout|socket hang up|timed out/i;
8778
+ function readStatus(record) {
8779
+ let status = record["status_code"];
8780
+ if (status === null || status === void 0) {
8781
+ const response = record["response"];
8782
+ if (response !== null && typeof response === "object") {
8783
+ status = response["status_code"];
8784
+ }
8785
+ }
8786
+ if (status === null || status === void 0) {
8787
+ status = record["status"];
8788
+ }
8789
+ return status;
8790
+ }
8791
+ function classifyModelError(error) {
8792
+ let current = error;
8793
+ const seen = /* @__PURE__ */ new Set();
8794
+ while (current !== null && current !== void 0 && (typeof current === "object" || typeof current === "function") && !seen.has(current)) {
8795
+ seen.add(current);
8796
+ const record = current;
8797
+ const status = readStatus(record);
8798
+ if (status === 401 || status === 403 || record["name"] === "AuthenticationError") {
8799
+ return "authentication";
8800
+ }
8801
+ if (status === 429 || record["name"] === "RateLimitError") {
8802
+ return "rate_limited";
8803
+ }
8804
+ const message = typeof record["message"] === "string" ? record["message"] : "";
8805
+ if (status === 408 || status === 504 || record["name"] === "TimeoutError" || /\btimeout\b|\btimed out\b/i.test(message)) {
8806
+ return "timeout";
8807
+ }
8808
+ if (CONTEXT_OVERFLOW_PATTERN.test(message)) {
8809
+ return "context_overflow";
8948
8810
  }
8949
- throw new SessionJournalLockError(`session journal is locked: ${path5}`);
8950
- }
8951
- }
8952
- function isLiveLock(path5) {
8953
- if (!existsSync(path5)) {
8954
- return false;
8955
- }
8956
- try {
8957
- const parsed = JSON.parse(readFileSync2(path5, "utf8"));
8958
- if (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid)) {
8959
- return false;
8811
+ if (typeof status === "number" && status >= 500) {
8812
+ return "server";
8960
8813
  }
8961
- process.kill(parsed.pid, 0);
8962
- return true;
8963
- } catch (error) {
8964
- const code = error.code;
8965
- return code === "EPERM";
8966
- }
8967
- }
8968
- function releaseLock(path5) {
8969
- if (existsSync(path5)) {
8970
- unlinkSync(path5);
8814
+ if (record["name"] === "APIConnectionError" || NETWORK_PATTERN.test(message)) {
8815
+ return "retryable";
8816
+ }
8817
+ current = record["cause"];
8971
8818
  }
8819
+ return "retryable";
8972
8820
  }
8973
- function writeLine(fd, value) {
8974
- writeSync(fd, `${JSON.stringify(value)}
8975
- `);
8821
+ function modelErrorKind(error) {
8822
+ return error instanceof ModelError ? error.kind : classifyModelError(error);
8976
8823
  }
8977
- function required(value, label) {
8978
- if (value === void 0 || value === "") {
8979
- throw new Error(`${label} is required`);
8824
+ function portableModelMessage(message) {
8825
+ if (message.role !== "assistant") {
8826
+ return message;
8980
8827
  }
8981
- return value;
8828
+ return {
8829
+ role: "assistant",
8830
+ provider: message.provider,
8831
+ model: message.model,
8832
+ content: message.content
8833
+ };
8982
8834
  }
8983
8835
 
8984
- // ../../packages/session/session-store/dist/session-manager.js
8985
- import { existsSync as existsSync2, readdirSync as readdirSync2, statSync } from "node:fs";
8986
- import { join as join3 } from "node:path";
8987
-
8988
- // ../../packages/session/session-store/dist/session-tree.js
8989
- function buildSessionTree(sessions) {
8990
- const nodes = /* @__PURE__ */ new Map();
8991
- for (const session of sessions) {
8992
- nodes.set(session.sessionId, { session, children: [] });
8836
+ // ../../packages/llm/llm/dist/model-runtime.js
8837
+ var ModelRuntime = class {
8838
+ adapter;
8839
+ retryDelaysMs;
8840
+ sleep;
8841
+ constructor(adapter, options = {}) {
8842
+ this.adapter = adapter;
8843
+ this.retryDelaysMs = validateRetryDelays(options.retryDelaysMs ?? [250, 1e3]);
8844
+ this.sleep = options.sleep ?? defaultSleep;
8993
8845
  }
8994
- const roots = [];
8995
- for (const session of sessions) {
8996
- const node = nodes.get(session.sessionId);
8997
- const parentId = session.parentSessionId;
8998
- const parent = parentId === void 0 ? void 0 : nodes.get(parentId);
8999
- if (parent === void 0) {
9000
- roots.push(node);
9001
- } else {
9002
- parent.children.push(node);
8846
+ async complete(request) {
8847
+ if (request.cancelToken?.isCancelled()) {
8848
+ throw new ModelStreamCancelled(request.cancelToken.reason || "cancelled");
9003
8849
  }
9004
- }
9005
- const sortNodes = (items) => items.sort((left, right) => right.session.createdAt.localeCompare(left.session.createdAt)).map((item) => ({
9006
- session: item.session,
9007
- children: sortNodes(item.children)
9008
- }));
9009
- return sortNodes(roots);
9010
- }
9011
-
9012
- // ../../packages/session/session-store/dist/session-manager.js
9013
- var SessionManager = class {
9014
- #attachments;
9015
- #sessionsRoot;
9016
- #appVersion;
9017
- constructor(options) {
9018
- this.#attachments = options.attachments;
9019
- this.#sessionsRoot = options.sessionsRoot;
9020
- this.#appVersion = options.appVersion;
9021
- }
9022
- create(options) {
9023
- const journal = createSessionJournal({
9024
- attachments: this.#attachments,
9025
- sessionsRoot: this.#sessionsRoot,
9026
- projectRoot: options.projectRoot,
9027
- initialCwd: options.initialCwd,
9028
- appVersion: this.#appVersion,
9029
- provider: options.provider,
9030
- model: options.model,
9031
- reasoningEffort: options.reasoningEffort,
9032
- origin: "new"
9033
- });
9034
- return {
9035
- header: journal.header,
9036
- journal,
9037
- replay: {
9038
- header: journal.header,
9039
- items: [],
9040
- lastSeq: 0,
9041
- ignoredTornTail: false,
9042
- openToolCalls: []
8850
+ const maxAttempts = validateMaxAttempts(request.maxAttempts ?? 3);
8851
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
8852
+ const adapterRequest = {
8853
+ ...request.imageContext === void 0 ? {} : { imageContext: request.imageContext },
8854
+ provider: request.provider,
8855
+ model: request.model,
8856
+ ...request.baseUrl === void 0 ? {} : { baseUrl: request.baseUrl },
8857
+ messages: request.messages,
8858
+ tools: request.tools,
8859
+ ...request.reasoningEffort === void 0 ? {} : { reasoningEffort: request.reasoningEffort },
8860
+ ...request.temperature === void 0 ? {} : { temperature: request.temperature },
8861
+ ...request.maxOutputTokens === void 0 ? {} : { maxOutputTokens: request.maxOutputTokens },
8862
+ ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs },
8863
+ ...request.requestId === void 0 ? {} : { requestId: request.requestId },
8864
+ ...request.cancelToken == null ? {} : { cancelToken: request.cancelToken },
8865
+ ...request.isRequestActive == null ? {} : { isRequestActive: request.isRequestActive },
8866
+ ...request.onDelta == null ? {} : { onEvent: (event) => forwardEvent(event, request.onDelta) },
8867
+ ...request.onRequestOpened == null ? {} : { onRequestOpened: request.onRequestOpened }
8868
+ };
8869
+ try {
8870
+ return await this.adapter.runAttempt(adapterRequest);
8871
+ } catch (error) {
8872
+ const normalized = normalizeRuntimeError(error, request.cancelToken ?? null);
8873
+ if (!shouldRetry(normalized, attempt, maxAttempts)) {
8874
+ throw normalized;
8875
+ }
8876
+ const delayMs = this.retryDelaysMs[Math.min(attempt - 1, this.retryDelaysMs.length - 1)];
8877
+ request.onDelta?.("model_retry_scheduled", {
8878
+ attempt: attempt + 1,
8879
+ max_attempts: maxAttempts,
8880
+ delay_ms: delayMs,
8881
+ error_kind: normalized.kind
8882
+ });
8883
+ await this.sleep(delayMs, request.cancelToken ?? null);
9043
8884
  }
9044
- };
9045
- }
9046
- open(sessionId) {
9047
- const summary = this.list().find((item) => item.sessionId === sessionId);
9048
- if (summary === void 0 || summary.status === "corrupt") {
9049
- throw new Error(`unknown session: ${sessionId}`);
9050
8885
  }
9051
- const replay = readSessionFile(summary.path);
9052
- const journal = createSessionJournal({
9053
- attachments: this.#attachments,
9054
- sessionsRoot: this.#sessionsRoot,
9055
- header: replay.header,
9056
- path: summary.path
9057
- });
9058
- return { header: replay.header, journal, replay };
9059
- }
9060
- continueLatest(projectRoot) {
9061
- const latest = this.list(projectRoot).find((item) => item.status !== "corrupt");
9062
- return latest === void 0 ? null : this.open(latest.sessionId);
8886
+ throw new ModelError("model retry loop exhausted", { kind: "protocol" });
9063
8887
  }
9064
- list(projectRoot) {
9065
- const canonical = projectRoot === void 0 ? null : canonicalProjectRoot(projectRoot);
9066
- return this.sessionPaths().map((path5) => summaryForPath(path5)).filter((summary) => canonical === null || summary.projectRoot === canonical).sort((left, right) => {
9067
- const updated = right.updatedAt.localeCompare(left.updatedAt);
9068
- return updated === 0 ? right.createdAt.localeCompare(left.createdAt) : updated;
8888
+ };
8889
+ function forwardEvent(event, handler) {
8890
+ if (event.type === "text-delta") {
8891
+ handler?.("model_text_delta", { text: event.text });
8892
+ } else if (event.type === "reasoning-delta") {
8893
+ handler?.("model_reasoning_delta", { text: event.text });
8894
+ } else if (event.type === "tool-call-delta") {
8895
+ handler?.("model_tool_call_delta", {
8896
+ index: event.index,
8897
+ id: event.id,
8898
+ name: event.name ?? null,
8899
+ arguments: event.argumentsDelta
9069
8900
  });
8901
+ } else {
8902
+ handler?.("model_response_validating", {});
9070
8903
  }
9071
- tree(projectRoot) {
9072
- return buildSessionTree(this.list(projectRoot).filter((item) => item.status !== "corrupt"));
8904
+ }
8905
+ function normalizeRuntimeError(error, cancelToken) {
8906
+ if (error instanceof ModelStreamCancelled) {
8907
+ return error;
9073
8908
  }
9074
- fork(options) {
9075
- const parent = this.parentReplay(options.parentSessionId);
9076
- const target = parent.items.find((item) => item.kind === "entry" && item.entryType === "user_message" && item.id === options.entryId);
9077
- if (target === void 0) {
9078
- throw new Error("fork target must be a user_message entry");
9079
- }
9080
- const child = createSessionJournal({
9081
- attachments: this.#attachments,
9082
- sessionsRoot: this.#sessionsRoot,
9083
- projectRoot: parent.header.projectRoot,
9084
- initialCwd: parent.header.initialCwd,
9085
- appVersion: this.#appVersion,
9086
- provider: parent.header.provider,
9087
- model: parent.header.model,
9088
- reasoningEffort: parent.header.reasoningEffort,
9089
- origin: "fork",
9090
- parentSessionId: parent.header.sessionId,
9091
- forkedFrom: {
9092
- sessionId: parent.header.sessionId,
9093
- seq: target.seq,
9094
- entryId: target.id,
9095
- mode: options.mode
9096
- }
9097
- });
9098
- const throughSeq = options.mode === "before" ? target.seq - 1 : target.seq;
9099
- copyEntries(child, parent.items.filter(isEntry).filter((item) => item.seq <= throughSeq));
9100
- appendTitleSnapshot(child, sessionTitleAt(parent.items, throughSeq));
9101
- child.close();
9102
- return {
9103
- sessionId: child.header.sessionId,
9104
- path: child.path,
9105
- editorText: options.mode === "before" ? target.payload.message.skillContext?.input ?? target.payload.message.content : ""
9106
- };
8909
+ if (cancelToken?.isCancelled()) {
8910
+ return new ModelStreamCancelled(cancelToken.reason || "cancelled", { cause: error });
9107
8911
  }
9108
- clone(options) {
9109
- const parent = this.parentReplay(options.parentSessionId);
9110
- const lastEntry = [...parent.items].reverse().find((item) => item.kind === "entry");
9111
- const child = createSessionJournal({
9112
- attachments: this.#attachments,
9113
- sessionsRoot: this.#sessionsRoot,
9114
- projectRoot: parent.header.projectRoot,
9115
- initialCwd: parent.header.initialCwd,
9116
- appVersion: this.#appVersion,
9117
- provider: parent.header.provider,
9118
- model: parent.header.model,
9119
- reasoningEffort: parent.header.reasoningEffort,
9120
- origin: "clone",
9121
- parentSessionId: parent.header.sessionId,
9122
- forkedFrom: {
9123
- sessionId: parent.header.sessionId,
9124
- ...lastEntry === void 0 ? {} : { seq: lastEntry.seq, entryId: lastEntry.id },
9125
- mode: "clone"
9126
- }
8912
+ if (error instanceof ModelError) {
8913
+ return error;
8914
+ }
8915
+ if (error instanceof ModelStreamError) {
8916
+ return new ModelError(error.message, {
8917
+ kind: modelErrorKind(error),
8918
+ hadDelta: error.hadDelta,
8919
+ cause: error
9127
8920
  });
9128
- copyEntries(child, parent.items.filter(isEntry));
9129
- appendTitleSnapshot(child, sessionTitleAt(parent.items));
9130
- child.close();
9131
- return { sessionId: child.header.sessionId, path: child.path };
9132
8921
  }
9133
- parentReplay(sessionId) {
9134
- const summary = this.list().find((item) => item.sessionId === sessionId);
9135
- if (summary === void 0 || summary.status === "corrupt") {
9136
- throw new Error(`unknown session: ${sessionId}`);
8922
+ return new ModelError(errorMessage(error), {
8923
+ kind: modelErrorKind(error),
8924
+ cause: error
8925
+ });
8926
+ }
8927
+ function shouldRetry(error, attempt, maxAttempts) {
8928
+ if (!(error instanceof ModelError) || attempt >= maxAttempts) {
8929
+ return false;
8930
+ }
8931
+ if (error.hadDelta) {
8932
+ return false;
8933
+ }
8934
+ return error.kind === "timeout" || error.kind === "rate_limited" || error.kind === "server" || error.kind === "retryable";
8935
+ }
8936
+ function validateMaxAttempts(value) {
8937
+ if (!Number.isInteger(value) || value < 1 || value > 10) {
8938
+ throw new RangeError("maxAttempts must be an integer from 1 through 10");
8939
+ }
8940
+ return value;
8941
+ }
8942
+ function validateRetryDelays(value) {
8943
+ if (value.length === 0) {
8944
+ throw new RangeError("retryDelaysMs must not be empty");
8945
+ }
8946
+ for (const delay2 of value) {
8947
+ if (!Number.isInteger(delay2) || delay2 < 0 || !Number.isFinite(delay2)) {
8948
+ throw new RangeError("retryDelaysMs must contain finite non-negative integers");
9137
8949
  }
9138
- return readSessionFile(summary.path);
9139
8950
  }
9140
- sessionPaths() {
9141
- if (!existsSync2(this.#sessionsRoot)) {
9142
- return [];
8951
+ return [...value];
8952
+ }
8953
+ async function defaultSleep(delayMs, cancelToken) {
8954
+ if (cancelToken?.isCancelled()) {
8955
+ throw new ModelStreamCancelled(cancelToken.reason || "cancelled");
8956
+ }
8957
+ const cancelled = await cancelToken?.wait(delayMs);
8958
+ if (cancelled) {
8959
+ throw new ModelStreamCancelled(cancelToken?.reason || "cancelled");
8960
+ }
8961
+ if (cancelToken === null) {
8962
+ await new Promise((resolve15) => setTimeout(resolve15, delayMs));
8963
+ }
8964
+ }
8965
+ function errorMessage(error) {
8966
+ return error instanceof Error ? error.message : String(error);
8967
+ }
8968
+
8969
+ // ../../packages/session/session-context/dist/context-builder.js
8970
+ var ContextBuildError = class extends Error {
8971
+ entryId;
8972
+ constructor(entryId, message) {
8973
+ super(`${entryId}: ${message}`);
8974
+ this.name = "ContextBuildError";
8975
+ this.entryId = entryId;
8976
+ }
8977
+ };
8978
+ var ContextBuilder = class {
8979
+ build(input) {
8980
+ const sorted = [...input.entries].sort((left, right) => left.seq - right.seq);
8981
+ const superseded = supersededEntryIds(sorted);
8982
+ const boundary = contextResetBoundary(sorted);
8983
+ const activeCompaction = latestCompaction(sorted.filter((entry) => entry.seq > boundary));
8984
+ const selected = [];
8985
+ const system = latestActive(sorted, "system_context", superseded);
8986
+ if (system !== null) {
8987
+ selected.push({ entryId: system.id, message: system.payload.message });
9143
8988
  }
9144
- const paths = [];
9145
- const visit = (directory) => {
9146
- for (const entry of readdirSync2(directory)) {
9147
- const absolute = join3(directory, entry);
9148
- const stat6 = statSync(absolute);
9149
- if (stat6.isDirectory()) {
9150
- visit(absolute);
9151
- } else if (absolute.endsWith(".jsonl")) {
9152
- paths.push(absolute);
8989
+ for (const entry of sorted) {
8990
+ if (entry.entryType === "project_instructions" && !superseded.has(entry.id)) {
8991
+ selected.push({ entryId: entry.id, message: entry.payload.message });
8992
+ }
8993
+ }
8994
+ if (activeCompaction !== null) {
8995
+ selected.push({
8996
+ entryId: activeCompaction.id,
8997
+ message: {
8998
+ role: "user",
8999
+ content: `<conversation_summary>
9000
+ ${activeCompaction.payload.summary}
9001
+ </conversation_summary>`
9153
9002
  }
9003
+ });
9004
+ }
9005
+ for (const entry of conversationTail(sorted, activeCompaction).filter((entry2) => entry2.seq > boundary)) {
9006
+ const message = modelMessageForEntry(entry, input.currentProvider, input.currentModel);
9007
+ if (message !== null) {
9008
+ selected.push({ entryId: entry.id, message });
9154
9009
  }
9010
+ }
9011
+ validateToolPairs(selected);
9012
+ const offloaded = new Set(sorted.flatMap((entry) => entry.entryType === "image_offload" ? entry.payload.keys : []));
9013
+ return {
9014
+ messages: selected.map((item) => projectImageOffloads(item.message, offloaded)),
9015
+ sourceEntryIds: selected.map((item) => item.entryId),
9016
+ activeCompactionId: activeCompaction?.id ?? null
9155
9017
  };
9156
- visit(this.#sessionsRoot);
9157
- return paths.sort();
9158
9018
  }
9159
9019
  };
9160
- function summaryForPath(path5) {
9161
- try {
9162
- const replay = readSessionFile(path5);
9163
- const last = replay.items.at(-1);
9164
- const title = sessionTitleAt(replay.items);
9165
- return {
9166
- sessionId: replay.header.sessionId,
9167
- path: path5,
9168
- createdAt: replay.header.createdAt,
9169
- updatedAt: last?.timestamp ?? replay.header.createdAt,
9170
- projectRoot: replay.header.projectRoot,
9171
- cwd: cwdFrom(replay),
9172
- provider: replay.header.provider,
9173
- model: replay.header.model,
9174
- origin: replay.header.origin,
9175
- ...replay.header.parentSessionId === void 0 ? {} : { parentSessionId: replay.header.parentSessionId },
9176
- ...title === null ? {} : { title },
9177
- lastUserText: lastUserText(replay.items),
9178
- status: last?.kind === "record" && last.recordType === "session_closed" ? "closed" : existsSync2(`${path5}.lock`) ? "open" : "interrupted"
9179
- };
9180
- } catch {
9181
- return {
9182
- sessionId: path5,
9183
- path: path5,
9184
- createdAt: "",
9185
- updatedAt: "",
9186
- projectRoot: "",
9187
- cwd: "",
9188
- provider: "",
9189
- model: "",
9190
- origin: "import",
9191
- lastUserText: "",
9192
- status: "corrupt"
9193
- };
9020
+ function supersededEntryIds(entries) {
9021
+ const superseded = /* @__PURE__ */ new Set();
9022
+ for (const entry of entries) {
9023
+ if (entry.entryType === "system_context" && entry.payload.supersedesEntryId !== void 0) {
9024
+ superseded.add(entry.payload.supersedesEntryId);
9025
+ }
9026
+ if (entry.entryType === "project_instructions") {
9027
+ for (const id of entry.payload.supersedesEntryIds) {
9028
+ superseded.add(id);
9029
+ }
9030
+ }
9194
9031
  }
9032
+ return superseded;
9195
9033
  }
9196
- function cwdFrom(replay) {
9197
- for (const item of [...replay.items].reverse()) {
9198
- if (item.kind === "entry" && item.entryType === "system_context") {
9199
- return item.payload.cwd;
9034
+ function latestActive(entries, entryType, superseded) {
9035
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
9036
+ const entry = entries[index];
9037
+ if (entry.entryType === entryType && !superseded.has(entry.id)) {
9038
+ return entry;
9200
9039
  }
9201
9040
  }
9202
- return replay.header.initialCwd;
9041
+ return null;
9203
9042
  }
9204
- function lastUserText(items) {
9205
- for (const item of [...items].reverse()) {
9206
- if (item.kind === "entry" && item.entryType === "user_message") {
9207
- return item.payload.message.skillContext?.input ?? item.payload.message.content;
9043
+ function latestCompaction(entries) {
9044
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
9045
+ const entry = entries[index];
9046
+ if (entry.entryType === "compaction") {
9047
+ return entry;
9208
9048
  }
9209
9049
  }
9210
- return "";
9050
+ return null;
9211
9051
  }
9212
- function copyEntries(journal, entries) {
9213
- for (const entry of entries) {
9214
- const input = {
9215
- entryType: entry.entryType,
9216
- payload: entry.payload,
9217
- ...entry.taskId === void 0 ? {} : { taskId: entry.taskId },
9218
- ...entry.turnId === void 0 ? {} : { turnId: entry.turnId },
9219
- copiedFrom: {
9220
- sessionId: entry.sessionId,
9221
- itemId: entry.id,
9222
- seq: entry.seq
9052
+ function conversationTail(entries, activeCompaction) {
9053
+ const boundary = activeCompaction?.payload.retainedFromSeq ?? 1;
9054
+ return entries.filter((entry) => entry.seq >= boundary && entry.entryType !== "system_context" && entry.entryType !== "project_instructions" && entry.entryType !== "compaction");
9055
+ }
9056
+ function modelMessageForEntry(entry, currentProvider, currentModel) {
9057
+ if (entry.entryType === "tool_definitions" || entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
9058
+ return entry.payload.message;
9059
+ if (entry.entryType === "user_message" || entry.entryType === "reminder") {
9060
+ return entry.payload.message;
9061
+ }
9062
+ if (entry.entryType === "assistant_message") {
9063
+ const message = entry.payload.message;
9064
+ if (message.provider !== currentProvider || message.model !== currentModel) {
9065
+ return portableModelMessage(message);
9066
+ }
9067
+ return message;
9068
+ }
9069
+ if (entry.entryType === "tool_result") {
9070
+ return entry.payload.message;
9071
+ }
9072
+ return null;
9073
+ }
9074
+ function validateToolPairs(selected) {
9075
+ let expected = [];
9076
+ let ownerEntryId = null;
9077
+ for (const item of selected) {
9078
+ if (item.message.role === "assistant") {
9079
+ if (expected.length > 0) {
9080
+ throw new ContextBuildError(ownerEntryId ?? item.entryId, "missing tool result");
9223
9081
  }
9224
- };
9225
- journal.appendEntry(input);
9082
+ expected = item.message.content.filter((block) => block.type === "tool-call").map((block) => block.call.id);
9083
+ ownerEntryId = expected.length === 0 ? null : item.entryId;
9084
+ continue;
9085
+ }
9086
+ if (item.message.role === "tool-result") {
9087
+ const [next, ...rest] = expected;
9088
+ if (next === void 0) {
9089
+ throw new ContextBuildError(item.entryId, "orphan tool result");
9090
+ }
9091
+ if (item.message.toolCallId !== next) {
9092
+ throw new ContextBuildError(item.entryId, "tool result order does not match tool call order");
9093
+ }
9094
+ expected = rest;
9095
+ ownerEntryId = expected.length === 0 ? null : ownerEntryId;
9096
+ continue;
9097
+ }
9098
+ if (expected.length > 0) {
9099
+ throw new ContextBuildError(ownerEntryId ?? item.entryId, "missing tool result");
9100
+ }
9101
+ }
9102
+ if (expected.length > 0) {
9103
+ throw new ContextBuildError(ownerEntryId ?? "unknown", "missing tool result");
9104
+ }
9105
+ }
9106
+
9107
+ // ../../packages/session/session-context/dist/summary-prompt.js
9108
+ var COMPACTION_SYSTEM_PROMPT = "Summarize the conversation for continuation. Preserve user goals, explicit constraints, completed work, modified files, architectural decisions, tool outcomes, failures, current TODOs, and concrete state needed to continue. Do not include credentials. Do not invent results.";
9109
+
9110
+ // ../../packages/session/session-context/dist/compaction.js
9111
+ function selectCompactionPlan(input) {
9112
+ const boundary = contextResetBoundary(input.entries);
9113
+ input = { ...input, entries: input.entries.filter((entry) => entry.seq > boundary) };
9114
+ const units = semanticUnits(input.entries);
9115
+ const retained = [];
9116
+ let tokens = 0;
9117
+ for (let index = units.length - 1; index >= 0; index -= 1) {
9118
+ const unit = units[index];
9119
+ const unitTokens = input.estimator.estimateMessages(unit.messages);
9120
+ retained.unshift(unit);
9121
+ tokens += unitTokens;
9122
+ if (tokens >= input.retainTokens) {
9123
+ break;
9124
+ }
9226
9125
  }
9227
- }
9228
- function isEntry(item) {
9229
- return item.kind === "entry";
9230
- }
9231
- function appendTitleSnapshot(journal, title) {
9232
- if (title !== null) {
9233
- journal.appendRecord({
9234
- recordType: "session_name_changed",
9235
- payload: { title }
9236
- });
9126
+ if (retained.length === 0 && units.length > 0) {
9127
+ retained.push(units.at(-1));
9237
9128
  }
9129
+ const currentTurn = input.protectedFromSeq ?? [...input.entries].reverse().find((entry) => entry.entryType === "user_message")?.seq ?? 0;
9130
+ const hasCurrentImages = input.entries.some((entry) => entry.seq >= currentTurn && "message" in entry.payload && "attachments" in entry.payload.message && entry.payload.message.attachments?.some((block) => block.type === "image"));
9131
+ const retainedFirst = Math.min(retained[0]?.entries[0]?.seq ?? Number.MAX_SAFE_INTEGER, hasCurrentImages ? currentTurn : Number.MAX_SAFE_INTEGER);
9132
+ const retainedUnits = units.filter((unit) => unit.entries[0].seq >= retainedFirst);
9133
+ const summarized = units.filter((unit) => unit.entries[0].seq < retainedFirst);
9134
+ return {
9135
+ summarizedEntries: summarized.flatMap((unit) => unit.entries),
9136
+ retainedEntries: retainedUnits.flatMap((unit) => unit.entries),
9137
+ retainedFromSeq: retainedUnits[0]?.entries[0]?.seq ?? 1
9138
+ };
9238
9139
  }
9239
-
9240
- // ../../packages/session/session-store/dist/transcript-projector.js
9241
- function projectTranscript(entries) {
9242
- const items = [];
9243
- const skillNames = /* @__PURE__ */ new Map();
9140
+ function serializeConversation(entries) {
9141
+ const lines2 = ["<conversation>"];
9244
9142
  for (const entry of entries) {
9245
- if (entry.entryType === "user_message") {
9246
- items.push({ kind: "user", text: entry.payload.message.skillContext?.input ?? entry.payload.message.content });
9247
- } else if (entry.entryType === "assistant_message") {
9143
+ lines2.push(`<entry id="${entry.id}" seq="${entry.seq}" type="${entry.entryType}">`);
9144
+ if (entry.entryType === "assistant_message") {
9248
9145
  for (const block of entry.payload.message.content) {
9249
- if (block.type !== "tool-call" || block.call.name !== "skill")
9250
- continue;
9251
- try {
9252
- const args = JSON.parse(block.call.arguments);
9253
- if (args && typeof args === "object" && "name" in args && typeof args.name === "string") {
9254
- skillNames.set(block.call.id, args.name);
9255
- }
9256
- } catch {
9146
+ if (block.type === "tool-call") {
9147
+ lines2.push(`[assistant tool-call ${block.call.id} ${block.call.name}] ${JSON.stringify(block.call.arguments)}`);
9148
+ } else {
9149
+ lines2.push(`[assistant ${block.type}] ${block.text}`);
9257
9150
  }
9258
9151
  }
9259
- const text3 = entry.payload.message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
9260
- const reasoning = entry.payload.message.content.filter((block) => block.type === "reasoning").map((block) => block.text).join("");
9261
- items.push({
9262
- kind: "assistant",
9263
- text: text3,
9264
- ...reasoning === "" ? {} : { reasoning }
9265
- });
9266
9152
  } else if (entry.entryType === "tool_result") {
9267
- const message = entry.payload.message;
9268
- let result = message.content;
9269
- if (message.toolName === "skill") {
9270
- try {
9271
- const value = JSON.parse(result);
9272
- if (value && typeof value === "object") {
9273
- if ("content" in value && typeof value.content === "string")
9274
- result = value.content;
9275
- else if ("error" in value && typeof value.error === "string")
9276
- result = value.error;
9153
+ lines2.push(`[tool-result ${entry.payload.message.toolCallId} ${entry.payload.message.toolName}] ${entry.payload.message.content}`);
9154
+ } else if (entry.entryType === "tool_definitions") {
9155
+ lines2.push(`[loaded tool definitions] ${(entry.payload.message.toolDefinitions ?? []).map((tool) => tool.spec.name).join(", ")}`);
9156
+ } else if (entry.entryType === "user_message" || entry.entryType === "skill_context" || entry.entryType === "reminder" || entry.entryType === "project_instructions") {
9157
+ lines2.push(`[${entry.payload.message.role}] ${entry.payload.message.content}`);
9158
+ }
9159
+ if ("message" in entry.payload && "attachments" in entry.payload.message) {
9160
+ for (const block of entry.payload.message.attachments ?? [])
9161
+ lines2.push(`[attachment metadata only; not visual interpretation] ${JSON.stringify(block)}`);
9162
+ }
9163
+ lines2.push("</entry>");
9164
+ }
9165
+ lines2.push("</conversation>");
9166
+ return lines2.join("\n");
9167
+ }
9168
+ function semanticUnits(entries) {
9169
+ const units = [];
9170
+ const groupedDefinitions = /* @__PURE__ */ new Set();
9171
+ for (let index = 0; index < entries.length; index += 1) {
9172
+ const entry = entries[index];
9173
+ if (groupedDefinitions.has(entry.id))
9174
+ continue;
9175
+ if (entry.entryType === "context_reset" || entry.entryType === "image_offload" || entry.entryType === "system_context" || entry.entryType === "project_instructions" || entry.entryType === "compaction") {
9176
+ continue;
9177
+ }
9178
+ if (entry.entryType === "assistant_message") {
9179
+ const toolCallIds = entry.payload.message.content.filter((block) => block.type === "tool-call").map((block) => block.call.id);
9180
+ if (toolCallIds.length > 0) {
9181
+ const grouped = [entry];
9182
+ for (const id of toolCallIds) {
9183
+ const resultIndex = entries.findIndex((candidate) => candidate.entryType === "tool_result" && candidate.payload.message.toolCallId === id);
9184
+ if (resultIndex < 0) {
9185
+ throw new Error(`cannot compact incomplete tool call: ${id}`);
9277
9186
  }
9278
- } catch {
9187
+ grouped.push(entries[resultIndex]);
9188
+ }
9189
+ const lastResultIndex = entries.findIndex((candidate) => candidate.id === grouped.at(-1).id);
9190
+ const definitions = entries[lastResultIndex + 1];
9191
+ if (definitions?.entryType === "tool_definitions") {
9192
+ grouped.push(definitions);
9193
+ groupedDefinitions.add(definitions.id);
9279
9194
  }
9195
+ units.push({ entries: grouped, messages: grouped.map(messageForEntry) });
9196
+ continue;
9280
9197
  }
9281
- items.push({
9282
- kind: "tool",
9283
- callId: entry.payload.message.toolCallId,
9284
- name: entry.payload.message.toolName,
9285
- subject: message.toolName === "skill" ? skillNames.get(message.toolCallId) ?? "" : message.toolCallId,
9286
- result,
9287
- isError: entry.payload.message.isError
9288
- });
9289
- } else if (entry.entryType === "compaction") {
9290
- items.push({
9291
- kind: "notice",
9292
- text: "Conversation context was compacted.",
9293
- tone: "info"
9294
- });
9295
9198
  }
9199
+ if (entry.entryType === "tool_result") {
9200
+ continue;
9201
+ }
9202
+ units.push({ entries: [entry], messages: [messageForEntry(entry)] });
9203
+ }
9204
+ return units;
9205
+ }
9206
+ function messageForEntry(entry) {
9207
+ if (entry.entryType === "tool_definitions" || entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
9208
+ return entry.payload.message;
9209
+ if (entry.entryType === "assistant_message") {
9210
+ return entry.payload.message;
9211
+ }
9212
+ if (entry.entryType === "tool_result") {
9213
+ return entry.payload.message;
9214
+ }
9215
+ if (entry.entryType === "user_message" || entry.entryType === "reminder" || entry.entryType === "project_instructions") {
9216
+ return entry.payload.message;
9217
+ }
9218
+ throw new Error(`entry has no model message: ${entry.entryType}`);
9219
+ }
9220
+
9221
+ // ../../packages/session/session-context/dist/context-governor.js
9222
+ var ContextBudgetError = class extends Error {
9223
+ constructor(message) {
9224
+ super(message);
9225
+ this.name = "ContextBudgetError";
9226
+ }
9227
+ };
9228
+ var ContextGovernor = class {
9229
+ #builder;
9230
+ #estimator;
9231
+ #summarize;
9232
+ #appendCompaction;
9233
+ constructor(options) {
9234
+ this.#builder = options.builder ?? new ContextBuilder();
9235
+ this.#estimator = options.estimator ?? new DefaultTokenEstimator();
9236
+ this.#summarize = options.summarize;
9237
+ this.#appendCompaction = options.appendCompaction;
9238
+ }
9239
+ async prepare(input) {
9240
+ let built = this.#builder.build(input);
9241
+ let tokens = this.measure(input, built);
9242
+ const calculated = calculateModelBudget({ budget: input.budget, policy: input.policy });
9243
+ if (!input.policy.auto || tokens <= calculated.autoTrigger) {
9244
+ if (tokens > calculated.hardInputLimit)
9245
+ throw new ContextBudgetError("context exceeds hard input limit");
9246
+ return { built, messages: built.messages, compacted: false, tokens };
9247
+ }
9248
+ const result = await this.compact({ ...input, trigger: "automatic" });
9249
+ const entries = [...input.entries, result.entry];
9250
+ built = this.#builder.build({ ...input, entries });
9251
+ tokens = this.measure({ ...input, entries }, built);
9252
+ if (tokens > calculated.hardInputLimit) {
9253
+ throw new ContextBudgetError("context remains over hard input limit after compaction");
9254
+ }
9255
+ return { built, messages: built.messages, compacted: true, tokens };
9296
9256
  }
9297
- return items;
9257
+ async compact(input) {
9258
+ const calculated = calculateModelBudget({ budget: input.budget, policy: input.policy });
9259
+ const plan = selectCompactionPlan({
9260
+ protectedFromSeq: input.protectedFromSeq,
9261
+ entries: input.entries,
9262
+ // A search preflight reserves the pending call/result/definition unit.
9263
+ // Keep the latest completed unit and summarize older units to make room.
9264
+ retainTokens: input.reserveTokens === void 0 ? calculated.retainTokens : 1,
9265
+ estimator: this.#estimator
9266
+ });
9267
+ if (input.trigger === "manual" && plan.summarizedEntries.length === 0) {
9268
+ throw new Error("No messages to compact in current history.");
9269
+ }
9270
+ const summarizedFromSeq = plan.summarizedEntries[0]?.seq ?? 1;
9271
+ const summarizedThroughSeq = plan.summarizedEntries.at(-1)?.seq ?? Math.max(0, summarizedFromSeq - 1);
9272
+ const built = this.#builder.build(input);
9273
+ const activeCompaction = input.entries.find((entry) => entry.id === built.activeCompactionId);
9274
+ const messagesBefore = built.messages;
9275
+ const tokensBefore = this.#estimator.estimateMessages(messagesBefore) + this.#estimator.estimateTools(input.projectTools?.(messagesBefore) ?? input.tools);
9276
+ const summary = plan.summarizedEntries.length === 0 ? {
9277
+ summary: "No prior conversation needed compaction.",
9278
+ inputTokens: 0,
9279
+ outputTokens: 0
9280
+ } : await this.#summarize({
9281
+ serialized: serializeConversation(plan.summarizedEntries),
9282
+ maxSummaryTokens: input.policy.maxSummaryTokens
9283
+ });
9284
+ const payload = {
9285
+ summary: summary.summary,
9286
+ summarizedFromSeq,
9287
+ summarizedThroughSeq,
9288
+ retainedFromSeq: plan.retainedFromSeq,
9289
+ ...activeCompaction === void 0 ? {} : { supersedesCompactionId: activeCompaction.id },
9290
+ tokensBefore,
9291
+ retainedTokens: calculated.retainTokens,
9292
+ summaryInputTokens: summary.inputTokens,
9293
+ summaryOutputTokens: summary.outputTokens,
9294
+ provider: input.currentProvider,
9295
+ model: input.currentModel,
9296
+ trigger: input.trigger
9297
+ };
9298
+ return { entry: this.#appendCompaction(payload) };
9299
+ }
9300
+ async completeWithOverflowRecovery(input, send) {
9301
+ let prepared = await this.prepare(input);
9302
+ try {
9303
+ return await send(prepared);
9304
+ } catch (error) {
9305
+ if (!(error instanceof ModelError) || modelErrorKind(error) !== "context_overflow" || error.hadDelta) {
9306
+ throw error;
9307
+ }
9308
+ }
9309
+ const compaction = await this.compact({ ...input, trigger: "provider_overflow" });
9310
+ prepared = await this.prepare({ ...input, entries: [...input.entries, compaction.entry] });
9311
+ return await send(prepared);
9312
+ }
9313
+ measure(input, built) {
9314
+ const tools = input.projectTools?.(built.messages) ?? input.tools;
9315
+ return this.#estimator.measure({
9316
+ messages: built.messages,
9317
+ tools,
9318
+ entries: input.entries,
9319
+ anchor: input.anchor ?? null,
9320
+ provider: input.currentProvider,
9321
+ model: input.currentModel,
9322
+ systemFingerprint: fingerprintContextPart(built.messages.find((message) => message.role === "system") ?? null),
9323
+ projectInstructionsFingerprint: fingerprintContextPart(built.messages.filter((message, index) => index > 0 && message.role === "user")),
9324
+ toolsFingerprint: fingerprintContextPart(tools)
9325
+ }).totalTokens + (input.reserveTokens ?? 0);
9326
+ }
9327
+ };
9328
+ function calculateModelBudget(input) {
9329
+ const hardInputLimit = input.budget.contextWindow - input.budget.maxOutputTokens;
9330
+ const safetyTokens = Math.ceil(input.budget.contextWindow * input.policy.safetyRatio);
9331
+ const autoTrigger = Math.min(Math.floor(input.budget.contextWindow * input.policy.thresholdRatio), hardInputLimit - safetyTokens);
9332
+ const retainTokens = input.policy.retainTokens ?? Math.floor(input.budget.contextWindow * input.policy.retainRatio);
9333
+ return { hardInputLimit, safetyTokens, autoTrigger, retainTokens };
9298
9334
  }
9299
9335
 
9300
9336
  // ../../packages/llm/llm-pi-ai/dist/adapter.js