@chanlerdev/scorel 0.0.6 → 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/README.md CHANGED
@@ -206,7 +206,7 @@ Session 是 append-only JSONL。Host 是唯一 writer,Entry 不直接写会话
206
206
  一次会话里会写入这些事件:
207
207
 
208
208
  - `instruction_snapshot`:Session 初始化时冻结的系统提示词来源。
209
- - `harness_item`:系统注入给模型的上下文,例如 memory、skill listing、steer。
209
+ - `harness_item`:运行中或 session 级注入给模型的上下文,例如 memory、skill listing、steer;进入模型前会转成结构化 `system_reminder` block
210
210
  - `user_message`:用户输入。
211
211
  - `assistant_message`:模型输出。
212
212
  - `tool_result`:工具执行结果。
@@ -215,7 +215,9 @@ Session 是 append-only JSONL。Host 是唯一 writer,Entry 不直接写会话
215
215
  - `queue_update`:follow-up / steer 队列状态。
216
216
  - `skill_index_snapshot` / `skill_index_delta`:Skill 路由表。
217
217
 
218
- JSONL 是恢复会话的事实来源。`buildContext()` 从事件树构造下一轮 LLM messages;遇到 `compact` 时,会用 compact summary 替换更早上下文,并最多保留最近 8 条从 `user_message`、`compact` 或带 `tool_call` 的 `assistant_message` 边界开始的原始 conversation events;遇到 `context_control operation="hide_user_turn"` 时,会从未来模型上下文中过滤对应 user turn span。UI 则从同一条事件流投影 transcript、工具块、运行状态和队列状态。
218
+ JSONL 是恢复会话的事实来源。`buildContext()` 从事件树构造下一轮 canonical LLM messages;遇到 `compact` 时,会用 `system_reminder kind="compact_summary"` 替换更早上下文,并最多保留最近 8 条从 `user_message`、`compact` 或带 `tool_call` 的 `assistant_message` 边界开始的原始 conversation events;遇到 `context_control operation="hide_user_turn"` 时,会从未来模型上下文中过滤对应 user turn span。UI 则从同一条事件流投影 transcript、工具块、运行状态和队列状态。
219
+
220
+ `system_reminder` 是结构化 content block,不是 JSONL 事件类型,也不是 provider-level system prompt。它可以作为 user message 的 model-only sidecar(例如当前 turn 信息、`snip.userMessageId`、用户引用),也可以由 `harness_item` / `compact` 在 `buildContext()` 时产生。Provider adapter 最后才把它 lower 成 `<system-reminder>...</system-reminder>` 文本;GUI / WebUI / CLI 只按 block type 和 `visibility` 决定展示,不解析 XML。
219
221
 
220
222
  这个设计的重点不是“保存聊天记录”,而是让 Agent 的输入、输出、工具调用、控制事件和恢复状态都落在同一条可检查的链路里。
221
223
 
@@ -258,7 +260,7 @@ Memory 的链路分三段:
258
260
 
259
261
  Auto compact 是当前 session 的上下文管理,不是长期 memory。Host 在每轮新用户消息写入前估算 context,默认达到模型窗口 80% 时追加 `compact` 事件。`compact` 优先使用后台维护的 session memory;如果 session memory 不存在或关闭,会降级到 foreground compact。后台 session memory 正在更新时,Host 最多等待 5 秒,然后继续执行,不让用户 turn 无限等待。
260
262
 
261
- `snip` 是另一种当前 session 的上下文管理:Host 创建每条 `user_message` 时会持久写入一个 model-only 的短 `snip.userMessageId`,UI 不展示,但模型能用它通过 Host 注册的 `snip` 工具请求隐藏其中一个已完成 user turn。因为这个 id 随消息创建时固定下来,后续 `buildContext()` replay 不会动态改写历史 user message,也不会破坏 prompt cache 前缀。Host 会校验目标 `user_message` 在当前 active path 上,并 append `context_control` 事件;原始 JSONL 不删除、不重写,只是后续 `buildContext()` 不再把该 span 放进模型输入。
263
+ `snip` 是另一种当前 session 的上下文管理:Host 创建每条 `user_message` 时会持久写入一个 model-only `system_reminder kind="message_ref"`,内容包含短 `snip.userMessageId`。UI 不展示,但模型能用它通过 Host 注册的 `snip` 工具请求隐藏其中一个已完成 user turn。因为这个 id 随消息创建时固定下来,后续 `buildContext()` replay 不会动态改写历史 user message,也不会破坏 prompt cache 前缀。Host 会校验目标 `user_message` 在当前 active path 上,并 append `context_control` 事件;原始 JSONL 不删除、不重写,只是后续 `buildContext()` 不再把该 span 放进模型输入。
262
264
 
