@aigne/agent-runtime 1.12.0-beta.5

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 (234) hide show
  1. package/LICENSE.md +26 -0
  2. package/README.md +79 -0
  3. package/dist/agent-runners/ai-runner.cjs +133 -0
  4. package/dist/agent-runners/ai-runner.d.cts +33 -0
  5. package/dist/agent-runners/ai-runner.d.cts.map +1 -0
  6. package/dist/agent-runners/ai-runner.d.mts +33 -0
  7. package/dist/agent-runners/ai-runner.d.mts.map +1 -0
  8. package/dist/agent-runners/ai-runner.mjs +134 -0
  9. package/dist/agent-runners/ai-runner.mjs.map +1 -0
  10. package/dist/agent-runners/ash-runner.cjs +272 -0
  11. package/dist/agent-runners/ash-runner.d.cts +30 -0
  12. package/dist/agent-runners/ash-runner.d.cts.map +1 -0
  13. package/dist/agent-runners/ash-runner.d.mts +30 -0
  14. package/dist/agent-runners/ash-runner.d.mts.map +1 -0
  15. package/dist/agent-runners/ash-runner.mjs +273 -0
  16. package/dist/agent-runners/ash-runner.mjs.map +1 -0
  17. package/dist/agent-runners/budget.cjs +71 -0
  18. package/dist/agent-runners/budget.d.cts +40 -0
  19. package/dist/agent-runners/budget.d.cts.map +1 -0
  20. package/dist/agent-runners/budget.d.mts +40 -0
  21. package/dist/agent-runners/budget.d.mts.map +1 -0
  22. package/dist/agent-runners/budget.mjs +67 -0
  23. package/dist/agent-runners/budget.mjs.map +1 -0
  24. package/dist/agent-runners/code-runner.cjs +61 -0
  25. package/dist/agent-runners/code-runner.mjs +61 -0
  26. package/dist/agent-runners/code-runner.mjs.map +1 -0
  27. package/dist/agent-runners/env-whitelist.cjs +36 -0
  28. package/dist/agent-runners/env-whitelist.d.cts +5 -0
  29. package/dist/agent-runners/env-whitelist.d.cts.map +1 -0
  30. package/dist/agent-runners/env-whitelist.d.mts +5 -0
  31. package/dist/agent-runners/env-whitelist.d.mts.map +1 -0
  32. package/dist/agent-runners/env-whitelist.mjs +36 -0
  33. package/dist/agent-runners/env-whitelist.mjs.map +1 -0
  34. package/dist/agent-runners/guide-rails.cjs +47 -0
  35. package/dist/agent-runners/guide-rails.d.cts +16 -0
  36. package/dist/agent-runners/guide-rails.d.cts.map +1 -0
  37. package/dist/agent-runners/guide-rails.d.mts +16 -0
  38. package/dist/agent-runners/guide-rails.d.mts.map +1 -0
  39. package/dist/agent-runners/guide-rails.mjs +48 -0
  40. package/dist/agent-runners/guide-rails.mjs.map +1 -0
  41. package/dist/agent-runners/hook-channel.cjs +55 -0
  42. package/dist/agent-runners/hook-channel.d.cts +38 -0
  43. package/dist/agent-runners/hook-channel.d.cts.map +1 -0
  44. package/dist/agent-runners/hook-channel.d.mts +38 -0
  45. package/dist/agent-runners/hook-channel.d.mts.map +1 -0
  46. package/dist/agent-runners/hook-channel.mjs +55 -0
  47. package/dist/agent-runners/hook-channel.mjs.map +1 -0
  48. package/dist/agent-runners/hooks.cjs +107 -0
  49. package/dist/agent-runners/hooks.d.cts +41 -0
  50. package/dist/agent-runners/hooks.d.cts.map +1 -0
  51. package/dist/agent-runners/hooks.d.mts +41 -0
  52. package/dist/agent-runners/hooks.d.mts.map +1 -0
  53. package/dist/agent-runners/hooks.mjs +107 -0
  54. package/dist/agent-runners/hooks.mjs.map +1 -0
  55. package/dist/agent-runners/memory-afs.cjs +342 -0
  56. package/dist/agent-runners/memory-afs.d.cts +221 -0
  57. package/dist/agent-runners/memory-afs.d.cts.map +1 -0
  58. package/dist/agent-runners/memory-afs.d.mts +221 -0
  59. package/dist/agent-runners/memory-afs.d.mts.map +1 -0
  60. package/dist/agent-runners/memory-afs.mjs +326 -0
  61. package/dist/agent-runners/memory-afs.mjs.map +1 -0
  62. package/dist/agent-runners/memory-archive.cjs +263 -0
  63. package/dist/agent-runners/memory-archive.d.cts +47 -0
  64. package/dist/agent-runners/memory-archive.d.cts.map +1 -0
  65. package/dist/agent-runners/memory-archive.d.mts +47 -0
  66. package/dist/agent-runners/memory-archive.d.mts.map +1 -0
  67. package/dist/agent-runners/memory-archive.mjs +257 -0
  68. package/dist/agent-runners/memory-archive.mjs.map +1 -0
  69. package/dist/agent-runners/orchestrator-runner.cjs +302 -0
  70. package/dist/agent-runners/orchestrator-runner.d.cts +23 -0
  71. package/dist/agent-runners/orchestrator-runner.d.cts.map +1 -0
  72. package/dist/agent-runners/orchestrator-runner.d.mts +23 -0
  73. package/dist/agent-runners/orchestrator-runner.d.mts.map +1 -0
  74. package/dist/agent-runners/orchestrator-runner.mjs +303 -0
  75. package/dist/agent-runners/orchestrator-runner.mjs.map +1 -0
  76. package/dist/agent-runners/orchestrator-state.cjs +68 -0
  77. package/dist/agent-runners/orchestrator-state.d.cts +23 -0
  78. package/dist/agent-runners/orchestrator-state.d.cts.map +1 -0
  79. package/dist/agent-runners/orchestrator-state.d.mts +23 -0
  80. package/dist/agent-runners/orchestrator-state.d.mts.map +1 -0
  81. package/dist/agent-runners/orchestrator-state.mjs +65 -0
  82. package/dist/agent-runners/orchestrator-state.mjs.map +1 -0
  83. package/dist/agent-runners/retry.cjs +30 -0
  84. package/dist/agent-runners/retry.d.cts +13 -0
  85. package/dist/agent-runners/retry.d.cts.map +1 -0
  86. package/dist/agent-runners/retry.d.mts +13 -0
  87. package/dist/agent-runners/retry.d.mts.map +1 -0
  88. package/dist/agent-runners/retry.mjs +30 -0
  89. package/dist/agent-runners/retry.mjs.map +1 -0
  90. package/dist/agent-runners/scratchpad-afs.cjs +276 -0
  91. package/dist/agent-runners/scratchpad-afs.d.cts +103 -0
  92. package/dist/agent-runners/scratchpad-afs.d.cts.map +1 -0
  93. package/dist/agent-runners/scratchpad-afs.d.mts +103 -0
  94. package/dist/agent-runners/scratchpad-afs.d.mts.map +1 -0
  95. package/dist/agent-runners/scratchpad-afs.mjs +271 -0
  96. package/dist/agent-runners/scratchpad-afs.mjs.map +1 -0
  97. package/dist/agent-runners/stream-router.cjs +72 -0
  98. package/dist/agent-runners/stream-router.d.cts +22 -0
  99. package/dist/agent-runners/stream-router.d.cts.map +1 -0
  100. package/dist/agent-runners/stream-router.d.mts +22 -0
  101. package/dist/agent-runners/stream-router.d.mts.map +1 -0
  102. package/dist/agent-runners/stream-router.mjs +72 -0
  103. package/dist/agent-runners/stream-router.mjs.map +1 -0
  104. package/dist/agent-runners/system-prompt.cjs +35 -0
  105. package/dist/agent-runners/system-prompt.d.cts +17 -0
  106. package/dist/agent-runners/system-prompt.d.cts.map +1 -0
  107. package/dist/agent-runners/system-prompt.d.mts +17 -0
  108. package/dist/agent-runners/system-prompt.d.mts.map +1 -0
  109. package/dist/agent-runners/system-prompt.mjs +34 -0
  110. package/dist/agent-runners/system-prompt.mjs.map +1 -0
  111. package/dist/agent-runners/team-modes/aggregates.cjs +36 -0
  112. package/dist/agent-runners/team-modes/aggregates.mjs +35 -0
  113. package/dist/agent-runners/team-modes/aggregates.mjs.map +1 -0
  114. package/dist/agent-runners/team-modes/iterate.cjs +46 -0
  115. package/dist/agent-runners/team-modes/iterate.mjs +47 -0
  116. package/dist/agent-runners/team-modes/iterate.mjs.map +1 -0
  117. package/dist/agent-runners/team-modes/parallel.cjs +59 -0
  118. package/dist/agent-runners/team-modes/parallel.mjs +60 -0
  119. package/dist/agent-runners/team-modes/parallel.mjs.map +1 -0
  120. package/dist/agent-runners/team-modes/reflection.cjs +155 -0
  121. package/dist/agent-runners/team-modes/reflection.mjs +156 -0
  122. package/dist/agent-runners/team-modes/reflection.mjs.map +1 -0
  123. package/dist/agent-runners/team-modes/sequential.cjs +63 -0
  124. package/dist/agent-runners/team-modes/sequential.mjs +64 -0
  125. package/dist/agent-runners/team-modes/sequential.mjs.map +1 -0
  126. package/dist/agent-runners/team-modes/utils.cjs +39 -0
  127. package/dist/agent-runners/team-modes/utils.mjs +36 -0
  128. package/dist/agent-runners/team-modes/utils.mjs.map +1 -0
  129. package/dist/agent-runners/team-runner.cjs +277 -0
  130. package/dist/agent-runners/team-runner.d.cts +34 -0
  131. package/dist/agent-runners/team-runner.d.cts.map +1 -0
  132. package/dist/agent-runners/team-runner.d.mts +34 -0
  133. package/dist/agent-runners/team-runner.d.mts.map +1 -0
  134. package/dist/agent-runners/team-runner.mjs +278 -0
  135. package/dist/agent-runners/team-runner.mjs.map +1 -0
  136. package/dist/agent-runners/template-context.cjs +32 -0
  137. package/dist/agent-runners/template-context.d.cts +43 -0
  138. package/dist/agent-runners/template-context.d.cts.map +1 -0
  139. package/dist/agent-runners/template-context.d.mts +43 -0
  140. package/dist/agent-runners/template-context.d.mts.map +1 -0
  141. package/dist/agent-runners/template-context.mjs +33 -0
  142. package/dist/agent-runners/template-context.mjs.map +1 -0
  143. package/dist/index.cjs +123 -0
  144. package/dist/index.d.cts +29 -0
  145. package/dist/index.d.mts +29 -0
  146. package/dist/index.mjs +29 -0
  147. package/dist/program/agent-action.cjs +496 -0
  148. package/dist/program/agent-action.d.cts +42 -0
  149. package/dist/program/agent-action.d.cts.map +1 -0
  150. package/dist/program/agent-action.d.mts +42 -0
  151. package/dist/program/agent-action.d.mts.map +1 -0
  152. package/dist/program/agent-action.mjs +497 -0
  153. package/dist/program/agent-action.mjs.map +1 -0
  154. package/dist/program/agent-messages-tools.cjs +137 -0
  155. package/dist/program/agent-messages-tools.d.cts +59 -0
  156. package/dist/program/agent-messages-tools.d.cts.map +1 -0
  157. package/dist/program/agent-messages-tools.d.mts +59 -0
  158. package/dist/program/agent-messages-tools.d.mts.map +1 -0
  159. package/dist/program/agent-messages-tools.mjs +130 -0
  160. package/dist/program/agent-messages-tools.mjs.map +1 -0
  161. package/dist/program/agent-runner-registry.cjs +314 -0
  162. package/dist/program/agent-runner-registry.d.cts +52 -0
  163. package/dist/program/agent-runner-registry.d.cts.map +1 -0
  164. package/dist/program/agent-runner-registry.d.mts +52 -0
  165. package/dist/program/agent-runner-registry.d.mts.map +1 -0
  166. package/dist/program/agent-runner-registry.mjs +313 -0
  167. package/dist/program/agent-runner-registry.mjs.map +1 -0
  168. package/dist/program/agent-subscribe.cjs +72 -0
  169. package/dist/program/agent-subscribe.d.cts +18 -0
  170. package/dist/program/agent-subscribe.d.cts.map +1 -0
  171. package/dist/program/agent-subscribe.d.mts +18 -0
  172. package/dist/program/agent-subscribe.d.mts.map +1 -0
  173. package/dist/program/agent-subscribe.mjs +73 -0
  174. package/dist/program/agent-subscribe.mjs.map +1 -0
  175. package/dist/program/agent-tools.cjs +135 -0
  176. package/dist/program/agent-tools.d.cts +35 -0
  177. package/dist/program/agent-tools.d.cts.map +1 -0
  178. package/dist/program/agent-tools.d.mts +35 -0
  179. package/dist/program/agent-tools.d.mts.map +1 -0
  180. package/dist/program/agent-tools.mjs +136 -0
  181. package/dist/program/agent-tools.mjs.map +1 -0
  182. package/dist/program/manifest-nesting.cjs +57 -0
  183. package/dist/program/manifest-nesting.d.cts +13 -0
  184. package/dist/program/manifest-nesting.d.cts.map +1 -0
  185. package/dist/program/manifest-nesting.d.mts +13 -0
  186. package/dist/program/manifest-nesting.d.mts.map +1 -0
  187. package/dist/program/manifest-nesting.mjs +57 -0
  188. package/dist/program/manifest-nesting.mjs.map +1 -0
  189. package/dist/program/result-contract.cjs +55 -0
  190. package/dist/program/result-contract.d.cts +23 -0
  191. package/dist/program/result-contract.d.cts.map +1 -0
  192. package/dist/program/result-contract.d.mts +23 -0
  193. package/dist/program/result-contract.d.mts.map +1 -0
  194. package/dist/program/result-contract.mjs +51 -0
  195. package/dist/program/result-contract.mjs.map +1 -0
  196. package/dist/program/runner-error.cjs +71 -0
  197. package/dist/program/runner-error.d.cts +25 -0
  198. package/dist/program/runner-error.d.cts.map +1 -0
  199. package/dist/program/runner-error.d.mts +25 -0
  200. package/dist/program/runner-error.d.mts.map +1 -0
  201. package/dist/program/runner-error.mjs +72 -0
  202. package/dist/program/runner-error.mjs.map +1 -0
  203. package/dist/program/scoped-afs.cjs +538 -0
  204. package/dist/program/scoped-afs.d.cts +245 -0
  205. package/dist/program/scoped-afs.d.cts.map +1 -0
  206. package/dist/program/scoped-afs.d.mts +245 -0
  207. package/dist/program/scoped-afs.d.mts.map +1 -0
  208. package/dist/program/scoped-afs.mjs +531 -0
  209. package/dist/program/scoped-afs.mjs.map +1 -0
  210. package/dist/program/session-hierarchy.cjs +1 -0
  211. package/dist/program/session-hierarchy.d.cts +2 -0
  212. package/dist/program/session-hierarchy.d.mts +2 -0
  213. package/dist/program/session-hierarchy.mjs +3 -0
  214. package/dist/program/tool-discovery/ash-frontmatter.cjs +83 -0
  215. package/dist/program/tool-discovery/ash-frontmatter.d.cts +21 -0
  216. package/dist/program/tool-discovery/ash-frontmatter.d.cts.map +1 -0
  217. package/dist/program/tool-discovery/ash-frontmatter.d.mts +21 -0
  218. package/dist/program/tool-discovery/ash-frontmatter.d.mts.map +1 -0
  219. package/dist/program/tool-discovery/ash-frontmatter.mjs +84 -0
  220. package/dist/program/tool-discovery/ash-frontmatter.mjs.map +1 -0
  221. package/dist/program/tool-discovery/dot-path-resolver.cjs +60 -0
  222. package/dist/program/tool-discovery/dot-path-resolver.mjs +61 -0
  223. package/dist/program/tool-discovery/dot-path-resolver.mjs.map +1 -0
  224. package/dist/program/tool-discovery/skill-gate.cjs +36 -0
  225. package/dist/program/tool-discovery/skill-gate.d.cts +10 -0
  226. package/dist/program/tool-discovery/skill-gate.d.cts.map +1 -0
  227. package/dist/program/tool-discovery/skill-gate.d.mts +10 -0
  228. package/dist/program/tool-discovery/skill-gate.d.mts.map +1 -0
  229. package/dist/program/tool-discovery/skill-gate.mjs +35 -0
  230. package/dist/program/tool-discovery/skill-gate.mjs.map +1 -0
  231. package/dist/providers/basic/ash/dist/index.cjs +1809 -0
  232. package/dist/providers/basic/ash/dist/index.mjs +1807 -0
  233. package/dist/providers/basic/ash/dist/index.mjs.map +1 -0
  234. package/package.json +67 -0
