@te-river/opencode-team-mode 1.5.15 → 1.6.1

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 (196) hide show
  1. package/README.md +853 -690
  2. package/README.zh-CN.md +761 -631
  3. package/dist/agents.d.ts +5 -3
  4. package/dist/agents.d.ts.map +1 -1
  5. package/dist/agents.js +19 -8
  6. package/dist/agents.js.map +1 -1
  7. package/dist/capabilities.d.ts +1 -1
  8. package/dist/capabilities.js +2 -2
  9. package/dist/capabilities.js.map +1 -1
  10. package/dist/host/note.d.ts +7 -0
  11. package/dist/host/note.d.ts.map +1 -0
  12. package/dist/host/note.js +28 -0
  13. package/dist/host/note.js.map +1 -0
  14. package/dist/host/v1.d.ts +14 -0
  15. package/dist/host/v1.d.ts.map +1 -0
  16. package/dist/host/v1.js +340 -0
  17. package/dist/host/v1.js.map +1 -0
  18. package/dist/host/v2-browser-gate.d.ts +45 -0
  19. package/dist/host/v2-browser-gate.d.ts.map +1 -0
  20. package/dist/host/v2-browser-gate.js +319 -0
  21. package/dist/host/v2-browser-gate.js.map +1 -0
  22. package/dist/host/v2-capabilities.d.ts +68 -0
  23. package/dist/host/v2-capabilities.d.ts.map +1 -0
  24. package/dist/host/v2-capabilities.js +146 -0
  25. package/dist/host/v2-capabilities.js.map +1 -0
  26. package/dist/host/v2-client.d.ts +53 -0
  27. package/dist/host/v2-client.d.ts.map +1 -0
  28. package/dist/host/v2-client.js +193 -0
  29. package/dist/host/v2-client.js.map +1 -0
  30. package/dist/host/v2-events.d.ts +56 -0
  31. package/dist/host/v2-events.d.ts.map +1 -0
  32. package/dist/host/v2-events.js +158 -0
  33. package/dist/host/v2-events.js.map +1 -0
  34. package/dist/host/v2-guard.d.ts +101 -0
  35. package/dist/host/v2-guard.d.ts.map +1 -0
  36. package/dist/host/v2-guard.js +199 -0
  37. package/dist/host/v2-guard.js.map +1 -0
  38. package/dist/host/v2-offload.d.ts +148 -0
  39. package/dist/host/v2-offload.d.ts.map +1 -0
  40. package/dist/host/v2-offload.js +288 -0
  41. package/dist/host/v2-offload.js.map +1 -0
  42. package/dist/host/v2-permissions.d.ts +123 -0
  43. package/dist/host/v2-permissions.d.ts.map +1 -0
  44. package/dist/host/v2-permissions.js +190 -0
  45. package/dist/host/v2-permissions.js.map +1 -0
  46. package/dist/host/v2-probe.d.ts +85 -0
  47. package/dist/host/v2-probe.d.ts.map +1 -0
  48. package/dist/host/v2-probe.js +268 -0
  49. package/dist/host/v2-probe.js.map +1 -0
  50. package/dist/host/v2-scope.d.ts +51 -0
  51. package/dist/host/v2-scope.d.ts.map +1 -0
  52. package/dist/host/v2-scope.js +58 -0
  53. package/dist/host/v2-scope.js.map +1 -0
  54. package/dist/host/v2-session-client.d.ts +74 -0
  55. package/dist/host/v2-session-client.d.ts.map +1 -0
  56. package/dist/host/v2-session-client.js +171 -0
  57. package/dist/host/v2-session-client.js.map +1 -0
  58. package/dist/host/v2-session.d.ts +85 -0
  59. package/dist/host/v2-session.d.ts.map +1 -0
  60. package/dist/host/v2-session.js +171 -0
  61. package/dist/host/v2-session.js.map +1 -0
  62. package/dist/host/v2-subagent.d.ts +157 -0
  63. package/dist/host/v2-subagent.d.ts.map +1 -0
  64. package/dist/host/v2-subagent.js +286 -0
  65. package/dist/host/v2-subagent.js.map +1 -0
  66. package/dist/host/v2-tool.d.ts +67 -0
  67. package/dist/host/v2-tool.d.ts.map +1 -0
  68. package/dist/host/v2-tool.js +209 -0
  69. package/dist/host/v2-tool.js.map +1 -0
  70. package/dist/host/v2-transcript.d.ts +73 -0
  71. package/dist/host/v2-transcript.d.ts.map +1 -0
  72. package/dist/host/v2-transcript.js +151 -0
  73. package/dist/host/v2-transcript.js.map +1 -0
  74. package/dist/host/v2-types.d.ts +229 -0
  75. package/dist/host/v2-types.d.ts.map +1 -0
  76. package/dist/host/v2-types.js +17 -0
  77. package/dist/host/v2-types.js.map +1 -0
  78. package/dist/host/v2.d.ts +42 -0
  79. package/dist/host/v2.d.ts.map +1 -0
  80. package/dist/host/v2.js +706 -0
  81. package/dist/host/v2.js.map +1 -0
  82. package/dist/host-hooks.d.ts +13 -2
  83. package/dist/host-hooks.d.ts.map +1 -1
  84. package/dist/host-hooks.js +18 -5
  85. package/dist/host-hooks.js.map +1 -1
  86. package/dist/index.d.ts +23 -17
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +29 -326
  89. package/dist/index.js.map +1 -1
  90. package/dist/prompts/lead.d.ts +1 -1
  91. package/dist/prompts/lead.d.ts.map +1 -1
  92. package/dist/prompts/lead.js +452 -359
  93. package/dist/prompts/lead.js.map +1 -1
  94. package/dist/prompts/shared.d.ts +2 -2
  95. package/dist/prompts/shared.d.ts.map +1 -1
  96. package/dist/prompts/shared.js +89 -17
  97. package/dist/prompts/shared.js.map +1 -1
  98. package/dist/prompts/specialists.d.ts +2 -2
  99. package/dist/prompts/specialists.d.ts.map +1 -1
  100. package/dist/prompts/specialists.js +44 -10
  101. package/dist/prompts/specialists.js.map +1 -1
  102. package/dist/task-offload.d.ts +83 -0
  103. package/dist/task-offload.d.ts.map +1 -0
  104. package/dist/task-offload.js +174 -0
  105. package/dist/task-offload.js.map +1 -0
  106. package/dist/tm/args-schema.d.ts +4 -0
  107. package/dist/tm/args-schema.d.ts.map +1 -1
  108. package/dist/tm/args-schema.js +36 -3
  109. package/dist/tm/args-schema.js.map +1 -1
  110. package/dist/tm/board.d.ts +113 -0
  111. package/dist/tm/board.d.ts.map +1 -0
  112. package/dist/tm/board.js +275 -0
  113. package/dist/tm/board.js.map +1 -0
  114. package/dist/tm/browser.d.ts +325 -6
  115. package/dist/tm/browser.d.ts.map +1 -1
  116. package/dist/tm/browser.js +1360 -135
  117. package/dist/tm/browser.js.map +1 -1
  118. package/dist/tm/config.d.ts +36 -25
  119. package/dist/tm/config.d.ts.map +1 -1
  120. package/dist/tm/config.js +29 -7
  121. package/dist/tm/config.js.map +1 -1
  122. package/dist/tm/dispatch.d.ts +133 -53
  123. package/dist/tm/dispatch.d.ts.map +1 -1
  124. package/dist/tm/dispatch.js +481 -279
  125. package/dist/tm/dispatch.js.map +1 -1
  126. package/dist/tm/dupe-guard.d.ts +16 -3
  127. package/dist/tm/dupe-guard.d.ts.map +1 -1
  128. package/dist/tm/dupe-guard.js +27 -7
  129. package/dist/tm/dupe-guard.js.map +1 -1
  130. package/dist/tm/egress.d.ts +44 -0
  131. package/dist/tm/egress.d.ts.map +1 -0
  132. package/dist/tm/egress.js +191 -0
  133. package/dist/tm/egress.js.map +1 -0
  134. package/dist/tm/guard.js +1 -1
  135. package/dist/tm/guard.js.map +1 -1
  136. package/dist/tm/index.d.ts +67 -2
  137. package/dist/tm/index.d.ts.map +1 -1
  138. package/dist/tm/index.js +228 -20
  139. package/dist/tm/index.js.map +1 -1
  140. package/dist/tm/ledger.d.ts +114 -0
  141. package/dist/tm/ledger.d.ts.map +1 -0
  142. package/dist/tm/ledger.js +308 -0
  143. package/dist/tm/ledger.js.map +1 -0
  144. package/dist/tm/perm-ask.d.ts +45 -5
  145. package/dist/tm/perm-ask.d.ts.map +1 -1
  146. package/dist/tm/perm-ask.js +107 -7
  147. package/dist/tm/perm-ask.js.map +1 -1
  148. package/dist/tm/pipelines.d.ts.map +1 -1
  149. package/dist/tm/pipelines.js +24 -2
  150. package/dist/tm/pipelines.js.map +1 -1
  151. package/dist/tm/preview.d.ts +55 -0
  152. package/dist/tm/preview.d.ts.map +1 -1
  153. package/dist/tm/preview.js +225 -0
  154. package/dist/tm/preview.js.map +1 -1
  155. package/dist/tm/ptc/tool.d.ts.map +1 -1
  156. package/dist/tm/ptc/tool.js +2 -4
  157. package/dist/tm/ptc/tool.js.map +1 -1
  158. package/dist/tm/pty.d.ts.map +1 -1
  159. package/dist/tm/pty.js +6 -3
  160. package/dist/tm/pty.js.map +1 -1
  161. package/dist/tm/search.d.ts +5 -0
  162. package/dist/tm/search.d.ts.map +1 -1
  163. package/dist/tm/search.js +40 -8
  164. package/dist/tm/search.js.map +1 -1
  165. package/dist/tm/serp-loop.d.ts +45 -0
  166. package/dist/tm/serp-loop.d.ts.map +1 -0
  167. package/dist/tm/serp-loop.js +80 -0
  168. package/dist/tm/serp-loop.js.map +1 -0
  169. package/dist/tm/stats.d.ts +26 -0
  170. package/dist/tm/stats.d.ts.map +1 -1
  171. package/dist/tm/stats.js +194 -4
  172. package/dist/tm/stats.js.map +1 -1
  173. package/dist/tm/webfetch.d.ts +56 -0
  174. package/dist/tm/webfetch.d.ts.map +1 -1
  175. package/dist/tm/webfetch.js +187 -19
  176. package/dist/tm/webfetch.js.map +1 -1
  177. package/dist/tool-coerce.d.ts +24 -0
  178. package/dist/tool-coerce.d.ts.map +1 -0
  179. package/dist/tool-coerce.js +63 -0
  180. package/dist/tool-coerce.js.map +1 -0
  181. package/dist/types.d.ts +7 -1
  182. package/dist/types.d.ts.map +1 -1
  183. package/docs/installation-v2.md +436 -0
  184. package/docs/installation.md +81 -2
  185. package/docs/research/agent-data-exchange.md +117 -0
  186. package/docs/research/browser-pane.md +89 -0
  187. package/docs/research/host-http-api.md +132 -0
  188. package/docs/research/host-subagent-injection.md +187 -0
  189. package/docs/research/plugin-loader-contract.md +261 -0
  190. package/docs/research/v2-builtin-tools.md +447 -0
  191. package/docs/v2-port-notes.md +315 -0
  192. package/index.js +7 -0
  193. package/package.json +4 -1
  194. package/scripts/gen-v2-config.mjs +304 -0
  195. package/scripts/lib/config-surgery.cjs +239 -0
  196. package/scripts/lib/fake-ctx.mjs +146 -0
