laohuang 0.9.2 → 0.9.4

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 +1970 -1708
  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
- import { dirname as dirname13, join as join20 } from "node:path";
7385
+ import { dirname as dirname13, join as join21 } 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
+ };
7981
+ }
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;
8300
7988
  }
8301
7989
  };
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 };
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 });
8002
+ }
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;
8014
+ }
8015
+ }
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}`);
8050
+ }
8051
+ }
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;
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`);
8080
+ }
8081
+ return value;
8308
8082
  }
8309
8083
 
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;
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";
8087
+
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: [] });
8317
8093
  }
8318
- };
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
+ }
8104
+ }
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);
8110
+ }
8319
8111
 
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");
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: []
8143
+ }
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}`);
8341
8150
  }
8342
- if (block.view !== void 0)
8343
- assertImageView(block.view);
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 };
8159
+ }
8160
+ continueLatest(projectRoot) {
8161
+ const latest = this.list(projectRoot).find((item) => item.status !== "corrupt");
8162
+ return latest === void 0 ? null : this.open(latest.sessionId);
8163
+ }
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
+ });
8170
+ }
8171
+ tree(projectRoot) {
8172
+ return buildSessionTree(this.list(projectRoot).filter((item) => item.status !== "corrupt"));
8173
+ }
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
+ };
8207
+ }
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 };
8232
+ }
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);
8239
+ }
8240
+ sessionPaths() {
8241
+ if (!existsSync2(this.#sessionsRoot)) {
8242
+ return [];
8243
+ }
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
+ };
8344
8294
  }
8345
8295
  }
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");
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;
8300
+ }
8350
8301
  }
8302
+ return replay.header.initialCwd;
8351
8303
  }
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");
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;
8362
8308
  }
8363
8309
  }
8310
+ return "";
8364
8311
  }
8365
-
8366
- // ../../packages/attachment/attachment/dist/attachment-store.js
8367
- var AttachmentStore = class {
8368
- };
8369
-
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.");
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);
8374
8326
  }
8375
- const title = input.trim();
8376
- if (title.length === 0) {
8377
- throw new Error("Session name must not be empty.");
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
+ });
8378
8337
  }
8379
- return title;
8380
8338
  }
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.");
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
+ }
8391
8358
  }
8392
- title = normalizeSessionTitle(value);
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
+ });
8393
8397
  }
8394
8398
  }
8395
- return title;
8399
+ return items;
8396
8400
  }
8397
8401
 
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";
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);
8448
8409
  }
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");
8410
+ estimateTools(tools) {
8411
+ if (tools.length === 0) {
8412
+ return 0;
8413
+ }
8414
+ return this.estimateText(canonicalJson(tools.map(({ name, description, parameters }) => ({ name, description, parameters })))) + BLOCK_OVERHEAD;
8454
8415
  }
8455
- if (input["type"] !== "session_header") {
8456
- throw new SessionSchemaError("invalid session header type");
8416
+ estimateText(text3) {
8417
+ return Math.ceil(Buffer.byteLength(text3, "utf8") / TEXT_BYTES_PER_TOKEN);
8457
8418
  }
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");
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
+ };
8430
+ }
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
+ };
8468
8438
  }
8469
- if (!ORIGINS.has(stringValue(input, "origin"))) {
8470
- throw new SessionSchemaError("invalid session origin");
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;
8445
+ }
8446
+ if (message.role === "tool-result") {
8447
+ return MESSAGE_OVERHEAD + BLOCK_OVERHEAD + this.estimateText(message.content) + attachmentTokens;
8448
+ }
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);
8455
+ }
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);
8458
+ }
8459
+ };
8460
+ function normalizeProviderUsage(usage) {
8461
+ return usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0) + usage.outputTokens;
8462
+ }
8463
+ function fingerprintContextPart(value) {
8464
+ return createHash2("sha256").update(canonicalJson(value)).digest("hex");
8465
+ }
8466
+ function canonicalJson(value) {
8467
+ return JSON.stringify(sortStable(value));
8468
+ }
8469
+ function sortStable(value) {
8470
+ if (Array.isArray(value)) {
8471
+ return value.map(sortStable);
8471
8472
  }
8472
- if (input["parentSessionId"] !== void 0) {
8473
- requireString(input, "parentSessionId");
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;
8474
8480
  }
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");
8481
+ return value;
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];
8480
8489
  }
8481
- if (forkedFrom["entryId"] !== void 0) {
8482
- requireString(forkedFrom, "entryId");
8490
+ if (entry.entryType === "project_instructions" || entry.entryType === "user_message" || entry.entryType === "reminder") {
8491
+ return [entry.payload.message];
8483
8492
  }
8484
- const mode = stringValue(forkedFrom, "mode");
8485
- if (mode !== "before" && mode !== "at" && mode !== "clone") {
8486
- throw new SessionSchemaError("invalid fork mode");
8493
+ if (entry.entryType === "assistant_message") {
8494
+ return [entry.payload.message];
8487
8495
  }
8488
- }
8489
- return input;
8496
+ if (entry.entryType === "tool_result") {
8497
+ return [entry.payload.message];
8498
+ }
8499
+ return [];
8500
+ });
8490
8501
  }
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");
8502
+
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);
8495
8509
  }
8496
- const sessionId = requireString(input, "sessionId");
8497
- if (expectedSessionId !== void 0 && sessionId !== expectedSessionId) {
8498
- throw new SessionSchemaError("session id does not match header");
8510
+ get tokens() {
8511
+ return this.#tokens;
8499
8512
  }
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");
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;
8512
8518
  }
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");
8519
+ if (entry.entryType === "assistant_message") {
8520
+ const usage = validUsageTokens(entry.payload.usage);
8521
+ if (usage !== null) {
8522
+ this.#tokens = usage;
8523
+ return;
8522
8524
  }
8523
8525
  }
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");
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);
8530
8530
  }
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
- }
8531
+ }
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;
8546
+ }
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);
8559
+ }
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);
8572
+ }
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
8554
8597
  }
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
- }
8598
+ });
8565
8599
  }
8566
- return input;
8600
+ return history;
8567
8601
  }
8568
- if (kind === "record") {
8569
- const recordType = stringValue(input, "recordType");
8570
- if (!RECORD_TYPES.has(recordType)) {
8571
- throw new SessionSchemaError("invalid session record type");
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;
8572
8611
  }
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");
8612
+ return this.append({
8613
+ entryType: "project_instructions",
8614
+ payload: {
8615
+ message: input.message,
8616
+ files: input.files,
8617
+ supersedesEntryIds: input.supersedesEntryIds ?? []
8588
8618
  }
8619
+ });
8620
+ }
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}`);
8589
8640
  }
