@chanlerdev/scorel 0.0.5 → 0.0.7

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,53 @@ ${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: "Snipped the selected user turn. It will be omitted from future model context."
2766
+ }],
2767
+ details: result
2768
+ };
2769
+ }
2770
+ });
2771
+ parseSnipToolInput = (args) => {
2772
+ if (!isRecord4(args) || typeof args.userMessageId !== "string") {
2773
+ throw new Error("snip requires { userMessageId: string }");
2774
+ }
2775
+ return {
2776
+ userMessageId: args.userMessageId,
2777
+ ...typeof args.reason === "string" && args.reason.trim() ? { reason: args.reason.trim() } : {}
2778
+ };
2779
+ };
2780
+ isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2691
2781
  }
2692
2782
  });
2693
2783
 
2694
2784
  // packages/core/src/channel/index.ts
2695
- var createSendChannelMessageTool, parseSendChannelMessageInput, parseAttachments, optionalString2, isRecord4;
2785
+ import { Type as Type3 } from "@mariozechner/pi-ai";
2786
+ var createSendChannelMessageTool, parseSendChannelMessageInput, parseAttachments, optionalString2, isRecord5;
2696
2787
  var init_channel = __esm({
2697
2788
  "packages/core/src/channel/index.ts"() {
2698
2789
  "use strict";
@@ -2700,6 +2791,18 @@ var init_channel = __esm({
2700
2791
  createSendChannelMessageTool = (options) => defineTool({
2701
2792
  name: "SendChannelMessage",
2702
2793
  description: "Send a text reply to the current IM channel conversation. Do not provide raw platform user ids or group ids.",
2794
+ parameters: Type3.Object({
2795
+ text: Type3.Optional(Type3.String()),
2796
+ attachments: Type3.Optional(Type3.Array(Type3.Object({
2797
+ type: Type3.Union([Type3.Literal("image"), Type3.Literal("file")]),
2798
+ path: Type3.Optional(Type3.String()),
2799
+ url: Type3.Optional(Type3.String()),
2800
+ mimeType: Type3.Optional(Type3.String()),
2801
+ caption: Type3.Optional(Type3.String())
2802
+ }))),
2803
+ channel: Type3.Optional(Type3.String()),
2804
+ target: Type3.Optional(Type3.Literal("current"))
2805
+ }),
2703
2806
  execute: async (_toolCallId, args) => {
2704
2807
  const input = parseSendChannelMessageInput(args);
2705
2808
  const result = await options.sendCurrent(input);
@@ -2710,7 +2813,7 @@ var init_channel = __esm({
2710
2813
  }
2711
2814
  });
2712
2815
  parseSendChannelMessageInput = (value) => {
2713
- if (!isRecord4(value)) {
2816
+ if (!isRecord5(value)) {
2714
2817
  throw new Error("SendChannelMessage args must be an object");
2715
2818
  }
2716
2819
  const text = typeof value.text === "string" && value.text.trim().length > 0 ? value.text : void 0;
@@ -2739,7 +2842,7 @@ var init_channel = __esm({
2739
2842
  throw new Error("SendChannelMessage.attachments must be an array");
2740
2843
  }
2741
2844
  return value.map((item, index) => {
2742
- if (!isRecord4(item)) {
2845
+ if (!isRecord5(item)) {
2743
2846
  throw new Error(`SendChannelMessage.attachments.${index} must be an object`);
2744
2847
  }
2745
2848
  if (item.type !== "image" && item.type !== "file") {
@@ -2768,14 +2871,14 @@ var init_channel = __esm({
2768
2871
  }
2769
2872
  return value;
2770
2873
  };
2771
- isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2874
+ isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2772
2875
  }
2773
2876
  });
2774
2877
 
2775
2878
  // packages/core/src/extensions/index.ts
2776
2879
  import { readFile as readFile5 } from "node:fs/promises";
2777
2880
  import { dirname as dirname4, resolve as resolve2 } from "node:path";
2778
- var loadExtensionManifest, parseExtensionManifest, requireString2, requireIdentifier2, requireKind, requireRelativePath, optionalRelativePaths, isRecord5;
2881
+ var loadExtensionManifest, parseExtensionManifest, requireString2, requireIdentifier2, requireKind, requireRelativePath, optionalRelativePaths, isRecord6;
2779
2882
  var init_extensions = __esm({
2780
2883
  "packages/core/src/extensions/index.ts"() {
2781
2884
  "use strict";
@@ -2788,7 +2891,7 @@ var init_extensions = __esm({
2788
2891
  const message = cause instanceof Error ? cause.message : String(cause);
2789
2892
  throw new Error(`Invalid extension manifest JSON at ${manifestPath}: ${message}`);
2790
2893
  }
2791
- if (!isRecord5(value)) {
2894
+ if (!isRecord6(value)) {
2792
2895
  throw new Error(`Extension manifest at ${manifestPath} must be an object`);
2793
2896
  }
2794
2897
  const rootDir = dirname4(resolve2(manifestPath));
@@ -2844,7 +2947,7 @@ var init_extensions = __esm({
2844
2947
  }
2845
2948
  return value.map((item, index) => requireRelativePath(item, `${name}.${index}`, manifestPath));
2846
2949
  };
2847
- isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2950
+ isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2848
2951
  }
2849
2952
  });
2850
2953
 
@@ -2988,7 +3091,8 @@ var init_instructions = __esm({
2988
3091
  import { appendFile, mkdir as mkdir3, readFile as readFile7, writeFile as writeFile3 } from "node:fs/promises";
2989
3092
  import { homedir as homedir3 } from "node:os";
2990
3093
  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;
3094
+ import { Type as Type4 } from "@mariozechner/pi-ai";
3095
+ 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
3096
  var init_memory = __esm({
2993
3097
  "packages/core/src/memory/index.ts"() {
2994
3098
  "use strict";
@@ -3077,6 +3181,14 @@ var init_memory = __esm({
3077
3181
  "Use this once near the end of a completed user turn when there is progress, a decision, or a follow-up worth preserving.",
3078
3182
  "Do not include secrets, raw logs, speculation, or facts that should be re-read from the repository."
3079
3183
  ].join(" "),
3184
+ parameters: Type4.Object({
3185
+ summary: Type4.String(),
3186
+ completed: Type4.Optional(Type4.Array(Type4.String())),
3187
+ decisions: Type4.Optional(Type4.Array(Type4.String())),
3188
+ followUps: Type4.Optional(Type4.Array(Type4.String())),
3189
+ memoryCandidates: Type4.Optional(Type4.Array(Type4.String())),
3190
+ evidence: Type4.Optional(Type4.Array(Type4.String()))
3191
+ }),
3080
3192
  execute: async (_toolCallId, args) => {
3081
3193
  const input = parseAppendDailyInput(args);
3082
3194
  validateAppendDailyInput(input);
@@ -3220,7 +3332,7 @@ var init_memory = __esm({
3220
3332
  normalizeMarkdownFile = (value) => `${value.trimEnd()}
3221
3333
  `;
3222
3334
  parseAppendDailyInput = (value) => {
3223
- if (!isRecord6(value)) {
3335
+ if (!isRecord7(value)) {
3224
3336
  throw new Error("AppendDaily args must be an object");
3225
3337
  }
3226
3338
  const summary = requireString3(value.summary, "summary");
@@ -3286,12 +3398,12 @@ var init_memory = __esm({
3286
3398
  optionalNumber2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
3287
3399
  optionalString3 = (value) => typeof value === "string" && value.trim() ? value : void 0;
3288
3400
  parseLastFailure = (value) => {
3289
- if (!isRecord6(value)) return void 0;
3401
+ if (!isRecord7(value)) return void 0;
3290
3402
  const at = optionalNumber2(value.at);
3291
3403
  const message = optionalString3(value.message);
3292
3404
  return at !== void 0 && message ? { at, message } : void 0;
3293
3405
  };
3294
- isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3406
+ isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3295
3407
  safeProjectId = (projectId) => {
3296
3408
  if (!/^[A-Za-z0-9_-]+$/.test(projectId)) {
3297
3409
  throw new Error("projectId must contain only letters, numbers, underscores, or hyphens");
@@ -3302,16 +3414,65 @@ var init_memory = __esm({
3302
3414
  }
3303
3415
  });
3304
3416
 
3417
+ // packages/core/src/reminders/index.ts
3418
+ var createSystemReminderBlock, renderSystemReminderText, renderSystemReminder, systemReminderMessage, appendSystemReminderToToolResult, cloneSystemReminderBlock, isToolResultWithContent;
3419
+ var init_reminders = __esm({
3420
+ "packages/core/src/reminders/index.ts"() {
3421
+ "use strict";
3422
+ createSystemReminderBlock = (input) => ({
3423
+ type: "system_reminder",
3424
+ kind: input.kind,
3425
+ origin: input.origin,
3426
+ text: input.text,
3427
+ visibility: input.visibility,
3428
+ scope: input.scope,
3429
+ ...input.data ? { data: { ...input.data } } : {}
3430
+ });
3431
+ renderSystemReminderText = (text) => `<system-reminder>
3432
+ ${text}
3433
+ </system-reminder>`;
3434
+ renderSystemReminder = (input) => renderSystemReminderText(typeof input === "string" ? input : input.text);
3435
+ systemReminderMessage = (block, meta) => ({
3436
+ role: "user",
3437
+ content: [cloneSystemReminderBlock(block)],
3438
+ ...meta ? { meta: { ...meta } } : {}
3439
+ });
3440
+ appendSystemReminderToToolResult = (message, block) => {
3441
+ for (let i = message.content.length - 1; i >= 0; i -= 1) {
3442
+ const candidate = message.content[i];
3443
+ if (candidate?.type !== "tool_result" || !isToolResultWithContent(candidate.result)) {
3444
+ continue;
3445
+ }
3446
+ const mergedResult = {
3447
+ ...candidate.result,
3448
+ content: [...candidate.result.content, cloneSystemReminderBlock(block)]
3449
+ };
3450
+ message.content[i] = {
3451
+ ...candidate,
3452
+ result: mergedResult
3453
+ };
3454
+ return true;
3455
+ }
3456
+ return false;
3457
+ };
3458
+ cloneSystemReminderBlock = (block) => ({
3459
+ ...block,
3460
+ ...block.data ? { data: { ...block.data } } : {}
3461
+ });
3462
+ isToolResultWithContent = (value) => typeof value === "object" && value !== null && "content" in value && Array.isArray(value.content);
3463
+ }
3464
+ });
3465
+
3305
3466
  // packages/core/src/provider/pi-ai.ts
3306
3467
  import {
3307
- Type,
3308
3468
  getModels,
3309
3469
  streamSimple
3310
3470
  } 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;
3471
+ var DEFAULT_CUSTOM_MODEL_CONTEXT_WINDOW, DEFAULT_CUSTOM_MODEL_MAX_TOKENS, createPiAiProvider, resolvePiAiModel, toPiContext, toPiMessage, toPiAssistantBlock, fromPiAssistant, fromPiContentBlock, toPiTool, textContent, toolResultText, isSystemReminderContentBlock, stringMeta, toPiStopReason, fromPiStopReason, fromPiUsage;
3312
3472
  var init_pi_ai = __esm({
3313
3473
  "packages/core/src/provider/pi-ai.ts"() {
3314
3474
  "use strict";
3475
+ init_reminders();
3315
3476
  DEFAULT_CUSTOM_MODEL_CONTEXT_WINDOW = 2e5;
3316
3477
  DEFAULT_CUSTOM_MODEL_MAX_TOKENS = 64e3;
3317
3478
  createPiAiProvider = (options) => ({
@@ -3410,6 +3571,9 @@ var init_pi_ai = __esm({
3410
3571
  if (block.type === "text") {
3411
3572
  return [{ type: "text", text: block.text }];
3412
3573
  }
3574
+ if (block.type === "system_reminder") {
3575
+ return [{ type: "text", text: renderSystemReminder(block) }];
3576
+ }
3413
3577
  if (block.type === "thinking") {
3414
3578
  return [{ type: "thinking", thinking: block.text }];
3415
3579
  }
@@ -3446,99 +3610,35 @@ var init_pi_ai = __esm({
3446
3610
  toPiTool = (tool) => ({
3447
3611
  name: tool.name,
3448
3612
  description: tool.description,
3449
- parameters: toolParameters(tool.name)
3613
+ parameters: tool.parameters
3450
3614
  });
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({});
3615
+ textContent = (message) => message.content.flatMap((block) => {
3616
+ if (block.type === "text") {
3617
+ return [block.text];
3530
3618
  }
3531
- };
3532
- textContent = (message) => message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
3619
+ if (block.type === "system_reminder") {
3620
+ return [renderSystemReminder(block)];
3621
+ }
3622
+ return [];
3623
+ }).join("\n");
3533
3624
  toolResultText = (result) => {
3534
3625
  if (typeof result === "object" && result !== null && "content" in result) {
3535
3626
  const content = result.content;
3536
3627
  if (Array.isArray(content)) {
3537
- return content.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
3628
+ return content.flatMap((block) => {
3629
+ if (block?.type === "text" && typeof block.text === "string") {
3630
+ return [block.text];
3631
+ }
3632
+ if (isSystemReminderContentBlock(block)) {
3633
+ return [renderSystemReminder(block)];
3634
+ }
3635
+ return [];
3636
+ }).join("\n");
3538
3637
  }
3539
3638
  }
3540
3639
  return JSON.stringify(result);
3541
3640
  };
3641
+ isSystemReminderContentBlock = (value) => typeof value === "object" && value !== null && value.type === "system_reminder" && typeof value.text === "string";
3542
3642
  stringMeta = (message, key) => {
3543
3643
  const value = message.meta?.[key];
3544
3644
  return typeof value === "string" ? value : void 0;
@@ -3776,22 +3876,23 @@ var init_runtime = __esm({
3776
3876
  });
3777
3877
 
3778
3878
  // packages/core/src/session/index.ts
3879
+ import { createHash as createHash2 } from "node:crypto";
3779
3880
  import { appendFile as appendFile2, mkdir as mkdir4, readFile as readFile8, writeFile as writeFile4 } from "node:fs/promises";
3780
3881
  import { dirname as dirname6, join as join7 } from "node:path";
3781
3882
  function assertTreeEvent(value) {
3782
- if (!isRecord7(value)) {
3883
+ if (!isRecord8(value)) {
3783
3884
  throw new SessionStoreError("invalid_event", "Event must be an object");
3784
3885
  }
3785
3886
  if (value.type === "session_header") {
3786
3887
  throw new SessionStoreError("invalid_event", "Session header must be stored as the JSONL header line");
3787
3888
  }
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") {
3889
+ 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
3890
  throw new SessionStoreError("invalid_event", "Unsupported session event type");
3790
3891
  }
3791
3892
  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
3893
  throw new SessionStoreError("invalid_event", "Event is missing required base fields");
3793
3894
  }
3794
- if ((value.type === "user_message" || value.type === "assistant_message" || value.type === "tool_result") && !isRecord7(value.message)) {
3895
+ if ((value.type === "user_message" || value.type === "assistant_message" || value.type === "tool_result") && !isRecord8(value.message)) {
3795
3896
  throw new SessionStoreError("invalid_event", "Message event is missing message payload");
3796
3897
  }
3797
3898
  if (value.type === "session_title_updated" && !isSessionTitleUpdated(value)) {
@@ -3806,6 +3907,9 @@ function assertTreeEvent(value) {
3806
3907
  if (value.type === "compact" && !isCompactEvent(value)) {
3807
3908
  throw new SessionStoreError("invalid_event", "compact is missing summary payload");
3808
3909
  }
3910
+ if (value.type === "context_control" && !isContextControlEvent(value)) {
3911
+ throw new SessionStoreError("invalid_event", "context_control is missing hide_user_turn payload");
3912
+ }
3809
3913
  if (value.type === "queue_update" && !isQueueUpdate(value)) {
3810
3914
  throw new SessionStoreError("invalid_event", "queue_update is missing queue payload");
3811
3915
  }
@@ -3816,11 +3920,13 @@ function assertTreeEvent(value) {
3816
3920
  throw new SessionStoreError("invalid_event", "skill_index_delta is missing delta payload");
3817
3921
  }
3818
3922
  }
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;
3923
+ 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, compactSummaryMessage, reminderKindFromHarness, reminderVisibilityFromHarness, reminderScopeFromHarness, cloneMessage, isRecord8;
3820
3924
  var init_session = __esm({
3821
3925
  "packages/core/src/session/index.ts"() {
3822
3926
  "use strict";
3823
3927
  init_src();
3928
+ init_reminders();
3929
+ snipUserMessageAlias = (eventId) => `u_${createHash2("sha256").update(eventId).digest("hex").slice(0, 8)}`;
3824
3930
  SessionStoreError = class extends Error {
3825
3931
  code;
3826
3932
  line;
@@ -3844,7 +3950,8 @@ var init_session = __esm({
3844
3950
  steer: []
3845
3951
  },
3846
3952
  skillIndexInitialized: false,
3847
- skillIndex: {}
3953
+ skillIndex: {},
3954
+ hiddenUserTurnSpans: []
3848
3955
  };
3849
3956
  get rootId() {
3850
3957
  return this.#rootId;
@@ -3964,6 +4071,16 @@ var init_session = __esm({
3964
4071
  delete next[removed.name];
3965
4072
  }
3966
4073
  this.controlState.skillIndex = next;
4074
+ } else if (event.type === "context_control") {
4075
+ this.controlState.hiddenUserTurnSpans = [
4076
+ ...this.controlState.hiddenUserTurnSpans.filter(
4077
+ (span) => span.anchorUserEventId !== event.anchorUserEventId
4078
+ ),
4079
+ {
4080
+ anchorUserEventId: event.anchorUserEventId,
4081
+ throughEventId: event.throughEventId
4082
+ }
4083
+ ];
3967
4084
  }
3968
4085
  }
3969
4086
  };
@@ -4025,7 +4142,11 @@ var init_session = __esm({
4025
4142
  };
4026
4143
  buildContext = (tree, leafId) => {
4027
4144
  const path = tree.getPath(leafId);
4145
+ const hiddenIds = hiddenContextEventIds(tree, path, leafId);
4028
4146
  return path.reduce((messages, id, index) => {
4147
+ if (hiddenIds.has(id)) {
4148
+ return messages;
4149
+ }
4029
4150
  const event = tree.get(id)?.event;
4030
4151
  if (!event) {
4031
4152
  return messages;
@@ -4038,7 +4159,7 @@ var init_session = __esm({
4038
4159
  appendHarnessItemToContext(messages, event);
4039
4160
  }
4040
4161
  if (event.type === "compact") {
4041
- const retained = retainedMessagesBeforeCompact(tree, path.slice(0, index), event.retainedEventCount);
4162
+ const retained = retainedMessagesBeforeCompact(tree, path.slice(0, index), event.retainedEventCount, hiddenIds);
4042
4163
  messages.length = 0;
4043
4164
  messages.push(compactSummaryMessage(event));
4044
4165
  messages.push(...retained);
@@ -4046,7 +4167,29 @@ var init_session = __esm({
4046
4167
  return messages;
4047
4168
  }, []);
4048
4169
  };
4049
- retainedMessagesBeforeCompact = (tree, pathBeforeCompact, retainedEventCount) => {
4170
+ hiddenContextEventIds = (tree, path, leafId) => {
4171
+ const leaf = tree.get(leafId)?.event;
4172
+ if (!leaf) {
4173
+ return /* @__PURE__ */ new Set();
4174
+ }
4175
+ const pathIndexes = new Map(path.map((id, index) => [id, index]));
4176
+ const hidden = /* @__PURE__ */ new Set();
4177
+ for (const event of tree) {
4178
+ if (event.type !== "context_control" || Number(event.seq) > Number(leaf.seq)) {
4179
+ continue;
4180
+ }
4181
+ const start = pathIndexes.get(event.anchorUserEventId);
4182
+ const end = pathIndexes.get(event.throughEventId);
4183
+ if (start === void 0 || end === void 0 || end < start) {
4184
+ continue;
4185
+ }
4186
+ for (const id of path.slice(start, end + 1)) {
4187
+ hidden.add(id);
4188
+ }
4189
+ }
4190
+ return hidden;
4191
+ };
4192
+ retainedMessagesBeforeCompact = (tree, pathBeforeCompact, retainedEventCount, hiddenIds = /* @__PURE__ */ new Set()) => {
4050
4193
  if (retainedEventCount <= 0) {
4051
4194
  return [];
4052
4195
  }
@@ -4061,6 +4204,9 @@ var init_session = __esm({
4061
4204
  }
4062
4205
  const retained = [];
4063
4206
  for (const id of pathBeforeCompact.slice(start)) {
4207
+ if (hiddenIds.has(id)) {
4208
+ continue;
4209
+ }
4064
4210
  const event = tree.get(id)?.event;
4065
4211
  if (!event) {
4066
4212
  continue;
@@ -4085,13 +4231,13 @@ var init_session = __esm({
4085
4231
  }
4086
4232
  };
4087
4233
  parseHeader = (value) => {
4088
- if (!isRecord7(value)) {
4234
+ if (!isRecord8(value)) {
4089
4235
  throw new SessionStoreError("invalid_header", "Session header must be an object");
4090
4236
  }
4091
4237
  if (value.version !== 1 || typeof value.sessionId !== "string" || typeof value.deviceId !== "string") {
4092
4238
  throw new SessionStoreError("invalid_header", "Session header is missing required identity fields");
4093
4239
  }
4094
- if (typeof value.createdAt !== "number" || !isRecord7(value.meta)) {
4240
+ if (typeof value.createdAt !== "number" || !isRecord8(value.meta)) {
4095
4241
  throw new SessionStoreError("invalid_header", "Session header is missing createdAt or meta");
4096
4242
  }
4097
4243
  if (typeof value.meta.projectId !== "string" || value.meta.projectId.length === 0) {
@@ -4105,7 +4251,7 @@ var init_session = __esm({
4105
4251
  return value;
4106
4252
  };
4107
4253
  validateSessionMatch = (header, value) => {
4108
- if (!isRecord7(value) || typeof value.sessionId !== "string") {
4254
+ if (!isRecord8(value) || typeof value.sessionId !== "string") {
4109
4255
  throw new SessionStoreError("invalid_header", "Event must be an object with a sessionId");
4110
4256
  }
4111
4257
  if (value.sessionId !== header.sessionId) {
@@ -4114,88 +4260,98 @@ var init_session = __esm({
4114
4260
  };
4115
4261
  isConversationEvent = (event) => event.type === "user_message" || event.type === "assistant_message" || event.type === "tool_result" || event.type === "harness_item" || event.type === "compact";
4116
4262
  isInstructionSnapshot = (value) => {
4117
- if (!isRecord7(value) || value.version !== 1 || typeof value.cwd !== "string" || !Array.isArray(value.sections)) {
4263
+ if (!isRecord8(value) || value.version !== 1 || typeof value.cwd !== "string" || !Array.isArray(value.sections)) {
4118
4264
  return false;
4119
4265
  }
4120
4266
  return value.sections.every(
4121
- (section2) => isRecord7(section2) && typeof section2.kind === "string" && typeof section2.frozenAt === "number" && typeof section2.renderedBlock === "string"
4267
+ (section2) => isRecord8(section2) && typeof section2.kind === "string" && typeof section2.frozenAt === "number" && typeof section2.renderedBlock === "string"
4122
4268
  );
4123
4269
  };
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");
4270
+ 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
4271
  isCompactEvent = (value) => typeof value.summary === "string" && typeof value.compactedThrough === "string" && typeof value.tokensBefore === "number" && typeof value.tokensAfter === "number" && typeof value.retainedEventCount === "number";
4272
+ 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
4273
  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"
4274
+ (item) => isRecord8(item) && typeof item.id === "string" && Array.isArray(item.content) && typeof item.createdAt === "number" && typeof item.updatedAt === "number" && typeof item.clientId === "string"
4128
4275
  );
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");
4276
+ 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
4277
  isSkillIndexSnapshot = (value) => (value.anchorEventId === null || typeof value.anchorEventId === "string") && Array.isArray(value.entries) && value.entries.every(isSkillIndexEntry);
4131
4278
  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"
4279
+ (item) => isRecord8(item) && typeof item.name === "string" && typeof item.previousPath === "string"
4133
4280
  );
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";
4281
+ 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
4282
  appendHarnessItemToContext = (messages, event) => {
4136
- const reminder = renderSystemReminder(event.item.content);
4283
+ const reminder = createSystemReminderBlock({
4284
+ kind: reminderKindFromHarness(event.item.kind),
4285
+ origin: event.item.origin,
4286
+ text: event.item.content,
4287
+ visibility: reminderVisibilityFromHarness(event.item.visibility),
4288
+ scope: reminderScopeFromHarness(event.item.kind),
4289
+ ...event.item.data ? { data: event.item.data } : {}
4290
+ });
4137
4291
  const last = messages.at(-1);
4138
- if (last?.role === "tool_result" && appendReminderToToolResult(last, reminder)) {
4292
+ if (last?.role === "tool_result" && appendSystemReminderToToolResult(last, reminder)) {
4139
4293
  return;
4140
4294
  }
4141
- messages.push({
4142
- role: "user",
4143
- content: [{ type: "text", text: reminder }],
4144
- meta: {
4145
- source: "harness_item",
4146
- harnessKind: event.item.kind,
4147
- harnessOrigin: event.item.origin
4148
- }
4149
- });
4295
+ messages.push(systemReminderMessage(reminder, {
4296
+ source: "harness_item",
4297
+ harnessKind: event.item.kind,
4298
+ harnessOrigin: event.item.origin
4299
+ }));
4150
4300
  };
4151
- appendReminderToToolResult = (message, reminder) => {
4152
- for (let i = message.content.length - 1; i >= 0; i -= 1) {
4153
- const block = message.content[i];
4154
- if (block?.type !== "tool_result" || !isToolResultWithContent(block.result)) {
4155
- continue;
4156
- }
4157
- const mergedResult = {
4158
- ...block.result,
4159
- content: [...block.result.content, { type: "text", text: `
4160
-
4161
- ${reminder}` }]
4162
- };
4163
- message.content[i] = {
4164
- ...block,
4165
- result: mergedResult
4166
- };
4167
- return true;
4168
- }
4169
- return false;
4170
- };
4171
- isToolResultWithContent = (value) => isRecord7(value) && Array.isArray(value.content);
4172
- renderSystemReminder = (content) => `<system-reminder>
4173
- ${content}
4174
- </system-reminder>`;
4175
4301
  compactSummaryMessage = (event) => ({
4176
4302
  role: "user",
4177
- content: [{
4178
- type: "text",
4179
- text: renderSystemReminder([
4303
+ content: [createSystemReminderBlock({
4304
+ kind: "compact_summary",
4305
+ origin: "system",
4306
+ text: [
4180
4307
  "Earlier session context has been compacted.",
4181
4308
  "",
4182
4309
  event.summary.trim(),
4183
4310
  "",
4184
4311
  "Use this summary as continuity context. Verify current repository facts before acting."
4185
- ].join("\n"))
4186
- }],
4312
+ ].join("\n"),
4313
+ visibility: "model",
4314
+ scope: "session"
4315
+ })],
4187
4316
  meta: {
4188
4317
  source: "compact",
4189
4318
  compactedThrough: event.compactedThrough
4190
4319
  }
4191
4320
  });
4321
+ reminderKindFromHarness = (kind) => {
4322
+ if (kind === "attachment" || kind === "skill_listing" || kind === "skill_delta" || kind === "memory" || kind === "channel_context" || kind === "steer" || kind === "runtime_notice") {
4323
+ return kind;
4324
+ }
4325
+ if (kind === "date_change") {
4326
+ return "time";
4327
+ }
4328
+ return "runtime_notice";
4329
+ };
4330
+ reminderVisibilityFromHarness = (visibility) => {
4331
+ if (visibility === "hidden") {
4332
+ return "model";
4333
+ }
4334
+ return visibility;
4335
+ };
4336
+ reminderScopeFromHarness = (kind) => {
4337
+ if (kind === "steer" || kind === "skill_delta" || kind === "runtime_notice") {
4338
+ return "next_model_call";
4339
+ }
4340
+ if (kind === "channel_context" || kind === "attachment" || kind === "date_change") {
4341
+ return "turn";
4342
+ }
4343
+ return "session";
4344
+ };
4192
4345
  cloneMessage = (message) => ({
4193
4346
  ...message,
4194
4347
  content: message.content.map((block) => {
4195
- if (block.type !== "tool_result" || !isRecord7(block.result)) {
4348
+ if (block.type === "system_reminder") {
4349
+ return cloneSystemReminderBlock(block);
4350
+ }
4351
+ if (block.type !== "tool_result" || !isRecord8(block.result)) {
4196
4352
  return { ...block };
4197
4353
  }
4198
- const content = Array.isArray(block.result.content) ? { content: block.result.content.map((item) => isRecord7(item) ? { ...item } : item) } : {};
4354
+ const content = Array.isArray(block.result.content) ? { content: block.result.content.map((item) => isRecord8(item) ? { ...item } : item) } : {};
4199
4355
  return {
4200
4356
  ...block,
4201
4357
  result: {
@@ -4205,16 +4361,17 @@ ${content}
4205
4361
  }),
4206
4362
  ...message.meta ? { meta: { ...message.meta } } : {}
4207
4363
  });
4208
- isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4364
+ isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4209
4365
  }
4210
4366
  });
4211
4367
 
4212
4368
  // packages/core/src/skills/index.ts
4213
- import { createHash as createHash2 } from "node:crypto";
4369
+ import { createHash as createHash3 } from "node:crypto";
4214
4370
  import { existsSync as existsSync2 } from "node:fs";
4215
4371
  import { readdir as readdir5, readFile as readFile9, stat as stat4 } from "node:fs/promises";
4216
4372
  import { homedir as homedir4 } from "node:os";
4217
4373
  import { dirname as dirname7, join as join8, resolve as resolve4 } from "node:path";
4374
+ import { Type as Type5 } from "@mariozechner/pi-ai";
4218
4375
  var scanSkillIndex, diffSkillIndex, hasSkillIndexDelta, renderSkillListing, renderSkillDelta, createSkillTool, projectSkillRoots, readSkillEntry, parseSkillMetadata, firstParagraph, parseSkillArgs, findGitRoot2, isNodeErrorCode4;
4219
4376
  var init_skills = __esm({
4220
4377
  "packages/core/src/skills/index.ts"() {
@@ -4305,6 +4462,10 @@ var init_skills = __esm({
4305
4462
  createSkillTool = (options) => defineTool({
4306
4463
  name: "Skill",
4307
4464
  description: "Load the full SKILL.md instructions for an available session-indexed skill by name.",
4465
+ parameters: Type5.Object({
4466
+ name: Type5.String(),
4467
+ args: Type5.Optional(Type5.String())
4468
+ }),
4308
4469
  execute: async (_toolCallId, args) => {
4309
4470
  const input = parseSkillArgs(args);
4310
4471
  const entry = options.getEntry(input.name);
@@ -4369,7 +4530,7 @@ var init_skills = __esm({
4369
4530
  ...parsed.displayName ? { displayName: parsed.displayName } : {},
4370
4531
  mtimeMs: fileStat.mtimeMs,
4371
4532
  size: fileStat.size,
4372
- contentHash: createHash2("sha256").update(content).digest("hex"),
4533
+ contentHash: createHash3("sha256").update(content).digest("hex"),
4373
4534
  priority: options.priority
4374
4535
  };
4375
4536
  };
@@ -4446,6 +4607,7 @@ var init_src3 = __esm({
4446
4607
  init_instructions();
4447
4608
  init_memory();
4448
4609
  init_pi_ai();
4610
+ init_reminders();
4449
4611
  init_runtime();
4450
4612
  init_session();
4451
4613
  init_skills();
@@ -4785,7 +4947,7 @@ import { basename as basename3, dirname as dirname8, join as join10, resolve as
4785
4947
  import { pathToFileURL } from "node:url";
4786
4948
  import { promisify as promisify2 } from "node:util";
4787
4949
  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;
4950
+ 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
4951
  var init_src4 = __esm({
4790
4952
  "packages/daemon/src/index.ts"() {
4791
4953
  "use strict";
@@ -5502,7 +5664,7 @@ var init_src4 = __esm({
5502
5664
  ts: this.#now(),
5503
5665
  message: {
5504
5666
  role: "user",
5505
- content: input.content,
5667
+ content: [...input.content, snipUserMessageIdBlock(userEventId)],
5506
5668
  ...input.source === "follow_up" ? { meta: { source: "follow_up", queueItemId: input.queueItemId } } : {}
5507
5669
  }
5508
5670
  });
@@ -5522,6 +5684,7 @@ var init_src4 = __esm({
5522
5684
  finalAssistantEventId: firstAssistantEventId
5523
5685
  };
5524
5686
  lane.channelContext = input.channelContext;
5687
+ lane.snipClientId = clientId;
5525
5688
  try {
5526
5689
  for await (const rawEvent of lane.runtime.executeTurn(
5527
5690
  buildContext(lane.session.tree, userEvent.id),
@@ -5537,6 +5700,7 @@ var init_src4 = __esm({
5537
5700
  }
5538
5701
  } finally {
5539
5702
  lane.channelContext = void 0;
5703
+ lane.snipClientId = void 0;
5540
5704
  lane.runtime.unregisterTool("SendChannelMessage");
5541
5705
  }
5542
5706
  const result = { userEventId, assistantEventId: state.finalAssistantEventId };
@@ -6786,6 +6950,80 @@ var init_src4 = __esm({
6786
6950
  listNames: () => Object.keys(lane.session.tree.controlState.skillIndex).sort()
6787
6951
  })
6788
6952
  );
6953
+ lane.runtime.registerTool(
6954
+ createSnipTool({
6955
+ snip: async (input) => this.#snipUserTurn(lane, input.userMessageId, input.reason)
6956
+ })
6957
+ );
6958
+ }
6959
+ async #snipUserTurn(lane, userMessageId, reason) {
6960
+ const leafId = lane.session.activeLeafId;
6961
+ if (!leafId) {
6962
+ throw new Error("snip requires an active conversation");
6963
+ }
6964
+ const path = lane.session.tree.getPath(leafId);
6965
+ const anchorUserEventId = this.#resolveSnipUserMessageId(lane, path, userMessageId);
6966
+ const anchorIndex = path.findIndex((id) => id === anchorUserEventId);
6967
+ if (anchorIndex < 0) {
6968
+ throw new Error(`snip target is not on the active conversation path: ${anchorUserEventId}`);
6969
+ }
6970
+ const anchor = lane.session.tree.get(anchorUserEventId)?.event;
6971
+ if (anchor?.type !== "user_message") {
6972
+ throw new Error(`snip target must be a user_message: ${anchorUserEventId}`);
6973
+ }
6974
+ const nextUserIndex = path.findIndex(
6975
+ (id, index) => index > anchorIndex && lane.session.tree.get(id)?.event.type === "user_message"
6976
+ );
6977
+ if (nextUserIndex < 0) {
6978
+ throw new Error("snip cannot hide the current user turn before the next user message exists");
6979
+ }
6980
+ const throughEventId = path[nextUserIndex - 1];
6981
+ if (!throughEventId || throughEventId === anchorUserEventId) {
6982
+ throw new Error(`snip target has no completed turn content: ${anchorUserEventId}`);
6983
+ }
6984
+ const clientId = lane.snipClientId;
6985
+ if (!clientId) {
6986
+ throw new Error("snip is only available while a user turn is running");
6987
+ }
6988
+ await this.#appendPersistent(lane, {
6989
+ type: "context_control",
6990
+ id: asEventId(this.#createId()),
6991
+ parentId: null,
6992
+ sessionId: lane.session.header.sessionId,
6993
+ clientId,
6994
+ ts: this.#now(),
6995
+ operation: "hide_user_turn",
6996
+ anchorUserEventId,
6997
+ throughEventId,
6998
+ actor: "agent",
6999
+ ...reason ? { reason } : {}
7000
+ });
7001
+ await this.#appendDiagnostic(lane.session.header.sessionId, "context_snipped", {
7002
+ anchorUserEventId,
7003
+ throughEventId,
7004
+ hiddenEventCount: nextUserIndex - anchorIndex
7005
+ });
7006
+ return {
7007
+ anchorUserEventId,
7008
+ throughEventId,
7009
+ hiddenEventCount: nextUserIndex - anchorIndex
7010
+ };
7011
+ }
7012
+ #resolveSnipUserMessageId(lane, path, userMessageId) {
7013
+ if (path.includes(userMessageId)) {
7014
+ return userMessageId;
7015
+ }
7016
+ const matches = path.filter((id) => {
7017
+ const event = lane.session.tree.get(id)?.event;
7018
+ return event?.type === "user_message" && snipUserMessageAlias(id) === userMessageId;
7019
+ });
7020
+ if (matches.length === 1) {
7021
+ return matches[0];
7022
+ }
7023
+ if (matches.length > 1) {
7024
+ throw new Error(`snip target short id is ambiguous: ${userMessageId}`);
7025
+ }
7026
+ return asEventId(userMessageId);
6789
7027
  }
6790
7028
  async #selectChatRuntime(lane, modelSelection) {
6791
7029
  if (!modelSelection) {
@@ -7548,7 +7786,17 @@ var init_src4 = __esm({
7548
7786
  };
7549
7787
  countContentBlocks = (message, type) => message.content.filter((block) => block.type === type).length;
7550
7788
  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();
7789
+ snipUserMessageIdBlock = (userEventId) => ({
7790
+ ...createSystemReminderBlock({
7791
+ kind: "message_ref",
7792
+ origin: "system",
7793
+ text: `snip.userMessageId: ${snipUserMessageAlias(userEventId)}`,
7794
+ visibility: "model",
7795
+ scope: "message",
7796
+ data: { userMessageId: snipUserMessageAlias(userEventId) }
7797
+ })
7798
+ });
7799
+ inputText = (message) => message.content.flatMap((block) => block.type === "text" && block.visibility !== "model" ? [block.text] : []).join("\n").trim();
7552
7800
  assistantText = (message) => message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
7553
7801
  messageText = (message) => {
7554
7802
  const text = message.content.map((block) => {
@@ -7564,6 +7812,9 @@ var init_src4 = __esm({
7564
7812
  if (block.type === "tool_result") {
7565
7813
  return `[tool_result:${block.toolName}] ${JSON.stringify(block.result)}`;
7566
7814
  }
7815
+ if (block.type === "system_reminder") {
7816
+ return `[system_reminder:${block.kind}] ${block.text}`;
7817
+ }
7567
7818
  return "";
7568
7819
  }).filter(Boolean).join("\n").trim();
7569
7820
  return text || "(empty)";
@@ -7577,7 +7828,7 @@ var init_src4 = __esm({
7577
7828
  return void 0;
7578
7829
  }
7579
7830
  const parsed = JSON.parse(text);
7580
- if (!isRecord8(parsed)) {
7831
+ if (!isRecord9(parsed)) {
7581
7832
  return void 0;
7582
7833
  }
7583
7834
  return {
@@ -7665,7 +7916,7 @@ var init_src4 = __esm({
7665
7916
  }
7666
7917
  };
7667
7918
  parseRuntimeStats = (value) => {
7668
- if (!isRecord8(value) || !isRecord8(value.rtk)) {
7919
+ if (!isRecord9(value) || !isRecord9(value.rtk)) {
7669
7920
  return emptyRuntimeStats();
7670
7921
  }
7671
7922
  return {
@@ -7679,13 +7930,13 @@ var init_src4 = __esm({
7679
7930
  };
7680
7931
  };
7681
7932
  parseRuntimeStatsBuckets = (value) => {
7682
- if (!isRecord8(value)) {
7933
+ if (!isRecord9(value)) {
7683
7934
  return {};
7684
7935
  }
7685
7936
  return Object.fromEntries(
7686
7937
  Object.entries(value).map(([key, bucket]) => [
7687
7938
  key,
7688
- isRecord8(bucket) ? {
7939
+ isRecord9(bucket) ? {
7689
7940
  outputTokens: nonNegativeInteger2(bucket.outputTokens),
7690
7941
  savedTokens: nonNegativeInteger2(bucket.savedTokens)
7691
7942
  } : { outputTokens: 0, savedTokens: 0 }
@@ -7703,11 +7954,11 @@ var init_src4 = __esm({
7703
7954
  return bucket;
7704
7955
  };
7705
7956
  rtkSavingsFromToolResult = (result) => {
7706
- if (!isRecord8(result) || !isRecord8(result.details)) {
7957
+ if (!isRecord9(result) || !isRecord9(result.details)) {
7707
7958
  return void 0;
7708
7959
  }
7709
7960
  const rtk = result.details.rtk;
7710
- if (!isRecord8(rtk) || rtk.applied !== true) {
7961
+ if (!isRecord9(rtk) || rtk.applied !== true) {
7711
7962
  return void 0;
7712
7963
  }
7713
7964
  const outputTokens = nonNegativeInteger2(rtk.estimatedOutputTokens);
@@ -7752,7 +8003,7 @@ var init_src4 = __esm({
7752
8003
  ...context.data ? { data: context.data } : {}
7753
8004
  });
7754
8005
  parseQueuedChannelContext = (value) => {
7755
- if (!isRecord8(value)) {
8006
+ if (!isRecord9(value)) {
7756
8007
  return void 0;
7757
8008
  }
7758
8009
  if (typeof value.channel !== "string" || typeof value.externalConversationId !== "string") {
@@ -7764,11 +8015,11 @@ var init_src4 = __esm({
7764
8015
  ...typeof value.conversationType === "string" ? { conversationType: value.conversationType } : {},
7765
8016
  ...typeof value.senderDisplayName === "string" ? { senderDisplayName: value.senderDisplayName } : {},
7766
8017
  ...typeof value.mentionedBot === "boolean" ? { mentionedBot: value.mentionedBot } : {},
7767
- ...isRecord8(value.data) ? { data: value.data } : {}
8018
+ ...isRecord9(value.data) ? { data: value.data } : {}
7768
8019
  });
7769
8020
  };
7770
8021
  parseQueuedModelSelection = (value) => {
7771
- if (!isRecord8(value)) {
8022
+ if (!isRecord9(value)) {
7772
8023
  return void 0;
7773
8024
  }
7774
8025
  const selection = {};
@@ -7810,7 +8061,7 @@ var init_src4 = __esm({
7810
8061
  };
7811
8062
  isSteerMessage = (text) => /^\/(?:steer|interrupt)\b/i.test(text.trim());
7812
8063
  stripImCommandPrefix = (text) => text.trim().replace(/^\/(?:steer|interrupt)\s*/i, "").trim() || text;
7813
- isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8064
+ isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7814
8065
  parseMemoryUpdate = (raw) => {
7815
8066
  const text = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
7816
8067
  if (!text) {
@@ -8036,7 +8287,7 @@ var init_daemon_cli = __esm({
8036
8287
  DEFAULT_PORT = 7777;
8037
8288
  STOP_POLL_INTERVAL_MS = 200;
8038
8289
  STOP_GRACE_MS = 5e3;
8039
- START_READY_TIMEOUT_MS = 1e4;
8290
+ START_READY_TIMEOUT_MS = 3e4;
8040
8291
  AUTO_STARTED_IDLE_SHUTDOWN_MS = 15 * 60 * 1e3;
8041
8292
  FOREGROUND_IDLE_SHUTDOWN_MS = 0;
8042
8293
  defaultStateDir2 = () => join13(homedir6(), ".scorel");
@@ -9202,7 +9453,7 @@ var init_up_cli = __esm({
9202
9453
  init_daemon_cli();
9203
9454
  DEFAULT_DAEMON_PORT = 7777;
9204
9455
  DEFAULT_WEBUI_PORT = 3e3;
9205
- DEFAULT_DAEMON_READY_TIMEOUT_MS = 1e4;
9456
+ DEFAULT_DAEMON_READY_TIMEOUT_MS = 3e4;
9206
9457
  defaultStateDir3 = () => join16(homedir8(), ".scorel");
9207
9458
  defaultAttachSigint = (listener) => {
9208
9459
  process.on("SIGINT", listener);
@@ -9557,7 +9808,7 @@ __export(index_exports, {
9557
9808
  runChat: () => runChat,
9558
9809
  runCli: () => runCli
9559
9810
  });
9560
- import { createHash as createHash3 } from "node:crypto";
9811
+ import { createHash as createHash4 } from "node:crypto";
9561
9812
  import { appendFile as appendFile4, mkdir as mkdir8, readFile as readFile14, realpath as realpath3, readdir as readdir7, writeFile as writeFile8 } from "node:fs/promises";
9562
9813
  import { createInterface } from "node:readline/promises";
9563
9814
  import { homedir as homedir9 } from "node:os";
@@ -9879,11 +10130,11 @@ var init_index = __esm({
9879
10130
  };
9880
10131
  };
9881
10132
  attachCacheFilePath = (stateDir, scope, sessionId) => {
9882
- const scopeKey = createHash3("sha256").update(`${scope.kind}\0${scope.locator}`).digest("hex").slice(0, 24);
10133
+ const scopeKey = createHash4("sha256").update(`${scope.kind}\0${scope.locator}`).digest("hex").slice(0, 24);
9883
10134
  return join17(stateDir, "attach-cache", scopeKey, `${sessionId}.json`);
9884
10135
  };
9885
10136
  attachDiagnosticsFilePath = (stateDir, scope, sessionId) => {
9886
- const scopeKey = createHash3("sha256").update(`${scope.kind}\0${scope.locator}`).digest("hex").slice(0, 24);
10137
+ const scopeKey = createHash4("sha256").update(`${scope.kind}\0${scope.locator}`).digest("hex").slice(0, 24);
9887
10138
  return join17(stateDir, "attach-cache", scopeKey, `${sessionId}.log`);
9888
10139
  };
9889
10140
  findAttachDiagnosticsFilePath = async (stateDir, sessionId, _remoteUrl) => {
@@ -10406,7 +10657,18 @@ ${text}
10406
10657
  this.#atLineStart = text.endsWith("\n");
10407
10658
  }
10408
10659
  };
10409
- blocksToText = (blocks) => blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
10660
+ blocksToText = (blocks) => blocks.flatMap((block) => {
10661
+ if (block.type === "text") {
10662
+ if (block.visibility === "model") {
10663
+ return [];
10664
+ }
10665
+ return [block.text];
10666
+ }
10667
+ if (block.type === "system_reminder" && block.visibility !== "model") {
10668
+ return [block.text];
10669
+ }
10670
+ return [];
10671
+ }).join("");
10410
10672
  isCliEntrypoint = async () => {
10411
10673
  if (!process.argv[1]) return false;
10412
10674
  const [argvPath, modulePath] = await Promise.all([