@@ -0,0 +1,308 @@
1
+ /**
2
+ * `tm_ledger` — the LEDGER rule's landing spot on a host that has no `todowrite`.
3
+ *
4
+ * The lead's prompt mandates it: every new ask becomes a list item BEFORE the
5
+ * work, an interruption is an insertion rather than a replacement, `blocked` is a
6
+ * state and not an exit, and after a compaction the list is re-read before
7
+ * anything else. On v1 that list is the host's own `todowrite`. On v2 a plugin
8
+ * cannot create that tool (`V2_ONLY_ACTIONS` names `todowrite` as a key with no
9
+ * v2 counterpart), so the mandate had no落点 — and the goal tripwire in `tm_join`
10
+ * had nothing to check, which it now says out loud (`goal_unchecked reason=no_seam`).
11
+ *
12
+ * This module is the other answer. Storage is `ctx.storage` — the host's own
13
+ * key/value domain, proven by a boot self-check that writes a marker and reads it
14
+ * back. That choice beats the alternative (a file under the run store), which is
15
+ * the user's work-in-progress sitting in a temp directory with no owner. Two facts
16
+ * from a live 2.0.16 probe (`docs/research/agent-data-exchange.md`) shape this file:
17
+ * · the domain is shared across sessions, agents AND projects — the only namespace
18
+ * is the plugin id — so the session id is part of the key and nothing may be
19
+ * written without one;
20
+ * · there is no TTL and no quota, so a list that only ever grows is a leak with a
21
+ * friendly name. That is why `LEDGER_MAX_ITEMS` refuses instead of promising a
22
+ * cleanup mechanism this host does not offer.
23
+ *
24
+ * The honesty rule this module exists to obey (product goal #6): a reply about
25
+ * the list says whether the list REACHED storage. An in-memory-only "已记录"
26
+ * would be exactly the unverified "done" the rest of this plugin treats as a bug.
27
+ */
28
+ import { tmError, toToolResult } from "./result.js";
29
+ export const LEDGER_STATUSES = ["open", "doing", "done", "blocked"];
30
+ const KEY_PREFIX = "team-mode/ledger/";
31
+ /** `ctx.storage.get` answered with the raw value in one build and with
32
+ * `{value: …}` in another (the boot self-check accepts both), so the reader has
33
+ * to unwrap both rather than trusting either. */
34
+ function unwrapStored(raw) {
35
+ if (raw && typeof raw === "object" && "value" in raw) {
36
+ return raw.value;
37
+ }
38
+ return raw;
39
+ }
40
+ export function createStorageLedgerStore(storage) {
41
+ const st = storage;
42
+ const ok = !!st && typeof st.get === "function" && typeof st.set === "function";
43
+ let detail;
44
+ return {
45
+ kind: "ctx.storage",
46
+ available: () => ok,
47
+ detail: () => detail ?? "",
48
+ async load(sessionID) {
49
+ if (!ok)
50
+ throw new Error("这个宿主没有给 ctx.storage 的 get/set");
51
+ const raw = await st.get(KEY_PREFIX + sessionID);
52
+ return normalizeLedger(unwrapStored(raw));
53
+ },
54
+ async save(ledger) {
55
+ if (!ok)
56
+ throw new Error("这个宿主没有给 ctx.storage 的 get/set");
57
+ try {
58
+ await st.set(KEY_PREFIX + ledger.sessionID, ledger);
59
+ detail = undefined;
60
+ }
61
+ catch (err) {
62
+ detail = String(err?.message ?? err).slice(0, 120);
63
+ throw err;
64
+ }
65
+ },
66
+ };
67
+ }
68
+ /** Defensive read: a value that is not a ledger is treated as absent rather than
69
+ * half-trusted, because a silently-mangled list is worse than an empty one —
70
+ * the lead would wrap up believing the work was accounted for. */
71
+ export function normalizeLedger(raw) {
72
+ if (!raw || typeof raw !== "object")
73
+ return null;
74
+ const o = raw;
75
+ if (!Array.isArray(o.items))
76
+ return null;
77
+ const items = [];
78
+ for (const it of o.items) {
79
+ if (!it || typeof it !== "object")
80
+ continue;
81
+ const e = it;
82
+ const text = typeof e.text === "string" ? e.text.trim() : "";
83
+ if (!text)
84
+ continue;
85
+ items.push({
86
+ id: Number.isFinite(Number(e.id)) ? Number(e.id) : items.length + 1,
87
+ text,
88
+ status: LEDGER_STATUSES.includes(String(e.status))
89
+ ? String(e.status)
90
+ : "open",
91
+ at: Number.isFinite(Number(e.at)) ? Number(e.at) : Date.now(),
92
+ ...(typeof e.note === "string" && e.note ? { note: e.note } : {}),
93
+ });
94
+ }
95
+ return {
96
+ sessionID: String(o.sessionID ?? ""),
97
+ items,
98
+ updated: Number.isFinite(Number(o.updated)) ? Number(o.updated) : Date.now(),
99
+ };
100
+ }
101
+ /** The dedup key. The LEDGER rule says an interruption INSERTS, and the same ask
102
+ * arriving twice (a re-stated instruction, a screenshot of the same question)
103
+ * must not grow a second line the lead then has to close twice — so the key is
104
+ * the text with case/whitespace/trailing punctuation removed, which is the same
105
+ * "same question" notion tm_memory's dedupKey uses. */
106
+ export function ledgerDedupKey(text) {
107
+ return String(text ?? "")
108
+ .toLowerCase()
109
+ .replace(/[\s ]+/g, " ")
110
+ .replace(/[。..!!??;;::,,]+$/g, "")
111
+ .trim();
112
+ }
113
+ export function emptyLedger(sessionID) {
114
+ return { sessionID, items: [], updated: Date.now() };
115
+ }
116
+ /** `ctx.storage` never expires anything (measured), so the list needs a ceiling or
117
+ * it grows for the life of the install. Refusing is honest; silently truncating
118
+ * would be the overstated claim — the lead would believe its oldest asks were gone
119
+ * when they were only hidden. */
120
+ export const LEDGER_MAX_ITEMS_DEFAULT = 200;
121
+ export function ledgerMaxItems(env = process.env) {
122
+ const n = Number(String(env.TM_LEDGER_MAX_ITEMS ?? "").trim());
123
+ return Number.isFinite(n) && n >= 10 ? Math.floor(n) : LEDGER_MAX_ITEMS_DEFAULT;
124
+ }
125
+ /** A merge is reported, not hidden: the reply says "这条已经在清单上(#2)" so the
126
+ * lead learns the insertion was a repeat instead of wondering why the count did
127
+ * not move. */
128
+ export function addItem(ledger, text, now = Date.now()) {
129
+ const clean = String(text ?? "").replace(/[\s ]+/g, " ").trim().slice(0, 400);
130
+ const key = ledgerDedupKey(clean);
131
+ const existing = clean ? ledger.items.find((i) => ledgerDedupKey(i.text) === key) : undefined;
132
+ if (existing)
133
+ return { ledger, item: existing, merged: true };
134
+ const nextId = ledger.items.reduce((m, i) => Math.max(m, i.id), 0) + 1;
135
+ const item = { id: nextId, text: clean, status: "open", at: now };
136
+ return { ledger: { ...ledger, items: [...ledger.items, item], updated: now }, item, merged: false };
137
+ }
138
+ const MARKS = { open: "[ ]", doing: "[>]", done: "[x]", blocked: "[!]" };
139
+ export function openItems(ledger) {
140
+ return (ledger?.items ?? []).filter((i) => i.status !== "done");
141
+ }
142
+ /** Resolve `#4` / `4` / a substring, and refuse an ambiguous match by NAMING the
143
+ * candidates — a list tool that silently marks the wrong line destroys the one
144
+ * thing it is for. */
145
+ export function findItem(ledger, ref) {
146
+ const s = String(ref ?? "").trim().replace(/^#/, "");
147
+ if (!s)
148
+ return { candidates: ledger.items };
149
+ if (/^\d+$/.test(s)) {
150
+ const byId = ledger.items.find((i) => i.id === Number(s));
151
+ return byId ? { item: byId, candidates: [byId] } : { candidates: [] };
152
+ }
153
+ const key = ledgerDedupKey(s);
154
+ const hits = ledger.items.filter((i) => ledgerDedupKey(i.text).includes(key) || key.includes(ledgerDedupKey(i.text)));
155
+ return hits.length === 1 ? { item: hits[0], candidates: hits } : { candidates: hits };
156
+ }
157
+ export function markItem(ledger, ref, status, note, now = Date.now()) {
158
+ const { item, candidates } = findItem(ledger, ref);
159
+ if (!item) {
160
+ const named = candidates.slice(0, 4).map((c) => `#${c.id} ${c.text}`).join("、");
161
+ return {
162
+ error: candidates.length
163
+ ? `「${ref}」匹配到 ${candidates.length} 条,不能猜:${named} —— 用编号重发一次`
164
+ : `清单里没有「${ref}」这一条(现有 ${ledger.items.length} 条)—— 先 tm_ledger { action:"list" } 看一眼`,
165
+ };
166
+ }
167
+ const next = {
168
+ ...item,
169
+ status,
170
+ at: now,
171
+ ...(String(note ?? "").trim() ? { note: String(note).trim().slice(0, 400) } : {}),
172
+ };
173
+ return { ledger: { ...ledger, items: ledger.items.map((i) => (i.id === item.id ? next : i)), updated: now }, item: next };
174
+ }
175
+ export function renderLedger(ledger) {
176
+ const open = openItems(ledger);
177
+ const done = ledger.items.length - open.length;
178
+ const head = `清单 ${ledger.sessionID}:共 ${ledger.items.length} 条 · 未完成 ${open.length} · 已完成 ${done}`;
179
+ if (!ledger.items.length)
180
+ return `${head}\n(空 —— 每接一个新要求就先加一条,再动手)`;
181
+ const lines = ledger.items.map((i) => {
182
+ const note = i.note ? ` —— ${i.note}` : "";
183
+ return `- ${MARKS[i.status]} #${i.id} ${i.text}${note}`;
184
+ });
185
+ return [head, ...lines].join("\n");
186
+ }
187
+ /** The line `tm_join` appends when the round settled but the LEDGER did not —
188
+ * the goal tripwire's shape applied to a list the plugin actually owns. */
189
+ export function ledgerGoalLine(ledger, max = 6) {
190
+ const open = openItems(ledger);
191
+ if (!open.length)
192
+ return null;
193
+ const named = open
194
+ .slice(0, max)
195
+ .map((i) => `「${i.text}」(${i.status === "blocked" ? "卡住" : i.status === "doing" ? "进行中" : "未开始"})`)
196
+ .join("、");
197
+ return (`⚠ 目标未达成:tm_ledger 还有 ${open.length} 项未完成 —— ${named}` +
198
+ (open.length > max ? ` …+${open.length - max}` : "") +
199
+ `\n按目标指令:要么继续做掉,要么向用户写明哪一条被什么卡住;不要把这轮当成收尾。` +
200
+ `做完了就 tm_ledger { action:"done", id:#编号 } 更新状态,再收尾。`);
201
+ }
202
+ const DESCRIPTION = `你自己的任务清单(LEDGER)——这个宿主没有 todowrite,清单由本插件记在宿主的 ctx.storage 里。
203
+ - { action:"add", text:"…" }:接一个新要求就先加一条,再加进清单之后的活(中途插入的要求同样先加,别把原清单换掉)。同样的话说过两遍会告诉你它已经在第几条,不重复加。
204
+ - { action:"doing" | "done" | "blocked", id:"#编号 或一句话" }:改状态。blocked 是一个状态,不是收工的理由——note 里写清被什么卡住(这是别人复述不出来的那半句)。
205
+ - { action:"list" }:把整张清单读回来。上下文被压缩过、或者你不记得走到哪儿了,先读这个,别凭印象继续。
206
+ - 编号在答复里一定会回给你;id 用编号最准,用一句话的片段也行,但撞上两条会拒绝并把候选列给你,不会替你猜。
207
+ - 答复里会说明这条清单是否真的进了 ctx.storage:没进去就是失败,不会算"已记录"。`;
208
+ const LEDGER_ARGS = {
209
+ action: { descriptor: 'action: "add" | "doing" | "done" | "blocked" | "list"' },
210
+ text: { descriptor: "text: string (add 用:一条要求的原文,越短越好)" },
211
+ id: { descriptor: 'id: number|string (改状态用:清单编号,或者能唯一对上的一条的片段)' },
212
+ note: { descriptor: "note: string (可选:blocked 被什么卡住 / done 凭什么判定完成)" },
213
+ };
214
+ export function buildLedgerTool(deps) {
215
+ const tool = "tm_ledger";
216
+ const now = deps.now ?? (() => Date.now());
217
+ return {
218
+ description: DESCRIPTION,
219
+ args: LEDGER_ARGS,
220
+ execute: async (rawArgs, ctx) => {
221
+ const c = (ctx ?? {});
222
+ if (deps.onlyAgent && String(c.agent ?? "") !== deps.onlyAgent) {
223
+ return toToolResult(tmError(tool, "permission", `tm_ledger 只有领队能用(现在的调用方是「${String(c.agent || "未知")}」)。` +
224
+ `专家的状态写在 STATUS 里,由领队并进它的那张清单——不要另起一张。`));
225
+ }
226
+ const args = (rawArgs ?? {});
227
+ const sessionID = String(c.sessionID ?? "").trim();
228
+ if (!sessionID) {
229
+ return toToolResult(tmError(tool, "args", "这个调用没有带 sessionID —— 清单是按会话存的,不能凭空写到一个未知会话上。"));
230
+ }
231
+ if (!deps.store.available()) {
232
+ return toToolResult(tmError(tool, "client", `这个宿主没有给出可写的 ctx.storage(${deps.store.kind}),所以清单没地方落。` +
233
+ `不要把这件事当成"清单功能坏了":把状态写进 STATUS/CHANGES 里,照样可核对。`));
234
+ }
235
+ let ledger;
236
+ try {
237
+ ledger = (await deps.store.load(sessionID)) ?? emptyLedger(sessionID);
238
+ }
239
+ catch (err) {
240
+ return toToolResult(tmError(tool, "execute", `读清单失败(${deps.store.kind}):${String(err?.message ?? err).slice(0, 160)}`));
241
+ }
242
+ const action = String(args.action ?? "").trim().toLowerCase();
243
+ if (!["add", "doing", "done", "blocked", "list"].includes(action)) {
244
+ return toToolResult(tmError(tool, "args", `action 只能是 add / doing / done / blocked / list,收到「${action || "(空)"}」`));
245
+ }
246
+ if (action === "list") {
247
+ return toToolResult({ ok: true, output: renderLedger(ledger), meta: { open: openItems(ledger).length } });
248
+ }
249
+ let answer = "";
250
+ if (action === "add") {
251
+ const texts = Array.isArray(args.text)
252
+ ? args.text.map(String)
253
+ : String(args.text ?? "")
254
+ .split(/\r?\n|;/)
255
+ .map((s) => s.trim())
256
+ .filter(Boolean);
257
+ if (!texts.length) {
258
+ return toToolResult(tmError(tool, "args", "add 需要 text —— 空的一条要求只会让清单变长,不会让活变少"));
259
+ }
260
+ const max = ledgerMaxItems(deps.env);
261
+ if (ledger.items.length + texts.length > max) {
262
+ return toToolResult(tmError(tool, "args", `清单已有 ${ledger.items.length} 条,再加 ${texts.length} 条会超过上限 ${max}(TM_LEDGER_MAX_ITEMS)。` +
263
+ `宿主的 ctx.storage 不设 TTL,也不会替你回收:先把已完成的条目在回复里总结掉,或显式清一次,别让它无声涨下去。`));
264
+ }
265
+ let merged = 0;
266
+ const added = [];
267
+ for (const t of texts) {
268
+ const r = addItem(ledger, t, now());
269
+ ledger = r.ledger;
270
+ if (r.merged)
271
+ merged++;
272
+ else
273
+ added.push(r.item);
274
+ }
275
+ answer = added.length
276
+ ? `已记 ${added.map((i) => `#${i.id} ${i.text}`).join(";")}${merged ? `(另有 ${merged} 条重复,没再加)` : ""}`
277
+ : `这 ${merged} 条已经在清单上了,没重复加:${texts.map((t) => { const f = findItem(ledger, t); return f.item ? `#${f.item.id}(${f.item.status})` : "?"; }).join("、")}`;
278
+ }
279
+ else {
280
+ const ref = (args.id ?? args.text);
281
+ if (ref === undefined || String(ref).trim() === "") {
282
+ return toToolResult(tmError(tool, "args", `${action} 需要 id —— 编号,或者能唯一对上的一条的片段`));
283
+ }
284
+ const r = markItem(ledger, ref, action, typeof args.note === "string" ? args.note : undefined, now());
285
+ if ("error" in r)
286
+ return toToolResult(tmError(tool, "args", r.error));
287
+ ledger = r.ledger;
288
+ answer = `#${r.item.id} → ${action}:${r.item.text}${r.item.note ? `(${r.item.note})` : ""}`;
289
+ }
290
+ try {
291
+ await deps.store.save(ledger);
292
+ }
293
+ catch (err) {
294
+ // The change did not land. Saying 已记录 here would be the unverified
295
+ // "done" this product treats as its own defect class.
296
+ return toToolResult(tmError(tool, "execute", `${answer}\n但这次改动没写进 ${deps.store.kind}:${String(err?.message ?? err).slice(0, 160)} —— 清单还是上一次的样子。`));
297
+ }
298
+ deps.onWrite?.(sessionID, action);
299
+ const rest = openItems(ledger);
300
+ return toToolResult({
301
+ ok: true,
302
+ output: `${answer}\n(已写入 ${deps.store.kind} · 未完成 ${rest.length} 条${rest.length ? `:${rest.slice(0, 6).map((i) => `#${i.id}`).join(" ")}` : ""})`,
303
+ meta: { open: rest.length },
304
+ });
305
+ },
306
+ };
307
+ }
308
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../src/tm/ledger.ts"],"names":[],"mappings":"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"}
@@ -30,9 +30,49 @@ export declare function setAskBridgeObserver(fn: ((present: boolean) => void) |
30
30
  /** Extract ctx.ask defensively — host versions before the ctx bridge lack
31
31
  * it, and test stubs may not provide it. */
32
32
  export declare function askFnOf(ctx: unknown): TmAskFn | null;
33
- export type AskOutcome = "approved" | "rejected" | "unavailable";
34
- /** Drive the official dialog for an out-of-allowlist target. Never throws:
35
- * a missing bridge maps to "unavailable", a rejected/failed ask maps to
36
- * "rejected" — callers render their structured error either way. */
37
- export declare function askUserForTarget(ctx: unknown, req: TmAskRequest): Promise<AskOutcome>;
33
+ export type AskOutcome = "approved" | "rejected" | "timed-out" | "unavailable";
34
+ /** A remembered allow rule resolves the ask inside the host; a human has to
35
+ * read the dialog, decide and click. 1.5 s separates the two by an order of
36
+ * magnitude on the observed desktop, and this is a HEURISTIC, not a host
37
+ * contract — which is exactly why the sentence it produces says "没有人点这个
38
+ * 窗" only alongside the mechanism, never instead of it. */
39
+ export declare const ASK_AUTO_GRANT_MS = 1500;
40
+ export interface AskResult {
41
+ outcome: AskOutcome;
42
+ /** How long the host took to answer. Undefined when there was no bridge. */
43
+ answeredInMs?: number;
44
+ /** approved AND answered fast enough that nobody can have clicked it — i.e.
45
+ * a saved rule (an "always" the user granted earlier, possibly in another
46
+ * agent's session) answered on their behalf. */
47
+ autoGranted: boolean;
48
+ }
49
+ /** The same drive as `askUserForTarget`, but it reports HOW the approval
50
+ * arrived. Callers that show the user a result need this: an agent that
51
+ * cannot tell "the host remembered an always-allow" from "the user just
52
+ * approved this" will report 免弹窗 as evidence about the allowlist, and that
53
+ * is how a correct observation becomes a wrong conclusion. */
54
+ export declare function askUserForTargetDetailed(ctx: unknown, req: TmAskRequest, waitMs?: number, autoGrantMs?: number): Promise<AskResult>;
55
+ /** The line a caller appends when a governed web fetch/navigation proceeded on
56
+ * an approval rather than on the static allowlist. One function so no tool
57
+ * can half-report it. */
58
+ export declare function askGrantNote(res: AskResult): string;
59
+ /** The unified approval gate auto-rejects an unanswered dialog after
60
+ * `TM_ASK_TIMEOUT_MIN`, but it is only ARMED when R6 is on and the client can
61
+ * reply. Where it is not armed, `await ask()` hung until the user interrupted
62
+ * the whole turn — live evidence: an out-of-allowlist tm_webfetch that showed
63
+ * as a spinning card and then `Tool execution aborted`. A tool-side deadline
64
+ * is the backstop, so no caller has to remember it. */
65
+ export declare const ASK_GRACE_MS = 15000;
66
+ /** Wired once at boot from `resolveAskTimeoutMs() + ASK_GRACE_MS`, so the
67
+ * gate's authoritative reject always wins when it is able to fire and this
68
+ * timer only catches the un-armed case. */
69
+ export declare function setAskWaitMs(ms: number): void;
70
+ export declare function getAskWaitMs(): number;
71
+ /** Drive the official dialog for an out-of-allowlist target. Never throws: a
72
+ * missing bridge is "unavailable", a refusal is "rejected", and a dialog
73
+ * nobody answered is "timed-out" — three different answers that need three
74
+ * different next moves. */
75
+ export declare function askUserForTarget(ctx: unknown, req: TmAskRequest, waitMs?: number): Promise<AskOutcome>;
76
+ export declare function setAskUnavailableNote(note: string): void;
77
+ export declare function askRefusalNote(outcome: AskOutcome, waitMs?: number): string;
38
78
  //# sourceMappingURL=perm-ask.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"perm-ask.d.ts","sourceRoot":"","sources":["../../src/tm/perm-ask.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAM7D,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAElF;AAED;6CAC6C;AAC7C,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CASpD;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,aAAa,CAAA;AAEhE;;qEAEqE;AACrE,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAS3F"}
1
+ {"version":3,"file":"perm-ask.d.ts","sourceRoot":"","sources":["../../src/tm/perm-ask.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,UAAU,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAM7D,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAElF;AAED;6CAC6C;AAC7C,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CASpD;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,CAAA;AAE9E;;;;4DAI4D;AAC5D,eAAO,MAAM,iBAAiB,OAAQ,CAAA;AAEtC,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,UAAU,CAAA;IACnB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;qDAEiD;IACjD,WAAW,EAAE,OAAO,CAAA;CACrB;AAED;;;;+DAI+D;AAC/D,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,YAAY,EACjB,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,SAAoB,GAC9B,OAAO,CAAC,SAAS,CAAC,CAkBpB;AAED;;0BAE0B;AAC1B,wBAAgB,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAOnD;AAMD;;;;;wDAKwD;AACxD,eAAO,MAAM,YAAY,QAAS,CAAA;AAIlC;;4CAE4C;AAC5C,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE7C;AACD,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;4BAG4B;AAC5B,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,YAAY,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,CAAC,CAgBrB;AAUD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExD;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAY,GAAG,MAAM,CAU9E"}
@@ -37,19 +37,119 @@ export function askFnOf(ctx) {
37
37
  }
38
38
  return present ? ask : null;
39
39
  }
40
- /** Drive the official dialog for an out-of-allowlist target. Never throws:
41
- * a missing bridge maps to "unavailable", a rejected/failed ask maps to
42
- * "rejected" — callers render their structured error either way. */
43
- export async function askUserForTarget(ctx, req) {
40
+ /** A remembered allow rule resolves the ask inside the host; a human has to
41
+ * read the dialog, decide and click. 1.5 s separates the two by an order of
42
+ * magnitude on the observed desktop, and this is a HEURISTIC, not a host
43
+ * contract — which is exactly why the sentence it produces says "没有人点这个
44
+ * 窗" only alongside the mechanism, never instead of it. */
45
+ export const ASK_AUTO_GRANT_MS = 1_500;
46
+ /** The same drive as `askUserForTarget`, but it reports HOW the approval
47
+ * arrived. Callers that show the user a result need this: an agent that
48
+ * cannot tell "the host remembered an always-allow" from "the user just
49
+ * approved this" will report 免弹窗 as evidence about the allowlist, and that
50
+ * is how a correct observation becomes a wrong conclusion. */
51
+ export async function askUserForTargetDetailed(ctx, req, waitMs, autoGrantMs = ASK_AUTO_GRANT_MS) {
52
+ const ask = askFnOf(ctx);
53
+ if (!ask)
54
+ return { outcome: "unavailable", autoGranted: false };
55
+ const budget = Math.max(ASK_WAIT_FLOOR_MS, Math.round(waitMs ?? askWaitMs));
56
+ const t0 = Date.now();
57
+ let timer = null;
58
+ try {
59
+ await new Promise((resolve, reject) => {
60
+ timer = setTimeout(() => reject(TIMED_OUT), budget);
61
+ Promise.resolve(ask(req)).then(resolve, reject);
62
+ });
63
+ const answeredInMs = Date.now() - t0;
64
+ return { outcome: "approved", answeredInMs, autoGranted: answeredInMs < autoGrantMs };
65
+ }
66
+ catch (err) {
67
+ return { outcome: err === TIMED_OUT ? "timed-out" : "rejected", answeredInMs: Date.now() - t0, autoGranted: false };
68
+ }
69
+ finally {
70
+ if (timer)
71
+ clearTimeout(timer);
72
+ }
73
+ }
74
+ /** The line a caller appends when a governed web fetch/navigation proceeded on
75
+ * an approval rather than on the static allowlist. One function so no tool
76
+ * can half-report it. */
77
+ export function askGrantNote(res) {
78
+ if (res.outcome !== "approved")
79
+ return "";
80
+ return res.autoGranted
81
+ ? `\n(本次不是静态白名单放行:宿主按已记住的规则直接准了,${res.answeredInMs ?? 0}ms 内没有人可能点过这个窗。` +
82
+ `那通常是用户先前点过的"始终允许"——它按项目生效,对所有 agent 会话都算,所以这不证明本会话被单独征求过意见。` +
83
+ `要收回:在宿主的权限设置里删掉那条规则。)`
84
+ : `\n(本次由用户刚刚在确认窗里批准,仅这一会话有效。)`;
85
+ }
86
+ /** Our own reject sentinel — an `ask()` that never settles has to be told
87
+ * apart from a dialog the user actually answered "no". */
88
+ const TIMED_OUT = Symbol("ask-timed-out");
89
+ /** The unified approval gate auto-rejects an unanswered dialog after
90
+ * `TM_ASK_TIMEOUT_MIN`, but it is only ARMED when R6 is on and the client can
91
+ * reply. Where it is not armed, `await ask()` hung until the user interrupted
92
+ * the whole turn — live evidence: an out-of-allowlist tm_webfetch that showed
93
+ * as a spinning card and then `Tool execution aborted`. A tool-side deadline
94
+ * is the backstop, so no caller has to remember it. */
95
+ export const ASK_GRACE_MS = 15_000;
96
+ const ASK_WAIT_FLOOR_MS = 5_000;
97
+ let askWaitMs = 75_000;
98
+ /** Wired once at boot from `resolveAskTimeoutMs() + ASK_GRACE_MS`, so the
99
+ * gate's authoritative reject always wins when it is able to fire and this
100
+ * timer only catches the un-armed case. */
101
+ export function setAskWaitMs(ms) {
102
+ if (Number.isFinite(ms) && ms >= ASK_WAIT_FLOOR_MS)
103
+ askWaitMs = Math.round(ms);
104
+ }
105
+ export function getAskWaitMs() {
106
+ return askWaitMs;
107
+ }
108
+ /** Drive the official dialog for an out-of-allowlist target. Never throws: a
109
+ * missing bridge is "unavailable", a refusal is "rejected", and a dialog
110
+ * nobody answered is "timed-out" — three different answers that need three
111
+ * different next moves. */
112
+ export async function askUserForTarget(ctx, req, waitMs) {
44
113
  const ask = askFnOf(ctx);
45
114
  if (!ask)
46
115
  return "unavailable";
116
+ const budget = Math.max(ASK_WAIT_FLOOR_MS, Math.round(waitMs ?? askWaitMs));
117
+ let timer = null;
47
118
  try {
48
- await ask(req);
119
+ await new Promise((resolve, reject) => {
120
+ timer = setTimeout(() => reject(TIMED_OUT), budget);
121
+ Promise.resolve(ask(req)).then(resolve, reject);
122
+ });
49
123
  return "approved";
50
124
  }
51
- catch {
52
- return "rejected";
125
+ catch (err) {
126
+ return err === TIMED_OUT ? "timed-out" : "rejected";
127
+ }
128
+ finally {
129
+ if (timer)
130
+ clearTimeout(timer);
131
+ }
132
+ }
133
+ /** One sentence naming WHY the dialog did not grant access. This is not
134
+ * cosmetic: "用户未批准" for a dialog nobody saw teaches the agent to give up
135
+ * (or retry silently) instead of telling the human to look at the screen. */
136
+ /** What "we could not ask" means on THIS host generation. v1 says 旧版协议
137
+ * because there it is true; the v2 personality replaces the sentence because
138
+ * on v2 the absence is the NEW protocol, and telling the model the host is old
139
+ * would send it looking for an upgrade it already has. */
140
+ let unavailableNote = "宿主无法弹出确认窗口(旧版协议)。";
141
+ export function setAskUnavailableNote(note) {
142
+ if (typeof note === "string" && note.trim())
143
+ unavailableNote = note;
144
+ }
145
+ export function askRefusalNote(outcome, waitMs = askWaitMs) {
146
+ if (outcome === "rejected")
147
+ return "用户未批准。";
148
+ if (outcome === "timed-out") {
149
+ return (`确认窗无人应答——我等满了 ${Math.round(waitMs / 1000)} 秒(本机 ask 的等待上限)才放弃,` +
150
+ `所以这不是被拒绝,是没有人在界面上点它。请提醒用户查看待确认的对话框;` +
151
+ `如果确实不想开权限,就改用白名单内的源,不要重复调用。`);
53
152
  }
153
+ return unavailableNote;
54
154
  }
55
155
  //# sourceMappingURL=perm-ask.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"perm-ask.js","sourceRoot":"","sources":["../../src/tm/perm-ask.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAaH;;6EAE6E;AAC7E,IAAI,iBAAiB,GAAwC,IAAI,CAAA;AACjE,MAAM,UAAU,oBAAoB,CAAC,EAAuC;IAC1E,iBAAiB,GAAG,EAAE,CAAA;AACxB,CAAC;AAED;6CAC6C;AAC7C,MAAM,UAAU,OAAO,CAAC,GAAY;IAClC,MAAM,GAAG,GAAI,GAA4C,EAAE,GAAG,CAAA;IAC9D,MAAM,OAAO,GAAG,OAAO,GAAG,KAAK,UAAU,CAAA;IACzC,IAAI,CAAC;QACH,iBAAiB,EAAE,CAAC,OAAO,CAAC,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;IACD,OAAO,OAAO,CAAC,CAAC,CAAE,GAAe,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1C,CAAC;AAID;;qEAEqE;AACrE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAY,EAAE,GAAiB;IACpE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;IACxB,IAAI,CAAC,GAAG;QAAE,OAAO,aAAa,CAAA;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,GAAG,CAAC,CAAA;QACd,OAAO,UAAU,CAAA;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,UAAU,CAAA;IACnB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"perm-ask.js","sourceRoot":"","sources":["../../src/tm/perm-ask.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"pipelines.d.ts","sourceRoot":"","sources":["../../src/tm/pipelines.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,MAAM,MAAM,aAAa,CAAA;AAG1C,OAAO,EAAgE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACpH,OAAO,EAA0C,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAIL,iBAAiB,EAClB,MAAM,cAAc,CAAA;AAmBrB,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,OAAO,CAAA;IACf,CAAC,EAAE,OAAO,CAAA;IACV,GAAG,EAAE,QAAQ,CAAA;IACb,KAAK,EAAE,QAAQ,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC,UAAU,CAAA;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,cAAc,CAAA;IACpB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAID,UAAU,aAAa;IACrB,WAAW,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;IACjD,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AA6CD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA6ClE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM;;qBA0BjB,MAAM,QAAQ,MAAM,WAAW,MAAM,QAAQ,aAAa,KAAG,OAAO;sBA0D7D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;sBAgDxD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;sBAmDxD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;uBAsCvD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;;EAiH1F;AAED,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAA"}
1
+ {"version":3,"file":"pipelines.d.ts","sourceRoot":"","sources":["../../src/tm/pipelines.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,MAAM,MAAM,aAAa,CAAA;AAG1C,OAAO,EAAgE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACpH,OAAO,EAA0C,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAIL,iBAAiB,EAClB,MAAM,cAAc,CAAA;AAmBrB,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,OAAO,CAAA;IACf,CAAC,EAAE,OAAO,CAAA;IACV,GAAG,EAAE,QAAQ,CAAA;IACb,KAAK,EAAE,QAAQ,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC,UAAU,CAAA;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,cAAc,CAAA;IACpB,KAAK,EAAE,MAAM,EAAE,CAAA;IACf;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAID,UAAU,aAAa;IACrB,WAAW,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAA;IACjD,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AA6CD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA6ClE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM;;qBA0BjB,MAAM,QAAQ,MAAM,WAAW,MAAM,QAAQ,aAAa,KAAG,OAAO;sBA0D7D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;sBAgDxD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;sBAkExD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;uBA8CvD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;;EAiH1F;AAED,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAA"}
@@ -310,6 +310,21 @@ export function buildPipelines(deps) {
310
310
  return tmError(tool, "client", unwrapped.message);
311
311
  const content = extractText(unwrapped.data);
312
312
  const matchLines = content ? content.split(/\r?\n/).filter((l) => l.trim()).length : 0;
313
+ if (!matchLines) {
314
+ // A bare empty result reads as "the search is broken", and the agent
315
+ // then re-runs it a wider way or concludes the opposite of the truth
316
+ // (live: a 0-hit search the model called a tool failure). 0 is an
317
+ // ANSWER, so print it as one — with the scope it applies to.
318
+ // …and a widening hint ONLY when the caller narrowed it — telling a
319
+ // root-wide search to drop `path` is noise that costs a round.
320
+ const scopeNote = requested
321
+ ? `\n本次范围由 path="${shorten(requested, 80)}" 限定——要搜整个工作区就去掉 path 再试一次。`
322
+ : "";
323
+ return govern(stepId, tool, `(0 命中)pattern="${pattern}" · 范围=${shorten(scopeDir, 120)}${scopeNote}\n这只表示"在该目录及其子目录里没有匹配",不证明整个仓库没有:这是正则,换写法常能救回来([Aa]rk 同时命中 Ark/ark),或直接 tm_read 读你怀疑的那个文件。`, {
324
+ contentType: "text",
325
+ clue: `pattern=${shorten(pattern, 60)}, 命中 0 行, dir=${shorten(scopeDir, 80)}`,
326
+ });
327
+ }
313
328
  return govern(stepId, tool, content, {
314
329
  contentType: detectContentType(content),
315
330
  clue: `pattern=${shorten(pattern, 60)}, 命中 ${matchLines} 行, dir=${shorten(scopeDir, 80)}`,
@@ -345,8 +360,15 @@ export function buildPipelines(deps) {
345
360
  // workspace all session.
346
361
  const cwd = ctxDir(ctx);
347
362
  const output = await runShellCommand(deps.$, command, cwd);
348
- return govern(stepId, tool, output, {
349
- contentType: detectContentType(output),
363
+ // Empty stdout used to come back as an empty result — indistinguishable
364
+ // from "the tool never ran", so the model re-issued the command or
365
+ // concluded the tool was broken (live: a `-Skip 168` on a file PowerShell
366
+ // decodes as 168 lines, where the honest answer is "0 行输出").
367
+ const text = output.trim() === ""
368
+ ? `(命令执行完成,stdout 为空 = 0 行输出 · cwd=${shorten(cwd, 120)})\n空输出是命令给出的答案,不是工具失败。若你预期有内容:先核对相对路径是按上面的 cwd 解析的;读文件内容请用 tm_read——Windows PowerShell 5.1 的默认解码口径与 tm_read/tm_grep 不一致,含中文的 UTF-8 文件在它眼里行数可能更少,按行取片段时这会直接骗到你。`
369
+ : output;
370
+ return govern(stepId, tool, text, {
371
+ contentType: detectContentType(text),
350
372
  clue: `cmd=${shorten(command, 60)}, dir=${shorten(cwd, 80)}`,
351
373
  });
352
374
  }