8590
- return input;
8641
+ this.#staged.delete(token);
8642
+ return this.appendUser(staged);
8591
8643
  }
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`);
8644
+ discardStagedUser(token) {
8645
+ this.#staged.delete(token);
8597
8646
  }
8598
- return value;
8599
- }
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`);
8647
+ appendAssistant(input) {
8648
+ return this.append({
8649
+ entryType: "assistant_message",
8650
+ payload: input
8651
+ });
8604
8652
  }
8605
- return value;
8606
- }
8607
- function stringValue(input, key) {
8608
- const value = requireString(input, key);
8609
- return value;
8610
- }
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`);
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
8660
+ }
8661
+ }));
8615
8662
  }
8616
- return value;
8617
- }
8618
-
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;
8663
+ appendReminder(input) {
8664
+ return this.append({
8665
+ entryType: "reminder",
8666
+ payload: input
8667
+ });
8633
8668
  }
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();
8669
+ appendToolDefinitions(input) {
8670
+ return this.append({ entryType: "tool_definitions", payload: input });
8641
8671
  }
8642
- if (rawLines.length === 0 || rawLines[0] === "") {
8643
- throw new SessionCorruptError(path5, 1, "missing session header");
8672
+ appendToolCatalog(input) {
8673
+ return this.append({ entryType: "tool_catalog", payload: input });
8644
8674
  }
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;
8654
- }
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;
8666
- }
8667
- throw corrupt(path5, lineNumber, error);
8668
- }
8675
+ appendSkillContext(input) {
8676
+ return this.append({ entryType: "skill_context", payload: input });
8669
8677
  }
8670
- return {
8671
- header,
8672
- items,
8673
- lastSeq: previousSeq,
8674
- ignoredTornTail,
8675
- openToolCalls: findOpenToolCalls(items)
8676
- };
8677
- }
8678
- function parseLine(path5, line2, raw, parse3) {
8679
- try {
8680
- return parse3(JSON.parse(raw));
8681
- } catch (error) {
8682
- throw corrupt(path5, line2, error);
8678
+ appendCompaction(input) {
8679
+ return this.append({
8680
+ entryType: "compaction",
8681
+ payload: input
8682
+ });
8683
8683
  }
8684
- }
8685
- function corrupt(path5, line2, error) {
8686
- if (error instanceof SessionCorruptError) {
8687
- return error;
8684
+ entries() {
8685
+ return [...this.#entries];
8688
8686
  }
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
- });
8703
- }
8704
- }
8705
- }
8706
- if (item.kind === "entry" && item.entryType === "tool_result") {
8707
- calls.delete(item.payload.message.toolCallId);
8708
- }
8687
+ offloadImages(keys2) {
8688
+ if (keys2.length === 0)
8689
+ return;
8690
+ this.append({ entryType: "image_offload", payload: { keys: [...keys2] } });
8691
+ this.#journal.flush();
8709
8692
  }
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);
8727
- }
8693
+ get contextTokens() {
8694
+ return this.#contextUsage.tokens;
8728
8695
  }
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);
8748
- }
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);
8696
+ onChange(listener) {
8697
+ this.#listeners.add(listener);
8698
+ return () => {
8699
+ this.#listeners.delete(listener);
8700
+ };
8764
8701
  }
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";
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;
8792
8709
  }
8793
8710
  };
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`;
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;
8809
8720
  }
8810
- get nextSeq() {
8811
- return this.#nextSeq;
8721
+ };
8722
+ var ModelStreamCancelled = class extends ModelStreamError {
8723
+ constructor(message, options = {}) {
8724
+ super(message, options);
8725
+ this.name = "ModelStreamCancelled";
8812
8726
  }
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;
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;
8840
8744
  }
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();
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;
8851
8750
  }
8852
- return record;
8853
8751
  }
8854
- flush() {
8855
- if (!this.#closed) {
8856
- fsyncSync(this.#fd);
8752
+ for (let index = requestedIndex - 1; index >= 0; index -= 1) {
8753
+ const candidate = REASONING_EFFORTS2[index];
8754
+ if (available.includes(candidate)) {
8755
+ return candidate;
8857
8756
  }
8858
8757
  }
8859
- close() {
8860
- if (this.#closed) {
8861
- return;
8862
- }
8863
- this.flush();
8864
- closeSync(this.#fd);
8865
- this.#closed = true;
8866
- if (existsSync(this.#lockPath)) {
8867
- unlinkSync(this.#lockPath);
8758
+ return "off";
8759
+ }
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 };
8767
+ }
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"];
8868
8784
  }
8869
8785
  }
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 }
8880
- };
8786
+ if (status === null || status === void 0) {
8787
+ status = record["status"];
8881
8788
  }
8882
- writeItem(item) {
8883
- if (this.#closed) {
8884
- throw new Error("session journal is closed");
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";
8885
8800
  }
8886
- writeLine(this.#fd, item);
8887
- this.#nextSeq += 1;
8888
- }
8889
- };
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 });
8801
+ if (status === 429 || record["name"] === "RateLimitError") {
8802
+ return "rate_limited";
8902
8803
  }
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;
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";
8810
+ }
8811
+ if (typeof status === "number" && status >= 500) {
8812
+ return "server";
8813
+ }
8814
+ if (record["name"] === "APIConnectionError" || NETWORK_PATTERN.test(message)) {
8815
+ return "retryable";
8816
+ }
8817
+ current = record["cause"];
8914
8818
  }
