@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.
Files changed (34) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -1
  3. package/lib/client.js +1578 -232
  4. package/lib/client.js.map +1 -1
  5. package/lib/codemode.js +537 -150
  6. package/lib/index.js +177 -183
  7. package/lib/llm-compat-u9mor0uu.js +110 -0
  8. package/lib/types/client/codemode/CodeRunToolView.d.ts +68 -0
  9. package/lib/types/client/codemode/CodeRunToolView.d.ts.map +1 -0
  10. package/lib/types/client/codemode/highlight.d.ts +25 -0
  11. package/lib/types/client/codemode/highlight.d.ts.map +1 -0
  12. package/lib/types/client/codemode/styles.d.ts +11 -0
  13. package/lib/types/client/codemode/styles.d.ts.map +1 -0
  14. package/lib/types/client/codemode/tool-name.d.ts +10 -0
  15. package/lib/types/client/codemode/tool-name.d.ts.map +1 -0
  16. package/lib/types/client/index.d.ts.map +1 -1
  17. package/lib/types/client/locales-ru/hub.d.ts.map +1 -1
  18. package/lib/types/client/locales.d.ts +23 -1
  19. package/lib/types/client/locales.d.ts.map +1 -1
  20. package/lib/types/compat.d.ts +1 -1
  21. package/lib/types/entry-codemode.d.ts.map +1 -1
  22. package/lib/types/host/codemode/executor.d.ts.map +1 -1
  23. package/lib/types/host/codemode/rules.d.ts +16 -6
  24. package/lib/types/host/codemode/rules.d.ts.map +1 -1
  25. package/lib/types/host/codemode/transport.d.ts +82 -41
  26. package/lib/types/host/codemode/transport.d.ts.map +1 -1
  27. package/lib/types/host/command-code/model-catalog.d.ts +14 -14
  28. package/lib/types/host/command-code/model-catalog.d.ts.map +1 -1
  29. package/lib/types/host/command-code/routes.d.ts.map +1 -1
  30. package/lib/types/host/preset-sync.d.ts +19 -4
  31. package/lib/types/host/preset-sync.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/presets/dispatch/README.md +1 -3
  34. 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(["cache_warmup_result", "dispatch_ledger"]);
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 " + input.runToolName + ".",
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
- "```js",
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
- * 程序自己用 `Promise.all` 决定并发。本模块只做一件额外的事:把 `isConcurrencySafe` 不为
990
- * `true` 的调用串行化成互斥段,只读调用放行到 `maxParallelSubCalls` 个并发。与内核的
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
- return {
1119
+ const definition = {
1024
1120
  name: runToolName,
1025
- description: "Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the body of an async function (erasable syntax only; top-level await and return work), and `description`, a short summary of what the program does. Call tools as `await tools.name(args)`. Only what you print or return is program output.",
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
- for (const line of value.logs ?? []) if (line.length > 0) parts.push(line);
1070
- if (value.result !== void 0) try {
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 runSignal = AbortSignal.any([exec.signal, new AbortController().signal]);
1088
- const nestedSignal = markNested(runSignal) ?? runSignal;
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
- const limit = rules.options.maxSubCalls;
1189
+ let maxSubCalls = rules.options.maxSubCalls;
1091
1190
  let attempts = 0;
1092
- const exclusive = createMutex();
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
- functions[entry.name] = async (rawArgs) => {
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 > limit) throw new Error("program sub-call limit exceeded: " + String(limit) + " calls");
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: parsed
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
- return isConcurrencySafe(definitions.get(entry.name), parsed) ? run() : exclusive.run(run);
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
- result = await attempt();
1159
- } catch (error) {
1160
- if (!isRetainedPolicyChange(error)) throw error;
1161
- await host.releaseOwner(owner);
1162
- host.stateOwner(exec.agent);
1163
- result = await attempt();
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
- /** 一个 FIFO 互斥段:写类调用串行执行。 */
1311
- function createMutex() {
1312
- let tail = Promise.resolve();
1313
- return { run(task) {
1314
- const next = tail.then(task, task);
1315
- tail = next.catch(() => void 0);
1316
- return next;
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: () => buildCodemodeSectionText({
1465
- runToolName: rules.options.runToolName,
1466
- metaTools: rules.options.metaTools,
1467
- programFormat: runtimeOf()?.programFormat
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
  },