@wowyuarm/dsh-agent-team 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +4 -11
  2. package/README.zh.md +4 -11
  3. package/package.json +54 -44
  4. package/packages/agent-team/README.md +7 -5
  5. package/packages/agent-team/README.zh.md +7 -4
  6. package/packages/agent-team/lib/attachments.js +14 -4
  7. package/packages/agent-team/lib/context-management.js +382 -0
  8. package/packages/agent-team/lib/context-projection.js +578 -0
  9. package/packages/agent-team/lib/context-source.js +212 -0
  10. package/packages/agent-team/lib/index.js +1272 -76
  11. package/packages/agent-team/lib/ledger.js +333 -44
  12. package/packages/agent-team/lib/member-context.js +6 -2
  13. package/packages/agent-team/lib/member-runtime.js +159 -4
  14. package/packages/agent-team/lib/member-time-context.js +126 -0
  15. package/packages/agent-team/lib/pressure-policy.js +199 -0
  16. package/packages/agent-team/lib/progress-nudge.js +12 -4
  17. package/packages/agent-team/lib/session-remediation.js +481 -0
  18. package/packages/agent-team/lib/spec.js +21 -2
  19. package/packages/agent-team/lib/time-format.js +56 -0
  20. package/packages/agent-team/lib/typert.host.js +168 -54
  21. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  22. package/packages/agent-team/lib/typert.remote-client.js +59 -25
  23. package/packages/agent-team/lib/types/attachments.d.ts +1 -1
  24. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
  25. package/packages/agent-team/lib/types/context-management.d.ts +147 -0
  26. package/packages/agent-team/lib/types/context-management.d.ts.map +1 -0
  27. package/packages/agent-team/lib/types/context-projection.d.ts +219 -0
  28. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -0
  29. package/packages/agent-team/lib/types/context-source.d.ts +117 -0
  30. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -0
  31. package/packages/agent-team/lib/types/index.d.ts +300 -2
  32. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  33. package/packages/agent-team/lib/types/ledger.d.ts +119 -6
  34. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  35. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  36. package/packages/agent-team/lib/types/member-runtime.d.ts +30 -2
  37. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
  38. package/packages/agent-team/lib/types/member-time-context.d.ts +83 -0
  39. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -0
  40. package/packages/agent-team/lib/types/pressure-policy.d.ts +106 -0
  41. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -0
  42. package/packages/agent-team/lib/types/progress-nudge.d.ts.map +1 -1
  43. package/packages/agent-team/lib/types/session-remediation.d.ts +149 -0
  44. package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
  45. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  46. package/packages/agent-team/lib/types/time-format.d.ts +28 -0
  47. package/packages/agent-team/lib/types/time-format.d.ts.map +1 -0
  48. package/packages/agent-team/lib/types/types/entities.d.ts +22 -1
  49. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  50. package/packages/agent-team/lib/types/types/operations.d.ts +38 -3
  51. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
  52. package/packages/agent-team/lib/types/types/requests-results.d.ts +58 -1
  53. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  54. package/packages/agent-team/preset/team-member/agent.cordis.yml +27 -4
  55. package/packages/client-agent-team/lib/client.js +369 -352
  56. package/packages/client-agent-team/lib/client.js.map +1 -1
  57. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +1 -2
  58. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  59. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +61 -41
  60. package/packages/client-agent-team/lib/types/client/TeamMessage.js +2 -2
  61. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +4 -4
  62. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  63. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  64. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -2
  65. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/index.js +44 -16
  67. package/packages/client-agent-team/lib/types/client/locales.d.ts +0 -14
  68. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  69. package/packages/client-agent-team/lib/types/client/locales.js +0 -14
  70. package/packages/client-agent-team/lib/types/client/slots.d.ts +6 -1
  71. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  72. package/packages/tool-agent-team/README.md +11 -6
  73. package/packages/tool-agent-team/README.zh.md +11 -6
  74. package/packages/tool-agent-team/lib/context-tools.js +179 -0
  75. package/packages/tool-agent-team/lib/index.js +369 -58
  76. package/packages/tool-agent-team/lib/types/context-tools.d.ts +16 -0
  77. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -0
  78. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  79. package/packages/agent-team/lib/auto-compaction.js +0 -201
  80. package/packages/agent-team/lib/types/auto-compaction.d.ts +0 -47
  81. package/packages/agent-team/lib/types/auto-compaction.d.ts.map +0 -1