8819
+ return "retryable";
8915
8820
  }
8916
- function makeHeader(options) {
8917
- const projectRoot = canonicalProjectRoot(required(options.projectRoot, "projectRoot"));
8918
- const createdAt = (/* @__PURE__ */ new Date()).toISOString();
8821
+ function modelErrorKind(error) {
8822
+ return error instanceof ModelError ? error.kind : classifyModelError(error);
8823
+ }
8824
+ function portableModelMessage(message) {
8825
+ if (message.role !== "assistant") {
8826
+ return message;
8827
+ }
8919
8828
  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 }
8829
+ role: "assistant",
8830
+ provider: message.provider,
8831
+ model: message.model,
8832
+ content: message.content
8934
8833
  };
8935
8834
  }
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;
8835
+
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;
8845
+ }
8846
+ async complete(request) {
8847
+ if (request.cancelToken?.isCancelled()) {
8848
+ throw new ModelStreamCancelled(request.cancelToken.reason || "cancelled");
8849
+ }
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);
8884
+ }
8948
8885
  }
8949
- throw new SessionJournalLockError(`session journal is locked: ${path5}`);
8886
+ throw new ModelError("model retry loop exhausted", { kind: "protocol" });
8887
+ }
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
8900
+ });
8901
+ } else {
8902
+ handler?.("model_response_validating", {});
8950
8903
  }
8951
8904
  }
8952
- function isLiveLock(path5) {
8953
- if (!existsSync(path5)) {
8954
- return false;
8905
+ function normalizeRuntimeError(error, cancelToken) {
8906
+ if (error instanceof ModelStreamCancelled) {
8907
+ return error;
8955
8908
  }
8956
- try {
8957
- const parsed = JSON.parse(readFileSync2(path5, "utf8"));
8958
- if (typeof parsed.pid !== "number" || !Number.isInteger(parsed.pid)) {
8959
- return false;
8960
- }
8961
- process.kill(parsed.pid, 0);
8962
- return true;
8963
- } catch (error) {
8964
- const code = error.code;
8965
- return code === "EPERM";
8909
+ if (cancelToken?.isCancelled()) {
8910
+ return new ModelStreamCancelled(cancelToken.reason || "cancelled", { cause: error });
8966
8911
  }
8967
- }
8968
- function releaseLock(path5) {
8969
- if (existsSync(path5)) {
8970
- unlinkSync(path5);
8912
+ if (error instanceof ModelError) {
8913
+ return error;
8971
8914
  }
8915
+ if (error instanceof ModelStreamError) {
8916
+ return new ModelError(error.message, {
8917
+ kind: modelErrorKind(error),
8918
+ hadDelta: error.hadDelta,
8919
+ cause: error
8920
+ });
8921
+ }
8922
+ return new ModelError(errorMessage(error), {
8923
+ kind: modelErrorKind(error),
8924
+ cause: error
8925
+ });
8972
8926
  }
8973
- function writeLine(fd, value) {
8974
- writeSync(fd, `${JSON.stringify(value)}
8975
- `);
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";
8976
8935
  }
8977
- function required(value, label) {
8978
- if (value === void 0 || value === "") {
8979
- throw new Error(`${label} is required`);
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");
8980
8939
  }
8981
8940
  return value;
8982
8941
  }
8983
-
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: [] });
8942
+ function validateRetryDelays(value) {
8943
+ if (value.length === 0) {
8944
+ throw new RangeError("retryDelaysMs must not be empty");
8993
8945
  }
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);
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");
9003
8949
  }
9004
8950
  }
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);
8951
+ return [...value];
9010
8952
  }
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: []
9043
- }
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
- }
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 };
8953
+ async function defaultSleep(delayMs, cancelToken) {
8954
+ if (cancelToken?.isCancelled()) {
8955
+ throw new ModelStreamCancelled(cancelToken.reason || "cancelled");
9059
8956
  }
9060
- continueLatest(projectRoot) {
9061
- const latest = this.list(projectRoot).find((item) => item.status !== "corrupt");
9062
- return latest === void 0 ? null : this.open(latest.sessionId);
8957
+ const cancelled = await cancelToken?.wait(delayMs);
8958
+ if (cancelled) {
8959
+ throw new ModelStreamCancelled(cancelToken?.reason || "cancelled");
9063
8960
  }
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;
9069
- });
8961
+ if (cancelToken === null) {
8962
+ await new Promise((resolve15) => setTimeout(resolve15, delayMs));
9070
8963
  }
9071
- tree(projectRoot) {
9072
- return buildSessionTree(this.list(projectRoot).filter((item) => item.status !== "corrupt"));
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;
9073
8976
  }
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");
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 });
9079
8988
  }
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
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 });
9096
8992
  }
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();
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>`
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 });
9009
+ }
9010
+ }
9011
+ validateToolPairs(selected);
9012
+ const offloaded = new Set(sorted.flatMap((entry) => entry.entryType === "image_offload" ? entry.payload.keys : []));
9102
9013
  return {
9103
- sessionId: child.header.sessionId,
9104
- path: child.path,
9105
- editorText: options.mode === "before" ? target.payload.message.skillContext?.input ?? target.payload.message.content : ""
9014
+ messages: selected.map((item) => projectImageOffloads(item.message, offloaded)),
9015
+ sourceEntryIds: selected.map((item) => item.entryId),
9016
+ activeCompactionId: activeCompaction?.id ?? null
9106
9017
  };
9107
9018
  }
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"
9019
+ };
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);
9126
9029
  }
9127
- });
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 };
9030
+ }
9132
9031
  }
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}`);
9032
+ return superseded;
9033
+ }
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;
9137
9039
  }
