@botbotgo/agent-harness 0.0.298 → 0.0.299

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 (166) hide show
  1. package/README.md +77 -37
  2. package/README.zh.md +79 -30
  3. package/dist/acp.d.ts +3 -0
  4. package/dist/acp.js +10 -2
  5. package/dist/api.d.ts +14 -2
  6. package/dist/api.js +19 -3
  7. package/dist/cli.d.ts +18 -1
  8. package/dist/cli.js +1408 -319
  9. package/dist/client/acp.d.ts +9 -3
  10. package/dist/client/acp.js +55 -1
  11. package/dist/client/in-process.d.ts +5 -2
  12. package/dist/client/in-process.js +4 -6
  13. package/dist/client/index.d.ts +1 -1
  14. package/dist/client/types.d.ts +6 -5
  15. package/dist/config/agents/direct.yaml +7 -17
  16. package/dist/config/agents/orchestra.yaml +9 -65
  17. package/dist/config/catalogs/embedding-models.yaml +1 -1
  18. package/dist/config/catalogs/stores.yaml +1 -1
  19. package/dist/config/knowledge/knowledge-runtime.yaml +36 -2
  20. package/dist/config/knowledge/procedural-memory-runtime.yaml +78 -0
  21. package/dist/config/{catalogs/models.yaml → models.yaml} +2 -2
  22. package/dist/config/prompts/direct-system.md +16 -0
  23. package/dist/config/prompts/orchestra-system.md +62 -0
  24. package/dist/config/prompts/routing-system.md +14 -0
  25. package/dist/config/runtime/runtime-memory.yaml +39 -5
  26. package/dist/config/runtime/workspace.yaml +7 -16
  27. package/dist/contracts/runtime.d.ts +242 -1
  28. package/dist/contracts/workspace.d.ts +2 -0
  29. package/dist/index.d.ts +5 -3
  30. package/dist/index.js +2 -1
  31. package/dist/init-project.js +178 -33
  32. package/dist/knowledge/contracts.d.ts +5 -0
  33. package/dist/knowledge/module.d.ts +5 -0
  34. package/dist/knowledge/module.js +340 -18
  35. package/dist/package-version.d.ts +1 -1
  36. package/dist/package-version.js +1 -1
  37. package/dist/persistence/file-store.d.ts +5 -1
  38. package/dist/persistence/file-store.js +16 -0
  39. package/dist/persistence/sqlite-store.d.ts +4 -1
  40. package/dist/persistence/sqlite-store.js +88 -14
  41. package/dist/persistence/types.d.ts +4 -1
  42. package/dist/procedural/config.d.ts +63 -0
  43. package/dist/procedural/config.js +125 -0
  44. package/dist/procedural/index.d.ts +2 -0
  45. package/dist/procedural/index.js +1 -0
  46. package/dist/protocol/ag-ui/http.d.ts +3 -0
  47. package/dist/protocol/ag-ui/http.js +10 -0
  48. package/dist/request-events.d.ts +63 -0
  49. package/dist/request-events.js +400 -0
  50. package/dist/resource/isolation.js +11 -0
  51. package/dist/resource/resource-impl.d.ts +1 -0
  52. package/dist/resource/resource-impl.js +103 -12
  53. package/dist/resources/init-templates/agent-context/deep-research.md +5 -0
  54. package/dist/resources/init-templates/prompts/research-analyst-basic.md +1 -0
  55. package/dist/resources/init-templates/prompts/research-analyst-web-search.md +1 -0
  56. package/dist/resources/init-templates/prompts/research-host-deep-research-basic.md +1 -0
  57. package/dist/resources/init-templates/prompts/research-host-deep-research-web-search.md +1 -0
  58. package/dist/resources/init-templates/prompts/research-host-single-agent-basic.md +1 -0
  59. package/dist/resources/init-templates/prompts/research-host-single-agent-web-search.md +1 -0
  60. package/dist/resources/prompts/runtime/browser-capability-disclaimer-recovery.md +1 -0
  61. package/dist/resources/prompts/runtime/default-subagent.md +2 -0
  62. package/dist/resources/prompts/runtime/durable-memory-context.md +7 -0
  63. package/dist/resources/prompts/runtime/execution-with-tool-evidence-retry.md +1 -0
  64. package/dist/resources/prompts/runtime/execution-with-tool-evidence.md +1 -0
  65. package/dist/resources/prompts/runtime/invalid-tool-selection-recovery.md +1 -0
  66. package/dist/resources/prompts/runtime/memory-manager.md +31 -0
  67. package/dist/resources/prompts/runtime/memory-mutation-reconciliation.md +22 -0
  68. package/dist/resources/prompts/runtime/slash-command-skill.md +6 -0
  69. package/dist/resources/prompts/runtime/strict-tool-json.md +1 -0
  70. package/dist/resources/prompts/runtime/workspace-boundary-guidance.md +3 -0
  71. package/dist/resources/prompts/runtime/workspace-relative-path.md +1 -0
  72. package/dist/resources/prompts/runtime/write-todos-descriptive-content.md +1 -0
  73. package/dist/resources/prompts/runtime/write-todos-full-entry.md +1 -0
  74. package/dist/resources/prompts/runtime/write-todos-non-empty-initial-list.md +1 -0
  75. package/dist/resources/tools/_runtime_tool_helpers.mjs +152 -0
  76. package/dist/resources/tools/cancel_request.mjs +21 -0
  77. package/dist/resources/tools/fetch_url.mjs +23 -0
  78. package/dist/resources/tools/http_request.mjs +30 -0
  79. package/dist/resources/tools/inspect_approvals.mjs +27 -0
  80. package/dist/resources/tools/inspect_artifacts.mjs +21 -0
  81. package/dist/resources/tools/inspect_events.mjs +21 -0
  82. package/dist/resources/tools/inspect_requests.mjs +27 -0
  83. package/dist/resources/tools/inspect_sessions.mjs +21 -0
  84. package/dist/resources/tools/list_files.mjs +27 -0
  85. package/dist/resources/tools/read_artifact.mjs +22 -0
  86. package/dist/resources/tools/request_approval.mjs +27 -0
  87. package/dist/resources/tools/run_command.mjs +21 -0
  88. package/dist/resources/tools/schedule_task.mjs +76 -0
  89. package/dist/resources/tools/search_files.mjs +47 -0
  90. package/dist/resources/tools/send_message.mjs +23 -0
  91. package/dist/runtime/adapter/direct-builtin-utility.d.ts +1 -0
  92. package/dist/runtime/adapter/direct-builtin-utility.js +90 -0
  93. package/dist/runtime/adapter/flow/execution-context.d.ts +1 -1
  94. package/dist/runtime/adapter/flow/execution-context.js +1 -1
  95. package/dist/runtime/adapter/flow/invocation-flow.d.ts +1 -0
  96. package/dist/runtime/adapter/flow/invocation-flow.js +9 -1
  97. package/dist/runtime/adapter/flow/invoke-runtime.d.ts +1 -1
  98. package/dist/runtime/adapter/flow/stream-runtime.d.ts +5 -1
  99. package/dist/runtime/adapter/flow/stream-runtime.js +556 -35
  100. package/dist/runtime/adapter/invocation-result.js +3 -2
  101. package/dist/runtime/adapter/local-tool-invocation.d.ts +1 -1
  102. package/dist/runtime/adapter/local-tool-invocation.js +28 -4
  103. package/dist/runtime/adapter/middleware-assembly.js +3 -1
  104. package/dist/runtime/adapter/model/invocation-request.d.ts +4 -1
  105. package/dist/runtime/adapter/model/invocation-request.js +138 -16
  106. package/dist/runtime/adapter/model/message-assembly.js +2 -6
  107. package/dist/runtime/adapter/model/model-providers.js +103 -5
  108. package/dist/runtime/adapter/resilience.js +17 -2
  109. package/dist/runtime/adapter/runtime-adapter-support.d.ts +11 -7
  110. package/dist/runtime/adapter/runtime-adapter-support.js +39 -5
  111. package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +63 -1
  112. package/dist/runtime/adapter/tool/builtin-middleware-tools.js +193 -21
  113. package/dist/runtime/adapter/tool/tool-arguments.d.ts +3 -1
  114. package/dist/runtime/adapter/tool/tool-arguments.js +52 -17
  115. package/dist/runtime/adapter/tool-resolution.d.ts +1 -0
  116. package/dist/runtime/adapter/tool-resolution.js +4 -2
  117. package/dist/runtime/agent-runtime-adapter.d.ts +27 -0
  118. package/dist/runtime/agent-runtime-adapter.js +163 -11
  119. package/dist/runtime/harness/events/event-bus.d.ts +1 -0
  120. package/dist/runtime/harness/events/event-bus.js +3 -0
  121. package/dist/runtime/harness/events/event-sink.d.ts +3 -0
  122. package/dist/runtime/harness/events/event-sink.js +16 -7
  123. package/dist/runtime/harness/events/streaming.d.ts +18 -1
  124. package/dist/runtime/harness/events/streaming.js +23 -10
  125. package/dist/runtime/harness/run/inspection.js +26 -5
  126. package/dist/runtime/harness/run/stream-run.d.ts +13 -4
  127. package/dist/runtime/harness/run/stream-run.js +448 -4
  128. package/dist/runtime/harness/run/surface-semantics.js +7 -34
  129. package/dist/runtime/harness/system/runtime-memory-manager.d.ts +3 -0
  130. package/dist/runtime/harness/system/runtime-memory-manager.js +384 -69
  131. package/dist/runtime/harness/system/runtime-memory-policy.d.ts +20 -1
  132. package/dist/runtime/harness/system/runtime-memory-policy.js +65 -17
  133. package/dist/runtime/harness/system/runtime-memory-records.js +100 -0
  134. package/dist/runtime/harness/system/runtime-memory-sync.js +2 -2
  135. package/dist/runtime/harness/system/store.d.ts +4 -0
  136. package/dist/runtime/harness/system/store.js +153 -0
  137. package/dist/runtime/harness.d.ts +9 -1
  138. package/dist/runtime/harness.js +141 -7
  139. package/dist/runtime/maintenance/sqlite-checkpoint-saver.d.ts +8 -3
  140. package/dist/runtime/maintenance/sqlite-checkpoint-saver.js +152 -53
  141. package/dist/runtime/parsing/output-parsing.d.ts +10 -2
  142. package/dist/runtime/parsing/output-parsing.js +223 -16
  143. package/dist/runtime/parsing/stream-event-parsing.d.ts +7 -0
  144. package/dist/runtime/parsing/stream-event-parsing.js +51 -1
  145. package/dist/runtime/scheduling/system-schedule-manager.d.ts +41 -0
  146. package/dist/runtime/scheduling/system-schedule-manager.js +532 -0
  147. package/dist/runtime/support/embedding-models.d.ts +1 -1
  148. package/dist/runtime/support/embedding-models.js +5 -2
  149. package/dist/runtime/support/runtime-factories.js +1 -1
  150. package/dist/runtime/support/runtime-layout.d.ts +3 -0
  151. package/dist/runtime/support/runtime-layout.js +10 -1
  152. package/dist/runtime/support/runtime-prompts.d.ts +30 -0
  153. package/dist/runtime/support/runtime-prompts.js +55 -0
  154. package/dist/runtime/support/vector-stores.d.ts +1 -1
  155. package/dist/runtime/support/vector-stores.js +5 -2
  156. package/dist/upstream-events.js +8 -7
  157. package/dist/utils/bundled-text.d.ts +3 -0
  158. package/dist/utils/bundled-text.js +25 -0
  159. package/dist/utils/id.js +3 -2
  160. package/dist/workspace/agent-binding-compiler.js +53 -13
  161. package/dist/workspace/object-loader.js +64 -2
  162. package/dist/workspace/support/workspace-ref-utils.d.ts +2 -1
  163. package/dist/workspace/support/workspace-ref-utils.js +24 -5
  164. package/dist/workspace/yaml-object-reader.d.ts +1 -0
  165. package/dist/workspace/yaml-object-reader.js +95 -17
  166. package/package.json +11 -5
