@kiturone/kapibala 0.0.5 → 0.0.6
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/index.cjs +530 -72
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +224 -8
- package/dist/index.d.ts +224 -8
- package/dist/index.js +529 -72
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -119,12 +119,25 @@ var KapibalaError = class extends Error {
|
|
|
119
119
|
code;
|
|
120
120
|
retryPolicy;
|
|
121
121
|
safeMessage;
|
|
122
|
+
dispatchOutcome;
|
|
122
123
|
constructor(message, options) {
|
|
123
124
|
super(message);
|
|
124
125
|
this.name = "KapibalaError";
|
|
125
126
|
this.code = options?.code ?? "INTERNAL_ERROR";
|
|
126
127
|
this.retryPolicy = options?.retryPolicy ?? "never";
|
|
127
128
|
this.safeMessage = options?.safeMessage ?? "Operation failed";
|
|
129
|
+
this.dispatchOutcome = options?.dispatchOutcome;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
var PreDispatchError = class extends KapibalaError {
|
|
133
|
+
constructor(message, options) {
|
|
134
|
+
super(message, {
|
|
135
|
+
code: options?.code ?? "PRE_DISPATCH_FAILED",
|
|
136
|
+
retryPolicy: options?.retryPolicy ?? "never",
|
|
137
|
+
safeMessage: options?.safeMessage ?? "Tool execution was aborted before dispatch",
|
|
138
|
+
dispatchOutcome: "not_sent"
|
|
139
|
+
});
|
|
140
|
+
this.name = "PreDispatchError";
|
|
128
141
|
}
|
|
129
142
|
};
|
|
130
143
|
var ModelError = class extends KapibalaError {
|
|
@@ -309,6 +322,16 @@ function validateProtocolContent(content) {
|
|
|
309
322
|
invalidState();
|
|
310
323
|
}
|
|
311
324
|
}
|
|
325
|
+
if (block.type === "tool_result" && "execution" in block && block.execution !== void 0) {
|
|
326
|
+
const ex = block.execution;
|
|
327
|
+
if (!ex || typeof ex !== "object" || ex.version !== 1 || !["not_sent", "succeeded", "failed", "unknown"].includes(ex.dispatchOutcome) || ex.hostError !== void 0 && (!ex.hostError || typeof ex.hostError !== "object" || !["validation", "projection", "hook", "audit"].includes(ex.hostError.phase) || typeof ex.hostError.code !== "string" || !ex.hostError.code))
|
|
328
|
+
invalidState();
|
|
329
|
+
}
|
|
330
|
+
if (block.type === "text" && "promptSource" in block && block.promptSource !== void 0) {
|
|
331
|
+
const ps = block.promptSource;
|
|
332
|
+
if (!ps || typeof ps !== "object" || ps.version !== 1 || typeof ps.server !== "string" || !ps.server || typeof ps.name !== "string" || !ps.name || ps.scope !== void 0 && typeof ps.scope !== "string" || ps.contentDigest !== void 0 && typeof ps.contentDigest !== "string" || ps.configIdentity !== void 0 && typeof ps.configIdentity !== "string")
|
|
333
|
+
invalidState();
|
|
334
|
+
}
|
|
312
335
|
validatedBlocks.set(block, true);
|
|
313
336
|
}
|
|
314
337
|
validatedContents.set(content, true);
|
|
@@ -426,7 +449,11 @@ function normalizeHistory(input) {
|
|
|
426
449
|
content: "Tool execution was interrupted or crashed in previous session",
|
|
427
450
|
isError: true,
|
|
428
451
|
errorCode: "OUTCOME_UNKNOWN",
|
|
429
|
-
retryPolicy: "after_user_action"
|
|
452
|
+
retryPolicy: "after_user_action",
|
|
453
|
+
execution: {
|
|
454
|
+
version: 1,
|
|
455
|
+
dispatchOutcome: "unknown"
|
|
456
|
+
}
|
|
430
457
|
};
|
|
431
458
|
content.push(repair);
|
|
432
459
|
result.repairs.push({
|
|
@@ -4710,7 +4737,8 @@ var CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
4710
4737
|
"exec",
|
|
4711
4738
|
"net:outbound",
|
|
4712
4739
|
"env:read",
|
|
4713
|
-
"agent:spawn"
|
|
4740
|
+
"agent:spawn",
|
|
4741
|
+
"external:call"
|
|
4714
4742
|
]);
|
|
4715
4743
|
function validatePermissionRules(rules) {
|
|
4716
4744
|
for (const [index2, rule] of rules.entries()) {
|
|
@@ -4823,7 +4851,8 @@ var SessionApprovalCache = class {
|
|
|
4823
4851
|
[...request.capabilities].sort(),
|
|
4824
4852
|
request.input,
|
|
4825
4853
|
request.rootDir,
|
|
4826
|
-
request.shell ?? null
|
|
4854
|
+
request.shell ?? null,
|
|
4855
|
+
request.executionIdentity ?? null
|
|
4827
4856
|
])
|
|
4828
4857
|
).digest("hex");
|
|
4829
4858
|
}
|
|
@@ -4841,9 +4870,14 @@ var ToolRegistry = class {
|
|
|
4841
4870
|
toolSources = /* @__PURE__ */ new Map();
|
|
4842
4871
|
// toolName -> source
|
|
4843
4872
|
conflictPolicy;
|
|
4873
|
+
version = 0;
|
|
4844
4874
|
constructor(options) {
|
|
4845
4875
|
this.conflictPolicy = options?.conflictPolicy ?? "error";
|
|
4846
4876
|
}
|
|
4877
|
+
/** 获取当前注册表版本号;每次有效变更(注册、替换、注销)单调自增。 */
|
|
4878
|
+
get currentVersion() {
|
|
4879
|
+
return this.version;
|
|
4880
|
+
}
|
|
4847
4881
|
register(tool, opts) {
|
|
4848
4882
|
const source = opts?.source ?? tool.metadata?.source ?? "builtin";
|
|
4849
4883
|
const existing = this.tools.get(tool.name);
|
|
@@ -4859,27 +4893,93 @@ var ToolRegistry = class {
|
|
|
4859
4893
|
}
|
|
4860
4894
|
this.tools.set(tool.name, tool);
|
|
4861
4895
|
this.toolSources.set(tool.name, source);
|
|
4896
|
+
this.version++;
|
|
4862
4897
|
}
|
|
4863
|
-
|
|
4898
|
+
/**
|
|
4899
|
+
* 原子替换指定来源的全部工具:
|
|
4900
|
+
* 1. 预检候选批内自身重名:若候选批次内包含同名工具,整批拒绝;
|
|
4901
|
+
* 2. 预检跨来源冲突:若候选工具被其它来源占用且策略不允许覆盖,整批拒绝;
|
|
4902
|
+
* 3. 同步原子提交:预检全部通过后一次性清理该来源旧工具并写入新工具;失败时完全保持旧状态不变。
|
|
4903
|
+
*/
|
|
4904
|
+
replaceSource(source, tools) {
|
|
4905
|
+
const batchNames = /* @__PURE__ */ new Set();
|
|
4906
|
+
for (const tool of tools) {
|
|
4907
|
+
if (batchNames.has(tool.name)) {
|
|
4908
|
+
throw new Error(
|
|
4909
|
+
`Invalid candidate tools: duplicate tool name '${tool.name}' within source batch '${source}'`
|
|
4910
|
+
);
|
|
4911
|
+
}
|
|
4912
|
+
batchNames.add(tool.name);
|
|
4913
|
+
}
|
|
4864
4914
|
for (const tool of tools) {
|
|
4865
|
-
this.
|
|
4915
|
+
const existing = this.tools.get(tool.name);
|
|
4916
|
+
const existingSource = this.toolSources.get(tool.name);
|
|
4917
|
+
if (existing && existingSource !== source) {
|
|
4918
|
+
if (this.conflictPolicy === "error") {
|
|
4919
|
+
throw new Error(
|
|
4920
|
+
`Tool name conflict: candidate '${tool.name}' from '${source}' conflicts with existing tool registered by '${existingSource}'`
|
|
4921
|
+
);
|
|
4922
|
+
}
|
|
4923
|
+
if (this.conflictPolicy === "prefer-builtin" && existingSource === "builtin") {
|
|
4924
|
+
throw new Error(
|
|
4925
|
+
`Tool name conflict: candidate '${tool.name}' from '${source}' cannot override builtin tool`
|
|
4926
|
+
);
|
|
4927
|
+
}
|
|
4928
|
+
}
|
|
4866
4929
|
}
|
|
4930
|
+
for (const [name, s] of this.toolSources.entries()) {
|
|
4931
|
+
if (s === source) {
|
|
4932
|
+
this.tools.delete(name);
|
|
4933
|
+
this.toolSources.delete(name);
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
for (const tool of tools) {
|
|
4937
|
+
this.tools.set(tool.name, tool);
|
|
4938
|
+
this.toolSources.set(tool.name, source);
|
|
4939
|
+
}
|
|
4940
|
+
this.version++;
|
|
4941
|
+
}
|
|
4942
|
+
/**
|
|
4943
|
+
* 注册指定来源的工具集。
|
|
4944
|
+
*
|
|
4945
|
+
* 语义与约束:
|
|
4946
|
+
* 1. 委托给 `replaceSource` 实现整源原子替换(新集合未包含的该来源旧工具将被注销移除,非增量累加);
|
|
4947
|
+
* 2. 默认禁止跨来源名称冲突(抛出 KapibalaError TOOL_CONFLICT);若 options 配置了 conflictPolicy: 'prefer-last' 则允许覆盖旧来源同名工具;
|
|
4948
|
+
* 3. 预检阶段发现批内同名重复时整批拒绝,绝不破坏注册表原子状态。
|
|
4949
|
+
*/
|
|
4950
|
+
registerSource(source, tools) {
|
|
4951
|
+
this.replaceSource(source, tools);
|
|
4867
4952
|
}
|
|
4868
4953
|
unregisterSource(source) {
|
|
4954
|
+
let changed = false;
|
|
4869
4955
|
for (const [name, s] of this.toolSources.entries()) {
|
|
4870
4956
|
if (s === source) {
|
|
4871
4957
|
this.tools.delete(name);
|
|
4872
4958
|
this.toolSources.delete(name);
|
|
4959
|
+
changed = true;
|
|
4873
4960
|
}
|
|
4874
4961
|
}
|
|
4962
|
+
if (changed) {
|
|
4963
|
+
this.version++;
|
|
4964
|
+
}
|
|
4875
4965
|
}
|
|
4876
4966
|
get(name) {
|
|
4877
4967
|
return this.tools.get(name);
|
|
4878
4968
|
}
|
|
4969
|
+
has(name) {
|
|
4970
|
+
return this.tools.has(name);
|
|
4971
|
+
}
|
|
4972
|
+
/** 获取工具对应的来源标记。 */
|
|
4973
|
+
sourceOf(name) {
|
|
4974
|
+
return this.toolSources.get(name);
|
|
4975
|
+
}
|
|
4879
4976
|
/** 注销指定名称及来源记录;调用方须核对实例身份,不能移除同名替代工具。 */
|
|
4880
4977
|
unregister(name) {
|
|
4881
|
-
this.tools.
|
|
4882
|
-
|
|
4978
|
+
if (this.tools.has(name)) {
|
|
4979
|
+
this.tools.delete(name);
|
|
4980
|
+
this.toolSources.delete(name);
|
|
4981
|
+
this.version++;
|
|
4982
|
+
}
|
|
4883
4983
|
}
|
|
4884
4984
|
/** 未命中时抛 ToolNotFound,并携带当前全部可用名称辅助模型自纠。 */
|
|
4885
4985
|
resolve(name) {
|
|
@@ -4895,6 +4995,30 @@ var ToolRegistry = class {
|
|
|
4895
4995
|
definitions() {
|
|
4896
4996
|
return this.list().map(toToolDefinition);
|
|
4897
4997
|
}
|
|
4998
|
+
/**
|
|
4999
|
+
* 捕获当前注册表的不可变只读快照。
|
|
5000
|
+
* 注册表后续的增删改绝不改变该快照的定义列表、工具执行实例与来源版本。
|
|
5001
|
+
*/
|
|
5002
|
+
snapshot() {
|
|
5003
|
+
const snapshotTools = new Map(this.tools);
|
|
5004
|
+
const snapshotSources = new Map(this.toolSources);
|
|
5005
|
+
const snapshotVersion = this.version;
|
|
5006
|
+
return {
|
|
5007
|
+
version: snapshotVersion,
|
|
5008
|
+
get: (name) => snapshotTools.get(name),
|
|
5009
|
+
has: (name) => snapshotTools.has(name),
|
|
5010
|
+
resolve: (name) => {
|
|
5011
|
+
const tool = snapshotTools.get(name);
|
|
5012
|
+
if (!tool) {
|
|
5013
|
+
throw new ToolNotFound(name, Array.from(snapshotTools.keys()));
|
|
5014
|
+
}
|
|
5015
|
+
return tool;
|
|
5016
|
+
},
|
|
5017
|
+
list: () => Array.from(snapshotTools.values()),
|
|
5018
|
+
definitions: () => Array.from(snapshotTools.values()).map(toToolDefinition),
|
|
5019
|
+
sourceOf: (name) => snapshotSources.get(name)
|
|
5020
|
+
};
|
|
5021
|
+
}
|
|
4898
5022
|
};
|
|
4899
5023
|
|
|
4900
5024
|
// src/capabilities/tools/builtin/fs.ts
|
|
@@ -5370,14 +5494,18 @@ var ToolExecutor = class {
|
|
|
5370
5494
|
call.executionRoot = path15.resolve(this.rootDir);
|
|
5371
5495
|
const startedAt = Date.now();
|
|
5372
5496
|
const operationId = createLogId();
|
|
5373
|
-
const failure = (message, errorCode, retryPolicy = "never") => ({
|
|
5497
|
+
const failure = (message, errorCode, retryPolicy = "never", execution) => ({
|
|
5374
5498
|
type: "tool_result",
|
|
5375
5499
|
toolUseId: call.id,
|
|
5376
5500
|
content: JSON.stringify({ code: errorCode, retryPolicy, message }),
|
|
5377
5501
|
isError: true,
|
|
5378
5502
|
errorCode,
|
|
5379
5503
|
retryPolicy,
|
|
5380
|
-
durationMs: Math.max(0, Date.now() - startedAt)
|
|
5504
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
5505
|
+
execution: execution ?? {
|
|
5506
|
+
version: 1,
|
|
5507
|
+
dispatchOutcome: "not_sent"
|
|
5508
|
+
}
|
|
5381
5509
|
});
|
|
5382
5510
|
const audit = async (event, fields = {}) => {
|
|
5383
5511
|
if (!this.eventLogger || !this.sessionId) return;
|
|
@@ -5397,7 +5525,12 @@ var ToolExecutor = class {
|
|
|
5397
5525
|
return failure(
|
|
5398
5526
|
"Audit unavailable; tool was not started",
|
|
5399
5527
|
"AUDIT_UNAVAILABLE",
|
|
5400
|
-
"after_user_action"
|
|
5528
|
+
"after_user_action",
|
|
5529
|
+
{
|
|
5530
|
+
version: 1,
|
|
5531
|
+
dispatchOutcome: "not_sent",
|
|
5532
|
+
hostError: { phase: "audit", code: "AUDIT_UNAVAILABLE" }
|
|
5533
|
+
}
|
|
5401
5534
|
);
|
|
5402
5535
|
}
|
|
5403
5536
|
if (forcedDeny) {
|
|
@@ -5408,7 +5541,12 @@ var ToolExecutor = class {
|
|
|
5408
5541
|
return failure(
|
|
5409
5542
|
"Audit unavailable; tool was not started",
|
|
5410
5543
|
"AUDIT_UNAVAILABLE",
|
|
5411
|
-
"after_user_action"
|
|
5544
|
+
"after_user_action",
|
|
5545
|
+
{
|
|
5546
|
+
version: 1,
|
|
5547
|
+
dispatchOutcome: "not_sent",
|
|
5548
|
+
hostError: { phase: "audit", code: "AUDIT_UNAVAILABLE" }
|
|
5549
|
+
}
|
|
5412
5550
|
);
|
|
5413
5551
|
}
|
|
5414
5552
|
return failure(
|
|
@@ -5431,7 +5569,11 @@ var ToolExecutor = class {
|
|
|
5431
5569
|
toolUseId: call.id,
|
|
5432
5570
|
content: decision.result,
|
|
5433
5571
|
isError: decision.isError ?? false,
|
|
5434
|
-
durationMs: Math.max(0, Date.now() - startedAt)
|
|
5572
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
5573
|
+
execution: {
|
|
5574
|
+
version: 1,
|
|
5575
|
+
dispatchOutcome: "not_sent"
|
|
5576
|
+
}
|
|
5435
5577
|
};
|
|
5436
5578
|
}
|
|
5437
5579
|
}
|
|
@@ -5441,8 +5583,23 @@ var ToolExecutor = class {
|
|
|
5441
5583
|
await audit("tool.finished", { status: "failed" });
|
|
5442
5584
|
} catch {
|
|
5443
5585
|
}
|
|
5444
|
-
return failure("Tool preparation failed", "HOOK_ERROR"
|
|
5586
|
+
return failure("Tool preparation failed", "HOOK_ERROR", "never", {
|
|
5587
|
+
version: 1,
|
|
5588
|
+
dispatchOutcome: "not_sent",
|
|
5589
|
+
hostError: { phase: "hook", code: "HOOK_ERROR" }
|
|
5590
|
+
});
|
|
5445
5591
|
}
|
|
5592
|
+
function deepFreeze(obj) {
|
|
5593
|
+
if (obj === null || typeof obj !== "object" || Object.isFrozen(obj)) {
|
|
5594
|
+
return obj;
|
|
5595
|
+
}
|
|
5596
|
+
Object.freeze(obj);
|
|
5597
|
+
for (const key of Object.keys(obj)) {
|
|
5598
|
+
deepFreeze(obj[key]);
|
|
5599
|
+
}
|
|
5600
|
+
return obj;
|
|
5601
|
+
}
|
|
5602
|
+
const finalInput = deepFreeze(structuredClone(currentInput));
|
|
5446
5603
|
let tool;
|
|
5447
5604
|
try {
|
|
5448
5605
|
tool = this.tools.resolve(call.name);
|
|
@@ -5455,13 +5612,50 @@ var ToolExecutor = class {
|
|
|
5455
5612
|
}
|
|
5456
5613
|
return failure(
|
|
5457
5614
|
known?.safeMessage ?? "Requested tool is unavailable",
|
|
5458
|
-
known?.code ?? "TOOL_NOT_FOUND"
|
|
5615
|
+
known?.code ?? "TOOL_NOT_FOUND",
|
|
5616
|
+
"never",
|
|
5617
|
+
{
|
|
5618
|
+
version: 1,
|
|
5619
|
+
dispatchOutcome: "not_sent"
|
|
5620
|
+
}
|
|
5459
5621
|
);
|
|
5460
5622
|
}
|
|
5623
|
+
try {
|
|
5624
|
+
const validation = tool.validateInput?.(finalInput);
|
|
5625
|
+
if (validation && !validation.valid) {
|
|
5626
|
+
try {
|
|
5627
|
+
await audit("tool.decided", { decision: "deny", decisionSource: "validation" });
|
|
5628
|
+
await audit("tool.finished", { status: "failed", errorCode: "INVALID_INPUT" });
|
|
5629
|
+
} catch {
|
|
5630
|
+
}
|
|
5631
|
+
return failure(
|
|
5632
|
+
validation.message || "Tool input validation failed",
|
|
5633
|
+
"INVALID_INPUT",
|
|
5634
|
+
"never",
|
|
5635
|
+
{
|
|
5636
|
+
version: 1,
|
|
5637
|
+
dispatchOutcome: "not_sent",
|
|
5638
|
+
hostError: { phase: "validation", code: "INVALID_INPUT" }
|
|
5639
|
+
}
|
|
5640
|
+
);
|
|
5641
|
+
}
|
|
5642
|
+
} catch (err) {
|
|
5643
|
+
try {
|
|
5644
|
+
await audit("tool.decided", { decision: "deny", decisionSource: "validation" });
|
|
5645
|
+
await audit("tool.finished", { status: "failed", errorCode: "INVALID_INPUT" });
|
|
5646
|
+
} catch {
|
|
5647
|
+
}
|
|
5648
|
+
const msg = err instanceof Error ? err.message : "Tool input validation error";
|
|
5649
|
+
return failure(msg || "Tool input validation error", "INVALID_INPUT", "never", {
|
|
5650
|
+
version: 1,
|
|
5651
|
+
dispatchOutcome: "not_sent",
|
|
5652
|
+
hostError: { phase: "validation", code: "INVALID_INPUT" }
|
|
5653
|
+
});
|
|
5654
|
+
}
|
|
5461
5655
|
try {
|
|
5462
5656
|
return await this.executeResolved(
|
|
5463
5657
|
call,
|
|
5464
|
-
|
|
5658
|
+
finalInput,
|
|
5465
5659
|
tool,
|
|
5466
5660
|
startedAt,
|
|
5467
5661
|
audit,
|
|
@@ -5477,14 +5671,18 @@ var ToolExecutor = class {
|
|
|
5477
5671
|
return failure(
|
|
5478
5672
|
"Tool outcome is unknown after an executor failure",
|
|
5479
5673
|
"OUTCOME_UNKNOWN",
|
|
5480
|
-
"after_user_action"
|
|
5674
|
+
"after_user_action",
|
|
5675
|
+
{
|
|
5676
|
+
version: 1,
|
|
5677
|
+
dispatchOutcome: "unknown"
|
|
5678
|
+
}
|
|
5481
5679
|
);
|
|
5482
5680
|
}
|
|
5483
5681
|
}
|
|
5484
|
-
async executeResolved(call,
|
|
5682
|
+
async executeResolved(call, finalInput, tool, startedAt, audit, failure, hookCtx, onProgress) {
|
|
5485
5683
|
let shell;
|
|
5486
5684
|
try {
|
|
5487
|
-
shell = tool.approvalScope?.(
|
|
5685
|
+
shell = tool.approvalScope?.(finalInput, this.rootDir);
|
|
5488
5686
|
} catch {
|
|
5489
5687
|
try {
|
|
5490
5688
|
await audit("tool.decided", { decision: "deny", decisionSource: "invalid_target" });
|
|
@@ -5493,19 +5691,32 @@ var ToolExecutor = class {
|
|
|
5493
5691
|
}
|
|
5494
5692
|
return failure("Invalid tool execution target", "INVALID_TARGET");
|
|
5495
5693
|
}
|
|
5694
|
+
let executionIdentity;
|
|
5695
|
+
try {
|
|
5696
|
+
executionIdentity = tool.executionIdentity?.(finalInput, this.rootDir);
|
|
5697
|
+
} catch {
|
|
5698
|
+
try {
|
|
5699
|
+
await audit("tool.decided", { decision: "deny", decisionSource: "invalid_identity" });
|
|
5700
|
+
await audit("tool.finished", { status: "failed", errorCode: "INVALID_TARGET" });
|
|
5701
|
+
} catch {
|
|
5702
|
+
}
|
|
5703
|
+
return failure("Invalid tool execution identity", "INVALID_TARGET");
|
|
5704
|
+
}
|
|
5496
5705
|
let auditFields = {};
|
|
5497
5706
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5498
5707
|
const request = {
|
|
5499
5708
|
toolName: call.name,
|
|
5500
5709
|
capabilities: tool.metadata?.permissions ?? [],
|
|
5501
|
-
input:
|
|
5710
|
+
input: finalInput,
|
|
5502
5711
|
rootDir: this.rootDir,
|
|
5503
5712
|
...shell ? { shell } : {},
|
|
5713
|
+
...executionIdentity ? { executionIdentity } : {},
|
|
5504
5714
|
// 解释器内建查询与内容已绑定摘要的工作区直引脚本可确认不受外部脚本变更影响,
|
|
5505
5715
|
// 允许会话级批准;其余 shell 命令仅允许单次批准。
|
|
5716
|
+
// 非 shell 工具(含深度绑定 executionIdentity 的外部工具)允许会话级批准。
|
|
5506
5717
|
sessionAllowed: !shell || /^(?:pwd|get-location)$/i.test(shell.command.trim()) || shell.scriptDigest !== void 0
|
|
5507
5718
|
};
|
|
5508
|
-
const repeatKey = createHash11("sha256").update(stableJsonStringify([call.name,
|
|
5719
|
+
const repeatKey = createHash11("sha256").update(stableJsonStringify([call.name, finalInput])).digest("hex");
|
|
5509
5720
|
if (this.neverFailures.has(repeatKey)) {
|
|
5510
5721
|
try {
|
|
5511
5722
|
await audit("tool.decided", { decision: "deny", decisionSource: "repeat_block" });
|
|
@@ -5514,7 +5725,12 @@ var ToolExecutor = class {
|
|
|
5514
5725
|
return failure(
|
|
5515
5726
|
"Audit unavailable; tool was not started",
|
|
5516
5727
|
"AUDIT_UNAVAILABLE",
|
|
5517
|
-
"after_user_action"
|
|
5728
|
+
"after_user_action",
|
|
5729
|
+
{
|
|
5730
|
+
version: 1,
|
|
5731
|
+
dispatchOutcome: "not_sent",
|
|
5732
|
+
hostError: { phase: "audit", code: "AUDIT_UNAVAILABLE" }
|
|
5733
|
+
}
|
|
5518
5734
|
);
|
|
5519
5735
|
}
|
|
5520
5736
|
return failure(
|
|
@@ -5522,7 +5738,7 @@ var ToolExecutor = class {
|
|
|
5522
5738
|
"REPEAT_BLOCKED"
|
|
5523
5739
|
);
|
|
5524
5740
|
}
|
|
5525
|
-
const hardDenied = this.hardDenial?.(tool,
|
|
5741
|
+
const hardDenied = this.hardDenial?.(tool, finalInput);
|
|
5526
5742
|
const cached = this.approvalCache.get(request);
|
|
5527
5743
|
const decision = this.policy.decide({
|
|
5528
5744
|
hardDenied: hardDenied !== void 0,
|
|
@@ -5558,6 +5774,11 @@ var ToolExecutor = class {
|
|
|
5558
5774
|
executableDigest: shell.executableDigest,
|
|
5559
5775
|
executionFingerprint: shell.executionFingerprint,
|
|
5560
5776
|
...commandAuditSummary(shell.command)
|
|
5777
|
+
} : executionIdentity ? {
|
|
5778
|
+
serverIdentity: executionIdentity.serverIdentity,
|
|
5779
|
+
connectionGeneration: executionIdentity.connectionGeneration,
|
|
5780
|
+
catalogEpoch: executionIdentity.catalogEpoch,
|
|
5781
|
+
toolDigest: executionIdentity.toolDigest
|
|
5561
5782
|
} : {};
|
|
5562
5783
|
try {
|
|
5563
5784
|
await audit("tool.decided", {
|
|
@@ -5571,7 +5792,12 @@ var ToolExecutor = class {
|
|
|
5571
5792
|
return failure(
|
|
5572
5793
|
"Audit unavailable; tool was not started",
|
|
5573
5794
|
"AUDIT_UNAVAILABLE",
|
|
5574
|
-
"after_user_action"
|
|
5795
|
+
"after_user_action",
|
|
5796
|
+
{
|
|
5797
|
+
version: 1,
|
|
5798
|
+
dispatchOutcome: "not_sent",
|
|
5799
|
+
hostError: { phase: "audit", code: "AUDIT_UNAVAILABLE" }
|
|
5800
|
+
}
|
|
5575
5801
|
);
|
|
5576
5802
|
}
|
|
5577
5803
|
if (!permitted || this.signal?.aborted) {
|
|
@@ -5579,7 +5805,11 @@ var ToolExecutor = class {
|
|
|
5579
5805
|
try {
|
|
5580
5806
|
await audit("tool.finished", { status: "denied", errorCode: code });
|
|
5581
5807
|
} catch {
|
|
5582
|
-
return failure("Audit result unavailable", "AUDIT_UNAVAILABLE", "after_user_action"
|
|
5808
|
+
return failure("Audit result unavailable", "AUDIT_UNAVAILABLE", "after_user_action", {
|
|
5809
|
+
version: 1,
|
|
5810
|
+
dispatchOutcome: "not_sent",
|
|
5811
|
+
hostError: { phase: "audit", code: "AUDIT_UNAVAILABLE" }
|
|
5812
|
+
});
|
|
5583
5813
|
}
|
|
5584
5814
|
return failure(
|
|
5585
5815
|
this.signal?.aborted ? "Operation cancelled" : hardDenied?.message ?? "Tool permission denied",
|
|
@@ -5589,7 +5819,7 @@ var ToolExecutor = class {
|
|
|
5589
5819
|
}
|
|
5590
5820
|
if (shell) {
|
|
5591
5821
|
try {
|
|
5592
|
-
const next = tool.approvalScope?.(
|
|
5822
|
+
const next = tool.approvalScope?.(finalInput, this.rootDir);
|
|
5593
5823
|
if (next?.executionFingerprint !== shell.executionFingerprint) {
|
|
5594
5824
|
if (attempt === 0 && next) {
|
|
5595
5825
|
try {
|
|
@@ -5598,7 +5828,12 @@ var ToolExecutor = class {
|
|
|
5598
5828
|
return failure(
|
|
5599
5829
|
"Audit unavailable; tool was not started",
|
|
5600
5830
|
"AUDIT_UNAVAILABLE",
|
|
5601
|
-
"after_user_action"
|
|
5831
|
+
"after_user_action",
|
|
5832
|
+
{
|
|
5833
|
+
version: 1,
|
|
5834
|
+
dispatchOutcome: "not_sent",
|
|
5835
|
+
hostError: { phase: "audit", code: "AUDIT_UNAVAILABLE" }
|
|
5836
|
+
}
|
|
5602
5837
|
);
|
|
5603
5838
|
}
|
|
5604
5839
|
shell = next;
|
|
@@ -5619,6 +5854,37 @@ var ToolExecutor = class {
|
|
|
5619
5854
|
);
|
|
5620
5855
|
}
|
|
5621
5856
|
}
|
|
5857
|
+
if (executionIdentity) {
|
|
5858
|
+
try {
|
|
5859
|
+
const nextIdentity = tool.executionIdentity?.(finalInput, this.rootDir);
|
|
5860
|
+
if (!nextIdentity || nextIdentity.serverIdentity !== executionIdentity.serverIdentity || nextIdentity.connectionGeneration !== executionIdentity.connectionGeneration || nextIdentity.catalogEpoch !== executionIdentity.catalogEpoch || nextIdentity.toolDigest !== executionIdentity.toolDigest) {
|
|
5861
|
+
await audit("tool.finished", {
|
|
5862
|
+
status: "identity_changed",
|
|
5863
|
+
errorCode: "MCP_TOOL_CHANGED"
|
|
5864
|
+
});
|
|
5865
|
+
return failure(
|
|
5866
|
+
"Tool execution identity changed after approval",
|
|
5867
|
+
"MCP_TOOL_CHANGED",
|
|
5868
|
+
"after_user_action",
|
|
5869
|
+
{
|
|
5870
|
+
version: 1,
|
|
5871
|
+
dispatchOutcome: "not_sent",
|
|
5872
|
+
hostError: { phase: "validation", code: "MCP_TOOL_CHANGED" }
|
|
5873
|
+
}
|
|
5874
|
+
);
|
|
5875
|
+
}
|
|
5876
|
+
} catch {
|
|
5877
|
+
return failure(
|
|
5878
|
+
"Tool execution identity check failed after approval",
|
|
5879
|
+
"MCP_TOOL_CHANGED",
|
|
5880
|
+
"after_user_action",
|
|
5881
|
+
{
|
|
5882
|
+
version: 1,
|
|
5883
|
+
dispatchOutcome: "not_sent"
|
|
5884
|
+
}
|
|
5885
|
+
);
|
|
5886
|
+
}
|
|
5887
|
+
}
|
|
5622
5888
|
break;
|
|
5623
5889
|
}
|
|
5624
5890
|
try {
|
|
@@ -5627,13 +5893,19 @@ var ToolExecutor = class {
|
|
|
5627
5893
|
return failure(
|
|
5628
5894
|
"Audit unavailable; tool was not started",
|
|
5629
5895
|
"AUDIT_UNAVAILABLE",
|
|
5630
|
-
"after_user_action"
|
|
5896
|
+
"after_user_action",
|
|
5897
|
+
{
|
|
5898
|
+
version: 1,
|
|
5899
|
+
dispatchOutcome: "not_sent",
|
|
5900
|
+
hostError: { phase: "audit", code: "AUDIT_UNAVAILABLE" }
|
|
5901
|
+
}
|
|
5631
5902
|
);
|
|
5632
5903
|
}
|
|
5633
5904
|
let outputStr;
|
|
5634
5905
|
let isError = false;
|
|
5635
5906
|
let errorCode;
|
|
5636
5907
|
let retryPolicy;
|
|
5908
|
+
let execution;
|
|
5637
5909
|
try {
|
|
5638
5910
|
const executionController = new AbortController();
|
|
5639
5911
|
const abortFromSession = () => executionController.abort();
|
|
@@ -5652,16 +5924,52 @@ var ToolExecutor = class {
|
|
|
5652
5924
|
} : {}
|
|
5653
5925
|
};
|
|
5654
5926
|
try {
|
|
5655
|
-
const rawResult = tool.metadata?.managesTimeout ? await tool.execute(
|
|
5656
|
-
tool.execute(
|
|
5927
|
+
const rawResult = tool.metadata?.managesTimeout ? await tool.execute(finalInput, toolCtx) : await this.withTimeout(
|
|
5928
|
+
tool.execute(finalInput, toolCtx),
|
|
5657
5929
|
call.name,
|
|
5658
5930
|
executionController
|
|
5659
5931
|
);
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5932
|
+
if (typeof tool.toExecutionResult === "function") {
|
|
5933
|
+
try {
|
|
5934
|
+
const converted = tool.toExecutionResult(rawResult);
|
|
5935
|
+
outputStr = converted.content;
|
|
5936
|
+
isError = converted.isError ?? false;
|
|
5937
|
+
errorCode = converted.errorCode;
|
|
5938
|
+
retryPolicy = converted.retryPolicy;
|
|
5939
|
+
execution = converted.execution ?? {
|
|
5940
|
+
version: 1,
|
|
5941
|
+
dispatchOutcome: isError ? "failed" : "succeeded"
|
|
5942
|
+
};
|
|
5943
|
+
} catch (convErr) {
|
|
5944
|
+
isError = true;
|
|
5945
|
+
errorCode = "MCP_RESULT_INVALID";
|
|
5946
|
+
retryPolicy = "after_user_action";
|
|
5947
|
+
outputStr = convErr instanceof Error ? convErr.message : "Tool result projection failed";
|
|
5948
|
+
execution = {
|
|
5949
|
+
version: 1,
|
|
5950
|
+
dispatchOutcome: "succeeded",
|
|
5951
|
+
hostError: {
|
|
5952
|
+
phase: "projection",
|
|
5953
|
+
code: "MCP_RESULT_INVALID"
|
|
5954
|
+
}
|
|
5955
|
+
};
|
|
5956
|
+
}
|
|
5957
|
+
} else {
|
|
5958
|
+
outputStr = typeof rawResult === "string" ? rawResult : JSON.stringify(rawResult, null, 2);
|
|
5959
|
+
if (call.name === "run_command" && typeof rawResult === "object" && rawResult !== null && "exitCode" in rawResult && rawResult.exitCode !== 0) {
|
|
5960
|
+
isError = true;
|
|
5961
|
+
errorCode = "COMMAND_EXIT_NONZERO";
|
|
5962
|
+
retryPolicy = "never";
|
|
5963
|
+
execution = {
|
|
5964
|
+
version: 1,
|
|
5965
|
+
dispatchOutcome: "failed"
|
|
5966
|
+
};
|
|
5967
|
+
} else {
|
|
5968
|
+
execution = {
|
|
5969
|
+
version: 1,
|
|
5970
|
+
dispatchOutcome: "succeeded"
|
|
5971
|
+
};
|
|
5972
|
+
}
|
|
5665
5973
|
}
|
|
5666
5974
|
} finally {
|
|
5667
5975
|
this.signal?.removeEventListener("abort", abortFromSession);
|
|
@@ -5672,13 +5980,19 @@ var ToolExecutor = class {
|
|
|
5672
5980
|
outputStr = known?.safeMessage ?? "Tool execution failed";
|
|
5673
5981
|
errorCode = known?.code ?? "TOOL_FAILED";
|
|
5674
5982
|
retryPolicy = known?.retryPolicy ?? "never";
|
|
5983
|
+
const outcome = known?.dispatchOutcome ?? (errorCode === "OUTCOME_UNKNOWN" || known && known.retryPolicy === "after_user_action" ? "unknown" : "failed");
|
|
5984
|
+
execution = {
|
|
5985
|
+
version: 1,
|
|
5986
|
+
dispatchOutcome: outcome
|
|
5987
|
+
};
|
|
5675
5988
|
}
|
|
5676
5989
|
const resultBlock = {
|
|
5677
5990
|
type: "tool_result",
|
|
5678
5991
|
toolUseId: call.id,
|
|
5679
5992
|
content: outputStr,
|
|
5680
5993
|
isError,
|
|
5681
|
-
...errorCode ? { errorCode, retryPolicy } : {}
|
|
5994
|
+
...errorCode ? { errorCode, retryPolicy } : {},
|
|
5995
|
+
execution
|
|
5682
5996
|
};
|
|
5683
5997
|
if (resultBlock.isError && resultBlock.errorCode) {
|
|
5684
5998
|
resultBlock.content = JSON.stringify({
|
|
@@ -5691,7 +6005,7 @@ var ToolExecutor = class {
|
|
|
5691
6005
|
for (const hook of this.hooks.get("tool:after")) {
|
|
5692
6006
|
await hook(
|
|
5693
6007
|
hookCtx,
|
|
5694
|
-
{ id: call.id, name: call.name, input:
|
|
6008
|
+
{ id: call.id, name: call.name, input: finalInput },
|
|
5695
6009
|
{ output: outputStr, isError }
|
|
5696
6010
|
);
|
|
5697
6011
|
}
|
|
@@ -5704,11 +6018,19 @@ var ToolExecutor = class {
|
|
|
5704
6018
|
resultBlock.isError = true;
|
|
5705
6019
|
resultBlock.errorCode = "HOOK_ERROR";
|
|
5706
6020
|
resultBlock.retryPolicy = "after_user_action";
|
|
6021
|
+
resultBlock.execution = {
|
|
6022
|
+
version: 1,
|
|
6023
|
+
dispatchOutcome: resultBlock.execution?.dispatchOutcome ?? "failed",
|
|
6024
|
+
hostError: {
|
|
6025
|
+
phase: "hook",
|
|
6026
|
+
code: "HOOK_ERROR"
|
|
6027
|
+
}
|
|
6028
|
+
};
|
|
5707
6029
|
}
|
|
5708
6030
|
resultBlock.durationMs = Math.max(0, Date.now() - startedAt);
|
|
5709
6031
|
if (resultBlock.isError && resultBlock.retryPolicy === "never") {
|
|
5710
6032
|
this.neverFailures.add(
|
|
5711
|
-
createHash11("sha256").update(stableJsonStringify([call.name,
|
|
6033
|
+
createHash11("sha256").update(stableJsonStringify([call.name, finalInput])).digest("hex")
|
|
5712
6034
|
);
|
|
5713
6035
|
}
|
|
5714
6036
|
try {
|
|
@@ -5718,13 +6040,25 @@ var ToolExecutor = class {
|
|
|
5718
6040
|
durationMs: resultBlock.durationMs
|
|
5719
6041
|
});
|
|
5720
6042
|
} catch {
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
6043
|
+
resultBlock.isError = true;
|
|
6044
|
+
resultBlock.errorCode = "OUTCOME_UNKNOWN";
|
|
6045
|
+
resultBlock.retryPolicy = "after_user_action";
|
|
6046
|
+
resultBlock.content = JSON.stringify({
|
|
6047
|
+
code: "OUTCOME_UNKNOWN",
|
|
6048
|
+
retryPolicy: "after_user_action",
|
|
6049
|
+
message: "Tool outcome is unknown because the audit result could not be written"
|
|
6050
|
+
});
|
|
6051
|
+
resultBlock.execution = {
|
|
6052
|
+
version: 1,
|
|
6053
|
+
dispatchOutcome: resultBlock.execution?.dispatchOutcome ?? "succeeded",
|
|
6054
|
+
hostError: {
|
|
6055
|
+
phase: "audit",
|
|
6056
|
+
code: "AUDIT_UNAVAILABLE"
|
|
6057
|
+
}
|
|
6058
|
+
};
|
|
6059
|
+
return resultBlock;
|
|
5726
6060
|
}
|
|
5727
|
-
await this.onToolResult?.(tool,
|
|
6061
|
+
await this.onToolResult?.(tool, finalInput, resultBlock);
|
|
5728
6062
|
return resultBlock;
|
|
5729
6063
|
}
|
|
5730
6064
|
/**
|
|
@@ -7499,6 +7833,7 @@ var AgentLoop = class {
|
|
|
7499
7833
|
let consecutiveErrors = 0;
|
|
7500
7834
|
let completed = false;
|
|
7501
7835
|
let overflowRetried = false;
|
|
7836
|
+
const toolSnapshot = "snapshot" in this.tools ? this.tools.snapshot() : this.tools;
|
|
7502
7837
|
while (step < this.maxSteps) {
|
|
7503
7838
|
step++;
|
|
7504
7839
|
const turnStartTime = Date.now();
|
|
@@ -7509,7 +7844,7 @@ var AgentLoop = class {
|
|
|
7509
7844
|
let request = {
|
|
7510
7845
|
systemPrompt: this.systemPrompt,
|
|
7511
7846
|
messages: structuredClone(this.projectHistory?.(history) ?? history),
|
|
7512
|
-
tools: visibleTools(
|
|
7847
|
+
tools: visibleTools(toolSnapshot.list(), this.getMode(), this.permissionRules).map(
|
|
7513
7848
|
toToolDefinition
|
|
7514
7849
|
),
|
|
7515
7850
|
signal: this.signal,
|
|
@@ -7518,7 +7853,30 @@ var AgentLoop = class {
|
|
|
7518
7853
|
const beforeModelHooks = this.hooks.get("model:before");
|
|
7519
7854
|
const hookCtx = { signal: this.signal };
|
|
7520
7855
|
for (const hook of beforeModelHooks) {
|
|
7521
|
-
|
|
7856
|
+
const modified = await hook(hookCtx, request);
|
|
7857
|
+
if (modified) {
|
|
7858
|
+
request = modified;
|
|
7859
|
+
}
|
|
7860
|
+
}
|
|
7861
|
+
if (request.tools) {
|
|
7862
|
+
for (const t of request.tools) {
|
|
7863
|
+
const original = toolSnapshot.get(t.name);
|
|
7864
|
+
if (!original) {
|
|
7865
|
+
throw new ModelError(`Hook injected tool '${t.name}' not present in snapshot`, {
|
|
7866
|
+
stage: "request",
|
|
7867
|
+
retryable: false
|
|
7868
|
+
});
|
|
7869
|
+
}
|
|
7870
|
+
if (t.description !== original.description || JSON.stringify(t.parameters) !== JSON.stringify(original.parameters)) {
|
|
7871
|
+
throw new ModelError(
|
|
7872
|
+
`Hook mutated tool definition for '${t.name}' (description or parameters drifted from snapshot)`,
|
|
7873
|
+
{
|
|
7874
|
+
stage: "request",
|
|
7875
|
+
retryable: false
|
|
7876
|
+
}
|
|
7877
|
+
);
|
|
7878
|
+
}
|
|
7879
|
+
}
|
|
7522
7880
|
}
|
|
7523
7881
|
if (this.prepareRequest) request = yield* this.prepareRequest(request, history);
|
|
7524
7882
|
yield {
|
|
@@ -8258,6 +8616,14 @@ var AgentSession = class {
|
|
|
8258
8616
|
});
|
|
8259
8617
|
this.plugins.push(plugin);
|
|
8260
8618
|
}
|
|
8619
|
+
/**
|
|
8620
|
+
* 获取当前会话已成功挂载的扩展插件只读列表。
|
|
8621
|
+
*
|
|
8622
|
+
* 保证返回当前状态的独立切片,外部修改不污染内部挂载表。
|
|
8623
|
+
*/
|
|
8624
|
+
getPlugins() {
|
|
8625
|
+
return [...this.plugins];
|
|
8626
|
+
}
|
|
8261
8627
|
/**
|
|
8262
8628
|
* 空闲时原子替换角色的模型绑定;本方法不改变当前角色或工具权限。
|
|
8263
8629
|
* @param profile 模型协议、请求目标和输出预算。
|
|
@@ -8324,6 +8690,10 @@ var AgentSession = class {
|
|
|
8324
8690
|
getMode() {
|
|
8325
8691
|
return this.mode;
|
|
8326
8692
|
}
|
|
8693
|
+
/** 获取当前会话生效的权限规则列表。 */
|
|
8694
|
+
getPermissionRules() {
|
|
8695
|
+
return this.permissionRules;
|
|
8696
|
+
}
|
|
8327
8697
|
/** 返回最近一次成功装载的来源路径,不暴露指令正文。 */
|
|
8328
8698
|
getInstructionSources() {
|
|
8329
8699
|
return this.instructionSnapshot?.sources.map((source) => source.path) ?? [];
|
|
@@ -8452,8 +8822,23 @@ var AgentSession = class {
|
|
|
8452
8822
|
options?.signal
|
|
8453
8823
|
);
|
|
8454
8824
|
}
|
|
8825
|
+
/**
|
|
8826
|
+
* 受控任务准备入口:在同一运行锁内异步准备文本块/来源,预算和存储成功后才进入模型。
|
|
8827
|
+
*
|
|
8828
|
+
* @param task 用户显式任务正文(或命令描述)。
|
|
8829
|
+
* @param prepare 异步任务准备函数;在锁内执行,若准备失败或取消则释放锁且零 append。
|
|
8830
|
+
* @param options 可选中止信号与显式主任务角色。
|
|
8831
|
+
* @returns 结构化事件的异步流。
|
|
8832
|
+
* @throws {SessionBusyError} 当前会话忙碌时抛出。
|
|
8833
|
+
*/
|
|
8834
|
+
runPreparedTask(task, prepare, options) {
|
|
8835
|
+
return cancellableGenerator(
|
|
8836
|
+
(signal) => this.runOperation(task, { signal, role: options?.role }, void 0, prepare),
|
|
8837
|
+
options?.signal
|
|
8838
|
+
);
|
|
8839
|
+
}
|
|
8455
8840
|
/** 单次任务执行主体;调用方 return 的中止与 finally 清理由外层统一协调。 */
|
|
8456
|
-
async *runOperation(userInput, options, skillName) {
|
|
8841
|
+
async *runOperation(userInput, options, skillName, prepare) {
|
|
8457
8842
|
this.assertIdle("start another run");
|
|
8458
8843
|
const role = options?.role ?? this.primaryRole;
|
|
8459
8844
|
const { profile, provider } = this.resolvePrimaryBinding(role);
|
|
@@ -8537,12 +8922,22 @@ var AgentSession = class {
|
|
|
8537
8922
|
profile.contextWindow,
|
|
8538
8923
|
profile.maxOutputTokens
|
|
8539
8924
|
).inputBudget;
|
|
8925
|
+
const toolSnapshot = this.tools.snapshot();
|
|
8926
|
+
let prepared;
|
|
8927
|
+
if (prepare) {
|
|
8928
|
+
prepared = await prepare({ signal: options?.signal, runId, tools: toolSnapshot });
|
|
8929
|
+
if (options?.signal?.aborted) throw new AbortError();
|
|
8930
|
+
if (prepared.skillName) skillName = prepared.skillName;
|
|
8931
|
+
}
|
|
8540
8932
|
await this.skillRegistry?.refresh(options?.signal);
|
|
8541
8933
|
this.skillRuntime?.syncTools();
|
|
8934
|
+
const extraPreparedTokens = prepared?.content ? typeof prepared.content === "string" ? estimateSkillTokens(prepared.content) : prepared.content.filter(isTextBlock).reduce((sum, b) => sum + estimateSkillTokens(b.text), 0) : 0;
|
|
8542
8935
|
const promptAssembler = this.createPromptAssembler(
|
|
8543
8936
|
profile,
|
|
8544
8937
|
this.context.project(this.history),
|
|
8545
|
-
32 + estimateSkillTokens(userInput)
|
|
8938
|
+
32 + estimateSkillTokens(userInput) + extraPreparedTokens,
|
|
8939
|
+
toolSnapshot.list(),
|
|
8940
|
+
toolSnapshot
|
|
8546
8941
|
);
|
|
8547
8942
|
this.skillRuntime?.begin(
|
|
8548
8943
|
runId,
|
|
@@ -8551,7 +8946,7 @@ var AgentSession = class {
|
|
|
8551
8946
|
const current = {
|
|
8552
8947
|
...previousRequest ?? { systemPrompt: promptAssembler.assemble() },
|
|
8553
8948
|
messages: [...this.context.project(this.history), ...budgetToolMessages],
|
|
8554
|
-
tools: previousRequest?.tools ?? visibleTools(
|
|
8949
|
+
tools: previousRequest?.tools ?? visibleTools(toolSnapshot.list(), this.mode, this.permissionRules).map((tool) => ({
|
|
8555
8950
|
name: tool.name,
|
|
8556
8951
|
description: tool.description,
|
|
8557
8952
|
parameters: tool.parameters
|
|
@@ -8559,7 +8954,7 @@ var AgentSession = class {
|
|
|
8559
8954
|
};
|
|
8560
8955
|
return Math.max(
|
|
8561
8956
|
0,
|
|
8562
|
-
inputBudget - (this.estimator ?? new UnicodeTokenEstimator()).estimate(current).total - (previousRequest ? 0 : estimateSkillTokens(userInput) + 32)
|
|
8957
|
+
inputBudget - (this.estimator ?? new UnicodeTokenEstimator()).estimate(current).total - (previousRequest ? 0 : estimateSkillTokens(userInput) + extraPreparedTokens + 32)
|
|
8563
8958
|
);
|
|
8564
8959
|
},
|
|
8565
8960
|
() => previousRequest?.messages ?? this.context.project(this.history)
|
|
@@ -8571,6 +8966,51 @@ var AgentSession = class {
|
|
|
8571
8966
|
await this.authorizeSkill(skillName, runId, options?.signal);
|
|
8572
8967
|
preparedSkill = await this.skillRuntime.prepare(skillName, "user", options?.signal);
|
|
8573
8968
|
}
|
|
8969
|
+
if (prepared?.verifyIdentity) {
|
|
8970
|
+
const valid = await prepared.verifyIdentity();
|
|
8971
|
+
if (!valid) {
|
|
8972
|
+
throw new KapibalaError("Task preparation identity changed before commit", {
|
|
8973
|
+
code: "MCP_PROMPT_CHANGED",
|
|
8974
|
+
retryPolicy: "never"
|
|
8975
|
+
});
|
|
8976
|
+
}
|
|
8977
|
+
}
|
|
8978
|
+
const userContent = [{ type: "text", text: userInput }];
|
|
8979
|
+
if (preparedSkill)
|
|
8980
|
+
userContent.push({
|
|
8981
|
+
type: "text",
|
|
8982
|
+
text: preparedSkill.text,
|
|
8983
|
+
skillSource: preparedSkill.source
|
|
8984
|
+
});
|
|
8985
|
+
if (prepared?.content) {
|
|
8986
|
+
if (typeof prepared.content === "string") {
|
|
8987
|
+
userContent.push({
|
|
8988
|
+
type: "text",
|
|
8989
|
+
text: prepared.content,
|
|
8990
|
+
...prepared.promptSource ? { promptSource: prepared.promptSource } : {}
|
|
8991
|
+
});
|
|
8992
|
+
} else {
|
|
8993
|
+
userContent.push(...prepared.content);
|
|
8994
|
+
}
|
|
8995
|
+
}
|
|
8996
|
+
const userMessage = {
|
|
8997
|
+
id: createMessageId(),
|
|
8998
|
+
interactionId: runId,
|
|
8999
|
+
state: "completed",
|
|
9000
|
+
role: "user",
|
|
9001
|
+
content: userContent,
|
|
9002
|
+
timestamp: Date.now()
|
|
9003
|
+
};
|
|
9004
|
+
const taskBudgetCheck = (this.estimator ?? new UnicodeTokenEstimator()).estimate({
|
|
9005
|
+
systemPrompt: promptAssembler.assemble(),
|
|
9006
|
+
messages: [userMessage]
|
|
9007
|
+
}).total;
|
|
9008
|
+
if (taskBudgetCheck > inputBudget) {
|
|
9009
|
+
throw new ContextOverflowError(400, {
|
|
9010
|
+
message: "Prepared task content exceeds context input budget",
|
|
9011
|
+
stage: "request"
|
|
9012
|
+
});
|
|
9013
|
+
}
|
|
8574
9014
|
this.totalTurns++;
|
|
8575
9015
|
if (this.store && supportsSessionState(this.store)) {
|
|
8576
9016
|
const currentProject = resolveSessionProject(this.cwd);
|
|
@@ -8584,24 +9024,39 @@ var AgentSession = class {
|
|
|
8584
9024
|
});
|
|
8585
9025
|
}
|
|
8586
9026
|
runStarted = true;
|
|
8587
|
-
const userMessage = {
|
|
8588
|
-
id: createMessageId(),
|
|
8589
|
-
interactionId: runId,
|
|
8590
|
-
state: "completed",
|
|
8591
|
-
role: "user",
|
|
8592
|
-
content: [{ type: "text", text: userInput }],
|
|
8593
|
-
timestamp: Date.now()
|
|
8594
|
-
};
|
|
8595
|
-
if (preparedSkill)
|
|
8596
|
-
userMessage.content.push({
|
|
8597
|
-
type: "text",
|
|
8598
|
-
text: preparedSkill.text,
|
|
8599
|
-
skillSource: preparedSkill.source
|
|
8600
|
-
});
|
|
8601
9027
|
if (this.store) {
|
|
8602
9028
|
await this.store.append(userMessage);
|
|
8603
9029
|
}
|
|
8604
9030
|
this.history.push(userMessage);
|
|
9031
|
+
if (prepared?.promptSource) {
|
|
9032
|
+
const persistence = this.store ? "disk" : "memory";
|
|
9033
|
+
yield {
|
|
9034
|
+
type: "mcp_prompt_loaded",
|
|
9035
|
+
server: prepared.promptSource.server,
|
|
9036
|
+
name: prepared.promptSource.name,
|
|
9037
|
+
persistence,
|
|
9038
|
+
promptSource: prepared.promptSource,
|
|
9039
|
+
runId
|
|
9040
|
+
};
|
|
9041
|
+
try {
|
|
9042
|
+
await this.eventLogger.recordAudit({
|
|
9043
|
+
level: "info",
|
|
9044
|
+
event: "mcp.prompt_loaded",
|
|
9045
|
+
sessionId: this.sessionId,
|
|
9046
|
+
runId,
|
|
9047
|
+
fields: {
|
|
9048
|
+
server: prepared.promptSource.server,
|
|
9049
|
+
promptName: prepared.promptSource.name,
|
|
9050
|
+
contentDigest: prepared.promptSource.contentDigest,
|
|
9051
|
+
persistence
|
|
9052
|
+
}
|
|
9053
|
+
});
|
|
9054
|
+
} catch (auditErr) {
|
|
9055
|
+
const msg = `MCP prompt audit recording failed: ${auditErr instanceof Error ? auditErr.message : String(auditErr)}`;
|
|
9056
|
+
this.logger?.(msg);
|
|
9057
|
+
this.onDiagnostic?.(msg);
|
|
9058
|
+
}
|
|
9059
|
+
}
|
|
8605
9060
|
if (preparedSkill && this.skillRuntime) {
|
|
8606
9061
|
this.skillRuntime.acceptUser(preparedSkill);
|
|
8607
9062
|
for (const event of await this.commitSkills(runId)) yield event;
|
|
@@ -8612,7 +9067,7 @@ var AgentSession = class {
|
|
|
8612
9067
|
const executor = new ToolExecutor({
|
|
8613
9068
|
hardDenial: (tool) => this.skillRuntime?.denial(tool),
|
|
8614
9069
|
onToolResult: (tool, _input, result) => this.skillRuntime?.acceptResult(tool, result),
|
|
8615
|
-
tools:
|
|
9070
|
+
tools: toolSnapshot,
|
|
8616
9071
|
hooks: this.hooks,
|
|
8617
9072
|
rootDir: this.rootDir,
|
|
8618
9073
|
signal: options?.signal,
|
|
@@ -8734,12 +9189,12 @@ var AgentSession = class {
|
|
|
8734
9189
|
const skills = this.skillRuntime;
|
|
8735
9190
|
const preparation = context.prepare(history, candidate, profile, runId, reason, !frozen);
|
|
8736
9191
|
return (async function* () {
|
|
8737
|
-
const
|
|
8738
|
-
skills?.assertProjection(
|
|
8739
|
-
previousRequest = structuredClone({ ...
|
|
9192
|
+
const prepared2 = yield* preparation;
|
|
9193
|
+
skills?.assertProjection(prepared2.messages);
|
|
9194
|
+
previousRequest = structuredClone({ ...prepared2, signal: void 0 });
|
|
8740
9195
|
lastPromptTokens = void 0;
|
|
8741
9196
|
lastEstimatedPromptTokens = context.getSnapshot()?.estimate.total;
|
|
8742
|
-
return
|
|
9197
|
+
return prepared2;
|
|
8743
9198
|
})();
|
|
8744
9199
|
},
|
|
8745
9200
|
onUsage: async (request, usage, attemptId) => {
|
|
@@ -8758,7 +9213,7 @@ var AgentSession = class {
|
|
|
8758
9213
|
},
|
|
8759
9214
|
provider,
|
|
8760
9215
|
executor,
|
|
8761
|
-
tools:
|
|
9216
|
+
tools: toolSnapshot,
|
|
8762
9217
|
hooks: this.hooks,
|
|
8763
9218
|
systemPrompt: assembledSystemPrompt,
|
|
8764
9219
|
maxSteps: this.maxSteps,
|
|
@@ -8964,11 +9419,12 @@ var AgentSession = class {
|
|
|
8964
9419
|
if (this.activeRun) throw new SessionBusyError(operation);
|
|
8965
9420
|
}
|
|
8966
9421
|
/** 实际请求、静态统计和手动压缩共用目录空间推导;先计正文、工具和非目录提示,再预算整段目录。 */
|
|
8967
|
-
createPromptAssembler(profile, messages, extraTokens = 0) {
|
|
8968
|
-
const
|
|
9422
|
+
createPromptAssembler(profile, messages, extraTokens = 0, toolList, toolRegistry) {
|
|
9423
|
+
const registry = toolRegistry ?? this.tools;
|
|
9424
|
+
const tools = visibleTools(toolList ?? registry.list(), this.mode, this.permissionRules);
|
|
8969
9425
|
const options = {
|
|
8970
9426
|
skills: this.skillRegistry,
|
|
8971
|
-
tools:
|
|
9427
|
+
tools: registry,
|
|
8972
9428
|
rootDir: this.rootDir,
|
|
8973
9429
|
customInstructions: this.customSystemPrompt,
|
|
8974
9430
|
instructionSources: this.instructionSnapshot?.sources,
|
|
@@ -9268,6 +9724,7 @@ export {
|
|
|
9268
9724
|
OpenAIResponsesProvider,
|
|
9269
9725
|
PathSandbox,
|
|
9270
9726
|
PermissionPolicy,
|
|
9727
|
+
PreDispatchError,
|
|
9271
9728
|
PromptAssembler,
|
|
9272
9729
|
SESSION_SCHEMA_VERSION,
|
|
9273
9730
|
SKILL_LIMITS,
|