9138
- return readSessionFile(summary.path);
9139
9040
  }
9140
- sessionPaths() {
9141
- if (!existsSync2(this.#sessionsRoot)) {
9142
- return [];
9041
+ return null;
9042
+ }
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;
9143
9048
  }
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);
9153
- }
9154
- }
9155
- };
9156
- visit(this.#sessionsRoot);
9157
- return paths.sort();
9158
9049
  }
9159
- };
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
- };
9050
+ return null;
9051
+ }
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;
9194
9068
  }
9069
+ if (entry.entryType === "tool_result") {
9070
+ return entry.payload.message;
9071
+ }
9072
+ return null;
9195
9073
  }
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;
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");
9081
+ }
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");
9200
9100
  }
9201
9101
  }
9202
- return replay.header.initialCwd;
9102
+ if (expected.length > 0) {
9103
+ throw new ContextBuildError(ownerEntryId ?? "unknown", "missing tool result");
9104
+ }
9203
9105
  }
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;
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;
9208
9124
  }
9209
9125
  }
9210
- return "";
9126
+ if (retained.length === 0 && units.length > 0) {
9127
+ retained.push(units.at(-1));
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
+ };
9211
9139
  }
9212
- function copyEntries(journal, entries) {
9140
+ function serializeConversation(entries) {
9141
+ const lines2 = ["<conversation>"];
9213
9142
  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
9143
+ lines2.push(`<entry id="${entry.id}" seq="${entry.seq}" type="${entry.entryType}">`);
9144
+ if (entry.entryType === "assistant_message") {
9145
+ for (const block of entry.payload.message.content) {
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}`);
9150
+ }
9151
+ }
9152
+ } else if (entry.entryType === "tool_result") {
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}`);
9186
+ }
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);
9194
+ }
9195
+ units.push({ entries: grouped, messages: grouped.map(messageForEntry) });
9196
+ continue;
9223
9197
  }
9224
- };
9225
- journal.appendEntry(input);
9198
+ }
9199
+ if (entry.entryType === "tool_result") {
9200
+ continue;
9201
+ }
9202
+ units.push({ entries: [entry], messages: [messageForEntry(entry)] });
9226
9203
  }
9204
+ return units;
9227
9205
  }
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
- });
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;
9237
9217
  }
9218
+ throw new Error(`entry has no model message: ${entry.entryType}`);
9238
9219
  }
9239
9220
 
9240
- // ../../packages/session/session-store/dist/transcript-projector.js
9241
- function projectTranscript(entries) {
9242
- const items = [];
9243
- const skillNames = /* @__PURE__ */ new Map();
9244
- 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") {
9248
- 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 {
9257
- }
9258
- }
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
- } 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;
9277
- }
9278
- } catch {
9279
- }
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 };
9256
+ }
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;
9280
9307
  }
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
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);
9296
9312
  }
9297
- return items;
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
@@ -23486,7 +23522,8 @@ var CONTROL_KEYS = /* @__PURE__ */ new Map([
23486
23522
  [12, makeKeyInput("ctrl_l", { ctrl: true })],
23487
23523
  [15, makeKeyInput("character", { text: "o", ctrl: true })],
23488
23524
  [19, makeKeyInput("character", { text: "s", ctrl: true })],
23489
- [20, makeKeyInput("character", { text: "t", ctrl: true })]
23525
+ [20, makeKeyInput("character", { text: "t", ctrl: true })],
23526
+ [22, makeKeyInput("character", { text: "v", ctrl: true })]
23490
23527
  ]);
23491
23528
  var ESCAPE_INCOMPLETE = { status: "incomplete" };
23492
23529
  var ESCAPE_CONSUMED = { status: "consumed" };
@@ -23633,6 +23670,10 @@ var RawInputDecoder = class {
23633
23670
  this.buffer = this.buffer.subarray(3);
23634
23671
  return kind !== void 0 ? { status: "action", action: inputAction(kind) } : ESCAPE_CONSUMED;
23635
23672
  }
23673
+ if (second === 118) {
23674
+ this.buffer = this.buffer.subarray(2);
23675
+ return { status: "action", action: inputAction(InputActionKind.Key, "", makeKeyInput("character", { text: "v", alt: true })) };
23676
+ }
23636
23677
  this.buffer = this.buffer.subarray(1);
23637
23678
  return ESCAPE_CONSUMED;
23638
23679
  }
@@ -23920,7 +23961,7 @@ function decodeSpecialEscapeAction(sequence) {
23920
23961
  return kind !== void 0 ? inputAction(kind) : null;
23921
23962
  }
