@chanlerdev/scorel 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1746,6 +1746,7 @@ import { mkdir as mkdir2, readFile as readFile4, rename as rename2, rm, stat as
1746
1746
  import { userInfo } from "node:os";
1747
1747
  import { basename as basename2, dirname as dirname3, extname, isAbsolute, relative, resolve } from "node:path";
1748
1748
  import { promisify } from "node:util";
1749
+ import { Type } from "@mariozechner/pi-ai";
1749
1750
  var execFileAsync, DEFAULT_SEARCH_LIMIT, DEFAULT_GREP_LIMIT, DEFAULT_READ_LIMIT, DEFAULT_CONTEXT_WINDOW, READ_TOKEN_BUDGET_RATIO, FULL_READ_TOKEN_BUDGET_RATIO, createCodingTools, parseReadArgs, parseWriteArgs, parseEditArgs, parseBashArgs, parseGlobArgs, parseGrepArgs, parseTodoWriteArgs, parseTodoItem, expectRecord, expectPath, expectString, optionalString, optionalNumber, optionalBoolean, snapshotFile, sameSnapshot, exists, isWithin, linesOf, IMAGE_EXTENSIONS, DOCUMENT_EXTENSIONS, BINARY_EXTENSIONS, assertReadableFileKind, assertTextBuffer, selectCompleteLinesWithinBudget, estimateTokens, renderReadLines, readTokenBudget, completeRanges, hasCompleteCoverage, mergeRanges, countOccurrences, atomicWriteFile, bashResult, renderFullBashResult, writeBashArtifact, safeArtifactSegment, projectBashStreams, projectOutputStream, resolveDefaultShell, resolveRtkCommand, rtkRewriteResult, executableRewriteCommand, readRtkGain, rtkSavedTokenDelta, withRtkSavings, nonNegativeInteger, isRecord3, shellQuote, shellCommandArgs, userShell, truncate, sliceBytes, textResult, byteLength, isTimeoutError, isExecError, runRipgrep, splitOutput, vcsExcludes, grepArgs, splitGlobPatterns, paginate, toWorkspaceRelative, relativizeGrepLine, relativizeCountLine, sortPathsByMtime, formatPaginatedText, formatLimitSuffix, parseCountLines;
1750
1751
  var init_coding_tools = __esm({
1751
1752
  "packages/core/src/tools/coding-tools.ts"() {
@@ -1799,6 +1800,12 @@ var init_coding_tools = __esm({
1799
1800
  defineTool({
1800
1801
  name: "Read",
1801
1802
  description: "Read a text file from the workspace. Long reads are truncated by complete lines; accumulated coverage unlocks Write/Edit.",
1803
+ parameters: Type.Object({
1804
+ file_path: Type.String(),
1805
+ offset: Type.Optional(Type.Number()),
1806
+ limit: Type.Optional(Type.Number()),
1807
+ full: Type.Optional(Type.Boolean())
1808
+ }),
1802
1809
  execute: async (_toolCallId, args) => {
1803
1810
  const input = parseReadArgs(args);
1804
1811
  if (input.full && (input.offset !== void 0 || input.limit !== void 0)) {
@@ -1858,6 +1865,10 @@ var init_coding_tools = __esm({
1858
1865
  defineTool({
1859
1866
  name: "Write",
1860
1867
  description: "Create a new file or fully overwrite an existing file. Existing files require complete read coverage of the current file.",
1868
+ parameters: Type.Object({
1869
+ file_path: Type.String(),
1870
+ content: Type.String()
1871
+ }),
1861
1872
  execute: async (_toolCallId, args) => {
1862
1873
  const input = parseWriteArgs(args);
1863
1874
  const path = resolveWorkspacePath(input.path);
@@ -1879,6 +1890,12 @@ var init_coding_tools = __esm({
1879
1890
  defineTool({
1880
1891
  name: "Edit",
1881
1892
  description: "Perform an exact string replacement in an existing file. Requires complete read coverage and a unique old_string unless replace_all is true.",
1893
+ parameters: Type.Object({
1894
+ file_path: Type.String(),
1895
+ old_string: Type.String(),
1896
+ new_string: Type.String(),
1897
+ replace_all: Type.Optional(Type.Boolean())
1898
+ }),
1882
1899
  execute: async (_toolCallId, args) => {
1883
1900
  const input = parseEditArgs(args);
1884
1901
  const path = resolveWorkspacePath(input.path);
@@ -1914,6 +1931,13 @@ String: ${input.old_string}`
1914
1931
  defineTool({
1915
1932
  name: "Bash",
1916
1933
  description: "Execute a shell command in the workspace with timeout and output truncation.",
1934
+ parameters: Type.Object({
1935
+ command: Type.String(),
1936
+ cwd: Type.Optional(Type.String()),
1937
+ timeout: Type.Optional(Type.Number()),
1938
+ description: Type.Optional(Type.String()),
1939
+ maxOutputBytes: Type.Optional(Type.Number())
1940
+ }),
1917
1941
  execute: async (toolCallId, args, signal) => {
1918
1942
  const input = parseBashArgs(args);
1919
1943
  const commandCwd = input.cwd ? resolveWorkspacePath(input.cwd) : root;
@@ -1978,6 +2002,12 @@ String: ${input.old_string}`
1978
2002
  defineTool({
1979
2003
  name: "Glob",
1980
2004
  description: "Find files by glob pattern using ripgrep file discovery.",
2005
+ parameters: Type.Object({
2006
+ pattern: Type.String(),
2007
+ path: Type.Optional(Type.String()),
2008
+ head_limit: Type.Optional(Type.Number()),
2009
+ offset: Type.Optional(Type.Number())
2010
+ }),
1981
2011
  execute: async (_toolCallId, args, signal) => {
1982
2012
  const input = parseGlobArgs(args);
1983
2013
  const limit = input.head_limit ?? DEFAULT_SEARCH_LIMIT;
@@ -1998,6 +2028,22 @@ String: ${input.old_string}`
1998
2028
  defineTool({
1999
2029
  name: "Grep",
2000
2030
  description: 'Search file contents with ripgrep. Default output_mode is "files" for matching paths; use "content" for matching lines or "count" for match counts.',
2031
+ parameters: Type.Object({
2032
+ pattern: Type.String(),
2033
+ path: Type.Optional(Type.String()),
2034
+ glob: Type.Optional(Type.String()),
2035
+ output_mode: Type.Optional(Type.Union([Type.Literal("files"), Type.Literal("content"), Type.Literal("count")])),
2036
+ "-B": Type.Optional(Type.Number()),
2037
+ "-A": Type.Optional(Type.Number()),
2038
+ "-C": Type.Optional(Type.Number()),
2039
+ context: Type.Optional(Type.Number()),
2040
+ "-n": Type.Optional(Type.Boolean()),
2041
+ "-i": Type.Optional(Type.Boolean()),
2042
+ type: Type.Optional(Type.String()),
2043
+ head_limit: Type.Optional(Type.Number()),
2044
+ offset: Type.Optional(Type.Number()),
2045
+ multiline: Type.Optional(Type.Boolean())
2046
+ }),
2001
2047
  execute: async (_toolCallId, args, signal) => {
2002
2048
  const input = parseGrepArgs(args);
2003
2049
  const mode = input.output_mode ?? "files";
@@ -2051,6 +2097,15 @@ ${filenames.join("\n")}`,
2051
2097
  defineTool({
2052
2098
  name: "TodoWrite",
2053
2099
  description: "Replace the current session todo list with a complete updated list.",
2100
+ parameters: Type.Object({
2101
+ todos: Type.Array(
2102
+ Type.Object({
2103
+ content: Type.String(),
2104
+ status: Type.Union([Type.Literal("pending"), Type.Literal("in_progress"), Type.Literal("completed")]),
2105
+ activeForm: Type.Optional(Type.String())
2106
+ })
2107
+ )
2108
+ }),
2054
2109
  execute: async (_toolCallId, args) => {
2055
2110
  const input = parseTodoWriteArgs(args);
2056
2111
  const oldTodos = state.todos;
@@ -2682,17 +2737,58 @@ ${value}` };
2682
2737
  });
2683
2738
 
2684
2739
  // packages/core/src/tools/index.ts
2685
- var defineTool;
2740
+ import { Type as Type2 } from "@mariozechner/pi-ai";
2741
+ var defineTool, createSnipTool, parseSnipToolInput, isRecord4;
2686
2742
  var init_tools = __esm({
2687
2743
  "packages/core/src/tools/index.ts"() {
2688
2744
  "use strict";
2689
2745
  init_coding_tools();
2690
2746
  defineTool = (tool) => tool;
2747
+ createSnipTool = (options) => defineTool({
2748
+ name: "snip",
2749
+ description: [
2750
+ "Hide a completed user turn from future model context.",
2751
+ "Use only when an earlier user turn is obsolete or noisy.",
2752
+ "The session JSONL evidence is preserved; the hidden turn disappears from the next context build.",
2753
+ "Input: { userMessageId: string, reason?: string }."
2754
+ ].join(" "),
2755
+ parameters: Type2.Object({
2756
+ userMessageId: Type2.String(),
2757
+ reason: Type2.Optional(Type2.String())
2758
+ }),
2759
+ execute: async (_toolCallId, args) => {
2760
+ const input = parseSnipToolInput(args);
2761
+ const result = await options.snip(input);
2762
+ return {
2763
+ content: [{
2764
+ type: "text",
2765
+ text: [
2766
+ `Snipped user turn ${result.anchorUserEventId}.`,
2767
+ `Hidden through ${result.throughEventId}.`,
2768
+ `${result.hiddenEventCount} event(s) will be omitted from future model context.`,
2769
+ "Original session JSONL remains unchanged."
2770
+ ].join(" ")
2771
+ }],
2772
+ details: result
2773
+ };
2774
+ }
2775
+ });
2776
+ parseSnipToolInput = (args) => {
2777
+ if (!isRecord4(args) || typeof args.userMessageId !== "string") {
2778
+ throw new Error("snip requires { userMessageId: string }");
2779
+ }
2780
+ return {
2781
+ userMessageId: args.userMessageId,
2782
+ ...typeof args.reason === "string" && args.reason.trim() ? { reason: args.reason.trim() } : {}
2783
+ };
2784
+ };
2785
+ isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2691
2786
  }
2692
2787
  });
2693
2788
 
2694
2789
  // packages/core/src/channel/index.ts
2695
- var createSendChannelMessageTool, parseSendChannelMessageInput, parseAttachments, optionalString2, isRecord4;
2790
+ import { Type as Type3 } from "@mariozechner/pi-ai";
2791
+ var createSendChannelMessageTool, parseSendChannelMessageInput, parseAttachments, optionalString2, isRecord5;
2696
2792
  var init_channel = __esm({
2697
2793
  "packages/core/src/channel/index.ts"() {
2698
2794
  "use strict";
@@ -2700,6 +2796,18 @@ var init_channel = __esm({
2700
2796
  createSendChannelMessageTool = (options) => defineTool({
2701
2797
  name: "SendChannelMessage",
2702
2798
  description: "Send a text reply to the current IM channel conversation. Do not provide raw platform user ids or group ids.",
2799
+ parameters: Type3.Object({
2800
+ text: Type3.Optional(Type3.String()),
2801
+ attachments: Type3.Optional(Type3.Array(Type3.Object({
2802
+ type: Type3.Union([Type3.Literal("image"), Type3.Literal("file")]),
2803
+ path: Type3.Optional(Type3.String()),
2804
+ url: Type3.Optional(Type3.String()),
2805
+ mimeType: Type3.Optional(Type3.String()),
2806
+ caption: Type3.Optional(Type3.String())
2807
+ }))),
2808
+ channel: Type3.Optional(Type3.String()),
2809
+ target: Type3.Optional(Type3.Literal("current"))
2810
+ }),
2703
2811
  execute: async (_toolCallId, args) => {
2704
2812
  const input = parseSendChannelMessageInput(args);
2705
2813
  const result = await options.sendCurrent(input);
@@ -2710,7 +2818,7 @@ var init_channel = __esm({
2710
2818
  }
2711
2819
  });
2712
2820
  parseSendChannelMessageInput = (value) => {
2713
- if (!isRecord4(value)) {
2821
+ if (!isRecord5(value)) {
2714
2822
  throw new Error("SendChannelMessage args must be an object");
2715
2823
  }
2716
2824
  const text = typeof value.text === "string" && value.text.trim().length > 0 ? value.text : void 0;
@@ -2739,7 +2847,7 @@ var init_channel = __esm({
2739
2847
  throw new Error("SendChannelMessage.attachments must be an array");
2740
2848
  }
2741
2849
  return value.map((item, index) => {
2742
- if (!isRecord4(item)) {
2850
+ if (!isRecord5(item)) {
2743
2851
  throw new Error(`SendChannelMessage.attachments.${index} must be an object`);
2744
2852
  }
2745
2853
  if (item.type !== "image" && item.type !== "file") {
@@ -2768,14 +2876,14 @@ var init_channel = __esm({
2768
2876
  }
2769
2877
  return value;
2770
2878
  };
2771
- isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2879
+ isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2772
2880
  }
2773
2881
  });
2774
2882
 
2775
2883
  // packages/core/src/extensions/index.ts
2776
2884
  import { readFile as readFile5 } from "node:fs/promises";
2777
2885
  import { dirname as dirname4, resolve as resolve2 } from "node:path";
2778
- var loadExtensionManifest, parseExtensionManifest, requireString2, requireIdentifier2, requireKind, requireRelativePath, optionalRelativePaths, isRecord5;
2886
+ var loadExtensionManifest, parseExtensionManifest, requireString2, requireIdentifier2, requireKind, requireRelativePath, optionalRelativePaths, isRecord6;
2779
2887
  var init_extensions = __esm({
2780
2888
  "packages/core/src/extensions/index.ts"() {
2781
2889
  "use strict";
@@ -2788,7 +2896,7 @@ var init_extensions = __esm({
2788
2896
  const message = cause instanceof Error ? cause.message : String(cause);
2789
2897
  throw new Error(`Invalid extension manifest JSON at ${manifestPath}: ${message}`);
2790
2898
  }
2791
- if (!isRecord5(value)) {
2899
+ if (!isRecord6(value)) {
2792
2900
  throw new Error(`Extension manifest at ${manifestPath} must be an object`);
2793
2901
  }
2794
2902
  const rootDir = dirname4(resolve2(manifestPath));
@@ -2844,7 +2952,7 @@ var init_extensions = __esm({
2844
2952
  }
2845
2953
  return value.map((item, index) => requireRelativePath(item, `${name}.${index}`, manifestPath));
2846
2954
  };
2847
- isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2955
+ isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2848
2956
  }
2849
2957
  });
2850
2958
 
@@ -2988,7 +3096,8 @@ var init_instructions = __esm({
2988
3096
  import { appendFile, mkdir as mkdir3, readFile as readFile7, writeFile as writeFile3 } from "node:fs/promises";
2989
3097
  import { homedir as homedir3 } from "node:os";
2990
3098
  import { join as join6 } from "node:path";
2991
- var memoryDate, scorelMemoryPaths, scorelSessionMemoryPaths, buildMemoryContext, renderMemoryHarness, appendDailyEntry, createAppendDailyTool, renderDailyEntry, readMemoryDreamState, writeMemoryDreamState, readSessionMemory, writeSessionMemory, renderSessionMemory, ensureMemoryFiles, ensureFile, readOptional, trimForContext, compactLine, renderList, renderBullets, normalizeMarkdownFile, parseAppendDailyInput, validateAppendDailyInput, isLowSignalSummary, containsNormalizedDailyEntry, normalizeDailyText, requireString3, optionalStringArray, optionalNumber2, optionalString3, parseLastFailure, isRecord6, safeProjectId, isNodeErrorCode3;
3099
+ import { Type as Type4 } from "@mariozechner/pi-ai";
3100
+ var memoryDate, scorelMemoryPaths, scorelSessionMemoryPaths, buildMemoryContext, renderMemoryHarness, appendDailyEntry, createAppendDailyTool, renderDailyEntry, readMemoryDreamState, writeMemoryDreamState, readSessionMemory, writeSessionMemory, renderSessionMemory, ensureMemoryFiles, ensureFile, readOptional, trimForContext, compactLine, renderList, renderBullets, normalizeMarkdownFile, parseAppendDailyInput, validateAppendDailyInput, isLowSignalSummary, containsNormalizedDailyEntry, normalizeDailyText, requireString3, optionalStringArray, optionalNumber2, optionalString3, parseLastFailure, isRecord7, safeProjectId, isNodeErrorCode3;
2992
3101
  var init_memory = __esm({
2993
3102
  "packages/core/src/memory/index.ts"() {
2994
3103
  "use strict";
@@ -3077,6 +3186,14 @@ var init_memory = __esm({
3077
3186
  "Use this once near the end of a completed user turn when there is progress, a decision, or a follow-up worth preserving.",
3078
3187
  "Do not include secrets, raw logs, speculation, or facts that should be re-read from the repository."
3079
3188
  ].join(" "),
3189
+ parameters: Type4.Object({
3190
+ summary: Type4.String(),
3191
+ completed: Type4.Optional(Type4.Array(Type4.String())),
3192
+ decisions: Type4.Optional(Type4.Array(Type4.String())),
3193
+ followUps: Type4.Optional(Type4.Array(Type4.String())),
3194
+ memoryCandidates: Type4.Optional(Type4.Array(Type4.String())),
3195
+ evidence: Type4.Optional(Type4.Array(Type4.String()))
3196
+ }),
3080
3197
  execute: async (_toolCallId, args) => {
3081
3198
  const input = parseAppendDailyInput(args);
3082
3199
  validateAppendDailyInput(input);
@@ -3220,7 +3337,7 @@ var init_memory = __esm({
3220
3337
  normalizeMarkdownFile = (value) => `${value.trimEnd()}
3221
3338
  `;
3222
3339
  parseAppendDailyInput = (value) => {
3223
- if (!isRecord6(value)) {
3340
+ if (!isRecord7(value)) {
3224
3341
  throw new Error("AppendDaily args must be an object");
3225
3342
  }
3226
3343
  const summary = requireString3(value.summary, "summary");
@@ -3286,12 +3403,12 @@ var init_memory = __esm({
3286
3403
  optionalNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
3287
3404
  optionalString3 = (value) => typeof value === "string" && value.trim() ? value : void 0;
3288
3405
  parseLastFailure = (value) => {
3289
- if (!isRecord6(value)) return void 0;
3406
+ if (!isRecord7(value)) return void 0;
3290
3407
  const at = optionalNumber2(value.at);
3291
3408
  const message = optionalString3(value.message);
3292
3409
  return at !== void 0 && message ? { at, message } : void 0;
3293
3410
  };
3294
- isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3411
+ isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3295
3412
  safeProjectId = (projectId) => {
3296
3413
  if (!/^[A-Za-z0-9_-]+$/.test(projectId)) {
3297
3414
  throw new Error("projectId must contain only letters, numbers, underscores, or hyphens");
@@ -3304,11 +3421,10 @@ var init_memory = __esm({
3304
3421
 
3305
3422
  // packages/core/src/provider/pi-ai.ts
3306
3423
  import {
3307
- Type,
3308
3424
  getModels,
3309
3425
  streamSimple
3310
3426
  } from "@mariozechner/pi-ai";
3311
- var DEFAULT_CUSTOM_MODEL_CONTEXT_WINDOW, DEFAULT_CUSTOM_MODEL_MAX_TOKENS, createPiAiProvider, resolvePiAiModel, toPiContext, toPiMessage, toPiAssistantBlock, fromPiAssistant, fromPiContentBlock, toPiTool, toolParameters, textContent, toolResultText, stringMeta, toPiStopReason, fromPiStopReason, fromPiUsage;
3427
+ var DEFAULT_CUSTOM_MODEL_CONTEXT_WINDOW, DEFAULT_CUSTOM_MODEL_MAX_TOKENS, createPiAiProvider, resolvePiAiModel, toPiContext, toPiMessage, toPiAssistantBlock, fromPiAssistant, fromPiContentBlock, toPiTool, textContent, toolResultText, stringMeta, toPiStopReason, fromPiStopReason, fromPiUsage;
3312
3428
  var init_pi_ai = __esm({
3313
3429
  "packages/core/src/provider/pi-ai.ts"() {
3314
3430
  "use strict";
@@ -3446,89 +3562,8 @@ var init_pi_ai = __esm({
3446
3562
  toPiTool = (tool) => ({
3447
3563
  name: tool.name,
3448
3564
  description: tool.description,
3449
- parameters: toolParameters(tool.name)
3565
+ parameters: tool.parameters
3450
3566
  });
3451
- toolParameters = (name) => {
3452
- switch (name) {
3453
- case "Read":
3454
- return Type.Object({
3455
- file_path: Type.String(),
3456
- offset: Type.Optional(Type.Number()),
3457
- limit: Type.Optional(Type.Number()),
3458
- full: Type.Optional(Type.Boolean())
3459
- });
3460
- case "Write":
3461
- return Type.Object({
3462
- file_path: Type.String(),
3463
- content: Type.String()
3464
- });
3465
- case "Edit":
3466
- return Type.Object({
3467
- file_path: Type.String(),
3468
- old_string: Type.String(),
3469
- new_string: Type.String(),
3470
- replace_all: Type.Optional(Type.Boolean())
3471
- });
3472
- case "Bash":
3473
- return Type.Object({
3474
- command: Type.String(),
3475
- cwd: Type.Optional(Type.String()),
3476
- timeout: Type.Optional(Type.Number()),
3477
- description: Type.Optional(Type.String()),
3478
- maxOutputBytes: Type.Optional(Type.Number())
3479
- });
3480
- case "Glob":
3481
- return Type.Object({
3482
- pattern: Type.String(),
3483
- path: Type.Optional(Type.String()),
3484
- head_limit: Type.Optional(Type.Number()),
3485
- offset: Type.Optional(Type.Number())
3486
- });
3487
- case "Grep":
3488
- return Type.Object({
3489
- pattern: Type.String(),
3490
- path: Type.Optional(Type.String()),
3491
- glob: Type.Optional(Type.String()),
3492
- output_mode: Type.Optional(Type.Union([Type.Literal("files"), Type.Literal("content"), Type.Literal("count")])),
3493
- "-B": Type.Optional(Type.Number()),
3494
- "-A": Type.Optional(Type.Number()),
3495
- "-C": Type.Optional(Type.Number()),
3496
- context: Type.Optional(Type.Number()),
3497
- "-n": Type.Optional(Type.Boolean()),
3498
- "-i": Type.Optional(Type.Boolean()),
3499
- type: Type.Optional(Type.String()),
3500
- head_limit: Type.Optional(Type.Number()),
3501
- offset: Type.Optional(Type.Number()),
3502
- multiline: Type.Optional(Type.Boolean())
3503
- });
3504
- case "TodoWrite":
3505
- return Type.Object({
3506
- todos: Type.Array(
3507
- Type.Object({
3508
- content: Type.String(),
3509
- status: Type.Union([Type.Literal("pending"), Type.Literal("in_progress"), Type.Literal("completed")]),
3510
- activeForm: Type.Optional(Type.String())
3511
- })
3512
- )
3513
- });
3514
- case "AppendDaily":
3515
- return Type.Object({
3516
- summary: Type.String(),
3517
- completed: Type.Optional(Type.Array(Type.String())),
3518
- decisions: Type.Optional(Type.Array(Type.String())),
3519
- followUps: Type.Optional(Type.Array(Type.String())),
3520
- memoryCandidates: Type.Optional(Type.Array(Type.String())),
3521
- evidence: Type.Optional(Type.Array(Type.String()))
3522
- });
3523
- case "Skill":
3524
- return Type.Object({
3525
- name: Type.String(),
3526
- args: Type.Optional(Type.String())
3527
- });
3528
- default:
3529
- return Type.Object({});
3530
- }
3531
- };
3532
3567
  textContent = (message) => message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
3533
3568
  toolResultText = (result) => {
3534
3569
  if (typeof result === "object" && result !== null && "content" in result) {
@@ -3776,22 +3811,23 @@ var init_runtime = __esm({
3776
3811
  });
3777
3812
 
3778
3813
  // packages/core/src/session/index.ts
3814
+ import { createHash as createHash2 } from "node:crypto";
3779
3815
  import { appendFile as appendFile2, mkdir as mkdir4, readFile as readFile8, writeFile as writeFile4 } from "node:fs/promises";
3780
3816
  import { dirname as dirname6, join as join7 } from "node:path";
3781
3817
  function assertTreeEvent(value) {
3782
- if (!isRecord7(value)) {
3818
+ if (!isRecord8(value)) {
3783
3819
  throw new SessionStoreError("invalid_event", "Event must be an object");
3784
3820
  }
3785
3821
  if (value.type === "session_header") {
3786
3822
  throw new SessionStoreError("invalid_event", "Session header must be stored as the JSONL header line");
3787
3823
  }
3788
- if (value.type !== "user_message" && value.type !== "assistant_message" && value.type !== "tool_result" && value.type !== "session_title_updated" && value.type !== "instruction_snapshot" && value.type !== "harness_item" && value.type !== "compact" && value.type !== "queue_update" && value.type !== "skill_index_snapshot" && value.type !== "skill_index_delta") {
3824
+ if (value.type !== "user_message" && value.type !== "assistant_message" && value.type !== "tool_result" && value.type !== "session_title_updated" && value.type !== "instruction_snapshot" && value.type !== "harness_item" && value.type !== "compact" && value.type !== "context_control" && value.type !== "queue_update" && value.type !== "skill_index_snapshot" && value.type !== "skill_index_delta") {
3789
3825
  throw new SessionStoreError("invalid_event", "Unsupported session event type");
3790
3826
  }
3791
3827
  if (typeof value.id !== "string" || value.parentId !== null && typeof value.parentId !== "string" || typeof value.seq !== "number" || typeof value.clientId !== "string" || typeof value.ts !== "number") {
3792
3828
  throw new SessionStoreError("invalid_event", "Event is missing required base fields");
3793
3829
  }
3794
- if ((value.type === "user_message" || value.type === "assistant_message" || value.type === "tool_result") && !isRecord7(value.message)) {
3830
+ if ((value.type === "user_message" || value.type === "assistant_message" || value.type === "tool_result") && !isRecord8(value.message)) {
3795
3831
  throw new SessionStoreError("invalid_event", "Message event is missing message payload");
3796
3832
  }
3797
3833
  if (value.type === "session_title_updated" && !isSessionTitleUpdated(value)) {
@@ -3806,6 +3842,9 @@ function assertTreeEvent(value) {
3806
3842
  if (value.type === "compact" && !isCompactEvent(value)) {
3807
3843
  throw new SessionStoreError("invalid_event", "compact is missing summary payload");
3808
3844
  }
3845
+ if (value.type === "context_control" && !isContextControlEvent(value)) {
3846
+ throw new SessionStoreError("invalid_event", "context_control is missing hide_user_turn payload");
3847
+ }
3809
3848
  if (value.type === "queue_update" && !isQueueUpdate(value)) {
3810
3849
  throw new SessionStoreError("invalid_event", "queue_update is missing queue payload");
3811
3850
  }
@@ -3816,11 +3855,12 @@ function assertTreeEvent(value) {
3816
3855
  throw new SessionStoreError("invalid_event", "skill_index_delta is missing delta payload");
3817
3856
  }
3818
3857
  }
3819
- var SessionStoreError, SessionTree, JsonlSession, sessionFilePath, sessionLogFilePath, sessionArtifactsDirPath, createSession, loadSession, buildContext, retainedMessagesBeforeCompact, isRetainedContextStart, parseJsonLine, parseHeader, parseSessionEvent, validateSessionMatch, isConversationEvent, isInstructionSnapshot, isHarnessItem, isCompactEvent, isQueueUpdate, isSessionTitleUpdated, isSkillIndexSnapshot, isSkillIndexDelta, isSkillIndexEntry, appendHarnessItemToContext, appendReminderToToolResult, isToolResultWithContent, renderSystemReminder, compactSummaryMessage, cloneMessage, isRecord7;
3858
+ var snipUserMessageAlias, SessionStoreError, SessionTree, JsonlSession, sessionFilePath, sessionLogFilePath, sessionArtifactsDirPath, createSession, loadSession, buildContext, hiddenContextEventIds, retainedMessagesBeforeCompact, isRetainedContextStart, parseJsonLine, parseHeader, parseSessionEvent, validateSessionMatch, isConversationEvent, isInstructionSnapshot, isHarnessItem, isCompactEvent, isContextControlEvent, isQueueUpdate, isSessionTitleUpdated, isSkillIndexSnapshot, isSkillIndexDelta, isSkillIndexEntry, appendHarnessItemToContext, appendReminderToToolResult, isToolResultWithContent, renderSystemReminder, compactSummaryMessage, cloneMessage, isRecord8;
3820
3859
  var init_session = __esm({
3821
3860
  "packages/core/src/session/index.ts"() {
3822
3861
  "use strict";
3823
3862
  init_src();
3863
+ snipUserMessageAlias = (eventId) => `u_${createHash2("sha256").update(eventId).digest("hex").slice(0, 8)}`;
3824
3864
  SessionStoreError = class extends Error {
3825
3865
  code;
3826
3866
  line;
@@ -3844,7 +3884,8 @@ var init_session = __esm({
3844
3884
  steer: []
3845
3885
  },
3846
3886
  skillIndexInitialized: false,
3847
- skillIndex: {}
3887
+ skillIndex: {},
3888
+ hiddenUserTurnSpans: []
3848
3889
  };
3849
3890
  get rootId() {
3850
3891
  return this.#rootId;
@@ -3964,6 +4005,16 @@ var init_session = __esm({
3964
4005
  delete next[removed.name];
3965
4006
  }
3966
4007
  this.controlState.skillIndex = next;
4008
+ } else if (event.type === "context_control") {
4009
+ this.controlState.hiddenUserTurnSpans = [
4010
+ ...this.controlState.hiddenUserTurnSpans.filter(
4011
+ (span) => span.anchorUserEventId !== event.anchorUserEventId
4012
+ ),
4013
+ {
4014
+ anchorUserEventId: event.anchorUserEventId,
4015
+ throughEventId: event.throughEventId
4016
+ }
4017
+ ];
3967
4018
  }
3968
4019
  }
3969
4020
  };
@@ -4025,7 +4076,11 @@ var init_session = __esm({
4025
4076
  };
4026
4077
  buildContext = (tree, leafId) => {
4027
4078
  const path = tree.getPath(leafId);
4079
+ const hiddenIds = hiddenContextEventIds(tree, path, leafId);
4028
4080
  return path.reduce((messages, id, index) => {
4081
+ if (hiddenIds.has(id)) {
4082
+ return messages;
4083
+ }
4029
4084
  const event = tree.get(id)?.event;
4030
4085
  if (!event) {
4031
4086
  return messages;
@@ -4038,7 +4093,7 @@ var init_session = __esm({
4038
4093
  appendHarnessItemToContext(messages, event);
4039
4094
  }
4040
4095
  if (event.type === "compact") {
4041
- const retained = retainedMessagesBeforeCompact(tree, path.slice(0, index), event.retainedEventCount);
4096
+ const retained = retainedMessagesBeforeCompact(tree, path.slice(0, index), event.retainedEventCount, hiddenIds);
4042
4097
  messages.length = 0;
4043
4098
  messages.push(compactSummaryMessage(event));
4044
4099
  messages.push(...retained);
@@ -4046,7 +4101,29 @@ var init_session = __esm({
4046
4101
  return messages;
4047
4102
  }, []);
4048
4103
  };
4049
- retainedMessagesBeforeCompact = (tree, pathBeforeCompact, retainedEventCount) => {
4104
+ hiddenContextEventIds = (tree, path, leafId) => {
4105
+ const leaf = tree.get(leafId)?.event;
4106
+ if (!leaf) {
4107
+ return /* @__PURE__ */ new Set();
4108
+ }
4109
+ const pathIndexes = new Map(path.map((id, index) => [id, index]));
4110
+ const hidden = /* @__PURE__ */ new Set();
4111
+ for (const event of tree) {
4112
+ if (event.type !== "context_control" || Number(event.seq) > Number(leaf.seq)) {
4113
+ continue;
4114
+ }
4115
+ const start = pathIndexes.get(event.anchorUserEventId);
4116
+ const end = pathIndexes.get(event.throughEventId);
4117
+ if (start === void 0 || end === void 0 || end < start) {
4118
+ continue;
4119
+ }
4120
+ for (const id of path.slice(start, end + 1)) {
4121
+ hidden.add(id);
4122
+ }
4123
+ }
4124
+ return hidden;
4125
+ };
4126
+ retainedMessagesBeforeCompact = (tree, pathBeforeCompact, retainedEventCount, hiddenIds = /* @__PURE__ */ new Set()) => {
4050
4127
  if (retainedEventCount <= 0) {
4051
4128
  return [];
4052
4129
  }
@@ -4061,6 +4138,9 @@ var init_session = __esm({
4061
4138
  }
4062
4139
  const retained = [];
4063
4140
  for (const id of pathBeforeCompact.slice(start)) {
4141
+ if (hiddenIds.has(id)) {
4142
+ continue;
4143
+ }
4064
4144
  const event = tree.get(id)?.event;
4065
4145
  if (!event) {
4066
4146
  continue;
@@ -4085,13 +4165,13 @@ var init_session = __esm({
4085
4165
  }
4086
4166
  };
4087
4167
  parseHeader = (value) => {
4088
- if (!isRecord7(value)) {
4168
+ if (!isRecord8(value)) {
4089
4169
  throw new SessionStoreError("invalid_header", "Session header must be an object");
4090
4170
  }
4091
4171
  if (value.version !== 1 || typeof value.sessionId !== "string" || typeof value.deviceId !== "string") {
4092
4172
  throw new SessionStoreError("invalid_header", "Session header is missing required identity fields");
4093
4173
  }
4094
- if (typeof value.createdAt !== "number" || !isRecord7(value.meta)) {
4174
+ if (typeof value.createdAt !== "number" || !isRecord8(value.meta)) {
4095
4175
  throw new SessionStoreError("invalid_header", "Session header is missing createdAt or meta");
4096
4176
  }
4097
4177
  if (typeof value.meta.projectId !== "string" || value.meta.projectId.length === 0) {
@@ -4105,7 +4185,7 @@ var init_session = __esm({
4105
4185
  return value;
4106
4186
  };
4107
4187
  validateSessionMatch = (header, value) => {
4108
- if (!isRecord7(value) || typeof value.sessionId !== "string") {
4188
+ if (!isRecord8(value) || typeof value.sessionId !== "string") {
4109
4189
  throw new SessionStoreError("invalid_header", "Event must be an object with a sessionId");
4110
4190
  }
4111
4191
  if (value.sessionId !== header.sessionId) {
@@ -4114,24 +4194,25 @@ var init_session = __esm({
4114
4194
  };
4115
4195
  isConversationEvent = (event) => event.type === "user_message" || event.type === "assistant_message" || event.type === "tool_result" || event.type === "harness_item" || event.type === "compact";
4116
4196
  isInstructionSnapshot = (value) => {
4117
- if (!isRecord7(value) || value.version !== 1 || typeof value.cwd !== "string" || !Array.isArray(value.sections)) {
4197
+ if (!isRecord8(value) || value.version !== 1 || typeof value.cwd !== "string" || !Array.isArray(value.sections)) {
4118
4198
  return false;
4119
4199
  }
4120
4200
  return value.sections.every(
4121
- (section2) => isRecord7(section2) && typeof section2.kind === "string" && typeof section2.frozenAt === "number" && typeof section2.renderedBlock === "string"
4201
+ (section2) => isRecord8(section2) && typeof section2.kind === "string" && typeof section2.frozenAt === "number" && typeof section2.renderedBlock === "string"
4122
4202
  );
4123
4203
  };
4124
- isHarnessItem = (value) => isRecord7(value) && typeof value.kind === "string" && typeof value.origin === "string" && typeof value.content === "string" && (value.visibility === "display" || value.visibility === "hidden" || value.visibility === "compact");
4204
+ isHarnessItem = (value) => isRecord8(value) && typeof value.kind === "string" && typeof value.origin === "string" && typeof value.content === "string" && (value.visibility === "display" || value.visibility === "hidden" || value.visibility === "compact");
4125
4205
  isCompactEvent = (value) => typeof value.summary === "string" && typeof value.compactedThrough === "string" && typeof value.tokensBefore === "number" && typeof value.tokensAfter === "number" && typeof value.retainedEventCount === "number";
4206
+ isContextControlEvent = (value) => value.operation === "hide_user_turn" && typeof value.anchorUserEventId === "string" && typeof value.throughEventId === "string" && (value.actor === "agent" || value.actor === "user" || value.actor === "system") && (value.reason === void 0 || typeof value.reason === "string");
4126
4207
  isQueueUpdate = (value) => (value.queue === "follow_up" || value.queue === "steer") && value.operation === "rewrite" && Array.isArray(value.items) && (value.anchorEventId === null || typeof value.anchorEventId === "string") && value.items.every(
4127
- (item) => isRecord7(item) && typeof item.id === "string" && Array.isArray(item.content) && typeof item.createdAt === "number" && typeof item.updatedAt === "number" && typeof item.clientId === "string"
4208
+ (item) => isRecord8(item) && typeof item.id === "string" && Array.isArray(item.content) && typeof item.createdAt === "number" && typeof item.updatedAt === "number" && typeof item.clientId === "string"
4128
4209
  );
4129
- isSessionTitleUpdated = (value) => typeof value.title === "string" && value.title.length > 0 && (value.source === "model" || value.source === "user") && (value.derivedFrom === void 0 || isRecord7(value.derivedFrom) && typeof value.derivedFrom.eventId === "string" && typeof value.derivedFrom.seq === "number");
4210
+ isSessionTitleUpdated = (value) => typeof value.title === "string" && value.title.length > 0 && (value.source === "model" || value.source === "user") && (value.derivedFrom === void 0 || isRecord8(value.derivedFrom) && typeof value.derivedFrom.eventId === "string" && typeof value.derivedFrom.seq === "number");
4130
4211
  isSkillIndexSnapshot = (value) => (value.anchorEventId === null || typeof value.anchorEventId === "string") && Array.isArray(value.entries) && value.entries.every(isSkillIndexEntry);
4131
4212
  isSkillIndexDelta = (value) => (value.anchorEventId === null || typeof value.anchorEventId === "string") && Array.isArray(value.added) && Array.isArray(value.changed) && Array.isArray(value.removed) && value.added.every(isSkillIndexEntry) && value.changed.every(isSkillIndexEntry) && value.removed.every(
4132
- (item) => isRecord7(item) && typeof item.name === "string" && typeof item.previousPath === "string"
4213
+ (item) => isRecord8(item) && typeof item.name === "string" && typeof item.previousPath === "string"
4133
4214
  );
4134
- isSkillIndexEntry = (value) => isRecord7(value) && typeof value.name === "string" && typeof value.path === "string" && (value.scope === "user" || value.scope === "project" || value.scope === "extension") && typeof value.description === "string" && typeof value.mtimeMs === "number" && typeof value.size === "number" && typeof value.contentHash === "string" && typeof value.priority === "number";
4215
+ isSkillIndexEntry = (value) => isRecord8(value) && typeof value.name === "string" && typeof value.path === "string" && (value.scope === "user" || value.scope === "project" || value.scope === "extension") && typeof value.description === "string" && typeof value.mtimeMs === "number" && typeof value.size === "number" && typeof value.contentHash === "string" && typeof value.priority === "number";
4135
4216
  appendHarnessItemToContext = (messages, event) => {
4136
4217
  const reminder = renderSystemReminder(event.item.content);
4137
4218
  const last = messages.at(-1);
@@ -4168,7 +4249,7 @@ ${reminder}` }]
4168
4249
  }
4169
4250
  return false;
4170
4251
  };
4171
- isToolResultWithContent = (value) => isRecord7(value) && Array.isArray(value.content);
4252
+ isToolResultWithContent = (value) => isRecord8(value) && Array.isArray(value.content);
4172
4253
  renderSystemReminder = (content) => `<system-reminder>
4173
4254
  ${content}
4174
4255
  </system-reminder>`;
@@ -4192,10 +4273,10 @@ ${content}
4192
4273
  cloneMessage = (message) => ({
4193
4274
  ...message,
4194
4275
  content: message.content.map((block) => {
4195
- if (block.type !== "tool_result" || !isRecord7(block.result)) {
4276
+ if (block.type !== "tool_result" || !isRecord8(block.result)) {
4196
4277
  return { ...block };
4197
4278
  }
4198
- const content = Array.isArray(block.result.content) ? { content: block.result.content.map((item) => isRecord7(item) ? { ...item } : item) } : {};
4279
+ const content = Array.isArray(block.result.content) ? { content: block.result.content.map((item) => isRecord8(item) ? { ...item } : item) } : {};
4199
4280
  return {
4200
4281
  ...block,
4201
4282
  result: {
@@ -4205,16 +4286,17 @@ ${content}
4205
4286
  }),
4206
4287
  ...message.meta ? { meta: { ...message.meta } } : {}
4207
4288
  });
4208
- isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4289
+ isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4209
4290
  }
4210
4291
  });
4211
4292
 
4212
4293
  // packages/core/src/skills/index.ts
4213
- import { createHash as createHash2 } from "node:crypto";
4294
+ import { createHash as createHash3 } from "node:crypto";
4214
4295
  import { existsSync as existsSync2 } from "node:fs";
4215
4296
  import { readdir as readdir5, readFile as readFile9, stat as stat4 } from "node:fs/promises";
4216
4297
  import { homedir as homedir4 } from "node:os";
4217
4298
  import { dirname as dirname7, join as join8, resolve as resolve4 } from "node:path";
4299
+ import { Type as Type5 } from "@mariozechner/pi-ai";
4218
4300
  var scanSkillIndex, diffSkillIndex, hasSkillIndexDelta, renderSkillListing, renderSkillDelta, createSkillTool, projectSkillRoots, readSkillEntry, parseSkillMetadata, firstParagraph, parseSkillArgs, findGitRoot2, isNodeErrorCode4;
4219
4301
  var init_skills = __esm({
4220
4302
  "packages/core/src/skills/index.ts"() {
@@ -4305,6 +4387,10 @@ var init_skills = __esm({
4305
4387
  createSkillTool = (options) => defineTool({
4306
4388
  name: "Skill",
4307
4389
  description: "Load the full SKILL.md instructions for an available session-indexed skill by name.",
4390
+ parameters: Type5.Object({
4391
+ name: Type5.String(),
4392
+ args: Type5.Optional(Type5.String())
4393
+ }),
4308
4394
  execute: async (_toolCallId, args) => {
4309
4395
  const input = parseSkillArgs(args);
4310
4396
  const entry = options.getEntry(input.name);
@@ -4369,7 +4455,7 @@ var init_skills = __esm({
4369
4455
  ...parsed.displayName ? { displayName: parsed.displayName } : {},
4370
4456
  mtimeMs: fileStat.mtimeMs,
4371
4457
  size: fileStat.size,
4372
- contentHash: createHash2("sha256").update(content).digest("hex"),
4458
+ contentHash: createHash3("sha256").update(content).digest("hex"),
4373
4459
  priority: options.priority
4374
4460
  };
4375
4461
  };
@@ -4785,7 +4871,7 @@ import { basename as basename3, dirname as dirname8, join as join10, resolve as
4785
4871
  import { pathToFileURL } from "node:url";
4786
4872
  import { promisify as promisify2 } from "node:util";
4787
4873
  import { WebSocketServer } from "ws";
4788
- var daemonPackageName, SESSION_MEMORY_COMPACT_WAIT_MS, AUTO_COMPACT_RETAINED_EVENTS, execFileAsync2, localDaemonStateFile, createLocalDaemonState, readLocalDaemonState, removeLocalDaemonState, markDaemonStopped, daemonStateLiveness, defaultIsPidAlive, startRemoteDaemonWebSocketServer, startScorelHostWebSocketServer, closeWebSocketServer, createRealRuntime, ScorelHost, isMissingConfigError, createEmbeddedTransport, isNodeErrorCode5, wireErrorCode, hasContinuousCoverage, countContentBlocks, normalizeContent, inputText, assistantText, messageText, estimateScorelMessagesTokens, estimateTextTokens, compactLine2, parseSessionMemoryJson, stringArray, disabledMemorySettings, detectRtk, ensureRtkAvailable, emptyRuntimeStats, readRuntimeStats, writeRuntimeStats, parseRuntimeStats, parseRuntimeStatsBuckets, addRtkSavings, addRuntimeStatsBucket, rtkSavingsFromToolResult, nonNegativeInteger2, resolveDefaultShell2, shellCommandArgs2, userShell2, runtimeChannelContextFromWire, parseQueuedChannelContext, parseQueuedModelSelection, imBindingKey, defaultBuiltinExtensionsDir, runtimeModuleDir, findBuiltinExtensionsDir, isSteerMessage, stripImCommandPrefix, isRecord8, parseMemoryUpdate, normalizeMarkdownFile2, sanitizeSessionTitle, shortStack, formatDiagnosticLine, formatDiagnosticValue;
4874
+ var daemonPackageName, SESSION_MEMORY_COMPACT_WAIT_MS, AUTO_COMPACT_RETAINED_EVENTS, execFileAsync2, localDaemonStateFile, createLocalDaemonState, readLocalDaemonState, removeLocalDaemonState, markDaemonStopped, daemonStateLiveness, defaultIsPidAlive, startRemoteDaemonWebSocketServer, startScorelHostWebSocketServer, closeWebSocketServer, createRealRuntime, ScorelHost, isMissingConfigError, createEmbeddedTransport, isNodeErrorCode5, wireErrorCode, hasContinuousCoverage, countContentBlocks, normalizeContent, snipUserMessageIdBlock, inputText, assistantText, messageText, estimateScorelMessagesTokens, estimateTextTokens, compactLine2, parseSessionMemoryJson, stringArray, disabledMemorySettings, detectRtk, ensureRtkAvailable, emptyRuntimeStats, readRuntimeStats, writeRuntimeStats, parseRuntimeStats, parseRuntimeStatsBuckets, addRtkSavings, addRuntimeStatsBucket, rtkSavingsFromToolResult, nonNegativeInteger2, resolveDefaultShell2, shellCommandArgs2, userShell2, runtimeChannelContextFromWire, parseQueuedChannelContext, parseQueuedModelSelection, imBindingKey, defaultBuiltinExtensionsDir, runtimeModuleDir, findBuiltinExtensionsDir, isSteerMessage, stripImCommandPrefix, isRecord9, parseMemoryUpdate, normalizeMarkdownFile2, sanitizeSessionTitle, shortStack, formatDiagnosticLine, formatDiagnosticValue;
4789
4875
  var init_src4 = __esm({
4790
4876
  "packages/daemon/src/index.ts"() {
4791
4877
  "use strict";
@@ -5502,7 +5588,7 @@ var init_src4 = __esm({
5502
5588
  ts: this.#now(),
5503
5589
  message: {
5504
5590
  role: "user",
5505
- content: input.content,
5591
+ content: [...input.content, snipUserMessageIdBlock(userEventId)],
5506
5592
  ...input.source === "follow_up" ? { meta: { source: "follow_up", queueItemId: input.queueItemId } } : {}
5507
5593
  }
5508
5594
  });
@@ -5522,6 +5608,7 @@ var init_src4 = __esm({
5522
5608
  finalAssistantEventId: firstAssistantEventId
5523
5609
  };
5524
5610
  lane.channelContext = input.channelContext;
5611
+ lane.snipClientId = clientId;
5525
5612
  try {
5526
5613
  for await (const rawEvent of lane.runtime.executeTurn(
5527
5614
  buildContext(lane.session.tree, userEvent.id),
@@ -5537,6 +5624,7 @@ var init_src4 = __esm({
5537
5624
  }
5538
5625
  } finally {
5539
5626
  lane.channelContext = void 0;
5627
+ lane.snipClientId = void 0;
5540
5628
  lane.runtime.unregisterTool("SendChannelMessage");
5541
5629
  }
5542
5630
  const result = { userEventId, assistantEventId: state.finalAssistantEventId };
@@ -6786,6 +6874,80 @@ var init_src4 = __esm({
6786
6874
  listNames: () => Object.keys(lane.session.tree.controlState.skillIndex).sort()
6787
6875
  })
6788
6876
  );
6877
+ lane.runtime.registerTool(
6878
+ createSnipTool({
6879
+ snip: async (input) => this.#snipUserTurn(lane, input.userMessageId, input.reason)
6880
+ })
6881
+ );
6882
+ }
6883
+ async #snipUserTurn(lane, userMessageId, reason) {
6884
+ const leafId = lane.session.activeLeafId;
6885
+ if (!leafId) {
6886
+ throw new Error("snip requires an active conversation");
6887
+ }
6888
+ const path = lane.session.tree.getPath(leafId);
6889
+ const anchorUserEventId = this.#resolveSnipUserMessageId(lane, path, userMessageId);
6890
+ const anchorIndex = path.findIndex((id) => id === anchorUserEventId);
6891
+ if (anchorIndex < 0) {
6892
+ throw new Error(`snip target is not on the active conversation path: ${anchorUserEventId}`);
6893
+ }
6894
+ const anchor = lane.session.tree.get(anchorUserEventId)?.event;
6895
+ if (anchor?.type !== "user_message") {
6896
+ throw new Error(`snip target must be a user_message: ${anchorUserEventId}`);
6897
+ }
6898
+ const nextUserIndex = path.findIndex(
6899
+ (id, index) => index > anchorIndex && lane.session.tree.get(id)?.event.type === "user_message"
6900
+ );
6901
+ if (nextUserIndex < 0) {
6902
+ throw new Error("snip cannot hide the current user turn before the next user message exists");
6903
+ }
6904
+ const throughEventId = path[nextUserIndex - 1];
6905
+ if (!throughEventId || throughEventId === anchorUserEventId) {
6906
+ throw new Error(`snip target has no completed turn content: ${anchorUserEventId}`);
6907
+ }
6908
+ const clientId = lane.snipClientId;
6909
+ if (!clientId) {
6910
+ throw new Error("snip is only available while a user turn is running");
6911
+ }
6912
+ await this.#appendPersistent(lane, {
6913
+ type: "context_control",
6914
+ id: asEventId(this.#createId()),
6915
+ parentId: null,
6916
+ sessionId: lane.session.header.sessionId,
6917
+ clientId,
6918
+ ts: this.#now(),
6919
+ operation: "hide_user_turn",
6920
+ anchorUserEventId,
6921
+ throughEventId,
6922
+ actor: "agent",
6923
+ ...reason ? { reason } : {}
6924
+ });
6925
+ await this.#appendDiagnostic(lane.session.header.sessionId, "context_snipped", {
6926
+ anchorUserEventId,
6927
+ throughEventId,
6928
+ hiddenEventCount: nextUserIndex - anchorIndex
6929
+ });
6930
+ return {
6931
+ anchorUserEventId,
6932
+ throughEventId,
6933
+ hiddenEventCount: nextUserIndex - anchorIndex
6934
+ };
6935
+ }
6936
+ #resolveSnipUserMessageId(lane, path, userMessageId) {
6937
+ if (path.includes(userMessageId)) {
6938
+ return userMessageId;
6939
+ }
6940
+ const matches = path.filter((id) => {
6941
+ const event = lane.session.tree.get(id)?.event;
6942
+ return event?.type === "user_message" && snipUserMessageAlias(id) === userMessageId;
6943
+ });
6944
+ if (matches.length === 1) {
6945
+ return matches[0];
6946
+ }
6947
+ if (matches.length > 1) {
6948
+ throw new Error(`snip target short id is ambiguous: ${userMessageId}`);
6949
+ }
6950
+ return asEventId(userMessageId);
6789
6951
  }
6790
6952
  async #selectChatRuntime(lane, modelSelection) {
6791
6953
  if (!modelSelection) {
@@ -7548,7 +7710,14 @@ var init_src4 = __esm({
7548
7710
  };
7549
7711
  countContentBlocks = (message, type) => message.content.filter((block) => block.type === type).length;
7550
7712
  normalizeContent = (content) => typeof content === "string" ? [{ type: "text", text: content }] : content;
7551
- inputText = (message) => message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
7713
+ snipUserMessageIdBlock = (userEventId) => ({
7714
+ type: "text",
7715
+ text: `<system-reminder>
7716
+ snip.userMessageId: ${snipUserMessageAlias(userEventId)}
7717
+ </system-reminder>`,
7718
+ visibility: "model"
7719
+ });
7720
+ inputText = (message) => message.content.flatMap((block) => block.type === "text" && block.visibility !== "model" ? [block.text] : []).join("\n").trim();
7552
7721
  assistantText = (message) => message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
7553
7722
  messageText = (message) => {
7554
7723
  const text = message.content.map((block) => {
@@ -7577,7 +7746,7 @@ var init_src4 = __esm({
7577
7746
  return void 0;
7578
7747
  }
7579
7748
  const parsed = JSON.parse(text);
7580
- if (!isRecord8(parsed)) {
7749
+ if (!isRecord9(parsed)) {
7581
7750
  return void 0;
7582
7751
  }
7583
7752
  return {
@@ -7665,7 +7834,7 @@ var init_src4 = __esm({
7665
7834
  }
7666
7835
  };
7667
7836
  parseRuntimeStats = (value) => {
7668
- if (!isRecord8(value) || !isRecord8(value.rtk)) {
7837
+ if (!isRecord9(value) || !isRecord9(value.rtk)) {
7669
7838
  return emptyRuntimeStats();
7670
7839
  }
7671
7840
  return {
@@ -7679,13 +7848,13 @@ var init_src4 = __esm({
7679
7848
  };
7680
7849
  };
7681
7850
  parseRuntimeStatsBuckets = (value) => {
7682
- if (!isRecord8(value)) {
7851
+ if (!isRecord9(value)) {
7683
7852
  return {};
7684
7853
  }
7685
7854
  return Object.fromEntries(
7686
7855
  Object.entries(value).map(([key, bucket]) => [
7687
7856
  key,
7688
- isRecord8(bucket) ? {
7857
+ isRecord9(bucket) ? {
7689
7858
  outputTokens: nonNegativeInteger2(bucket.outputTokens),
7690
7859
  savedTokens: nonNegativeInteger2(bucket.savedTokens)
7691
7860
  } : { outputTokens: 0, savedTokens: 0 }
@@ -7703,11 +7872,11 @@ var init_src4 = __esm({
7703
7872
  return bucket;
7704
7873
  };
7705
7874
  rtkSavingsFromToolResult = (result) => {
7706
- if (!isRecord8(result) || !isRecord8(result.details)) {
7875
+ if (!isRecord9(result) || !isRecord9(result.details)) {
7707
7876
  return void 0;
7708
7877
  }
7709
7878
  const rtk = result.details.rtk;
7710
- if (!isRecord8(rtk) || rtk.applied !== true) {
7879
+ if (!isRecord9(rtk) || rtk.applied !== true) {
7711
7880
  return void 0;
7712
7881
  }
7713
7882
  const outputTokens = nonNegativeInteger2(rtk.estimatedOutputTokens);
@@ -7752,7 +7921,7 @@ var init_src4 = __esm({
7752
7921
  ...context.data ? { data: context.data } : {}
7753
7922
  });
7754
7923
  parseQueuedChannelContext = (value) => {
7755
- if (!isRecord8(value)) {
7924
+ if (!isRecord9(value)) {
7756
7925
  return void 0;
7757
7926
  }
7758
7927
  if (typeof value.channel !== "string" || typeof value.externalConversationId !== "string") {
@@ -7764,11 +7933,11 @@ var init_src4 = __esm({
7764
7933
  ...typeof value.conversationType === "string" ? { conversationType: value.conversationType } : {},
7765
7934
  ...typeof value.senderDisplayName === "string" ? { senderDisplayName: value.senderDisplayName } : {},
7766
7935
  ...typeof value.mentionedBot === "boolean" ? { mentionedBot: value.mentionedBot } : {},
7767
- ...isRecord8(value.data) ? { data: value.data } : {}
7936
+ ...isRecord9(value.data) ? { data: value.data } : {}
7768
7937
  });
7769
7938
  };
7770
7939
  parseQueuedModelSelection = (value) => {
7771
- if (!isRecord8(value)) {
7940
+ if (!isRecord9(value)) {
7772
7941
  return void 0;
7773
7942
  }
7774
7943
  const selection = {};
@@ -7810,7 +7979,7 @@ var init_src4 = __esm({
7810
7979
  };
7811
7980
  isSteerMessage = (text) => /^\/(?:steer|interrupt)\b/i.test(text.trim());
7812
7981
  stripImCommandPrefix = (text) => text.trim().replace(/^\/(?:steer|interrupt)\s*/i, "").trim() || text;
7813
- isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7982
+ isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7814
7983
  parseMemoryUpdate = (raw) => {
7815
7984
  const text = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
7816
7985
  if (!text) {
@@ -8036,7 +8205,7 @@ var init_daemon_cli = __esm({
8036
8205
  DEFAULT_PORT = 7777;
8037
8206
  STOP_POLL_INTERVAL_MS = 200;
8038
8207
  STOP_GRACE_MS = 5e3;
8039
- START_READY_TIMEOUT_MS = 1e4;
8208
+ START_READY_TIMEOUT_MS = 3e4;
8040
8209
  AUTO_STARTED_IDLE_SHUTDOWN_MS = 15 * 60 * 1e3;
8041
8210
  FOREGROUND_IDLE_SHUTDOWN_MS = 0;
8042
8211
  defaultStateDir2 = () => join13(homedir6(), ".scorel");
@@ -9202,7 +9371,7 @@ var init_up_cli = __esm({
9202
9371
  init_daemon_cli();
9203
9372
  DEFAULT_DAEMON_PORT = 7777;
9204
9373
  DEFAULT_WEBUI_PORT = 3e3;
9205
- DEFAULT_DAEMON_READY_TIMEOUT_MS = 1e4;
9374
+ DEFAULT_DAEMON_READY_TIMEOUT_MS = 3e4;
9206
9375
  defaultStateDir3 = () => join16(homedir8(), ".scorel");
9207
9376
  defaultAttachSigint = (listener) => {
9208
9377
  process.on("SIGINT", listener);
@@ -9557,7 +9726,7 @@ __export(index_exports, {
9557
9726
  runChat: () => runChat,
9558
9727
  runCli: () => runCli
9559
9728
  });
9560
- import { createHash as createHash3 } from "node:crypto";
9729
+ import { createHash as createHash4 } from "node:crypto";
9561
9730
  import { appendFile as appendFile4, mkdir as mkdir8, readFile as readFile14, realpath as realpath3, readdir as readdir7, writeFile as writeFile8 } from "node:fs/promises";
9562
9731
  import { createInterface } from "node:readline/promises";
9563
9732
  import { homedir as homedir9 } from "node:os";
@@ -9879,11 +10048,11 @@ var init_index = __esm({
9879
10048
  };
9880
10049
  };
9881
10050
  attachCacheFilePath = (stateDir, scope, sessionId) => {
9882
- const scopeKey = createHash3("sha256").update(`${scope.kind}\0${scope.locator}`).digest("hex").slice(0, 24);
10051
+ const scopeKey = createHash4("sha256").update(`${scope.kind}\0${scope.locator}`).digest("hex").slice(0, 24);
9883
10052
  return join17(stateDir, "attach-cache", scopeKey, `${sessionId}.json`);
9884
10053
  };
9885
10054
  attachDiagnosticsFilePath = (stateDir, scope, sessionId) => {
9886
- const scopeKey = createHash3("sha256").update(`${scope.kind}\0${scope.locator}`).digest("hex").slice(0, 24);
10055
+ const scopeKey = createHash4("sha256").update(`${scope.kind}\0${scope.locator}`).digest("hex").slice(0, 24);
9887
10056
  return join17(stateDir, "attach-cache", scopeKey, `${sessionId}.log`);
9888
10057
  };
9889
10058
  findAttachDiagnosticsFilePath = async (stateDir, sessionId, _remoteUrl) => {