progmune-runtime 2.0.4 → 2.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/.mcp.json +11 -0
- package/.progmune_allowlist +50 -0
- package/.test_report/test_report.md +87 -0
- package/Dockerfile +2 -10
- package/FAQ.md +167 -0
- package/demo-project/auth.ts +55 -0
- package/demo-project/tsconfig.json +8 -0
- package/dist/ab-stats.js +11 -0
- package/dist/acl-breakdown.js +13 -0
- package/dist/all-sessions.js +11 -0
- package/dist/antibody-stats.js +11 -0
- package/dist/audit.js +218 -0
- package/dist/benchmark-count.js +7 -0
- package/dist/benchmark-full.js +17 -0
- package/dist/benchmark-pass-rate.js +54 -0
- package/dist/benchmark-report.js +67 -0
- package/dist/benchmark-save.js +62 -0
- package/dist/benchmark-status.js +15 -0
- package/dist/branch-ledger.js +365 -0
- package/dist/branch-tree-count.js +14 -0
- package/dist/check.js +506 -0
- package/dist/common-fixpath.js +12 -0
- package/dist/constraint-types.js +12 -0
- package/dist/deterministic-replay.js +277 -0
- package/dist/emitter.js +112 -12
- package/dist/exec-metrics.js +11 -0
- package/dist/execute.js +242 -0
- package/dist/extract-ir.js +550 -5
- package/dist/failure-collector.js +143 -0
- package/dist/failure-corpus.js +481 -35
- package/dist/failure-report.js +11 -0
- package/dist/failures.js +11 -0
- package/dist/fast-path-hits.js +13 -0
- package/dist/feedback.js +6 -3
- package/dist/file-lock.js +82 -0
- package/dist/find-session.js +10 -0
- package/dist/fingerprint-list.js +15 -0
- package/dist/gen-history-log.js +13 -0
- package/dist/generate.js +2 -1
- package/dist/generate_500.js +4 -2
- package/dist/genome.js +11 -0
- package/dist/heatmap-data.js +11 -0
- package/dist/heatmap.js +11 -0
- package/dist/immune-reporter.js +33 -22
- package/dist/learned.js +11 -0
- package/dist/ledger-registry.js +241 -0
- package/dist/llm.js +43 -2
- package/dist/load-benchmarks.js +47 -0
- package/dist/main.js +2 -1
- package/dist/mcp-server.mjs +445 -43
- package/dist/memory-layer.js +51 -13
- package/dist/metrics.js +11 -0
- package/dist/obs-web.js +561 -0
- package/dist/p0_ssg_demo.js +255 -40
- package/dist/planner.js +905 -80
- package/dist/protocol-registry.js +105 -0
- package/dist/recent-session.js +12 -0
- package/dist/repair-proposal.js +353 -0
- package/dist/runtime-invariants.js +161 -0
- package/dist/runtime-types.js +117 -0
- package/dist/search-planner.js +38 -9
- package/dist/semantic-snapshot.js +155 -0
- package/dist/semantic-trace.js +1497 -0
- package/dist/semantic-validator.js +3 -2
- package/dist/semantic_guard_test.js +2 -1
- package/dist/sessions.js +11 -0
- package/dist/ssg-validator.js +658 -20
- package/dist/svl-distribution.js +11 -0
- package/dist/terminal-status.js +11 -0
- package/dist/test_failure_corpus.js +3 -1
- package/dist/token-savings.js +11 -0
- package/dist/total-repairs.js +12 -0
- package/dist/unresolved-count.js +12 -0
- package/dist/valid-fingerprints.js +13 -0
- package/dist/validator.js +116 -60
- package/dist/verify-fps.js +11 -0
- package/dist/verify-ledgers.js +11 -0
- package/docs/whitepaper-style.css +77 -0
- package/docs/whitepaper-v2.1.md +609 -0
- package/docs/whitepaper-v2.2.md +1064 -0
- package/docs/whitepaper-v2.2.pdf +0 -0
- package/fly.toml +1 -1
- package/package.json +13 -4
- package/protocols.json +131 -11
- package/public/dashboard.html +119 -0
- package/readme.md +829 -0
- package/server/hub.js +84 -12
- package/test/replay-golden/sess_1780063202050_mgeld.json +9 -0
- package/test/replay-golden/sess_1780064032560_gocld.json +354 -0
- package/test/replay-golden/sess_1780064413331_s2709.json +606 -0
- package/test/replay-golden/sess_1780064792710_y3avo.json +614 -0
- package/test/replay-golden.ts +84 -0
- package/test_benchmark.js +165 -0
- package/test_comprehensive.mjs +638 -0
- package/test_concurrency.js +129 -0
- package/test_ir_robustness.js +85 -0
- package/test_semantic_contracts.js +269 -0
- package/test_ssg_stress.js +156 -0
- package/test_svl3.js +58 -0
- package/tsconfig.json +1 -1
- package/.env.example +0 -3
- package/.progmune_memory/episodic.json +0 -186
- package/.progmune_memory/fingerprints.json +0 -7
- package/.progmune_memory/opt_in.json +0 -4
- package/README.md +0 -118
- package/immune_hub_data/2026-05-14.json +0 -50
package/dist/planner.js
CHANGED
|
@@ -35,14 +35,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.plan = plan;
|
|
37
37
|
const llm_1 = require("./llm");
|
|
38
|
+
const runtime_types_1 = require("./runtime-types");
|
|
38
39
|
const action_runtime_1 = require("./action-runtime");
|
|
39
40
|
const validator_1 = require("./validator");
|
|
40
41
|
const semantic_validator_1 = require("./semantic-validator");
|
|
41
|
-
const feedback_1 = require("./feedback");
|
|
42
42
|
const utils_1 = require("./utils");
|
|
43
43
|
const failure_corpus_1 = require("./failure-corpus");
|
|
44
44
|
const memory_layer_1 = require("./memory-layer");
|
|
45
45
|
const ssg_validator_1 = require("./ssg-validator");
|
|
46
|
+
const protocol_registry_1 = require("./protocol-registry");
|
|
47
|
+
const semantic_snapshot_1 = require("./semantic-snapshot");
|
|
46
48
|
const fs = __importStar(require("fs"));
|
|
47
49
|
function enrichActions(actions, ir) {
|
|
48
50
|
return actions.map(a => {
|
|
@@ -88,35 +90,535 @@ function determineConstraintType(svl) {
|
|
|
88
90
|
case "SVL-4": return "protocol";
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
|
-
/**
|
|
93
|
+
/** 构建紧凑函数列表(约节省 50% token) */
|
|
94
|
+
function buildCompactFuncList(funcs) {
|
|
95
|
+
return funcs.map((f) => {
|
|
96
|
+
const params = (f.params || []).map((p) => `${p.name}:${p.type}`).join(",");
|
|
97
|
+
return `${f.name}(${params})->${f.returnType || "any"}`;
|
|
98
|
+
}).join("\n");
|
|
99
|
+
}
|
|
100
|
+
const SYSTEM_PROMPT = `你是程序合成助手。只输出 JSON 数组,不输出解释。
|
|
101
|
+
|
|
102
|
+
格式(紧凑一行):
|
|
103
|
+
[{"f":"函数名","to":"变量名","a":[{"n":"参数名","t":"类型","v":值}]},{"r":"变量名"}]
|
|
104
|
+
|
|
105
|
+
规则:
|
|
106
|
+
- "f": 函数名,"to": 接收返回值的变量名(void 函数省略)
|
|
107
|
+
- "a": 参数数组,每个参数 n=名称 t=类型 v=值
|
|
108
|
+
- 字符串值直接写: "v":"hello",数字: "v":42
|
|
109
|
+
- 引用之前返回的变量: "v":"$变量名"($前缀表示这是变量引用)
|
|
110
|
+
- {"r":"变量名"} 表示返回该变量
|
|
111
|
+
|
|
112
|
+
示例 — 提取 IR 后校验:
|
|
113
|
+
[{"f":"extractIR","to":"ir","a":[{"n":"root","t":"str","v":"."}]},{"f":"validateActionSequence","to":"ok","a":[{"n":"actions","t":"any","v":"$ir"}]},{"r":"ok"}]
|
|
114
|
+
|
|
115
|
+
铁律:只输出 JSON 数组,双引号,不输出解释`;
|
|
116
|
+
const RETRY_HINT = `输出格式:紧凑 JSON 数组 [{"f":"函数名","to":"变量名","a":[...]}]`;
|
|
117
|
+
/** 构建重试 prompt:精简但包含必要的 IR 语法提示 */
|
|
118
|
+
/** 解析 LLM 输出的紧凑 JSON 为 Action[]。
|
|
119
|
+
* 格式: [{"f":"fn","to":"var","a":[{"n":"p","t":"str","v":"x"}]}, {"r":"var"}, ...]
|
|
120
|
+
* f=function(→call), to=assignTo, a=args, r=return, if=condition
|
|
121
|
+
*/
|
|
122
|
+
function parseActionJSON(text) {
|
|
123
|
+
const clean = text.replace(/```(?:json|javascript)?\s*/gi, '').replace(/```\s*/g, '').trim();
|
|
124
|
+
try {
|
|
125
|
+
const arr = JSON.parse(clean);
|
|
126
|
+
if (!Array.isArray(arr))
|
|
127
|
+
return null;
|
|
128
|
+
const actions = [];
|
|
129
|
+
for (const item of arr) {
|
|
130
|
+
if (!item || typeof item !== 'object')
|
|
131
|
+
return null;
|
|
132
|
+
if (item.r !== undefined) {
|
|
133
|
+
actions.push({ kind: "return", value: item.r });
|
|
134
|
+
}
|
|
135
|
+
else if (item.f) {
|
|
136
|
+
const a = {
|
|
137
|
+
kind: "call",
|
|
138
|
+
function: item.f,
|
|
139
|
+
args: (item.a || []).map((p) => ({
|
|
140
|
+
name: p.n || "arg",
|
|
141
|
+
type: p.t || "any",
|
|
142
|
+
value: p.v ?? null,
|
|
143
|
+
})),
|
|
144
|
+
};
|
|
145
|
+
if (item.to)
|
|
146
|
+
a.assignTo = item.to;
|
|
147
|
+
actions.push(a);
|
|
148
|
+
}
|
|
149
|
+
else if (item.if) {
|
|
150
|
+
const a = {
|
|
151
|
+
kind: "if",
|
|
152
|
+
condition: item.if,
|
|
153
|
+
thenActions: item.then ? parseActionJSON(JSON.stringify(item.then)) || [] : [],
|
|
154
|
+
elseActions: item.else ? parseActionJSON(JSON.stringify(item.else)) || [] : [],
|
|
155
|
+
};
|
|
156
|
+
actions.push(a);
|
|
157
|
+
}
|
|
158
|
+
else if (item.kind) {
|
|
159
|
+
// 完整 Action 格式(后向兼容)
|
|
160
|
+
actions.push(item);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return actions.length > 0 ? actions : null;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** 模糊函数名纠正:当 LLM 生成不存在的函数名时,用 Jaccard 相似度找最接近的 IR 函数 */
|
|
173
|
+
function correctFunctionNames(actions, ir) {
|
|
174
|
+
const corrections = [];
|
|
175
|
+
const corrected = actions.map((a, i) => {
|
|
176
|
+
if (a.kind !== "call" || !a.function)
|
|
177
|
+
return a;
|
|
178
|
+
// 跳过已知存在的函数和白名单
|
|
179
|
+
if (ir.some((f) => f.name === a.function))
|
|
180
|
+
return a;
|
|
181
|
+
if (["if", "for", "assign", "return"].includes(a.function))
|
|
182
|
+
return a;
|
|
183
|
+
// 用 Jaccard 相似度找最佳匹配
|
|
184
|
+
let bestMatch = "";
|
|
185
|
+
let bestScore = 0;
|
|
186
|
+
const target = a.function.toLowerCase();
|
|
187
|
+
for (const fn of ir) {
|
|
188
|
+
const score = (0, utils_1.jaccardSimilarity)(target, fn.name.toLowerCase());
|
|
189
|
+
if (score > bestScore) {
|
|
190
|
+
bestScore = score;
|
|
191
|
+
bestMatch = fn.name;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (bestMatch && bestScore >= 0.3) {
|
|
195
|
+
corrections.push(`action[${i}]: "${a.function}" → "${bestMatch}" (相似度 ${bestScore.toFixed(2)})`);
|
|
196
|
+
return { ...a, function: bestMatch };
|
|
197
|
+
}
|
|
198
|
+
return a;
|
|
199
|
+
});
|
|
200
|
+
return { actions: corrected, corrections };
|
|
201
|
+
}
|
|
202
|
+
/** 参数签名预检:确保 call action 的参数数量与 IR 函数签名一致 */
|
|
203
|
+
function fixParameterCounts(actions, ir) {
|
|
204
|
+
const fixes = [];
|
|
205
|
+
const corrected = actions.map((a, i) => {
|
|
206
|
+
if (a.kind !== "call" || !a.function)
|
|
207
|
+
return a;
|
|
208
|
+
const def = ir.find((f) => f.name === a.function);
|
|
209
|
+
if (!def || !def.params)
|
|
210
|
+
return a;
|
|
211
|
+
const expected = def.params.length;
|
|
212
|
+
const actual = a.args ? a.args.length : 0;
|
|
213
|
+
if (actual === expected)
|
|
214
|
+
return a;
|
|
215
|
+
if (actual < expected) {
|
|
216
|
+
// 参数太少:填充缺失参数
|
|
217
|
+
const padded = [...(a.args || [])];
|
|
218
|
+
for (let j = actual; j < expected; j++) {
|
|
219
|
+
padded.push({ name: def.params[j].name, type: def.params[j].type || "any", value: "" });
|
|
220
|
+
}
|
|
221
|
+
fixes.push(`action[${i}] ${a.function}: 参数 ${actual}→${expected} (填充 ${expected - actual} 个缺失参数)`);
|
|
222
|
+
return { ...a, args: padded };
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
// 参数太多:截断多余参数
|
|
226
|
+
fixes.push(`action[${i}] ${a.function}: 参数 ${actual}→${expected} (截断 ${actual - expected} 个多余参数)`);
|
|
227
|
+
return { ...a, args: a.args.slice(0, expected) };
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
return { actions: corrected, fixes };
|
|
231
|
+
}
|
|
232
|
+
/** 构建协议链提示:为 LLM 显示协议状态机的合法调用顺序 */
|
|
233
|
+
function buildProtocolChainHint(protocols) {
|
|
234
|
+
if (protocols.length === 0)
|
|
235
|
+
return "";
|
|
236
|
+
// 按命名空间分组
|
|
237
|
+
const byNs = new Map();
|
|
238
|
+
for (const p of protocols) {
|
|
239
|
+
const ns = p.protocol.namespace || "_global";
|
|
240
|
+
if (!byNs.has(ns))
|
|
241
|
+
byNs.set(ns, []);
|
|
242
|
+
byNs.get(ns).push(p);
|
|
243
|
+
}
|
|
244
|
+
const lines = ["\n⚠️ 协议约束(必须严格遵循调用顺序):"];
|
|
245
|
+
for (const [ns, fns] of byNs) {
|
|
246
|
+
if (ns === "_global" || fns.length <= 1)
|
|
247
|
+
continue;
|
|
248
|
+
lines.push(` [${ns}] 合法调用链: ${fns.map(p => p.function).join(" → ")}`);
|
|
249
|
+
for (const p of fns) {
|
|
250
|
+
const pre = p.protocol.pre_states?.join(",") || "(无)";
|
|
251
|
+
const post = p.protocol.post_states?.join(",") || "(无)";
|
|
252
|
+
lines.push(` ${p.function}: 前置状态=[${pre}] → 后置状态=[${post}]`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return lines.join("\n");
|
|
256
|
+
}
|
|
257
|
+
/** JSON schema pre-validation: structural checks before IR-aware validation.
|
|
258
|
+
* Catches malformed actions that parseActionJSON() accepted but are semantically invalid. */
|
|
259
|
+
function validateActionSchema(actions) {
|
|
260
|
+
const errors = [];
|
|
261
|
+
const assignedVars = new Set();
|
|
262
|
+
const validVarName = /^[a-zA-Z_]\w*$/;
|
|
263
|
+
for (let i = 0; i < actions.length; i++) {
|
|
264
|
+
const a = actions[i];
|
|
265
|
+
const pos = `action[${i}]`;
|
|
266
|
+
if (!a.kind) {
|
|
267
|
+
errors.push(`${pos}: missing "kind" field`);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
switch (a.kind) {
|
|
271
|
+
case "call": {
|
|
272
|
+
if (!a.function || typeof a.function !== "string") {
|
|
273
|
+
errors.push(`${pos}: call action requires "function" (string)`);
|
|
274
|
+
}
|
|
275
|
+
if (!Array.isArray(a.args)) {
|
|
276
|
+
errors.push(`${pos}: call action requires "args" (array)`);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
for (let j = 0; j < a.args.length; j++) {
|
|
280
|
+
const arg = a.args[j];
|
|
281
|
+
if (!arg || typeof arg !== "object") {
|
|
282
|
+
errors.push(`${pos}: args[${j}] must be an object`);
|
|
283
|
+
}
|
|
284
|
+
else if (arg.name === undefined && arg.type === undefined && arg.value === undefined) {
|
|
285
|
+
errors.push(`${pos}: args[${j}] missing name/type/value`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (a.assignTo !== undefined) {
|
|
290
|
+
if (typeof a.assignTo !== "string" || !validVarName.test(a.assignTo)) {
|
|
291
|
+
errors.push(`${pos}: assignTo "${a.assignTo}" is not a valid variable name`);
|
|
292
|
+
}
|
|
293
|
+
else if (assignedVars.has(a.assignTo)) {
|
|
294
|
+
errors.push(`${pos}: duplicate assignTo "${a.assignTo}" (previously assigned at another action)`);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
assignedVars.add(a.assignTo);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case "return": {
|
|
303
|
+
if (a.value === undefined) {
|
|
304
|
+
errors.push(`${pos}: return action requires "value"`);
|
|
305
|
+
}
|
|
306
|
+
if (i < actions.length - 1) {
|
|
307
|
+
errors.push(`${pos}: return should be the last action (actions after return are unreachable)`);
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
case "assign": {
|
|
312
|
+
if (!a.target || typeof a.target !== "string" || !validVarName.test(a.target)) {
|
|
313
|
+
errors.push(`${pos}: assign requires valid "target" variable name`);
|
|
314
|
+
}
|
|
315
|
+
else if (assignedVars.has(a.target)) {
|
|
316
|
+
errors.push(`${pos}: duplicate assign target "${a.target}"`);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
assignedVars.add(a.target);
|
|
320
|
+
}
|
|
321
|
+
if (a.value === undefined) {
|
|
322
|
+
errors.push(`${pos}: assign action requires "value"`);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case "if": {
|
|
327
|
+
if (!a.condition || typeof a.condition !== "string") {
|
|
328
|
+
errors.push(`${pos}: if action requires "condition" (string)`);
|
|
329
|
+
}
|
|
330
|
+
if (!Array.isArray(a.thenActions)) {
|
|
331
|
+
errors.push(`${pos}: if action requires "thenActions" (array)`);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
const thenCheck = validateActionSchema(a.thenActions);
|
|
335
|
+
errors.push(...thenCheck.errors.map(e => `${pos}.thenActions: ${e}`));
|
|
336
|
+
}
|
|
337
|
+
if (a.elseActions && !Array.isArray(a.elseActions)) {
|
|
338
|
+
errors.push(`${pos}: elseActions must be an array if present`);
|
|
339
|
+
}
|
|
340
|
+
else if (a.elseActions) {
|
|
341
|
+
const elseCheck = validateActionSchema(a.elseActions);
|
|
342
|
+
errors.push(...elseCheck.errors.map(e => `${pos}.elseActions: ${e}`));
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
case "for": {
|
|
347
|
+
if (!a.variable || typeof a.variable !== "string" || !validVarName.test(a.variable)) {
|
|
348
|
+
errors.push(`${pos}: for action requires valid "variable" name`);
|
|
349
|
+
}
|
|
350
|
+
if (!a.iterable || typeof a.iterable !== "string") {
|
|
351
|
+
errors.push(`${pos}: for action requires "iterable" (string)`);
|
|
352
|
+
}
|
|
353
|
+
if (!Array.isArray(a.bodyActions)) {
|
|
354
|
+
errors.push(`${pos}: for action requires "bodyActions" (array)`);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
const bodyCheck = validateActionSchema(a.bodyActions);
|
|
358
|
+
errors.push(...bodyCheck.errors.map(e => `${pos}.bodyActions: ${e}`));
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
default: {
|
|
363
|
+
errors.push(`${pos}: unknown action kind "${a.kind}"`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return { valid: errors.length === 0, errors };
|
|
368
|
+
}
|
|
369
|
+
/** 构建重试 prompt:精简但包含必要的 IR 语法提示 */
|
|
370
|
+
/** 加载 IR 中所有带 protocol 的函数为协议规则,同时从 protocols.json 加载命名空间初始状态 */
|
|
92
371
|
function loadProtocols(ir) {
|
|
93
|
-
|
|
372
|
+
const irProtocols = ir
|
|
94
373
|
.filter((f) => f.protocol)
|
|
95
374
|
.map((f) => ({ function: f.name, protocol: f.protocol }));
|
|
375
|
+
// Phase 6C: use ProtocolRegistry for nsInit (single source of truth)
|
|
376
|
+
const namespaceInitialStates = (0, protocol_registry_1.getNsInit)();
|
|
377
|
+
// Parse protocol rules from JSON (rules themselves still need the JSON for definitions)
|
|
378
|
+
let jsonProtocols = [];
|
|
379
|
+
try {
|
|
380
|
+
const protoDef = JSON.parse(fs.readFileSync("protocols.json", "utf-8"));
|
|
381
|
+
jsonProtocols = (0, ssg_validator_1.parseProtocolsFromJSON)(protoDef);
|
|
382
|
+
}
|
|
383
|
+
catch { }
|
|
384
|
+
// Merge: IR @protocol takes priority, but inherits JSON namespace
|
|
385
|
+
const merged = new Map();
|
|
386
|
+
for (const p of jsonProtocols)
|
|
387
|
+
merged.set(p.function, p);
|
|
388
|
+
for (const p of irProtocols) {
|
|
389
|
+
const existing = merged.get(p.function);
|
|
390
|
+
if (existing && existing.protocol.namespace && !p.protocol.namespace) {
|
|
391
|
+
p.protocol.namespace = existing.protocol.namespace;
|
|
392
|
+
}
|
|
393
|
+
merged.set(p.function, p);
|
|
394
|
+
}
|
|
395
|
+
return { protocols: [...merged.values()], namespaceInitialStates };
|
|
96
396
|
}
|
|
97
|
-
/**
|
|
98
|
-
function
|
|
99
|
-
const
|
|
397
|
+
/** 验证动作序列的协议合法性,使用 Semantic Ledger (Phase 3) 纯函数 */
|
|
398
|
+
function validateProtocolWithTransitions(actions, protocols, namespaceInitialStates) {
|
|
399
|
+
const rules = new Map();
|
|
400
|
+
for (const p of protocols)
|
|
401
|
+
rules.set(p.function, p.protocol);
|
|
402
|
+
const ruleHash = (0, ssg_validator_1.hashRules)(rules);
|
|
403
|
+
const ctx = {
|
|
404
|
+
ledger: [],
|
|
405
|
+
currentState: (0, ssg_validator_1.rebuildState)([], namespaceInitialStates),
|
|
406
|
+
};
|
|
407
|
+
const transitions = [];
|
|
100
408
|
for (let i = 0; i < actions.length; i++) {
|
|
101
409
|
const a = actions[i];
|
|
102
410
|
if (a.kind === "call" && a.function) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
411
|
+
const { valid, transition, rejection } = (0, ssg_validator_1.validateTransition)(ctx, a.function, i, rules, namespaceInitialStates, ruleHash);
|
|
412
|
+
transitions.push(transition);
|
|
413
|
+
if (!valid) {
|
|
414
|
+
const trace = transitions.map(t => ({
|
|
415
|
+
function: t.function,
|
|
416
|
+
statesBefore: t.statesBefore,
|
|
417
|
+
statesAfter: t.statesAfter,
|
|
418
|
+
}));
|
|
419
|
+
return { valid: false, rejection: rejection, index: i, trace, transitions, ruleHash };
|
|
106
420
|
}
|
|
421
|
+
// Incremental update — O(1) per step
|
|
422
|
+
ctx.ledger = transitions;
|
|
423
|
+
ctx.currentState = transition.statesAfter;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// Invariant check on full ledger
|
|
427
|
+
const consistency = (0, ssg_validator_1.checkLedgerConsistency)(transitions, namespaceInitialStates);
|
|
428
|
+
if (!consistency.consistent) {
|
|
429
|
+
console.error(`[Invariant] Ledger consistency violations: ${consistency.violations.length}`);
|
|
430
|
+
for (const v of consistency.violations) {
|
|
431
|
+
console.error(` [${v.invariant}] index=${v.index}: ${v.detail}`);
|
|
107
432
|
}
|
|
108
433
|
}
|
|
109
|
-
return { valid: true };
|
|
434
|
+
return { valid: true, transitions, ledgerConsistent: consistency.consistent, ruleHash };
|
|
435
|
+
}
|
|
436
|
+
/** SSG 确定性修复:当协议违规有已知修复路径时,自动插入缺失函数 */
|
|
437
|
+
function attemptSSGRepair(actions, rejection, ir, protocols, namespaceInitialStates) {
|
|
438
|
+
if (!rejection.fixPath || rejection.fixPath.length === 0)
|
|
439
|
+
return null;
|
|
440
|
+
// 找到被拦截函数在序列中的位置
|
|
441
|
+
const blockedIdx = actions.findIndex(a => a.kind === "call" && a.function === rejection.blocked);
|
|
442
|
+
if (blockedIdx === -1)
|
|
443
|
+
return null;
|
|
444
|
+
// 为修复路径中的每个函数创建合成 Action
|
|
445
|
+
const repairActions = [];
|
|
446
|
+
for (const fnName of rejection.fixPath) {
|
|
447
|
+
const def = ir.find((f) => f.name === fnName);
|
|
448
|
+
if (!def)
|
|
449
|
+
return null;
|
|
450
|
+
const args = (def.params || []).map((p, i) => ({
|
|
451
|
+
name: p.name || `p${i}`,
|
|
452
|
+
type: p.type || 'any',
|
|
453
|
+
value: "",
|
|
454
|
+
}));
|
|
455
|
+
const assignTo = def.returnType && def.returnType !== 'void' && def.returnType !== 'undefined'
|
|
456
|
+
? `${fnName}_result` : undefined;
|
|
457
|
+
const action = { kind: 'call', function: fnName, args };
|
|
458
|
+
if (assignTo)
|
|
459
|
+
action.assignTo = assignTo;
|
|
460
|
+
repairActions.push(action);
|
|
461
|
+
}
|
|
462
|
+
// 在被拦截函数前插入修复函数
|
|
463
|
+
const repaired = [
|
|
464
|
+
...actions.slice(0, blockedIdx),
|
|
465
|
+
...repairActions,
|
|
466
|
+
...actions.slice(blockedIdx),
|
|
467
|
+
];
|
|
468
|
+
// 重新验证
|
|
469
|
+
const recheck = validateProtocolWithTransitions(repaired, protocols, namespaceInitialStates);
|
|
470
|
+
if (recheck.valid) {
|
|
471
|
+
console.error(`🔧 SSG 确定性修复: 自动插入 ${rejection.fixPath.join(' → ')} 以解决协议违规`);
|
|
472
|
+
return repaired;
|
|
473
|
+
}
|
|
474
|
+
// 单步修复不够,尝试递归修复
|
|
475
|
+
if (recheck.rejection && recheck.rejection.fixPath && recheck.rejection.fixPath.length > 0) {
|
|
476
|
+
const nested = attemptSSGRepair(repaired, recheck.rejection, ir, protocols, namespaceInitialStates);
|
|
477
|
+
if (nested)
|
|
478
|
+
return nested;
|
|
479
|
+
}
|
|
480
|
+
return null;
|
|
110
481
|
}
|
|
111
482
|
async function plan(userIntent) {
|
|
112
483
|
(0, llm_1.resetCallCount)();
|
|
113
|
-
const
|
|
484
|
+
const irRaw = JSON.parse(fs.readFileSync("ir.json", "utf-8"));
|
|
485
|
+
// Support both old (array) and new ({typeMap, functions}) formats
|
|
486
|
+
const ir = Array.isArray(irRaw) ? irRaw : (irRaw.functions || []);
|
|
487
|
+
// Helper: wrap actions into PlanResult
|
|
488
|
+
let repairMetrics = { applied: false, count: 0, branchIds: [] };
|
|
489
|
+
const wrapResult = (actions, repair) => ({
|
|
490
|
+
actions,
|
|
491
|
+
sessionId: session?.sessionId || "",
|
|
492
|
+
ruleHash: session?.ruleHash,
|
|
493
|
+
repairApplied: repair?.applied ?? repairMetrics.applied,
|
|
494
|
+
repairCount: repair?.count ?? repairMetrics.count,
|
|
495
|
+
repairBranchIds: repair?.branchIds ?? repairMetrics.branchIds,
|
|
496
|
+
});
|
|
497
|
+
// 初始化执行会话和快照(需在抗体快速通道前创建,以便记录 antibody hits)
|
|
498
|
+
const sessionId = (0, runtime_types_1.generateSessionId)();
|
|
499
|
+
const session = {
|
|
500
|
+
sessionId,
|
|
501
|
+
intent: userIntent,
|
|
502
|
+
attempts: [],
|
|
503
|
+
resolved: false,
|
|
504
|
+
startedAt: Date.now(),
|
|
505
|
+
};
|
|
506
|
+
const snapshot = (0, semantic_snapshot_1.createSnapshot)(ir, userIntent);
|
|
507
|
+
const snapshotId = (0, semantic_snapshot_1.saveSnapshot)(snapshot);
|
|
114
508
|
// 语义模板快速通道
|
|
115
509
|
const cachedTemplate = (0, memory_layer_1.findSemanticTemplate)(userIntent);
|
|
116
510
|
if (cachedTemplate && cachedTemplate.successRate >= 0.8 && cachedTemplate.useCount >= 2) {
|
|
117
|
-
console.
|
|
511
|
+
console.error("⚡ 命中语义模板,直接复用已验证序列");
|
|
118
512
|
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: cachedTemplate.actionSequence, success: true });
|
|
119
|
-
return cachedTemplate.actionSequence;
|
|
513
|
+
return wrapResult(cachedTemplate.actionSequence);
|
|
514
|
+
}
|
|
515
|
+
// 提前加载协议(后续多处使用)
|
|
516
|
+
const { protocols, namespaceInitialStates } = loadProtocols(ir);
|
|
517
|
+
// 抗体免疫快速通道:查询高置信度抗体(ACL-3+),匹配则约束或跳过 LLM
|
|
518
|
+
const antibodies = (0, failure_corpus_1.queryAntibodies)(userIntent, "ACL-3");
|
|
519
|
+
let antibodyHint = "";
|
|
520
|
+
if (antibodies.length > 0) {
|
|
521
|
+
const top = antibodies[0];
|
|
522
|
+
const aclLabel = top.antibodyLevel;
|
|
523
|
+
console.error(`🛡️ 命中抗体: ${aclLabel} | 模式: ${top.signature} | 相似度: ${top._score.toFixed(2)}`);
|
|
524
|
+
console.error(` 修复路径: ${top.fixPath.join(" → ")}`);
|
|
525
|
+
// ACL-4: 全局稳定抗体 → 直接构建动作序列,跳过 LLM
|
|
526
|
+
if (aclLabel === "ACL-4" && top.fixPath.length > 0) {
|
|
527
|
+
const antibodyActions = top.fixPath.map((fnName) => {
|
|
528
|
+
const def = ir.find((f) => f.name === fnName);
|
|
529
|
+
const args = (def?.params || []).map((p, i) => ({
|
|
530
|
+
name: p.name || `p${i}`,
|
|
531
|
+
type: p.type || 'any',
|
|
532
|
+
value: "",
|
|
533
|
+
}));
|
|
534
|
+
const action = { kind: 'call', function: fnName, args };
|
|
535
|
+
if (def?.returnType && def.returnType !== 'void' && def.returnType !== 'undefined') {
|
|
536
|
+
action.assignTo = `${fnName}_result`;
|
|
537
|
+
}
|
|
538
|
+
return action;
|
|
539
|
+
});
|
|
540
|
+
const antibodyHit = {
|
|
541
|
+
level: aclLabel,
|
|
542
|
+
signature: top.signature,
|
|
543
|
+
fixPath: top.fixPath,
|
|
544
|
+
similarityScore: top._score,
|
|
545
|
+
action: "fast_path",
|
|
546
|
+
llmCallsSaved: 1,
|
|
547
|
+
estimatedTokensSaved: Math.ceil((0, llm_1.estimateTokens)(SYSTEM_PROMPT + userIntent) * 1.2),
|
|
548
|
+
};
|
|
549
|
+
// 验证抗体序列
|
|
550
|
+
const antibodyRuleHash = (() => {
|
|
551
|
+
const rules = new Map();
|
|
552
|
+
for (const p of protocols)
|
|
553
|
+
rules.set(p.function, p.protocol);
|
|
554
|
+
return (0, ssg_validator_1.hashRules)(rules);
|
|
555
|
+
})();
|
|
556
|
+
if (protocols.length > 0) {
|
|
557
|
+
const validation = validateProtocolWithTransitions(antibodyActions, protocols, namespaceInitialStates);
|
|
558
|
+
if (validation.valid) {
|
|
559
|
+
console.error(`⚡ ACL-4 抗体快速通道: 0 LLM 调用,节省 ~${Math.ceil((0, llm_1.estimateTokens)(SYSTEM_PROMPT + userIntent) * 1.2)} tokens (est.)`);
|
|
560
|
+
const antibodyAttempt = {
|
|
561
|
+
id: (0, runtime_types_1.generateAttemptId)(),
|
|
562
|
+
sessionId: session.sessionId,
|
|
563
|
+
attemptNumber: 1,
|
|
564
|
+
inputIntent: userIntent,
|
|
565
|
+
plannerSeed: (0, runtime_types_1.generatePlannerSeed)("antibody-acl4", "immune"),
|
|
566
|
+
constraintSnapshotId: snapshotId,
|
|
567
|
+
generatedActions: antibodyActions,
|
|
568
|
+
transitions: validation.transitions,
|
|
569
|
+
violations: [],
|
|
570
|
+
outcome: "success",
|
|
571
|
+
timestamp: Date.now(),
|
|
572
|
+
llmCallCount: 0,
|
|
573
|
+
durationMs: 0,
|
|
574
|
+
antibodyHit,
|
|
575
|
+
ruleHash: validation.ruleHash,
|
|
576
|
+
};
|
|
577
|
+
session.attempts.push(antibodyAttempt);
|
|
578
|
+
session.successfulAttempt = antibodyAttempt;
|
|
579
|
+
session.ruleHash = validation.ruleHash;
|
|
580
|
+
session.resolved = true;
|
|
581
|
+
session.snapshotId = snapshotId;
|
|
582
|
+
session.endedAt = Date.now();
|
|
583
|
+
(0, failure_corpus_1.recordSession)(session);
|
|
584
|
+
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: antibodyActions, success: true });
|
|
585
|
+
return wrapResult(antibodyActions);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
// 无协议规则,直接信任抗体
|
|
590
|
+
console.error(`⚡ ACL-4 抗体快速通道: 0 LLM 调用(无协议约束),节省 ~${Math.ceil((0, llm_1.estimateTokens)(SYSTEM_PROMPT + userIntent) * 1.2)} tokens (est.)`);
|
|
591
|
+
const antibodyAttempt = {
|
|
592
|
+
id: (0, runtime_types_1.generateAttemptId)(),
|
|
593
|
+
sessionId: session.sessionId,
|
|
594
|
+
attemptNumber: 1,
|
|
595
|
+
inputIntent: userIntent,
|
|
596
|
+
plannerSeed: (0, runtime_types_1.generatePlannerSeed)("antibody-acl4", "immune"),
|
|
597
|
+
constraintSnapshotId: snapshotId,
|
|
598
|
+
generatedActions: antibodyActions,
|
|
599
|
+
transitions: [],
|
|
600
|
+
violations: [],
|
|
601
|
+
outcome: "success",
|
|
602
|
+
timestamp: Date.now(),
|
|
603
|
+
llmCallCount: 0,
|
|
604
|
+
durationMs: 0,
|
|
605
|
+
antibodyHit,
|
|
606
|
+
ruleHash: antibodyRuleHash,
|
|
607
|
+
};
|
|
608
|
+
session.attempts.push(antibodyAttempt);
|
|
609
|
+
session.successfulAttempt = antibodyAttempt;
|
|
610
|
+
session.ruleHash = antibodyRuleHash;
|
|
611
|
+
session.resolved = true;
|
|
612
|
+
session.snapshotId = snapshotId;
|
|
613
|
+
session.endedAt = Date.now();
|
|
614
|
+
(0, failure_corpus_1.recordSession)(session);
|
|
615
|
+
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: antibodyActions, success: true });
|
|
616
|
+
return wrapResult(antibodyActions);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
// ACL-3: 注入修复路径作为提示约束
|
|
620
|
+
antibodyHint = `\n已知正确调用顺序: ${top.fixPath.join(" → ")}。请遵循此顺序。`;
|
|
621
|
+
console.error(`💉 ACL-3 抗体注入提示: ${top.fixPath.join(" → ")}`);
|
|
120
622
|
}
|
|
121
623
|
const keywords = (0, utils_1.extractKeywords)(userIntent);
|
|
122
624
|
const scored = ir.map((f) => {
|
|
@@ -130,118 +632,333 @@ async function plan(userIntent) {
|
|
|
130
632
|
});
|
|
131
633
|
scored.sort((a, b) => b.score - a.score);
|
|
132
634
|
const topFuncs = scored.slice(0, 15);
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
const star = rate > 0.8 ? "⭐" : rate > 0.5 ? "👍" : "⚠️";
|
|
136
|
-
const params = f.params.map((p) => `${p.name}: ${p.type}`).join(", ");
|
|
137
|
-
return `${star} ${f.name}(${params}) [${f.params.length}个参数] -> ${f.returnType} (成功率: ${(rate * 100).toFixed(0)}%)`;
|
|
138
|
-
}).join("\n");
|
|
139
|
-
const matchFunc = userIntent.match(/(?:实现|implement|编写|创建)\s*(\w+)\s*(?:函数|function)?/i);
|
|
635
|
+
const compactFuncList = buildCompactFuncList(topFuncs);
|
|
636
|
+
const userIntentPart = userIntent.match(/(?:实现|implement|编写|创建)\s*(\w+)\s*(?:函数|function)?/i);
|
|
140
637
|
const forbiddenFuncs = [];
|
|
141
|
-
if (
|
|
142
|
-
const targetName =
|
|
638
|
+
if (userIntentPart) {
|
|
639
|
+
const targetName = userIntentPart[1];
|
|
143
640
|
if (ir.find((f) => f.name.toLowerCase() === targetName.toLowerCase())) {
|
|
144
641
|
forbiddenFuncs.push(targetName);
|
|
145
642
|
}
|
|
146
643
|
}
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
output("cached_data")
|
|
151
|
-
}, () => {
|
|
152
|
-
callAssign("query_data", "fresh_data", "query_key")
|
|
153
|
-
call("cache_set", "query_key", "fresh_data")
|
|
154
|
-
output("fresh_data")
|
|
155
|
-
})`;
|
|
156
|
-
const basePrompt = `你能使用的函数:
|
|
157
|
-
${funcList}
|
|
158
|
-
|
|
159
|
-
绝对禁止调用列表外函数。
|
|
160
|
-
|
|
161
|
-
示例(缓存查询,注意 assign 先于条件):
|
|
162
|
-
${exampleCode}
|
|
163
|
-
|
|
164
|
-
全局函数及用法规则:
|
|
165
|
-
- 声明变量:assign("变量名", "值") 或 callAssign("函数", "变量名", ...)
|
|
166
|
-
- 条件分支:ifElse("变量名", () => { ... }, () => { ... })
|
|
167
|
-
- 简单分支:ifBlock("变量名", () => { ... })
|
|
168
|
-
- 调用:call("函数", "arg1", ...)
|
|
169
|
-
- 返回:output("值或变量名")
|
|
644
|
+
const protocolChainHint = buildProtocolChainHint(protocols);
|
|
645
|
+
const userPrompt = `可用函数:
|
|
646
|
+
${compactFuncList}${protocolChainHint}
|
|
170
647
|
|
|
171
|
-
|
|
172
|
-
1. 必须先 assign 或 callAssign 再使用变量。
|
|
173
|
-
2. 参数数量必须与函数声明一致。
|
|
174
|
-
3. 条件括号内只能是已声明的变量名。
|
|
648
|
+
需求:${userIntent}${antibodyHint}
|
|
175
649
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
650
|
+
${RETRY_HINT}
|
|
651
|
+
只输出 JSON。`;
|
|
652
|
+
const estimatedTokens = (0, llm_1.estimateTokens)(SYSTEM_PROMPT + userPrompt);
|
|
653
|
+
console.error(`💰 估算 prompt token: ${estimatedTokens}`);
|
|
654
|
+
// ── 执行持久化:检查是否有未完成的 checkpoint ──
|
|
655
|
+
const cp = (0, failure_corpus_1.loadCheckpoint)(userIntent);
|
|
656
|
+
let startRetry = 0;
|
|
180
657
|
let finalActions = [];
|
|
181
|
-
let currentPrompt =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
658
|
+
let currentPrompt = userPrompt;
|
|
659
|
+
let useSystem = true;
|
|
660
|
+
if (cp) {
|
|
661
|
+
console.error(`📌 恢复 checkpoint: 已完成 ${cp.attemptIndex} 次尝试,从第 ${cp.attemptIndex + 1} 次继续`);
|
|
662
|
+
startRetry = cp.attemptIndex;
|
|
663
|
+
currentPrompt = cp.currentPrompt;
|
|
664
|
+
useSystem = cp.useSystem;
|
|
665
|
+
// 从 checkpoint 恢复已有的 session.attempts
|
|
666
|
+
if (cp.sessionAttempts) {
|
|
667
|
+
session.attempts = cp.sessionAttempts;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
const sessionRuleHash = (() => {
|
|
671
|
+
const rules = new Map();
|
|
672
|
+
for (const p of protocols)
|
|
673
|
+
rules.set(p.function, p.protocol);
|
|
674
|
+
return (0, ssg_validator_1.hashRules)(rules);
|
|
675
|
+
})();
|
|
676
|
+
session.ruleHash = sessionRuleHash;
|
|
677
|
+
function getMaskedFuncList() {
|
|
678
|
+
if (protocols.length === 0)
|
|
679
|
+
return compactFuncList;
|
|
680
|
+
const rules = new Map();
|
|
681
|
+
for (const p of protocols)
|
|
682
|
+
rules.set(p.function, p.protocol);
|
|
683
|
+
const ctx = { ledger: [], currentState: (0, ssg_validator_1.rebuildState)([], namespaceInitialStates) };
|
|
684
|
+
const legalFuncs = topFuncs.filter((f) => {
|
|
685
|
+
const proto = protocols.find((p) => p.function === f.name);
|
|
686
|
+
if (!proto)
|
|
687
|
+
return true;
|
|
688
|
+
const { valid } = (0, ssg_validator_1.validateTransition)(ctx, f.name, 0, rules, namespaceInitialStates);
|
|
689
|
+
return valid;
|
|
690
|
+
});
|
|
691
|
+
if (legalFuncs.length === topFuncs.length)
|
|
692
|
+
return compactFuncList;
|
|
693
|
+
return buildCompactFuncList(legalFuncs);
|
|
694
|
+
}
|
|
695
|
+
const maxRetries = 3;
|
|
696
|
+
for (let r = startRetry; r < maxRetries; r++) {
|
|
185
697
|
let text;
|
|
186
698
|
try {
|
|
187
|
-
text =
|
|
699
|
+
text = useSystem
|
|
700
|
+
? await (0, llm_1.chat)(SYSTEM_PROMPT, currentPrompt)
|
|
701
|
+
: await (0, llm_1.generate)(`你是程序合成助手。\n\n${currentPrompt}`);
|
|
188
702
|
}
|
|
189
703
|
catch (e) {
|
|
190
704
|
continue;
|
|
191
705
|
}
|
|
192
706
|
if (!text)
|
|
193
707
|
continue;
|
|
194
|
-
text = text.replace(/```javascript
|
|
195
|
-
console.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
708
|
+
text = text.replace(/```(?:json|javascript)?\s*/gi, '').replace(/```\s*/g, '').trim();
|
|
709
|
+
console.error("📝 LLM 输出:\n", text);
|
|
710
|
+
// 优先尝试 JSON 解析,失败则回退到 DSL 执行
|
|
711
|
+
let rawActions = parseActionJSON(text);
|
|
712
|
+
if (!rawActions || !Array.isArray(rawActions)) {
|
|
713
|
+
console.error("⚠️ JSON 解析失败,尝试 DSL 回退...");
|
|
714
|
+
rawActions = (0, action_runtime_1.executeActionCode)(text);
|
|
715
|
+
}
|
|
716
|
+
if (!rawActions || !Array.isArray(rawActions) || rawActions.length === 0) {
|
|
717
|
+
console.error("⚠️ 解析失败,重试...");
|
|
718
|
+
currentPrompt = `可用函数:\n${compactFuncList}${protocolChainHint}\n\n需求:${userIntent}\n\n上一次输出无效。请严格输出 JSON 数组。\n${RETRY_HINT}\n只输出 JSON。`;
|
|
719
|
+
useSystem = false;
|
|
200
720
|
continue;
|
|
201
721
|
}
|
|
722
|
+
// 🔧 SVL-1 修复: 模糊函数名纠正 — 把 LLM 编造的函数名映射到真实 IR 函数
|
|
723
|
+
const nameCorrection = correctFunctionNames(rawActions, ir);
|
|
724
|
+
if (nameCorrection.corrections.length > 0) {
|
|
725
|
+
console.error("🔧 [SVL-1 自动修复] 函数名纠正:");
|
|
726
|
+
for (const c of nameCorrection.corrections)
|
|
727
|
+
console.error(` ${c}`);
|
|
728
|
+
rawActions = nameCorrection.actions;
|
|
729
|
+
}
|
|
730
|
+
// 🔧 SVL-2 修复: 参数签名预检 — 自动调整 args 数量匹配 IR 签名
|
|
731
|
+
const paramFix = fixParameterCounts(rawActions, ir);
|
|
732
|
+
if (paramFix.fixes.length > 0) {
|
|
733
|
+
console.error("🔧 [SVL-2 自动修复] 参数数量修正:");
|
|
734
|
+
for (const f of paramFix.fixes)
|
|
735
|
+
console.error(` ${f}`);
|
|
736
|
+
rawActions = paramFix.actions;
|
|
737
|
+
}
|
|
738
|
+
// P2: JSON schema pre-validation — catch structural errors early
|
|
739
|
+
const schemaCheck = validateActionSchema(rawActions);
|
|
740
|
+
if (!schemaCheck.valid) {
|
|
741
|
+
console.error("⚠️ JSON schema 校验失败:", schemaCheck.errors.join("; "));
|
|
742
|
+
currentPrompt = `可用函数:\n${compactFuncList}${protocolChainHint}\n\n需求:${userIntent}\n\n输出格式错误:${schemaCheck.errors.join(";")}。请修正 JSON 结构。\n${RETRY_HINT}\n只输出 JSON。`;
|
|
743
|
+
useSystem = false;
|
|
744
|
+
const schemaViolation = {
|
|
745
|
+
svl: 1,
|
|
746
|
+
violatedConstraint: "schema",
|
|
747
|
+
actionIndex: 0,
|
|
748
|
+
description: schemaCheck.errors.join("; "),
|
|
749
|
+
};
|
|
750
|
+
const schemaAttempt = {
|
|
751
|
+
id: (0, runtime_types_1.generateAttemptId)(),
|
|
752
|
+
sessionId: session.sessionId,
|
|
753
|
+
attemptNumber: r + 1,
|
|
754
|
+
inputIntent: userIntent,
|
|
755
|
+
plannerSeed: (0, runtime_types_1.generatePlannerSeed)(currentPrompt, process.env.LLM_MODEL || "deepseek-chat"),
|
|
756
|
+
constraintSnapshotId: snapshotId,
|
|
757
|
+
generatedActions: rawActions,
|
|
758
|
+
transitions: [],
|
|
759
|
+
violations: [schemaViolation],
|
|
760
|
+
outcome: "constraint_violation",
|
|
761
|
+
timestamp: Date.now(),
|
|
762
|
+
llmCallCount: 0,
|
|
763
|
+
durationMs: 0,
|
|
764
|
+
ruleHash: sessionRuleHash,
|
|
765
|
+
};
|
|
766
|
+
session.attempts.push(schemaAttempt);
|
|
767
|
+
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: rawActions, success: false, svlViolated: "SVL-1" });
|
|
768
|
+
(0, failure_corpus_1.saveCheckpoint)(userIntent, { attemptIndex: r + 1, sessionAttempts: session.attempts, currentPrompt, useSystem });
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
// 解析 $变量名 引用为实际变量名
|
|
772
|
+
rawActions = rawActions.map(a => {
|
|
773
|
+
if (a.kind === "call" && a.args) {
|
|
774
|
+
a.args = a.args.map(arg => {
|
|
775
|
+
if (typeof arg.value === 'string' && arg.value.startsWith('$')) {
|
|
776
|
+
return { ...arg, value: arg.value.slice(1) };
|
|
777
|
+
}
|
|
778
|
+
return arg;
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
return a;
|
|
782
|
+
});
|
|
202
783
|
const enriched = enrichActions(rawActions, ir);
|
|
203
|
-
const filtered = enriched.filter(a => !forbiddenFuncs.includes(a.function
|
|
784
|
+
const filtered = enriched.filter(a => !forbiddenFuncs.includes(a.kind === "call" ? a.function : ''));
|
|
785
|
+
let ssgTransitions = [];
|
|
204
786
|
// 1) 基础序列校验
|
|
205
787
|
const seqResult = (0, validator_1.validateActionSequence)(filtered);
|
|
206
788
|
if (!seqResult.valid) {
|
|
207
789
|
const errorsFlat = seqResult.errors.flat();
|
|
208
|
-
console.
|
|
209
|
-
|
|
790
|
+
console.error("⚠️ 序列校验失败:", errorsFlat.join(", "));
|
|
791
|
+
// Use structured violations directly from validator
|
|
792
|
+
const violations = seqResult.violations.length > 0
|
|
793
|
+
? seqResult.violations
|
|
794
|
+
: [{
|
|
795
|
+
svl: 1,
|
|
796
|
+
violatedConstraint: "symbol_existence",
|
|
797
|
+
actionIndex: 0,
|
|
798
|
+
description: errorsFlat.join("; "),
|
|
799
|
+
}];
|
|
800
|
+
const primarySvl = `SVL-${violations[0].svl}`;
|
|
801
|
+
const attempt = {
|
|
802
|
+
id: (0, runtime_types_1.generateAttemptId)(),
|
|
803
|
+
sessionId: session.sessionId,
|
|
804
|
+
attemptNumber: r + 1,
|
|
805
|
+
inputIntent: userIntent,
|
|
806
|
+
plannerSeed: (0, runtime_types_1.generatePlannerSeed)(currentPrompt, process.env.LLM_MODEL || "deepseek-chat"),
|
|
807
|
+
constraintSnapshotId: snapshotId,
|
|
808
|
+
generatedActions: filtered,
|
|
809
|
+
transitions: [],
|
|
810
|
+
violations,
|
|
811
|
+
outcome: "constraint_violation",
|
|
812
|
+
timestamp: Date.now(),
|
|
813
|
+
llmCallCount: 0,
|
|
814
|
+
durationMs: 0,
|
|
815
|
+
ruleHash: sessionRuleHash,
|
|
816
|
+
};
|
|
817
|
+
session.attempts.push(attempt);
|
|
210
818
|
(0, failure_corpus_1.recordFailure)({
|
|
211
819
|
intent: userIntent,
|
|
212
820
|
projectFunctions: ir.map((f) => f.name),
|
|
213
|
-
violatedSVL:
|
|
214
|
-
constraintType:
|
|
821
|
+
violatedSVL: primarySvl,
|
|
822
|
+
constraintType: violations[0].violatedConstraint,
|
|
215
823
|
actionSequence: filtered,
|
|
216
824
|
errorDetail: errorsFlat.join("; "),
|
|
825
|
+
ssgMissingFunctions: violations[0].missingStates,
|
|
826
|
+
plannerAttempt: r + 1,
|
|
827
|
+
plannerRetryTotal: maxRetries,
|
|
217
828
|
});
|
|
218
|
-
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: filtered, success: false, svlViolated:
|
|
219
|
-
currentPrompt =
|
|
829
|
+
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: filtered, success: false, svlViolated: primarySvl });
|
|
830
|
+
currentPrompt = `可用函数:\n${compactFuncList}${protocolChainHint}\n\n需求:${userIntent}\n\n错误:${errorsFlat.join(";")}。请修正。\n${RETRY_HINT}\n只输出 JSON。`;
|
|
831
|
+
useSystem = false;
|
|
832
|
+
(0, failure_corpus_1.saveCheckpoint)(userIntent, { attemptIndex: r + 1, sessionAttempts: session.attempts, currentPrompt, useSystem });
|
|
220
833
|
continue;
|
|
221
834
|
}
|
|
222
835
|
// 2) 协议状态机校验 (SSG)
|
|
223
836
|
if (protocols.length > 0) {
|
|
224
|
-
const protoResult =
|
|
225
|
-
if (!protoResult.valid) {
|
|
226
|
-
|
|
837
|
+
const protoResult = validateProtocolWithTransitions(filtered, protocols, namespaceInitialStates);
|
|
838
|
+
if (!protoResult.valid && protoResult.rejection) {
|
|
839
|
+
const rej = protoResult.rejection;
|
|
840
|
+
const explain = (0, ssg_validator_1.explainRejection)(rej);
|
|
841
|
+
console.error(explain);
|
|
842
|
+
const violation = {
|
|
843
|
+
svl: 4,
|
|
844
|
+
violatedConstraint: "protocol",
|
|
845
|
+
actionIndex: protoResult.index || 0,
|
|
846
|
+
currentStates: rej.currentState,
|
|
847
|
+
requiredStates: rej.requiredState,
|
|
848
|
+
missingStates: rej.missingFunctions,
|
|
849
|
+
fixPath: rej.fixPath,
|
|
850
|
+
namespace: rej.namespace,
|
|
851
|
+
description: JSON.stringify((0, ssg_validator_1.rejectionToJSON)(rej)),
|
|
852
|
+
};
|
|
853
|
+
const attempt = {
|
|
854
|
+
id: (0, runtime_types_1.generateAttemptId)(),
|
|
855
|
+
sessionId: session.sessionId,
|
|
856
|
+
attemptNumber: r + 1,
|
|
857
|
+
inputIntent: userIntent,
|
|
858
|
+
plannerSeed: (0, runtime_types_1.generatePlannerSeed)(currentPrompt, process.env.LLM_MODEL || "deepseek-chat"),
|
|
859
|
+
constraintSnapshotId: snapshotId,
|
|
860
|
+
generatedActions: filtered,
|
|
861
|
+
transitions: protoResult.transitions,
|
|
862
|
+
violations: [violation],
|
|
863
|
+
outcome: "constraint_violation",
|
|
864
|
+
timestamp: Date.now(),
|
|
865
|
+
llmCallCount: 0,
|
|
866
|
+
durationMs: 0,
|
|
867
|
+
ruleHash: sessionRuleHash,
|
|
868
|
+
};
|
|
869
|
+
session.attempts.push(attempt);
|
|
227
870
|
(0, failure_corpus_1.recordFailure)({
|
|
228
871
|
intent: userIntent,
|
|
229
872
|
projectFunctions: ir.map((f) => f.name),
|
|
230
873
|
violatedSVL: "SVL-4",
|
|
231
874
|
constraintType: "protocol",
|
|
232
875
|
actionSequence: filtered,
|
|
233
|
-
errorDetail:
|
|
876
|
+
errorDetail: JSON.stringify((0, ssg_validator_1.rejectionToJSON)(rej)),
|
|
877
|
+
ssgState: rej.currentState,
|
|
878
|
+
ssgTrace: protoResult.trace,
|
|
879
|
+
ssgFixPath: rej.fixPath,
|
|
880
|
+
ssgMissingFunctions: rej.missingFunctions,
|
|
881
|
+
plannerAttempt: r + 1,
|
|
882
|
+
plannerRetryTotal: maxRetries,
|
|
234
883
|
});
|
|
235
884
|
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: filtered, success: false, svlViolated: "SVL-4" });
|
|
236
|
-
|
|
237
|
-
|
|
885
|
+
// 尝试确定性修复:用 SSG 的 fixPath 自动插入缺失函数
|
|
886
|
+
const repaired = attemptSSGRepair(filtered, rej, ir, protocols, namespaceInitialStates);
|
|
887
|
+
if (repaired) {
|
|
888
|
+
console.error("🔧 SSG 修复成功,跳过 LLM 重试");
|
|
889
|
+
// Phase 6: Repair → Branch — 保留原始序列作为证据
|
|
890
|
+
const { createRootBranch, createBranch } = require("./branch-ledger");
|
|
891
|
+
const rootBranch = createRootBranch(filtered);
|
|
892
|
+
rootBranch.outcome = "violation";
|
|
893
|
+
const repairBranch = createBranch(rootBranch, "repair_attempt", repaired);
|
|
894
|
+
repairBranch.outcome = "success";
|
|
895
|
+
session.branchTree = [rootBranch, repairBranch];
|
|
896
|
+
session.rootBranchId = rootBranch.id;
|
|
897
|
+
repairMetrics = {
|
|
898
|
+
applied: true,
|
|
899
|
+
count: rej.fixPath?.length || 0,
|
|
900
|
+
branchIds: [rootBranch.id, repairBranch.id],
|
|
901
|
+
};
|
|
902
|
+
// Phase 7: Record repair event for analytics
|
|
903
|
+
try {
|
|
904
|
+
const repairDir = ".progmune_corpus/repairs";
|
|
905
|
+
if (!fs.existsSync(repairDir))
|
|
906
|
+
fs.mkdirSync(repairDir, { recursive: true });
|
|
907
|
+
const repairRecord = {
|
|
908
|
+
sessionId: session.sessionId,
|
|
909
|
+
timestamp: Date.now(),
|
|
910
|
+
violation: "SVL-4",
|
|
911
|
+
constraint: "protocol",
|
|
912
|
+
blockedFunction: rej.blocked,
|
|
913
|
+
namespace: rej.namespace,
|
|
914
|
+
missingStates: rej.missingFunctions,
|
|
915
|
+
fixPath: rej.fixPath,
|
|
916
|
+
originalPlan: filtered.map((a) => a.kind === "call" ? a.function : a.kind),
|
|
917
|
+
repairPlan: repaired.map((a) => a.kind === "call" ? a.function : a.kind),
|
|
918
|
+
success: true,
|
|
919
|
+
};
|
|
920
|
+
fs.writeFileSync(`${repairDir}/repair_${session.sessionId}.json`, JSON.stringify(repairRecord, null, 2), "utf-8");
|
|
921
|
+
}
|
|
922
|
+
catch { }
|
|
923
|
+
finalActions = repaired;
|
|
924
|
+
break;
|
|
925
|
+
}
|
|
926
|
+
const maskedFuncList = getMaskedFuncList();
|
|
927
|
+
currentPrompt = `当前协议状态只允许以下函数:\n${maskedFuncList}${protocolChainHint}\n\n需求:${userIntent}\n\n协议违规:${explain.replace(/\n/g, ';')}。请修正。\n${RETRY_HINT}\n只输出 JSON。`;
|
|
928
|
+
useSystem = false;
|
|
929
|
+
(0, failure_corpus_1.saveCheckpoint)(userIntent, { attemptIndex: r + 1, sessionAttempts: session.attempts, currentPrompt, useSystem });
|
|
238
930
|
continue;
|
|
239
931
|
}
|
|
932
|
+
// SSG passed — capture transitions
|
|
933
|
+
ssgTransitions = protoResult.transitions;
|
|
240
934
|
}
|
|
241
935
|
// 3) 语义合约校验
|
|
242
936
|
const semResult = (0, semantic_validator_1.checkSemantic)(userIntent, filtered);
|
|
243
937
|
if (!semResult.valid) {
|
|
244
|
-
console.
|
|
938
|
+
console.error("⚠️ 语义校验失败:", semResult.errors.join(", "));
|
|
939
|
+
const violation = {
|
|
940
|
+
svl: 4,
|
|
941
|
+
violatedConstraint: "semantic_contract",
|
|
942
|
+
actionIndex: 0,
|
|
943
|
+
description: semResult.errors.join("; "),
|
|
944
|
+
};
|
|
945
|
+
const attempt = {
|
|
946
|
+
id: (0, runtime_types_1.generateAttemptId)(),
|
|
947
|
+
sessionId: session.sessionId,
|
|
948
|
+
attemptNumber: r + 1,
|
|
949
|
+
inputIntent: userIntent,
|
|
950
|
+
plannerSeed: (0, runtime_types_1.generatePlannerSeed)(currentPrompt, process.env.LLM_MODEL || "deepseek-chat"),
|
|
951
|
+
constraintSnapshotId: snapshotId,
|
|
952
|
+
generatedActions: filtered,
|
|
953
|
+
transitions: ssgTransitions,
|
|
954
|
+
violations: [violation],
|
|
955
|
+
outcome: "constraint_violation",
|
|
956
|
+
timestamp: Date.now(),
|
|
957
|
+
llmCallCount: 0,
|
|
958
|
+
durationMs: 0,
|
|
959
|
+
ruleHash: sessionRuleHash,
|
|
960
|
+
};
|
|
961
|
+
session.attempts.push(attempt);
|
|
245
962
|
(0, failure_corpus_1.recordFailure)({
|
|
246
963
|
intent: userIntent,
|
|
247
964
|
projectFunctions: ir.map((f) => f.name),
|
|
@@ -249,19 +966,127 @@ ${userIntent}
|
|
|
249
966
|
constraintType: "protocol",
|
|
250
967
|
actionSequence: filtered,
|
|
251
968
|
errorDetail: semResult.errors.join("; "),
|
|
969
|
+
plannerAttempt: r + 1,
|
|
970
|
+
plannerRetryTotal: maxRetries,
|
|
252
971
|
});
|
|
253
972
|
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: filtered, success: false, svlViolated: "SVL-4" });
|
|
254
|
-
currentPrompt =
|
|
973
|
+
currentPrompt = `可用函数:\n${compactFuncList}${protocolChainHint}\n\n需求:${userIntent}\n\n语义错误:${semResult.errors.join(";")}。请修正。\n${RETRY_HINT}\n只输出 JSON。`;
|
|
974
|
+
useSystem = false;
|
|
975
|
+
(0, failure_corpus_1.saveCheckpoint)(userIntent, { attemptIndex: r + 1, sessionAttempts: session.attempts, currentPrompt, useSystem });
|
|
255
976
|
continue;
|
|
256
977
|
}
|
|
978
|
+
// 校验通过:构建成功 Attempt
|
|
979
|
+
const successAntibodyHit = antibodyHint
|
|
980
|
+
? {
|
|
981
|
+
level: antibodies[0]?.antibodyLevel || "ACL-3",
|
|
982
|
+
signature: antibodies[0]?.signature || "",
|
|
983
|
+
fixPath: antibodies[0]?.fixPath || [],
|
|
984
|
+
similarityScore: antibodies[0]?._score || 0,
|
|
985
|
+
action: "injected_hint",
|
|
986
|
+
llmCallsSaved: 0,
|
|
987
|
+
estimatedTokensSaved: 0,
|
|
988
|
+
}
|
|
989
|
+
: undefined;
|
|
990
|
+
const successAttempt = {
|
|
991
|
+
id: (0, runtime_types_1.generateAttemptId)(),
|
|
992
|
+
sessionId: session.sessionId,
|
|
993
|
+
attemptNumber: r + 1,
|
|
994
|
+
inputIntent: userIntent,
|
|
995
|
+
plannerSeed: (0, runtime_types_1.generatePlannerSeed)(currentPrompt, process.env.LLM_MODEL || "deepseek-chat"),
|
|
996
|
+
constraintSnapshotId: snapshotId,
|
|
997
|
+
generatedActions: filtered,
|
|
998
|
+
transitions: ssgTransitions,
|
|
999
|
+
violations: [],
|
|
1000
|
+
outcome: "success",
|
|
1001
|
+
timestamp: Date.now(),
|
|
1002
|
+
llmCallCount: 1,
|
|
1003
|
+
durationMs: 0,
|
|
1004
|
+
antibodyHit: successAntibodyHit,
|
|
1005
|
+
ruleHash: sessionRuleHash,
|
|
1006
|
+
};
|
|
1007
|
+
session.attempts.push(successAttempt);
|
|
1008
|
+
session.successfulAttempt = successAttempt;
|
|
257
1009
|
finalActions = filtered;
|
|
258
1010
|
break;
|
|
259
1011
|
}
|
|
260
1012
|
if (finalActions.length > 0) {
|
|
261
1013
|
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: finalActions, success: true });
|
|
1014
|
+
session.resolved = true;
|
|
1015
|
+
session.snapshotId = snapshotId;
|
|
1016
|
+
session.endedAt = Date.now();
|
|
1017
|
+
(0, failure_corpus_1.recordSession)(session); // Phase 5 will update recordSession to accept ExecutionSession
|
|
1018
|
+
(0, failure_corpus_1.clearCheckpoint)(userIntent);
|
|
262
1019
|
}
|
|
263
1020
|
else {
|
|
1021
|
+
// LLM 3 次重试失败,尝试本地规则回退
|
|
1022
|
+
console.error("[降级] LLM 规划失败,尝试本地规则回退");
|
|
1023
|
+
const fallback = generateFallbackPlan(userIntent, ir);
|
|
1024
|
+
if (fallback.length > 0) {
|
|
1025
|
+
console.error(`[降级] 本地规则生成了 ${fallback.length} 个动作`);
|
|
1026
|
+
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: fallback, success: true });
|
|
1027
|
+
const fallbackAttempt = {
|
|
1028
|
+
id: (0, runtime_types_1.generateAttemptId)(),
|
|
1029
|
+
sessionId: session.sessionId,
|
|
1030
|
+
attemptNumber: session.attempts.length + 1,
|
|
1031
|
+
inputIntent: userIntent,
|
|
1032
|
+
plannerSeed: (0, runtime_types_1.generatePlannerSeed)("fallback", "local-rule"),
|
|
1033
|
+
constraintSnapshotId: snapshotId,
|
|
1034
|
+
generatedActions: fallback,
|
|
1035
|
+
transitions: [],
|
|
1036
|
+
violations: [],
|
|
1037
|
+
outcome: "success",
|
|
1038
|
+
timestamp: Date.now(),
|
|
1039
|
+
llmCallCount: 0,
|
|
1040
|
+
durationMs: 0,
|
|
1041
|
+
ruleHash: sessionRuleHash,
|
|
1042
|
+
};
|
|
1043
|
+
session.attempts.push(fallbackAttempt);
|
|
1044
|
+
session.successfulAttempt = fallbackAttempt;
|
|
1045
|
+
session.resolved = true;
|
|
1046
|
+
session.snapshotId = snapshotId;
|
|
1047
|
+
session.endedAt = Date.now();
|
|
1048
|
+
(0, failure_corpus_1.recordSession)(session);
|
|
1049
|
+
(0, failure_corpus_1.clearCheckpoint)(userIntent);
|
|
1050
|
+
return wrapResult(fallback);
|
|
1051
|
+
}
|
|
264
1052
|
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: [], success: false });
|
|
1053
|
+
session.resolved = false;
|
|
1054
|
+
session.snapshotId = snapshotId;
|
|
1055
|
+
session.endedAt = Date.now();
|
|
1056
|
+
(0, failure_corpus_1.recordSession)(session);
|
|
1057
|
+
(0, failure_corpus_1.clearCheckpoint)(userIntent);
|
|
1058
|
+
}
|
|
1059
|
+
return wrapResult(finalActions);
|
|
1060
|
+
}
|
|
1061
|
+
/** 本地规则回退:当 LLM 不可用时,根据意图关键词生成简单动作序列 */
|
|
1062
|
+
function generateFallbackPlan(intent, ir) {
|
|
1063
|
+
const intentLower = intent.toLowerCase();
|
|
1064
|
+
const actions = [];
|
|
1065
|
+
const keywords = intentLower.split(/[\s,,、]+/).filter(k => k.length > 1);
|
|
1066
|
+
const matchedFuncs = [];
|
|
1067
|
+
for (const kw of keywords) {
|
|
1068
|
+
for (const fn of ir) {
|
|
1069
|
+
if (fn.name.toLowerCase().includes(kw) && !matchedFuncs.find((f) => f.name === fn.name)) {
|
|
1070
|
+
matchedFuncs.push(fn);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (matchedFuncs.length === 0)
|
|
1075
|
+
return [];
|
|
1076
|
+
for (const fn of matchedFuncs) {
|
|
1077
|
+
const args = (fn.params || []).map((p, i) => ({
|
|
1078
|
+
name: p.name || `p${i}`,
|
|
1079
|
+
type: p.type || 'any',
|
|
1080
|
+
value: `{{${p.name || `p${i}`}}}`
|
|
1081
|
+
}));
|
|
1082
|
+
const assignTo = fn.returnType && fn.returnType !== 'void' && fn.returnType !== 'undefined'
|
|
1083
|
+
? `${fn.name}_result` : undefined;
|
|
1084
|
+
if (assignTo) {
|
|
1085
|
+
actions.push({ kind: 'call', function: fn.name, args, assignTo });
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
actions.push({ kind: 'call', function: fn.name, args });
|
|
1089
|
+
}
|
|
265
1090
|
}
|
|
266
|
-
return
|
|
1091
|
+
return actions;
|
|
267
1092
|
}
|