@@ -6,15 +6,20 @@
6
6
 
7
7
  ## 工具
8
8
 
9
- - `team_inbox` 列出调用方 Member 的有界未读 Thread 摘要,先按 direct work、再按最近活动排序;只有 Thread 存在真实 Task overlay 时才出现 Task status/number。
10
- - `team_thread` 读取 Thread、分页历史、follow 或 unfollow。`threadRef` 是主身份;`taskRef` 只对 released task-only Client 是 Host alias。`read` 原子返回 Thread anchor、可选的当前 Task Claim 快照、有界背景和一批连续未读事实,并推进持久 watermark;`history` 不改变 read 状态。
11
- - `team_message` 创建顶层 Thread,或回复已有 Thread。它默认创建 taskless Thread;传入 `asTask: true` 才原子创建 Task。Reply 必须携带准确的 `baseRevision`,并在检查 revision 前先处理未读门禁。
12
- - `team_claim` 通过 `list`、`claim`、`done`、`release` 读取或修改调用方 Member 的 Direction Claim;它只适用于真实 TaskDirection 互斥键执行 Unicode NFKC normalization、trim、空白压缩和确定性大小写折叠。
13
- - `team_view` 发现有界、按 membership 授权的 Channel、真实 Task Member 摘要,不返回 Thread Message Activity
9
+ - `team_inbox` 列出调用方 Member 的有界未读 Thread 摘要,先按 direct work、再按最近活动排序;只有 Thread 存在真实 Task overlay 时才出现 Task standing。header 给出 unread/direct 总数与展示的 Threads 数(有界列表之外仍有未读时给出截断结论);每个条目显示精确的 unread/direct 计数、Channel ref 与 Task standing。页脚把阅读指向 `team_thread read`;渲染不携带 revision 与写令牌。
10
+ - `team_thread` 读取 Thread、分页历史、follow 或 unfollow。`threadRef` 是主身份;`taskRef` 只对 released task-only Client 是 Host alias。五个 action 分开渲染:`status`/`follow`/`unfollow` 一行只回答 Attention 问题;`read` 渲染结果、Thread 身份、导向(有 Host 提供的 background 时给 full anchor,否则给 bounded anchor subject,anchor 已在 facts 中时绝不重复)、只含 active Claims、带行内 unread/direct 标记的按时间 facts、read-through/剩余未读页脚;`history` 渲染历史页(首页 full anchor、continuation 页 bounded subject,绝无当前 Claims 或 advice)。`read` 推进持久 watermark,是 next-write token 的读侧唯一来源——仅在未读清零时渲染;你自己的已提交 public mutation 也会回传一份新 token。Activity facts 结构化渲染——actor、Task ref,以及每条 activity claim、完成、接受或 release 的 Claim refs。确认仍为 done 状态 Task 的未读验收的那次 read 附带一段 `contextAdvice`(用量、路由预算、`min(128_000, handoffAt)` 任务边界阈值、keep/rollover/handoff-now 唯一动作);它只是建议,测量失败时降级为显式 `unavailable` 文案。
11
+ - `team_message` 创建顶层 Thread、回复既有 Thread 或发送 DM。默认创建 taskless Thread;传入 `asTask: true` 才原子创建 Task。commit 的 start/reply 渲染一个 committed-verb 结果(Thread created / reply added),携带 Message、Thread 与可选 Task refs,以及恰好一个 next-write token hand-off。Reply 在检查令牌新鲜度前先拒绝未读工作。类型化拒绝以 `Not committed` 开头,保留重读与审慎重试所需的结构化 refs 与计数,不渲染数字 revision 或写令牌——恢复路径是 read-and-reconsider。DM 区分 `Delivered` 与 `Recorded, not delivered`(携带 delivery note 与禁止盲目重发的警告)。
12
+ - `team_claim` 通过 `list`、`claim`、`done`、`release` 读取或修改调用方 Member 的 Direction Claim;它只适用于真实 Task。`list` 渲染 Task/Thread 身份与只有 active Claims 的 collision surface,且无写令牌。commit 的 mutation 点名动作(Claim created/completed/released),先渲染权威的受影响 Claim,再 hand-off 一个 next-write token。拒绝与 message 拒绝共享同一形式。Direction 互斥键执行 Unicode NFKC normalization、trim、空白压缩和确定性大小写折叠。
13
+ - `team_view` address book:有界、按 membership 授权的 Channel、顶层 Thread Member 摘要——是当前的地址簿,不是工作队列。Threads 是唯一分页目录:newest-first 行携带 threadRef、Channel ref、bounded anchor subject 与行内 Task standing(绝无第二个 Task 索引,也不渲染 revision 或消息数)。cursor 只延续 Thread 行;continuation 页只渲染 Threads,翻页可达全部获授权的顶层 Threads,包括 taskless 与不在首页的 taskful
14
+ - `context_rollover` 为调用方 Member 安排一次进入全新上下文的 rollover。工具向 Host 校验有界的私有 `handoff`(及可选 `relatedFiles`;传入 `checkpointRef` 则改为回返到已记录的 checkpoint),结束当前 turn 并返回 `status: 'scheduled'`;Host 只在成功的 tool result 持久落盘后才执行真正的换窗。`checkpointRef` 在工具与参数两级 description 上做了 copy hardening:普通续代与压力换窗必须省略,只有引用 `context_timeline` 结果中列为 restorable 的精确 ref 时才提供——绝不合成或猜测。当下不可能成功的 `checkpointRef`——伪造、未 resolve、无法归属、不缩减、不可测量、超预算或被多个 active Claim 阻塞——以 model-visible 的 error result 拒绝,而不是返回假 `scheduled`;可变 guards(jobs、route limits)在换窗 seam 复查,seam 失败后 Member 仍可通过后续 turn 的显式 fresh rollover 恢复。`context_rollover` 与 `context_checkpoint` 都会结束 Agent turn(换窗后不得再接旧代工作;checkpoint 在其所属 turn 结束时 resolve)。
15
+ - `context_checkpoint` 为调用的 Member 记录一个命名的当前上下文 checkpoint。durable checkpoint 就是 Session projection 折叠的成功 `tool/call`+`tool/result` 对;返回的 ref 由 Member Session 身份加 tool call id 确定性派生。工具体不做 lifecycle 副作用,但会结束 turn:checkpoint 在其所属 turn 结束时 resolve,因此模型把它作为一个完整工作单元的最后动作来记录。
16
+ - `context_timeline` 返回该 Member 跨当前 Session 与已归档祖先 lineage 的上下文代际有界结构视图:已记录的 checkpoints 与 handoff/Team/compaction 边界,无法证明可安全回返的条目携带拒绝原因。仅结构信息——不含任何 transcript 正文。fresh 的 `context_rollover` 从不需要先查 timeline。
14
17
 