@@ -0,0 +1,263 @@
1
+ let ufo = require("ufo");
2
+ let _aigne_afs_index = require("@aigne/afs-index");
3
+
4
+ //#region src/agent-runners/memory-archive.ts
5
+ /** Upper bound on per-entry `content` payload (chars). Matches design §5. */
6
+ const ARCHIVE_CONTENT_LIMIT = 2048;
7
+ /**
8
+ * Stable 16-char hex hash (two interleaved FNV-1a 32-bit streams). Used by
9
+ * reflection archive to bind reviewer verdicts to the actor output they
10
+ * reviewed via `meta.target_hash`; same primitive is reused by forthcoming
11
+ * G1/G2 wirings for args-hashing.
12
+ */
13
+ function contentHash(s) {
14
+ let a = -2128831035;
15
+ let b = 461845907;
16
+ for (let i = 0; i < s.length; i++) {
17
+ const c = s.charCodeAt(i);
18
+ a = Math.imul(a ^ c, 16777619);
19
+ b = Math.imul(b ^ c + i, 2246822507);
20
+ }
21
+ const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
22
+ return toHex(a) + toHex(b);
23
+ }
24
+ /**
25
+ * Stringify a value for archival `content`, bounding the output at
26
+ * `ARCHIVE_CONTENT_LIMIT` chars (truncated with `…` when over).
27
+ */
28
+ function toContentString(v) {
29
+ const s = typeof v === "string" ? v : JSON.stringify(v ?? "");
30
+ return s.length > ARCHIVE_CONTENT_LIMIT ? `${s.slice(0, ARCHIVE_CONTENT_LIMIT - 1)}…` : s;
31
+ }
32
+ /**
33
+ * Fire-and-forget call to `/memory/.actions/remember`. Silent when `afs`
34
+ * is absent or when the archive dispatch throws / returns non-success —
35
+ * by design, archive failures must never break the surrounding agent
36
+ * flow.
37
+ */
38
+ async function tryRemember(afs, args) {
39
+ if (!afs?.exec) return;
40
+ try {
41
+ await afs.exec("/memory/.actions/remember", args, {});
42
+ } catch {}
43
+ }
44
+ /** Upper bound on per-tool-call archived `content` (chars). */
45
+ const TOOL_CALL_CONTENT_LIMIT = 300;
46
+ /** Paths whose invocations must NEVER be archived (recursion guard). */
47
+ const MEMORY_SELF_ACTION_PREFIX = "/memory/.actions/";
48
+ /**
49
+ * Decides whether `wrapToolProgress` should archive calls for this
50
+ * manifest's `memory` field. Default-on when memory is enabled; only
51
+ * `archiveTools: false` opts out.
52
+ */
53
+ function isMemoryArchiveEnabled(memory) {
54
+ if (memory === true) return true;
55
+ if (memory && typeof memory === "object") return memory.archiveTools !== false;
56
+ return false;
57
+ }
58
+ /**
59
+ * Single-call archive: maps a `ToolCallRecord` to `/memory/.actions/remember`
60
+ * args per design §3.2. Double-guard against self-calls; fire-and-forget.
61
+ *
62
+ * The anchors emitted are a union of three sources:
63
+ *
64
+ * 1. The tool name (`afs_exec`, `custom_tool`, …) — generic but
65
+ * always present.
66
+ * 2. The literal `tool_call` topic — lets cross-session recall
67
+ * filter to "any past tool call" without naming a specific
68
+ * tool.
69
+ * 3. Path-derived topics from `extractPathTopicAnchors(call.path)`
70
+ * — pulls the same tier-1 retrieval signal that lifts R@1 from
71
+ * 13% → 100% on the docs/ corpus into tool-call observations.
72
+ * A run that called `/blocklets/runtime-demos/.actions/
73
+ * s12-memory-recall-run` becomes recallable by topic
74
+ * "s12-memory-recall-run", "runtime-demos", or "memory-recall-run"
75
+ * — not just by the generic "afs_exec".
76
+ *
77
+ * All three coalesce into a single `topic`-typed anchor with deduped
78
+ * `values` so the index keeps the lookup table compact.
79
+ */
80
+ async function archiveToolCall(afs, call) {
81
+ if (!afs?.exec) return;
82
+ if (call.path.startsWith(MEMORY_SELF_ACTION_PREFIX)) return;
83
+ const argsStr = toContentString(call.args);
84
+ const content = argsStr.slice(0, TOOL_CALL_CONTENT_LIMIT);
85
+ const args_hash = contentHash(argsStr);
86
+ const duration_ms = Math.max(0, call.endTs - call.startTs);
87
+ const meta = {
88
+ tool: call.tool,
89
+ path: call.path,
90
+ status: call.status,
91
+ args_hash,
92
+ role: "tool",
93
+ round: call.round,
94
+ session: call.session,
95
+ duration_ms
96
+ };
97
+ if (call.error !== void 0) meta.error = call.error;
98
+ const pathValues = (0, _aigne_afs_index.extractPathTopicAnchors)(call.path).flatMap((a) => a.values);
99
+ await tryRemember(afs, {
100
+ content,
101
+ level: "observation",
102
+ layer: "execution",
103
+ anchors: [{
104
+ type: "topic",
105
+ values: [...new Set([
106
+ call.tool,
107
+ "tool_call",
108
+ ...pathValues
109
+ ])],
110
+ confidence: 1
111
+ }],
112
+ meta
113
+ });
114
+ }
115
+ const ORPHAN_TTL_MS = 6e4;
116
+ function asString(v) {
117
+ return typeof v === "string" ? v : "";
118
+ }
119
+ function asRound(v) {
120
+ return typeof v === "number" ? v : 0;
121
+ }
122
+ /**
123
+ * Wrap a user-supplied `_on_tool_progress` callback so each completed
124
+ * (non-self, non-pending) tool call fires a fire-and-forget archive to
125
+ * the scope-local memory provider. See design §3.2.
126
+ *
127
+ * Always passes every event through to `userCallback` first — archive is
128
+ * a side channel and must be transparent to the caller.
129
+ */
130
+ function wrapToolProgress(userCallback, ctx) {
131
+ const now = ctx.now ?? (() => Date.now());
132
+ const startMap = /* @__PURE__ */ new Map();
133
+ const wrapped = (event) => {
134
+ try {
135
+ userCallback?.(event);
136
+ } catch {}
137
+ const currentNow = now();
138
+ const cutoff = currentNow - ORPHAN_TTL_MS;
139
+ for (const [id, entry] of startMap) if (entry.startTs < cutoff) startMap.delete(id);
140
+ const calls = Array.isArray(event?.calls) ? event.calls : [];
141
+ const eventRound = asRound(event?.round);
142
+ if (event?.type === "tool_start") {
143
+ for (const call of calls) if (call?.id) startMap.set(call.id, {
144
+ startTs: currentNow,
145
+ round: eventRound,
146
+ args: call.args
147
+ });
148
+ return;
149
+ }
150
+ if (event?.type !== "tool_result") return;
151
+ for (const call of calls) {
152
+ if (!call?.id) continue;
153
+ const path = asString(call.path);
154
+ if (path.startsWith(MEMORY_SELF_ACTION_PREFIX)) continue;
155
+ if (call.status === "pending") continue;
156
+ const start = startMap.get(call.id);
157
+ startMap.delete(call.id);
158
+ archiveToolCall(ctx.afs, {
159
+ id: call.id,
160
+ tool: asString(call.tool),
161
+ path,
162
+ status: call.status === "error" ? "error" : "ok",
163
+ args: call.args ?? start?.args,
164
+ error: typeof call.error === "string" ? call.error : void 0,
165
+ startTs: start?.startTs ?? currentNow,
166
+ endTs: currentNow,
167
+ round: start?.round ?? eventRound,
168
+ session: ctx.session
169
+ });
170
+ }
171
+ };
172
+ Object.defineProperty(wrapped, "__pendingSize", {
173
+ value: () => startMap.size,
174
+ enumerable: false,
175
+ configurable: true
176
+ });
177
+ return wrapped;
178
+ }
179
+ const HANDOFF_CONTENT_LIMIT = 200;
180
+ const HANDOFF_DEFAULT_LIMIT = 3;
181
+ /**
182
+ * Opt-in flag: only `{ handoffReadThrough: true }` turns on the appendix.
183
+ * Boolean-form `memory: true` does NOT enable handoff read-through — it
184
+ * stays archive-only by default.
185
+ */
186
+ function isHandoffReadThroughEnabled(memory) {
187
+ if (!memory || typeof memory !== "object") return false;
188
+ return memory.handoffReadThrough === true;
189
+ }
190
+ /**
191
+ * Linear scan of `/memory/${domain}/observations` → top-N execution-layer
192
+ * entries sorted by createdAt DESC. Fire-and-forget on read: every failure
193
+ * mode (missing provider, list rejection, missing path) collapses to `[]`.
194
+ *
195
+ * Intentionally does not filter by anchors/memberName — memory provider
196
+ * stores anchors in the index, not in `meta`, so linear scan can't see
197
+ * them. See design §2.2.2.
198
+ */
199
+ async function recallForHandoff(opts) {
200
+ if (!opts.afs?.list) return [];
201
+ try {
202
+ const path = (0, ufo.joinURL)("/memory", opts.domain, "observations");
203
+ return ((await opts.afs.list(path))?.data ?? []).filter((e) => e.meta?.layer === "execution").sort((a, b) => toEpoch(b.createdAt) - toEpoch(a.createdAt)).slice(0, opts.limit ?? HANDOFF_DEFAULT_LIMIT).map((e) => ({
204
+ content: typeof e.content === "string" ? e.content : JSON.stringify(e.content ?? ""),
205
+ meta: e.meta ?? {},
206
+ createdAt: toEpoch(e.createdAt)
207
+ }));
208
+ } catch {
209
+ return [];
210
+ }
211
+ }
212
+ function toEpoch(v) {
213
+ if (v instanceof Date) return v.getTime();
214
+ if (typeof v === "string" || typeof v === "number") return +new Date(v);
215
+ return 0;
216
+ }
217
+ /**
218
+ * Render recalled entries as a `## 相关历史经验` appendix suitable for
219
+ * appending to a child agent's `_system_append`. Empty input → `""`
220
+ * (no header-only dangle). Each content is defanged against six
221
+ * injection vectors (see design §5.2) and truncated to 200 chars.
222
+ */
223
+ function formatRecallForPrompt(entries) {
224
+ if (entries.length === 0) return "";
225
+ return [
226
+ "## 相关历史经验",
227
+ "",
228
+ "以下是团队最近的 top-3 执行记录,仅供参考;如果与当前任务无关请忽略。",
229
+ "",
230
+ ...entries.map((e, i) => {
231
+ const { round, role } = e.meta ?? {};
232
+ const safe = truncate(defangForPrompt(e.content ?? ""), HANDOFF_CONTENT_LIMIT);
233
+ return `${i + 1}. [${renderRoundLabel(round)}/${renderRoleLabel(role)}] ${safe}`;
234
+ })
235
+ ].join("\n");
236
+ }
237
+ const SAFE_ROLE_LABEL = /^[a-zA-Z0-9_-]{1,32}$/;
238
+ function renderRoundLabel(round) {
239
+ return typeof round === "number" && Number.isFinite(round) ? String(round) : "?";
240
+ }
241
+ function renderRoleLabel(role) {
242
+ return typeof role === "string" && SAFE_ROLE_LABEL.test(role) ? role : "entry";
243
+ }
244
+ /** Unicode ranges that can reorder / hide visible text. */
245
+ const UNICODE_INVISIBLES = /[​‎‏‪-‮]/g;
246
+ /** Neutralize the 6 injection vectors from design §5.2. Heading rule collapses
247
+ * any `#+` level to ` # ` so all depths produce the same neutralized form. */
248
+ function defangForPrompt(text) {
249
+ return text.replace(UNICODE_INVISIBLES, "").replace(/<\|/g, "‹|").replace(/\|>/g, "|›").replace(/^#+\s+/gm, " # ").replace(/^```+/gm, " ```").replace(/^---\s*$/gm, " ---");
250
+ }
251
+ function truncate(text, limit) {
252
+ return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
253
+ }
254
+
255
+ //#endregion
256
+ exports.contentHash = contentHash;
257
+ exports.formatRecallForPrompt = formatRecallForPrompt;
258
+ exports.isHandoffReadThroughEnabled = isHandoffReadThroughEnabled;
259
+ exports.isMemoryArchiveEnabled = isMemoryArchiveEnabled;
260
+ exports.recallForHandoff = recallForHandoff;
261
+ exports.toContentString = toContentString;
262
+ exports.tryRemember = tryRemember;
263
+ exports.wrapToolProgress = wrapToolProgress;
@@ -0,0 +1,47 @@
1
+ import { AFSRoot } from "@aigne/afs";
2
+
3
+ //#region src/agent-runners/memory-archive.d.ts
4
+ /**
5
+ * Decides whether `wrapToolProgress` should archive calls for this
6
+ * manifest's `memory` field. Default-on when memory is enabled; only
7
+ * `archiveTools: false` opts out.
8
+ */
9
+ declare function isMemoryArchiveEnabled(memory: unknown): boolean;
10
+ /**
11
+ * Opt-in flag: only `{ handoffReadThrough: true }` turns on the appendix.
12
+ * Boolean-form `memory: true` does NOT enable handoff read-through — it
13
+ * stays archive-only by default.
14
+ */
15
+ declare function isHandoffReadThroughEnabled(memory: unknown): boolean;
16
+ interface RecalledEntry {
17
+ content: string;
18
+ meta: Record<string, unknown>;
19
+ createdAt: number;
20
+ }
21
+ interface HandoffRecallOptions {
22
+ afs: AFSRoot | undefined;
23
+ /** Resolved domain from `scope.memoryDomain` — NOT from the manifest. */
24
+ domain: string;
25
+ /** Default 3. */
26
+ limit?: number;
27
+ }
28
+ /**
29
+ * Linear scan of `/memory/${domain}/observations` → top-N execution-layer
30
+ * entries sorted by createdAt DESC. Fire-and-forget on read: every failure
31
+ * mode (missing provider, list rejection, missing path) collapses to `[]`.
32
+ *
33
+ * Intentionally does not filter by anchors/memberName — memory provider
34
+ * stores anchors in the index, not in `meta`, so linear scan can't see
35
+ * them. See design §2.2.2.
36
+ */
37
+ declare function recallForHandoff(opts: HandoffRecallOptions): Promise<ReadonlyArray<RecalledEntry>>;
38
+ /**
39
+ * Render recalled entries as a `## 相关历史经验` appendix suitable for
40
+ * appending to a child agent's `_system_append`. Empty input → `""`
41
+ * (no header-only dangle). Each content is defanged against six
42
+ * injection vectors (see design §5.2) and truncated to 200 chars.
43
+ */
44
+ declare function formatRecallForPrompt(entries: ReadonlyArray<RecalledEntry>): string;
45
+ //#endregion
46
+ export { RecalledEntry, formatRecallForPrompt, isHandoffReadThroughEnabled, isMemoryArchiveEnabled, recallForHandoff };
47
+ //# sourceMappingURL=memory-archive.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-archive.d.cts","names":[],"sources":["../../src/agent-runners/memory-archive.ts"],"mappings":";;;AAoTA;;;;;AAAA,iBAzOgB,sBAAA,CAAuB,MAAA;;;;;;iBA6MvB,2BAAA,CAA4B,MAAA;AAAA,UAK3B,aAAA;EACf,OAAA;EACA,IAAA,EAAM,MAAA;EACN,SAAA;AAAA;AAAA,UAGe,oBAAA;EACf,GAAA,EAAK,OAAA;;EAEL,MAAA;;EAEA,KAAA;AAAA;;;;;;;;;;iBAYoB,gBAAA,CACpB,IAAA,EAAM,oBAAA,GACL,OAAA,CAAQ,aAAA,CAAc,aAAA;;;;;;;iBAiCT,qBAAA,CAAsB,OAAA,EAAS,aAAA,CAAc,aAAA"}
@@ -0,0 +1,47 @@
1
+ import { AFSRoot } from "@aigne/afs";
2
+
3
+ //#region src/agent-runners/memory-archive.d.ts
4
+ /**
5
+ * Decides whether `wrapToolProgress` should archive calls for this
6
+ * manifest's `memory` field. Default-on when memory is enabled; only
7
+ * `archiveTools: false` opts out.
8
+ */
9
+ declare function isMemoryArchiveEnabled(memory: unknown): boolean;
10
+ /**
11
+ * Opt-in flag: only `{ handoffReadThrough: true }` turns on the appendix.
12
+ * Boolean-form `memory: true` does NOT enable handoff read-through — it
13
+ * stays archive-only by default.
14
+ */
15
+ declare function isHandoffReadThroughEnabled(memory: unknown): boolean;
16
+ interface RecalledEntry {
17
+ content: string;
18
+ meta: Record<string, unknown>;
19
+ createdAt: number;
20
+ }
21
+ interface HandoffRecallOptions {
22
+ afs: AFSRoot | undefined;
23
+ /** Resolved domain from `scope.memoryDomain` — NOT from the manifest. */
24
+ domain: string;
25
+ /** Default 3. */
26
+ limit?: number;
27
+ }
28
+ /**
29
+ * Linear scan of `/memory/${domain}/observations` → top-N execution-layer
30
+ * entries sorted by createdAt DESC. Fire-and-forget on read: every failure
31
+ * mode (missing provider, list rejection, missing path) collapses to `[]`.
32
+ *
33
+ * Intentionally does not filter by anchors/memberName — memory provider
34
+ * stores anchors in the index, not in `meta`, so linear scan can't see
35
+ * them. See design §2.2.2.
36
+ */
37
+ declare function recallForHandoff(opts: HandoffRecallOptions): Promise<ReadonlyArray<RecalledEntry>>;
38
+ /**
39
+ * Render recalled entries as a `## 相关历史经验` appendix suitable for
40
+ * appending to a child agent's `_system_append`. Empty input → `""`
41
+ * (no header-only dangle). Each content is defanged against six
42
+ * injection vectors (see design §5.2) and truncated to 200 chars.
43
+ */
44
+ declare function formatRecallForPrompt(entries: ReadonlyArray<RecalledEntry>): string;
45
+ //#endregion
46
+ export { RecalledEntry, formatRecallForPrompt, isHandoffReadThroughEnabled, isMemoryArchiveEnabled, recallForHandoff };
47
+ //# sourceMappingURL=memory-archive.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-archive.d.mts","names":[],"sources":["../../src/agent-runners/memory-archive.ts"],"mappings":";;;AAoTA;;;;;AAAA,iBAzOgB,sBAAA,CAAuB,MAAA;;;;;;iBA6MvB,2BAAA,CAA4B,MAAA;AAAA,UAK3B,aAAA;EACf,OAAA;EACA,IAAA,EAAM,MAAA;EACN,SAAA;AAAA;AAAA,UAGe,oBAAA;EACf,GAAA,EAAK,OAAA;;EAEL,MAAA;;EAEA,KAAA;AAAA;;;;;;;;;;iBAYoB,gBAAA,CACpB,IAAA,EAAM,oBAAA,GACL,OAAA,CAAQ,aAAA,CAAc,aAAA;;;;;;;iBAiCT,qBAAA,CAAsB,OAAA,EAAS,aAAA,CAAc,aAAA"}
@@ -0,0 +1,257 @@
1
+ import { joinURL } from "ufo";
2
+ import { extractPathTopicAnchors } from "@aigne/afs-index";
3
+
4
+ //#region src/agent-runners/memory-archive.ts
5
+ /** Upper bound on per-entry `content` payload (chars). Matches design §5. */
6
+ const ARCHIVE_CONTENT_LIMIT = 2048;
7
+ /**
8
+ * Stable 16-char hex hash (two interleaved FNV-1a 32-bit streams). Used by
9
+ * reflection archive to bind reviewer verdicts to the actor output they
10
+ * reviewed via `meta.target_hash`; same primitive is reused by forthcoming
11
+ * G1/G2 wirings for args-hashing.
12
+ */
13
+ function contentHash(s) {
14
+ let a = -2128831035;
15
+ let b = 461845907;
16
+ for (let i = 0; i < s.length; i++) {
17
+ const c = s.charCodeAt(i);
18
+ a = Math.imul(a ^ c, 16777619);
19
+ b = Math.imul(b ^ c + i, 2246822507);
20
+ }
21
+ const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
22
+ return toHex(a) + toHex(b);
23
+ }
24
+ /**
25
+ * Stringify a value for archival `content`, bounding the output at
26
+ * `ARCHIVE_CONTENT_LIMIT` chars (truncated with `…` when over).
27
+ */
28
+ function toContentString(v) {
29
+ const s = typeof v === "string" ? v : JSON.stringify(v ?? "");
30
+ return s.length > ARCHIVE_CONTENT_LIMIT ? `${s.slice(0, ARCHIVE_CONTENT_LIMIT - 1)}…` : s;
31
+ }
32
+ /**
33
+ * Fire-and-forget call to `/memory/.actions/remember`. Silent when `afs`
34
+ * is absent or when the archive dispatch throws / returns non-success —
35
+ * by design, archive failures must never break the surrounding agent
36
+ * flow.
37
+ */
38
+ async function tryRemember(afs, args) {
39
+ if (!afs?.exec) return;
40
+ try {
41
+ await afs.exec("/memory/.actions/remember", args, {});
42
+ } catch {}
43
+ }
44
+ /** Upper bound on per-tool-call archived `content` (chars). */
45
+ const TOOL_CALL_CONTENT_LIMIT = 300;
46
+ /** Paths whose invocations must NEVER be archived (recursion guard). */
47
+ const MEMORY_SELF_ACTION_PREFIX = "/memory/.actions/";
48
+ /**
49
+ * Decides whether `wrapToolProgress` should archive calls for this
50
+ * manifest's `memory` field. Default-on when memory is enabled; only
51
+ * `archiveTools: false` opts out.
52
+ */
53
+ function isMemoryArchiveEnabled(memory) {
54
+ if (memory === true) return true;
55
+ if (memory && typeof memory === "object") return memory.archiveTools !== false;
56
+ return false;
57
+ }
58
+ /**
59
+ * Single-call archive: maps a `ToolCallRecord` to `/memory/.actions/remember`
60
+ * args per design §3.2. Double-guard against self-calls; fire-and-forget.
61
+ *
62
+ * The anchors emitted are a union of three sources:
63
+ *
64
+ * 1. The tool name (`afs_exec`, `custom_tool`, …) — generic but
65
+ * always present.
66
+ * 2. The literal `tool_call` topic — lets cross-session recall
67
+ * filter to "any past tool call" without naming a specific
68
+ * tool.
69
+ * 3. Path-derived topics from `extractPathTopicAnchors(call.path)`
70
+ * — pulls the same tier-1 retrieval signal that lifts R@1 from
71
+ * 13% → 100% on the docs/ corpus into tool-call observations.
72
+ * A run that called `/blocklets/runtime-demos/.actions/
73
+ * s12-memory-recall-run` becomes recallable by topic
74
+ * "s12-memory-recall-run", "runtime-demos", or "memory-recall-run"
75
+ * — not just by the generic "afs_exec".
76
+ *
77
+ * All three coalesce into a single `topic`-typed anchor with deduped
78
+ * `values` so the index keeps the lookup table compact.
79
+ */
80
+ async function archiveToolCall(afs, call) {
81
+ if (!afs?.exec) return;
82
+ if (call.path.startsWith(MEMORY_SELF_ACTION_PREFIX)) return;
83
+ const argsStr = toContentString(call.args);
84
+ const content = argsStr.slice(0, TOOL_CALL_CONTENT_LIMIT);
85
+ const args_hash = contentHash(argsStr);
86
+ const duration_ms = Math.max(0, call.endTs - call.startTs);
87
+ const meta = {
88
+ tool: call.tool,
89
+ path: call.path,
90
+ status: call.status,
91
+ args_hash,
92
+ role: "tool",
93
+ round: call.round,
94
+ session: call.session,
95
+ duration_ms
96
+ };
97
+ if (call.error !== void 0) meta.error = call.error;
98
+ const pathValues = extractPathTopicAnchors(call.path).flatMap((a) => a.values);
99
+ await tryRemember(afs, {
100
+ content,
101
+ level: "observation",
102
+ layer: "execution",
103
+ anchors: [{
104
+ type: "topic",
105
+ values: [...new Set([
106
+ call.tool,
107
+ "tool_call",
108
+ ...pathValues
109
+ ])],
110
+ confidence: 1
111
+ }],
112
+ meta
113
+ });
114
+ }
115
+ const ORPHAN_TTL_MS = 6e4;
116
+ function asString(v) {
117
+ return typeof v === "string" ? v : "";
118
+ }
119
+ function asRound(v) {
120
+ return typeof v === "number" ? v : 0;
121
+ }
122
+ /**
123
+ * Wrap a user-supplied `_on_tool_progress` callback so each completed
124
+ * (non-self, non-pending) tool call fires a fire-and-forget archive to
125
+ * the scope-local memory provider. See design §3.2.
126
+ *
127
+ * Always passes every event through to `userCallback` first — archive is
128
+ * a side channel and must be transparent to the caller.
129
+ */
130
+ function wrapToolProgress(userCallback, ctx) {
131
+ const now = ctx.now ?? (() => Date.now());
132
+ const startMap = /* @__PURE__ */ new Map();
133
+ const wrapped = (event) => {
134
+ try {
135
+ userCallback?.(event);
136
+ } catch {}
137
+ const currentNow = now();
138
+ const cutoff = currentNow - ORPHAN_TTL_MS;
139
+ for (const [id, entry] of startMap) if (entry.startTs < cutoff) startMap.delete(id);
140
+ const calls = Array.isArray(event?.calls) ? event.calls : [];
141
+ const eventRound = asRound(event?.round);
142
+ if (event?.type === "tool_start") {
143
+ for (const call of calls) if (call?.id) startMap.set(call.id, {
144
+ startTs: currentNow,
145
+ round: eventRound,
146
+ args: call.args
147
+ });
148
+ return;
149
+ }
150
+ if (event?.type !== "tool_result") return;
151
+ for (const call of calls) {
152
+ if (!call?.id) continue;
153
+ const path = asString(call.path);
154
+ if (path.startsWith(MEMORY_SELF_ACTION_PREFIX)) continue;
155
+ if (call.status === "pending") continue;
156
+ const start = startMap.get(call.id);
157
+ startMap.delete(call.id);
158
+ archiveToolCall(ctx.afs, {
159
+ id: call.id,
160
+ tool: asString(call.tool),
161
+ path,
162
+ status: call.status === "error" ? "error" : "ok",
163
+ args: call.args ?? start?.args,
164
+ error: typeof call.error === "string" ? call.error : void 0,
165
+ startTs: start?.startTs ?? currentNow,
166
+ endTs: currentNow,
167
+ round: start?.round ?? eventRound,
168
+ session: ctx.session
169
+ });
170
+ }
171
+ };
172
+ Object.defineProperty(wrapped, "__pendingSize", {
173
+ value: () => startMap.size,
174
+ enumerable: false,
175
+ configurable: true
176
+ });
177
+ return wrapped;
178
+ }
179
+ const HANDOFF_CONTENT_LIMIT = 200;
180
+ const HANDOFF_DEFAULT_LIMIT = 3;
181
+ /**
182
+ * Opt-in flag: only `{ handoffReadThrough: true }` turns on the appendix.
183
+ * Boolean-form `memory: true` does NOT enable handoff read-through — it
184
+ * stays archive-only by default.
185
+ */
186
+ function isHandoffReadThroughEnabled(memory) {
187
+ if (!memory || typeof memory !== "object") return false;
188
+ return memory.handoffReadThrough === true;
189
+ }
190
+ /**
191
+ * Linear scan of `/memory/${domain}/observations` → top-N execution-layer
192
+ * entries sorted by createdAt DESC. Fire-and-forget on read: every failure
193
+ * mode (missing provider, list rejection, missing path) collapses to `[]`.
194
+ *
195
+ * Intentionally does not filter by anchors/memberName — memory provider
196
+ * stores anchors in the index, not in `meta`, so linear scan can't see
197
+ * them. See design §2.2.2.
198
+ */
199
+ async function recallForHandoff(opts) {
200
+ if (!opts.afs?.list) return [];
201
+ try {
202
+ const path = joinURL("/memory", opts.domain, "observations");
203
+ return ((await opts.afs.list(path))?.data ?? []).filter((e) => e.meta?.layer === "execution").sort((a, b) => toEpoch(b.createdAt) - toEpoch(a.createdAt)).slice(0, opts.limit ?? HANDOFF_DEFAULT_LIMIT).map((e) => ({
204
+ content: typeof e.content === "string" ? e.content : JSON.stringify(e.content ?? ""),
205
+ meta: e.meta ?? {},
206
+ createdAt: toEpoch(e.createdAt)
207
+ }));
208
+ } catch {
209
+ return [];
210
+ }
211
+ }
212
+ function toEpoch(v) {
213
+ if (v instanceof Date) return v.getTime();
214
+ if (typeof v === "string" || typeof v === "number") return +new Date(v);
215
+ return 0;
216
+ }
217
+ /**
218
+ * Render recalled entries as a `## 相关历史经验` appendix suitable for
219
+ * appending to a child agent's `_system_append`. Empty input → `""`
220
+ * (no header-only dangle). Each content is defanged against six
221
+ * injection vectors (see design §5.2) and truncated to 200 chars.
222
+ */
223
+ function formatRecallForPrompt(entries) {
224
+ if (entries.length === 0) return "";
225
+ return [
226
+ "## 相关历史经验",
227
+ "",
228
+ "以下是团队最近的 top-3 执行记录,仅供参考;如果与当前任务无关请忽略。",
229
+ "",
230
+ ...entries.map((e, i) => {
231
+ const { round, role } = e.meta ?? {};
232
+ const safe = truncate(defangForPrompt(e.content ?? ""), HANDOFF_CONTENT_LIMIT);
233
+ return `${i + 1}. [${renderRoundLabel(round)}/${renderRoleLabel(role)}] ${safe}`;
234
+ })
235
+ ].join("\n");
236
+ }
237
+ const SAFE_ROLE_LABEL = /^[a-zA-Z0-9_-]{1,32}$/;
238
+ function renderRoundLabel(round) {
239
+ return typeof round === "number" && Number.isFinite(round) ? String(round) : "?";
240
+ }
241
+ function renderRoleLabel(role) {
242
+ return typeof role === "string" && SAFE_ROLE_LABEL.test(role) ? role : "entry";
243
+ }
244
+ /** Unicode ranges that can reorder / hide visible text. */
245
+ const UNICODE_INVISIBLES = /[​‎‏‪-‮]/g;
246
+ /** Neutralize the 6 injection vectors from design §5.2. Heading rule collapses
247
+ * any `#+` level to ` # ` so all depths produce the same neutralized form. */
248
+ function defangForPrompt(text) {
249
+ return text.replace(UNICODE_INVISIBLES, "").replace(/<\|/g, "‹|").replace(/\|>/g, "|›").replace(/^#+\s+/gm, " # ").replace(/^```+/gm, " ```").replace(/^---\s*$/gm, " ---");
250
+ }
251
+ function truncate(text, limit) {
252
+ return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
253
+ }
254
+
255
+ //#endregion
256
+ export { contentHash, formatRecallForPrompt, isHandoffReadThroughEnabled, isMemoryArchiveEnabled, recallForHandoff, toContentString, tryRemember, wrapToolProgress };
257
+ //# sourceMappingURL=memory-archive.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-archive.mjs","names":[],"sources":["../../src/agent-runners/memory-archive.ts"],"sourcesContent":["/**\n * Shared helpers for archiving agent observations to the scope-local\n * `/memory/.actions/remember` endpoint. Extracted from reflection.ts so\n * forthcoming wirings (G1 tool-call archive, G2 handoff read-through, …)\n * can share the same content-bounding + hashing + fire-and-forget policy.\n *\n * Phase 0 is a pure move — semantics must remain byte-identical to the\n * versions that previously lived alongside the reflection loop.\n */\n\nimport type { AFSEntry, AFSRoot } from \"@aigne/afs\";\nimport { extractPathTopicAnchors } from \"@aigne/afs-index\";\nimport { joinURL } from \"ufo\";\n\n/** Upper bound on per-entry `content` payload (chars). Matches design §5. */\nexport const ARCHIVE_CONTENT_LIMIT = 2048;\n\n/**\n * Stable 16-char hex hash (two interleaved FNV-1a 32-bit streams). Used by\n * reflection archive to bind reviewer verdicts to the actor output they\n * reviewed via `meta.target_hash`; same primitive is reused by forthcoming\n * G1/G2 wirings for args-hashing.\n */\nexport function contentHash(s: string): string {\n let a = 0x811c9dc5 | 0;\n let b = 0x1b873593 | 0;\n for (let i = 0; i < s.length; i++) {\n const c = s.charCodeAt(i);\n a = Math.imul(a ^ c, 0x01000193);\n b = Math.imul(b ^ (c + i), 0x85ebca6b);\n }\n const toHex = (n: number) => (n >>> 0).toString(16).padStart(8, \"0\");\n return toHex(a) + toHex(b);\n}\n\n/**\n * Stringify a value for archival `content`, bounding the output at\n * `ARCHIVE_CONTENT_LIMIT` chars (truncated with `…` when over).\n */\nexport function toContentString(v: unknown): string {\n const s = typeof v === \"string\" ? v : JSON.stringify(v ?? \"\");\n return s.length > ARCHIVE_CONTENT_LIMIT ? `${s.slice(0, ARCHIVE_CONTENT_LIMIT - 1)}…` : s;\n}\n\n/**\n * Fire-and-forget call to `/memory/.actions/remember`. Silent when `afs`\n * is absent or when the archive dispatch throws / returns non-success —\n * by design, archive failures must never break the surrounding agent\n * flow.\n */\nexport async function tryRemember(\n afs: AFSRoot | undefined,\n args: Record<string, unknown>,\n): Promise<void> {\n if (!afs?.exec) return;\n try {\n await afs.exec(\"/memory/.actions/remember\", args, {});\n } catch {\n // Fire-and-forget: archive failures must never propagate.\n }\n}\n\n// ─── G1: Tool call archive ──────────────────────────────────────────────\n\n/** Upper bound on per-tool-call archived `content` (chars). */\nexport const TOOL_CALL_CONTENT_LIMIT = 300;\n\n/** Paths whose invocations must NEVER be archived (recursion guard). */\nconst MEMORY_SELF_ACTION_PREFIX = \"/memory/.actions/\";\n\n/**\n * Decides whether `wrapToolProgress` should archive calls for this\n * manifest's `memory` field. Default-on when memory is enabled; only\n * `archiveTools: false` opts out.\n */\nexport function isMemoryArchiveEnabled(memory: unknown): boolean {\n if (memory === true) return true;\n if (memory && typeof memory === \"object\") {\n return (memory as { archiveTools?: boolean }).archiveTools !== false;\n }\n return false;\n}\n\nexport interface ToolCallRecord {\n id: string;\n tool: string;\n path: string;\n status: \"ok\" | \"error\";\n args?: unknown;\n error?: string;\n startTs: number;\n endTs: number;\n round: number;\n session: string;\n}\n\n/**\n * Single-call archive: maps a `ToolCallRecord` to `/memory/.actions/remember`\n * args per design §3.2. Double-guard against self-calls; fire-and-forget.\n *\n * The anchors emitted are a union of three sources:\n *\n * 1. The tool name (`afs_exec`, `custom_tool`, …) — generic but\n * always present.\n * 2. The literal `tool_call` topic — lets cross-session recall\n * filter to \"any past tool call\" without naming a specific\n * tool.\n * 3. Path-derived topics from `extractPathTopicAnchors(call.path)`\n * — pulls the same tier-1 retrieval signal that lifts R@1 from\n * 13% → 100% on the docs/ corpus into tool-call observations.\n * A run that called `/blocklets/runtime-demos/.actions/\n * s12-memory-recall-run` becomes recallable by topic\n * \"s12-memory-recall-run\", \"runtime-demos\", or \"memory-recall-run\"\n * — not just by the generic \"afs_exec\".\n *\n * All three coalesce into a single `topic`-typed anchor with deduped\n * `values` so the index keeps the lookup table compact.\n */\nexport async function archiveToolCall(\n afs: AFSRoot | undefined,\n call: ToolCallRecord,\n): Promise<void> {\n if (!afs?.exec) return;\n if (call.path.startsWith(MEMORY_SELF_ACTION_PREFIX)) return;\n\n const argsStr = toContentString(call.args);\n const content = argsStr.slice(0, TOOL_CALL_CONTENT_LIMIT);\n const args_hash = contentHash(argsStr);\n const duration_ms = Math.max(0, call.endTs - call.startTs);\n\n const meta: Record<string, unknown> = {\n tool: call.tool,\n path: call.path,\n status: call.status,\n args_hash,\n role: \"tool\",\n round: call.round,\n session: call.session,\n duration_ms,\n };\n if (call.error !== undefined) meta.error = call.error;\n\n const pathAnchors = extractPathTopicAnchors(call.path);\n const pathValues = pathAnchors.flatMap((a) => a.values);\n const dedupedValues = [...new Set([call.tool, \"tool_call\", ...pathValues])];\n\n await tryRemember(afs, {\n content,\n level: \"observation\",\n layer: \"execution\",\n anchors: [{ type: \"topic\", values: dedupedValues, confidence: 1 }],\n meta,\n });\n}\n\n/** Shape of the events `agent-run` emits via `_on_tool_progress`. */\nexport interface ToolProgressEvent {\n type?: string;\n round?: number;\n calls?: Array<{\n id?: string;\n tool?: string;\n path?: string;\n status?: string;\n args?: unknown;\n result?: unknown;\n error?: string;\n }>;\n [key: string]: unknown;\n}\n\nexport interface WrapToolProgressContext {\n afs: AFSRoot | undefined;\n session: string;\n /** Injectable clock for deterministic orphan-cleanup tests. */\n now?: () => number;\n}\n\nconst ORPHAN_TTL_MS = 60_000;\n\nfunction asString(v: unknown): string {\n return typeof v === \"string\" ? v : \"\";\n}\n\nfunction asRound(v: unknown): number {\n return typeof v === \"number\" ? v : 0;\n}\n\n/**\n * Wrap a user-supplied `_on_tool_progress` callback so each completed\n * (non-self, non-pending) tool call fires a fire-and-forget archive to\n * the scope-local memory provider. See design §3.2.\n *\n * Always passes every event through to `userCallback` first — archive is\n * a side channel and must be transparent to the caller.\n */\nexport function wrapToolProgress(\n userCallback: ((event: ToolProgressEvent) => void) | undefined,\n ctx: WrapToolProgressContext,\n): (event: ToolProgressEvent) => void {\n const now = ctx.now ?? (() => Date.now());\n // ash's `tool_result` events do not carry the original `args` (only id /\n // tool / path / status / result|error). Pair them with `tool_start` here\n // by call.id so archived records preserve the input that was actually run.\n const startMap = new Map<string, { startTs: number; round: number; args: unknown }>();\n\n const wrapped = (event: ToolProgressEvent): void => {\n // Always passthrough first; user callback errors must not break archive.\n try {\n userCallback?.(event);\n } catch {\n // Swallow — archive side-channel must remain transparent.\n }\n\n const currentNow = now();\n const cutoff = currentNow - ORPHAN_TTL_MS;\n for (const [id, entry] of startMap) {\n if (entry.startTs < cutoff) startMap.delete(id);\n }\n\n const calls = Array.isArray(event?.calls) ? event.calls : [];\n const eventRound = asRound(event?.round);\n\n if (event?.type === \"tool_start\") {\n for (const call of calls) {\n if (call?.id) {\n startMap.set(call.id, {\n startTs: currentNow,\n round: eventRound,\n args: call.args,\n });\n }\n }\n return;\n }\n\n if (event?.type !== \"tool_result\") return;\n\n for (const call of calls) {\n if (!call?.id) continue;\n const path = asString(call.path);\n if (path.startsWith(MEMORY_SELF_ACTION_PREFIX)) continue;\n if (call.status === \"pending\") continue;\n\n const start = startMap.get(call.id);\n startMap.delete(call.id);\n void archiveToolCall(ctx.afs, {\n id: call.id,\n tool: asString(call.tool),\n path,\n status: call.status === \"error\" ? \"error\" : \"ok\",\n args: call.args ?? start?.args,\n error: typeof call.error === \"string\" ? call.error : undefined,\n startTs: start?.startTs ?? currentNow,\n endTs: currentNow,\n round: start?.round ?? eventRound,\n session: ctx.session,\n });\n }\n };\n\n Object.defineProperty(wrapped, \"__pendingSize\", {\n value: () => startMap.size,\n enumerable: false,\n configurable: true,\n });\n\n return wrapped;\n}\n\n// ─── G2: Handoff read-through ──────────────────────────────────────────\n\nconst HANDOFF_CONTENT_LIMIT = 200;\nconst HANDOFF_DEFAULT_LIMIT = 3;\n\n/**\n * Opt-in flag: only `{ handoffReadThrough: true }` turns on the appendix.\n * Boolean-form `memory: true` does NOT enable handoff read-through — it\n * stays archive-only by default.\n */\nexport function isHandoffReadThroughEnabled(memory: unknown): boolean {\n if (!memory || typeof memory !== \"object\") return false;\n return (memory as { handoffReadThrough?: boolean }).handoffReadThrough === true;\n}\n\nexport interface RecalledEntry {\n content: string;\n meta: Record<string, unknown>;\n createdAt: number;\n}\n\nexport interface HandoffRecallOptions {\n afs: AFSRoot | undefined;\n /** Resolved domain from `scope.memoryDomain` — NOT from the manifest. */\n domain: string;\n /** Default 3. */\n limit?: number;\n}\n\n/**\n * Linear scan of `/memory/${domain}/observations` → top-N execution-layer\n * entries sorted by createdAt DESC. Fire-and-forget on read: every failure\n * mode (missing provider, list rejection, missing path) collapses to `[]`.\n *\n * Intentionally does not filter by anchors/memberName — memory provider\n * stores anchors in the index, not in `meta`, so linear scan can't see\n * them. See design §2.2.2.\n */\nexport async function recallForHandoff(\n opts: HandoffRecallOptions,\n): Promise<ReadonlyArray<RecalledEntry>> {\n if (!opts.afs?.list) return [];\n try {\n const path = joinURL(\"/memory\", opts.domain, \"observations\");\n const result = await opts.afs.list(path);\n const entries = (result?.data ?? []) as AFSEntry[];\n\n return entries\n .filter((e) => (e.meta as { layer?: string } | undefined)?.layer === \"execution\")\n .sort((a, b) => toEpoch(b.createdAt) - toEpoch(a.createdAt))\n .slice(0, opts.limit ?? HANDOFF_DEFAULT_LIMIT)\n .map((e) => ({\n content: typeof e.content === \"string\" ? e.content : JSON.stringify(e.content ?? \"\"),\n meta: (e.meta ?? {}) as Record<string, unknown>,\n createdAt: toEpoch(e.createdAt),\n }));\n } catch {\n return [];\n }\n}\n\nfunction toEpoch(v: unknown): number {\n if (v instanceof Date) return v.getTime();\n if (typeof v === \"string\" || typeof v === \"number\") return +new Date(v);\n return 0;\n}\n\n/**\n * Render recalled entries as a `## 相关历史经验` appendix suitable for\n * appending to a child agent's `_system_append`. Empty input → `\"\"`\n * (no header-only dangle). Each content is defanged against six\n * injection vectors (see design §5.2) and truncated to 200 chars.\n */\nexport function formatRecallForPrompt(entries: ReadonlyArray<RecalledEntry>): string {\n if (entries.length === 0) return \"\";\n const lines = entries.map((e, i) => {\n const { round, role } = (e.meta ?? {}) as { round?: unknown; role?: unknown };\n const safe = truncate(defangForPrompt(e.content ?? \"\"), HANDOFF_CONTENT_LIMIT);\n return `${i + 1}. [${renderRoundLabel(round)}/${renderRoleLabel(role)}] ${safe}`;\n });\n return [\n \"## 相关历史经验\",\n \"\",\n \"以下是团队最近的 top-3 执行记录,仅供参考;如果与当前任务无关请忽略。\",\n \"\",\n ...lines,\n ].join(\"\\n\");\n}\n\nconst SAFE_ROLE_LABEL = /^[a-zA-Z0-9_-]{1,32}$/;\n\nfunction renderRoundLabel(round: unknown): string {\n return typeof round === \"number\" && Number.isFinite(round) ? String(round) : \"?\";\n}\n\nfunction renderRoleLabel(role: unknown): string {\n return typeof role === \"string\" && SAFE_ROLE_LABEL.test(role) ? role : \"entry\";\n}\n\n/** Unicode ranges that can reorder / hide visible text. */\nconst UNICODE_INVISIBLES = /[​‎‏‪-‮]/g;\n\n/** Neutralize the 6 injection vectors from design §5.2. Heading rule collapses\n * any `#+` level to ` # ` so all depths produce the same neutralized form. */\nfunction defangForPrompt(text: string): string {\n return text\n .replace(UNICODE_INVISIBLES, \"\")\n .replace(/<\\|/g, \"‹|\")\n .replace(/\\|>/g, \"|›\")\n .replace(/^#+\\s+/gm, \" # \")\n .replace(/^```+/gm, \" ```\")\n .replace(/^---\\s*$/gm, \" ---\");\n}\n\nfunction truncate(text: string, limit: number): string {\n return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;\n}\n"],"mappings":"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"}