23922
23963
  function decodeModifiedControlKey(sequence) {
23923
- const kitty = /^\x1b\[(13|57414|9|117)(?::\d*)?(?::\d+)?;(\d+)(?::(\d+))?u$/.exec(sequence);
23964
+ const kitty = /^\x1b\[(13|57414|9|117|118)(?::\d*)?(?::\d+)?;(\d+)(?::(\d+))?u$/.exec(sequence);
23924
23965
  if (kitty !== null) {
23925
23966
  const code = kitty[1];
23926
23967
  const modifier2 = Number.parseInt(kitty[2], 10) - 1;
@@ -23930,6 +23971,9 @@ function decodeModifiedControlKey(sequence) {
23930
23971
  if (!isKittyPressEvent(kitty[3])) {
23931
23972
  return null;
23932
23973
  }
23974
+ if (code === "118" && (matchesKittyModifiers(modifier2, 4) || matchesKittyModifiers(modifier2, 2))) {
23975
+ return inputAction(InputActionKind.Key, "", makeKeyInput("character", { text: "v", ctrl: matchesKittyModifiers(modifier2, 4), alt: matchesKittyModifiers(modifier2, 2) }));
23976
+ }
23933
23977
  if ((code === "13" || code === "57414") && matchesKittyModifiers(modifier2, 2)) {
23934
23978
  return inputAction(InputActionKind.Key, "", makeKeyInput("enter", { alt: true }));
23935
23979
  }
@@ -23944,6 +23988,9 @@ function decodeModifiedControlKey(sequence) {
23944
23988
  }
23945
23989
  const modifier = Number.parseInt(modifyOtherKeys[1], 10) - 1;
23946
23990
  const codepoint = Number.parseInt(modifyOtherKeys[2], 10);
23991
+ if (codepoint === 118 && (modifier === 4 || modifier === 2)) {
23992
+ return inputAction(InputActionKind.Key, "", makeKeyInput("character", { text: "v", ctrl: modifier === 4, alt: modifier === 2 }));
23993
+ }
23947
23994
  if (codepoint === 117 && modifier === 4) {
23948
23995
  return inputAction(InputActionKind.DeleteToLineStart);
23949
23996
  }
@@ -24294,6 +24341,7 @@ var TerminalInputDecoder = class {
24294
24341
 
24295
24342
  // ../../packages/terminal/tui/dist/keybindings/default-keybindings.js
24296
24343
  var DEFAULT_KEYBINDINGS = [
24344
+ { context: "editor", key: process.platform === "win32" ? "alt+v" : "ctrl+v", action: "paste_clipboard" },
24297
24345
  { context: "terminal", key: "ctrl+o", action: "toggle_tool_output" },
24298
24346
  { context: "terminal", key: "ctrl+l", action: "select_model" },
24299
24347
  { context: "terminal", key: "ctrl+t", action: "toggle_thinking" },
@@ -27462,6 +27510,7 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
27462
27510
  #renderTimer = null;
27463
27511
  #lastRenderAt = 0;
27464
27512
  #disposeResize = null;
27513
+ #clipboard = null;
27465
27514
  writeError = null;
27466
27515
  constructor(ui, driver, editor, decoderFactory) {
27467
27516
  this.#ui = ui;
@@ -27564,6 +27613,7 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
27564
27613
  return this.#viewHost.render(context);
27565
27614
  }
27566
27615
  requestExit() {
27616
+ this.invalidateClipboardPaste();
27567
27617
  this.#exitRequested = true;
27568
27618
  this.#exitResolve?.();
27569
27619
  }
@@ -27572,6 +27622,7 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
27572
27622
  return;
27573
27623
  }
27574
27624
  this.#closed = true;
27625
+ this.invalidateClipboardPaste();
27575
27626
  if (this.#renderTimer !== null) {
27576
27627
  clearTimeout(this.#renderTimer);
27577
27628
  this.#renderTimer = null;
@@ -27778,14 +27829,29 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
27778
27829
  }
27779
27830
  changed = true;
27780
27831
  } else if (item.type === "open_selection") {
27832
+ this.invalidateClipboardPaste();
27781
27833
  this.#viewHost.openSelection(item.request).then(item.resolve);
27782
27834
  changed = true;
27783
27835
  } else if (item.type === "open_prompt") {
27836
+ this.invalidateClipboardPaste();
27784
27837
  this.#viewHost.openPrompt(item.request).then(item.resolve);
27785
27838
  changed = true;
27786
27839
  } else if (item.type === "close_view") {
27787
27840
  this.#viewHost.closeActive(item.value);
27788
27841
  changed = true;
27842
+ } else if (item.type === "clipboard") {
27843
+ if (this.#clipboard === item.controller)
27844
+ this.#clipboard = null;
27845
+ if (item.controller.signal.aborted) {
27846
+ this.#appendNotice("Clipboard paste cancelled because input changed; paste again.");
27847
+ } else if (item.error) {
27848
+ this.#appendNotice(item.error);
27849
+ } else if (item.text) {
27850
+ this.#applyEditorAction(inputAction(InputActionKind.Insert, item.text));
27851
+ } else {
27852
+ this.#appendNotice("Clipboard has no image or text.");
27853
+ }
27854
+ changed = true;
27789
27855
  } else if (item.event instanceof LocalMessage) {
27790
27856
  const message = item.event;
27791
27857
  this.#ui.appendTranscript(createNoticeBlock(this.#ui.newBlockId(), message.text, message.tone));
@@ -27820,6 +27886,7 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
27820
27886
  #applyActions(actions) {
27821
27887
  for (const action of actions) {
27822
27888
  if (action.kind === InputActionKind.Newline) {
27889
+ this.invalidateClipboardPaste();
27823
27890
  if (this.#viewHost.activeId() !== null) {
27824
27891
  this.#viewHost.handleInput({ type: "text", text: "\n" });
27825
27892
  this.#needsRender = true;
@@ -27832,6 +27899,12 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
27832
27899
  }
27833
27900
  }
27834
27901
  #applyInputEvent(event) {
27902
+ const clipboardAction = event.type === "key" && this.#ui.keybindings.resolve(event.key, ["editor"]) === "paste_clipboard";
27903
+ if (clipboardAction && this.#viewHost.activeId() === null) {
27904
+ this.#pasteClipboard();
27905
+ return;
27906
+ }
27907
+ this.invalidateClipboardPaste();
27835
27908
  if (this.#viewHost.handleInput(event)) {
27836
27909
  this.#needsRender = true;
27837
27910
  return;
@@ -27851,6 +27924,8 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
27851
27924
  }
27852
27925
  }
27853
27926
  #applyKeyAction(action) {
27927
+ if (action === "paste_clipboard")
27928
+ return;
27854
27929
  const capability = ACTION_CAPABILITIES[action];
27855
27930
  if (capability !== void 0 && !this.#ui.capabilities[capability]) {
27856
27931
  this.#appendNotice(unavailableActionNotice(action));
@@ -27960,6 +28035,7 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
27960
28035
  return true;
27961
28036
  }
27962
28037
  #applyEof() {
28038
+ this.invalidateClipboardPaste();
27963
28039
  const effect = this.#editor.apply(inputAction(InputActionKind.Eof), { runtimeActive: this.#ui.isRunning() });
27964
28040
  this.#applyEffect(effect);
27965
28041
  this.#needsRender = true;
@@ -27982,6 +28058,27 @@ var InteractiveTerminalLoop = class _InteractiveTerminalLoop {
27982
28058
  #appendNotice(text3) {
27983
28059
  this.#ui.appendTranscript(createNoticeBlock(this.#ui.newBlockId(), text3, "warning"));
27984
28060
  }
28061
+ invalidateClipboardPaste() {
28062
+ this.#clipboard?.abort();
28063
+ }
28064
+ #pasteClipboard() {
28065
+ if (this.#clipboard)
28066
+ return;
28067
+ const read = this.#ui.clipboardReader;
28068
+ if (!read) {
28069
+ this.#appendNotice("Clipboard paste is unavailable in this host.");
28070
+ return;
28071
+ }
28072
+ const controller = new AbortController();
28073
+ this.#clipboard = controller;
28074
+ const deliver = (text3, error) => {
28075
+ if (this.#closed || this.#exitRequested)
28076
+ return;
28077
+ this.#work.push({ type: "clipboard", controller, text: text3, error });
28078
+ this.#scheduleWakeup();
28079
+ };
28080
+ void Promise.resolve().then(() => read(controller.signal)).then((text3) => deliver(text3), (error) => deliver(null, error instanceof Error ? error.message : "Clipboard paste failed."));
28081
+ }
27985
28082
  #refreshCompletions() {
27986
28083
  const registry = this.#ui.commandRegistry;
27987
28084
  if (registry !== null) {
@@ -28040,6 +28137,7 @@ function normalizePositiveInteger(value) {
28040
28137
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
28041
28138
  }
28042
28139
  var TerminalUI = class {
28140
+ clipboardReader;
28043
28141
  theme;
28044
28142
  state;
28045
28143
  reducer;
@@ -28070,6 +28168,7 @@ var TerminalUI = class {
28070
28168
  #pendingDisplayDrops = 0;
28071
28169
  #keyActionCallback;
28072
28170
  constructor(options = {}) {
28171
+ this.clipboardReader = options.clipboardReader;
28073
28172
  this.theme = resolveTerminalTheme(options.theme);
28074
28173
  this.projectRoot = options.projectRoot ?? null;
28075
28174
  this.provider = options.provider ?? null;
@@ -28188,6 +28287,8 @@ var TerminalUI = class {
28188
28287
  Object.assign(this.capabilities, capabilities);
28189
28288
  }
28190
28289
  setSessionId(sessionId) {
28290
+ if (sessionId !== this.#sessionId)
28291
+ this.#loop?.invalidateClipboardPaste();
28191
28292
  if (sessionId !== this.#sessionId)
28192
28293
  this.state.contextTokens = 0;
28193
28294
  this.#sessionId = sessionId;
@@ -28207,6 +28308,7 @@ var TerminalUI = class {
28207
28308
  this.#loop?.requestRender();
28208
28309
  }
28209
28310
  setComposerText(text3) {
28311
+ this.#loop?.invalidateClipboardPaste();
28210
28312
  const editor = this.#loop?.editor;
28211
28313
  if (editor === void 0) {
28212
28314
  return;
@@ -39394,6 +39496,160 @@ async function copyText(text3, options) {
39394
39496
  return terminalCopy(text3, options, failures);
39395
39497
  }
39396
39498
 
39499
+ // src/clipboard-paste.ts
39500
+ import { spawn as spawn4 } from "node:child_process";
39501
+ import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
39502
+ import { tmpdir as tmpdir2 } from "node:os";
39503
+ import { join as join20 } from "node:path";
39504
+ import { randomUUID as randomUUID18 } from "node:crypto";
39505
+ import sharp3 from "sharp";
39506
+ var MAX_OUTPUT = 30 * 1024 * 1024;
39507
+ var MAX_TEXT = 1024 * 1024;
39508
+ var MAC_CLIPBOARD_SCRIPT = `
39509
+ ObjC.import('AppKit');
39510
+ function run() {
39511
+ var pb = $.NSPasteboard.generalPasteboard;
39512
+ var data = pb.dataForType($.NSPasteboardTypePNG);
39513
+ if (!data.js) {
39514
+ var tiff = pb.dataForType($.NSPasteboardTypeTIFF);
39515
+ if (tiff.js) {
39516
+ var bitmap = $.NSBitmapImageRep.imageRepWithData(tiff);
39517
+ if (!bitmap.js) throw new Error('Cannot decode clipboard image');
39518
+ if (bitmap.pixelsWide * bitmap.pixelsHigh > 64000000) throw new Error('Clipboard image exceeds pixel limit');
39519
+ data = bitmap.representationUsingTypeProperties($.NSPNGFileType, $({}));
39520
+ if (!data.js) throw new Error('Cannot encode clipboard image');
39521
+ }
39522
+ }
39523
+ if (data.js) {
39524
+ if (data.length > 20971520) throw new Error('Clipboard image exceeds 20 MiB');
39525
+ return JSON.stringify({kind:'image', base64:ObjC.unwrap(data.base64EncodedStringWithOptions(0))});
39526
+ }
39527
+ var text = pb.stringForType($.NSPasteboardTypeString);
39528
+ return JSON.stringify(text.js ? {kind:'text', text:ObjC.unwrap(text)} : {kind:'empty'});
39529
+ }`;
39530
+ var WINDOWS_CLIPBOARD_SCRIPT = `
39531
+ $ErrorActionPreference = 'Stop'
39532
+ [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
39533
+ Add-Type -AssemblyName System.Windows.Forms
39534
+ Add-Type -AssemblyName System.Drawing
39535
+ $image = [System.Windows.Forms.Clipboard]::GetImage()
39536
+ if ($null -ne $image) {
39537
+ $stream = [System.IO.MemoryStream]::new()
39538
+ try {
39539
+ if ([long]$image.Width * $image.Height -gt 64000000) { throw 'Clipboard image exceeds pixel limit' }
39540
+ $image.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)
39541
+ if ($stream.Length -gt 20971520) { throw 'Clipboard image exceeds 20 MiB' }
39542
+ @{kind='image'; base64=[Convert]::ToBase64String($stream.ToArray())} | ConvertTo-Json -Compress
39543
+ } finally { $stream.Dispose(); $image.Dispose() }
39544
+ } elseif ([System.Windows.Forms.Clipboard]::ContainsText()) {
39545
+ @{kind='text'; text=[System.Windows.Forms.Clipboard]::GetText()} | ConvertTo-Json -Compress
39546
+ } else { @{kind='empty'} | ConvertTo-Json -Compress }
39547
+ `;
39548
+ function clipboardReadCommand(platform) {
39549
+ if (platform === "darwin") return { command: "/usr/bin/osascript", args: ["-l", "JavaScript", "-e", MAC_CLIPBOARD_SCRIPT] };
39550
+ if (platform === "win32") return { command: "powershell.exe", args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-STA", "-EncodedCommand", Buffer.from(WINDOWS_CLIPBOARD_SCRIPT, "utf16le").toString("base64")] };
39551
+ throw new Error("Image clipboard paste requires native macOS or Windows; use a local image path on this platform.");
39552
+ }
39553
+ function createClipboardReadRunner(env, timeoutMs = 5e3) {
39554
+ return (command, args, signal) => new Promise((resolve15, reject) => {
39555
+ signal.throwIfAborted();
39556
+ const child = spawn4(command, [...args], { env: { ...env }, shell: false, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
39557
+ const chunks = [];
39558
+ let bytes = 0;
39559
+ let settled = false;
39560
+ const finish = (error) => {
39561
+ if (settled) return;
39562
+ settled = true;
39563
+ clearTimeout(timer);
39564
+ signal.removeEventListener("abort", abort);
39565
+ if (error) {
39566
+ child.kill("SIGKILL");
39567
+ reject(error);
39568
+ } else resolve15(Buffer.concat(chunks).toString("utf8"));
39569
+ };
39570
+ const abort = () => finish(new Error("Clipboard paste cancelled."));
39571
+ const timer = setTimeout(() => finish(new Error("Clipboard read timed out.")), timeoutMs);
39572
+ signal.addEventListener("abort", abort, { once: true });
39573
+ child.stdout.on("data", (chunk) => {
39574
+ bytes += chunk.length;
39575
+ if (bytes > MAX_OUTPUT) finish(new Error("Clipboard content exceeds the size limit."));
39576
+ else if (!settled) chunks.push(chunk);
39577
+ });
39578
+ child.stderr.resume();
39579
+ child.once("error", () => finish(new Error("Cannot start the system clipboard reader.")));
39580
+ child.once("close", (code) => finish(code === 0 ? void 0 : new Error("System clipboard read failed; check clipboard access and try again.")));
39581
+ if (signal.aborted) abort();
39582
+ });
39583
+ }
39584
+ function createClipboardPaste(options) {
39585
+ const run = options.run ?? createClipboardReadRunner(options.env);
39586
+ let directory;
39587
+ let closed = false;
39588
+ let pending;
39589
+ const read = async (signal) => {
39590
+ signal.throwIfAborted();
39591
+ if (closed) throw new Error("Clipboard reader is closed.");
39592
+ if (options.env.SSH_CONNECTION || options.env.SSH_TTY) throw new Error("Remote clipboard reading is unavailable over SSH; use a remote image path.");
39593
+ const command = clipboardReadCommand(options.platform);
39594
+ const raw = await run(command.command, command.args, signal);
39595
+ signal.throwIfAborted();
39596
+ let value;
39597
+ try {
39598
+ value = JSON.parse(raw.replace(/^\uFEFF/, ""));
39599
+ } catch {
39600
+ throw new Error("Invalid response from clipboard reader.");
39601
+ }
39602
+ if (!value || typeof value !== "object" || !("kind" in value)) throw new Error("Invalid clipboard content.");
39603
+ if (value.kind === "empty") return null;
39604
+ if (value.kind === "text" && "text" in value && typeof value.text === "string") {
39605
+ if (Buffer.byteLength(value.text) > MAX_TEXT) throw new Error("Clipboard text exceeds 1 MiB.");
39606
+ return value.text.replace(/\r\n?/g, "\n").replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, "");
39607
+ }
39608
+ if (value.kind !== "image" || !("base64" in value) || typeof value.base64 !== "string" || value.base64.length > Math.ceil(DEFAULT_IMAGE_LIMITS.maxBytes / 3) * 4 || value.base64.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value.base64)) {
39609
+ throw new Error("Invalid or oversized clipboard image.");
39610
+ }
39611
+ const data = Buffer.from(value.base64, "base64");
39612
+ if (data.toString("base64") !== value.base64) throw new Error("Invalid clipboard image encoding.");
39613
+ if (!data.length || data.length > DEFAULT_IMAGE_LIMITS.maxBytes) throw new Error("Clipboard image exceeds size limits.");
39614
+ const image = sharp3(data, { limitInputPixels: DEFAULT_IMAGE_LIMITS.maxPixels });
39615
+ try {
39616
+ const meta = await image.metadata();
39617
+ if (meta.format !== "png" || !meta.width || !meta.height || Math.max(meta.width, meta.height) > DEFAULT_IMAGE_LIMITS.maxDimension) {
39618
+ throw new Error("Invalid image");
39619
+ }
39620
+ await image.stats();
39621
+ } catch {
39622
+ throw new Error("Clipboard image is invalid or exceeds image limits.");
39623
+ }
39624
+ signal.throwIfAborted();
39625
+ directory ??= await mkdtemp2(join20(options.tempRoot ?? tmpdir2(), "laohuang-clipboard-"));
39626
+ const path5 = join20(directory, `${randomUUID18()}.png`);
39627
+ try {
39628
+ await writeFile5(path5, data, { flag: "wx", mode: 384 });
39629
+ signal.throwIfAborted();
39630
+ return ` ${JSON.stringify(path5)} `;
39631
+ } catch (error) {
39632
+ await rm2(path5, { force: true });
39633
+ throw error;
39634
+ }
39635
+ };
39636
+ return {
39637
+ read(signal) {
39638
+ if (pending) return Promise.reject(new Error("Clipboard paste already in progress."));
39639
+ pending = read(signal).finally(() => {
39640
+ pending = void 0;
39641
+ });
39642
+ return pending;
39643
+ },
39644
+ async close() {
39645
+ closed = true;
39646
+ await pending?.catch(() => {
39647
+ });
39648
+ if (directory) await rm2(directory, { recursive: true, force: true });
39649
+ }
39650
+ };
39651
+ }
39652
+
39397
39653
  // src/main.ts
39398
39654
  var VERSION = readPackageVersion();
39399
39655
  function readPackageVersion() {
@@ -39499,13 +39755,13 @@ async function main(argv, options = {}) {
39499
39755
  const configPath = options.configPath ?? defaultConfigPath(environ);
39500
39756
  const manager = new ConfigManager(configPath);
39501
39757
  const credentials = new CredentialStore(
39502
- options.credentialsPath ?? join20(dirname13(configPath), "credentials.json")
39758
+ options.credentialsPath ?? join21(dirname13(configPath), "credentials.json")
39503
39759
  );
39504
39760
  const modelCatalogStore = new ModelCatalogStore(
39505
- options.modelsPath ?? join20(dirname13(configPath), "models.json")
39761
+ options.modelsPath ?? join21(dirname13(configPath), "models.json")
39506
39762
  );
39507
39763
  const customModels = new CustomModelsStore(
39508
- options.customModelsPath ?? join20(dirname13(configPath), "custom-models.json")
39764
+ options.customModelsPath ?? join21(dirname13(configPath), "custom-models.json")
39509
39765
  );
39510
39766
  let modelPlatform;
39511
39767
  let attachmentRuntime;
@@ -39683,10 +39939,11 @@ async function main(argv, options = {}) {
39683
39939
  return 2;
39684
39940
  }
39685
39941
  let terminalUi = null;
39942
+ const clipboardPaste = createClipboardPaste({ platform: process.platform, env: environ });
39686
39943
  let terminalDriver = null;
39687
39944
  let selectedModel = modelPlatform.catalog.getModel(config.provider, config.model);
39688
39945
  attachmentRuntime = createAttachmentRuntime({
39689
- root: join20(getHomeDirectory({ env: environ }), ".laohuang", "attachments"),
39946
+ root: join21(getHomeDirectory({ env: environ }), ".laohuang", "attachments"),
39690
39947
  sessionsRoot: defaultSessionsRoot(environ),
39691
39948
  onError: (error) => writeStderr(`Attachment cleanup failed: ${errorMessage11(error)}`)
39692
39949
  });
@@ -39728,6 +39985,7 @@ async function main(argv, options = {}) {
39728
39985
  version: VERSION,
39729
39986
  theme: args.theme,
39730
39987
  driver: terminalDriver,
39988
+ clipboardReader: (signal) => clipboardPaste.read(signal),
39731
39989
  capabilities: { reasoning: selectedModel?.reasoning ?? false },
39732
39990
  contextWindow: selectedModel?.contextWindow
39733
39991
  });
@@ -39999,7 +40257,11 @@ Submit an empty line to retry, or enter a replacement.`);
39999
40257
  if (composedForCleanup) await composedForCleanup.close();
40000
40258
  else await sessionController.close();
40001
40259
  } finally {
40002
- attachmentRuntime.close();
40260
+ try {
40261
+ await clipboardPaste.close();
40262
+ } finally {
40263
+ attachmentRuntime.close();
40264
+ }
40003
40265
  }
40004
40266
  }
40005
40267
  }
@@ -40007,7 +40269,7 @@ Submit an empty line to retry, or enter a replacement.`);
40007
40269
  }
40008
40270
  }
40009
40271
  function defaultSessionsRoot(environ) {
40010
- return join20(getHomeDirectory({ env: environ }), ".laohuang", "sessions");
40272
+ return join21(getHomeDirectory({ env: environ }), ".laohuang", "sessions");
40011
40273
  }
40012
40274
  async function runInitialModelSelection(options) {
40013
40275
  let providerName = options.providerName;