@eddyskywalker/dsh-chatgpt-subscription 0.59.0 → 0.60.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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/lib/client.js +1578 -232
- package/lib/client.js.map +1 -1
- package/lib/codemode.js +537 -150
- package/lib/index.js +177 -183
- package/lib/llm-compat-u9mor0uu.js +110 -0
- package/lib/types/client/codemode/CodeRunToolView.d.ts +68 -0
- package/lib/types/client/codemode/CodeRunToolView.d.ts.map +1 -0
- package/lib/types/client/codemode/highlight.d.ts +25 -0
- package/lib/types/client/codemode/highlight.d.ts.map +1 -0
- package/lib/types/client/codemode/styles.d.ts +11 -0
- package/lib/types/client/codemode/styles.d.ts.map +1 -0
- package/lib/types/client/codemode/tool-name.d.ts +10 -0
- package/lib/types/client/codemode/tool-name.d.ts.map +1 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales-ru/hub.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +23 -1
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/compat.d.ts +1 -1
- package/lib/types/entry-codemode.d.ts.map +1 -1
- package/lib/types/host/codemode/executor.d.ts.map +1 -1
- package/lib/types/host/codemode/rules.d.ts +16 -6
- package/lib/types/host/codemode/rules.d.ts.map +1 -1
- package/lib/types/host/codemode/transport.d.ts +82 -41
- package/lib/types/host/codemode/transport.d.ts.map +1 -1
- package/lib/types/host/command-code/model-catalog.d.ts +14 -14
- package/lib/types/host/command-code/model-catalog.d.ts.map +1 -1
- package/lib/types/host/command-code/routes.d.ts.map +1 -1
- package/lib/types/host/preset-sync.d.ts +19 -4
- package/lib/types/host/preset-sync.d.ts.map +1 -1
- package/package.json +1 -1
- package/presets/dispatch/README.md +1 -3
- package/presets/dispatch/agent.cordis.yml +9 -7
package/lib/codemode.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { n as PLUGIN_MESSAGE_SOURCE_KIND } from "./llm-compat-u9mor0uu.js";
|
|
2
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
1
3
|
import { NativeToolExecutor } from "@deepseek-ai/dsh-tool-executor-native";
|
|
2
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
5
|
//#region src/host/codemode/discovery.ts
|
|
@@ -448,10 +450,18 @@ const DEFAULT_HIDDEN_PREFIXES = Object.freeze(["mcp__"]);
|
|
|
448
450
|
/**
|
|
449
451
|
* 默认始终原生呈现的名字。
|
|
450
452
|
*
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
+
* 恒定系统提示词或子代理运行时契约里点名要求直接调用这些工具(大输出预览补读、调度账本查询、
|
|
454
|
+
* 进展更新汇报、结构化子代理输出提交,以及 Windows 下替代被撤回 `pwsh` 的 `win_bash`):
|
|
455
|
+
* 遮蔽它们要么让提示词指向模型看不见的名字并触发整批 `UNKNOWN_TOOL` 重跑,要么让会话顶层
|
|
456
|
+
* 失去唯一可用的 Shell。若某个工具当前未在宿主/子会话注册,保留在此集合中不会凭空暴露它。
|
|
453
457
|
*/
|
|
454
|
-
const DEFAULT_NATIVE_TOOLS = Object.freeze([
|
|
458
|
+
const DEFAULT_NATIVE_TOOLS = Object.freeze([
|
|
459
|
+
"cache_warmup_result",
|
|
460
|
+
"dispatch_ledger",
|
|
461
|
+
"progress_update",
|
|
462
|
+
"structured_output",
|
|
463
|
+
"win_bash"
|
|
464
|
+
]);
|
|
455
465
|
/**
|
|
456
466
|
* 把原始配置归一化。
|
|
457
467
|
*
|
|
@@ -462,12 +472,8 @@ function createRules(config) {
|
|
|
462
472
|
const options = normalizeConfig(config);
|
|
463
473
|
return {
|
|
464
474
|
options,
|
|
465
|
-
hidden: new Set(options.hiddenTools),
|
|
466
|
-
native: /* @__PURE__ */ new Set([
|
|
467
|
-
...options.nativeTools,
|
|
468
|
-
options.runToolName,
|
|
469
|
-
...options.metaTools
|
|
470
|
-
])
|
|
475
|
+
hidden: /* @__PURE__ */ new Set([...options.hiddenTools, ...options.metaTools]),
|
|
476
|
+
native: /* @__PURE__ */ new Set([...options.nativeTools, options.runToolName])
|
|
471
477
|
};
|
|
472
478
|
}
|
|
473
479
|
/**
|
|
@@ -491,6 +497,8 @@ function normalizeConfig(config) {
|
|
|
491
497
|
runToolName,
|
|
492
498
|
maxParallelSubCalls: positiveInteger(source.maxParallelSubCalls, 10),
|
|
493
499
|
maxSubCalls: positiveInteger(source.maxSubCalls, 512),
|
|
500
|
+
explicitMaxSubCalls: typeof source.maxSubCalls === "number" && Number.isInteger(source.maxSubCalls) && source.maxSubCalls > 0,
|
|
501
|
+
maxSubCallsPerMinute: positiveInteger(source.maxSubCallsPerMinute, 1024),
|
|
494
502
|
guard: source.guard !== false,
|
|
495
503
|
sdkSection: source.sdkSection !== false,
|
|
496
504
|
debug: source.debug !== false
|
|
@@ -561,7 +569,7 @@ function summarizeHidden(names) {
|
|
|
561
569
|
*
|
|
562
570
|
* ## 恒定契约
|
|
563
571
|
*
|
|
564
|
-
* 这段正文**只**依赖入参里的两个工具名与部署级的 `programFormat`。它刻意不含遮蔽
|
|
572
|
+
* 这段正文**只**依赖入参里的两个工具名与部署级的 `language` / `programFormat`。它刻意不含遮蔽
|
|
565
573
|
* 工具的数量、namespace 分布、会话 id 或时间,因此同一部署下逐字节恒定:挂载或卸载
|
|
566
574
|
* MCP 服务端不会改写它,前缀缓存不会因为它失效。
|
|
567
575
|
*
|
|
@@ -569,26 +577,40 @@ function summarizeHidden(names) {
|
|
|
569
577
|
* @returns 说明段正文。
|
|
570
578
|
*/
|
|
571
579
|
function buildCodemodeSectionText(input) {
|
|
580
|
+
const python = input.language === "python";
|
|
572
581
|
const retained = input.programFormat === "repl";
|
|
573
582
|
const listTool = input.metaTools[0] ?? "code_tools_list";
|
|
574
583
|
const searchTool = input.metaTools[1] ?? "code_tools_search";
|
|
575
584
|
const describeTool = input.metaTools[2] ?? "code_tools_describe";
|
|
585
|
+
const run = input.runToolName;
|
|
586
|
+
const example = python ? [
|
|
587
|
+
"```py",
|
|
588
|
+
"# standard tools and hidden tools alike",
|
|
589
|
+
"import asyncio",
|
|
590
|
+
"files = await tools.glob({\"pattern\": \"src/**/*.ts\"})",
|
|
591
|
+
"rows = await asyncio.gather(*(tools.read({\"file_path\": f}) for f in files))",
|
|
592
|
+
"[r.splitlines()[0] for r in rows if \"TODO\" in r]",
|
|
593
|
+
"```"
|
|
594
|
+
] : [
|
|
595
|
+
"```js",
|
|
596
|
+
"// standard tools and hidden tools alike",
|
|
597
|
+
"const files = await tools.glob({ pattern: \"src/**/*.ts\" })",
|
|
598
|
+
"const rows = await Promise.all(files.map((f) => tools.read({ file_path: f })))",
|
|
599
|
+
retained ? "rows.filter((r) => r.includes(\"TODO\")).map((r) => r.split(\"\\n\")[0])" : "return rows.filter((r) => r.includes(\"TODO\")).map((r) => r.split(\"\\n\")[0])",
|
|
600
|
+
"```"
|
|
601
|
+
];
|
|
576
602
|
return [
|
|
577
603
|
"## Code Mode",
|
|
578
604
|
"",
|
|
579
|
-
"This session runs in Code Mode. The tools in your tool list are called directly, exactly as usual. A second, larger set of tools is deliberately **not** in your context: every MCP tool, and every tool contributed by a plugin outside this preset. Reach them from inside " +
|
|
605
|
+
"This session runs in Code Mode. The tools in your tool list are called directly, exactly as usual. A second, larger set of tools is deliberately **not** in your context: every MCP tool, and every tool contributed by a plugin outside this preset. Reach them from inside " + run + ".",
|
|
580
606
|
"",
|
|
581
607
|
"### Calling tools from a program",
|
|
582
608
|
"",
|
|
583
|
-
|
|
584
|
-
"// standard tools and hidden tools alike",
|
|
585
|
-
"const files = await tools.glob({ pattern: \"src/**/*.ts\" })",
|
|
586
|
-
"const rows = await Promise.all(files.map((f) => tools.read({ file_path: f })))",
|
|
587
|
-
retained ? "rows.filter((r) => r.includes(\"TODO\")).map((r) => r.split(\"\\n\")[0])" : "return rows.filter((r) => r.includes(\"TODO\")).map((r) => r.split(\"\\n\")[0])",
|
|
588
|
-
"```",
|
|
609
|
+
...example,
|
|
589
610
|
"",
|
|
590
|
-
retained ? "- A retained TypeScript REPL: top-level declarations, await, and a final expression. Its variables and imported modules survive into your later programs in this session." : "- Erasable TypeScript, run as the body of an async function: top-level await and return work.",
|
|
611
|
+
python ? retained ? "- A retained Python REPL: top-level await and a final expression. Its variables and imported modules survive into your later programs in this session." : "- Async Python program: top-level await works; print or evaluate a final expression to produce output." : retained ? "- A retained TypeScript REPL: top-level declarations, await, and a final expression. Its variables and imported modules survive into your later programs in this session." : "- Erasable TypeScript, run as the body of an async function: top-level await and return work.",
|
|
591
612
|
"- Only what you return or console.log reaches the conversation; every intermediate result stays in the program, so filter and aggregate there instead of returning raw payloads.",
|
|
613
|
+
"- Prefer batching multi-step read/search chains inside one " + run + " program instead of issuing many sequential single-tool turns.",
|
|
592
614
|
"- Independent read-only calls may overlap under Promise.all; mutating calls run alone, in submission order.",
|
|
593
615
|
"- A failed sub-call rejects; try/catch or Promise.allSettled it to keep the calls that succeeded. Completed calls are real side effects and are not rolled back.",
|
|
594
616
|
"",
|
|
@@ -782,6 +804,7 @@ function denialFor(call, rules, hooks, batchSignal) {
|
|
|
782
804
|
if (isNestedCall(call, batchSignal)) return void 0;
|
|
783
805
|
if (name === rules.options.runToolName) return void 0;
|
|
784
806
|
if (!isMaskedName(name, rules, hooks.viewForName(name))) return void 0;
|
|
807
|
+
if (rules.options.metaTools.includes(name)) return "The discovery tool \"" + name + "\" is callable only from inside " + rules.options.runToolName + ": run a program and call await tools." + name + "(...) there.";
|
|
785
808
|
const searchTool = rules.options.metaTools[1] ?? rules.options.metaTools[0] ?? "code_tools_search";
|
|
786
809
|
return "The tool \"" + name + "\" is not part of this session tool list. In Code Mode, tools this preset does not own (MCP servers, plugins outside the preset) are reachable only from inside " + rules.options.runToolName + ": call tools." + searchTool + "({ query: \"...\" }) there to discover it, then call it by exact name.";
|
|
787
810
|
}
|
|
@@ -978,17 +1001,17 @@ function asRegistry(tools) {
|
|
|
978
1001
|
* 用自己的名字注册同一件事——把模型写的一段程序交给 `ctx.ptcRuntime` 执行,程序里的每次
|
|
979
1002
|
* `tools.<name>(args)` 都是一次真实的工具调用。
|
|
980
1003
|
*
|
|
981
|
-
* ##
|
|
982
|
-
*
|
|
983
|
-
* 子调用走公开的 `ctx.tools.execute()`,因此落到**注册表选中的执行器**上:在本预设里就是
|
|
984
|
-
* 原生 executor 的完整管线(pre-policy、审批、around-dispatch、post-execute 一个不少)。
|
|
985
|
-
* 手写这条管线是插件静默丢策略的典型方式,所以这里一行都不抄。
|
|
986
|
-
*
|
|
987
|
-
* ## 子调度的取舍
|
|
1004
|
+
* ## 与内核 PTC `run_code` 对齐的使用配置
|
|
988
1005
|
*
|
|
989
|
-
*
|
|
990
|
-
* `
|
|
991
|
-
*
|
|
1006
|
+
* 1. **运行时感知 Schema**:按 `ptcRuntime` 的语言(TypeScript / Python)、程序形态
|
|
1007
|
+
* (`function-body` / 常驻 REPL)、超时预算(`defaultMs` / `maxMs`)与沙箱支持动态投影
|
|
1008
|
+
* 描述和参数(含单次提权的 `sandbox_permissions` + `justification`);同一部署下静态恒定,
|
|
1009
|
+
* 不会扰动前缀缓存。
|
|
1010
|
+
* 2. **完整子调用调度与上下文转发**:只读并发上限受 `maxParallelSubCalls` 约束,写类调用等待
|
|
1011
|
+
* 在飞调用排空后独占执行;程序结算时中止悬空请求并等待已启动子调用落盘;子调用产生的图片块、
|
|
1012
|
+
* `additionalContexts` 与 `concludesTurn` 原样转交给外层步骤。
|
|
1013
|
+
* 3. **展示元数据与输出格式化**:声明 `presentCall` 与 `output.presentationMeta`(记录语言),
|
|
1014
|
+
* 深层 JSON 结果控制缩进上限,失败时保留已捕获输出与沙箱诊断信息。
|
|
992
1015
|
*
|
|
993
1016
|
* @module @eddyskywalker/dsh-chatgpt-subscription/host/codemode/transport
|
|
994
1017
|
*/
|
|
@@ -1011,6 +1034,79 @@ const catalogScope = new AsyncLocalStorage();
|
|
|
1011
1034
|
function currentCatalogSnapshot() {
|
|
1012
1035
|
return catalogScope.getStore();
|
|
1013
1036
|
}
|
|
1037
|
+
/** 允许单次提权的目标沙箱模式(对齐 `@deepseek-ai/dsh-sandbox` 的 `ESCALATION_TARGETS`)。 */
|
|
1038
|
+
const ESCALATION_TARGETS = Object.freeze(["workspace-write", "danger-full-access"]);
|
|
1039
|
+
/** 与内核 `run_code` 一致的 `description` 参数说明。 */
|
|
1040
|
+
const DESCRIPTION_PARAM_DESCRIPTION = "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\".";
|
|
1041
|
+
function buildFlavor(languageName, execution) {
|
|
1042
|
+
return {
|
|
1043
|
+
description: `Execute a ${languageName} program against the available tools. Takes two required arguments: \`code\`, ${execution.programDescription}, and \`description\`, a short summary of what the program does. Call tools as \`await tools.name(args)\`. ${execution.outputInstruction} Image-bearing subtool results are attached after the run.`,
|
|
1044
|
+
codeDescription: execution.codeDescription
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
const TYPESCRIPT_REPL_FLAVOR = buildFlavor("TypeScript", {
|
|
1048
|
+
programDescription: "the program to execute",
|
|
1049
|
+
outputInstruction: "Curate the program output.",
|
|
1050
|
+
codeDescription: "The TypeScript program to execute, following this tool's execution instructions."
|
|
1051
|
+
});
|
|
1052
|
+
const TYPESCRIPT_FUNCTION_FLAVOR = buildFlavor("TypeScript", {
|
|
1053
|
+
programDescription: "the BODY of an async function (erasable syntax only; top-level `await` and `return` work)",
|
|
1054
|
+
outputInstruction: "Only what you print or return is program output — curate it.",
|
|
1055
|
+
codeDescription: "The program: the body of an async TypeScript function."
|
|
1056
|
+
});
|
|
1057
|
+
const PYTHON_FLAVOR = buildFlavor("Python", {
|
|
1058
|
+
programDescription: "the program to execute",
|
|
1059
|
+
outputInstruction: "Curate the program output.",
|
|
1060
|
+
codeDescription: "The Python program to execute, following this tool's execution instructions."
|
|
1061
|
+
});
|
|
1062
|
+
function resolveFlavor(runtime) {
|
|
1063
|
+
if (runtime?.language === "python") return PYTHON_FLAVOR;
|
|
1064
|
+
if (runtime?.language === "typescript" && runtime.programFormat === "repl") return TYPESCRIPT_REPL_FLAVOR;
|
|
1065
|
+
return TYPESCRIPT_FUNCTION_FLAVOR;
|
|
1066
|
+
}
|
|
1067
|
+
function escalationGuidance(sandboxMode) {
|
|
1068
|
+
return sandboxMode === void 0 ? "" : " A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.";
|
|
1069
|
+
}
|
|
1070
|
+
function buildTransportDescription(runtime) {
|
|
1071
|
+
const flavor = resolveFlavor(runtime);
|
|
1072
|
+
const instructions = typeof runtime?.executionInstructions === "string" && runtime.executionInstructions.length > 0 ? ` ${runtime.executionInstructions}` : "";
|
|
1073
|
+
return flavor.description + instructions + " Each program starts in the Session's current directory. Running programs keep their initial directory." + escalationGuidance(runtime?.sandboxMode);
|
|
1074
|
+
}
|
|
1075
|
+
function buildTransportParameters(runtime) {
|
|
1076
|
+
const flavor = resolveFlavor(runtime);
|
|
1077
|
+
const timeoutDescription = runtime?.timeout !== void 0 ? `Positive elapsed-time budget in milliseconds, including nested tool and approval waits. Default ${String(runtime.timeout.defaultMs)}; capped at ${String(runtime.timeout.maxMs)}. Zero does not disable the deadline.` : "Positive elapsed-time budget in milliseconds, capped by the runtime maximum.";
|
|
1078
|
+
const properties = {
|
|
1079
|
+
code: {
|
|
1080
|
+
type: "string",
|
|
1081
|
+
description: flavor.codeDescription
|
|
1082
|
+
},
|
|
1083
|
+
description: {
|
|
1084
|
+
type: "string",
|
|
1085
|
+
description: DESCRIPTION_PARAM_DESCRIPTION
|
|
1086
|
+
},
|
|
1087
|
+
timeoutMs: {
|
|
1088
|
+
type: "number",
|
|
1089
|
+
description: timeoutDescription
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
if (runtime?.sandboxMode !== void 0) {
|
|
1093
|
+
properties["sandbox_permissions"] = {
|
|
1094
|
+
type: "string",
|
|
1095
|
+
enum: [...ESCALATION_TARGETS],
|
|
1096
|
+
description: "Wider sandbox mode for this complete program execution; requires justification and approval."
|
|
1097
|
+
};
|
|
1098
|
+
properties["justification"] = {
|
|
1099
|
+
type: "string",
|
|
1100
|
+
description: "Reason this complete program needs wider access, shown to the user for approval. Use the language of the user’s current request."
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
return {
|
|
1104
|
+
type: "object",
|
|
1105
|
+
additionalProperties: false,
|
|
1106
|
+
required: ["code", "description"],
|
|
1107
|
+
properties
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1014
1110
|
/**
|
|
1015
1111
|
* 造 `code_run` 的工具定义。
|
|
1016
1112
|
*
|
|
@@ -1020,28 +1116,10 @@ function currentCatalogSnapshot() {
|
|
|
1020
1116
|
*/
|
|
1021
1117
|
function createTransportDefinition(host, rules) {
|
|
1022
1118
|
const runToolName = rules.options.runToolName;
|
|
1023
|
-
|
|
1119
|
+
const definition = {
|
|
1024
1120
|
name: runToolName,
|
|
1025
|
-
description:
|
|
1026
|
-
parameters:
|
|
1027
|
-
type: "object",
|
|
1028
|
-
additionalProperties: false,
|
|
1029
|
-
required: ["code", "description"],
|
|
1030
|
-
properties: {
|
|
1031
|
-
code: {
|
|
1032
|
-
type: "string",
|
|
1033
|
-
description: "The program: the body of an async TypeScript function."
|
|
1034
|
-
},
|
|
1035
|
-
description: {
|
|
1036
|
-
type: "string",
|
|
1037
|
-
description: "Clear, concise description of what this program does in active voice, 5-10 words."
|
|
1038
|
-
},
|
|
1039
|
-
timeoutMs: {
|
|
1040
|
-
type: "number",
|
|
1041
|
-
description: "Positive elapsed-time budget in milliseconds, capped by the runtime maximum."
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
},
|
|
1121
|
+
description: buildTransportDescription(void 0),
|
|
1122
|
+
parameters: buildTransportParameters(void 0),
|
|
1045
1123
|
output: {
|
|
1046
1124
|
schema: {
|
|
1047
1125
|
type: "object",
|
|
@@ -1059,53 +1137,80 @@ function createTransportDefinition(host, rules) {
|
|
|
1059
1137
|
required: ["mode", "denied"],
|
|
1060
1138
|
properties: {
|
|
1061
1139
|
mode: { type: "string" },
|
|
1062
|
-
denied: { type: "boolean" }
|
|
1140
|
+
denied: { type: "boolean" },
|
|
1141
|
+
enforcement: {
|
|
1142
|
+
type: "string",
|
|
1143
|
+
enum: ["full", "partial"]
|
|
1144
|
+
}
|
|
1063
1145
|
}
|
|
1064
1146
|
}
|
|
1065
1147
|
}
|
|
1066
1148
|
},
|
|
1067
1149
|
render: (_args, value) => {
|
|
1068
|
-
const parts = [];
|
|
1069
|
-
|
|
1070
|
-
if (value.
|
|
1071
|
-
parts.push(typeof value.result === "string" ? value.result : JSON.stringify(value.result, null, 2));
|
|
1072
|
-
} catch {
|
|
1073
|
-
parts.push(String(value.result));
|
|
1074
|
-
}
|
|
1075
|
-
if (value.sandbox?.denied === true) parts.push("The " + value.sandbox.mode + " file sandbox denied an operation.");
|
|
1150
|
+
const parts = [(value.logs ?? []).join("\n"), value.result === void 0 ? "" : renderProgramValue(value.result)].filter((part) => part.length > 0);
|
|
1151
|
+
if (value.sandbox?.enforcement === "partial") parts.push("File sandbox enforcement is partial on this host.");
|
|
1152
|
+
if (value.sandbox?.denied === true) parts.push("The " + value.sandbox.mode + " file sandbox denied an operation." + escalationGuidance(host.runtime()?.sandboxMode));
|
|
1076
1153
|
return [{
|
|
1077
1154
|
type: "text",
|
|
1078
1155
|
text: parts.length > 0 ? parts.join("\n") : "(" + runToolName + " completed with no output)"
|
|
1079
1156
|
}];
|
|
1157
|
+
},
|
|
1158
|
+
presentationMeta: () => {
|
|
1159
|
+
return { language: host.runtime()?.language === "python" ? "python" : "typescript" };
|
|
1080
1160
|
}
|
|
1081
1161
|
},
|
|
1162
|
+
presentCall(args) {
|
|
1163
|
+
if (typeof args !== "object" || args === null) return void 0;
|
|
1164
|
+
const record = args;
|
|
1165
|
+
const code = record["code"];
|
|
1166
|
+
const description = record["description"];
|
|
1167
|
+
if (typeof code !== "string" || typeof description !== "string" || description.trim().length === 0) return;
|
|
1168
|
+
return {
|
|
1169
|
+
card: "generic",
|
|
1170
|
+
title: description,
|
|
1171
|
+
kind: "execute",
|
|
1172
|
+
rawInput: code
|
|
1173
|
+
};
|
|
1174
|
+
},
|
|
1082
1175
|
async execute(args, exec) {
|
|
1083
1176
|
const input = readArguments(args);
|
|
1084
1177
|
const runtime = host.runtime();
|
|
1085
1178
|
if (runtime === void 0) throw new Error("codemode: no PTC runtime is mounted (ctx.ptcRuntime); the program transport is unavailable");
|
|
1179
|
+
if (input.timeoutMs !== void 0 && runtime.timeout !== void 0 === false && isStrictTimeoutUnsupported(runtime)) throw new Error("timeoutMs is not available for this PTC runtime");
|
|
1086
1180
|
const catalog = host.catalog();
|
|
1087
|
-
const
|
|
1088
|
-
const
|
|
1181
|
+
const runController = new AbortController();
|
|
1182
|
+
const onOuterAbort = () => {
|
|
1183
|
+
runController.abort(exec.signal.reason);
|
|
1184
|
+
};
|
|
1185
|
+
if (exec.signal.aborted) runController.abort(exec.signal.reason);
|
|
1186
|
+
else exec.signal.addEventListener("abort", onOuterAbort, { once: true });
|
|
1187
|
+
const nestedSignal = markNested(runController.signal) ?? runController.signal;
|
|
1089
1188
|
const owner = host.stateOwner(exec.agent);
|
|
1090
|
-
|
|
1189
|
+
let maxSubCalls = rules.options.maxSubCalls;
|
|
1091
1190
|
let attempts = 0;
|
|
1092
|
-
const
|
|
1191
|
+
const scheduler = createSubCallScheduler(rules.options.maxParallelSubCalls, () => runController.signal.aborted);
|
|
1093
1192
|
const definitions = /* @__PURE__ */ new Map();
|
|
1094
1193
|
for (const entry of catalog) definitions.set(entry.name, entry);
|
|
1095
|
-
const functions =
|
|
1194
|
+
const functions = Object.create(null);
|
|
1096
1195
|
for (const entry of catalog) {
|
|
1097
1196
|
if (entry.name === runToolName) continue;
|
|
1098
|
-
|
|
1197
|
+
const invoke = async (rawArgs) => {
|
|
1198
|
+
if (runController.signal.aborted) throw new Error(runToolName + " run is over; " + entry.name + " not dispatched");
|
|
1099
1199
|
const n = ++attempts;
|
|
1100
|
-
if (n >
|
|
1200
|
+
if (n > maxSubCalls) {
|
|
1201
|
+
const limitErr = /* @__PURE__ */ new Error("program sub-call limit exceeded: " + String(maxSubCalls) + " calls");
|
|
1202
|
+
runController.abort(limitErr);
|
|
1203
|
+
throw limitErr;
|
|
1204
|
+
}
|
|
1101
1205
|
const parsed = losslessArgs(rawArgs);
|
|
1206
|
+
const loggedArgs = losslessArgs(parsed);
|
|
1102
1207
|
const callId = String(exec.callId) + NESTED_CALL_ID_MARKER + String(n);
|
|
1103
1208
|
const dispatchLog = createDispatchLog(exec.agent, {
|
|
1104
1209
|
rootCallId: rootCallIdOf(String(exec.callId)),
|
|
1105
1210
|
parentCallId: String(exec.callId),
|
|
1106
1211
|
subCallId: callId,
|
|
1107
1212
|
name: entry.name,
|
|
1108
|
-
arguments:
|
|
1213
|
+
arguments: loggedArgs
|
|
1109
1214
|
});
|
|
1110
1215
|
const run = async () => {
|
|
1111
1216
|
dispatchLog.start();
|
|
@@ -1124,55 +1229,151 @@ function createTransportDefinition(host, rules) {
|
|
|
1124
1229
|
throw error;
|
|
1125
1230
|
}
|
|
1126
1231
|
dispatchLog.settle(outcome);
|
|
1232
|
+
if (outcome.error === void 0 && Array.isArray(outcome.content) && hasImageBlock(outcome.content)) {
|
|
1233
|
+
const imageMsg = host.createImageContext?.(outcome.content);
|
|
1234
|
+
if (imageMsg !== void 0) exec.deferContext?.(imageMsg);
|
|
1235
|
+
}
|
|
1236
|
+
if (Array.isArray(outcome.additionalContexts)) for (const contextMsg of outcome.additionalContexts) exec.deferContext?.(contextMsg);
|
|
1237
|
+
if (outcome.error === void 0 && outcome.concluded === true) exec.concludeTurn?.();
|
|
1238
|
+
if (runController.signal.aborted) throw new Error(runToolName + " run is over; " + entry.name + " result discarded");
|
|
1127
1239
|
if (outcome.error !== void 0) throw new Error(outcome.error);
|
|
1128
1240
|
return outcome.value !== void 0 ? outcome.value : outcome.text ?? null;
|
|
1129
1241
|
};
|
|
1130
|
-
|
|
1242
|
+
const mode = isConcurrencySafe(definitions.get(entry.name), parsed) ? "parallel" : "exclusive";
|
|
1243
|
+
return scheduler.schedule(mode, run, () => {
|
|
1244
|
+
throw new Error(runToolName + " run is over; " + entry.name + " tool call abandoned");
|
|
1245
|
+
});
|
|
1131
1246
|
};
|
|
1247
|
+
Object.defineProperty(functions, entry.name, {
|
|
1248
|
+
enumerable: true,
|
|
1249
|
+
configurable: true,
|
|
1250
|
+
writable: true,
|
|
1251
|
+
value: invoke
|
|
1252
|
+
});
|
|
1132
1253
|
}
|
|
1133
|
-
/**
|
|
1134
|
-
* 跑一次程序:每次重取会话授权,因为重试发生在策略变更之后。
|
|
1135
|
-
*
|
|
1136
|
-
* `resolve()` 是新代内核的前置步骤(目录 / 超时 / 文件策略)。老代运行时没有它,
|
|
1137
|
-
* 那时把原始请求直接交给 `run()` 才是当时的契约。
|
|
1138
|
-
*/
|
|
1139
|
-
const attempt = async () => {
|
|
1140
|
-
const authority = await host.runAuthority?.(exec.agent, nestedSignal);
|
|
1141
|
-
const request = {
|
|
1142
|
-
program: input.code,
|
|
1143
|
-
bindings: [{
|
|
1144
|
-
global: "tools",
|
|
1145
|
-
functions
|
|
1146
|
-
}],
|
|
1147
|
-
stateOwner: owner,
|
|
1148
|
-
...authority?.cwd === void 0 ? {} : { cwd: authority.cwd },
|
|
1149
|
-
...authority?.sandboxPolicy === void 0 ? {} : { sandboxPolicy: authority.sandboxPolicy },
|
|
1150
|
-
...input.timeoutMs === void 0 ? {} : { timeoutMs: input.timeoutMs },
|
|
1151
|
-
signal: nestedSignal
|
|
1152
|
-
};
|
|
1153
|
-
const spec = typeof runtime.resolve === "function" ? runtime.resolve(request) : request;
|
|
1154
|
-
return await catalogScope.run(catalog, () => runtime.run(spec));
|
|
1155
|
-
};
|
|
1156
|
-
let result;
|
|
1157
1254
|
try {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1255
|
+
/**
|
|
1256
|
+
* 跑一次程序:每次重取会话授权,因为重试发生在策略变更之后。
|
|
1257
|
+
*
|
|
1258
|
+
* 单次沙箱提权(`sandbox_permissions`)只在第一次进入前审批一次,且提权后的单次执行不传
|
|
1259
|
+
* `stateOwner`,避免把单次授权固化到常驻环境中(对齐内核 `ptc.ts`)。
|
|
1260
|
+
*/
|
|
1261
|
+
let approvedEscalatedMode;
|
|
1262
|
+
const attempt = async () => {
|
|
1263
|
+
const authority = await host.runAuthority?.(exec.agent, nestedSignal);
|
|
1264
|
+
const standingPolicy = authority?.sandboxPolicy;
|
|
1265
|
+
let effectivePolicy = standingPolicy;
|
|
1266
|
+
let escalated = false;
|
|
1267
|
+
if (input.sandboxPermissions !== void 0 && input.justification !== void 0) {
|
|
1268
|
+
if (standingPolicy === void 0 || typeof host.escalateSandbox !== "function") throw new Error("sandbox_permissions is not available for this PTC runtime");
|
|
1269
|
+
if (approvedEscalatedMode === void 0) {
|
|
1270
|
+
const nextPolicy = await host.escalateSandbox({
|
|
1271
|
+
requestedMode: input.sandboxPermissions,
|
|
1272
|
+
justification: input.justification,
|
|
1273
|
+
standingPolicy,
|
|
1274
|
+
agent: exec.agent,
|
|
1275
|
+
callId: String(exec.callId),
|
|
1276
|
+
toolName: runToolName,
|
|
1277
|
+
signal: exec.signal
|
|
1278
|
+
});
|
|
1279
|
+
effectivePolicy = nextPolicy;
|
|
1280
|
+
approvedEscalatedMode = modeOfPolicy(nextPolicy);
|
|
1281
|
+
} else if (typeof standingPolicy === "object" && standingPolicy !== null) effectivePolicy = {
|
|
1282
|
+
...standingPolicy,
|
|
1283
|
+
mode: approvedEscalatedMode
|
|
1284
|
+
};
|
|
1285
|
+
escalated = modeOfPolicy(effectivePolicy) !== modeOfPolicy(standingPolicy);
|
|
1286
|
+
}
|
|
1287
|
+
const request = {
|
|
1288
|
+
program: input.code,
|
|
1289
|
+
bindings: [{
|
|
1290
|
+
global: "tools",
|
|
1291
|
+
functions,
|
|
1292
|
+
errorClass: {
|
|
1293
|
+
name: "ToolCallError",
|
|
1294
|
+
memberNameProperty: "toolName"
|
|
1295
|
+
}
|
|
1296
|
+
}],
|
|
1297
|
+
...escalated ? {} : { stateOwner: owner },
|
|
1298
|
+
...authority?.cwd === void 0 ? {} : { cwd: authority.cwd },
|
|
1299
|
+
...effectivePolicy === void 0 ? {} : { sandboxPolicy: effectivePolicy },
|
|
1300
|
+
...input.timeoutMs === void 0 ? {} : { timeoutMs: input.timeoutMs },
|
|
1301
|
+
signal: nestedSignal
|
|
1302
|
+
};
|
|
1303
|
+
const spec = typeof runtime.resolve === "function" ? runtime.resolve(request) : request;
|
|
1304
|
+
maxSubCalls = resolveSubCallBudget(spec, rules);
|
|
1305
|
+
return await catalogScope.run(catalog, () => runtime.run(spec));
|
|
1306
|
+
};
|
|
1307
|
+
let result;
|
|
1308
|
+
try {
|
|
1309
|
+
try {
|
|
1310
|
+
result = await attempt();
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
if (!isRetainedPolicyChange(error)) throw error;
|
|
1313
|
+
await host.releaseOwner(owner);
|
|
1314
|
+
host.stateOwner(exec.agent);
|
|
1315
|
+
result = await attempt();
|
|
1316
|
+
}
|
|
1317
|
+
} finally {
|
|
1318
|
+
runController.abort(runToolName + " settled");
|
|
1319
|
+
await scheduler.drain();
|
|
1320
|
+
}
|
|
1321
|
+
if (result.error !== void 0) {
|
|
1322
|
+
const logsText = Array.isArray(result.logs) && result.logs.length > 0 ? "\nCaptured output:\n" + result.logs.join("\n") : "";
|
|
1323
|
+
const sandboxText = result.sandbox === void 0 ? "" : "\nFile sandbox: " + result.sandbox.mode + (result.sandbox.enforcement === void 0 ? "" : "; enforcement: " + result.sandbox.enforcement) + (result.sandbox.denied ? "; operation denied" : "") + ".";
|
|
1324
|
+
const hint = result.sandbox?.denied ? escalationGuidance(runtime.sandboxMode) : "";
|
|
1325
|
+
throw new Error("program failed (" + result.error.kind + "): " + result.error.message + logsText + sandboxText + hint);
|
|
1326
|
+
}
|
|
1327
|
+
return {
|
|
1328
|
+
logs: [...result.logs ?? []],
|
|
1329
|
+
...result.value === void 0 ? {} : { result: result.value },
|
|
1330
|
+
...result.sandbox === void 0 ? {} : { sandbox: {
|
|
1331
|
+
mode: result.sandbox.mode,
|
|
1332
|
+
denied: result.sandbox.denied,
|
|
1333
|
+
...result.sandbox.enforcement === void 0 ? {} : { enforcement: result.sandbox.enforcement }
|
|
1334
|
+
} }
|
|
1335
|
+
};
|
|
1336
|
+
} finally {
|
|
1337
|
+
exec.signal.removeEventListener("abort", onOuterAbort);
|
|
1164
1338
|
}
|
|
1165
|
-
if (result.error !== void 0) throw new Error("program failed (" + result.error.kind + "): " + result.error.message);
|
|
1166
|
-
return {
|
|
1167
|
-
logs: [...result.logs ?? []],
|
|
1168
|
-
...result.value === void 0 ? {} : { result: result.value },
|
|
1169
|
-
...result.sandbox === void 0 ? {} : { sandbox: {
|
|
1170
|
-
mode: result.sandbox.mode,
|
|
1171
|
-
denied: result.sandbox.denied
|
|
1172
|
-
} }
|
|
1173
|
-
};
|
|
1174
1339
|
}
|
|
1175
1340
|
};
|
|
1341
|
+
Object.defineProperty(definition, "description", {
|
|
1342
|
+
enumerable: true,
|
|
1343
|
+
configurable: true,
|
|
1344
|
+
get: () => buildTransportDescription(safeRuntime(host))
|
|
1345
|
+
});
|
|
1346
|
+
Object.defineProperty(definition, "parameters", {
|
|
1347
|
+
enumerable: true,
|
|
1348
|
+
configurable: true,
|
|
1349
|
+
get: () => buildTransportParameters(safeRuntime(host))
|
|
1350
|
+
});
|
|
1351
|
+
return definition;
|
|
1352
|
+
}
|
|
1353
|
+
function safeRuntime(host) {
|
|
1354
|
+
try {
|
|
1355
|
+
return host.runtime();
|
|
1356
|
+
} catch {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
/** 只有明确声明了 `timeout: undefined` 自身属性(而非测试假对象省略字段)才视作不支持 `timeoutMs`。 */
|
|
1361
|
+
function isStrictTimeoutUnsupported(runtime) {
|
|
1362
|
+
return Object.prototype.hasOwnProperty.call(runtime, "timeout") && runtime.timeout === void 0;
|
|
1363
|
+
}
|
|
1364
|
+
function modeOfPolicy(policy) {
|
|
1365
|
+
if (typeof policy !== "object" || policy === null) return void 0;
|
|
1366
|
+
const mode = policy.mode;
|
|
1367
|
+
return typeof mode === "string" ? mode : void 0;
|
|
1368
|
+
}
|
|
1369
|
+
function hasImageBlock(content) {
|
|
1370
|
+
return content.some((block) => typeof block === "object" && block !== null && block.type === "image");
|
|
1371
|
+
}
|
|
1372
|
+
function resolveSubCallBudget(spec, rules) {
|
|
1373
|
+
if (rules.options.explicitMaxSubCalls) return rules.options.maxSubCalls;
|
|
1374
|
+
if (spec.timeoutMs === null) return Infinity;
|
|
1375
|
+
if (typeof spec.timeoutMs === "number" && Number.isFinite(spec.timeoutMs) && spec.timeoutMs > 0) return Math.max(1, Math.floor(spec.timeoutMs / 6e4 * rules.options.maxSubCallsPerMinute));
|
|
1376
|
+
return rules.options.maxSubCalls;
|
|
1176
1377
|
}
|
|
1177
1378
|
/** 没有会话可写时的空记录。 */
|
|
1178
1379
|
const NO_DISPATCH_LOG = Object.freeze({
|
|
@@ -1183,21 +1384,6 @@ const NO_DISPATCH_LOG = Object.freeze({
|
|
|
1183
1384
|
const DISPATCH_LOG_TEXT_LIMIT = 8e3;
|
|
1184
1385
|
/**
|
|
1185
1386
|
* 造一条子调用的回放记录。
|
|
1186
|
-
*
|
|
1187
|
-
* 内核自己的 PTC 传输会为每条子调用 append `tool/ptc-dispatch-start` /
|
|
1188
|
-
* `tool/ptc-dispatch`,界面(`tool-call-tree`)靠这一对事件把子调用画到父调用下面。
|
|
1189
|
-
* 本行的程序交给内核运行时执行,而那两条事件不在运行时的对外契约里,只能在这里补。
|
|
1190
|
-
*
|
|
1191
|
-
* 三条约束:
|
|
1192
|
-
*
|
|
1193
|
-
* 1. **时序诚实**:`-start` 只在真正进入执行 lane 之后写,排队中的调用不写;
|
|
1194
|
-
* 2. **必须成对**:只写 start 会留下永远不收尾的行,比不显示更糟;
|
|
1195
|
-
* 3. **绝不带崩程序**:append 只是日志侧工作,异常一律吞掉——老代 harness 没有这两
|
|
1196
|
-
* 种事件类型,那里应当表现为「没有回放」,而不是 `code_run` 失败。
|
|
1197
|
-
*
|
|
1198
|
-
* @param agent - 调用方 agent(`agent.session.append` 的宿主)。
|
|
1199
|
-
* @param entry - 这次子调用的身份与参数。
|
|
1200
|
-
* @returns 回放记录;没有会话可写时是空实现。
|
|
1201
1387
|
*/
|
|
1202
1388
|
function createDispatchLog(agent, entry) {
|
|
1203
1389
|
const session = typeof agent === "object" && agent !== null ? agent.session : void 0;
|
|
@@ -1232,12 +1418,6 @@ function createDispatchLog(agent, entry) {
|
|
|
1232
1418
|
}
|
|
1233
1419
|
/**
|
|
1234
1420
|
* 把内容块裁到日志能承受的长度。
|
|
1235
|
-
*
|
|
1236
|
-
* 程序本身拿到的值不受影响;这里只是回放副本,所以宁可截断也不让一条子调用把整份
|
|
1237
|
-
* 大文件写进会话记录。
|
|
1238
|
-
*
|
|
1239
|
-
* @param content - 原始内容块。
|
|
1240
|
-
* @returns 可写进事件的副本。
|
|
1241
1421
|
*/
|
|
1242
1422
|
function clipContent(content) {
|
|
1243
1423
|
if (content === void 0) return [];
|
|
@@ -1254,12 +1434,6 @@ function clipContent(content) {
|
|
|
1254
1434
|
}
|
|
1255
1435
|
/**
|
|
1256
1436
|
* 一条调用所属调用树的根 id。
|
|
1257
|
-
*
|
|
1258
|
-
* `code_run` 不会被本行自己的程序嵌套调用,所以顶层工作量下就是它自己;这里仍按
|
|
1259
|
-
* `:ptc:` 前缀剥一层,避免将来出现嵌套时把根写错。
|
|
1260
|
-
*
|
|
1261
|
-
* @param callId - 本次调用的 id。
|
|
1262
|
-
* @returns 根 id。
|
|
1263
1437
|
*/
|
|
1264
1438
|
function rootCallIdOf(callId) {
|
|
1265
1439
|
const at = callId.indexOf(NESTED_CALL_ID_MARKER);
|
|
@@ -1269,6 +1443,18 @@ function rootCallIdOf(callId) {
|
|
|
1269
1443
|
function isRetainedPolicyChange(error) {
|
|
1270
1444
|
return (error instanceof Error ? error.message : String(error)).includes("release the retained environment before changing its sandbox policy");
|
|
1271
1445
|
}
|
|
1446
|
+
/** 校验单次沙箱提权参数配对(对齐 `@deepseek-ai/dsh-sandbox` 的 `validateEscalationArgs`)。 */
|
|
1447
|
+
function validateEscalationArgs(sandboxPermissions, justification) {
|
|
1448
|
+
if (sandboxPermissions !== void 0 && justification === void 0) throw new Error("invalid escalation: sandbox_permissions requires a justification");
|
|
1449
|
+
if (justification !== void 0 && sandboxPermissions === void 0) throw new Error("invalid escalation: justification is only valid together with sandbox_permissions");
|
|
1450
|
+
if (sandboxPermissions === void 0 && justification === void 0) return {};
|
|
1451
|
+
if (typeof sandboxPermissions !== "string" || !ESCALATION_TARGETS.includes(sandboxPermissions)) throw new Error("invalid sandbox_permissions: expected \"workspace-write\" or \"danger-full-access\"");
|
|
1452
|
+
if (typeof justification !== "string" || justification.trim().length === 0) throw new Error("invalid justification: expected a non-empty sentence");
|
|
1453
|
+
return {
|
|
1454
|
+
sandboxPermissions,
|
|
1455
|
+
justification
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1272
1458
|
/** 校验并读出工具参数。 */
|
|
1273
1459
|
function readArguments(args) {
|
|
1274
1460
|
if (typeof args !== "object" || args === null) throw new Error("codemode: expected an arguments object");
|
|
@@ -1277,16 +1463,19 @@ function readArguments(args) {
|
|
|
1277
1463
|
const description = record["description"];
|
|
1278
1464
|
if (typeof code !== "string" || code.trim().length === 0) throw new Error("codemode: invalid code: expected a non-empty string");
|
|
1279
1465
|
if (typeof description !== "string" || description.trim().length === 0) throw new Error("codemode: invalid description: expected a non-empty string");
|
|
1466
|
+
const escalation = validateEscalationArgs(record["sandbox_permissions"], record["justification"]);
|
|
1280
1467
|
const timeoutMs = record["timeoutMs"];
|
|
1281
1468
|
if (timeoutMs === void 0) return {
|
|
1282
1469
|
code,
|
|
1283
|
-
description
|
|
1470
|
+
description,
|
|
1471
|
+
...escalation
|
|
1284
1472
|
};
|
|
1285
1473
|
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("codemode: invalid timeoutMs: expected a positive finite number");
|
|
1286
1474
|
return {
|
|
1287
1475
|
code,
|
|
1288
1476
|
description,
|
|
1289
|
-
timeoutMs
|
|
1477
|
+
timeoutMs,
|
|
1478
|
+
...escalation
|
|
1290
1479
|
};
|
|
1291
1480
|
}
|
|
1292
1481
|
/** 子调用参数必须是可无损 JSON 化的值。 */
|
|
@@ -1307,14 +1496,161 @@ function isConcurrencySafe(entry, args) {
|
|
|
1307
1496
|
return false;
|
|
1308
1497
|
}
|
|
1309
1498
|
}
|
|
1310
|
-
/**
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1499
|
+
/**
|
|
1500
|
+
* 子调用并发与互斥调度器(对齐内核 `ptc.ts` 的读写屏障):
|
|
1501
|
+
*
|
|
1502
|
+
* - `parallel` 调用可并发重叠,最多 `maxParallel` 个同时在飞;
|
|
1503
|
+
* - `exclusive` 调用必须等待所有已在飞调用结算后独占运行,并在自身结算后才放行后续调用;
|
|
1504
|
+
* - 排队期间若外层运行已中止,直接放弃且不启动任务(保证未启动调用零 `start` 事件);
|
|
1505
|
+
* - `drain()` 等待全部已启动任务结算。
|
|
1506
|
+
*/
|
|
1507
|
+
function createSubCallScheduler(maxParallel, isAborted) {
|
|
1508
|
+
const cap = Math.max(1, maxParallel);
|
|
1509
|
+
const queue = [];
|
|
1510
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
1511
|
+
let exclusiveActive = false;
|
|
1512
|
+
const pump = () => {
|
|
1513
|
+
while (queue.length > 0) {
|
|
1514
|
+
const head = queue[0];
|
|
1515
|
+
if (isAborted()) {
|
|
1516
|
+
queue.shift();
|
|
1517
|
+
head.abandon();
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1520
|
+
if (exclusiveActive) return;
|
|
1521
|
+
if (head.mode === "exclusive") {
|
|
1522
|
+
if (inFlight.size > 0) return;
|
|
1523
|
+
exclusiveActive = true;
|
|
1524
|
+
queue.shift();
|
|
1525
|
+
head.start();
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
if (inFlight.size >= cap) return;
|
|
1529
|
+
queue.shift();
|
|
1530
|
+
head.start();
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
return {
|
|
1534
|
+
schedule(mode, task, onAbandon) {
|
|
1535
|
+
return new Promise((resolve, reject) => {
|
|
1536
|
+
queue.push({
|
|
1537
|
+
mode,
|
|
1538
|
+
abandon() {
|
|
1539
|
+
try {
|
|
1540
|
+
onAbandon();
|
|
1541
|
+
} catch (error) {
|
|
1542
|
+
reject(error);
|
|
1543
|
+
}
|
|
1544
|
+
},
|
|
1545
|
+
start() {
|
|
1546
|
+
const tracked = task().then(resolve, reject).finally(() => {
|
|
1547
|
+
inFlight.delete(tracked);
|
|
1548
|
+
if (mode === "exclusive") exclusiveActive = false;
|
|
1549
|
+
pump();
|
|
1550
|
+
}).then(() => void 0, () => void 0);
|
|
1551
|
+
inFlight.add(tracked);
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
pump();
|
|
1555
|
+
});
|
|
1556
|
+
},
|
|
1557
|
+
async drain() {
|
|
1558
|
+
pump();
|
|
1559
|
+
while (inFlight.size > 0) {
|
|
1560
|
+
await Promise.allSettled([...inFlight]);
|
|
1561
|
+
pump();
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
const JSON_INDENT = " ";
|
|
1567
|
+
const MAX_JSON_INDENT_CHARS = 10;
|
|
1568
|
+
/** 迭代式 JSON 渲染:与内核 `ptc.ts` 一致,深于 5 层后自动紧凑化,防止深层缩进膨胀。 */
|
|
1569
|
+
function renderProgramValue(value) {
|
|
1570
|
+
if (typeof value === "string") return value;
|
|
1571
|
+
let normalized;
|
|
1572
|
+
try {
|
|
1573
|
+
normalized = JSON.parse(JSON.stringify(value));
|
|
1574
|
+
} catch {
|
|
1575
|
+
return String(value);
|
|
1576
|
+
}
|
|
1577
|
+
if (typeof normalized === "string") return normalized;
|
|
1578
|
+
if (normalized === void 0) return "";
|
|
1579
|
+
const chunks = [];
|
|
1580
|
+
const tasks = [{
|
|
1581
|
+
kind: "value",
|
|
1582
|
+
value: normalized,
|
|
1583
|
+
depth: 0,
|
|
1584
|
+
compact: false
|
|
1585
|
+
}];
|
|
1586
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
1587
|
+
if (task.kind === "text") {
|
|
1588
|
+
chunks.push(task.text);
|
|
1589
|
+
continue;
|
|
1590
|
+
}
|
|
1591
|
+
const current = task.value;
|
|
1592
|
+
if (current === null || typeof current === "boolean" || typeof current === "number") {
|
|
1593
|
+
chunks.push(String(current));
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
if (typeof current === "string") {
|
|
1597
|
+
chunks.push(JSON.stringify(current));
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
const compact = task.compact || (task.depth + 1) * 2 > MAX_JSON_INDENT_CHARS;
|
|
1601
|
+
const childDepth = task.depth + 1;
|
|
1602
|
+
if (Array.isArray(current)) {
|
|
1603
|
+
chunks.push("[");
|
|
1604
|
+
if (current.length === 0) {
|
|
1605
|
+
chunks.push("]");
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
tasks.push({
|
|
1609
|
+
kind: "text",
|
|
1610
|
+
text: compact ? "]" : `\n${JSON_INDENT.repeat(task.depth)}]`
|
|
1611
|
+
});
|
|
1612
|
+
for (let index = current.length - 1; index >= 0; index -= 1) {
|
|
1613
|
+
tasks.push({
|
|
1614
|
+
kind: "value",
|
|
1615
|
+
value: current[index],
|
|
1616
|
+
depth: childDepth,
|
|
1617
|
+
compact
|
|
1618
|
+
});
|
|
1619
|
+
tasks.push({
|
|
1620
|
+
kind: "text",
|
|
1621
|
+
text: compact ? index === 0 ? "" : "," : `${index === 0 ? "\n" : ",\n"}${JSON_INDENT.repeat(childDepth)}`
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
if (typeof current === "object") {
|
|
1627
|
+
const record = current;
|
|
1628
|
+
const keys = Object.keys(record);
|
|
1629
|
+
chunks.push("{");
|
|
1630
|
+
if (keys.length === 0) {
|
|
1631
|
+
chunks.push("}");
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
tasks.push({
|
|
1635
|
+
kind: "text",
|
|
1636
|
+
text: compact ? "}" : `\n${JSON_INDENT.repeat(task.depth)}}`
|
|
1637
|
+
});
|
|
1638
|
+
for (let index = keys.length - 1; index >= 0; index -= 1) {
|
|
1639
|
+
const key = keys[index];
|
|
1640
|
+
tasks.push({
|
|
1641
|
+
kind: "value",
|
|
1642
|
+
value: record[key],
|
|
1643
|
+
depth: childDepth,
|
|
1644
|
+
compact
|
|
1645
|
+
});
|
|
1646
|
+
tasks.push({
|
|
1647
|
+
kind: "text",
|
|
1648
|
+
text: compact ? `${index === 0 ? "" : ","}${JSON.stringify(key)}:` : `${index === 0 ? "\n" : ",\n"}${JSON_INDENT.repeat(childDepth)}${JSON.stringify(key)}: `
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
return chunks.join("");
|
|
1318
1654
|
}
|
|
1319
1655
|
//#endregion
|
|
1320
1656
|
//#region src/entry-codemode.ts
|
|
@@ -1348,6 +1684,11 @@ const name = "codemode";
|
|
|
1348
1684
|
const inject = ["tools", "systemPrompt"];
|
|
1349
1685
|
/** 决策环长度:只留给 `code_debug`。 */
|
|
1350
1686
|
const DEBUG_RING = 50;
|
|
1687
|
+
/** 严格加宽阶梯(对齐 `@deepseek-ai/dsh-sandbox` 的 `WIDER_MODES`)。 */
|
|
1688
|
+
const WIDER_SANDBOX_MODES = {
|
|
1689
|
+
"read-only": ["workspace-write", "danger-full-access"],
|
|
1690
|
+
"workspace-write": ["danger-full-access"]
|
|
1691
|
+
};
|
|
1351
1692
|
/**
|
|
1352
1693
|
* 装配本行。
|
|
1353
1694
|
*
|
|
@@ -1409,12 +1750,50 @@ function apply(ctx, config = {}) {
|
|
|
1409
1750
|
let catalog = [];
|
|
1410
1751
|
const debugRing = [];
|
|
1411
1752
|
let firstDescribeLogged = false;
|
|
1753
|
+
const escalateSandbox = async (input) => {
|
|
1754
|
+
const record = typeof input.standingPolicy === "object" && input.standingPolicy !== null ? input.standingPolicy : void 0;
|
|
1755
|
+
const effectiveMode = typeof record?.["mode"] === "string" ? record["mode"] : void 0;
|
|
1756
|
+
if (record === void 0 || effectiveMode === void 0) throw new Error("sandbox_permissions is not available for this PTC runtime");
|
|
1757
|
+
if (input.requestedMode === effectiveMode) return input.standingPolicy;
|
|
1758
|
+
if (!(WIDER_SANDBOX_MODES[effectiveMode] ?? []).includes(input.requestedMode)) throw new Error(`sandbox escalation to "${input.requestedMode}" is not strictly wider than this call's current "${effectiveMode}" mode`);
|
|
1759
|
+
const approver = serviceOf("approval");
|
|
1760
|
+
if (typeof approver?.request !== "function") throw new Error(`sandbox escalation to "${input.requestedMode}" requires approval, but no approval service is composed`);
|
|
1761
|
+
if (input.agent === void 0) throw new Error(`sandbox escalation to "${input.requestedMode}" requires approval, but the call has no agent to route it through`);
|
|
1762
|
+
const outcome = await approver.request({
|
|
1763
|
+
agent: input.agent,
|
|
1764
|
+
toolName: input.toolName,
|
|
1765
|
+
callId: input.callId,
|
|
1766
|
+
reason: `escalate sandbox to ${input.requestedMode}: ${input.justification}`,
|
|
1767
|
+
displayReason: {
|
|
1768
|
+
en: `Allow this program to run once with ${input.requestedMode} access: ${input.justification}`,
|
|
1769
|
+
zh: `允许本次程序以 ${input.requestedMode} 权限单次运行:${input.justification}`
|
|
1770
|
+
},
|
|
1771
|
+
signal: input.signal
|
|
1772
|
+
});
|
|
1773
|
+
switch (outcome) {
|
|
1774
|
+
case "allowed-once": return {
|
|
1775
|
+
...record,
|
|
1776
|
+
mode: input.requestedMode
|
|
1777
|
+
};
|
|
1778
|
+
case "rejected": throw new Error(`the user rejected escalating this program to "${input.requestedMode}"; it stays denied, so stop and explain instead of working around it`);
|
|
1779
|
+
case "cancelled": throw new Error(`approval for escalating to "${input.requestedMode}" was cancelled`);
|
|
1780
|
+
case "unavailable": throw new Error(`sandbox escalation to "${input.requestedMode}" requires approval, but no approval channel is available`);
|
|
1781
|
+
default: throw new Error(`unexpected approval outcome: ${String(outcome)}`);
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1412
1784
|
const host = {
|
|
1413
1785
|
catalog: () => catalog,
|
|
1414
1786
|
runtime: runtimeOf,
|
|
1415
1787
|
stateOwner: (agent) => retention.owner(agent),
|
|
1416
1788
|
releaseOwner: (owner) => retention.release(owner),
|
|
1417
1789
|
runAuthority,
|
|
1790
|
+
escalateSandbox,
|
|
1791
|
+
createImageContext(content) {
|
|
1792
|
+
return createUserMessage({
|
|
1793
|
+
content,
|
|
1794
|
+
source: { kind: PLUGIN_MESSAGE_SOURCE_KIND }
|
|
1795
|
+
});
|
|
1796
|
+
},
|
|
1418
1797
|
async callTool(request) {
|
|
1419
1798
|
const call = {
|
|
1420
1799
|
type: "tool-call",
|
|
@@ -1436,16 +1815,20 @@ function apply(ctx, config = {}) {
|
|
|
1436
1815
|
const result = batch.results?.[0]?.result;
|
|
1437
1816
|
if (result === void 0) return { error: "sub-call produced no result" };
|
|
1438
1817
|
const content = result.content ?? [];
|
|
1818
|
+
const additionalContexts = Array.isArray(batch.additionalContexts) && batch.additionalContexts.length > 0 ? batch.additionalContexts : void 0;
|
|
1439
1819
|
if (result.isError === true) return {
|
|
1440
1820
|
error: errorTextOf(result),
|
|
1441
1821
|
content,
|
|
1442
|
-
...errorInfoOf(result)
|
|
1822
|
+
...errorInfoOf(result),
|
|
1823
|
+
...additionalContexts === void 0 ? {} : { additionalContexts }
|
|
1443
1824
|
};
|
|
1444
1825
|
const meta = result.meta;
|
|
1445
1826
|
return {
|
|
1446
1827
|
...result.value !== void 0 ? { value: result.value } : { text: textOf(result.content) },
|
|
1447
1828
|
content,
|
|
1448
|
-
...meta === void 0 ? {} : { meta }
|
|
1829
|
+
...meta === void 0 ? {} : { meta },
|
|
1830
|
+
...additionalContexts === void 0 ? {} : { additionalContexts },
|
|
1831
|
+
...batch.concluded === true ? { concluded: true } : {}
|
|
1449
1832
|
};
|
|
1450
1833
|
}
|
|
1451
1834
|
};
|
|
@@ -1461,11 +1844,15 @@ function apply(ctx, config = {}) {
|
|
|
1461
1844
|
delegate,
|
|
1462
1845
|
view,
|
|
1463
1846
|
viewForName: (toolName) => resolveNameView(tools, scopeKey, toolName),
|
|
1464
|
-
sectionText: () =>
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1847
|
+
sectionText: () => {
|
|
1848
|
+
const runtime = runtimeOf();
|
|
1849
|
+
return buildCodemodeSectionText({
|
|
1850
|
+
runToolName: rules.options.runToolName,
|
|
1851
|
+
metaTools: rules.options.metaTools,
|
|
1852
|
+
language: runtime?.language,
|
|
1853
|
+
programFormat: runtime?.programFormat
|
|
1854
|
+
});
|
|
1855
|
+
},
|
|
1469
1856
|
onCatalog: (definitions) => {
|
|
1470
1857
|
catalog = definitions.map(toCatalogEntry).filter((entry) => entry !== void 0);
|
|
1471
1858
|
},
|