memeloop 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -0
- package/dist/browser.cjs +1413 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +3066 -0
- package/dist/browser.d.ts +3066 -0
- package/dist/browser.js +43 -0
- package/dist/chunk-426CJUZP.js +630 -0
- package/dist/chunk-426CJUZP.js.map +1 -0
- package/dist/chunk-44YHBKDL.js +382 -0
- package/dist/chunk-44YHBKDL.js.map +1 -0
- package/dist/chunk-7ZDTB4VZ.js +63 -0
- package/dist/chunk-7ZDTB4VZ.js.map +1 -0
- package/dist/chunk-AZFYDWIN.js +98 -0
- package/dist/chunk-AZFYDWIN.js.map +1 -0
- package/dist/chunk-BGPU4RR7.js +367 -0
- package/dist/chunk-BGPU4RR7.js.map +1 -0
- package/dist/chunk-CCEFAOSH.js +496 -0
- package/dist/chunk-CCEFAOSH.js.map +1 -0
- package/dist/chunk-FWSY5HOA.js +63 -0
- package/dist/chunk-FWSY5HOA.js.map +1 -0
- package/dist/chunk-GYIOMQMY.js +8726 -0
- package/dist/chunk-GYIOMQMY.js.map +1 -0
- package/dist/chunk-HVB2BR3H.js +42 -0
- package/dist/chunk-HVB2BR3H.js.map +1 -0
- package/dist/chunk-K6P63KD4.js +68 -0
- package/dist/chunk-K6P63KD4.js.map +1 -0
- package/dist/chunk-MRKY4HGF.js +205 -0
- package/dist/chunk-MRKY4HGF.js.map +1 -0
- package/dist/chunk-QFHAAIIM.js +2657 -0
- package/dist/chunk-QFHAAIIM.js.map +1 -0
- package/dist/chunk-VZKQX5PP.js +1 -0
- package/dist/chunk-VZKQX5PP.js.map +1 -0
- package/dist/chunk-WAI6BODI.js +175 -0
- package/dist/chunk-WAI6BODI.js.map +1 -0
- package/dist/chunk-XHRBV6I3.js +3637 -0
- package/dist/chunk-XHRBV6I3.js.map +1 -0
- package/dist/chunk-YMNXDNDW.js +798 -0
- package/dist/chunk-YMNXDNDW.js.map +1 -0
- package/dist/conversation.cjs +208 -0
- package/dist/conversation.cjs.map +1 -0
- package/dist/conversation.d.cts +100 -0
- package/dist/conversation.d.ts +100 -0
- package/dist/conversation.js +21 -0
- package/dist/conversation.js.map +1 -0
- package/dist/device-network-portable.cjs +702 -0
- package/dist/device-network-portable.cjs.map +1 -0
- package/dist/device-network-portable.d.cts +110 -0
- package/dist/device-network-portable.d.ts +110 -0
- package/dist/device-network-portable.js +53 -0
- package/dist/device-network-portable.js.map +1 -0
- package/dist/device-network.cjs +906 -0
- package/dist/device-network.cjs.map +1 -0
- package/dist/device-network.d.cts +6 -0
- package/dist/device-network.d.ts +6 -0
- package/dist/device-network.js +59 -0
- package/dist/device-network.js.map +1 -0
- package/dist/fetchProvider-BpcGVSOG.d.cts +91 -0
- package/dist/fetchProvider-Cm0sO4Tl.d.ts +91 -0
- package/dist/index.cjs +27957 -5459
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +94 -1320
- package/dist/index.d.ts +94 -1320
- package/dist/index.js +10576 -2386
- package/dist/index.js.map +1 -1
- package/dist/interface-IrKBFtL1.d.cts +493 -0
- package/dist/interface-IrKBFtL1.d.ts +493 -0
- package/dist/llm-providers.cjs +43 -8
- package/dist/llm-providers.cjs.map +1 -1
- package/dist/llm-providers.d.cts +2 -1
- package/dist/llm-providers.d.ts +2 -1
- package/dist/llm-providers.js +4 -2
- package/dist/llm-providers.js.map +1 -1
- package/dist/loop-api.cjs +5177 -2050
- package/dist/loop-api.cjs.map +1 -1
- package/dist/loop-api.d.cts +11 -5
- package/dist/loop-api.d.ts +11 -5
- package/dist/loop-api.js +84 -49
- package/dist/mobile.cjs +2625 -0
- package/dist/mobile.cjs.map +1 -0
- package/dist/mobile.d.cts +6 -0
- package/dist/mobile.d.ts +6 -0
- package/dist/mobile.js +22 -0
- package/dist/mobile.js.map +1 -0
- package/dist/orchestration-portable-Bsf8YRfp.d.cts +2192 -0
- package/dist/orchestration-portable-Zw0g5tbj.d.ts +2192 -0
- package/dist/orchestration-portable.cjs +9553 -0
- package/dist/orchestration-portable.cjs.map +1 -0
- package/dist/orchestration-portable.d.cts +5 -0
- package/dist/orchestration-portable.d.ts +5 -0
- package/dist/orchestration-portable.js +290 -0
- package/dist/orchestration-portable.js.map +1 -0
- package/dist/pairingInvite-BXPWY3aM.d.cts +149 -0
- package/dist/pairingInvite-Cuz0wuq9.d.ts +149 -0
- package/dist/promptConcat-WTN-uHcz.d.cts +516 -0
- package/dist/promptConcat-deDSWZE3.d.ts +516 -0
- package/dist/providerRegistry-9trr5aoK.d.ts +86 -0
- package/dist/providerRegistry-xk_B1Ekv.d.cts +86 -0
- package/dist/registry-8eztoD_r.d.cts +393 -0
- package/dist/registry-DV24sI-3.d.ts +393 -0
- package/dist/remoteClient-B_spK019.d.ts +87 -0
- package/dist/remoteClient-CBggnRy3.d.cts +87 -0
- package/dist/runtime-SKSXVYIM.js +18 -0
- package/dist/runtime-SKSXVYIM.js.map +1 -0
- package/dist/scriptDeploymentPipeline-BAeo1dPb.d.cts +2463 -0
- package/dist/scriptDeploymentPipeline-DsnT0AG6.d.ts +2463 -0
- package/dist/syncCloudDevices-BEqwgrhD.d.ts +49 -0
- package/dist/syncCloudDevices-Ds_Kcgrv.d.cts +49 -0
- package/dist/types-7pYti-k9.d.cts +255 -0
- package/dist/types-CdFVSntj.d.ts +255 -0
- package/package.json +110 -35
- package/dist/anthropic.cjs +0 -72
- package/dist/anthropic.cjs.map +0 -1
- package/dist/anthropic.d.cts +0 -21
- package/dist/anthropic.d.ts +0 -21
- package/dist/anthropic.js +0 -21
- package/dist/anthropic.js.map +0 -1
- package/dist/azure.cjs +0 -72
- package/dist/azure.cjs.map +0 -1
- package/dist/azure.d.cts +0 -21
- package/dist/azure.d.ts +0 -21
- package/dist/azure.js +0 -21
- package/dist/azure.js.map +0 -1
- package/dist/chunk-3CN7DFBL.js +0 -4894
- package/dist/chunk-3CN7DFBL.js.map +0 -1
- package/dist/chunk-4AZ4D7U5.js +0 -32
- package/dist/chunk-4AZ4D7U5.js.map +0 -1
- package/dist/chunk-6ON4XSOV.js +0 -63
- package/dist/chunk-6ON4XSOV.js.map +0 -1
- package/dist/chunk-DJQNGIXX.js +0 -4793
- package/dist/chunk-DJQNGIXX.js.map +0 -1
- package/dist/chunk-FKQP6AOQ.js +0 -32
- package/dist/chunk-FKQP6AOQ.js.map +0 -1
- package/dist/chunk-OVJ7JJBA.js +0 -35
- package/dist/chunk-OVJ7JJBA.js.map +0 -1
- package/dist/chunk-P4UZ7IXC.js +0 -42
- package/dist/chunk-YHJ2AEFR.js +0 -4933
- package/dist/chunk-YHJ2AEFR.js.map +0 -1
- package/dist/chunk-YVK7RYRO.js +0 -4973
- package/dist/chunk-YVK7RYRO.js.map +0 -1
- package/dist/code-assistant.json +0 -114
- package/dist/cohere.cjs +0 -72
- package/dist/cohere.cjs.map +0 -1
- package/dist/cohere.d.cts +0 -21
- package/dist/cohere.d.ts +0 -21
- package/dist/cohere.js +0 -21
- package/dist/cohere.js.map +0 -1
- package/dist/deepseek.cjs +0 -72
- package/dist/deepseek.cjs.map +0 -1
- package/dist/deepseek.d.cts +0 -21
- package/dist/deepseek.d.ts +0 -21
- package/dist/deepseek.js +0 -21
- package/dist/deepseek.js.map +0 -1
- package/dist/dist-65U7OYRG.js +0 -1257
- package/dist/dist-65U7OYRG.js.map +0 -1
- package/dist/fetchProvider-Cu4WkU5O.d.cts +0 -819
- package/dist/fetchProvider-Cu4WkU5O.d.ts +0 -819
- package/dist/frontend-ui-ux.json +0 -105
- package/dist/general-assistant.json +0 -95
- package/dist/git-master.json +0 -51
- package/dist/google-vertex.cjs +0 -72
- package/dist/google-vertex.cjs.map +0 -1
- package/dist/google-vertex.d.cts +0 -21
- package/dist/google-vertex.d.ts +0 -21
- package/dist/google-vertex.js +0 -21
- package/dist/google-vertex.js.map +0 -1
- package/dist/google.cjs +0 -72
- package/dist/google.cjs.map +0 -1
- package/dist/google.d.cts +0 -21
- package/dist/google.d.ts +0 -21
- package/dist/google.js +0 -21
- package/dist/google.js.map +0 -1
- package/dist/groq.cjs +0 -72
- package/dist/groq.cjs.map +0 -1
- package/dist/groq.d.cts +0 -21
- package/dist/groq.d.ts +0 -21
- package/dist/groq.js +0 -21
- package/dist/groq.js.map +0 -1
- package/dist/index.d.mts +0 -3203
- package/dist/index.mjs +0 -7064
- package/dist/index.mjs.map +0 -1
- package/dist/mistral.cjs +0 -72
- package/dist/mistral.cjs.map +0 -1
- package/dist/mistral.d.cts +0 -21
- package/dist/mistral.d.ts +0 -21
- package/dist/mistral.js +0 -21
- package/dist/mistral.js.map +0 -1
- package/dist/multipart-parser-EJCXIUHC.js +0 -372
- package/dist/multipart-parser-EJCXIUHC.js.map +0 -1
- package/dist/openai.cjs +0 -72
- package/dist/openai.cjs.map +0 -1
- package/dist/openai.d.cts +0 -21
- package/dist/openai.d.ts +0 -21
- package/dist/openai.js +0 -21
- package/dist/openai.js.map +0 -1
- package/dist/perplexity.cjs +0 -72
- package/dist/perplexity.cjs.map +0 -1
- package/dist/perplexity.d.cts +0 -21
- package/dist/perplexity.d.ts +0 -21
- package/dist/perplexity.js +0 -21
- package/dist/perplexity.js.map +0 -1
- package/dist/playwright.json +0 -52
- package/dist/promptConcat-BJdazTg5.d.cts +0 -1832
- package/dist/promptConcat-BJdazTg5.d.ts +0 -1832
- package/dist/promptConcat-CAkpni_8.d.cts +0 -1834
- package/dist/promptConcat-CAkpni_8.d.ts +0 -1834
- package/dist/promptConcat-CfH5-GAE.d.cts +0 -1036
- package/dist/promptConcat-ChcAZOOL.d.ts +0 -1036
- package/dist/promptConcat-G5TnJVRy.d.cts +0 -1800
- package/dist/promptConcat-G5TnJVRy.d.ts +0 -1800
- package/dist/promptConcat-L72eowzp.d.ts +0 -1090
- package/dist/promptConcat-eLHdN7K8.d.cts +0 -1090
- package/dist/src-VTSXKG72.js +0 -1409
- package/dist/src-VTSXKG72.js.map +0 -1
- package/dist/togetherai.cjs +0 -72
- package/dist/togetherai.cjs.map +0 -1
- package/dist/togetherai.d.cts +0 -21
- package/dist/togetherai.d.ts +0 -21
- package/dist/togetherai.js +0 -21
- package/dist/togetherai.js.map +0 -1
- package/dist/types-B0Jm2l38.d.cts +0 -767
- package/dist/types-B0Jm2l38.d.ts +0 -767
- package/dist/xai.cjs +0 -72
- package/dist/xai.cjs.map +0 -1
- package/dist/xai.d.cts +0 -21
- package/dist/xai.d.ts +0 -21
- package/dist/xai.js +0 -21
- package/dist/xai.js.map +0 -1
- /package/dist/{chunk-P4UZ7IXC.js.map → browser.js.map} +0 -0
|
@@ -0,0 +1,2657 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createChatMessage,
|
|
3
|
+
getChatMessageParts,
|
|
4
|
+
isToolResultPart
|
|
5
|
+
} from "./chunk-WAI6BODI.js";
|
|
6
|
+
import {
|
|
7
|
+
TOOL_OPERATION_API_VERSION,
|
|
8
|
+
TOOL_OPERATION_KIND,
|
|
9
|
+
createToolOperationManifest
|
|
10
|
+
} from "./chunk-BGPU4RR7.js";
|
|
11
|
+
import {
|
|
12
|
+
OrchestrationError
|
|
13
|
+
} from "./chunk-FWSY5HOA.js";
|
|
14
|
+
|
|
15
|
+
// src/permission/engine.ts
|
|
16
|
+
function matchPattern(toolName, pattern) {
|
|
17
|
+
if (pattern === toolName) return true;
|
|
18
|
+
if (pattern === "*") return true;
|
|
19
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
|
|
20
|
+
return new RegExp(`^${escaped}$`).test(toolName);
|
|
21
|
+
}
|
|
22
|
+
function mergePermissionSets(sets) {
|
|
23
|
+
const merged = /* @__PURE__ */ new Map();
|
|
24
|
+
merged.set("allow", /* @__PURE__ */ new Map());
|
|
25
|
+
merged.set("deny", /* @__PURE__ */ new Map());
|
|
26
|
+
merged.set("ask", /* @__PURE__ */ new Map());
|
|
27
|
+
for (const set of sets) {
|
|
28
|
+
for (const rule of set.rules) {
|
|
29
|
+
for (const [action, map] of merged) {
|
|
30
|
+
if (action !== rule.action) {
|
|
31
|
+
map.delete(rule.toolPattern);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
merged.get(rule.action).set(rule.toolPattern, rule.action);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
allow: [...merged.get("allow").keys()],
|
|
39
|
+
deny: [...merged.get("deny").keys()],
|
|
40
|
+
ask: [...merged.get("ask").keys()]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function checkPermission(toolName, merged) {
|
|
44
|
+
const isWildcard = (p) => p.includes("*");
|
|
45
|
+
for (const pattern of merged.deny) {
|
|
46
|
+
if (!isWildcard(pattern) && matchPattern(toolName, pattern)) return "deny";
|
|
47
|
+
}
|
|
48
|
+
for (const pattern of merged.ask) {
|
|
49
|
+
if (!isWildcard(pattern) && matchPattern(toolName, pattern)) return "ask";
|
|
50
|
+
}
|
|
51
|
+
for (const pattern of merged.allow) {
|
|
52
|
+
if (!isWildcard(pattern) && matchPattern(toolName, pattern)) return "allow";
|
|
53
|
+
}
|
|
54
|
+
for (const pattern of merged.deny) {
|
|
55
|
+
if (isWildcard(pattern) && matchPattern(toolName, pattern)) return "deny";
|
|
56
|
+
}
|
|
57
|
+
for (const pattern of merged.ask) {
|
|
58
|
+
if (isWildcard(pattern) && matchPattern(toolName, pattern)) return "ask";
|
|
59
|
+
}
|
|
60
|
+
for (const pattern of merged.allow) {
|
|
61
|
+
if (isWildcard(pattern) && matchPattern(toolName, pattern)) return "allow";
|
|
62
|
+
}
|
|
63
|
+
const hasAnyRules = merged.allow.length > 0 || merged.deny.length > 0 || merged.ask.length > 0;
|
|
64
|
+
return hasAnyRules ? "deny" : "allow";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/orchestration/security/admission.ts
|
|
68
|
+
function defaultAdmissionPolicyForTrustClass(trustClass) {
|
|
69
|
+
switch (trustClass) {
|
|
70
|
+
case "restricted":
|
|
71
|
+
case "quarantine":
|
|
72
|
+
return { defaultAction: "deny", rules: [] };
|
|
73
|
+
default:
|
|
74
|
+
return { defaultAction: "allow", rules: [] };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function resolveAdmissionPolicy(profile, trustClass) {
|
|
78
|
+
const base = defaultAdmissionPolicyForTrustClass(trustClass);
|
|
79
|
+
const overlay = profile?.spec.toolAdmission;
|
|
80
|
+
if (!overlay) return base;
|
|
81
|
+
const defaultAction = trustClass === "trusted" ? overlay.defaultAction ?? base.defaultAction : "deny";
|
|
82
|
+
return {
|
|
83
|
+
defaultAction,
|
|
84
|
+
rules: [...overlay.rules ?? [], ...base.rules ?? []]
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function defaultPermissionActionForTrustClass(trustClass) {
|
|
88
|
+
return trustClass === "restricted" || trustClass === "quarantine" ? "deny" : "allow";
|
|
89
|
+
}
|
|
90
|
+
function evaluateToolAdmission(policy, operation) {
|
|
91
|
+
const toolName = operation.spec.toolRef.name;
|
|
92
|
+
const effect = operation.spec.effect;
|
|
93
|
+
for (const rule of policy.rules ?? []) {
|
|
94
|
+
if (!matchPattern(toolName, rule.toolPattern)) continue;
|
|
95
|
+
if (rule.effects && !rule.effects.includes(effect)) continue;
|
|
96
|
+
return {
|
|
97
|
+
action: rule.action,
|
|
98
|
+
source: "rule",
|
|
99
|
+
reason: rule.reason,
|
|
100
|
+
matchedRule: rule
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return { action: policy.defaultAction, source: "default" };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/orchestration/drivers/unknownEffect.ts
|
|
107
|
+
var TOOL_OPERATION_CONDITION_EFFECT_UNKNOWN = "EffectUnknown";
|
|
108
|
+
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
109
|
+
function attemptsOf(operation) {
|
|
110
|
+
return operation.status?.attempts ?? 0;
|
|
111
|
+
}
|
|
112
|
+
function maxAttemptsOf(operation) {
|
|
113
|
+
return operation.spec.retry?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
114
|
+
}
|
|
115
|
+
function reconcileUnknownEffect(operation, evidence) {
|
|
116
|
+
if (evidence.resultObserved) {
|
|
117
|
+
return { action: "succeeded", reason: "result was observed before the disconnect; only the ack was lost" };
|
|
118
|
+
}
|
|
119
|
+
if (operation.spec.retry?.nonRetryable) {
|
|
120
|
+
return { action: "manual-intervention", reason: "operation is marked nonRetryable and its effect is unknown" };
|
|
121
|
+
}
|
|
122
|
+
const attempts = attemptsOf(operation);
|
|
123
|
+
const maxAttempts = maxAttemptsOf(operation);
|
|
124
|
+
const withinBudget = attempts < maxAttempts;
|
|
125
|
+
if (operation.spec.effect === "read") {
|
|
126
|
+
if (withinBudget) {
|
|
127
|
+
return { action: "retry", reason: `read effect has no side effects (attempt ${attempts + 1}/${maxAttempts})` };
|
|
128
|
+
}
|
|
129
|
+
return { action: "verification-required", reason: `read retry budget exhausted (${attempts}/${maxAttempts})` };
|
|
130
|
+
}
|
|
131
|
+
if (operation.spec.idempotencyKey) {
|
|
132
|
+
if (withinBudget) {
|
|
133
|
+
return {
|
|
134
|
+
action: "retry",
|
|
135
|
+
reason: `idempotencyKey deduplicates repeated delivery (attempt ${attempts + 1}/${maxAttempts})`
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
action: "verification-required",
|
|
140
|
+
reason: `idempotent retry budget exhausted (${attempts}/${maxAttempts}); verify before further attempts`
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
action: "verification-required",
|
|
145
|
+
reason: `effect '${operation.spec.effect}' without idempotencyKey must be verified before any retry`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function effectUnknownCondition(decision, at) {
|
|
149
|
+
return {
|
|
150
|
+
type: TOOL_OPERATION_CONDITION_EFFECT_UNKNOWN,
|
|
151
|
+
status: "True",
|
|
152
|
+
reason: decision.action,
|
|
153
|
+
message: decision.reason,
|
|
154
|
+
lastTransitionTime: at
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function applyUnknownEffectDecision(operation, decision, at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
158
|
+
const conditions = [
|
|
159
|
+
...(operation.status?.conditions ?? []).filter((c) => c.type !== TOOL_OPERATION_CONDITION_EFFECT_UNKNOWN),
|
|
160
|
+
effectUnknownCondition(decision, at)
|
|
161
|
+
];
|
|
162
|
+
const base = {
|
|
163
|
+
...operation.status,
|
|
164
|
+
conditions
|
|
165
|
+
};
|
|
166
|
+
if (decision.action === "retry") {
|
|
167
|
+
return { ...operation, status: { ...base, phase: "Pending" } };
|
|
168
|
+
}
|
|
169
|
+
if (decision.action === "succeeded") {
|
|
170
|
+
return {
|
|
171
|
+
...operation,
|
|
172
|
+
status: { ...base, phase: "Completed", completedAt: at }
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return { ...operation, status: { ...base, phase: "Running" } };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/tools/pluginRegistry.ts
|
|
179
|
+
var defaultPluginRegistry = /* @__PURE__ */ new Map();
|
|
180
|
+
var pluginRegistry = defaultPluginRegistry;
|
|
181
|
+
var activeOverride = null;
|
|
182
|
+
function getActivePluginRegistry() {
|
|
183
|
+
return activeOverride ?? defaultPluginRegistry;
|
|
184
|
+
}
|
|
185
|
+
function runWithPluginRegistry(registry, function_) {
|
|
186
|
+
const previous = activeOverride;
|
|
187
|
+
activeOverride = registry;
|
|
188
|
+
try {
|
|
189
|
+
return function_();
|
|
190
|
+
} finally {
|
|
191
|
+
activeOverride = previous;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function createHookSlot() {
|
|
195
|
+
const handlers = [];
|
|
196
|
+
return {
|
|
197
|
+
handlers,
|
|
198
|
+
tapAsync(_name, function_) {
|
|
199
|
+
handlers.push(function_);
|
|
200
|
+
},
|
|
201
|
+
async promise(context) {
|
|
202
|
+
for (const function_ of handlers) {
|
|
203
|
+
await new Promise((resolve) => {
|
|
204
|
+
function_(context, resolve);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function createAgentFrameworkHooks() {
|
|
211
|
+
return {
|
|
212
|
+
processPrompts: createHookSlot(),
|
|
213
|
+
finalizePrompts: createHookSlot(),
|
|
214
|
+
postProcess: createHookSlot(),
|
|
215
|
+
userMessageReceived: createHookSlot(),
|
|
216
|
+
agentStatusChanged: createHookSlot(),
|
|
217
|
+
toolExecuted: createHookSlot(),
|
|
218
|
+
responseUpdate: createHookSlot(),
|
|
219
|
+
responseComplete: createHookSlot()
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
var hookHandlers = {};
|
|
223
|
+
async function runProcessPromptsHooks(_hooks, context) {
|
|
224
|
+
const slot = _hooks.processPrompts;
|
|
225
|
+
const fns = slot?.handlers ?? hookHandlers.processPrompts ?? [];
|
|
226
|
+
for (const function_ of fns) {
|
|
227
|
+
await new Promise((resolve) => {
|
|
228
|
+
function_(context, resolve);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return context;
|
|
232
|
+
}
|
|
233
|
+
async function runResponseCompleteHooks(hooks, context) {
|
|
234
|
+
await hooks.responseComplete.promise(context);
|
|
235
|
+
}
|
|
236
|
+
async function runPostProcessHooks(hooks, context) {
|
|
237
|
+
await hooks.postProcess.promise(context);
|
|
238
|
+
}
|
|
239
|
+
async function runToolExecutedHooks(hooks, context) {
|
|
240
|
+
await hooks.toolExecuted.promise(context);
|
|
241
|
+
}
|
|
242
|
+
function resolvePromptPluginMap(context) {
|
|
243
|
+
const fromTools = context.tools?.getPromptPlugins?.();
|
|
244
|
+
if (fromTools) return fromTools;
|
|
245
|
+
return getActivePluginRegistry();
|
|
246
|
+
}
|
|
247
|
+
async function createHooksWithPlugins(agentFrameworkConfig, options) {
|
|
248
|
+
const reg = options?.pluginRegistry ?? getActivePluginRegistry();
|
|
249
|
+
const hooks = createAgentFrameworkHooks();
|
|
250
|
+
if (agentFrameworkConfig.plugins) {
|
|
251
|
+
for (const pluginConfig of agentFrameworkConfig.plugins) {
|
|
252
|
+
const { toolId } = pluginConfig;
|
|
253
|
+
const plugin = reg.get(toolId);
|
|
254
|
+
if (plugin) {
|
|
255
|
+
plugin(hooks);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
hooks,
|
|
261
|
+
pluginConfigs: agentFrameworkConfig.plugins ?? []
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/promptUtilities/responsePatternUtility.ts
|
|
266
|
+
import JSON5 from "json5";
|
|
267
|
+
var MAX_FALLBACK_INPUT_LENGTH = 1e3;
|
|
268
|
+
function parseToolParameters(parametersText) {
|
|
269
|
+
if (!parametersText || !parametersText.trim()) {
|
|
270
|
+
return {};
|
|
271
|
+
}
|
|
272
|
+
const trimmedText = parametersText.trim();
|
|
273
|
+
try {
|
|
274
|
+
return JSON.parse(trimmedText);
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
return JSON5.parse(trimmedText);
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
return { input: trimmedText.substring(0, MAX_FALLBACK_INPUT_LENGTH) };
|
|
282
|
+
}
|
|
283
|
+
function extractFunctionCallsParameters(text) {
|
|
284
|
+
const parameters = {};
|
|
285
|
+
const parameterRegex = /<parameter\s+name="([^"]+)"[^>]*>([^<]*)<\/parameter>/g;
|
|
286
|
+
let m;
|
|
287
|
+
while ((m = parameterRegex.exec(text)) !== null) {
|
|
288
|
+
parameters[m[1]] = m[2].trim();
|
|
289
|
+
}
|
|
290
|
+
return parameters;
|
|
291
|
+
}
|
|
292
|
+
var toolPatterns = [
|
|
293
|
+
{
|
|
294
|
+
name: "tool_use",
|
|
295
|
+
pattern: /<tool_use\s+name="([^"]+)"[^>]*>(.*?)<\/tool_use>/gis,
|
|
296
|
+
extractToolId: (match) => match[1],
|
|
297
|
+
extractParams: (match) => match[2],
|
|
298
|
+
extractOriginalText: (match) => match[0]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "function_call",
|
|
302
|
+
pattern: /<function_call\s+name="([^"]+)"[^>]*>(.*?)<\/function_call>/gis,
|
|
303
|
+
extractToolId: (match) => match[1],
|
|
304
|
+
extractParams: (match) => match[2],
|
|
305
|
+
extractOriginalText: (match) => match[0]
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: "function_calls_invoke",
|
|
309
|
+
pattern: /<invoke\s+name="([^"]+)"[^>]*>(.*?)<\/invoke>/gis,
|
|
310
|
+
extractToolId: (match) => match[1],
|
|
311
|
+
extractParams: (match) => JSON.stringify(extractFunctionCallsParameters(match[2])),
|
|
312
|
+
extractOriginalText: (match) => match[0]
|
|
313
|
+
}
|
|
314
|
+
];
|
|
315
|
+
function matchToolCalling(responseText) {
|
|
316
|
+
try {
|
|
317
|
+
for (const toolPattern of toolPatterns) {
|
|
318
|
+
toolPattern.pattern.lastIndex = 0;
|
|
319
|
+
const match = toolPattern.pattern.exec(responseText);
|
|
320
|
+
if (match) {
|
|
321
|
+
const toolId = toolPattern.extractToolId(match);
|
|
322
|
+
const parametersText = toolPattern.extractParams(match);
|
|
323
|
+
const originalText = toolPattern.extractOriginalText(match);
|
|
324
|
+
return {
|
|
325
|
+
found: true,
|
|
326
|
+
toolId,
|
|
327
|
+
parameters: parseToolParameters(parametersText),
|
|
328
|
+
originalText
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return { found: false };
|
|
333
|
+
} catch {
|
|
334
|
+
return { found: false };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function matchAllToolCallings(responseText) {
|
|
338
|
+
const calls = [];
|
|
339
|
+
const parallel = /<parallel_tool_calls>/i.test(responseText);
|
|
340
|
+
try {
|
|
341
|
+
for (const toolPattern of toolPatterns) {
|
|
342
|
+
toolPattern.pattern.lastIndex = 0;
|
|
343
|
+
let match;
|
|
344
|
+
while ((match = toolPattern.pattern.exec(responseText)) !== null) {
|
|
345
|
+
calls.push({
|
|
346
|
+
found: true,
|
|
347
|
+
toolId: toolPattern.extractToolId(match),
|
|
348
|
+
parameters: parseToolParameters(toolPattern.extractParams(match)),
|
|
349
|
+
originalText: toolPattern.extractOriginalText(match)
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
return { calls, parallel };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/storage/nextLamport.ts
|
|
359
|
+
async function nextLamportClockForConversation(storage, conversationId) {
|
|
360
|
+
if (typeof storage.getMaxLamportClockForConversation === "function") {
|
|
361
|
+
const max2 = await storage.getMaxLamportClockForConversation(conversationId);
|
|
362
|
+
return max2 + 1;
|
|
363
|
+
}
|
|
364
|
+
const msgs = await storage.getMessages(conversationId, { mode: "full-content" });
|
|
365
|
+
let max = 0;
|
|
366
|
+
for (const m of msgs) {
|
|
367
|
+
if (typeof m.lamportClock === "number" && m.lamportClock > max) {
|
|
368
|
+
max = m.lamportClock;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return max + 1;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/loopAPI/hooks/registry.ts
|
|
375
|
+
var HookRegistry = class {
|
|
376
|
+
hookRegistry = /* @__PURE__ */ new Map();
|
|
377
|
+
hookOrder = /* @__PURE__ */ new Map();
|
|
378
|
+
/**
|
|
379
|
+
* Register a hook handler for a specific lifecycle event.
|
|
380
|
+
*/
|
|
381
|
+
registerHook(type, handler, name) {
|
|
382
|
+
const key = name ?? `hook:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 8)}`;
|
|
383
|
+
const handlers = this.hookOrder.get(type) ?? [];
|
|
384
|
+
handlers.push(handler);
|
|
385
|
+
this.hookOrder.set(type, handlers);
|
|
386
|
+
const map = this.hookRegistry.get(type) ?? /* @__PURE__ */ new Map();
|
|
387
|
+
map.set(key, handler);
|
|
388
|
+
this.hookRegistry.set(type, map);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Unregister a specific hook handler by name.
|
|
392
|
+
*/
|
|
393
|
+
unregisterHook(type, name) {
|
|
394
|
+
const map = this.hookRegistry.get(type);
|
|
395
|
+
if (!map) return false;
|
|
396
|
+
const deleted = map.delete(name);
|
|
397
|
+
if (deleted) {
|
|
398
|
+
this.hookOrder.set(type, Array.from(map.values()));
|
|
399
|
+
}
|
|
400
|
+
return deleted;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Execute all registered hooks for a given type in registration order.
|
|
404
|
+
*/
|
|
405
|
+
async executeHooks(type, context, data) {
|
|
406
|
+
const handlers = this.hookOrder.get(type);
|
|
407
|
+
if (!handlers || handlers.length === 0) {
|
|
408
|
+
return { allowed: true };
|
|
409
|
+
}
|
|
410
|
+
let currentData = data;
|
|
411
|
+
let mergedModified;
|
|
412
|
+
let permissionAction;
|
|
413
|
+
for (const handler of handlers) {
|
|
414
|
+
try {
|
|
415
|
+
const result = await handler(context, currentData);
|
|
416
|
+
if (result.modified) {
|
|
417
|
+
mergedModified = { ...mergedModified ?? {}, ...result.modified };
|
|
418
|
+
currentData = { ...currentData, ...result.modified };
|
|
419
|
+
}
|
|
420
|
+
if (result.permissionAction && result.permissionAction !== "allow") {
|
|
421
|
+
permissionAction = result.permissionAction;
|
|
422
|
+
}
|
|
423
|
+
if (!result.allowed) {
|
|
424
|
+
return {
|
|
425
|
+
...result,
|
|
426
|
+
modified: mergedModified ?? result.modified,
|
|
427
|
+
permissionAction: permissionAction ?? result.permissionAction
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
} catch (error) {
|
|
431
|
+
return {
|
|
432
|
+
allowed: false,
|
|
433
|
+
reason: error instanceof Error ? error.message : "Hook execution failed",
|
|
434
|
+
modified: mergedModified,
|
|
435
|
+
permissionAction
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const finalResult = { allowed: true };
|
|
440
|
+
if (mergedModified) finalResult.modified = mergedModified;
|
|
441
|
+
if (permissionAction) finalResult.permissionAction = permissionAction;
|
|
442
|
+
return finalResult;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Check if any hooks are registered for a given type.
|
|
446
|
+
*/
|
|
447
|
+
hasHooks(type) {
|
|
448
|
+
const map = this.hookRegistry.get(type);
|
|
449
|
+
return map != null && map.size > 0;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Remove all registered hooks of all types.
|
|
453
|
+
*/
|
|
454
|
+
clearHooks() {
|
|
455
|
+
this.hookRegistry.clear();
|
|
456
|
+
this.hookOrder.clear();
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* List all hook types that have at least one registered handler.
|
|
460
|
+
*/
|
|
461
|
+
listRegisteredHookTypes() {
|
|
462
|
+
const types = [];
|
|
463
|
+
for (const [type, map] of this.hookRegistry) {
|
|
464
|
+
if (map.size > 0) {
|
|
465
|
+
types.push(type);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return types;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Get the count of registered handlers for a hook type.
|
|
472
|
+
*/
|
|
473
|
+
getHookCount(type) {
|
|
474
|
+
const map = this.hookRegistry.get(type);
|
|
475
|
+
return map?.size ?? 0;
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
var defaultHookRegistry = new HookRegistry();
|
|
479
|
+
function getDefaultHookRegistry() {
|
|
480
|
+
return defaultHookRegistry;
|
|
481
|
+
}
|
|
482
|
+
function registerHook(type, handler, name) {
|
|
483
|
+
defaultHookRegistry.registerHook(type, handler, name);
|
|
484
|
+
}
|
|
485
|
+
function unregisterHook(type, name) {
|
|
486
|
+
return defaultHookRegistry.unregisterHook(type, name);
|
|
487
|
+
}
|
|
488
|
+
async function executeHooks(type, context, data) {
|
|
489
|
+
return defaultHookRegistry.executeHooks(type, context, data);
|
|
490
|
+
}
|
|
491
|
+
function hasHooks(type) {
|
|
492
|
+
return defaultHookRegistry.hasHooks(type);
|
|
493
|
+
}
|
|
494
|
+
function clearHooks() {
|
|
495
|
+
defaultHookRegistry.clearHooks();
|
|
496
|
+
}
|
|
497
|
+
function listRegisteredHookTypes() {
|
|
498
|
+
return defaultHookRegistry.listRegisteredHookTypes();
|
|
499
|
+
}
|
|
500
|
+
function getHookCount(type) {
|
|
501
|
+
return defaultHookRegistry.getHookCount(type);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/loopAPI/agent-tool-loop/compaction.ts
|
|
505
|
+
function countTurns(messages) {
|
|
506
|
+
let turns = 0;
|
|
507
|
+
for (const message of messages) {
|
|
508
|
+
if (message.role === "user") {
|
|
509
|
+
turns++;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return turns;
|
|
513
|
+
}
|
|
514
|
+
function estimateTokens(messages) {
|
|
515
|
+
let total = 0;
|
|
516
|
+
for (const message of messages) {
|
|
517
|
+
const content = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
|
|
518
|
+
total += content.length / 3.5;
|
|
519
|
+
}
|
|
520
|
+
return Math.ceil(total);
|
|
521
|
+
}
|
|
522
|
+
function buildTruncationSummary(dropped, totalDropped) {
|
|
523
|
+
const turns = countTurns(dropped);
|
|
524
|
+
const oldest = dropped[0];
|
|
525
|
+
const newest = dropped[dropped.length - 1];
|
|
526
|
+
const oldestTime = oldest?.timestamp ? new Date(oldest.timestamp).toISOString() : "unknown";
|
|
527
|
+
const newestTime = newest?.timestamp ? new Date(newest.timestamp).toISOString() : "unknown";
|
|
528
|
+
return `[context-summary] ${totalDropped} earlier messages (${turns} turns, ${oldestTime} \u2192 ${newestTime}) were compacted. Key topics: see recent messages below.`;
|
|
529
|
+
}
|
|
530
|
+
function createSummaryMessage(conversationId, summaryText, baseMessage) {
|
|
531
|
+
return {
|
|
532
|
+
messageId: `${conversationId}:compacted:${Date.now().toString(36)}`,
|
|
533
|
+
conversationId,
|
|
534
|
+
originNodeId: baseMessage.originNodeId,
|
|
535
|
+
timestamp: Date.now(),
|
|
536
|
+
lamportClock: -1,
|
|
537
|
+
// Will be replaced by AgentToolLoop
|
|
538
|
+
role: "assistant",
|
|
539
|
+
content: summaryText,
|
|
540
|
+
metadata: { compacted: true }
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function compactMessages(messages, options = {}) {
|
|
544
|
+
const recentTurnsToKeep = options.recentTurnsToKeep ?? 4;
|
|
545
|
+
if (messages.length <= recentTurnsToKeep * 2) {
|
|
546
|
+
return { messages, compacted: false, droppedCount: 0, summaryText: "" };
|
|
547
|
+
}
|
|
548
|
+
const userIndices = [];
|
|
549
|
+
for (let index = 0; index < messages.length; index++) {
|
|
550
|
+
if (messages[index].role === "user") {
|
|
551
|
+
userIndices.push(index);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (userIndices.length <= recentTurnsToKeep) {
|
|
555
|
+
return { messages, compacted: false, droppedCount: 0, summaryText: "" };
|
|
556
|
+
}
|
|
557
|
+
const keepStartIndex = userIndices[userIndices.length - recentTurnsToKeep];
|
|
558
|
+
const dropped = messages.slice(0, keepStartIndex);
|
|
559
|
+
const kept = messages.slice(keepStartIndex);
|
|
560
|
+
if (dropped.length === 0) {
|
|
561
|
+
return { messages, compacted: false, droppedCount: 0, summaryText: "" };
|
|
562
|
+
}
|
|
563
|
+
const conversationId = messages[0]?.conversationId ?? "unknown";
|
|
564
|
+
const summaryText = buildTruncationSummary(dropped, dropped.length);
|
|
565
|
+
const summaryMessage = createSummaryMessage(conversationId, summaryText, messages[0]);
|
|
566
|
+
const compacted = [summaryMessage, ...kept];
|
|
567
|
+
return {
|
|
568
|
+
messages: compacted,
|
|
569
|
+
compacted: true,
|
|
570
|
+
droppedCount: dropped.length,
|
|
571
|
+
summaryText
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function shouldCompact(messages, threshold) {
|
|
575
|
+
const t = threshold ?? 50;
|
|
576
|
+
return messages.length > t;
|
|
577
|
+
}
|
|
578
|
+
async function autoCompact(messages, options = {}) {
|
|
579
|
+
const recentTurnsToKeep = options.recentTurnsToKeep ?? 4;
|
|
580
|
+
if (options.maxTokens && options.maxTokens > 0) {
|
|
581
|
+
const currentTokens = estimateTokens(messages);
|
|
582
|
+
if (currentTokens <= options.maxTokens) {
|
|
583
|
+
return { messages, compacted: false, droppedCount: 0, summaryText: "" };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (options.useLlmSummary !== false && options.llmProvider?.model != null) {
|
|
587
|
+
try {
|
|
588
|
+
const result = await llmCompact(messages, options.llmProvider, recentTurnsToKeep);
|
|
589
|
+
if (result) return result;
|
|
590
|
+
} catch {
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return compactMessages(messages, { ...options });
|
|
594
|
+
}
|
|
595
|
+
async function llmCompact(messages, llmProvider, recentTurnsToKeep) {
|
|
596
|
+
const userIndices = [];
|
|
597
|
+
for (let index = 0; index < messages.length; index++) {
|
|
598
|
+
if (messages[index].role === "user") {
|
|
599
|
+
userIndices.push(index);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (userIndices.length <= recentTurnsToKeep) {
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
const keepStartIndex = userIndices[userIndices.length - recentTurnsToKeep];
|
|
606
|
+
const toSummarize = messages.slice(0, keepStartIndex);
|
|
607
|
+
const kept = messages.slice(keepStartIndex);
|
|
608
|
+
if (toSummarize.length === 0) return null;
|
|
609
|
+
const conversationId = messages[0]?.conversationId ?? "unknown";
|
|
610
|
+
const conversationText = toSummarize.map((m) => {
|
|
611
|
+
const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
|
|
612
|
+
const toolId = typeof m.metadata?.toolId === "string" ? m.metadata.toolId : "unknown";
|
|
613
|
+
const roleLabel = m.role === "tool" ? `[tool: ${toolId}]` : `[${m.role}]`;
|
|
614
|
+
if (m.role === "tool" && content.length > 500) {
|
|
615
|
+
return `${roleLabel} ${content.slice(0, 500)}... (truncated)`;
|
|
616
|
+
}
|
|
617
|
+
return `${roleLabel} ${content}`;
|
|
618
|
+
}).join("\n\n");
|
|
619
|
+
const prompt = `Summarize the following conversation excerpt. Be concise but capture key decisions, action items, tool calls, and important context. Focus on information useful for continuing the conversation.
|
|
620
|
+
|
|
621
|
+
<conversation>
|
|
622
|
+
${conversationText.slice(0, 8e3)}
|
|
623
|
+
</conversation>
|
|
624
|
+
|
|
625
|
+
Provide a brief summary (3-5 paragraphs) of the key points.`;
|
|
626
|
+
try {
|
|
627
|
+
const summaryText = await generateSummary(llmProvider, prompt);
|
|
628
|
+
if (!summaryText || summaryText.length < 10) return null;
|
|
629
|
+
const summaryMessage = createSummaryMessage(
|
|
630
|
+
conversationId,
|
|
631
|
+
`[context-summary] ${summaryText}`,
|
|
632
|
+
messages[0]
|
|
633
|
+
);
|
|
634
|
+
return {
|
|
635
|
+
messages: [summaryMessage, ...kept],
|
|
636
|
+
compacted: true,
|
|
637
|
+
droppedCount: toSummarize.length,
|
|
638
|
+
summaryText
|
|
639
|
+
};
|
|
640
|
+
} catch {
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
async function generateSummary(llmProvider, prompt) {
|
|
645
|
+
if (typeof llmProvider.chat !== "function") return null;
|
|
646
|
+
const raw = llmProvider.chat({ messages: [{ role: "user", content: prompt }] });
|
|
647
|
+
let resolved = raw;
|
|
648
|
+
if (resolved != null && typeof resolved.then === "function") {
|
|
649
|
+
resolved = await resolved;
|
|
650
|
+
}
|
|
651
|
+
if (resolved != null && typeof resolved[Symbol.asyncIterator] === "function") {
|
|
652
|
+
let text = "";
|
|
653
|
+
for await (const chunk of resolved) {
|
|
654
|
+
text += chunkToText(chunk);
|
|
655
|
+
}
|
|
656
|
+
return text || null;
|
|
657
|
+
}
|
|
658
|
+
if (typeof resolved === "string") return resolved;
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
function chunkToText(chunk) {
|
|
662
|
+
if (typeof chunk === "string") return chunk;
|
|
663
|
+
if (chunk != null && typeof chunk === "object" && "content" in chunk) {
|
|
664
|
+
const content = chunk.content;
|
|
665
|
+
return typeof content === "string" ? content : JSON.stringify(content);
|
|
666
|
+
}
|
|
667
|
+
return JSON.stringify(chunk);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/promptUtilities/promptConcat.ts
|
|
671
|
+
function collectPromptSourcePaths(prompts, basePath = "agentFrameworkConfig.prompts") {
|
|
672
|
+
const out = {};
|
|
673
|
+
function walk(nodes, prefix) {
|
|
674
|
+
nodes.forEach((n, index) => {
|
|
675
|
+
const p = `${prefix}.${index}`;
|
|
676
|
+
if (n.id) {
|
|
677
|
+
out[n.id] = p;
|
|
678
|
+
}
|
|
679
|
+
if (n.children?.length) {
|
|
680
|
+
walk(n.children, `${p}.children`);
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
walk(prompts, basePath);
|
|
685
|
+
return out;
|
|
686
|
+
}
|
|
687
|
+
var logger = {
|
|
688
|
+
debug: (..._arguments) => {
|
|
689
|
+
},
|
|
690
|
+
info: (..._arguments) => {
|
|
691
|
+
},
|
|
692
|
+
warn: (..._arguments) => {
|
|
693
|
+
},
|
|
694
|
+
error: (..._arguments) => {
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
function findPromptById(prompts, id) {
|
|
698
|
+
for (let index = 0; index < prompts.length; index++) {
|
|
699
|
+
const prompt = prompts[index];
|
|
700
|
+
if (prompt.id === id) {
|
|
701
|
+
return { prompt, parent: prompts, index };
|
|
702
|
+
}
|
|
703
|
+
if (prompt.children) {
|
|
704
|
+
const found = findPromptById(prompt.children, id);
|
|
705
|
+
if (found) return found;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return void 0;
|
|
709
|
+
}
|
|
710
|
+
function flattenPrompts(prompts) {
|
|
711
|
+
const result = [];
|
|
712
|
+
function processPrompt(prompt) {
|
|
713
|
+
let text = prompt.text ?? "";
|
|
714
|
+
if (prompt.children) {
|
|
715
|
+
for (const child of prompt.children) {
|
|
716
|
+
if (!child.role) {
|
|
717
|
+
text += processPrompt(child);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return text;
|
|
722
|
+
}
|
|
723
|
+
function collectRolePrompts(nodes) {
|
|
724
|
+
for (const prompt of nodes) {
|
|
725
|
+
if (prompt.enabled === false) continue;
|
|
726
|
+
const content = processPrompt(prompt);
|
|
727
|
+
if (content.trim() || prompt.role) {
|
|
728
|
+
result.push({
|
|
729
|
+
role: prompt.role ?? "system",
|
|
730
|
+
content: content.trim()
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (prompt.children) {
|
|
734
|
+
collectRolePrompts(prompt.children);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
collectRolePrompts(prompts);
|
|
739
|
+
return result;
|
|
740
|
+
}
|
|
741
|
+
async function* promptConcatStream(agentConfig, messages, agentFrameworkContext, options) {
|
|
742
|
+
const frameworkConfig = agentConfig.agentFrameworkConfig;
|
|
743
|
+
const promptConfigs = frameworkConfig?.prompts ?? [];
|
|
744
|
+
const plugins = frameworkConfig?.plugins ?? [];
|
|
745
|
+
const hooks = createAgentFrameworkHooks();
|
|
746
|
+
const pluginMap = resolvePromptPluginMap(agentFrameworkContext);
|
|
747
|
+
for (const plugin of plugins) {
|
|
748
|
+
const entry = pluginMap.get(plugin.toolId);
|
|
749
|
+
if (entry) entry(hooks);
|
|
750
|
+
}
|
|
751
|
+
let processedContext = { prompts: promptConfigs };
|
|
752
|
+
for (let index = 0; index < plugins.length; index++) {
|
|
753
|
+
const plugin = plugins[index];
|
|
754
|
+
processedContext = await runProcessPromptsHooks(hooks, {
|
|
755
|
+
prompts: processedContext.prompts,
|
|
756
|
+
messages,
|
|
757
|
+
toolConfig: plugin,
|
|
758
|
+
pluginIndex: index,
|
|
759
|
+
agentFrameworkContext
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
const processed = processedContext.prompts;
|
|
763
|
+
const flat = flattenPrompts(processed);
|
|
764
|
+
const last = messages[messages.length - 1];
|
|
765
|
+
if (last && last.role === "user") {
|
|
766
|
+
const content = last.content;
|
|
767
|
+
const fileMeta = last.metadata?.file;
|
|
768
|
+
if (fileMeta?.path && options?.readAttachmentFile) {
|
|
769
|
+
try {
|
|
770
|
+
const buf = await options.readAttachmentFile(fileMeta.path);
|
|
771
|
+
flat.push({
|
|
772
|
+
role: "user",
|
|
773
|
+
content: [
|
|
774
|
+
{ type: "image", image: buf },
|
|
775
|
+
{ type: "text", text: content }
|
|
776
|
+
]
|
|
777
|
+
});
|
|
778
|
+
} catch (error) {
|
|
779
|
+
logger.error("failed to read attached file", { error, path: fileMeta.path });
|
|
780
|
+
}
|
|
781
|
+
} else if (fileMeta?.path && !options?.readAttachmentFile) {
|
|
782
|
+
flat.push({
|
|
783
|
+
role: "user",
|
|
784
|
+
content: `[attached path: ${fileMeta.path}]
|
|
785
|
+
${content}`
|
|
786
|
+
});
|
|
787
|
+
} else {
|
|
788
|
+
flat.push({ role: "user", content });
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const state = {
|
|
792
|
+
processedPrompts: processed,
|
|
793
|
+
flatPrompts: flat,
|
|
794
|
+
step: "complete",
|
|
795
|
+
isComplete: true,
|
|
796
|
+
sourcePaths: collectPromptSourcePaths(processed)
|
|
797
|
+
};
|
|
798
|
+
yield state;
|
|
799
|
+
return state;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// src/tools/structuredToolResult.ts
|
|
803
|
+
var MEMELOOP_STRUCTURED_TOOL_KEY = "__memeloopToolResult";
|
|
804
|
+
function truncateToolSummary(s, max = 2e3) {
|
|
805
|
+
if (s.length <= max) return s;
|
|
806
|
+
return `${s.slice(0, max - 3)}...`;
|
|
807
|
+
}
|
|
808
|
+
function extractMemeloopStructuredToolPayload(raw) {
|
|
809
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
810
|
+
const o = raw;
|
|
811
|
+
const payload = o[MEMELOOP_STRUCTURED_TOOL_KEY];
|
|
812
|
+
if (payload === null || typeof payload !== "object") return null;
|
|
813
|
+
const p = payload;
|
|
814
|
+
if (typeof p.summary !== "string" || p.summary.length === 0) return null;
|
|
815
|
+
const awaitSessionId = typeof p.awaitSessionId === "string" && p.awaitSessionId.length > 0 ? p.awaitSessionId : void 0;
|
|
816
|
+
return {
|
|
817
|
+
summary: p.summary,
|
|
818
|
+
detailRef: p.detailRef,
|
|
819
|
+
awaitSessionId
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/tools/approval.ts
|
|
824
|
+
var pendingApprovals = /* @__PURE__ */ new Map();
|
|
825
|
+
var approvalListeners = /* @__PURE__ */ new Set();
|
|
826
|
+
function onApprovalRequest(listener) {
|
|
827
|
+
approvalListeners.add(listener);
|
|
828
|
+
return () => {
|
|
829
|
+
approvalListeners.delete(listener);
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function evaluateApproval(approval, toolName, parameters) {
|
|
833
|
+
if (!approval || approval.mode === "auto") {
|
|
834
|
+
return "allow";
|
|
835
|
+
}
|
|
836
|
+
const callContent = JSON.stringify({ tool: toolName, parameters });
|
|
837
|
+
if (approval.denyPatterns?.length) {
|
|
838
|
+
for (const pattern of approval.denyPatterns) {
|
|
839
|
+
try {
|
|
840
|
+
if (new RegExp(pattern, "i").test(callContent)) {
|
|
841
|
+
return "deny";
|
|
842
|
+
}
|
|
843
|
+
} catch {
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (approval.allowPatterns?.length) {
|
|
848
|
+
for (const pattern of approval.allowPatterns) {
|
|
849
|
+
try {
|
|
850
|
+
if (new RegExp(pattern, "i").test(callContent)) {
|
|
851
|
+
return "allow";
|
|
852
|
+
}
|
|
853
|
+
} catch {
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return "pending";
|
|
858
|
+
}
|
|
859
|
+
function requestApproval(request, timeoutMs = 6e4) {
|
|
860
|
+
return new Promise((resolve) => {
|
|
861
|
+
pendingApprovals.set(request.approvalId, { request, resolve });
|
|
862
|
+
for (const listener of approvalListeners) {
|
|
863
|
+
try {
|
|
864
|
+
listener(request);
|
|
865
|
+
} catch {
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
if (timeoutMs > 0) {
|
|
869
|
+
setTimeout(() => {
|
|
870
|
+
if (pendingApprovals.has(request.approvalId)) {
|
|
871
|
+
pendingApprovals.delete(request.approvalId);
|
|
872
|
+
resolve("deny");
|
|
873
|
+
}
|
|
874
|
+
}, timeoutMs);
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
function resolveApproval(approvalId, decision) {
|
|
879
|
+
const pending = pendingApprovals.get(approvalId);
|
|
880
|
+
if (pending) {
|
|
881
|
+
pendingApprovals.delete(approvalId);
|
|
882
|
+
pending.resolve(decision);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
function getPendingApprovals() {
|
|
886
|
+
return [...pendingApprovals.values()].map((p) => p.request);
|
|
887
|
+
}
|
|
888
|
+
function cancelPendingApprovals(agentId) {
|
|
889
|
+
for (const [id, pending] of pendingApprovals) {
|
|
890
|
+
if (pending.request.agentId === agentId) {
|
|
891
|
+
pendingApprovals.delete(id);
|
|
892
|
+
pending.resolve("deny");
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/promptUtilities/responseConcat.ts
|
|
898
|
+
function cloneResponses(responses) {
|
|
899
|
+
return structuredClone(responses);
|
|
900
|
+
}
|
|
901
|
+
async function responseConcat(agentFrameworkConfig, llmResponse, agentFrameworkContext, messages) {
|
|
902
|
+
const responses = Array.isArray(agentFrameworkConfig?.response) ? cloneResponses(agentFrameworkConfig.response) : [];
|
|
903
|
+
const toolConfigs = (Array.isArray(agentFrameworkConfig.plugins) ? agentFrameworkConfig.plugins : []).filter((t) => t.enabled !== false);
|
|
904
|
+
const hooks = createAgentFrameworkHooks();
|
|
905
|
+
const pluginMap = resolvePromptPluginMap(agentFrameworkContext);
|
|
906
|
+
for (const tool of toolConfigs) {
|
|
907
|
+
const builtInTool = pluginMap.get(tool.toolId);
|
|
908
|
+
if (builtInTool) {
|
|
909
|
+
builtInTool(hooks);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
let yieldNextRoundTo;
|
|
913
|
+
let toolCallInfo;
|
|
914
|
+
for (const tool of toolConfigs) {
|
|
915
|
+
const responseContext = {
|
|
916
|
+
agentFrameworkContext,
|
|
917
|
+
messages,
|
|
918
|
+
prompts: [],
|
|
919
|
+
toolConfig: tool,
|
|
920
|
+
llmResponse,
|
|
921
|
+
responses,
|
|
922
|
+
metadata: {},
|
|
923
|
+
actions: {}
|
|
924
|
+
};
|
|
925
|
+
await runPostProcessHooks(hooks, responseContext);
|
|
926
|
+
if (responseContext.actions?.yieldNextRoundTo) {
|
|
927
|
+
yieldNextRoundTo = responseContext.actions.yieldNextRoundTo;
|
|
928
|
+
if (responseContext.actions.toolCalling) {
|
|
929
|
+
toolCallInfo = responseContext.actions.toolCalling;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
const processedResponse = flattenResponses(responses);
|
|
934
|
+
return {
|
|
935
|
+
processedResponse: processedResponse || llmResponse,
|
|
936
|
+
yieldNextRoundTo,
|
|
937
|
+
toolCallInfo
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function flattenResponses(responses) {
|
|
941
|
+
if (responses.length === 0) {
|
|
942
|
+
return "";
|
|
943
|
+
}
|
|
944
|
+
return responses.filter((response) => response.enabled !== false).map((response) => response.text || "").join("\n\n").trim();
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// src/loopAPI/agent-tool-loop/historyCompaction.ts
|
|
948
|
+
function compactHistory(history, options) {
|
|
949
|
+
const maxMessages = options?.contextCompaction?.maxMessages ?? 0;
|
|
950
|
+
if (maxMessages <= 0 || history.length <= maxMessages) return history;
|
|
951
|
+
const dropped = history.length - maxMessages;
|
|
952
|
+
const tail = history.slice(-maxMessages);
|
|
953
|
+
const summaryMessage = {
|
|
954
|
+
...tail[0],
|
|
955
|
+
messageId: `${tail[0]?.conversationId ?? "unknown"}:summary:${Date.now().toString(36)}`,
|
|
956
|
+
role: "assistant",
|
|
957
|
+
content: `[context-summary] ${dropped} earlier messages were compacted.`
|
|
958
|
+
};
|
|
959
|
+
if (options?.contextCompaction?.replayLastUserMessage === false) return tail;
|
|
960
|
+
const lastUser = [...history].reverse().find((message) => message.role === "user");
|
|
961
|
+
if (!lastUser) return [summaryMessage, ...tail];
|
|
962
|
+
if (tail.some((message) => message.messageId === lastUser.messageId)) return tail;
|
|
963
|
+
return [summaryMessage, lastUser, ...tail];
|
|
964
|
+
}
|
|
965
|
+
function asChatMessages(value) {
|
|
966
|
+
if (!Array.isArray(value)) return void 0;
|
|
967
|
+
if (!value.every((message) => message != null && typeof message === "object")) return void 0;
|
|
968
|
+
return value;
|
|
969
|
+
}
|
|
970
|
+
function buildCompactedStep(conversationId, iteration, droppedCount, summaryText) {
|
|
971
|
+
return {
|
|
972
|
+
type: "thinking",
|
|
973
|
+
data: {
|
|
974
|
+
status: "compacted",
|
|
975
|
+
conversationId,
|
|
976
|
+
droppedCount: typeof droppedCount === "number" ? droppedCount : 0,
|
|
977
|
+
summaryText: typeof summaryText === "string" ? summaryText : "",
|
|
978
|
+
iteration
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
async function persistSummaryMessage(context, conversationId, summaryMessage) {
|
|
983
|
+
if (!summaryMessage) return;
|
|
984
|
+
const lamportSummary = await nextLamportClockForConversation(context.storage, conversationId);
|
|
985
|
+
await context.storage.appendMessage({
|
|
986
|
+
...summaryMessage,
|
|
987
|
+
lamportClock: lamportSummary
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
async function maybeApplyContextCompactionHook(options) {
|
|
991
|
+
const { context, conversationId, iteration, history, agentToolLoopOptions } = options;
|
|
992
|
+
if (!hasHooks("ContextCompaction")) {
|
|
993
|
+
return { handled: false, history, steps: [] };
|
|
994
|
+
}
|
|
995
|
+
const hookResult = await executeHooks("ContextCompaction", context, {
|
|
996
|
+
conversationId,
|
|
997
|
+
iteration,
|
|
998
|
+
history,
|
|
999
|
+
autoCompact: agentToolLoopOptions?.autoCompact,
|
|
1000
|
+
contextCompaction: agentToolLoopOptions?.contextCompaction
|
|
1001
|
+
});
|
|
1002
|
+
const modified = hookResult.modified;
|
|
1003
|
+
const modifiedHistory = asChatMessages(modified?.history);
|
|
1004
|
+
const nextHistory = modifiedHistory ?? history;
|
|
1005
|
+
const skipDefault = !hookResult.allowed || modifiedHistory != null || modified?.skipDefault === true;
|
|
1006
|
+
if (!skipDefault) {
|
|
1007
|
+
return { handled: false, history: nextHistory, steps: [] };
|
|
1008
|
+
}
|
|
1009
|
+
const steps = modified?.compacted === true ? [buildCompactedStep(conversationId, iteration, modified.droppedCount, modified.summaryText)] : [];
|
|
1010
|
+
if (modified?.persistSummaryMessage === true) {
|
|
1011
|
+
await persistSummaryMessage(context, conversationId, nextHistory[0]);
|
|
1012
|
+
}
|
|
1013
|
+
return { handled: true, history: nextHistory, steps };
|
|
1014
|
+
}
|
|
1015
|
+
async function applyBuiltInAutoCompact(options) {
|
|
1016
|
+
const { context, conversationId, iteration, agentToolLoopOptions } = options;
|
|
1017
|
+
let history = options.history;
|
|
1018
|
+
const autoCompactOptions = agentToolLoopOptions?.autoCompact;
|
|
1019
|
+
if (!autoCompactOptions) {
|
|
1020
|
+
return { history, steps: [] };
|
|
1021
|
+
}
|
|
1022
|
+
const threshold = autoCompactOptions.threshold ?? 50;
|
|
1023
|
+
if (!shouldCompact(history, threshold)) {
|
|
1024
|
+
return { history, steps: [] };
|
|
1025
|
+
}
|
|
1026
|
+
try {
|
|
1027
|
+
const result = await autoCompact(history, {
|
|
1028
|
+
recentTurnsToKeep: autoCompactOptions.recentTurnsToKeep ?? 4,
|
|
1029
|
+
maxTokens: autoCompactOptions.maxTokens ?? 0,
|
|
1030
|
+
llmProvider: context.llmProvider
|
|
1031
|
+
});
|
|
1032
|
+
if (!result.compacted) {
|
|
1033
|
+
return { history, steps: [] };
|
|
1034
|
+
}
|
|
1035
|
+
history = result.messages;
|
|
1036
|
+
await persistSummaryMessage(context, conversationId, result.messages[0]);
|
|
1037
|
+
return {
|
|
1038
|
+
history,
|
|
1039
|
+
steps: [
|
|
1040
|
+
buildCompactedStep(conversationId, iteration, result.droppedCount, result.summaryText)
|
|
1041
|
+
]
|
|
1042
|
+
};
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
if (context.logger?.warn) {
|
|
1045
|
+
context.logger.warn("[agentToolLoop] auto-compact failed:", error);
|
|
1046
|
+
} else {
|
|
1047
|
+
console.warn("[agentToolLoop] auto-compact failed:", error);
|
|
1048
|
+
}
|
|
1049
|
+
return { history, steps: [] };
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
async function prepareIterationHistory(options) {
|
|
1053
|
+
const { agentToolLoopOptions } = options;
|
|
1054
|
+
const hookResult = await maybeApplyContextCompactionHook({
|
|
1055
|
+
...options,
|
|
1056
|
+
history: options.rawHistory
|
|
1057
|
+
});
|
|
1058
|
+
if (hookResult.handled) {
|
|
1059
|
+
return hookResult;
|
|
1060
|
+
}
|
|
1061
|
+
const autoCompactResult = await applyBuiltInAutoCompact({
|
|
1062
|
+
...options,
|
|
1063
|
+
history: hookResult.history
|
|
1064
|
+
});
|
|
1065
|
+
return {
|
|
1066
|
+
history: compactHistory(autoCompactResult.history, agentToolLoopOptions),
|
|
1067
|
+
steps: autoCompactResult.steps
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// src/loopAPI/agent-tool-loop/llmStream.ts
|
|
1072
|
+
function isAsyncIterable(value) {
|
|
1073
|
+
return value != null && typeof value[Symbol.asyncIterator] === "function";
|
|
1074
|
+
}
|
|
1075
|
+
function chunkToText2(chunk) {
|
|
1076
|
+
if (typeof chunk === "string") return chunk;
|
|
1077
|
+
if (chunk != null && typeof chunk === "object" && "content" in chunk) {
|
|
1078
|
+
const content = chunk.content;
|
|
1079
|
+
return typeof content === "string" ? content : JSON.stringify(content);
|
|
1080
|
+
}
|
|
1081
|
+
return JSON.stringify(chunk);
|
|
1082
|
+
}
|
|
1083
|
+
async function* streamLlm(context, request) {
|
|
1084
|
+
const chatFunction = context.llmProvider.chat;
|
|
1085
|
+
if (typeof chatFunction !== "function") {
|
|
1086
|
+
throw new Error(
|
|
1087
|
+
"LLM provider does not support legacy chat() method. Use AI SDK's streamText instead."
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
const raw = chatFunction(request);
|
|
1091
|
+
let resolved = raw;
|
|
1092
|
+
if (resolved != null && typeof resolved.then === "function") {
|
|
1093
|
+
resolved = await resolved;
|
|
1094
|
+
}
|
|
1095
|
+
if (isAsyncIterable(resolved)) {
|
|
1096
|
+
for await (const chunk of resolved) {
|
|
1097
|
+
yield chunk;
|
|
1098
|
+
}
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
yield resolved;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// src/promptUtilities/utilities.ts
|
|
1105
|
+
function filterOldMessagesByDuration(messages, maxAgeMs, now = Date.now()) {
|
|
1106
|
+
if (maxAgeMs <= 0) return messages;
|
|
1107
|
+
const cutoff = now - maxAgeMs;
|
|
1108
|
+
return messages.filter((m) => typeof m.timestamp === "number" && m.timestamp >= cutoff);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/loopAPI/agent-tool-loop/toolResultMessage.ts
|
|
1112
|
+
function formatToolResultMessage(toolName, parameters, body, isError) {
|
|
1113
|
+
return `<functions_result>
|
|
1114
|
+
Tool: ${toolName}
|
|
1115
|
+
Parameters: ${JSON.stringify(parameters)}
|
|
1116
|
+
${isError ? "Error" : "Result"}: ${body}
|
|
1117
|
+
</functions_result>`;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// src/loopAPI/agent-tool-loop/modelMessages.ts
|
|
1121
|
+
function chatMessageToModelMessage(message) {
|
|
1122
|
+
const role = message.role === "agent" || message.role === "error" ? "assistant" : message.role === "tool" ? "tool" : message.role === "user" ? "user" : "assistant";
|
|
1123
|
+
if (role === "tool") {
|
|
1124
|
+
const toolResults = getChatMessageParts(message).filter(isToolResultPart);
|
|
1125
|
+
if (toolResults.length > 0) {
|
|
1126
|
+
return {
|
|
1127
|
+
role,
|
|
1128
|
+
content: toolResults.map(
|
|
1129
|
+
(part) => formatToolResultMessage(
|
|
1130
|
+
part.toolName,
|
|
1131
|
+
part.parameters && typeof part.parameters === "object" ? part.parameters : {},
|
|
1132
|
+
part.result,
|
|
1133
|
+
part.isError === true
|
|
1134
|
+
)
|
|
1135
|
+
).join("\n\n")
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return {
|
|
1140
|
+
role,
|
|
1141
|
+
content: message.content
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
async function resolveAgentDefinitionModel(context, definitionId) {
|
|
1145
|
+
if (context.resolveAgentDefinition) {
|
|
1146
|
+
return context.resolveAgentDefinition(definitionId);
|
|
1147
|
+
}
|
|
1148
|
+
return context.storage.getAgentDefinition(definitionId);
|
|
1149
|
+
}
|
|
1150
|
+
async function inferDefinitionId(storage, conversationId) {
|
|
1151
|
+
try {
|
|
1152
|
+
const meta = await storage.getConversationMeta(conversationId);
|
|
1153
|
+
if (meta?.definitionId) return meta.definitionId;
|
|
1154
|
+
} catch {
|
|
1155
|
+
}
|
|
1156
|
+
const parts = conversationId.split(":");
|
|
1157
|
+
if (parts.length >= 2) {
|
|
1158
|
+
return parts.slice(0, -1).join(":");
|
|
1159
|
+
}
|
|
1160
|
+
return conversationId;
|
|
1161
|
+
}
|
|
1162
|
+
async function buildLlmMessages(context, conversationId, history) {
|
|
1163
|
+
const definitionId = await inferDefinitionId(context.storage, conversationId);
|
|
1164
|
+
const definition = await resolveAgentDefinitionModel(context, definitionId);
|
|
1165
|
+
const fw = definition?.agentFrameworkConfig;
|
|
1166
|
+
const maxHistoryAgeMs = context.agentToolLoop?.maxHistoryAgeMs ?? 0;
|
|
1167
|
+
const historyForPrompt = maxHistoryAgeMs > 0 ? filterOldMessagesByDuration(history, maxHistoryAgeMs) : history;
|
|
1168
|
+
if (fw?.prompts && Array.isArray(fw.prompts) && fw.prompts.length > 0) {
|
|
1169
|
+
const readAttachmentFile = context.agentToolLoop?.readAttachmentFile;
|
|
1170
|
+
const gen = promptConcatStream(
|
|
1171
|
+
{
|
|
1172
|
+
agentFrameworkConfig: {
|
|
1173
|
+
prompts: fw.prompts,
|
|
1174
|
+
plugins: fw.plugins ?? [],
|
|
1175
|
+
response: []
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
historyForPrompt,
|
|
1179
|
+
context,
|
|
1180
|
+
readAttachmentFile ? { readAttachmentFile } : void 0
|
|
1181
|
+
);
|
|
1182
|
+
let lastFlat = [];
|
|
1183
|
+
for await (const state of gen) {
|
|
1184
|
+
lastFlat = state.flatPrompts;
|
|
1185
|
+
}
|
|
1186
|
+
const withoutTrailingUser = lastFlat.length > 0 && lastFlat[lastFlat.length - 1]?.role === "user" ? lastFlat.slice(0, -1) : lastFlat;
|
|
1187
|
+
return [...withoutTrailingUser, ...historyForPrompt.map(chatMessageToModelMessage)];
|
|
1188
|
+
}
|
|
1189
|
+
const systemText = typeof definition?.systemPrompt === "string" ? definition.systemPrompt.trim() : "";
|
|
1190
|
+
if (systemText.length > 0) {
|
|
1191
|
+
return [
|
|
1192
|
+
{ role: "system", content: systemText },
|
|
1193
|
+
...historyForPrompt.map(chatMessageToModelMessage)
|
|
1194
|
+
];
|
|
1195
|
+
}
|
|
1196
|
+
return historyForPrompt.map(chatMessageToModelMessage);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/loopAPI/agent-tool-loop/toolCallRunner.ts
|
|
1200
|
+
var TOOL_OPERATION_DEFAULT_TIMEOUT_MS = 6e4;
|
|
1201
|
+
var TOOL_OPERATION_POLL_INTERVAL_MS = 250;
|
|
1202
|
+
var toolOperationCounter = 0;
|
|
1203
|
+
var toolResultMessageCounter = 0;
|
|
1204
|
+
function isTerminalToolOperationPhase(phase) {
|
|
1205
|
+
return phase === "Completed" || phase === "Failed" || phase === "Cancelled";
|
|
1206
|
+
}
|
|
1207
|
+
function stableStringify(value) {
|
|
1208
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
1209
|
+
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
1210
|
+
const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`);
|
|
1211
|
+
return `{${entries.join(",")}}`;
|
|
1212
|
+
}
|
|
1213
|
+
function fnv1aHex(input) {
|
|
1214
|
+
let hash = 2166136261;
|
|
1215
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
1216
|
+
hash = Math.imul(hash ^ input.charCodeAt(index), 16777619) >>> 0;
|
|
1217
|
+
}
|
|
1218
|
+
return hash.toString(16).padStart(8, "0");
|
|
1219
|
+
}
|
|
1220
|
+
function isTransientGetError(error) {
|
|
1221
|
+
return error instanceof OrchestrationError && (error.code === "UNAVAILABLE" || error.code === "TIMEOUT" || error.code === "INTERNAL");
|
|
1222
|
+
}
|
|
1223
|
+
async function waitForToolOperationTerminal(client, reference, timeoutMs, applied) {
|
|
1224
|
+
const deadline = Date.now() + timeoutMs;
|
|
1225
|
+
let last = applied ?? null;
|
|
1226
|
+
while (Date.now() < deadline) {
|
|
1227
|
+
let resource;
|
|
1228
|
+
try {
|
|
1229
|
+
resource = await client.get(reference);
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
if (!isTransientGetError(error)) throw error;
|
|
1232
|
+
const basis = last ?? applied;
|
|
1233
|
+
if (!basis) throw error;
|
|
1234
|
+
const decision = reconcileUnknownEffect(basis, { resultObserved: false });
|
|
1235
|
+
if (decision.action === "retry") {
|
|
1236
|
+
} else {
|
|
1237
|
+
throw new Error(
|
|
1238
|
+
`ToolOperation ${reference.name ?? "<unknown>"} effect unknown after transport failure: ${decision.action} \u2014 ${decision.reason}`
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
if (resource) {
|
|
1243
|
+
last = resource;
|
|
1244
|
+
if (isTerminalToolOperationPhase(last.status?.phase)) {
|
|
1245
|
+
return last;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
await new Promise((resolve) => {
|
|
1249
|
+
setTimeout(resolve, TOOL_OPERATION_POLL_INTERVAL_MS);
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
if (last) {
|
|
1253
|
+
return last;
|
|
1254
|
+
}
|
|
1255
|
+
throw new Error(`ToolOperation ${reference.name ?? "<unknown>"} was not observed before timeout`);
|
|
1256
|
+
}
|
|
1257
|
+
function toolOperationRow(resource) {
|
|
1258
|
+
const status = resource.status;
|
|
1259
|
+
if (status?.phase === "Completed") {
|
|
1260
|
+
const value = status.result?.value;
|
|
1261
|
+
if (value != null && typeof value === "object") {
|
|
1262
|
+
const structured = extractMemeloopStructuredToolPayload(value);
|
|
1263
|
+
if (structured) {
|
|
1264
|
+
return {
|
|
1265
|
+
text: structured.summary,
|
|
1266
|
+
isError: false,
|
|
1267
|
+
detailRef: structured.detailRef,
|
|
1268
|
+
awaitSessionId: structured.awaitSessionId
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
if ("error" in value && typeof value.error === "string") {
|
|
1272
|
+
return { text: value.error, isError: true };
|
|
1273
|
+
}
|
|
1274
|
+
if ("result" in value && value.result != null) {
|
|
1275
|
+
return {
|
|
1276
|
+
text: typeof value.result === "string" ? value.result : JSON.stringify(value.result),
|
|
1277
|
+
payload: typeof value.result === "string" ? void 0 : value.result,
|
|
1278
|
+
isError: false
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return { text: typeof value === "string" ? value : JSON.stringify(value), isError: false };
|
|
1283
|
+
}
|
|
1284
|
+
if (status?.phase === "Failed" || status?.phase === "Cancelled") {
|
|
1285
|
+
return {
|
|
1286
|
+
text: status.result?.error?.message ?? `ToolOperation ${status.phase}`,
|
|
1287
|
+
isError: true
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
return {
|
|
1291
|
+
text: `ToolOperation did not reach a terminal phase (last phase: ${status?.phase ?? "unknown"})`,
|
|
1292
|
+
isError: true
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
async function executeToolOperation(context, conversationId, call, occurrence) {
|
|
1296
|
+
const client = context.orchestration;
|
|
1297
|
+
if (!client) return null;
|
|
1298
|
+
try {
|
|
1299
|
+
const caps = await client.getCapabilities();
|
|
1300
|
+
if (!caps.resourceKinds.includes(TOOL_OPERATION_KIND) || !caps.operations.includes("apply") || !caps.operations.includes("get")) {
|
|
1301
|
+
return null;
|
|
1302
|
+
}
|
|
1303
|
+
} catch {
|
|
1304
|
+
return null;
|
|
1305
|
+
}
|
|
1306
|
+
const timeoutMs = context.agentToolLoop?.toolOperationTimeoutMs ?? TOOL_OPERATION_DEFAULT_TIMEOUT_MS;
|
|
1307
|
+
const idempotencyKey = `${conversationId}:${fnv1aHex(stableStringify({
|
|
1308
|
+
toolId: call.toolId,
|
|
1309
|
+
parameters: call.parameters
|
|
1310
|
+
}))}:${occurrence}`;
|
|
1311
|
+
toolOperationCounter += 1;
|
|
1312
|
+
const operation = createToolOperationManifest(
|
|
1313
|
+
`${call.toolId}-${Date.now().toString(36)}-${toolOperationCounter.toString(36)}`,
|
|
1314
|
+
{
|
|
1315
|
+
toolRef: { kind: "BuiltinTool", name: call.toolId },
|
|
1316
|
+
effect: context.tools.getToolEffect?.(call.toolId) ?? "execute",
|
|
1317
|
+
arguments: call.parameters,
|
|
1318
|
+
idempotencyKey,
|
|
1319
|
+
timeoutMs,
|
|
1320
|
+
policy: { auditLevel: "metadata" }
|
|
1321
|
+
}
|
|
1322
|
+
);
|
|
1323
|
+
try {
|
|
1324
|
+
const applied = await client.apply(operation);
|
|
1325
|
+
if (applied.apiVersion !== TOOL_OPERATION_API_VERSION || applied.kind !== TOOL_OPERATION_KIND) {
|
|
1326
|
+
return { text: "ToolOperation apply returned an unexpected resource kind", isError: true };
|
|
1327
|
+
}
|
|
1328
|
+
let resource = applied;
|
|
1329
|
+
if (!isTerminalToolOperationPhase(resource.status?.phase)) {
|
|
1330
|
+
const reference = {
|
|
1331
|
+
apiVersion: applied.apiVersion,
|
|
1332
|
+
kind: applied.kind,
|
|
1333
|
+
name: applied.metadata.name,
|
|
1334
|
+
namespace: applied.metadata.namespace
|
|
1335
|
+
};
|
|
1336
|
+
resource = await waitForToolOperationTerminal(client, reference, timeoutMs, resource);
|
|
1337
|
+
}
|
|
1338
|
+
return toolOperationRow(resource);
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1341
|
+
return { text: `ToolOperation execution error: ${message}`, isError: true };
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
async function executeRegistryTool(context, toolId, parameters) {
|
|
1345
|
+
const normalizedId = toolId.includes("-") ? toolId.replace(/-([a-z])/g, (_m, c) => c.toUpperCase()) : toolId;
|
|
1346
|
+
const impl = context.tools.getTool(toolId) ?? context.tools.getTool(normalizedId);
|
|
1347
|
+
if (typeof impl !== "function") {
|
|
1348
|
+
return {
|
|
1349
|
+
text: `No tool registered for "${toolId}".`,
|
|
1350
|
+
isError: true
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
try {
|
|
1354
|
+
const raw = await impl(parameters);
|
|
1355
|
+
if (raw != null && typeof raw === "object") {
|
|
1356
|
+
const o = raw;
|
|
1357
|
+
if (typeof o.error === "string" && o.error.length > 0) {
|
|
1358
|
+
return { text: o.error, isError: true };
|
|
1359
|
+
}
|
|
1360
|
+
if ("result" in o) {
|
|
1361
|
+
return {
|
|
1362
|
+
text: typeof o.result === "string" ? o.result : JSON.stringify(o.result),
|
|
1363
|
+
payload: typeof o.result === "string" ? void 0 : o.result,
|
|
1364
|
+
isError: false
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
const structured = extractMemeloopStructuredToolPayload(raw);
|
|
1368
|
+
if (structured) {
|
|
1369
|
+
return {
|
|
1370
|
+
text: structured.summary,
|
|
1371
|
+
isError: false,
|
|
1372
|
+
detailRef: structured.detailRef,
|
|
1373
|
+
awaitSessionId: structured.awaitSessionId
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return { text: typeof raw === "string" ? raw : JSON.stringify(raw), isError: false };
|
|
1378
|
+
} catch (error) {
|
|
1379
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1380
|
+
if (context.logger?.warn) {
|
|
1381
|
+
context.logger.warn("[agentToolLoop] tool execution error", toolId, message);
|
|
1382
|
+
} else {
|
|
1383
|
+
console.warn("[agentToolLoop] tool execution error", toolId, message);
|
|
1384
|
+
}
|
|
1385
|
+
return { text: message, isError: true };
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
async function executeWithGuards(context, options, conversationId, recentToolCalls, call) {
|
|
1389
|
+
const signature = `${call.toolId}:${JSON.stringify(call.parameters)}`;
|
|
1390
|
+
recentToolCalls.push(signature);
|
|
1391
|
+
const threshold = Math.max(2, options?.doomLoopThreshold ?? 3);
|
|
1392
|
+
const last = recentToolCalls.slice(-threshold);
|
|
1393
|
+
if (last.length === threshold && last.every((x) => x === signature)) {
|
|
1394
|
+
return { text: "Blocked by doom-loop guard", isError: true };
|
|
1395
|
+
}
|
|
1396
|
+
const occurrence = recentToolCalls.filter((entry) => entry === signature).length;
|
|
1397
|
+
const row = await executeToolOperation(context, conversationId, call, occurrence) ?? await executeRegistryTool(context, call.toolId, call.parameters);
|
|
1398
|
+
if (hasHooks("PostToolUse")) {
|
|
1399
|
+
await executeHooks("PostToolUse", context, {
|
|
1400
|
+
toolId: call.toolId,
|
|
1401
|
+
parameters: call.parameters,
|
|
1402
|
+
result: row.text,
|
|
1403
|
+
isError: row.isError,
|
|
1404
|
+
conversationId
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
return row;
|
|
1408
|
+
}
|
|
1409
|
+
async function persistToolResult(context, conversationId, call, row) {
|
|
1410
|
+
toolResultMessageCounter += 1;
|
|
1411
|
+
const lamportTool = await nextLamportClockForConversation(context.storage, conversationId);
|
|
1412
|
+
await context.storage.appendMessage(createChatMessage({
|
|
1413
|
+
// Counter suffix keeps message identity unique for identical calls within
|
|
1414
|
+
// the same millisecond (parallel tools or fast consecutive rounds).
|
|
1415
|
+
messageId: `${conversationId}:t:${call.toolId}:${Date.now().toString(36)}:${toolResultMessageCounter.toString(36)}`,
|
|
1416
|
+
conversationId,
|
|
1417
|
+
originNodeId: "local",
|
|
1418
|
+
lamportClock: lamportTool,
|
|
1419
|
+
role: "tool",
|
|
1420
|
+
parts: [{
|
|
1421
|
+
type: "tool-result",
|
|
1422
|
+
toolName: call.toolId,
|
|
1423
|
+
parameters: call.parameters,
|
|
1424
|
+
result: row.text,
|
|
1425
|
+
isError: row.isError,
|
|
1426
|
+
payload: row.payload,
|
|
1427
|
+
detailRef: row.detailRef
|
|
1428
|
+
}],
|
|
1429
|
+
detailRef: row.detailRef,
|
|
1430
|
+
metadata: {
|
|
1431
|
+
isToolResult: true,
|
|
1432
|
+
isError: row.isError,
|
|
1433
|
+
toolId: call.toolId,
|
|
1434
|
+
toolParameters: call.parameters
|
|
1435
|
+
}
|
|
1436
|
+
}));
|
|
1437
|
+
}
|
|
1438
|
+
async function persistTerminalAwaitCompletion(context, options, conversationId, call, row) {
|
|
1439
|
+
const sid = row.awaitSessionId;
|
|
1440
|
+
const wait = options?.waitForTerminalSession;
|
|
1441
|
+
if (!sid || !wait || row.isError) return;
|
|
1442
|
+
const done = await wait(sid);
|
|
1443
|
+
const lamportTool = await nextLamportClockForConversation(context.storage, conversationId);
|
|
1444
|
+
const body = truncateToolSummary(
|
|
1445
|
+
`[terminal.await done] session=${sid}
|
|
1446
|
+
exitCode: ${done.exitCode ?? "null"}
|
|
1447
|
+
---
|
|
1448
|
+
${done.truncatedOutput}`
|
|
1449
|
+
);
|
|
1450
|
+
await context.storage.appendMessage(createChatMessage({
|
|
1451
|
+
messageId: `${conversationId}:t:${call.toolId}:await:${Date.now().toString(36)}:${toolResultMessageCounter.toString(36)}`,
|
|
1452
|
+
conversationId,
|
|
1453
|
+
originNodeId: "local",
|
|
1454
|
+
lamportClock: lamportTool,
|
|
1455
|
+
role: "tool",
|
|
1456
|
+
parts: [{
|
|
1457
|
+
type: "tool-result",
|
|
1458
|
+
toolName: call.toolId,
|
|
1459
|
+
parameters: call.parameters,
|
|
1460
|
+
result: body,
|
|
1461
|
+
detailRef: row.detailRef ? { ...row.detailRef, exitCode: done.exitCode ?? row.detailRef.exitCode } : void 0
|
|
1462
|
+
}],
|
|
1463
|
+
detailRef: row.detailRef ? { ...row.detailRef, exitCode: done.exitCode ?? row.detailRef.exitCode } : void 0,
|
|
1464
|
+
metadata: {
|
|
1465
|
+
isToolResult: true,
|
|
1466
|
+
toolId: call.toolId,
|
|
1467
|
+
toolParameters: call.parameters,
|
|
1468
|
+
awaitSessionId: sid
|
|
1469
|
+
}
|
|
1470
|
+
}));
|
|
1471
|
+
}
|
|
1472
|
+
function toolStep(row, parallel) {
|
|
1473
|
+
return {
|
|
1474
|
+
type: "tool",
|
|
1475
|
+
data: {
|
|
1476
|
+
toolId: row.call.toolId,
|
|
1477
|
+
parameters: row.call.parameters,
|
|
1478
|
+
parallel,
|
|
1479
|
+
result: row.text,
|
|
1480
|
+
isError: row.isError
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
async function* runRegistryToolCalls(options) {
|
|
1485
|
+
const { context, agentToolLoopOptions, conversationId, calls, parallel, recentToolCalls } = options;
|
|
1486
|
+
if (parallel) {
|
|
1487
|
+
const results = await Promise.all(
|
|
1488
|
+
calls.map(
|
|
1489
|
+
async (call) => ({
|
|
1490
|
+
call,
|
|
1491
|
+
...await executeWithGuards(
|
|
1492
|
+
context,
|
|
1493
|
+
agentToolLoopOptions,
|
|
1494
|
+
conversationId,
|
|
1495
|
+
recentToolCalls,
|
|
1496
|
+
call
|
|
1497
|
+
)
|
|
1498
|
+
})
|
|
1499
|
+
)
|
|
1500
|
+
);
|
|
1501
|
+
for (const row of results) {
|
|
1502
|
+
yield toolStep(row, true);
|
|
1503
|
+
}
|
|
1504
|
+
for (const row of results) {
|
|
1505
|
+
await persistToolResult(context, conversationId, row.call, row);
|
|
1506
|
+
}
|
|
1507
|
+
for (const row of results) {
|
|
1508
|
+
await persistTerminalAwaitCompletion(
|
|
1509
|
+
context,
|
|
1510
|
+
agentToolLoopOptions,
|
|
1511
|
+
conversationId,
|
|
1512
|
+
row.call,
|
|
1513
|
+
row
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
for (const call of calls) {
|
|
1519
|
+
const row = await executeWithGuards(
|
|
1520
|
+
context,
|
|
1521
|
+
agentToolLoopOptions,
|
|
1522
|
+
conversationId,
|
|
1523
|
+
recentToolCalls,
|
|
1524
|
+
call
|
|
1525
|
+
);
|
|
1526
|
+
yield toolStep({ call, ...row }, false);
|
|
1527
|
+
await persistToolResult(context, conversationId, call, row);
|
|
1528
|
+
await persistTerminalAwaitCompletion(context, agentToolLoopOptions, conversationId, call, row);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// src/loopAPI/agent-tool-loop/toolUseGate.ts
|
|
1533
|
+
function buildLayeredPermissions(options, definitionId, userSet) {
|
|
1534
|
+
const globalPerms = options?.toolPermissions;
|
|
1535
|
+
const sets = [];
|
|
1536
|
+
if (globalPerms?.default) {
|
|
1537
|
+
sets.push({
|
|
1538
|
+
source: "default",
|
|
1539
|
+
rules: [{ toolPattern: "*", action: globalPerms.default }]
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
const scoped = globalPerms?.perAgent?.[definitionId];
|
|
1543
|
+
if (scoped) {
|
|
1544
|
+
if (scoped.default) {
|
|
1545
|
+
sets.push({
|
|
1546
|
+
source: `agent:${definitionId}:default`,
|
|
1547
|
+
rules: [{ toolPattern: "*", action: scoped.default }]
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
if (scoped.rules && scoped.rules.length > 0) {
|
|
1551
|
+
sets.push({
|
|
1552
|
+
source: `agent:${definitionId}`,
|
|
1553
|
+
rules: scoped.rules.map((r) => ({ toolPattern: r.pattern, action: r.action }))
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
if (userSet && userSet.rules.length > 0) {
|
|
1558
|
+
sets.push(userSet);
|
|
1559
|
+
}
|
|
1560
|
+
if (globalPerms?.rules && globalPerms.rules.length > 0) {
|
|
1561
|
+
sets.push({
|
|
1562
|
+
source: "session",
|
|
1563
|
+
rules: globalPerms.rules.map((r) => ({ toolPattern: r.pattern, action: r.action }))
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
if (!sets.some((s) => s.rules.some((r) => r.toolPattern === "*"))) {
|
|
1567
|
+
sets.unshift({
|
|
1568
|
+
source: "implied-default",
|
|
1569
|
+
rules: [{ toolPattern: "*", action: defaultPermissionActionForTrustClass(options?.trustClass) }]
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
return mergePermissionSets(sets);
|
|
1573
|
+
}
|
|
1574
|
+
function createPermissionPreToolUseHook(options, definitionId, userSet) {
|
|
1575
|
+
const mergedPermissions = buildLayeredPermissions(options, definitionId, userSet);
|
|
1576
|
+
return async (_context, data) => {
|
|
1577
|
+
const toolId = typeof data.toolId === "string" ? data.toolId : "";
|
|
1578
|
+
const action = checkPermission(toolId, mergedPermissions);
|
|
1579
|
+
if (action === "allow") {
|
|
1580
|
+
return { allowed: true, permissionAction: "allow" };
|
|
1581
|
+
}
|
|
1582
|
+
return {
|
|
1583
|
+
allowed: true,
|
|
1584
|
+
permissionAction: action,
|
|
1585
|
+
reason: action === "deny" ? "Denied by tool permission" : void 0
|
|
1586
|
+
};
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
function applyModifiedCall(call, modified) {
|
|
1590
|
+
if (!modified) return call;
|
|
1591
|
+
const toolId = typeof modified.toolId === "string" ? modified.toolId : call.toolId;
|
|
1592
|
+
const parameters = modified.parameters != null && typeof modified.parameters === "object" ? modified.parameters : call.parameters;
|
|
1593
|
+
return { ...call, toolId, parameters };
|
|
1594
|
+
}
|
|
1595
|
+
function normalizePreToolUseResult(result) {
|
|
1596
|
+
if (!result.allowed) {
|
|
1597
|
+
return { action: "deny", reason: result.reason ?? "Blocked by PreToolUse hook" };
|
|
1598
|
+
}
|
|
1599
|
+
if (result.permissionAction === "ask" || result.permissionAction === "deny") {
|
|
1600
|
+
return { action: result.permissionAction, reason: result.reason };
|
|
1601
|
+
}
|
|
1602
|
+
return { action: "allow", reason: result.reason };
|
|
1603
|
+
}
|
|
1604
|
+
async function persistDeniedToolResult(context, conversationId, call, errorText) {
|
|
1605
|
+
const lamportTool = await nextLamportClockForConversation(context.storage, conversationId);
|
|
1606
|
+
await context.storage.appendMessage(createChatMessage({
|
|
1607
|
+
messageId: `${conversationId}:t:${call.toolId}:${Date.now().toString(36)}`,
|
|
1608
|
+
conversationId,
|
|
1609
|
+
originNodeId: "local",
|
|
1610
|
+
lamportClock: lamportTool,
|
|
1611
|
+
role: "tool",
|
|
1612
|
+
parts: [{
|
|
1613
|
+
type: "tool-result",
|
|
1614
|
+
toolName: call.toolId,
|
|
1615
|
+
parameters: call.parameters,
|
|
1616
|
+
result: errorText,
|
|
1617
|
+
isError: true
|
|
1618
|
+
}],
|
|
1619
|
+
metadata: {
|
|
1620
|
+
isToolResult: true,
|
|
1621
|
+
isError: true,
|
|
1622
|
+
toolId: call.toolId,
|
|
1623
|
+
toolParameters: call.parameters
|
|
1624
|
+
}
|
|
1625
|
+
}));
|
|
1626
|
+
}
|
|
1627
|
+
async function* resolveAskAction(conversationId, call) {
|
|
1628
|
+
yield {
|
|
1629
|
+
type: "permission_request",
|
|
1630
|
+
data: { tool: call.toolId, args: call.parameters }
|
|
1631
|
+
};
|
|
1632
|
+
const decision = await requestApproval(
|
|
1633
|
+
{
|
|
1634
|
+
approvalId: `${conversationId}:${Date.now().toString(36)}:${call.toolId}`,
|
|
1635
|
+
agentId: conversationId,
|
|
1636
|
+
toolName: call.toolId,
|
|
1637
|
+
parameters: call.parameters,
|
|
1638
|
+
created: /* @__PURE__ */ new Date()
|
|
1639
|
+
},
|
|
1640
|
+
6e4
|
|
1641
|
+
);
|
|
1642
|
+
return decision === "allow" ? "allow" : "deny";
|
|
1643
|
+
}
|
|
1644
|
+
async function runPreToolUseHook(context, data) {
|
|
1645
|
+
if (!hasHooks("PreToolUse")) return { allowed: true };
|
|
1646
|
+
return executeHooks("PreToolUse", context, data);
|
|
1647
|
+
}
|
|
1648
|
+
async function* gateToolCallsWithPreToolUse(context, options, definitionId, conversationId, calls) {
|
|
1649
|
+
const permissionHook = createPermissionPreToolUseHook(options, definitionId);
|
|
1650
|
+
const allowedCalls = [];
|
|
1651
|
+
for (const originalCall of calls) {
|
|
1652
|
+
let call = originalCall;
|
|
1653
|
+
const permissionResult = await permissionHook(context, {
|
|
1654
|
+
toolId: call.toolId,
|
|
1655
|
+
parameters: call.parameters,
|
|
1656
|
+
conversationId
|
|
1657
|
+
});
|
|
1658
|
+
call = applyModifiedCall(call, permissionResult.modified);
|
|
1659
|
+
let { action, reason } = normalizePreToolUseResult(permissionResult);
|
|
1660
|
+
if (action === "ask") {
|
|
1661
|
+
action = yield* resolveAskAction(conversationId, call);
|
|
1662
|
+
reason = action === "deny" ? "Tool approval denied or timed out" : reason;
|
|
1663
|
+
}
|
|
1664
|
+
if (action === "deny") {
|
|
1665
|
+
const errorText = reason ?? "Denied by tool permission";
|
|
1666
|
+
yield {
|
|
1667
|
+
type: "tool",
|
|
1668
|
+
data: {
|
|
1669
|
+
toolId: call.toolId,
|
|
1670
|
+
parameters: call.parameters,
|
|
1671
|
+
parallel: false,
|
|
1672
|
+
result: errorText,
|
|
1673
|
+
isError: true
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
await persistDeniedToolResult(context, conversationId, call, errorText);
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
const hookResult = await runPreToolUseHook(context, {
|
|
1680
|
+
toolId: call.toolId,
|
|
1681
|
+
parameters: call.parameters,
|
|
1682
|
+
conversationId
|
|
1683
|
+
});
|
|
1684
|
+
call = applyModifiedCall(call, hookResult.modified);
|
|
1685
|
+
({ action, reason } = normalizePreToolUseResult(hookResult));
|
|
1686
|
+
if (action === "ask") {
|
|
1687
|
+
action = yield* resolveAskAction(conversationId, call);
|
|
1688
|
+
reason = action === "deny" ? "Tool approval denied or timed out" : reason;
|
|
1689
|
+
}
|
|
1690
|
+
if (action === "deny") {
|
|
1691
|
+
const errorText = reason ?? "Blocked by PreToolUse hook";
|
|
1692
|
+
yield {
|
|
1693
|
+
type: "tool",
|
|
1694
|
+
data: {
|
|
1695
|
+
toolId: call.toolId,
|
|
1696
|
+
parameters: call.parameters,
|
|
1697
|
+
parallel: false,
|
|
1698
|
+
result: errorText,
|
|
1699
|
+
isError: true
|
|
1700
|
+
}
|
|
1701
|
+
};
|
|
1702
|
+
await persistDeniedToolResult(context, conversationId, call, errorText);
|
|
1703
|
+
continue;
|
|
1704
|
+
}
|
|
1705
|
+
allowedCalls.push(call);
|
|
1706
|
+
}
|
|
1707
|
+
return allowedCalls;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
// src/loopAPI/agent-tool-loop/turnPrimitives.ts
|
|
1711
|
+
var DEFAULT_MAX_ITERATIONS = 256;
|
|
1712
|
+
function toolCallHandledInAgentMessages(agentMessages, assistantContent, call) {
|
|
1713
|
+
let assistantIndex = -1;
|
|
1714
|
+
for (let index = agentMessages.length - 1; index >= 0; index -= 1) {
|
|
1715
|
+
const message = agentMessages[index];
|
|
1716
|
+
if (message.role === "assistant" && message.content === assistantContent) {
|
|
1717
|
+
assistantIndex = index;
|
|
1718
|
+
break;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
if (assistantIndex < 0) return false;
|
|
1722
|
+
const after = agentMessages.slice(assistantIndex + 1);
|
|
1723
|
+
return after.some(
|
|
1724
|
+
(message) => message.role === "tool" && (message.metadata?.toolId === call.toolId || typeof message.content === "string" && message.content.includes(`Tool: ${call.toolId}`))
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
function resolveMaxIterations(context) {
|
|
1728
|
+
const configured = context.agentToolLoop?.maxIterations;
|
|
1729
|
+
return configured != null && configured > 0 ? configured : DEFAULT_MAX_ITERATIONS;
|
|
1730
|
+
}
|
|
1731
|
+
function createAgentToolLoopState(context) {
|
|
1732
|
+
return {
|
|
1733
|
+
iteration: 0,
|
|
1734
|
+
maxIterations: resolveMaxIterations(context),
|
|
1735
|
+
recentToolCalls: [],
|
|
1736
|
+
agentStarted: false,
|
|
1737
|
+
agentStopped: false
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
function markAgentToolLoopStop(state, reason) {
|
|
1741
|
+
state.stopReason ??= reason;
|
|
1742
|
+
}
|
|
1743
|
+
function finishAgentToolLoopThinking(state, reason, data) {
|
|
1744
|
+
markAgentToolLoopStop(state, reason);
|
|
1745
|
+
return { type: "thinking", data };
|
|
1746
|
+
}
|
|
1747
|
+
async function startAgentToolLoopTurn(context, input, state) {
|
|
1748
|
+
const now = Date.now();
|
|
1749
|
+
const lamportClock = await nextLamportClockForConversation(
|
|
1750
|
+
context.storage,
|
|
1751
|
+
input.conversationId
|
|
1752
|
+
);
|
|
1753
|
+
const hostUserMessage = input.userMessage;
|
|
1754
|
+
const userMessage = context.normalizeMessage?.({
|
|
1755
|
+
...hostUserMessage,
|
|
1756
|
+
messageId: hostUserMessage?.messageId ?? `${input.conversationId}:${now.toString(36)}`,
|
|
1757
|
+
conversationId: input.conversationId,
|
|
1758
|
+
originNodeId: hostUserMessage?.originNodeId ?? "local",
|
|
1759
|
+
timestamp: hostUserMessage?.timestamp ?? now,
|
|
1760
|
+
lamportClock: hostUserMessage?.lamportClock ?? lamportClock,
|
|
1761
|
+
role: "user",
|
|
1762
|
+
content: hostUserMessage?.content ?? input.message
|
|
1763
|
+
}) ?? {
|
|
1764
|
+
...hostUserMessage,
|
|
1765
|
+
messageId: hostUserMessage?.messageId ?? `${input.conversationId}:${now.toString(36)}`,
|
|
1766
|
+
conversationId: input.conversationId,
|
|
1767
|
+
originNodeId: hostUserMessage?.originNodeId ?? "local",
|
|
1768
|
+
timestamp: hostUserMessage?.timestamp ?? now,
|
|
1769
|
+
lamportClock: hostUserMessage?.lamportClock ?? lamportClock,
|
|
1770
|
+
role: "user",
|
|
1771
|
+
content: hostUserMessage?.content ?? input.message
|
|
1772
|
+
};
|
|
1773
|
+
if (input.resumeSession && input.resumeSession.length > 0) {
|
|
1774
|
+
await context.storage.insertMessagesIfAbsent(input.resumeSession);
|
|
1775
|
+
}
|
|
1776
|
+
await context.storage.appendMessage(userMessage);
|
|
1777
|
+
if (hasHooks("UserPromptSubmit")) {
|
|
1778
|
+
const hookResult = await executeHooks("UserPromptSubmit", context, {
|
|
1779
|
+
message: input.message,
|
|
1780
|
+
conversationId: input.conversationId
|
|
1781
|
+
});
|
|
1782
|
+
if (!hookResult.allowed) {
|
|
1783
|
+
return {
|
|
1784
|
+
action: "stop",
|
|
1785
|
+
step: {
|
|
1786
|
+
type: "thinking",
|
|
1787
|
+
data: {
|
|
1788
|
+
status: "blocked",
|
|
1789
|
+
conversationId: input.conversationId,
|
|
1790
|
+
reason: hookResult.reason ?? "Blocked by UserPromptSubmit hook"
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
const initialDefinitionId = await inferDefinitionId(context.storage, input.conversationId);
|
|
1797
|
+
if (hasHooks("AgentStart")) {
|
|
1798
|
+
const hookResult = await executeHooks("AgentStart", context, {
|
|
1799
|
+
conversationId: input.conversationId,
|
|
1800
|
+
definitionId: initialDefinitionId
|
|
1801
|
+
});
|
|
1802
|
+
if (!hookResult.allowed) {
|
|
1803
|
+
return {
|
|
1804
|
+
action: "stop",
|
|
1805
|
+
step: {
|
|
1806
|
+
type: "thinking",
|
|
1807
|
+
data: {
|
|
1808
|
+
status: "blocked",
|
|
1809
|
+
conversationId: input.conversationId,
|
|
1810
|
+
reason: hookResult.reason ?? "Blocked by AgentStart hook"
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
state.agentStarted = true;
|
|
1817
|
+
return { action: "continue" };
|
|
1818
|
+
}
|
|
1819
|
+
async function* runAgentToolLoopIteration(context, input, state) {
|
|
1820
|
+
const options = context.agentToolLoop ?? {};
|
|
1821
|
+
const enableToolLoop = options.enableToolLoop !== false;
|
|
1822
|
+
const fallbackRegistry = options.fallbackRegistryTools !== false;
|
|
1823
|
+
const checkpointOptions = options.sessionCheckpoint;
|
|
1824
|
+
if (state.iteration >= state.maxIterations) {
|
|
1825
|
+
yield finishAgentToolLoopThinking(state, "max-iterations", {
|
|
1826
|
+
status: "max-iterations",
|
|
1827
|
+
conversationId: input.conversationId,
|
|
1828
|
+
maxIterations: state.maxIterations
|
|
1829
|
+
});
|
|
1830
|
+
return { action: "stop", reason: "max-iterations" };
|
|
1831
|
+
}
|
|
1832
|
+
state.iteration += 1;
|
|
1833
|
+
const iteration = state.iteration;
|
|
1834
|
+
if (options.isCancelled?.(input.conversationId)) {
|
|
1835
|
+
yield finishAgentToolLoopThinking(state, "cancelled", {
|
|
1836
|
+
status: "cancelled",
|
|
1837
|
+
conversationId: input.conversationId
|
|
1838
|
+
});
|
|
1839
|
+
return { action: "stop", reason: "cancelled" };
|
|
1840
|
+
}
|
|
1841
|
+
const rawHistory = await context.storage.getMessages(input.conversationId, {
|
|
1842
|
+
mode: "full-content"
|
|
1843
|
+
});
|
|
1844
|
+
const { history, steps: compactionSteps } = await prepareIterationHistory({
|
|
1845
|
+
context,
|
|
1846
|
+
conversationId: input.conversationId,
|
|
1847
|
+
iteration,
|
|
1848
|
+
rawHistory,
|
|
1849
|
+
agentToolLoopOptions: options
|
|
1850
|
+
});
|
|
1851
|
+
for (const step of compactionSteps) {
|
|
1852
|
+
yield step;
|
|
1853
|
+
}
|
|
1854
|
+
const runtimeAgent = context.resolveAgentRuntimeView ? await context.resolveAgentRuntimeView(input.conversationId, history) : { id: input.conversationId, messages: history };
|
|
1855
|
+
const hookContext = {
|
|
1856
|
+
...context,
|
|
1857
|
+
agent: runtimeAgent,
|
|
1858
|
+
persistAgentMessage: async (message) => {
|
|
1859
|
+
await context.storage.appendMessage(message);
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
yield {
|
|
1863
|
+
type: "thinking",
|
|
1864
|
+
data: {
|
|
1865
|
+
status: "calling-llm",
|
|
1866
|
+
conversationId: input.conversationId,
|
|
1867
|
+
messageCount: history.length,
|
|
1868
|
+
iteration
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
const messages = await buildLlmMessages(context, input.conversationId, history);
|
|
1872
|
+
const request = { conversationId: input.conversationId, messages };
|
|
1873
|
+
const assistantMessageId = `${input.conversationId}:a:${iteration}:${Date.now().toString(36)}`;
|
|
1874
|
+
const assistantLamportClock = await nextLamportClockForConversation(
|
|
1875
|
+
context.storage,
|
|
1876
|
+
input.conversationId
|
|
1877
|
+
);
|
|
1878
|
+
const buildAssistantMessage = (content) => context.normalizeMessage?.({
|
|
1879
|
+
messageId: assistantMessageId,
|
|
1880
|
+
conversationId: input.conversationId,
|
|
1881
|
+
originNodeId: "local",
|
|
1882
|
+
timestamp: Date.now(),
|
|
1883
|
+
lamportClock: assistantLamportClock,
|
|
1884
|
+
role: "assistant",
|
|
1885
|
+
content
|
|
1886
|
+
}) ?? {
|
|
1887
|
+
messageId: assistantMessageId,
|
|
1888
|
+
conversationId: input.conversationId,
|
|
1889
|
+
originNodeId: "local",
|
|
1890
|
+
timestamp: Date.now(),
|
|
1891
|
+
lamportClock: assistantLamportClock,
|
|
1892
|
+
role: "assistant",
|
|
1893
|
+
content
|
|
1894
|
+
};
|
|
1895
|
+
const updateAssistantView = (message) => {
|
|
1896
|
+
const existingIndex = hookContext.agent.messages.findIndex(
|
|
1897
|
+
(item) => item.messageId === message.messageId
|
|
1898
|
+
);
|
|
1899
|
+
if (existingIndex >= 0) {
|
|
1900
|
+
hookContext.agent.messages[existingIndex] = message;
|
|
1901
|
+
} else {
|
|
1902
|
+
hookContext.agent.messages.push(message);
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
let assistantText = "";
|
|
1906
|
+
for await (const chunk of streamLlm(context, request)) {
|
|
1907
|
+
assistantText += chunkToText2(chunk);
|
|
1908
|
+
const transientAssistantMessage = buildAssistantMessage(assistantText);
|
|
1909
|
+
updateAssistantView(transientAssistantMessage);
|
|
1910
|
+
try {
|
|
1911
|
+
await context.onTransientMessage?.(transientAssistantMessage);
|
|
1912
|
+
} catch (error) {
|
|
1913
|
+
context.logger?.warn?.("[agentToolLoop] transient message subscriber failed:", error);
|
|
1914
|
+
}
|
|
1915
|
+
yield { type: "message", data: chunk };
|
|
1916
|
+
}
|
|
1917
|
+
const definitionId = await inferDefinitionId(context.storage, input.conversationId);
|
|
1918
|
+
const agentDefinition = await resolveAgentDefinitionModel(context, definitionId);
|
|
1919
|
+
const frameworkConfig = agentDefinition?.agentFrameworkConfig;
|
|
1920
|
+
const hasPlugins = Boolean(
|
|
1921
|
+
frameworkConfig?.plugins && Array.isArray(frameworkConfig.plugins) && frameworkConfig.plugins.length > 0
|
|
1922
|
+
);
|
|
1923
|
+
const assistantMessage = buildAssistantMessage(assistantText);
|
|
1924
|
+
updateAssistantView(assistantMessage);
|
|
1925
|
+
await hookContext.persistAgentMessage?.(assistantMessage);
|
|
1926
|
+
const { calls, parallel } = matchAllToolCallings(assistantText);
|
|
1927
|
+
if (hasPlugins && frameworkConfig) {
|
|
1928
|
+
const { hooks } = await createHooksWithPlugins(
|
|
1929
|
+
frameworkConfig,
|
|
1930
|
+
{
|
|
1931
|
+
pluginRegistry: resolvePromptPluginMap(context)
|
|
1932
|
+
}
|
|
1933
|
+
);
|
|
1934
|
+
const responseCompletePayload = {
|
|
1935
|
+
agentFrameworkContext: hookContext,
|
|
1936
|
+
response: { status: "done", content: assistantText },
|
|
1937
|
+
agentFrameworkConfig: frameworkConfig,
|
|
1938
|
+
requestId: void 0,
|
|
1939
|
+
toolConfig: { id: "_memeloop", toolId: "_memeloop" },
|
|
1940
|
+
actions: {}
|
|
1941
|
+
};
|
|
1942
|
+
await runResponseCompleteHooks(hooks, responseCompletePayload);
|
|
1943
|
+
await context.storage.insertMessagesIfAbsent(hookContext.agent.messages);
|
|
1944
|
+
const postProcess = await responseConcat(
|
|
1945
|
+
frameworkConfig,
|
|
1946
|
+
assistantText,
|
|
1947
|
+
hookContext,
|
|
1948
|
+
hookContext.agent.messages
|
|
1949
|
+
);
|
|
1950
|
+
const yieldTarget = responseCompletePayload.actions?.yieldNextRoundTo ?? postProcess.yieldNextRoundTo;
|
|
1951
|
+
if (yieldTarget === "human") {
|
|
1952
|
+
yield finishAgentToolLoopThinking(state, "completed", {
|
|
1953
|
+
status: "input-required",
|
|
1954
|
+
conversationId: input.conversationId
|
|
1955
|
+
});
|
|
1956
|
+
return { action: "stop", reason: "completed" };
|
|
1957
|
+
}
|
|
1958
|
+
if (yieldTarget === "self") {
|
|
1959
|
+
return { action: "continue" };
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
if (calls.length === 0) {
|
|
1963
|
+
markAgentToolLoopStop(state, "completed");
|
|
1964
|
+
return { action: "stop", reason: "completed" };
|
|
1965
|
+
}
|
|
1966
|
+
if (!enableToolLoop) {
|
|
1967
|
+
markAgentToolLoopStop(state, "completed");
|
|
1968
|
+
return { action: "stop", reason: "completed" };
|
|
1969
|
+
}
|
|
1970
|
+
const pending = calls.filter(
|
|
1971
|
+
(call) => !toolCallHandledInAgentMessages(hookContext.agent.messages, assistantText, call)
|
|
1972
|
+
);
|
|
1973
|
+
if (pending.length === 0) {
|
|
1974
|
+
if (hasPlugins && calls.length > 0) {
|
|
1975
|
+
return { action: "continue" };
|
|
1976
|
+
}
|
|
1977
|
+
markAgentToolLoopStop(state, "completed");
|
|
1978
|
+
return { action: "stop", reason: "completed" };
|
|
1979
|
+
}
|
|
1980
|
+
if (!fallbackRegistry && hasPlugins) {
|
|
1981
|
+
return { action: "continue" };
|
|
1982
|
+
}
|
|
1983
|
+
const allowedCalls = yield* gateToolCallsWithPreToolUse(
|
|
1984
|
+
context,
|
|
1985
|
+
options,
|
|
1986
|
+
definitionId,
|
|
1987
|
+
input.conversationId,
|
|
1988
|
+
pending
|
|
1989
|
+
);
|
|
1990
|
+
if (allowedCalls.length === 0) {
|
|
1991
|
+
return { action: "continue" };
|
|
1992
|
+
}
|
|
1993
|
+
yield* runRegistryToolCalls({
|
|
1994
|
+
context,
|
|
1995
|
+
agentToolLoopOptions: options,
|
|
1996
|
+
conversationId: input.conversationId,
|
|
1997
|
+
calls: allowedCalls,
|
|
1998
|
+
parallel,
|
|
1999
|
+
recentToolCalls: state.recentToolCalls
|
|
2000
|
+
});
|
|
2001
|
+
if (checkpointOptions?.enabled) {
|
|
2002
|
+
const checkpointStore = checkpointOptions.store;
|
|
2003
|
+
if (!checkpointStore) {
|
|
2004
|
+
if (context.logger?.warn) {
|
|
2005
|
+
context.logger.warn("[agentToolLoop] checkpoint enabled without a checkpoint store");
|
|
2006
|
+
} else {
|
|
2007
|
+
console.warn("[agentToolLoop] checkpoint enabled without a checkpoint store");
|
|
2008
|
+
}
|
|
2009
|
+
return { action: "continue" };
|
|
2010
|
+
}
|
|
2011
|
+
try {
|
|
2012
|
+
const allMessages = await context.storage.getMessages(input.conversationId, {
|
|
2013
|
+
mode: "full-content"
|
|
2014
|
+
});
|
|
2015
|
+
await checkpointStore.saveCheckpoint(input.conversationId, allMessages);
|
|
2016
|
+
} catch (error) {
|
|
2017
|
+
if (context.logger?.warn) {
|
|
2018
|
+
context.logger.warn("[agentToolLoop] checkpoint save failed:", error);
|
|
2019
|
+
} else {
|
|
2020
|
+
console.warn("[agentToolLoop] checkpoint save failed:", error);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
return { action: "continue" };
|
|
2025
|
+
}
|
|
2026
|
+
async function stopAgentToolLoopTurn(context, input, state, reason) {
|
|
2027
|
+
if (reason) markAgentToolLoopStop(state, reason);
|
|
2028
|
+
if (state.agentStarted && !state.agentStopped && state.stopReason && hasHooks("AgentStop")) {
|
|
2029
|
+
state.agentStopped = true;
|
|
2030
|
+
await executeHooks("AgentStop", context, {
|
|
2031
|
+
conversationId: input.conversationId,
|
|
2032
|
+
reason: state.stopReason
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// src/loopAPI/agent-tool-loop/directRunner.ts
|
|
2038
|
+
function createAgentToolLoopRunner(context) {
|
|
2039
|
+
return async function* agentToolLoopRunner(input) {
|
|
2040
|
+
const state = createAgentToolLoopState(context);
|
|
2041
|
+
try {
|
|
2042
|
+
const start = await startAgentToolLoopTurn(context, input, state);
|
|
2043
|
+
if (start.step) yield start.step;
|
|
2044
|
+
if (start.action === "stop") return;
|
|
2045
|
+
while (true) {
|
|
2046
|
+
const result = yield* runAgentToolLoopIteration(context, input, state);
|
|
2047
|
+
if (result.action === "stop") return;
|
|
2048
|
+
}
|
|
2049
|
+
} catch (error) {
|
|
2050
|
+
await stopAgentToolLoopTurn(context, input, state, "error");
|
|
2051
|
+
throw error;
|
|
2052
|
+
} finally {
|
|
2053
|
+
await stopAgentToolLoopTurn(context, input, state);
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// src/loopAPI/registry.ts
|
|
2059
|
+
function normalizePluginSelections(selected) {
|
|
2060
|
+
if (!selected) return void 0;
|
|
2061
|
+
const result = /* @__PURE__ */ new Map();
|
|
2062
|
+
for (const entry of selected) {
|
|
2063
|
+
if (typeof entry === "string") {
|
|
2064
|
+
result.set(entry, { id: entry, enabled: true });
|
|
2065
|
+
continue;
|
|
2066
|
+
}
|
|
2067
|
+
if (entry.enabled === false) continue;
|
|
2068
|
+
result.set(entry.id, entry);
|
|
2069
|
+
}
|
|
2070
|
+
return result;
|
|
2071
|
+
}
|
|
2072
|
+
var LoopRegistryImpl = class {
|
|
2073
|
+
loops = /* @__PURE__ */ new Map();
|
|
2074
|
+
profiles = /* @__PURE__ */ new Map();
|
|
2075
|
+
plugins = /* @__PURE__ */ new Map();
|
|
2076
|
+
// ── Loop registration ──
|
|
2077
|
+
registerLoop(definition) {
|
|
2078
|
+
if (!definition.id) throw new Error("Loop definition must have an id");
|
|
2079
|
+
this.loops.set(definition.id, definition);
|
|
2080
|
+
}
|
|
2081
|
+
getLoop(id) {
|
|
2082
|
+
return this.loops.get(id);
|
|
2083
|
+
}
|
|
2084
|
+
listLoops() {
|
|
2085
|
+
return Array.from(this.loops.values());
|
|
2086
|
+
}
|
|
2087
|
+
// ── Profile registration ──
|
|
2088
|
+
registerProfile(profile) {
|
|
2089
|
+
if (!profile.id) throw new Error("Loop profile must have an id");
|
|
2090
|
+
this.profiles.set(profile.id, profile);
|
|
2091
|
+
}
|
|
2092
|
+
getProfile(id) {
|
|
2093
|
+
return this.profiles.get(id);
|
|
2094
|
+
}
|
|
2095
|
+
listProfiles() {
|
|
2096
|
+
return Array.from(this.profiles.values());
|
|
2097
|
+
}
|
|
2098
|
+
// ── Plugin registration ──
|
|
2099
|
+
registerPlugin(plugin) {
|
|
2100
|
+
if (!plugin.id) throw new Error("Loop plugin must have an id");
|
|
2101
|
+
this.plugins.set(plugin.id, plugin);
|
|
2102
|
+
}
|
|
2103
|
+
getPlugin(id) {
|
|
2104
|
+
return this.plugins.get(id);
|
|
2105
|
+
}
|
|
2106
|
+
listPlugins() {
|
|
2107
|
+
return Array.from(this.plugins.values());
|
|
2108
|
+
}
|
|
2109
|
+
installPluginsForLoop(loopId, target, selected) {
|
|
2110
|
+
const selectedEntries = normalizePluginSelections(selected);
|
|
2111
|
+
for (const plugin of this.plugins.values()) {
|
|
2112
|
+
const entry = selectedEntries?.get(plugin.id);
|
|
2113
|
+
if (selectedEntries && !entry) continue;
|
|
2114
|
+
if (plugin.targetLoopId && plugin.targetLoopId !== "*" && plugin.targetLoopId !== loopId) {
|
|
2115
|
+
continue;
|
|
2116
|
+
}
|
|
2117
|
+
if (plugin.install) {
|
|
2118
|
+
plugin.install(target, entry?.config);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
installPluginsForProfile(profile, target) {
|
|
2123
|
+
this.installPluginsForLoop(profile.loopId ?? "agent-tool-loop", target, profile.plugins ?? []);
|
|
2124
|
+
}
|
|
2125
|
+
createRunnerForProfile(profile, context = {}) {
|
|
2126
|
+
const loopId = profile.loopId ?? "agent-tool-loop";
|
|
2127
|
+
this.installPluginsForProfile(profile, context);
|
|
2128
|
+
return this.createRunner(loopId, { ...context, profile });
|
|
2129
|
+
}
|
|
2130
|
+
// ── Lifecycle ──
|
|
2131
|
+
createRunner(loopId, context = {}) {
|
|
2132
|
+
const definition = this.loops.get(loopId);
|
|
2133
|
+
if (!definition) return null;
|
|
2134
|
+
return definition.createRunner(context);
|
|
2135
|
+
}
|
|
2136
|
+
reset() {
|
|
2137
|
+
this.loops.clear();
|
|
2138
|
+
this.profiles.clear();
|
|
2139
|
+
this.plugins.clear();
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
var defaultRegistry = null;
|
|
2143
|
+
function getLoopRegistry() {
|
|
2144
|
+
if (!defaultRegistry) {
|
|
2145
|
+
defaultRegistry = new LoopRegistryImpl();
|
|
2146
|
+
}
|
|
2147
|
+
return defaultRegistry;
|
|
2148
|
+
}
|
|
2149
|
+
function resetLoopRegistry() {
|
|
2150
|
+
if (defaultRegistry) {
|
|
2151
|
+
defaultRegistry.reset();
|
|
2152
|
+
}
|
|
2153
|
+
defaultRegistry = null;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
// src/loopProfiles/builtinProfileSources.ts
|
|
2157
|
+
var builtinProfileSources = {
|
|
2158
|
+
"code-assistant": [
|
|
2159
|
+
"{",
|
|
2160
|
+
' "id": "memeloop:code-assistant",',
|
|
2161
|
+
' "name": "\u4EE3\u7801\u52A9\u624B",',
|
|
2162
|
+
' "description": "\u4E13\u6CE8\u4E8E\u4EE3\u7801\u7F16\u5199\u3001\u91CD\u6784\u548C\u8C03\u8BD5\u7684\u52A9\u624B\u3002",',
|
|
2163
|
+
' "loopId": "agent-tool-loop",',
|
|
2164
|
+
' "systemPrompt": "You are a senior software engineer helping the user write and refactor code.",',
|
|
2165
|
+
' "tools": [',
|
|
2166
|
+
' "workspacesList",',
|
|
2167
|
+
' "modelContextProtocol",',
|
|
2168
|
+
' "spawnAgent",',
|
|
2169
|
+
' "askQuestion",',
|
|
2170
|
+
' "getErrors",',
|
|
2171
|
+
' "webFetch"',
|
|
2172
|
+
" ],",
|
|
2173
|
+
' "plugins": [',
|
|
2174
|
+
' { "id": "builtin:mcp-client" },',
|
|
2175
|
+
' { "id": "builtin:mcp-forward" },',
|
|
2176
|
+
' { "id": "builtin:spawn-agent" },',
|
|
2177
|
+
' { "id": "builtin:ask-question" }',
|
|
2178
|
+
" ],",
|
|
2179
|
+
' "agentTools": [',
|
|
2180
|
+
" {",
|
|
2181
|
+
' "toolId": "workspacesList",',
|
|
2182
|
+
' "parameters": {',
|
|
2183
|
+
' "workspacesListParam": { "targetId": "builtin-system", "position": "after" }',
|
|
2184
|
+
" }",
|
|
2185
|
+
" },",
|
|
2186
|
+
" {",
|
|
2187
|
+
' "toolId": "modelContextProtocol",',
|
|
2188
|
+
' "parameters": {',
|
|
2189
|
+
' "modelContextProtocolParam": {',
|
|
2190
|
+
' "serverUrl": "http://127.0.0.1:38385/mcp",',
|
|
2191
|
+
' "timeoutSecond": 30,',
|
|
2192
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2193
|
+
' "toolResultDuration": 1',
|
|
2194
|
+
" }",
|
|
2195
|
+
" }",
|
|
2196
|
+
" },",
|
|
2197
|
+
" {",
|
|
2198
|
+
' "toolId": "spawnAgent",',
|
|
2199
|
+
' "parameters": {',
|
|
2200
|
+
' "spawnAgentParam": {',
|
|
2201
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2202
|
+
' "toolResultDuration": 2,',
|
|
2203
|
+
' "defaultTimeoutMs": 120000',
|
|
2204
|
+
" }",
|
|
2205
|
+
" }",
|
|
2206
|
+
" },",
|
|
2207
|
+
" {",
|
|
2208
|
+
' "toolId": "askQuestion",',
|
|
2209
|
+
' "parameters": {',
|
|
2210
|
+
' "askQuestionParam": {',
|
|
2211
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" }',
|
|
2212
|
+
" }",
|
|
2213
|
+
" }",
|
|
2214
|
+
" },",
|
|
2215
|
+
" {",
|
|
2216
|
+
' "toolId": "getErrors",',
|
|
2217
|
+
' "parameters": {',
|
|
2218
|
+
' "getErrorsParam": {',
|
|
2219
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" }',
|
|
2220
|
+
" }",
|
|
2221
|
+
" }",
|
|
2222
|
+
" },",
|
|
2223
|
+
" {",
|
|
2224
|
+
' "toolId": "webFetch",',
|
|
2225
|
+
' "parameters": {',
|
|
2226
|
+
' "webFetchParam": {',
|
|
2227
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2228
|
+
' "toolResultDuration": 1',
|
|
2229
|
+
" }",
|
|
2230
|
+
" }",
|
|
2231
|
+
" }",
|
|
2232
|
+
" ],",
|
|
2233
|
+
' "agentFrameworkConfig": {',
|
|
2234
|
+
' "prompts": [',
|
|
2235
|
+
" {",
|
|
2236
|
+
' "id": "builtin-system",',
|
|
2237
|
+
' "role": "system",',
|
|
2238
|
+
' "text": "You are a senior software engineer helping the user write and refactor code. Prefer small, safe edits; use file.read and file.search before changing code; use terminal.execute for build/tests when appropriate."',
|
|
2239
|
+
" }",
|
|
2240
|
+
" ],",
|
|
2241
|
+
' "plugins": [{ "toolId": "fullReplacement" }],',
|
|
2242
|
+
' "response": []',
|
|
2243
|
+
" },",
|
|
2244
|
+
' "modelConfig": {',
|
|
2245
|
+
' "provider": "memeloop",',
|
|
2246
|
+
' "model": "claude-opus-4.6",',
|
|
2247
|
+
' "temperature": 0.2,',
|
|
2248
|
+
' "maxTokens": 4096',
|
|
2249
|
+
" },",
|
|
2250
|
+
' "version": "1.0.0"',
|
|
2251
|
+
"}",
|
|
2252
|
+
""
|
|
2253
|
+
].join("\n"),
|
|
2254
|
+
"frontend-ui-ux": [
|
|
2255
|
+
"{",
|
|
2256
|
+
' "id": "memeloop:frontend-ui-ux",',
|
|
2257
|
+
' "name": "Frontend UI/UX",',
|
|
2258
|
+
' "description": "Designer-turned-developer who crafts stunning UI/UX even without design mockups.",',
|
|
2259
|
+
' "loopId": "agent-tool-loop",',
|
|
2260
|
+
' "systemPrompt": "You are a designer-turned-developer who crafts stunning UI/UX even without design mockups.\\n\\nWhen working on frontend tasks:\\n- Prioritize visual polish and user experience above all else\\n- Use consistent spacing, typography, and color schemes\\n- Apply modern design patterns: rounded corners, subtle shadows, smooth transitions\\n- Ensure responsive design across mobile, tablet, and desktop breakpoints\\n- Prefer accessible patterns: semantic HTML, ARIA labels, keyboard navigation\\n- Optimize for perceived performance: skeleton loaders, optimistic updates, progressive enhancement\\n- Use component composition over monolithic layouts\\n- Match the existing design system when extending an existing UI",',
|
|
2261
|
+
' "tools": [',
|
|
2262
|
+
' "workspacesList",',
|
|
2263
|
+
' "wikiSearch",',
|
|
2264
|
+
' "wikiOperation",',
|
|
2265
|
+
' "modelContextProtocol",',
|
|
2266
|
+
' "spawnAgent",',
|
|
2267
|
+
' "askQuestion",',
|
|
2268
|
+
' "webFetch"',
|
|
2269
|
+
" ],",
|
|
2270
|
+
' "plugins": [',
|
|
2271
|
+
' { "id": "builtin:mcp-client" },',
|
|
2272
|
+
' { "id": "builtin:mcp-forward" },',
|
|
2273
|
+
' { "id": "builtin:spawn-agent" },',
|
|
2274
|
+
' { "id": "builtin:ask-question" }',
|
|
2275
|
+
" ],",
|
|
2276
|
+
' "agentTools": [',
|
|
2277
|
+
" {",
|
|
2278
|
+
' "toolId": "workspacesList",',
|
|
2279
|
+
' "parameters": {',
|
|
2280
|
+
' "workspacesListParam": { "targetId": "builtin-system", "position": "after" }',
|
|
2281
|
+
" }",
|
|
2282
|
+
" },",
|
|
2283
|
+
" {",
|
|
2284
|
+
' "toolId": "wikiSearch",',
|
|
2285
|
+
' "parameters": {',
|
|
2286
|
+
' "wikiSearchParam": {',
|
|
2287
|
+
' "sourceType": "wiki",',
|
|
2288
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2289
|
+
' "toolResultDuration": 1',
|
|
2290
|
+
" }",
|
|
2291
|
+
" }",
|
|
2292
|
+
" },",
|
|
2293
|
+
" {",
|
|
2294
|
+
' "toolId": "wikiOperation",',
|
|
2295
|
+
' "parameters": {',
|
|
2296
|
+
' "wikiOperationParam": {',
|
|
2297
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2298
|
+
' "toolResultDuration": 1',
|
|
2299
|
+
" }",
|
|
2300
|
+
" }",
|
|
2301
|
+
" },",
|
|
2302
|
+
" {",
|
|
2303
|
+
' "toolId": "modelContextProtocol",',
|
|
2304
|
+
' "parameters": {',
|
|
2305
|
+
' "modelContextProtocolParam": {',
|
|
2306
|
+
' "serverUrl": "http://127.0.0.1:38385/mcp",',
|
|
2307
|
+
' "timeoutSecond": 30,',
|
|
2308
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2309
|
+
' "toolResultDuration": 1',
|
|
2310
|
+
" }",
|
|
2311
|
+
" }",
|
|
2312
|
+
" },",
|
|
2313
|
+
" {",
|
|
2314
|
+
' "toolId": "spawnAgent",',
|
|
2315
|
+
' "parameters": {',
|
|
2316
|
+
' "spawnAgentParam": {',
|
|
2317
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2318
|
+
' "toolResultDuration": 2,',
|
|
2319
|
+
' "defaultTimeoutMs": 120000',
|
|
2320
|
+
" }",
|
|
2321
|
+
" }",
|
|
2322
|
+
" },",
|
|
2323
|
+
" {",
|
|
2324
|
+
' "toolId": "askQuestion",',
|
|
2325
|
+
' "parameters": {',
|
|
2326
|
+
' "askQuestionParam": {',
|
|
2327
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" }',
|
|
2328
|
+
" }",
|
|
2329
|
+
" }",
|
|
2330
|
+
" },",
|
|
2331
|
+
" {",
|
|
2332
|
+
' "toolId": "webFetch",',
|
|
2333
|
+
' "parameters": {',
|
|
2334
|
+
' "webFetchParam": {',
|
|
2335
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2336
|
+
' "toolResultDuration": 1',
|
|
2337
|
+
" }",
|
|
2338
|
+
" }",
|
|
2339
|
+
" }",
|
|
2340
|
+
" ],",
|
|
2341
|
+
' "agentFrameworkConfig": {',
|
|
2342
|
+
' "prompts": [',
|
|
2343
|
+
" {",
|
|
2344
|
+
' "id": "builtin-system",',
|
|
2345
|
+
' "role": "system",',
|
|
2346
|
+
' "text": "You are a designer-turned-developer who crafts stunning UI/UX even without design mockups.\\n\\nWhen working on frontend tasks:\\n- Prioritize visual polish and user experience above all else\\n- Use consistent spacing, typography, and color schemes\\n- Apply modern design patterns: rounded corners, subtle shadows, smooth transitions\\n- Ensure responsive design across mobile, tablet, and desktop breakpoints\\n- Prefer accessible patterns: semantic HTML, ARIA labels, keyboard navigation\\n- Optimize for perceived performance: skeleton loaders, optimistic updates, progressive enhancement\\n- Use component composition over monolithic layouts\\n- Match the existing design system when extending an existing UI"',
|
|
2347
|
+
" }",
|
|
2348
|
+
" ],",
|
|
2349
|
+
' "plugins": [],',
|
|
2350
|
+
' "response": []',
|
|
2351
|
+
" },",
|
|
2352
|
+
' "modelConfig": {',
|
|
2353
|
+
' "provider": "memeloop",',
|
|
2354
|
+
' "model": "claude-opus-4.6",',
|
|
2355
|
+
' "temperature": 0.3,',
|
|
2356
|
+
' "maxTokens": 4096',
|
|
2357
|
+
" },",
|
|
2358
|
+
' "version": "1.0.0"',
|
|
2359
|
+
"}",
|
|
2360
|
+
""
|
|
2361
|
+
].join("\n"),
|
|
2362
|
+
"general-assistant": [
|
|
2363
|
+
"{",
|
|
2364
|
+
' "id": "memeloop:general-assistant",',
|
|
2365
|
+
' "name": "\u901A\u7528\u52A9\u624B",',
|
|
2366
|
+
' "description": "\u901A\u7528\u81EA\u7136\u8BED\u8A00\u52A9\u7406\uFF0C\u7528\u4E8E\u65E5\u5E38\u95EE\u7B54\u548C\u7B80\u5355\u4EFB\u52A1\u3002",',
|
|
2367
|
+
' "loopId": "agent-tool-loop",',
|
|
2368
|
+
' "systemPrompt": "You are a helpful general-purpose assistant. When you need to use a tool, output ONLY the tool call in this exact XML format:\\n<tool_use name=\\"TOOL_NAME\\">{\\"param\\":\\"value\\"}</tool_use>\\nNever wrap tool calls in markdown code blocks or add explanatory text before/after the tool call in the same message. After receiving the tool result, continue responding normally.",',
|
|
2369
|
+
' "tools": [',
|
|
2370
|
+
' "workspacesList",',
|
|
2371
|
+
' "wikiSearch",',
|
|
2372
|
+
' "wikiOperation",',
|
|
2373
|
+
' "modelContextProtocol",',
|
|
2374
|
+
' "spawnAgent",',
|
|
2375
|
+
' "askQuestion"',
|
|
2376
|
+
" ],",
|
|
2377
|
+
' "plugins": [',
|
|
2378
|
+
' { "id": "builtin:mcp-client" },',
|
|
2379
|
+
' { "id": "builtin:mcp-forward" },',
|
|
2380
|
+
' { "id": "builtin:spawn-agent" },',
|
|
2381
|
+
' { "id": "builtin:ask-question" }',
|
|
2382
|
+
" ],",
|
|
2383
|
+
' "agentTools": [',
|
|
2384
|
+
" {",
|
|
2385
|
+
' "toolId": "workspacesList",',
|
|
2386
|
+
' "parameters": {',
|
|
2387
|
+
' "workspacesListParam": { "targetId": "builtin-system", "position": "after" }',
|
|
2388
|
+
" }",
|
|
2389
|
+
" },",
|
|
2390
|
+
" {",
|
|
2391
|
+
' "toolId": "wikiSearch",',
|
|
2392
|
+
' "parameters": {',
|
|
2393
|
+
' "wikiSearchParam": {',
|
|
2394
|
+
' "sourceType": "wiki",',
|
|
2395
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2396
|
+
' "toolResultDuration": 1',
|
|
2397
|
+
" }",
|
|
2398
|
+
" }",
|
|
2399
|
+
" },",
|
|
2400
|
+
" {",
|
|
2401
|
+
' "toolId": "wikiOperation",',
|
|
2402
|
+
' "parameters": {',
|
|
2403
|
+
' "wikiOperationParam": {',
|
|
2404
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2405
|
+
' "toolResultDuration": 1',
|
|
2406
|
+
" }",
|
|
2407
|
+
" }",
|
|
2408
|
+
" },",
|
|
2409
|
+
" {",
|
|
2410
|
+
' "toolId": "modelContextProtocol",',
|
|
2411
|
+
' "parameters": {',
|
|
2412
|
+
' "modelContextProtocolParam": {',
|
|
2413
|
+
' "serverUrl": "http://127.0.0.1:38385/mcp",',
|
|
2414
|
+
' "timeoutSecond": 30,',
|
|
2415
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2416
|
+
' "toolResultDuration": 1',
|
|
2417
|
+
" }",
|
|
2418
|
+
" }",
|
|
2419
|
+
" },",
|
|
2420
|
+
" {",
|
|
2421
|
+
' "toolId": "spawnAgent",',
|
|
2422
|
+
' "parameters": {',
|
|
2423
|
+
' "spawnAgentParam": {',
|
|
2424
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2425
|
+
' "toolResultDuration": 2,',
|
|
2426
|
+
' "defaultTimeoutMs": 120000',
|
|
2427
|
+
" }",
|
|
2428
|
+
" }",
|
|
2429
|
+
" },",
|
|
2430
|
+
" {",
|
|
2431
|
+
' "toolId": "askQuestion",',
|
|
2432
|
+
' "parameters": {',
|
|
2433
|
+
' "askQuestionParam": {',
|
|
2434
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" }',
|
|
2435
|
+
" }",
|
|
2436
|
+
" }",
|
|
2437
|
+
" }",
|
|
2438
|
+
" ],",
|
|
2439
|
+
' "agentFrameworkConfig": {',
|
|
2440
|
+
' "prompts": [',
|
|
2441
|
+
" {",
|
|
2442
|
+
' "id": "builtin-system",',
|
|
2443
|
+
' "role": "system",',
|
|
2444
|
+
' "text": "You are a helpful general-purpose assistant. When you need to use a tool, output ONLY the tool call in this exact XML format:\\n<tool_use name=\\"TOOL_NAME\\">{\\"param\\":\\"value\\"}</tool_use>\\nNever wrap tool calls in markdown code blocks or add explanatory text before/after the tool call in the same message. After receiving the tool result, continue responding normally."',
|
|
2445
|
+
" }",
|
|
2446
|
+
" ],",
|
|
2447
|
+
' "plugins": [{ "toolId": "fullReplacement" }],',
|
|
2448
|
+
' "response": []',
|
|
2449
|
+
" },",
|
|
2450
|
+
' "modelConfig": {',
|
|
2451
|
+
' "provider": "memeloop",',
|
|
2452
|
+
' "model": "claude-opus-4.6",',
|
|
2453
|
+
' "temperature": 0.5,',
|
|
2454
|
+
' "maxTokens": 4096',
|
|
2455
|
+
" },",
|
|
2456
|
+
' "version": "1.0.0"',
|
|
2457
|
+
"}",
|
|
2458
|
+
""
|
|
2459
|
+
].join("\n"),
|
|
2460
|
+
"git-master": [
|
|
2461
|
+
"{",
|
|
2462
|
+
' "id": "memeloop:git-master",',
|
|
2463
|
+
' "name": "Git Master",',
|
|
2464
|
+
' "description": "Expert git workflow management with safety-first approach.",',
|
|
2465
|
+
' "loopId": "agent-tool-loop",',
|
|
2466
|
+
' "systemPrompt": "You are an expert in git workflow management. Follow these rules:\\n\\nGit Safety Protocol:\\n- NEVER update the git config\\n- NEVER run destructive/irreversible git commands (push --force, hard reset, etc.) unless the user explicitly requests them\\n- NEVER skip hooks (--no-verify, --no-gpg-sign, etc.) unless the user explicitly requests it\\n- NEVER force push to main/master; warn the user if they request it\\n- Avoid git commit --amend. ONLY use --amend when ALL conditions are met:\\n (1) User explicitly requested amend, OR the commit succeeded and pre-commit hooks auto-modified files that need including\\n (2) HEAD commit was created by you in this conversation\\n (3) Commit has NOT been pushed to remote\\n- If commit FAILED or was REJECTED by hook, NEVER amend \u2014 fix the issue and create a NEW commit\\n- If you already pushed to remote, NEVER amend unless user explicitly requests it (requires force push)\\n\\nCommitting:\\n- NEVER commit changes unless the user explicitly asks\\n- Run: git status, git diff, git log (recent commits)\\n- Analyze all staged changes and draft a concise commit message\\n- Summarize nature of changes: add/update/fix/refactor/test/docs\\n- Do NOT commit files that likely contain secrets\\n\\nPull Requests:\\n- Check branch status, divergence from base, full commit history\\n- Create PR with descriptive title and summary body\\n- Return the PR URL when done",',
|
|
2467
|
+
' "tools": ["workspacesList", "git", "askQuestion"],',
|
|
2468
|
+
' "plugins": [{ "id": "builtin:ask-question" }],',
|
|
2469
|
+
' "agentTools": [',
|
|
2470
|
+
" {",
|
|
2471
|
+
' "toolId": "workspacesList",',
|
|
2472
|
+
' "parameters": {',
|
|
2473
|
+
' "workspacesListParam": { "targetId": "builtin-system", "position": "after" }',
|
|
2474
|
+
" }",
|
|
2475
|
+
" },",
|
|
2476
|
+
" {",
|
|
2477
|
+
' "toolId": "git",',
|
|
2478
|
+
' "parameters": {',
|
|
2479
|
+
' "gitParam": {',
|
|
2480
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" }',
|
|
2481
|
+
" }",
|
|
2482
|
+
" }",
|
|
2483
|
+
" },",
|
|
2484
|
+
" {",
|
|
2485
|
+
' "toolId": "askQuestion",',
|
|
2486
|
+
' "parameters": {',
|
|
2487
|
+
' "askQuestionParam": {',
|
|
2488
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" }',
|
|
2489
|
+
" }",
|
|
2490
|
+
" }",
|
|
2491
|
+
" }",
|
|
2492
|
+
" ],",
|
|
2493
|
+
' "agentFrameworkConfig": {',
|
|
2494
|
+
' "prompts": [',
|
|
2495
|
+
" {",
|
|
2496
|
+
' "id": "builtin-system",',
|
|
2497
|
+
' "role": "system",',
|
|
2498
|
+
' "text": "You are an expert in git workflow management. Follow these rules:\\n\\nGit Safety Protocol:\\n- NEVER update the git config\\n- NEVER run destructive/irreversible git commands (push --force, hard reset, etc.) unless the user explicitly requests them\\n- NEVER skip hooks (--no-verify, --no-gpg-sign, etc.) unless the user explicitly requests it\\n- NEVER force push to main/master; warn the user if they request it\\n- Avoid git commit --amend. ONLY use --amend when ALL conditions are met:\\n (1) User explicitly requested amend, OR the commit succeeded and pre-commit hooks auto-modified files that need including\\n (2) HEAD commit was created by you in this conversation\\n (3) Commit has NOT been pushed to remote\\n- If commit FAILED or was REJECTED by hook, NEVER amend \u2014 fix the issue and create a NEW commit\\n- If you already pushed to remote, NEVER amend unless user explicitly requests it (requires force push)\\n\\nCommitting:\\n- NEVER commit changes unless the user explicitly asks\\n- Run: git status, git diff, git log (recent commits)\\n- Analyze all staged changes and draft a concise commit message\\n- Summarize nature of changes: add/update/fix/refactor/test/docs\\n- Do NOT commit files that likely contain secrets\\n\\nPull Requests:\\n- Check branch status, divergence from base, full commit history\\n- Create PR with descriptive title and summary body\\n- Return the PR URL when done"',
|
|
2499
|
+
" }",
|
|
2500
|
+
" ],",
|
|
2501
|
+
' "plugins": [],',
|
|
2502
|
+
' "response": []',
|
|
2503
|
+
" },",
|
|
2504
|
+
' "modelConfig": {',
|
|
2505
|
+
' "provider": "memeloop",',
|
|
2506
|
+
' "model": "claude-opus-4.6",',
|
|
2507
|
+
' "temperature": 0.2,',
|
|
2508
|
+
' "maxTokens": 4096',
|
|
2509
|
+
" },",
|
|
2510
|
+
' "version": "1.0.0"',
|
|
2511
|
+
"}",
|
|
2512
|
+
""
|
|
2513
|
+
].join("\n"),
|
|
2514
|
+
playwright: [
|
|
2515
|
+
"{",
|
|
2516
|
+
' "id": "memeloop:playwright",',
|
|
2517
|
+
' "name": "Playwright",',
|
|
2518
|
+
' "description": "Browser automation via Playwright for verification, browsing, information gathering, web scraping, testing, and screenshots.",',
|
|
2519
|
+
' "loopId": "agent-tool-loop",',
|
|
2520
|
+
' "systemPrompt": "You have access to Playwright for browser automation. Use it for:\\n\\nBrowser Automation:\\n- Navigate to URLs and verify page content\\n- Fill forms, click buttons, and interact with page elements\\n- Take screenshots of specific elements or full pages\\n- Extract data from web pages (scraping)\\n- Test web application functionality end-to-end\\n- Automate browser workflows\\n- Log into websites and maintain sessions\\n\\nBest Practices:\\n- Use specific selectors (data-testid, id, or unique CSS selectors) over fragile XPath\\n- Wait for elements to be visible before interacting\\n- Handle page navigation and loading states explicitly\\n- Take screenshots at key steps for debugging\\n- Clean up browser resources when done\\n- Handle timeouts and error states gracefully\\n\\nAvailable Playwright MCP Tools:\\n- browser_navigate: Navigate to a URL\\n- browser_click: Click an element\\n- browser_type: Type into an input field\\n- browser_snapshot: Take accessibility snapshot of page\\n- browser_take_screenshot: Capture screenshot\\n- browser_fill_form: Fill multiple form fields at once\\n- browser_evaluate: Execute JavaScript in page context\\n- browser_select_option: Select from dropdown\\n- browser_drag: Drag and drop elements\\n- browser_hover: Hover over an element\\n- browser_press_key: Press a keyboard key\\n- browser_handle_dialog: Handle browser dialogs (alert/confirm/prompt)\\n- browser_close: Close the browser",',
|
|
2521
|
+
' "tools": ["modelContextProtocol", "askQuestion"],',
|
|
2522
|
+
' "plugins": [',
|
|
2523
|
+
' { "id": "builtin:mcp-client" },',
|
|
2524
|
+
' { "id": "builtin:mcp-forward" },',
|
|
2525
|
+
' { "id": "builtin:ask-question" }',
|
|
2526
|
+
" ],",
|
|
2527
|
+
' "agentTools": [',
|
|
2528
|
+
" {",
|
|
2529
|
+
' "toolId": "modelContextProtocol",',
|
|
2530
|
+
' "parameters": {',
|
|
2531
|
+
' "modelContextProtocolParam": {',
|
|
2532
|
+
' "serverUrl": "http://127.0.0.1:38385/mcp",',
|
|
2533
|
+
' "timeoutSecond": 30,',
|
|
2534
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" },',
|
|
2535
|
+
' "toolResultDuration": 1',
|
|
2536
|
+
" }",
|
|
2537
|
+
" }",
|
|
2538
|
+
" },",
|
|
2539
|
+
" {",
|
|
2540
|
+
' "toolId": "askQuestion",',
|
|
2541
|
+
' "parameters": {',
|
|
2542
|
+
' "askQuestionParam": {',
|
|
2543
|
+
' "toolListPosition": { "targetId": "builtin-system", "position": "after" }',
|
|
2544
|
+
" }",
|
|
2545
|
+
" }",
|
|
2546
|
+
" }",
|
|
2547
|
+
" ],",
|
|
2548
|
+
' "agentFrameworkConfig": {',
|
|
2549
|
+
' "prompts": [',
|
|
2550
|
+
" {",
|
|
2551
|
+
' "id": "builtin-system",',
|
|
2552
|
+
' "role": "system",',
|
|
2553
|
+
' "text": "You have access to Playwright for browser automation. Use it for:\\n\\nBrowser Automation:\\n- Navigate to URLs and verify page content\\n- Fill forms, click buttons, and interact with page elements\\n- Take screenshots of specific elements or full pages\\n- Extract data from web pages (scraping)\\n- Test web application functionality end-to-end\\n- Automate browser workflows\\n- Log into websites and maintain sessions\\n\\nBest Practices:\\n- Use specific selectors (data-testid, id, or unique CSS selectors) over fragile XPath\\n- Wait for elements to be visible before interacting\\n- Handle page navigation and loading states explicitly\\n- Take screenshots at key steps for debugging\\n- Clean up browser resources when done\\n- Handle timeouts and error states gracefully\\n\\nAvailable Playwright MCP Tools:\\n- browser_navigate: Navigate to a URL\\n- browser_click: Click an element\\n- browser_type: Type into an input field\\n- browser_snapshot: Take accessibility snapshot of page\\n- browser_take_screenshot: Capture screenshot\\n- browser_fill_form: Fill multiple form fields at once\\n- browser_evaluate: Execute JavaScript in page context\\n- browser_select_option: Select from dropdown\\n- browser_drag: Drag and drop elements\\n- browser_hover: Hover over an element\\n- browser_press_key: Press a keyboard key\\n- browser_handle_dialog: Handle browser dialogs (alert/confirm/prompt)\\n- browser_close: Close the browser"',
|
|
2554
|
+
" }",
|
|
2555
|
+
" ],",
|
|
2556
|
+
' "plugins": [],',
|
|
2557
|
+
' "response": []',
|
|
2558
|
+
" },",
|
|
2559
|
+
' "modelConfig": {',
|
|
2560
|
+
' "provider": "memeloop",',
|
|
2561
|
+
' "model": "claude-opus-4.6",',
|
|
2562
|
+
' "temperature": 0.3,',
|
|
2563
|
+
' "maxTokens": 4096',
|
|
2564
|
+
" },",
|
|
2565
|
+
' "version": "1.0.0"',
|
|
2566
|
+
"}",
|
|
2567
|
+
""
|
|
2568
|
+
].join("\n")
|
|
2569
|
+
};
|
|
2570
|
+
|
|
2571
|
+
// src/loopProfiles/loadBuiltins.ts
|
|
2572
|
+
function loadProfile(name) {
|
|
2573
|
+
const source = builtinProfileSources[name];
|
|
2574
|
+
if (!source) {
|
|
2575
|
+
throw new Error(`Builtin profile not found: ${name}`);
|
|
2576
|
+
}
|
|
2577
|
+
return JSON.parse(source);
|
|
2578
|
+
}
|
|
2579
|
+
function getBuiltinLoopProfiles() {
|
|
2580
|
+
return [
|
|
2581
|
+
loadProfile("general-assistant"),
|
|
2582
|
+
loadProfile("code-assistant"),
|
|
2583
|
+
loadProfile("frontend-ui-ux"),
|
|
2584
|
+
loadProfile("git-master"),
|
|
2585
|
+
loadProfile("playwright")
|
|
2586
|
+
];
|
|
2587
|
+
}
|
|
2588
|
+
function getBuiltinLoopProfile(id) {
|
|
2589
|
+
return getBuiltinLoopProfiles().find((p) => p.id === id);
|
|
2590
|
+
}
|
|
2591
|
+
function registerBuiltinLoopProfiles() {
|
|
2592
|
+
const registry = getLoopRegistry();
|
|
2593
|
+
for (const profile of getBuiltinLoopProfiles()) {
|
|
2594
|
+
registry.registerProfile(profile);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
export {
|
|
2599
|
+
matchPattern,
|
|
2600
|
+
mergePermissionSets,
|
|
2601
|
+
checkPermission,
|
|
2602
|
+
defaultAdmissionPolicyForTrustClass,
|
|
2603
|
+
resolveAdmissionPolicy,
|
|
2604
|
+
defaultPermissionActionForTrustClass,
|
|
2605
|
+
evaluateToolAdmission,
|
|
2606
|
+
TOOL_OPERATION_CONDITION_EFFECT_UNKNOWN,
|
|
2607
|
+
reconcileUnknownEffect,
|
|
2608
|
+
applyUnknownEffectDecision,
|
|
2609
|
+
pluginRegistry,
|
|
2610
|
+
getActivePluginRegistry,
|
|
2611
|
+
runWithPluginRegistry,
|
|
2612
|
+
createAgentFrameworkHooks,
|
|
2613
|
+
runProcessPromptsHooks,
|
|
2614
|
+
runResponseCompleteHooks,
|
|
2615
|
+
runPostProcessHooks,
|
|
2616
|
+
runToolExecutedHooks,
|
|
2617
|
+
resolvePromptPluginMap,
|
|
2618
|
+
createHooksWithPlugins,
|
|
2619
|
+
matchToolCalling,
|
|
2620
|
+
matchAllToolCallings,
|
|
2621
|
+
nextLamportClockForConversation,
|
|
2622
|
+
HookRegistry,
|
|
2623
|
+
getDefaultHookRegistry,
|
|
2624
|
+
registerHook,
|
|
2625
|
+
unregisterHook,
|
|
2626
|
+
executeHooks,
|
|
2627
|
+
hasHooks,
|
|
2628
|
+
clearHooks,
|
|
2629
|
+
listRegisteredHookTypes,
|
|
2630
|
+
getHookCount,
|
|
2631
|
+
compactMessages,
|
|
2632
|
+
shouldCompact,
|
|
2633
|
+
autoCompact,
|
|
2634
|
+
findPromptById,
|
|
2635
|
+
flattenPrompts,
|
|
2636
|
+
promptConcatStream,
|
|
2637
|
+
MEMELOOP_STRUCTURED_TOOL_KEY,
|
|
2638
|
+
truncateToolSummary,
|
|
2639
|
+
extractMemeloopStructuredToolPayload,
|
|
2640
|
+
onApprovalRequest,
|
|
2641
|
+
evaluateApproval,
|
|
2642
|
+
requestApproval,
|
|
2643
|
+
resolveApproval,
|
|
2644
|
+
getPendingApprovals,
|
|
2645
|
+
cancelPendingApprovals,
|
|
2646
|
+
createAgentToolLoopState,
|
|
2647
|
+
startAgentToolLoopTurn,
|
|
2648
|
+
runAgentToolLoopIteration,
|
|
2649
|
+
stopAgentToolLoopTurn,
|
|
2650
|
+
createAgentToolLoopRunner,
|
|
2651
|
+
getLoopRegistry,
|
|
2652
|
+
resetLoopRegistry,
|
|
2653
|
+
getBuiltinLoopProfiles,
|
|
2654
|
+
getBuiltinLoopProfile,
|
|
2655
|
+
registerBuiltinLoopProfiles
|
|
2656
|
+
};
|
|
2657
|
+
//# sourceMappingURL=chunk-QFHAAIIM.js.map
|