263
265
  Session memory 写在:
264
266
 
package/dist/index.js CHANGED
@@ -2762,12 +2762,7 @@ var init_tools = __esm({
2762
2762
  return {
2763
2763
  content: [{
2764
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(" ")
2765
+ text: "Snipped the selected user turn. It will be omitted from future model context."
2771
2766
  }],
2772
2767
  details: result
2773
2768
  };
@@ -3419,15 +3414,65 @@ var init_memory = __esm({
3419
3414
  }
3420
3415
  });
3421
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
+
3422
3466
  // packages/core/src/provider/pi-ai.ts
3423
3467
  import {
3424
3468
  getModels,
3425
3469
  streamSimple
3426
3470
  } from "@mariozechner/pi-ai";
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;
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;
3428
3472
  var init_pi_ai = __esm({
3429
3473
  "packages/core/src/provider/pi-ai.ts"() {
3430
3474
  "use strict";
3475
+ init_reminders();
3431
3476
  DEFAULT_CUSTOM_MODEL_CONTEXT_WINDOW = 2e5;
3432
3477
  DEFAULT_CUSTOM_MODEL_MAX_TOKENS = 64e3;
3433
3478
  createPiAiProvider = (options) => ({
@@ -3526,6 +3571,9 @@ var init_pi_ai = __esm({
3526
3571
  if (block.type === "text") {
3527
3572
  return [{ type: "text", text: block.text }];
3528
3573
  }
3574
+ if (block.type === "system_reminder") {
3575
+ return [{ type: "text", text: renderSystemReminder(block) }];
3576
+ }
3529
3577
  if (block.type === "thinking") {
3530
3578
  return [{ type: "thinking", thinking: block.text }];
3531
3579
  }
@@ -3564,16 +3612,33 @@ var init_pi_ai = __esm({
3564
3612
  description: tool.description,
3565
3613
  parameters: tool.parameters
3566
3614
  });
3567
- textContent = (message) => message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
3615
+ textContent = (message) => message.content.flatMap((block) => {
3616
+ if (block.type === "text") {
3617
+ return [block.text];
3618
+ }
3619
+ if (block.type === "system_reminder") {
3620
+ return [renderSystemReminder(block)];
3621
+ }
3622
+ return [];
3623
+ }).join("\n");
3568
3624
  toolResultText = (result) => {
3569
3625
  if (typeof result === "object" && result !== null && "content" in result) {
3570
3626
  const content = result.content;
3571
3627
  if (Array.isArray(content)) {
3572
- 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");
3573
3637
  }
3574
3638
  }
3575
3639
  return JSON.stringify(result);
3576
3640
  };
3641
+ isSystemReminderContentBlock = (value) => typeof value === "object" && value !== null && value.type === "system_reminder" && typeof value.text === "string";
3577
3642
  stringMeta = (message, key) => {
3578
3643
  const value = message.meta?.[key];
3579
3644
  return typeof value === "string" ? value : void 0;
@@ -3855,11 +3920,12 @@ function assertTreeEvent(value) {
3855
3920
  throw new SessionStoreError("invalid_event", "skill_index_delta is missing delta payload");
3856
3921
  }
3857
3922
  }
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;
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;
3859
3924
  var init_session = __esm({
3860
3925
  "packages/core/src/session/index.ts"() {
3861
3926
  "use strict";
3862
3927
  init_src();
3928
+ init_reminders();
3863
3929
  snipUserMessageAlias = (eventId) => `u_${createHash2("sha256").update(eventId).digest("hex").slice(0, 8)}`;
3864
3930
  SessionStoreError = class extends Error {
3865
3931
  code;
@@ -4214,65 +4280,74 @@ var init_session = __esm({
4214
4280
  );
4215
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";
4216
4282
  appendHarnessItemToContext = (messages, event) => {
4217
- 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
+ });
4218
4291
  const last = messages.at(-1);
4219
- if (last?.role === "tool_result" && appendReminderToToolResult(last, reminder)) {
4292
+ if (last?.role === "tool_result" && appendSystemReminderToToolResult(last, reminder)) {
4220
4293
  return;
4221
4294
  }
4222
- messages.push({
4223
- role: "user",
4224
- content: [{ type: "text", text: reminder }],
4225
- meta: {
4226
- source: "harness_item",
4227
- harnessKind: event.item.kind,
4228
- harnessOrigin: event.item.origin
4229
- }
4230
- });
4231
- };
4232
- appendReminderToToolResult = (message, reminder) => {
4233
- for (let i = message.content.length - 1; i >= 0; i -= 1) {
4234
- const block = message.content[i];
4235
- if (block?.type !== "tool_result" || !isToolResultWithContent(block.result)) {
4236
- continue;
4237
- }
4238
- const mergedResult = {
4239
- ...block.result,
4240
- content: [...block.result.content, { type: "text", text: `
4241
-
4242
- ${reminder}` }]
4243
- };
4244
- message.content[i] = {
4245
- ...block,
4246
- result: mergedResult
4247
- };
4248
- return true;
4249
- }
4250
- return false;
4295
+ messages.push(systemReminderMessage(reminder, {
4296
+ source: "harness_item",
4297
+ harnessKind: event.item.kind,
4298
+ harnessOrigin: event.item.origin
4299
+ }));
4251
4300
  };
4252
- isToolResultWithContent = (value) => isRecord8(value) && Array.isArray(value.content);
4253
- renderSystemReminder = (content) => `<system-reminder>
4254
- ${content}
4255
- </system-reminder>`;
4256
4301
  compactSummaryMessage = (event) => ({
4257
4302
  role: "user",
4258
- content: [{
4259
- type: "text",
4260
- text: renderSystemReminder([
4303
+ content: [createSystemReminderBlock({
4304
+ kind: "compact_summary",
4305
+ origin: "system",
4306
+ text: [
4261
4307
  "Earlier session context has been compacted.",
4262
4308
  "",
4263
4309
  event.summary.trim(),
4264
4310
  "",
4265
4311
  "Use this summary as continuity context. Verify current repository facts before acting."
4266
- ].join("\n"))
4267
- }],
4312
+ ].join("\n"),
4313
+ visibility: "model",
4314
+ scope: "session"
4315
+ })],
4268
4316
  meta: {
4269
4317
  source: "compact",
4270
4318
  compactedThrough: event.compactedThrough
4271
4319
  }
4272
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
+ };
4273
4345
  cloneMessage = (message) => ({
4274
4346
  ...message,
4275
4347
  content: message.content.map((block) => {
4348
+ if (block.type === "system_reminder") {
4349
+ return cloneSystemReminderBlock(block);
4350
+ }
4276
4351
  if (block.type !== "tool_result" || !isRecord8(block.result)) {
4277
4352
  return { ...block };
4278
4353
  }
@@ -4532,6 +4607,7 @@ var init_src3 = __esm({
4532
4607
  init_instructions();
4533
4608
  init_memory();
4534
4609
  init_pi_ai();
4610
+ init_reminders();
4535
4611
  init_runtime();
4536
4612
  init_session();
4537
4613
  init_skills();
@@ -7711,11 +7787,14 @@ var init_src4 = __esm({
7711
7787
  countContentBlocks = (message, type) => message.content.filter((block) => block.type === type).length;
7712
7788
  normalizeContent = (content) => typeof content === "string" ? [{ type: "text", text: content }] : content;
7713
7789
  snipUserMessageIdBlock = (userEventId) => ({
7714
- type: "text",
7715
- text: `<system-reminder>
7716
- snip.userMessageId: ${snipUserMessageAlias(userEventId)}
7717
- </system-reminder>`,
7718
- visibility: "model"
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
+ })
7719
7798
  });
7720
7799
  inputText = (message) => message.content.flatMap((block) => block.type === "text" && block.visibility !== "model" ? [block.text] : []).join("\n").trim();
7721
7800
  assistantText = (message) => message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim();
@@ -7733,6 +7812,9 @@ snip.userMessageId: ${snipUserMessageAlias(userEventId)}
7733
7812
  if (block.type === "tool_result") {
7734
7813
  return `[tool_result:${block.toolName}] ${JSON.stringify(block.result)}`;
7735
7814
  }
7815
+ if (block.type === "system_reminder") {
7816
+ return `[system_reminder:${block.kind}] ${block.text}`;
7817
+ }
7736
7818
  return "";
7737
7819
  }).filter(Boolean).join("\n").trim();
7738
7820
  return text || "(empty)";
@@ -10575,7 +10657,18 @@ ${text}
10575
10657
  this.#atLineStart = text.endsWith("\n");
10576
10658
  }
10577
10659
  };
10578
- 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("");
10579
10672
  isCliEntrypoint = async () => {
10580
10673
  if (!process.argv[1]) return false;
10581
10674
  const [argvPath, modulePath] = await Promise.all([