@webskill/sdk 0.0.2 → 0.0.4
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/dist/browser.d.ts +78 -2
- package/dist/browser.js +388 -7
- package/dist/{dist-BMioPPri.js → dist-Bvhbxrrt.js} +13 -31
- package/dist/{dist-BbyIk4vG.js → dist-COsE72Ct.js} +426 -106
- package/dist/governance.d.ts +2 -2
- package/dist/governance.js +2 -2
- package/dist/{index-DF5ea732.d.ts → index-BsbfEiaC.d.ts} +3 -53
- package/dist/{index-j3Z3hbDk.d.ts → index-CqDIreSE.d.ts} +141 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +1 -1
- package/dist/node.d.ts +3 -3
- package/dist/node.js +2 -2
- package/dist/ui-react.d.ts +1 -1
- package/dist/ui-vue.d.ts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +1 -1
- package/package.json +1 -1
|
@@ -12,6 +12,41 @@ var WebSkillError = class extends Error {
|
|
|
12
12
|
this.details = details;
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
|
+
/** 技能目录内的安装清单文件名(不参与 files 列表与 digest) */
|
|
16
|
+
const SKILL_MANIFEST_FILE = "webskill.skill-manifest.json";
|
|
17
|
+
/** managed root 下的锁定文件名(不参与 files 列表与 digest) */
|
|
18
|
+
const SKILLS_LOCKFILE = "skills.lock.json";
|
|
19
|
+
/** 整体 digest:逐文件 "<path>:<sha256>\n" 拼接串(文件按 path 排序)的 sha256(hash 由调用方提供,可为 WebCrypto 异步实现) */
|
|
20
|
+
async function computeDigest(files, sha256) {
|
|
21
|
+
return sha256([...files].sort((a, b) => a.path.localeCompare(b.path)).map((f) => `${f.path}:${f.sha256}\n`).join(""));
|
|
22
|
+
}
|
|
23
|
+
/** 由逐文件清单构建 manifest(digest 由 computeDigest 计算) */
|
|
24
|
+
async function buildManifest(input) {
|
|
25
|
+
return {
|
|
26
|
+
schemaVersion: 1,
|
|
27
|
+
name: input.name,
|
|
28
|
+
...input.version !== void 0 ? { version: input.version } : {},
|
|
29
|
+
source: input.source,
|
|
30
|
+
installedAt: input.installedAt,
|
|
31
|
+
integrity: {
|
|
32
|
+
algorithm: "sha256",
|
|
33
|
+
digest: await computeDigest(input.files, input.sha256)
|
|
34
|
+
},
|
|
35
|
+
files: input.files
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** 按 manifest.files 比对实际 hash(调用方负责计算 actualHashes) */
|
|
39
|
+
function verifyManifest(manifest, actualHashes) {
|
|
40
|
+
const mismatches = [];
|
|
41
|
+
for (const file of manifest.files) {
|
|
42
|
+
const actual = actualHashes.get(file.path);
|
|
43
|
+
if (actual === void 0 || actual !== file.sha256) mismatches.push(file.path);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ok: mismatches.length === 0,
|
|
47
|
+
mismatches
|
|
48
|
+
};
|
|
49
|
+
}
|
|
15
50
|
const ROOT = "/";
|
|
16
51
|
/** 内存文件系统:路径一律规范化为 `/` 分隔,写入自动创建父目录 */
|
|
17
52
|
var MemoryFS = class {
|
|
@@ -1349,34 +1384,35 @@ var AgentLoop = class {
|
|
|
1349
1384
|
};
|
|
1350
1385
|
}
|
|
1351
1386
|
async run(input) {
|
|
1352
|
-
|
|
1353
|
-
|
|
1387
|
+
this.#deps.llm;
|
|
1388
|
+
this.#deps.artifactStore;
|
|
1354
1389
|
const now = this.#deps.clock?.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
1355
1390
|
const runId = input.runId ?? `run-${Math.random().toString(36).slice(2, 10)}`;
|
|
1356
1391
|
const trace = new TraceRecorder(runId, this.#deps.clock);
|
|
1357
1392
|
const startedAt = now();
|
|
1358
1393
|
const startMs = Date.parse(startedAt);
|
|
1359
|
-
const run = {
|
|
1360
|
-
id: runId,
|
|
1361
|
-
sessionId: input.sessionId,
|
|
1362
|
-
status: "running",
|
|
1363
|
-
phase: "route",
|
|
1364
|
-
userPrompt: input.userPrompt,
|
|
1365
|
-
startedAt,
|
|
1366
|
-
activeSkillNames: [],
|
|
1367
|
-
artifacts: [],
|
|
1368
|
-
trace: []
|
|
1369
|
-
};
|
|
1370
1394
|
const state = {
|
|
1371
1395
|
runId,
|
|
1372
|
-
run
|
|
1396
|
+
run: {
|
|
1397
|
+
id: runId,
|
|
1398
|
+
sessionId: input.sessionId,
|
|
1399
|
+
status: "running",
|
|
1400
|
+
phase: "route",
|
|
1401
|
+
userPrompt: input.userPrompt,
|
|
1402
|
+
startedAt,
|
|
1403
|
+
activeSkillNames: [],
|
|
1404
|
+
artifacts: [],
|
|
1405
|
+
trace: []
|
|
1406
|
+
},
|
|
1373
1407
|
trace,
|
|
1374
1408
|
reader: new SkillReader(this.#deps.fs, this.#deps.skillIndex),
|
|
1375
1409
|
activated: /* @__PURE__ */ new Set(),
|
|
1376
1410
|
activatedTools: /* @__PURE__ */ new Map(),
|
|
1377
1411
|
toolTimeoutMs: this.#config.toolTimeoutMs,
|
|
1378
1412
|
now,
|
|
1379
|
-
interactionSeq: 0
|
|
1413
|
+
interactionSeq: 0,
|
|
1414
|
+
messages: [],
|
|
1415
|
+
turn: 0
|
|
1380
1416
|
};
|
|
1381
1417
|
if (this.#deps.hooks && !this.#deps.hooks.onWarning) this.#deps.hooks.onWarning = (message) => trace.record("run.warning", { message });
|
|
1382
1418
|
const route = this.#deps.catalogFilter ? {
|
|
@@ -1396,113 +1432,272 @@ var AgentLoop = class {
|
|
|
1396
1432
|
return [];
|
|
1397
1433
|
}
|
|
1398
1434
|
}))).flat();
|
|
1399
|
-
|
|
1435
|
+
state.messages = [{
|
|
1400
1436
|
role: "system",
|
|
1401
1437
|
content: route.systemPrompt
|
|
1402
1438
|
}, {
|
|
1403
1439
|
role: "user",
|
|
1404
1440
|
content: input.userPrompt
|
|
1405
1441
|
}];
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1442
|
+
try {
|
|
1443
|
+
return await this.#turnLoop(state, startMs, 1, externalSpecs);
|
|
1444
|
+
} catch (e) {
|
|
1445
|
+
if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf(e), "RUN_FAILED");
|
|
1446
|
+
throw e;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
/** 主循环(run 从第 1 轮、resume 从快照轮次续跑;totalTimeout 以 startedAt 续算) */
|
|
1450
|
+
async #turnLoop(state, startMs, startTurn, externalSpecs) {
|
|
1451
|
+
const finish = (status, reason, output, errorCode) => this.#finish(state, status, reason, output, errorCode);
|
|
1452
|
+
const { llm } = this.#deps;
|
|
1453
|
+
const { trace, messages } = state;
|
|
1454
|
+
for (let turn = startTurn;; turn++) {
|
|
1455
|
+
state.turn = turn;
|
|
1456
|
+
if (turn > this.#config.maxTurns) return finish("failed", "max-turns", `Agent loop exceeded the maximum of ${this.#config.maxTurns} turns`, "RUN_MAX_TURNS_EXCEEDED");
|
|
1457
|
+
if (Date.parse(state.now()) - startMs > this.#config.totalTimeoutMs) return finish("failed", "timeout", `Agent loop exceeded the total timeout of ${this.#config.totalTimeoutMs}ms`, "RUN_TIMEOUT");
|
|
1458
|
+
const toolSpecs = [
|
|
1459
|
+
toLlmToolSpec(READ_SKILL_FILE_TOOL),
|
|
1460
|
+
...this.#deps.uiBridge ? [toLlmToolSpec(ASK_USER_TOOL)] : [],
|
|
1461
|
+
...[...state.activatedTools.values()].map(toLlmToolSpec),
|
|
1462
|
+
...externalSpecs
|
|
1463
|
+
];
|
|
1464
|
+
trace.record("llm.request", { data: {
|
|
1465
|
+
turn,
|
|
1466
|
+
messageCount: messages.length,
|
|
1467
|
+
toolCount: toolSpecs.length
|
|
1468
|
+
} });
|
|
1469
|
+
let response;
|
|
1432
1470
|
try {
|
|
1433
|
-
|
|
1471
|
+
response = llm.stream ? await this.#completeStreaming(llm, state, {
|
|
1472
|
+
model: this.#deps.model,
|
|
1473
|
+
messages,
|
|
1474
|
+
tools: toolSpecs,
|
|
1475
|
+
temperature: this.#config.temperature
|
|
1476
|
+
}) : await llm.complete({
|
|
1477
|
+
model: this.#deps.model,
|
|
1478
|
+
messages,
|
|
1479
|
+
tools: toolSpecs,
|
|
1480
|
+
temperature: this.#config.temperature
|
|
1481
|
+
});
|
|
1434
1482
|
} catch (e) {
|
|
1435
|
-
|
|
1483
|
+
const code = e instanceof WebSkillError && (e.code === "LLM_UNAVAILABLE" || e.code === "LLM_REQUEST_FAILED") ? e.code : "LLM_REQUEST_FAILED";
|
|
1484
|
+
return finish("failed", "llm-error", messageOf(e), code);
|
|
1436
1485
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
};
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
...externalSpecs
|
|
1452
|
-
];
|
|
1453
|
-
trace.record("llm.request", { data: {
|
|
1454
|
-
turn,
|
|
1455
|
-
messageCount: messages.length,
|
|
1456
|
-
toolCount: toolSpecs.length
|
|
1457
|
-
} });
|
|
1458
|
-
let response;
|
|
1486
|
+
trace.record("llm.response", { data: {
|
|
1487
|
+
turn,
|
|
1488
|
+
hasToolCalls: Boolean(response.toolCalls?.length),
|
|
1489
|
+
contentLength: response.content?.length ?? 0
|
|
1490
|
+
} });
|
|
1491
|
+
if (!response.toolCalls?.length) return finish("completed", "final-answer", response.content ?? "");
|
|
1492
|
+
await this.#lifecycle("execute", state, { turn });
|
|
1493
|
+
messages.push({
|
|
1494
|
+
role: "assistant",
|
|
1495
|
+
content: response.content ?? "",
|
|
1496
|
+
toolCalls: response.toolCalls
|
|
1497
|
+
});
|
|
1498
|
+
for (const call of response.toolCalls) {
|
|
1499
|
+
let result;
|
|
1459
1500
|
try {
|
|
1460
|
-
|
|
1461
|
-
model: this.#deps.model,
|
|
1462
|
-
messages,
|
|
1463
|
-
tools: toolSpecs,
|
|
1464
|
-
temperature: this.#config.temperature
|
|
1465
|
-
}) : await llm.complete({
|
|
1466
|
-
model: this.#deps.model,
|
|
1467
|
-
messages,
|
|
1468
|
-
tools: toolSpecs,
|
|
1469
|
-
temperature: this.#config.temperature
|
|
1470
|
-
});
|
|
1501
|
+
result = await this.#executeCall(call, state);
|
|
1471
1502
|
} catch (e) {
|
|
1472
|
-
|
|
1473
|
-
|
|
1503
|
+
if (e instanceof RunTerminated) return finish(e.outcome.status, e.outcome.reason, e.outcome.message, e.outcome.code);
|
|
1504
|
+
throw e;
|
|
1474
1505
|
}
|
|
1475
|
-
trace.record("llm.response", { data: {
|
|
1476
|
-
turn,
|
|
1477
|
-
hasToolCalls: Boolean(response.toolCalls?.length),
|
|
1478
|
-
contentLength: response.content?.length ?? 0
|
|
1479
|
-
} });
|
|
1480
|
-
if (!response.toolCalls?.length) return await finish("completed", "final-answer", response.content ?? "");
|
|
1481
|
-
await this.#lifecycle("execute", state, { turn });
|
|
1482
1506
|
messages.push({
|
|
1483
|
-
role: "
|
|
1484
|
-
|
|
1485
|
-
|
|
1507
|
+
role: "tool",
|
|
1508
|
+
toolCallId: call.id,
|
|
1509
|
+
content: JSON.stringify(result)
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
/** 统一终态处理:completed/failed/cancelled;终态即删快照(快照是续命机制,审计归治理) */
|
|
1515
|
+
async #finish(state, status, reason, output, errorCode) {
|
|
1516
|
+
const { run, trace } = state;
|
|
1517
|
+
run.status = status;
|
|
1518
|
+
run.terminationReason = reason;
|
|
1519
|
+
run.endedAt = state.now();
|
|
1520
|
+
run.activeSkillNames = [...state.activated].sort();
|
|
1521
|
+
run.artifacts = await this.#deps.artifactStore.listArtifacts(state.runId);
|
|
1522
|
+
if (status === "completed") trace.record("run.completed", { data: { reason } });
|
|
1523
|
+
else if (status === "cancelled") trace.record("run.cancelled", {
|
|
1524
|
+
message: output,
|
|
1525
|
+
data: { reason }
|
|
1526
|
+
});
|
|
1527
|
+
else trace.record("run.failed", {
|
|
1528
|
+
message: output,
|
|
1529
|
+
data: {
|
|
1530
|
+
reason,
|
|
1531
|
+
...errorCode ? { code: errorCode } : {}
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1534
|
+
const bridge = this.#deps.uiBridge;
|
|
1535
|
+
if (status === "completed" && bridge?.renderResult && this.#config.renderResult !== "off") try {
|
|
1536
|
+
const request = buildRenderResult(run, output);
|
|
1537
|
+
await bridge.renderResult(request);
|
|
1538
|
+
trace.record("ui.rendered", { data: { blockCount: request.blocks.length } });
|
|
1539
|
+
} catch (e) {
|
|
1540
|
+
trace.record("run.warning", { message: `renderResult failed: ${messageOf(e)}` });
|
|
1541
|
+
}
|
|
1542
|
+
if (this.#deps.snapshotStore) try {
|
|
1543
|
+
await this.#deps.snapshotStore.delete(state.runId);
|
|
1544
|
+
} catch (e) {
|
|
1545
|
+
trace.record("run.warning", { message: `Failed to delete run snapshot: ${messageOf(e)}` });
|
|
1546
|
+
}
|
|
1547
|
+
try {
|
|
1548
|
+
await this.#lifecycle(status === "completed" ? "complete" : "fail", state, { reason });
|
|
1549
|
+
} catch (e) {
|
|
1550
|
+
trace.record("run.warning", { message: `Terminal lifecycle hook failed: ${messageOf(e)}` });
|
|
1551
|
+
}
|
|
1552
|
+
run.trace = trace.list();
|
|
1553
|
+
return {
|
|
1554
|
+
output,
|
|
1555
|
+
run
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
/** D3 快照写入(含 pendingInteraction 与过期时间;写失败降级 run.warning) */
|
|
1559
|
+
async #saveSnapshot(state, request) {
|
|
1560
|
+
const store = this.#deps.snapshotStore;
|
|
1561
|
+
if (!store) return;
|
|
1562
|
+
const snapshot = {
|
|
1563
|
+
schemaVersion: 1,
|
|
1564
|
+
runId: state.runId,
|
|
1565
|
+
sessionId: state.run.sessionId,
|
|
1566
|
+
userPrompt: state.run.userPrompt,
|
|
1567
|
+
startedAt: state.run.startedAt,
|
|
1568
|
+
messages: state.messages.map((m) => ({ ...m })),
|
|
1569
|
+
turn: state.turn,
|
|
1570
|
+
activeSkillNames: [...state.activated].sort(),
|
|
1571
|
+
activatedTools: [...state.activatedTools.values()],
|
|
1572
|
+
pendingInteraction: request,
|
|
1573
|
+
interactionExpiresAt: state.run.interruptExpiresAt,
|
|
1574
|
+
config: {
|
|
1575
|
+
maxTurns: this.#config.maxTurns,
|
|
1576
|
+
totalTimeoutMs: this.#config.totalTimeoutMs,
|
|
1577
|
+
toolTimeoutMs: this.#config.toolTimeoutMs,
|
|
1578
|
+
...this.#config.temperature !== void 0 ? { temperature: this.#config.temperature } : {}
|
|
1579
|
+
},
|
|
1580
|
+
snapshotAt: state.now()
|
|
1581
|
+
};
|
|
1582
|
+
try {
|
|
1583
|
+
await store.save(snapshot);
|
|
1584
|
+
} catch (e) {
|
|
1585
|
+
state.trace.record("run.warning", { message: `Failed to save run snapshot: ${messageOf(e)}` });
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* D3 恢复:以快照重建 LoopState,重新发起等待中的交互(表单重新渲染),
|
|
1590
|
+
* 应答后从快照轮次续跑主循环;totalTimeout 以快照 startedAt 续算。
|
|
1591
|
+
*/
|
|
1592
|
+
async resume(snapshot) {
|
|
1593
|
+
const now = this.#deps.clock?.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
1594
|
+
const runId = snapshot.runId;
|
|
1595
|
+
const trace = new TraceRecorder(runId, this.#deps.clock);
|
|
1596
|
+
const startMs = Date.parse(snapshot.startedAt);
|
|
1597
|
+
const state = {
|
|
1598
|
+
runId,
|
|
1599
|
+
run: {
|
|
1600
|
+
id: runId,
|
|
1601
|
+
sessionId: snapshot.sessionId,
|
|
1602
|
+
status: "running",
|
|
1603
|
+
phase: "execute",
|
|
1604
|
+
userPrompt: snapshot.userPrompt,
|
|
1605
|
+
startedAt: snapshot.startedAt,
|
|
1606
|
+
activeSkillNames: snapshot.activeSkillNames,
|
|
1607
|
+
artifacts: [],
|
|
1608
|
+
trace: []
|
|
1609
|
+
},
|
|
1610
|
+
trace,
|
|
1611
|
+
reader: new SkillReader(this.#deps.fs, this.#deps.skillIndex),
|
|
1612
|
+
activated: new Set(snapshot.activeSkillNames),
|
|
1613
|
+
activatedTools: new Map(snapshot.activatedTools.map((d) => [d.name, d])),
|
|
1614
|
+
toolTimeoutMs: snapshot.config.toolTimeoutMs,
|
|
1615
|
+
now,
|
|
1616
|
+
interactionSeq: 0,
|
|
1617
|
+
messages: snapshot.messages.map((m) => ({ ...m })),
|
|
1618
|
+
turn: snapshot.turn
|
|
1619
|
+
};
|
|
1620
|
+
if (this.#deps.hooks && !this.#deps.hooks.onWarning) this.#deps.hooks.onWarning = (message) => trace.record("run.warning", { message });
|
|
1621
|
+
trace.record("run.resumed", { data: {
|
|
1622
|
+
snapshotAt: snapshot.snapshotAt,
|
|
1623
|
+
turn: snapshot.turn,
|
|
1624
|
+
interactionType: snapshot.pendingInteraction.type
|
|
1625
|
+
} });
|
|
1626
|
+
const pending = snapshot.pendingInteraction;
|
|
1627
|
+
const pendingCall = this.#findPendingToolCall(state.messages);
|
|
1628
|
+
try {
|
|
1629
|
+
if ((pending.type === "form" || pending.type === "select") && pendingCall) {
|
|
1630
|
+
const value = await this.#interact(state, pending, {
|
|
1631
|
+
tool: pendingCall.name,
|
|
1632
|
+
resumed: true
|
|
1633
|
+
});
|
|
1634
|
+
const args = {
|
|
1635
|
+
...pendingCall.arguments,
|
|
1636
|
+
...typeof value === "object" && value !== null ? value : {}
|
|
1637
|
+
};
|
|
1638
|
+
const result = await this.#executeCall({
|
|
1639
|
+
...pendingCall,
|
|
1640
|
+
arguments: args
|
|
1641
|
+
}, state);
|
|
1642
|
+
state.messages.push({
|
|
1643
|
+
role: "tool",
|
|
1644
|
+
toolCallId: pendingCall.id,
|
|
1645
|
+
content: JSON.stringify(result)
|
|
1646
|
+
});
|
|
1647
|
+
} else if (pending.type === "ask" && pendingCall) {
|
|
1648
|
+
const value = await this.#interact(state, pending, {
|
|
1649
|
+
tool: pendingCall.name,
|
|
1650
|
+
resumed: true
|
|
1651
|
+
});
|
|
1652
|
+
const result = {
|
|
1653
|
+
ok: true,
|
|
1654
|
+
content: [{
|
|
1655
|
+
type: "text",
|
|
1656
|
+
text: typeof value === "string" ? value : JSON.stringify(value ?? "")
|
|
1657
|
+
}]
|
|
1658
|
+
};
|
|
1659
|
+
state.trace.record("tool.completed", { data: {
|
|
1660
|
+
name: pendingCall.name,
|
|
1661
|
+
callId: pendingCall.id
|
|
1662
|
+
} });
|
|
1663
|
+
state.messages.push({
|
|
1664
|
+
role: "tool",
|
|
1665
|
+
toolCallId: pendingCall.id,
|
|
1666
|
+
content: JSON.stringify(result)
|
|
1667
|
+
});
|
|
1668
|
+
} else if (pendingCall) {
|
|
1669
|
+
const result = await this.#executeCall(pendingCall, state);
|
|
1670
|
+
state.messages.push({
|
|
1671
|
+
role: "tool",
|
|
1672
|
+
toolCallId: pendingCall.id,
|
|
1673
|
+
content: JSON.stringify(result)
|
|
1486
1674
|
});
|
|
1487
|
-
for (const call of response.toolCalls) {
|
|
1488
|
-
let result;
|
|
1489
|
-
try {
|
|
1490
|
-
result = await this.#executeCall(call, state);
|
|
1491
|
-
} catch (e) {
|
|
1492
|
-
if (e instanceof RunTerminated) return await finish(e.outcome.status, e.outcome.reason, e.outcome.message, e.outcome.code);
|
|
1493
|
-
throw e;
|
|
1494
|
-
}
|
|
1495
|
-
messages.push({
|
|
1496
|
-
role: "tool",
|
|
1497
|
-
toolCallId: call.id,
|
|
1498
|
-
content: JSON.stringify(result)
|
|
1499
|
-
});
|
|
1500
|
-
}
|
|
1501
1675
|
}
|
|
1502
1676
|
} catch (e) {
|
|
1503
|
-
if (e instanceof
|
|
1677
|
+
if (e instanceof RunTerminated) return this.#finish(state, e.outcome.status, e.outcome.reason, e.outcome.message, e.outcome.code);
|
|
1504
1678
|
throw e;
|
|
1505
1679
|
}
|
|
1680
|
+
const externalSpecs = (await Promise.all((this.#deps.externalTools ?? []).map(async (source) => {
|
|
1681
|
+
try {
|
|
1682
|
+
return await source.listToolSpecs();
|
|
1683
|
+
} catch (e) {
|
|
1684
|
+
trace.record("run.warning", { message: `External tool source "${source.kind}" failed to list specs: ${messageOf(e)}` });
|
|
1685
|
+
return [];
|
|
1686
|
+
}
|
|
1687
|
+
}))).flat();
|
|
1688
|
+
try {
|
|
1689
|
+
return await this.#turnLoop(state, startMs, snapshot.turn + 1, externalSpecs);
|
|
1690
|
+
} catch (e) {
|
|
1691
|
+
if (e instanceof WebSkillError && e.code === "RUN_FAILED") return this.#finish(state, "failed", "hook-error", messageOf(e), "RUN_FAILED");
|
|
1692
|
+
throw e;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
/** 最后一条 assistant 消息里第一个尚无 tool 响应的工具调用(即中断时正在处理的那个) */
|
|
1696
|
+
#findPendingToolCall(messages) {
|
|
1697
|
+
const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant" && m.toolCalls?.length);
|
|
1698
|
+
if (!lastAssistant?.toolCalls) return void 0;
|
|
1699
|
+
const answered = new Set(messages.filter((m) => m.role === "tool").map((m) => m.toolCallId));
|
|
1700
|
+
return lastAssistant.toolCalls.find((call) => !answered.has(call.id));
|
|
1506
1701
|
}
|
|
1507
1702
|
/**
|
|
1508
1703
|
* 流式 LLM 调用:text-delta 仅在 EventBus 有订阅者时发射 llm.delta 事件,
|
|
@@ -1563,6 +1758,7 @@ var AgentLoop = class {
|
|
|
1563
1758
|
const { run } = state;
|
|
1564
1759
|
run.status = "interrupted";
|
|
1565
1760
|
run.interruptExpiresAt = new Date(Date.parse(state.now()) + this.#policy.interactionTimeoutMs).toISOString();
|
|
1761
|
+
await this.#saveSnapshot(state, request);
|
|
1566
1762
|
await this.#lifecycle("interact", state, {
|
|
1567
1763
|
interactionId: request.id,
|
|
1568
1764
|
type: request.type
|
|
@@ -1919,6 +2115,58 @@ function mergeCatalogEntries(localEntries, providerEntries) {
|
|
|
1919
2115
|
for (const entry of providerEntries) if (!byName.has(entry.name)) byName.set(entry.name, entry);
|
|
1920
2116
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
1921
2117
|
}
|
|
2118
|
+
const RUN_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
2119
|
+
const SNAPSHOT_SUFFIX = ".snapshot.json";
|
|
2120
|
+
/**
|
|
2121
|
+
* FileSystemProvider 后端的快照存储:<root>/<runId>.snapshot.json。
|
|
2122
|
+
* 坏 JSON → RUN_SNAPSHOT_INCOMPATIBLE 并自动清理坏文件;runId 过路径安全校验。
|
|
2123
|
+
*/
|
|
2124
|
+
var FsRunSnapshotStore = class {
|
|
2125
|
+
#root;
|
|
2126
|
+
#fs;
|
|
2127
|
+
constructor(deps) {
|
|
2128
|
+
this.#root = deps.root.replace(/\/+$/, "");
|
|
2129
|
+
this.#fs = deps.fs;
|
|
2130
|
+
}
|
|
2131
|
+
#path(runId) {
|
|
2132
|
+
return resolveInsideRoot(this.#root, `${runId}${SNAPSHOT_SUFFIX}`);
|
|
2133
|
+
}
|
|
2134
|
+
async save(snapshot) {
|
|
2135
|
+
await this.#fs.writeText(this.#path(snapshot.runId), JSON.stringify(snapshot, null, 2));
|
|
2136
|
+
}
|
|
2137
|
+
async load(runId) {
|
|
2138
|
+
const path = this.#path(runId);
|
|
2139
|
+
if (!await this.#fs.exists(path)) return void 0;
|
|
2140
|
+
let parsed;
|
|
2141
|
+
try {
|
|
2142
|
+
parsed = JSON.parse(await this.#fs.readText(path));
|
|
2143
|
+
} catch (e) {
|
|
2144
|
+
await this.#fs.remove(path).catch(() => void 0);
|
|
2145
|
+
throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${runId}" is corrupted and was deleted: ${e instanceof Error ? e.message : String(e)}`, e);
|
|
2146
|
+
}
|
|
2147
|
+
const snapshot = parsed;
|
|
2148
|
+
if (typeof snapshot !== "object" || snapshot === null || snapshot.runId !== runId) {
|
|
2149
|
+
await this.#fs.remove(path).catch(() => void 0);
|
|
2150
|
+
throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${runId}" has an unexpected shape and was deleted`);
|
|
2151
|
+
}
|
|
2152
|
+
return snapshot;
|
|
2153
|
+
}
|
|
2154
|
+
async delete(runId) {
|
|
2155
|
+
const path = this.#path(runId);
|
|
2156
|
+
if (await this.#fs.exists(path)) await this.#fs.remove(path);
|
|
2157
|
+
}
|
|
2158
|
+
async list() {
|
|
2159
|
+
if (!await this.#fs.exists(this.#root)) return [];
|
|
2160
|
+
const out = [];
|
|
2161
|
+
for (const entry of await this.#fs.list(this.#root)) {
|
|
2162
|
+
if (entry.type !== "file" || !entry.path.endsWith(SNAPSHOT_SUFFIX)) continue;
|
|
2163
|
+
try {
|
|
2164
|
+
out.push(JSON.parse(await this.#fs.readText(entry.path)));
|
|
2165
|
+
} catch {}
|
|
2166
|
+
}
|
|
2167
|
+
return out.sort((a, b) => a.snapshotAt.localeCompare(b.snapshotAt));
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
1922
2170
|
/** runtime 门面:组合 discovery / router / agent loop / lifecycle */
|
|
1923
2171
|
var WebSkillRuntime = class {
|
|
1924
2172
|
#deps;
|
|
@@ -1982,7 +2230,8 @@ var WebSkillRuntime = class {
|
|
|
1982
2230
|
longTerm: this.#deps.longTerm,
|
|
1983
2231
|
externalTools: this.#deps.externalTools,
|
|
1984
2232
|
skillProviders: this.#deps.skillProviders,
|
|
1985
|
-
catalogFilter: this.#deps.catalogFilter
|
|
2233
|
+
catalogFilter: this.#deps.catalogFilter,
|
|
2234
|
+
snapshotStore: this.#deps.snapshotStore
|
|
1986
2235
|
}, this.#deps.config).run({
|
|
1987
2236
|
sessionId: this.#session.id,
|
|
1988
2237
|
userPrompt,
|
|
@@ -2002,7 +2251,78 @@ var WebSkillRuntime = class {
|
|
|
2002
2251
|
});
|
|
2003
2252
|
return result;
|
|
2004
2253
|
}
|
|
2254
|
+
/** D3:列出可恢复的 interrupted run(供 UI 展示"未完成任务") */
|
|
2255
|
+
async listInterruptedRuns() {
|
|
2256
|
+
if (!this.#deps.snapshotStore) return [];
|
|
2257
|
+
return this.#deps.snapshotStore.list();
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* D3 恢复 interrupted run:
|
|
2261
|
+
* 不存在 → RUN_SNAPSHOT_NOT_FOUND;schemaVersion 不匹配 → 删除 + RUN_SNAPSHOT_INCOMPATIBLE;
|
|
2262
|
+
* 已过期 → interaction-timeout 终态并删快照;否则重建 LoopState 重新发起交互续跑。
|
|
2263
|
+
*/
|
|
2264
|
+
async resumeRun(runId) {
|
|
2265
|
+
const store = this.#deps.snapshotStore;
|
|
2266
|
+
const snapshot = store ? await store.load(runId) : void 0;
|
|
2267
|
+
if (!snapshot) throw new WebSkillError("RUN_SNAPSHOT_NOT_FOUND", `No snapshot found for run "${runId}"`);
|
|
2268
|
+
if (snapshot.schemaVersion !== 1) {
|
|
2269
|
+
await store.delete(runId);
|
|
2270
|
+
throw new WebSkillError("RUN_SNAPSHOT_INCOMPATIBLE", `Snapshot for run "${runId}" has incompatible schemaVersion ${String(snapshot.schemaVersion)}`);
|
|
2271
|
+
}
|
|
2272
|
+
if (Date.now() > Date.parse(snapshot.interactionExpiresAt)) {
|
|
2273
|
+
await store.delete(runId);
|
|
2274
|
+
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2275
|
+
return {
|
|
2276
|
+
output: "Interaction timed out before the run was resumed",
|
|
2277
|
+
run: {
|
|
2278
|
+
id: runId,
|
|
2279
|
+
sessionId: snapshot.sessionId,
|
|
2280
|
+
status: "failed",
|
|
2281
|
+
phase: "fail",
|
|
2282
|
+
userPrompt: snapshot.userPrompt,
|
|
2283
|
+
startedAt: snapshot.startedAt,
|
|
2284
|
+
endedAt,
|
|
2285
|
+
terminationReason: "interaction-timeout",
|
|
2286
|
+
activeSkillNames: snapshot.activeSkillNames,
|
|
2287
|
+
artifacts: [],
|
|
2288
|
+
trace: [{
|
|
2289
|
+
id: `evt-expired-${runId}`,
|
|
2290
|
+
runId,
|
|
2291
|
+
ts: endedAt,
|
|
2292
|
+
type: "run.failed",
|
|
2293
|
+
message: `Interaction timed out before the run was resumed (expired at ${snapshot.interactionExpiresAt})`,
|
|
2294
|
+
data: {
|
|
2295
|
+
reason: "interaction-timeout",
|
|
2296
|
+
code: "RUN_INTERACTION_TIMEOUT"
|
|
2297
|
+
}
|
|
2298
|
+
}]
|
|
2299
|
+
}
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
if (!this.#catalogCache) await this.discover();
|
|
2303
|
+
const cache = this.#catalogCache;
|
|
2304
|
+
if (!cache) throw new WebSkillError("RUN_FAILED", "discover() did not populate the catalog cache");
|
|
2305
|
+
return new AgentLoop({
|
|
2306
|
+
llm: this.#deps.llm,
|
|
2307
|
+
executor: this.#deps.executor,
|
|
2308
|
+
schemaInferer: this.#deps.schemaInferer,
|
|
2309
|
+
artifactStore: this.#deps.artifactStore,
|
|
2310
|
+
fs: this.#deps.fs,
|
|
2311
|
+
skillIndex: cache.index,
|
|
2312
|
+
model: this.#deps.model,
|
|
2313
|
+
uiBridge: this.#deps.uiBridge,
|
|
2314
|
+
interaction: this.#deps.interaction,
|
|
2315
|
+
memory: this.#deps.memory,
|
|
2316
|
+
hooks: this.#deps.hooks,
|
|
2317
|
+
eventBus: this.#events,
|
|
2318
|
+
longTerm: this.#deps.longTerm,
|
|
2319
|
+
externalTools: this.#deps.externalTools,
|
|
2320
|
+
skillProviders: this.#deps.skillProviders,
|
|
2321
|
+
catalogFilter: this.#deps.catalogFilter,
|
|
2322
|
+
snapshotStore: store
|
|
2323
|
+
}, this.#deps.config).resume(snapshot);
|
|
2324
|
+
}
|
|
2005
2325
|
};
|
|
2006
2326
|
|
|
2007
2327
|
//#endregion
|
|
2008
|
-
export {
|
|
2328
|
+
export { renderCatalogJson as $, parseBridgeRequest as A, SkillDiscovery as B, bridgeError as C, fromVercelStreamPart as D, fromVercelResult as E, MemoryFS as F, checkSkillRules as G, WebSkillError as H, SKILLS_LOCKFILE as I, isValidSkillName as J, computeDigest as K, SKILL_MANIFEST_FILE as L, schemaToForm as M, toLlmToolSpec as N, mergeCatalogEntries as O, toVercelToolSpecs as P, renderAvailableSkillsXml as Q, SKILL_NAME_MAX_LENGTH as R, WebSkillRuntime as S, createScriptContext as T, buildCatalog as U, SkillReader as V, buildManifest as W, normalizePath as X, jsonRenderer as Y, parseSkillMarkdown as Z, READ_SKILL_FILE_INPUT_SCHEMA as _, EventBus as a, RUN_SNAPSHOT_SCHEMA_VERSION as b, FsRunSnapshotStore as c, InMemoryStore as d, resolveInsideRoot as et, MemoryArtifactStore as f, ProgressiveRouter as g, OpenAiCompatibleClient as h, AgentLoop as i, resolveToolName as j, normalizeToolContent as k, FullDisclosureRouter as l, MockUiBridge as m, ASK_USER_TOOL as n, verifyManifest as nt, FsArtifactStore as o, MockLlmClient as p, escapeXml as q, ASK_USER_TOOL_NAME as r, xmlRenderer as rt, FsMemoryStore as s, ASK_USER_INPUT_SCHEMA as t, validateSkills as tt, HookRunner as u, READ_SKILL_FILE_TOOL as v, buildRenderResult as w, TraceRecorder as x, READ_SKILL_FILE_TOOL_NAME as y, SKILL_NAME_PATTERN as z };
|
package/dist/governance.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { A as LlmClient, Gt as SkillCatalogEntry, Lt as FileSystemProvider, M as LlmMessage, Zt as SkillManifest, at as RuntimeRun, bt as WebSkillRuntime, vt as UiBridge } from "./index-CqDIreSE.js";
|
|
2
|
+
import { d as SkillManager } from "./index-BsbfEiaC.js";
|
|
3
3
|
//#region ../governance/dist/index.d.ts
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
type CandidateStatus = 'draft' | 'pending-review' | 'approved' | 'published' | 'rejected';
|
package/dist/governance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as
|
|
2
|
-
import { i as NodeFS } from "./dist-
|
|
1
|
+
import { H as WebSkillError, J as isValidSkillName, et as resolveInsideRoot, tt as validateSkills } from "./dist-COsE72Ct.js";
|
|
2
|
+
import { i as NodeFS } from "./dist-Bvhbxrrt.js";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { mkdtemp } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|