15
18
  Agent 不能通过 mention 静默把另一个 unfollowed Agent 加入 Thread;Host 返回 `member_not_following`。Human confirmation 属于单独的 Host/Client 流程。Closed Task 在 Human reopen 前拒绝 reply、Claim 和新的 Attention;taskless Thread 没有 Claim 或 Task resolution mutation path。
16
19
 
17
- Canonical result 包含稳定 refs、可选的 Task status、Thread revision、Claim history、Attention 和未读 facts。类型化的 `unread_required` `stale_revision` 结果包含重新读取和审慎重试所需字段。工具执行通过准确的 live `exec.agent` 解析 actor;参数不能选择或冒充 actor Workspace。写操作的 request identity 由 sessionId tool callId 派生。Team tools 将结果返回模型循环,不主动结束 turn
20
+ 所有 agent-facing 渲染都在固定的 Team 协调时区 UTC+8 下携带带显式偏移的绝对事件时刻(`2026-09-08T17:00:00+08:00`):`team_thread` fact 行与 anchor 为每条 fact 标注其提交 operation 的时刻,`team_inbox` 行携带 `newestOccurredAt`,`team_view` Thread 行携带 `lastActivityAt`,通知给出 `Occurred at:`,已提交变更从 receipt 渲染 `Committed at:`。同一存储时刻在任何重读路径中渲染完全一致;只渲染绝对 timestamp,绝不出现相对时间文案,且 sequencerevision——而非 wall-clock 时间——仍是顺序 authority
21
+
22
+ Canonical result 以结构化字段暴露稳定 refs、可选 Task status、Thread revision、Claim history、Attention 和未读 facts。模型可见的渲染遵循 action decision surface:写令牌只出现在未读清零的 `team_thread read` 与一次已提交的 public mutation 上;浏览类结果(`team_view`、`team_inbox`、status/follow/unfollow、`history`、`team_claim list`)与所有类型化拒绝都不渲染 revision 与令牌。类型化的 `unread_required` 与 `stale_revision` 结果保留重新读取和审慎重试所需的结构化字段。工具执行通过准确的 live `exec.agent` 解析 actor;参数不能选择或冒充 actor 或 Workspace。写操作的 request identity 由 sessionId 与 tool callId 派生。`context_rollover` 与 `context_checkpoint` 结束 Agent turn;其余 Team tools 将结果返回模型循环,不主动结束 turn。
18
23
 