@@ -3,7 +3,8 @@ import { extractMessageText } from "../../../utils/message-content.js";
3
3
  import { createResolvedModel } from "../../adapter/model/model-providers.js";
4
4
  import { FileBackedStore } from "./store.js";
5
5
  import { compileModel } from "../../../workspace/resource-compilers.js";
6
- import { resolveRefId } from "../../../workspace/support/workspace-ref-utils.js";
6
+ import { resolvePromptValue, resolveRefId } from "../../../workspace/support/workspace-ref-utils.js";
7
+ import { renderRuntimeMemoryManagerPrompt, renderRuntimeMemoryMutationReconciliationPrompt } from "../../support/runtime-prompts.js";
7
8
  const FORMATION_EVENT_TYPES = new Set([
8
9
  "request.state.changed",
9
10
  "approval.resolved",
@@ -52,14 +53,15 @@ export function readRuntimeMemoryFormationConfig(runtimeMemory, workspaceRoot) {
52
53
  },
53
54
  manager: {
54
55
  enabled: asBoolean(manager?.enabled) ?? true,
55
- strategy: asStrategy(manager?.strategy) ?? "rules",
56
+ strategy: asStrategy(manager?.strategy) ?? "model",
56
57
  modelRef: asNonEmptyString(manager?.modelRef),
58
+ prompt: resolvePromptValue(manager?.prompt, workspaceRoot),
57
59
  maxContextRecords: asPositiveInteger(manager?.maxContextRecords) ?? 12,
58
60
  },
59
61
  background: {
60
62
  enabled: asBoolean(background?.enabled) ?? true,
61
63
  maxMessagesPerRequest: asPositiveInteger(background?.maxMessagesPerRequest) ?? asPositiveInteger(ingestion?.maxMessagesPerRequest) ?? 40,
62
- scopes: normalizeScopeList(asMemoryScopes(background?.scopes) ?? ["session"]),
64
+ scopes: normalizeScopeList(asMemoryScopes(background?.scopes) ?? ["user", "project", "workspace"]),
63
65
  stateStorePath: asNonEmptyString(background?.stateStorePath) ?? `knowledge/${workspaceBaseName}-memory-formation-state.json`,
64
66
  writeOnApprovalResolution: asBoolean(background?.writeOnApprovalResolution) ?? asBoolean(ingestion?.writeOnApprovalResolution) ?? true,
65
67
  writeOnRequestCompletion: asBoolean(background?.writeOnRequestCompletion) ?? asBoolean(ingestion?.writeOnRequestCompletion) ?? true,
@@ -91,29 +93,38 @@ function summarizeApprovals(approvals) {
91
93
  export function createBackgroundMemoryCandidates(input) {
92
94
  const latestUser = excerpt(input.messages.filter((message) => message.role === "user").at(-1));
93
95
  const latestAssistant = excerpt(input.messages.filter((message) => message.role === "assistant").at(-1));
94
- const userContext = latestUser ?? "No durable user context captured.";
95
- const assistantContext = latestAssistant ?? "No durable assistant response captured.";
96
+ const userContext = latestUser ?? "(none)";
97
+ const assistantContext = latestAssistant ?? "(none)";
96
98
  const approvals = summarizeApprovals(input.approvals);
97
99
  const summarySeed = latestUser ?? latestAssistant ?? `Run ${input.requestId}`;
98
100
  const baseSummary = summarySeed.length > 120 ? `${summarySeed.slice(0, 117)}...` : summarySeed;
99
101
  const sourceRefBase = `runtime://sessions/${input.session.sessionId}/requests/${input.requestId}/background-reflection`;
100
- return input.scopes.map((scope) => ({
101
- kind: "episodic",
102
- scope,
103
- sourceType: "runtime-reflection",
104
- sourceRef: `${sourceRefBase}#${scope}`,
105
- summary: `${baseSummary} (${scope})`,
106
- content: [
107
- `Run ${input.requestId} completed for session ${input.session.sessionId}.`,
108
- `Trigger: ${input.trigger}.`,
109
- `Latest user context: ${userContext}`,
110
- `Latest assistant context: ${assistantContext}`,
111
- `Approvals: ${approvals}`,
112
- ].join("\n"),
113
- confidence: 0.72,
114
- observedAt: input.recordedAt,
115
- tags: ["background-reflection", "langmem-aligned", scope, input.trigger],
116
- }));
102
+ return [{
103
+ kind: "episodic",
104
+ sourceType: "runtime-transcript",
105
+ sourceRef: sourceRefBase,
106
+ summary: baseSummary,
107
+ content: [
108
+ "Transcript evidence for durable knowledge extraction.",
109
+ `Trigger: ${input.trigger}`,
110
+ "",
111
+ "Latest user message:",
112
+ userContext,
113
+ "",
114
+ "Latest assistant response:",
115
+ assistantContext,
116
+ "",
117
+ "Approval snapshot:",
118
+ approvals,
119
+ ].join("\n"),
120
+ confidence: 0.72,
121
+ observedAt: input.recordedAt,
122
+ provenance: {
123
+ scopeOptions: input.scopes,
124
+ trigger: input.trigger,
125
+ },
126
+ tags: ["background-extraction", "langmem-aligned", "reusable_summaries", input.trigger],
127
+ }];
117
128
  }
118
129
  function normalizeKind(value, fallback) {
119
130
  if (value === "semantic" || value === "episodic" || value === "procedural") {
@@ -127,12 +138,41 @@ function normalizeScope(value, fallback) {
127
138
  }
128
139
  return fallback;
129
140
  }
141
+ function extractAllowedScopes(candidate) {
142
+ const provenance = typeof candidate.provenance === "object" && candidate.provenance !== null && !Array.isArray(candidate.provenance)
143
+ ? candidate.provenance
144
+ : undefined;
145
+ const scopeOptions = provenance?.scopeOptions;
146
+ if (!Array.isArray(scopeOptions)) {
147
+ return undefined;
148
+ }
149
+ const allowed = scopeOptions
150
+ .filter((item) => item === "session" || item === "agent" || item === "workspace" || item === "user" || item === "project");
151
+ return allowed.length > 0 ? Array.from(new Set(allowed)) : undefined;
152
+ }
153
+ function normalizeScopeWithAllowedSet(value, fallback, allowedScopes) {
154
+ const normalized = normalizeScope(value, fallback);
155
+ if (!allowedScopes || allowedScopes.length === 0) {
156
+ return normalized;
157
+ }
158
+ if (normalized && allowedScopes.includes(normalized)) {
159
+ return normalized;
160
+ }
161
+ if (fallback && allowedScopes.includes(fallback)) {
162
+ return fallback;
163
+ }
164
+ return allowedScopes[0];
165
+ }
130
166
  function normalizeTags(value, fallback) {
167
+ const fallbackTags = Array.isArray(fallback)
168
+ ? fallback.filter((item) => typeof item === "string" && item.trim().length > 0)
169
+ : [];
131
170
  if (!Array.isArray(value)) {
132
- return fallback;
171
+ return fallbackTags.length > 0 ? fallbackTags : fallback;
133
172
  }
134
173
  const tags = value.filter((item) => typeof item === "string" && item.trim().length > 0);
135
- return tags.length > 0 ? tags : fallback;
174
+ const merged = Array.from(new Set([...fallbackTags, ...tags]));
175
+ return merged.length > 0 ? merged : fallback;
136
176
  }
137
177
  function normalizeConfidence(value, fallback) {
138
178
  if (typeof value !== "number" || Number.isNaN(value)) {
@@ -140,15 +180,256 @@ function normalizeConfidence(value, fallback) {
140
180
  }
141
181
  return Math.min(1, Math.max(0, value));
142
182
  }
183
+ function normalizeOperationalRule(value, fallback) {
184
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
185
+ return fallback;
186
+ }
187
+ const typed = value;
188
+ const trigger = typeof typed.trigger === "string" && typed.trigger.trim().length > 0 ? typed.trigger.trim() : undefined;
189
+ const action = typeof typed.action === "string" && typed.action.trim().length > 0 ? typed.action.trim() : undefined;
190
+ const target = typeof typed.target === "string" && typed.target.trim().length > 0 ? typed.target.trim() : undefined;
191
+ const effect = typed.effect === "apply" || typed.effect === "invalidate"
192
+ ? typed.effect
193
+ : undefined;
194
+ if (!trigger || !action || !target) {
195
+ return fallback;
196
+ }
197
+ return { trigger, action, target, ...(effect ? { effect } : {}) };
198
+ }
199
+ function normalizeKnowledgeMutation(value, fallback) {
200
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
201
+ return fallback;
202
+ }
203
+ const typed = value;
204
+ const identity = typeof typed.identity === "string" && typed.identity.trim().length > 0 ? typed.identity.trim() : undefined;
205
+ const operation = typed.operation === "create" || typed.operation === "update" || typed.operation === "delete"
206
+ ? typed.operation
207
+ : undefined;
208
+ if (!identity) {
209
+ return fallback;
210
+ }
211
+ return { identity, ...(operation ? { operation } : {}) };
212
+ }
213
+ function asCandidateOutputs(parsed, candidate, allowedScopes, recordedAt) {
214
+ const rawMutations = Array.isArray(parsed.mutations) ? parsed.mutations : undefined;
215
+ const outputs = rawMutations && rawMutations.length > 0
216
+ ? rawMutations.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item))
217
+ : [parsed];
218
+ return outputs.map((output) => ({
219
+ ...candidate,
220
+ content: typeof output.content === "string" && output.content.trim().length > 0 ? output.content.trim() : candidate.content,
221
+ summary: typeof output.summary === "string" && output.summary.trim().length > 0 ? output.summary.trim() : candidate.summary,
222
+ kind: normalizeKind(output.kind, normalizeKind(candidate.kind, undefined)),
223
+ scope: normalizeScopeWithAllowedSet(output.scope, normalizeScope(candidate.scope, undefined), allowedScopes),
224
+ tags: normalizeTags(output.tags, candidate.tags),
225
+ confidence: normalizeConfidence(output.confidence, candidate.confidence),
226
+ knowledgeMutation: normalizeKnowledgeMutation(output.knowledgeMutation, candidate.knowledgeMutation),
227
+ operationalRule: normalizeOperationalRule(output.operationalRule, candidate.operationalRule),
228
+ observedAt: candidate.observedAt ?? recordedAt,
229
+ }));
230
+ }
143
231
  function extractText(value) {
144
232
  if (typeof value === "string") {
145
233
  return value;
146
234
  }
147
- if (typeof value === "object" && value !== null && "content" in value && typeof value.content === "string") {
235
+ if (typeof value !== "object" || value === null) {
236
+ return undefined;
237
+ }
238
+ if ("content" in value && typeof value.content === "string") {
148
239
  return String(value.content);
149
240
  }
241
+ const kwargs = "kwargs" in value && typeof value.kwargs === "object" && value.kwargs !== null
242
+ ? value.kwargs
243
+ : undefined;
244
+ if (typeof kwargs?.content === "string") {
245
+ return kwargs.content;
246
+ }
150
247
  return undefined;
151
248
  }
249
+ function normalizeCompareText(value) {
250
+ return value.toLowerCase().replace(/\s+/g, " ").trim();
251
+ }
252
+ function tokenizeText(value) {
253
+ return new Set(value
254
+ .toLowerCase()
255
+ .split(/[^a-z0-9_]+/i)
256
+ .map((token) => token.trim())
257
+ .filter((token) => token.length > 2));
258
+ }
259
+ function jaccardTextSimilarity(left, right) {
260
+ const leftTokens = tokenizeText(left);
261
+ const rightTokens = tokenizeText(right);
262
+ if (leftTokens.size === 0 || rightTokens.size === 0) {
263
+ return 0;
264
+ }
265
+ let intersection = 0;
266
+ for (const token of leftTokens) {
267
+ if (rightTokens.has(token)) {
268
+ intersection += 1;
269
+ }
270
+ }
271
+ const union = new Set([...leftTokens, ...rightTokens]).size;
272
+ return union === 0 ? 0 : intersection / union;
273
+ }
274
+ function containsDeletionSignal(value) {
275
+ const normalized = normalizeCompareText(value);
276
+ if (!normalized) {
277
+ return false;
278
+ }
279
+ return [
280
+ "delete",
281
+ "deleted",
282
+ "remove",
283
+ "removed",
284
+ "revoked",
285
+ "forbidden",
286
+ "not allowed",
287
+ "no longer",
288
+ "unavailable",
289
+ "disabled",
290
+ "deprecated",
291
+ "取消",
292
+ "撤销",
293
+ "删除",
294
+ "已删除",
295
+ "删掉",
296
+ "移除",
297
+ "不再",
298
+ "不可用",
299
+ "不能",
300
+ "无法",
301
+ "禁用",
302
+ "废弃",
303
+ ].some((signal) => normalized.includes(signal));
304
+ }
305
+ function containsRuleTarget(candidate, record) {
306
+ const target = record.operationalRule?.target?.trim().toLowerCase();
307
+ if (!target) {
308
+ return false;
309
+ }
310
+ const candidateText = `${candidate.summary ?? ""}\n${candidate.content}`.toLowerCase();
311
+ return candidateText.includes(target);
312
+ }
313
+ function extractSalientLiterals(value) {
314
+ const results = new Set();
315
+ const normalized = value.trim();
316
+ if (!normalized) {
317
+ return results;
318
+ }
319
+ for (const match of normalized.matchAll(/[`'"]([^`'"]{1,64})[`'"]/g)) {
320
+ const literal = match[1]?.trim().toLowerCase();
321
+ if (literal && literal.length >= 2) {
322
+ results.add(literal);
323
+ }
324
+ }
325
+ for (const match of normalized.matchAll(/\b[a-z][a-z0-9._/-]{1,63}\b/gi)) {
326
+ const token = match[0]?.trim().toLowerCase();
327
+ if (token && !["system", "startup", "command", "memory", "instruction", "user", "deleted"].includes(token)) {
328
+ results.add(token);
329
+ }
330
+ }
331
+ return results;
332
+ }
333
+ function sharesSalientLiteral(candidate, record) {
334
+ const candidateLiterals = extractSalientLiterals(`${candidate.summary ?? ""}\n${candidate.content}`);
335
+ const recordLiterals = extractSalientLiterals(`${record.summary}\n${record.content}`);
336
+ if (candidateLiterals.size === 0 || recordLiterals.size === 0) {
337
+ return false;
338
+ }
339
+ for (const literal of candidateLiterals) {
340
+ if (recordLiterals.has(literal)) {
341
+ return true;
342
+ }
343
+ }
344
+ return false;
345
+ }
346
+ function selectMutationFallbackMatch(candidate, existingRecords) {
347
+ const candidateScope = normalizeScope(candidate.scope, undefined);
348
+ return existingRecords
349
+ .filter((record) => record.status === "active" && !!record.knowledgeIdentity && record.knowledgeOperation !== "delete")
350
+ .map((record) => {
351
+ let score = 0;
352
+ if (candidateScope && record.scope === candidateScope) {
353
+ score += 2;
354
+ }
355
+ if (containsRuleTarget(candidate, record)) {
356
+ score += 10;
357
+ }
358
+ if (sharesSalientLiteral(candidate, record)) {
359
+ score += 6;
360
+ }
361
+ if (record.operationalRule) {
362
+ score += 2;
363
+ }
364
+ return { record, score };
365
+ })
366
+ .filter((item) => item.score > 0)
367
+ .sort((left, right) => right.score - left.score || right.record.lastConfirmedAt.localeCompare(left.record.lastConfirmedAt))
368
+ .map((item) => item.record)
369
+ .at(0);
370
+ }
371
+ function applyDeterministicMutationFallback(candidate, existingRecords) {
372
+ const currentMutation = candidate.knowledgeMutation;
373
+ if (currentMutation && currentMutation.operation !== "create") {
374
+ return candidate;
375
+ }
376
+ if (!containsDeletionSignal(`${candidate.summary ?? ""}\n${candidate.content}`)) {
377
+ return candidate;
378
+ }
379
+ const matchingRule = selectMutationFallbackMatch(candidate, existingRecords);
380
+ if (!matchingRule?.knowledgeIdentity) {
381
+ return candidate;
382
+ }
383
+ return {
384
+ ...candidate,
385
+ scope: matchingRule.scope,
386
+ knowledgeMutation: {
387
+ identity: matchingRule.knowledgeIdentity,
388
+ operation: "delete",
389
+ },
390
+ ...(matchingRule.operationalRule
391
+ ? {
392
+ operationalRule: {
393
+ ...matchingRule.operationalRule,
394
+ effect: "invalidate",
395
+ },
396
+ }
397
+ : {}),
398
+ };
399
+ }
400
+ function selectRelatedContextRecords(candidate, existingRecords, maxRecords) {
401
+ const candidateScope = normalizeScope(candidate.scope, undefined);
402
+ const candidateSummary = normalizeCompareText(candidate.summary?.trim() || candidate.content);
403
+ const candidateContent = normalizeCompareText(candidate.content);
404
+ const candidateSourceRef = typeof candidate.sourceRef === "string" ? candidate.sourceRef.trim() : "";
405
+ return existingRecords
406
+ .filter((record) => record.status === "active")
407
+ .map((record) => {
408
+ let score = 0;
409
+ if (candidateScope && record.scope === candidateScope) {
410
+ score += 4;
411
+ }
412
+ if (candidateSourceRef && record.sourceRefs.includes(candidateSourceRef)) {
413
+ score += 12;
414
+ }
415
+ if (normalizeCompareText(record.summary) === candidateSummary) {
416
+ score += 10;
417
+ }
418
+ if (normalizeCompareText(record.content) === candidateContent) {
419
+ score += 9;
420
+ }
421
+ score += jaccardTextSimilarity(record.summary, candidate.summary ?? candidate.content) * 4;
422
+ score += jaccardTextSimilarity(record.content, candidate.content) * 3;
423
+ if (score > 0 && record.lastConfirmedAt) {
424
+ score += Math.max(0, 1 - ((Date.now() - Date.parse(record.lastConfirmedAt)) / (1000 * 60 * 60 * 24 * 365)));
425
+ }
426
+ return { record, score };
427
+ })
428
+ .filter((item) => item.score > 0)
429
+ .sort((left, right) => right.score - left.score || right.record.lastConfirmedAt.localeCompare(left.record.lastConfirmedAt))
430
+ .slice(0, maxRecords)
431
+ .map((item) => item.record);
432
+ }
152
433
  function tryParseJsonObject(text) {
153
434
  try {
154
435
  const parsed = JSON.parse(text);
@@ -168,49 +449,83 @@ function tryParseJsonObject(text) {
168
449
  }
169
450
  }
170
451
  }
171
- function renderManagerPrompt(input) {
172
- const existing = input.existingRecords.length === 0
173
- ? "(none)"
174
- : input.existingRecords
175
- .map((record) => `- scope=${record.scope}; kind=${record.kind}; summary=${record.summary}; status=${record.status}; content=${record.content.replace(/\s+/g, " ").slice(0, 220)}`)
176
- .join("\n");
177
- return [
178
- "You are the runtime memory manager.",
179
- "Decide whether a candidate should be stored as durable memory and refine it if appropriate.",
180
- "Return JSON only.",
181
- "",
182
- "Rules:",
183
- '- Store only durable reusable knowledge. Reject transient chatter, scratchpad, or duplication without added value.',
184
- '- Prefer semantic/episodic/procedural kinds only.',
185
- '- Prefer scopes session/agent/workspace/user/project only.',
186
- '- If the candidate should not be stored, return {"store": false, "reason": "..."}',
187
- '- If it should be stored, return {"store": true, "content": "...", "summary": "...", "kind": "...", "scope": "...", "tags": ["..."], "confidence": 0.0}',
188
- "",
189
- `sessionId=${input.sessionId}`,
190
- `requestId=${input.requestId}`,
191
- "",
192
- "Candidate:",
193
- JSON.stringify(input.candidate, null, 2),
194
- "",
195
- "Existing relevant records:",
196
- existing,
197
- ].join("\n");
452
+ async function reconcileKnowledgeMutation(input) {
453
+ const eligibleExisting = input.existingRecords.filter((record) => typeof record.knowledgeIdentity === "string" && record.knowledgeIdentity.trim().length > 0);
454
+ if (eligibleExisting.length === 0) {
455
+ return input.candidate;
456
+ }
457
+ const currentMutation = input.candidate.knowledgeMutation;
458
+ if (!currentMutation || currentMutation.operation !== "create") {
459
+ return input.candidate;
460
+ }
461
+ try {
462
+ const response = await input.invoker.invoke(renderRuntimeMemoryMutationReconciliationPrompt({
463
+ candidate: input.candidate,
464
+ existingRecords: eligibleExisting,
465
+ }), {});
466
+ const parsed = tryParseJsonObject(extractText(response) ?? "");
467
+ if (!parsed || parsed.reuseExistingIdentity !== true) {
468
+ return input.candidate;
469
+ }
470
+ const identity = typeof parsed.identity === "string" ? parsed.identity.trim() : "";
471
+ const matched = eligibleExisting.find((record) => record.knowledgeIdentity === identity);
472
+ if (!matched) {
473
+ return input.candidate;
474
+ }
475
+ const operation = parsed.operation === "update" || parsed.operation === "delete"
476
+ ? parsed.operation
477
+ : currentMutation.operation;
478
+ const reconciledScope = normalizeScopeWithAllowedSet(parsed.scope, matched.scope, extractAllowedScopes(input.candidate));
479
+ return {
480
+ ...input.candidate,
481
+ knowledgeMutation: {
482
+ identity,
483
+ operation,
484
+ },
485
+ scope: reconciledScope ?? input.candidate.scope ?? matched.scope,
486
+ operationalRule: operation === "delete" && matched.operationalRule
487
+ ? {
488
+ ...matched.operationalRule,
489
+ effect: "invalidate",
490
+ }
491
+ : input.candidate.operationalRule,
492
+ };
493
+ }
494
+ catch {
495
+ return input.candidate;
496
+ }
198
497
  }
199
498
  export async function runModelMemoryManager(input) {
200
- const resolvedModel = await createResolvedModel(input.model, input.modelResolver);
499
+ let resolvedModel;
500
+ try {
501
+ resolvedModel = await createResolvedModel(input.model, input.modelResolver);
502
+ }
503
+ catch {
504
+ return input.candidates;
505
+ }
201
506
  const invoker = resolvedModel;
202
507
  if (typeof invoker.invoke !== "function") {
203
508
  return input.candidates;
204
509
  }
510
+ const callableInvoker = invoker;
205
511
  const transformed = [];
206
512
  for (const candidate of input.candidates) {
207
- const prompt = renderManagerPrompt({
513
+ const allowedScopes = extractAllowedScopes(candidate);
514
+ const prompt = renderRuntimeMemoryManagerPrompt({
208
515
  candidate,
209
516
  sessionId: input.sessionId,
210
517
  requestId: input.requestId,
211
- existingRecords: input.existingRecords,
518
+ existingRecords: selectRelatedContextRecords(candidate, input.existingRecords, input.maxContextRecords ?? input.existingRecords.length),
519
+ template: input.promptTemplate,
212
520
  });
213
- const response = await invoker.invoke(prompt, {});
521
+ let response;
522
+ try {
523
+ response = await callableInvoker.invoke(prompt, {});
524
+ }
525
+ catch {
526
+ transformed.push(candidate);
527
+ continue;
528
+ }
214
529
  const parsed = tryParseJsonObject(extractText(response) ?? "");
215
530
  if (!parsed) {
216
531
  transformed.push(candidate);
@@ -219,16 +534,15 @@ export async function runModelMemoryManager(input) {
219
534
  if (parsed.store === false) {
220
535
  continue;
221
536
  }
222
- transformed.push({
223
- ...candidate,
224
- content: typeof parsed.content === "string" && parsed.content.trim().length > 0 ? parsed.content.trim() : candidate.content,
225
- summary: typeof parsed.summary === "string" && parsed.summary.trim().length > 0 ? parsed.summary.trim() : candidate.summary,
226
- kind: normalizeKind(parsed.kind, normalizeKind(candidate.kind, undefined)),
227
- scope: normalizeScope(parsed.scope, normalizeScope(candidate.scope, undefined)),
228
- tags: normalizeTags(parsed.tags, candidate.tags),
229
- confidence: normalizeConfidence(parsed.confidence, candidate.confidence),
230
- observedAt: candidate.observedAt ?? input.recordedAt,
231
- });
537
+ const refinedCandidates = asCandidateOutputs(parsed, candidate, allowedScopes, input.recordedAt);
538
+ for (const refinedCandidate of refinedCandidates) {
539
+ const reconciledCandidate = await reconcileKnowledgeMutation({
540
+ invoker: callableInvoker,
541
+ candidate: refinedCandidate,
542
+ existingRecords: selectRelatedContextRecords(refinedCandidate, input.existingRecords, input.maxContextRecords ?? input.existingRecords.length),
543
+ });
544
+ transformed.push(applyDeterministicMutationFallback(reconciledCandidate, input.existingRecords));
545
+ }
232
546
  }
233
547
  return transformed;
234
548
  }
@@ -239,9 +553,8 @@ export function createRuntimeMemoryManager(input) {
239
553
  return candidates;
240
554
  }
241
555
  const contextRecords = existingRecords
242
- .filter((record) => record.status === "active")
243
- .slice(-input.config.manager.maxContextRecords);
244
- if (input.config.manager.strategy !== "model") {
556
+ .filter((record) => record.status === "active");
557
+ if (input.config.manager.strategy === "rules") {
245
558
  return candidates;
246
559
  }
247
560
  if (!binding.langchainAgentParams?.model) {
@@ -263,6 +576,8 @@ export function createRuntimeMemoryManager(input) {
263
576
  requestId,
264
577
  recordedAt,
265
578
  existingRecords: contextRecords,
579
+ maxContextRecords: input.config.manager.maxContextRecords,
580
+ promptTemplate: input.config.manager.prompt,
266
581
  modelResolver: input.modelResolver,
267
582
  });
268
583
  },
@@ -4,6 +4,11 @@ export type ResolvedRuntimeMemoryPolicyConfig = {
4
4
  defaultTopK: number;
5
5
  maxPromptMemories: number;
6
6
  };
7
+ writePolicy: {
8
+ mode: "all" | "selective" | "disabled";
9
+ allow: string[];
10
+ deny: string[];
11
+ };
7
12
  namespaces: {
8
13
  session: string;
9
14
  workspace: string;
@@ -21,4 +26,18 @@ export declare function normalizeLangMemMemoryKind(kind: string | undefined): "s
21
26
  export declare function readRuntimeMemoryPolicyConfig(runtimeMemory: Record<string, unknown> | undefined, workspaceRoot: string): ResolvedRuntimeMemoryPolicyConfig | undefined;
22
27
  export declare function readRuntimeMemoryMaintenanceConfig(runtimeMemory: Record<string, unknown> | undefined): ResolvedRuntimeMemoryMaintenanceConfig | undefined;
23
28
  export declare function resolveMemoryNamespace(template: string, values: Record<string, string | undefined>): string[];
24
- export declare function scoreMemoryText(query: string, content: string, scopeBoost?: number): number;
29
+ export declare function hasExplicitResourceReference(text: string): boolean;
30
+ export declare function extractExplicitResourceReferences(text: string): string[];
31
+ export declare function shouldRecallDurableMemory(text: string): boolean;
32
+ export declare function collectMemoryCandidateLabels(candidate: {
33
+ kind?: string;
34
+ scope?: string;
35
+ sourceType?: string;
36
+ tags?: string[];
37
+ }): string[];
38
+ export declare function shouldStoreMemoryCandidate(policy: ResolvedRuntimeMemoryPolicyConfig | null | undefined, candidate: {
39
+ kind?: string;
40
+ scope?: string;
41
+ sourceType?: string;
42
+ tags?: string[];
43
+ }): boolean;