19
24
  完整的已实现协议见 [`../../docs/team-collaboration.md`](../../docs/team-collaboration.md)。
20
25
 
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Model-facing context-management tools for Team Members. Thin adapters
3
+ * only: validation runs in the Host adapter, the successful result is the
4
+ * durable intent, and every lifecycle side effect — generation swap, Session
5
+ * creation, inbox handling — happens in the Host coordinator after the
6
+ * result is durably appended. `concludeTurn()` rides the success result of
7
+ * `context_rollover` and `context_checkpoint`, so sibling calls settle in model
8
+ * order before the turn closes.
9
+ * @module @wowyuarm/dsh-agent-team/context-tools
10
+ */
11
+ import { defineTool } from '@deepseek-ai/dsh-tools';
12
+ function service(agent) {
13
+ const host = agent.ctx.get('agentTeam');
14
+ if (host === undefined)
15
+ throw new Error('Agent Team Host is unavailable');
16
+ return host;
17
+ }
18
+ function member(agent) {
19
+ const current = service(agent).memberForAgent(agent);
20
+ if (current === undefined)
21
+ throw new Error('team tool requires an active Team Member');
22
+ return current;
23
+ }
24
+ const MAX_HANDOFF_CHARS = 32 * 1024;
25
+ const MAX_RELATED_FILES = 32;
26
+ const contextRollover = defineTool({
27
+ name: 'context_rollover',
28
+ description: 'context_rollover: end this context generation and continue as the same Team Member in a new one. Without checkpointRef the context starts fresh and empty, seeded only by your handoff — this is the default, cheapest path at context pressure, and the right choice for ordinary generation changes and pressure-driven handoffs. Omit checkpointRef unless you are deliberately returning to a restorable anchor you just selected from a context_timeline result: supply a checkpointRef only when that timeline listed it as restorable and you are citing its exact ref — never synthesize, guess, or reconstruct one; a fabricated ref rejects as a model-visible error. Write the handoff as one prose string covering: current objective and every active Thread/Claim; verified facts and evidence; inferences and unresolved conflicts; current external side effects and their verification state (files, git, jobs, browser state, remote calls); one explicit next step. A context change never rolls back any external effect — describe current state so the next generation can re-verify. Record anything worth keeping in your private memory/notes first. Collect or stop your background jobs before calling: a rollover is refused while jobs this Member owns are still running.',
29
+ parameters: {
30
+ handoff: { type: 'string', required: true, description: 'Prose handoff for the next context generation: objective, active Threads/Claims, verified facts, inferences, external side effects and their verification state, next step.' },
31
+ checkpointRef: { type: 'string', description: 'Optional. Omit for the default fresh rollover — ordinary generation changes and pressure-driven handoffs must not supply this. Provide it only to resume from a restorable anchor you just selected in a context_timeline result, citing that exact ref; never synthesize or guess a ref.' },
32
+ relatedFiles: { type: 'array', items: { type: 'object', additionalProperties: false, properties: { path: { type: 'string', required: true }, reason: { type: 'string', required: true } } }, description: 'Workspace paths the next generation should look at first, each with one reason.' },
33
+ },
34
+ output: {
35
+ schema: { type: 'object', additionalProperties: false, properties: {
36
+ mode: { type: 'string', required: true }, status: { type: 'string', required: true },
37
+ } },
38
+ render: (_args, value) => [{ type: 'text', text: `Context rollover scheduled (${value.mode}). Finish this turn; the Host switches you to the next context generation afterward.` }],
39
+ },
40
+ async execute(args, exec) {
41
+ const agent = exec.agent;
42
+ if (agent === undefined)
43
+ throw new Error('context_rollover requires an Agent session');
44
+ const current = member(agent);
45
+ const host = service(agent);
46
+ const handoff = typeof args.handoff === 'string' ? args.handoff : '';
47
+ if (handoff.trim() === '')
48
+ throw new Error('context_rollover requires a non-empty handoff');
49
+ if (handoff.length > MAX_HANDOFF_CHARS)
50
+ throw new Error(`context_rollover handoff exceeds ${MAX_HANDOFF_CHARS} characters`);
51
+ const relatedFilesInput = Array.isArray(args.relatedFiles) ? args.relatedFiles : [];
52
+ if (relatedFilesInput.length > MAX_RELATED_FILES)
53
+ throw new Error(`context_rollover accepts at most ${MAX_RELATED_FILES} related files`);
54
+ // Tool argument validation is layered: the Harness schema (required and
55
+ // type checks) rejects at the execute boundary, and this body adds the
56
+ // checks the schema cannot express — each related file is validated
57
+ // here, so a blank path/reason rejects instead of seeding the handoff
58
+ // envelope with empty fields.
59
+ const relatedFiles = [];
60
+ for (const [index, entry] of relatedFilesInput.entries()) {
61
+ if (typeof entry !== 'object' || entry === null)
62
+ throw new Error(`context_rollover relatedFiles[${index}] must be an object with path and reason`);
63
+ const candidate = entry;
64
+ if (typeof candidate.path !== 'string' || candidate.path.trim() === '')
65
+ throw new Error(`context_rollover relatedFiles[${index}].path must be a non-empty string`);
66
+ if (typeof candidate.reason !== 'string' || candidate.reason.trim() === '')
67
+ throw new Error(`context_rollover relatedFiles[${index}].reason must be a non-empty string`);
68
+ relatedFiles.push({ path: candidate.path, reason: candidate.reason });
69
+ }
70
+ // Tool schemas are open at the root (Harness parameter specs set no
71
+ // `additionalProperties: false`), so an undeclared shape can still reach
72
+ // the body. Any supplied value that is not a non-empty string rejects
73
+ // here rather than being treated as absent — an absent ref means fresh,
74
+ // which is not what the model asked for.
75
+ const raw = args;
76
+ const suppliedRef = Object.hasOwn(raw, 'checkpointRef') ? raw.checkpointRef : undefined;
77
+ if (suppliedRef !== undefined && (typeof suppliedRef !== 'string' || suppliedRef.trim() === '')) {
78
+ throw new Error('context_rollover checkpointRef must be a non-empty string when supplied');
79
+ }
80
+ const checkpointRef = typeof suppliedRef === 'string' ? suppliedRef.trim() : undefined;
81
+ const outcome = await host.requestNewContext(agent, {
82
+ memberId: current.memberId,
83
+ ...(checkpointRef === undefined || checkpointRef === '' ? {} : { checkpointRef: checkpointRef }),
84
+ ...(relatedFiles.length === 0 ? {} : { relatedFiles }),
85
+ });
86
+ exec.concludeTurn();
87
+ return { mode: outcome.mode, status: 'scheduled' };
88
+ },
89
+ });
90
+ const contextCheckpoint = defineTool({
91
+ name: 'context_checkpoint',
92
+ description: 'Record a named checkpoint at the end of the current turn: an opaque, private, restorable anchor for this Member\'s context lineage. Use it before a noisy or risky phase — a broad refactor, an experiment whose value is unproven — when returning to the current completed state may later be useful. The checkpoint resolves only when this turn completes; the Host continues work in the next turn automatically. A checkpoint never snapshots files, git, jobs, or any external state: returning to one (via context_rollover with its checkpointRef) resumes the conversation prefix and nothing else. Checkpoints are private context structure, not Team facts, and are never visible to other Members.',
93
+ parameters: {
94
+ name: { type: 'string', required: true, description: 'Short semantic label for this checkpoint, shown in context_timeline.' },
95
+ },
96
+ output: {
97
+ schema: { type: 'object', additionalProperties: false, properties: {
98
+ checkpointRef: { type: 'string', required: true }, name: { type: 'string', required: true },
99
+ } },
100
+ // The ref is the selection surface for `context_rollover`: rendering only the
101
+ // name left the model with no legitimate way to cite the anchor it just
102
+ // recorded. Renders are the only channel results reach the model through.
103
+ render: (_args, value) => [{ type: 'text', text: `Checkpoint recorded: ${value.name} (ref: ${value.checkpointRef}). Work continues in the next turn; the Host will continue automatically.` }],
104
+ },
105
+ async execute(args, exec) {
106
+ const agent = exec.agent;
107
+ if (agent === undefined)
108
+ throw new Error('context_checkpoint requires an Agent session');
109
+ const current = member(agent);
110
+ const host = service(agent);
111
+ const name = typeof args.name === 'string' ? args.name : '';
112
+ // The Host validates binding, running-turn fencing, and the name budget;
113
+ // the durable checkpoint is the successful call/result pair the Session
114
+ // projection folds, and the ref derives from the tool call id.
115
+ const outcome = host.recordCheckpointForAgent(agent, { memberId: current.memberId, callId: exec.callId, name });
116
+ exec.concludeTurn();
117
+ return { checkpointRef: outcome.checkpointRef, name: outcome.name };
118
+ },
119
+ });
120
+ const contextTimeline = defineTool({
121
+ name: 'context_timeline',
122
+ description: 'Inspect the bounded structural timeline of this Member\'s context lineage: named checkpoints you recorded, Team boundaries (effect anchors: a committed team_message, a successful team_claim mutation, a follow/unfollow — rendered as `Team message`, `Team task claim change`, `Team attention change`; plus a Thread\'s first delivered notice, rendered as `First arrival: <refs>`), handoff and compaction boundaries, and the current head — across the current generation and its archived ancestors. Only the first delivery of a Thread\'s facts anchors; later re-deliveries and reminders produce no boundary. Returns approximate retained/discarded token estimates, current usage against the pressure budget, the Threads whose facts entered your context by each anchor, and which anchors are restorable. A Team boundary is a selectable default checkpoint exactly when it resolved at a completed turn and exactly one Thread is attributable to it; unattributable or multi-Thread boundaries state their reason. Structural only: no transcript content. A fresh context_rollover (no checkpointRef) never requires consulting this timeline first — call it directly. Use this tool only when you specifically intend a checkpointRef return: to pick the smallest sufficient ref, or to confirm that a fresh handoff is the better path when every anchor is marked non-restorable.',
123
+ parameters: {
124
+ limit: { type: 'number', description: 'Maximum number of items to return (default 12, at most 24).' },
125
+ },
126
+ output: {
127
+ schema: { type: 'object', additionalProperties: false, properties: {
128
+ usageTokens: { type: 'number', required: true },
129
+ hardLimit: { type: 'number', required: true },
130
+ handoffAt: { type: 'number', required: true },
131
+ items: { type: 'array', required: true, items: { type: 'object', additionalProperties: false, properties: {
132
+ checkpointRef: { type: 'string', required: true },
133
+ name: { type: 'string', required: true },
134
+ source: { type: 'string', required: true },
135
+ retainedTokens: { type: 'number', required: true },
136
+ discardedTokens: { type: 'number', required: true },
137
+ affectedThreads: { type: 'array', required: true, items: { type: 'string' } },
138
+ restorable: { type: 'boolean', required: true },
139
+ reason: { type: 'string' },
140
+ sourceSessionId: { type: 'string' },
141
+ } } },
142
+ } },
143
+ // The item list is the whole decision surface: without each anchor's
144
+ // ref, label, source, size estimates, affected Threads, and
145
+ // restorable/reason verdict, the model cannot pick a `checkpointRef` for
146
+ // `context_rollover` — the summary line alone left the tool unusable for
147
+ // seeded returns. The Host bounds items (default 12, at most 24), so this
148
+ // list cannot grow unbounded.
149
+ render: (_args, value) => {
150
+ const lines = [`Context timeline: ${value.usageTokens} tokens used (handoff at ${value.handoffAt}, hard limit ${value.hardLimit}). ${value.items.length} item(s):`];
151
+ for (const item of value.items) {
152
+ const threads = item.affectedThreads.length === 0 ? 'no Threads' : `Threads ${item.affectedThreads.join(', ')}`;
153
+ const size = `retained ~${item.retainedTokens}, discarded ~${item.discardedTokens}`;
154
+ const restorable = item.restorable
155
+ ? `restorable — ref: ${item.checkpointRef}`
156
+ : `not restorable — ${item.reason ?? 'no reason given'}`;
157
+ lines.push(`- ${item.name} [source: ${item.source}] (${size}; ${threads}) — ${restorable}`);
158
+ }
159
+ return [{ type: 'text', text: lines.join('\n') }];
160
+ },
161
+ },
162
+ async execute(args, exec) {
163
+ const agent = exec.agent;
164
+ if (agent === undefined)
165
+ throw new Error('context_timeline requires an Agent session');
166
+ const current = member(agent);
167
+ const host = service(agent);
168
+ const limit = typeof args.limit === 'number' ? args.limit : undefined;
169
+ const result = await host.contextTimelineForAgent(agent, { memberId: current.memberId, ...(limit === undefined ? {} : { limit }) });
170
+ // The Host result is deeply immutable; the tool output contract carries
171
+ // plain arrays, so re-shape without any semantic change.
172
+ return { usageTokens: result.usageTokens, hardLimit: result.hardLimit, handoffAt: result.handoffAt, items: result.items.map(item => ({ ...item, affectedThreads: [...item.affectedThreads] })) };
173
+ },
174
+ });
175
+ export function registerContextTools(ctx) {
176
+ ctx.tools.register(contextRollover);
177
+ ctx.tools.register(contextCheckpoint);
178
+ ctx.tools.register(contextTimeline);
179
+ }