@qilin-agent/tools 3.1.5 → 3.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Service } from "@qilin-agent/kylin";
1
+ import { Service, symbols } from "@qilin-agent/kylin";
2
2
  import z from "@qilin-agent/schemastery";
3
3
  import { AnonymousEntries, NamedEntries, ScopedLayers, scopeOf, scopeTarget } from "@qilin-agent/scope";
4
4
  import { HarnessError, createUserMessage } from "@qilin-agent/llm";
@@ -884,6 +884,63 @@ function defineTool(options) {
884
884
  return tool;
885
885
  }
886
886
  //#endregion
887
+ //#region lib/types/ptc-calls.js
888
+ /** Keeps calls under a changed policy from overlapping their canceled predecessors. */
889
+ var PtcCalls = class {
890
+ calls = /* @__PURE__ */ new Map();
891
+ disposed = false;
892
+ /**
893
+ * Observe policy changes for the owning tool registry.
894
+ * @param ctx - Registry lifecycle and Session events.
895
+ */
896
+ constructor(ctx) {
897
+ ctx.on("session/event", (session, event) => {
898
+ if (event.type !== "sandbox/mode") return;
899
+ for (const call of this.calls.get(session.id) ?? []) if (call.mode !== event.data.mode) call.controller.abort(/* @__PURE__ */ new Error("sandbox mode changed"));
900
+ }, { global: true });
901
+ ctx.effect(() => async () => {
902
+ this.disposed = true;
903
+ const calls = [...this.calls.values()].flatMap((calls) => [...calls]);
904
+ for (const call of calls) call.controller.abort(/* @__PURE__ */ new Error("PTC tool registry disposed"));
905
+ await Promise.all(calls.map((call) => call.done));
906
+ }, "PTC calls");
907
+ }
908
+ /**
909
+ * Await canceled predecessors before resolving authority and admitting a call.
910
+ * @param sessionId - Calling Session.
911
+ * @param signal - Caller cancellation.
912
+ * @param mode - Current standing mode, read after preceding cleanup.
913
+ * @param run - Complete call, including nested-tool cleanup.
914
+ * @returns The call result.
915
+ */
916
+ async run(sessionId, signal, mode, run) {
917
+ for (;;) {
918
+ const pending = [...this.calls.get(sessionId) ?? []].filter((call) => call.controller.signal.aborted);
919
+ if (pending.length === 0) break;
920
+ await Promise.all(pending.map((call) => call.done));
921
+ }
922
+ signal.throwIfAborted();
923
+ if (this.disposed) throw new Error("PTC tool registry disposed");
924
+ const controller = new AbortController();
925
+ const done = Promise.withResolvers();
926
+ const call = {
927
+ mode: mode(),
928
+ controller,
929
+ done: done.promise
930
+ };
931
+ let calls = this.calls.get(sessionId);
932
+ if (calls === void 0) this.calls.set(sessionId, calls = /* @__PURE__ */ new Set());
933
+ calls.add(call);
934
+ try {
935
+ return await run(AbortSignal.any([signal, controller.signal]));
936
+ } finally {
937
+ calls.delete(call);
938
+ if (calls.size === 0) this.calls.delete(sessionId);
939
+ done.resolve(void 0);
940
+ }
941
+ }
942
+ };
943
+ //#endregion
887
944
  //#region lib/types/ptc.js
888
945
  /**
889
946
  * PTC mode `run_code` transport. Programs call the registry's agent-visible
@@ -894,23 +951,27 @@ function defineTool(options) {
894
951
  */
895
952
  /** The model-facing name of the PTC mode tool. */
896
953
  const RUN_CODE_NAME = "run_code";
897
- /**
898
- * The TypeScript flavor: the fallback for a schema read with no runtime
899
- * mounted ({@link resolveFlavor} owns which readers reach that). A real
900
- * assembly always resolves a runtime first, so the model never sees this
901
- * fallback outside its own language.
902
- */
903
- const TYPESCRIPT_FLAVOR = {
904
- description: "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work). Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return is program output — curate it. Image-bearing subtool results are attached after the run.",
954
+ /** Keep tool-call rules here; the runtime owns evaluation syntax and state retention. */
955
+ function runCodeFlavor(language, execution = {
956
+ programDescription: "the program to execute",
957
+ outputInstruction: "Curate the program output.",
958
+ codeDescription: `The ${language} program to execute, following this tool's execution instructions.`
959
+ }) {
960
+ return {
961
+ description: `Execute a ${language} program against the available tools. Takes two required arguments: \`description\`, a short summary of what the program does, and \`code\`, ${execution.programDescription}. Call tools as \`await tools.name(args)\` per the declarations in the system prompt. ${execution.outputInstruction} Image-bearing subtool results are attached after the run.`,
962
+ codeDescription: execution.codeDescription
963
+ };
964
+ }
965
+ const TYPESCRIPT_FLAVOR = runCodeFlavor("TypeScript");
966
+ const TYPESCRIPT_FUNCTION_FLAVOR = runCodeFlavor("TypeScript", {
967
+ programDescription: "the BODY of an async function (erasable syntax only; top-level `await` and `return` work)",
968
+ outputInstruction: "Only what you print or return is program output — curate it.",
905
969
  codeDescription: "The program: the body of an async TypeScript function."
906
- };
970
+ });
907
971
  /** Per-language `run_code` schema flavors (see {@link RunCodeFlavor}); one entry per {@link PtcSdkLanguage}. */
908
972
  const RUN_CODE_FLAVORS = {
909
973
  typescript: TYPESCRIPT_FLAVOR,
910
- python: {
911
- description: "Execute a Python program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the BODY of an async function (top-level `await` and `return` work). Call tools as `await tools.name(args)` per the declarations in the system prompt. Use `print(...)` and/or `return <value>` for program output — curate it. Image-bearing subtool results are attached after the run.",
912
- codeDescription: "The program: the body of an async Python function."
913
- }
974
+ python: runCodeFlavor("Python")
914
975
  };
915
976
  /**
916
977
  * The `description` parameter's model-facing description: language-independent
@@ -972,7 +1033,7 @@ function resolveFlavor(peekRuntime) {
972
1033
  const known = Object.keys(RUN_CODE_FLAVORS).map((name) => JSON.stringify(name)).join(", ");
973
1034
  throw new Error(`qilin-tools: no run_code schema flavor registered for runtime language ${JSON.stringify(runtime.language)} (known: ${known})`);
974
1035
  }
975
- return flavor;
1036
+ return runtime.language === "typescript" && runtime.programFormat === "function-body" ? TYPESCRIPT_FUNCTION_FLAVOR : flavor;
976
1037
  }
977
1038
  /**
978
1039
  * Thrown by `run_code` when the program run itself failed — a program
@@ -1104,18 +1165,21 @@ function renderValue(value) {
1104
1165
  return typeof value === "string" ? value : renderJsonValue(value);
1105
1166
  }
1106
1167
  /**
1107
- * Build the `run_code` {@link ToolDefinition}: required `description` and
1108
- * `code` parameters, executed through the dispatch bridge described
1168
+ * Build the `run_code` {@link ToolDefinition}: required `code` and
1169
+ * `description` parameters, executed through the dispatch bridge described
1109
1170
  * above. The
1110
1171
  * registry reserves it as presentation infrastructure under non-native modes,
1111
1172
  * outside the filterable global/scoped capability layers.
1112
1173
  * @param registry - the owning registry (sub-calls go through its `execute`,
1113
1174
  * bindings cover its registered tools).
1114
1175
  * @param options - the registry-private capabilities described above.
1176
+ * @param ctx - Owning registry context, independent of the calling Agent.
1115
1177
  * @returns the registry-ready definition.
1116
1178
  */
1117
- function createRunCodeTool(registry, options) {
1179
+ function createRunCodeTool(registry, options, ctx) {
1118
1180
  const { requireRuntime, peekRuntime, maxParallel, shapeDispatchLog } = options;
1181
+ const stateOwners = /* @__PURE__ */ new WeakMap();
1182
+ const calls = new PtcCalls(ctx);
1119
1183
  const definition = defineTool({
1120
1184
  name: RUN_CODE_NAME,
1121
1185
  description: TYPESCRIPT_FLAVOR.description,
@@ -1182,265 +1246,287 @@ function createRunCodeTool(registry, options) {
1182
1246
  async execute(args, exec) {
1183
1247
  if (args.description.trim().length === 0) throw new Error("invalid description: expected a non-empty string");
1184
1248
  const runtime = requireRuntime();
1185
- validateEscalationArgs(args.sandbox_permissions, args.justification);
1186
- if (args.timeoutMs !== void 0 && runtime.timeout === void 0) throw new Error("timeoutMs is not available for this PTC runtime");
1187
- if (args.timeoutMs !== void 0 && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) throw new Error("invalid timeoutMs: expected a positive finite number");
1188
- const standingPolicy = runtime.sandboxMode === void 0 ? void 0 : options.resolveSandboxPolicy(exec);
1189
- let policy = standingPolicy;
1190
- if (args.sandbox_permissions !== void 0 && args.justification !== void 0) {
1191
- if (standingPolicy === void 0) throw new Error("sandbox_permissions is not available for this PTC runtime");
1192
- const approvedMode = await approveEscalation({
1193
- requestedMode: args.sandbox_permissions,
1194
- justification: args.justification,
1195
- effectiveMode: standingPolicy.mode,
1196
- subject: "program"
1197
- }, {
1198
- approver: options.peekApprover(),
1199
- agent: exec.agent,
1200
- callId: exec.callId,
1201
- toolName: RUN_CODE_NAME,
1202
- signal: exec.signal
1249
+ const run = async (callSignal) => {
1250
+ validateEscalationArgs(args.sandbox_permissions, args.justification);
1251
+ if (args.timeoutMs !== void 0 && runtime.timeout === void 0) throw new Error("timeoutMs is not available for this PTC runtime");
1252
+ if (args.timeoutMs !== void 0 && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) throw new Error("invalid timeoutMs: expected a positive finite number");
1253
+ const standingPolicy = runtime.sandboxMode === void 0 ? void 0 : options.resolveSandboxPolicy(exec);
1254
+ let policy = standingPolicy;
1255
+ if (args.sandbox_permissions !== void 0 && args.justification !== void 0) {
1256
+ if (standingPolicy === void 0) throw new Error("sandbox_permissions is not available for this PTC runtime");
1257
+ const approvedMode = await approveEscalation({
1258
+ requestedMode: args.sandbox_permissions,
1259
+ justification: args.justification,
1260
+ effectiveMode: standingPolicy.mode,
1261
+ subject: "program"
1262
+ }, {
1263
+ approver: options.peekApprover(),
1264
+ agent: exec.agent,
1265
+ callId: exec.callId,
1266
+ toolName: RUN_CODE_NAME,
1267
+ signal: callSignal
1268
+ });
1269
+ policy = {
1270
+ ...standingPolicy,
1271
+ mode: approvedMode
1272
+ };
1273
+ }
1274
+ const cwd = await options.resolveWorkingDirectory({
1275
+ ...exec,
1276
+ signal: callSignal
1203
1277
  });
1204
- policy = {
1205
- ...standingPolicy,
1206
- mode: approvedMode
1278
+ callSignal.throwIfAborted();
1279
+ const runController = new AbortController();
1280
+ const onOuterAbort = () => {
1281
+ runController.abort(callSignal.reason);
1207
1282
  };
1208
- }
1209
- const cwd = await options.resolveWorkingDirectory(exec);
1210
- exec.signal.throwIfAborted();
1211
- const runController = new AbortController();
1212
- const onOuterAbort = () => {
1213
- runController.abort(exec.signal.reason);
1214
- };
1215
- exec.signal.addEventListener("abort", onOuterAbort, { once: true });
1216
- let dispatches = 0;
1217
- const pendingQueue = [];
1218
- const inFlight = /* @__PURE__ */ new Set();
1219
- /** Tracked settle-event side work (log-content listener + append), drained at run settlement. */
1220
- const logWork = /* @__PURE__ */ new Set();
1221
- const commitQueue = [];
1222
- let exclusiveActive = false;
1223
- let driving = false;
1224
- let driverRun = Promise.resolve();
1225
- let wake;
1226
- const wakeup = () => {
1227
- const release = wake;
1228
- wake = void 0;
1229
- release?.();
1230
- };
1231
- /**
1232
- * The single ordered lane. Each pass commits the head-of-line settled
1233
- * dispatch (ordered post-execute), then starts the next queued entry if
1234
- * its slot is free (ordered pre-execute), and otherwise sleeps until a
1235
- * body settles or a new submission arrives. One run reaching the
1236
- * empty-queues/empty-pool state is quiescence.
1237
- */
1238
- const drive = () => {
1239
- if (driving) return driverRun;
1240
- driving = true;
1241
- driverRun = (async () => {
1242
- try {
1243
- for (;;) {
1244
- const signal = new Promise((resolve) => {
1245
- wake = resolve;
1246
- });
1247
- const commitHead = commitQueue[0];
1248
- if (commitHead !== void 0 && commitHead.settled) {
1249
- commitQueue.shift();
1250
- await commitHead.commit();
1251
- if (commitHead.mode === "exclusive") exclusiveActive = false;
1252
- continue;
1253
- }
1254
- const head = pendingQueue[0];
1255
- if (head !== void 0) {
1256
- if (runController.signal.aborted) {
1257
- pendingQueue.shift();
1258
- head.abandon();
1283
+ callSignal.addEventListener("abort", onOuterAbort, { once: true });
1284
+ let dispatches = 0;
1285
+ const pendingQueue = [];
1286
+ const inFlight = /* @__PURE__ */ new Set();
1287
+ /** Tracked settle-event side work (log-content listener + append), drained at run settlement. */
1288
+ const logWork = /* @__PURE__ */ new Set();
1289
+ const commitQueue = [];
1290
+ let exclusiveActive = false;
1291
+ let driving = false;
1292
+ let driverRun = Promise.resolve();
1293
+ let wake;
1294
+ const wakeup = () => {
1295
+ const release = wake;
1296
+ wake = void 0;
1297
+ release?.();
1298
+ };
1299
+ /**
1300
+ * The single ordered lane. Each pass commits the head-of-line settled
1301
+ * dispatch (ordered post-execute), then starts the next queued entry if
1302
+ * its slot is free (ordered pre-execute), and otherwise sleeps until a
1303
+ * body settles or a new submission arrives. One run reaching the
1304
+ * empty-queues/empty-pool state is quiescence.
1305
+ */
1306
+ const drive = () => {
1307
+ if (driving) return driverRun;
1308
+ driving = true;
1309
+ driverRun = (async () => {
1310
+ try {
1311
+ for (;;) {
1312
+ const signal = new Promise((resolve) => {
1313
+ wake = resolve;
1314
+ });
1315
+ const commitHead = commitQueue[0];
1316
+ if (commitHead !== void 0 && commitHead.settled) {
1317
+ commitQueue.shift();
1318
+ await commitHead.commit();
1319
+ if (commitHead.mode === "exclusive") exclusiveActive = false;
1259
1320
  continue;
1260
1321
  }
1261
- const mode = head.classify();
1262
- if (!exclusiveActive && (mode === "exclusive" ? inFlight.size === 0 : inFlight.size < maxParallel)) {
1263
- if (mode === "exclusive") exclusiveActive = true;
1264
- head.mode = mode;
1265
- pendingQueue.shift();
1266
- commitQueue.push(head);
1267
- await head.start();
1268
- const flight = head.flight.finally(() => {
1269
- inFlight.delete(flight);
1270
- wakeup();
1271
- });
1272
- inFlight.add(flight);
1273
- continue;
1322
+ const head = pendingQueue[0];
1323
+ if (head !== void 0) {
1324
+ if (runController.signal.aborted) {
1325
+ pendingQueue.shift();
1326
+ head.abandon();
1327
+ continue;
1328
+ }
1329
+ const mode = head.classify();
1330
+ if (!exclusiveActive && (mode === "exclusive" ? inFlight.size === 0 : inFlight.size < maxParallel)) {
1331
+ if (mode === "exclusive") exclusiveActive = true;
1332
+ head.mode = mode;
1333
+ pendingQueue.shift();
1334
+ commitQueue.push(head);
1335
+ await head.start();
1336
+ const flight = head.flight.finally(() => {
1337
+ inFlight.delete(flight);
1338
+ wakeup();
1339
+ });
1340
+ inFlight.add(flight);
1341
+ continue;
1342
+ }
1274
1343
  }
1344
+ if (pendingQueue.length === 0 && commitQueue.length === 0 && inFlight.size === 0) return;
1345
+ await signal;
1275
1346
  }
1276
- if (pendingQueue.length === 0 && commitQueue.length === 0 && inFlight.size === 0) return;
1277
- await signal;
1347
+ } finally {
1348
+ driving = false;
1349
+ wake = void 0;
1278
1350
  }
1279
- } finally {
1280
- driving = false;
1281
- wake = void 0;
1282
- }
1283
- })();
1284
- return driverRun;
1285
- };
1286
- /** Every dispatch settled AND committed; nothing can start (the run is aborted at call time). */
1287
- const drainDispatches = async () => {
1288
- await drive();
1289
- while (logWork.size > 0) await Promise.allSettled([...logWork]);
1290
- };
1291
- const runOver = () => runController.signal.aborted;
1292
- const binding = (schema) => async (rawArgs) => {
1293
- const { name } = schema;
1294
- if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`);
1295
- const normalized = jsonNormalizeArgs(rawArgs);
1296
- const n = ++dispatches;
1297
- const subCallId = brandString(`${String(exec.callId)}:ptc:${n}`);
1298
- const input = {
1299
- callId: subCallId,
1300
- rootCallId: exec.rootCallId,
1301
- name,
1302
- schema,
1303
- arguments: normalized.dispatched,
1304
- ...exec.agent ? { agent: exec.agent } : {},
1305
- parent: exec.token,
1306
- signal: runController.signal
1351
+ })();
1352
+ return driverRun;
1307
1353
  };
1308
- const scheduler = registry[TOOL_RUNTIME_SCHEDULER];
1309
- const outcome = await new Promise((resolve, reject) => {
1310
- let parked;
1311
- const settle = (result) => {
1312
- resolve(result.isError ? {
1313
- isError: true,
1314
- message: result.error.message
1315
- } : {
1316
- isError: false,
1317
- value: result.value
1318
- });
1319
- const agent = exec.agent;
1320
- if (agent === void 0) return;
1321
- const task = (async () => {
1322
- const logged = await shapeDispatchLog({
1323
- exec,
1324
- agent,
1325
- subCallId,
1326
- name,
1327
- isError: result.isError,
1328
- content: result.content
1329
- });
1330
- agent.session.append("tool/ptc-dispatch", {
1331
- rootCallId: exec.rootCallId,
1332
- parentCallId: exec.callId,
1333
- subCallId,
1334
- name,
1335
- arguments: normalized.logged,
1336
- isError: result.isError,
1337
- ...result.error?.info === void 0 ? {} : { error: result.error.info },
1338
- content: logged,
1339
- ...result.meta === void 0 ? {} : { meta: result.meta }
1340
- });
1341
- })().finally(() => {
1342
- logWork.delete(task);
1343
- });
1344
- logWork.add(task);
1354
+ /** Every dispatch settled AND committed; nothing can start (the run is aborted at call time). */
1355
+ const drainDispatches = async () => {
1356
+ await drive();
1357
+ while (logWork.size > 0) await Promise.allSettled([...logWork]);
1358
+ };
1359
+ const runOver = () => runController.signal.aborted;
1360
+ const binding = (schema) => async (rawArgs) => {
1361
+ const { name } = schema;
1362
+ if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} not dispatched`);
1363
+ const normalized = jsonNormalizeArgs(rawArgs);
1364
+ const n = ++dispatches;
1365
+ const subCallId = brandString(`${String(exec.callId)}:ptc:${n}`);
1366
+ const input = {
1367
+ callId: subCallId,
1368
+ rootCallId: exec.rootCallId,
1369
+ name,
1370
+ schema,
1371
+ arguments: normalized.dispatched,
1372
+ ...exec.agent ? { agent: exec.agent } : {},
1373
+ parent: exec.token,
1374
+ signal: runController.signal
1345
1375
  };
1346
- pendingQueue.push({
1347
- flight: Promise.resolve(),
1348
- settled: false,
1349
- classify: () => registry.executionMode(input).kind,
1350
- abandon: () => {
1351
- reject(/* @__PURE__ */ new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} tool call abandoned`));
1352
- },
1353
- async start() {
1354
- exec.agent?.session.append("tool/ptc-dispatch-start", {
1355
- rootCallId: exec.rootCallId,
1356
- parentCallId: exec.callId,
1357
- subCallId,
1358
- name,
1359
- arguments: normalized.logged
1376
+ const scheduler = registry[TOOL_RUNTIME_SCHEDULER];
1377
+ const outcome = await new Promise((resolve, reject) => {
1378
+ let parked;
1379
+ const settle = (result) => {
1380
+ resolve(result.isError ? {
1381
+ isError: true,
1382
+ message: result.error.message
1383
+ } : {
1384
+ isError: false,
1385
+ value: result.value
1386
+ });
1387
+ const agent = exec.agent;
1388
+ if (agent === void 0) return;
1389
+ const task = (async () => {
1390
+ const logged = await shapeDispatchLog({
1391
+ exec,
1392
+ agent,
1393
+ subCallId,
1394
+ name,
1395
+ isError: result.isError,
1396
+ content: result.content
1397
+ });
1398
+ agent.session.append("tool/ptc-dispatch", {
1399
+ rootCallId: exec.rootCallId,
1400
+ parentCallId: exec.callId,
1401
+ subCallId,
1402
+ name,
1403
+ arguments: normalized.logged,
1404
+ isError: result.isError,
1405
+ ...result.error?.info === void 0 ? {} : { error: result.error.info },
1406
+ content: logged,
1407
+ ...result.meta === void 0 ? {} : { meta: result.meta }
1408
+ });
1409
+ })().finally(() => {
1410
+ logWork.delete(task);
1360
1411
  });
1361
- const prepared = await scheduler.prepare(input);
1362
- if (prepared.kind === "dispatch") {
1363
- this.flight = scheduler.dispatch(prepared.exec).then((dispatchOutcome) => {
1364
- parked = {
1365
- kind: dispatchOutcome.kind,
1366
- exec: prepared.exec,
1367
- result: dispatchOutcome.result
1368
- };
1369
- this.settled = true;
1412
+ logWork.add(task);
1413
+ };
1414
+ pendingQueue.push({
1415
+ flight: Promise.resolve(),
1416
+ settled: false,
1417
+ classify: () => registry.executionMode(input).kind,
1418
+ abandon: () => {
1419
+ reject(/* @__PURE__ */ new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} tool call abandoned`));
1420
+ },
1421
+ async start() {
1422
+ exec.agent?.session.append("tool/ptc-dispatch-start", {
1423
+ rootCallId: exec.rootCallId,
1424
+ parentCallId: exec.callId,
1425
+ subCallId,
1426
+ name,
1427
+ arguments: normalized.logged
1370
1428
  });
1371
- return;
1429
+ const prepared = await scheduler.prepare(input);
1430
+ if (prepared.kind === "dispatch") {
1431
+ this.flight = scheduler.dispatch(prepared.exec).then((dispatchOutcome) => {
1432
+ parked = {
1433
+ kind: dispatchOutcome.kind,
1434
+ exec: prepared.exec,
1435
+ result: dispatchOutcome.result
1436
+ };
1437
+ this.settled = true;
1438
+ });
1439
+ return;
1440
+ }
1441
+ parked = {
1442
+ kind: prepared.kind,
1443
+ exec: prepared.exec,
1444
+ result: prepared.result
1445
+ };
1446
+ this.settled = true;
1447
+ },
1448
+ async commit() {
1449
+ /* v8 ignore next -- commit() runs only after `settled` flipped, which set parked. */
1450
+ if (parked === void 0) return;
1451
+ const result = parked.kind === "post-result" ? await scheduler.finalize(parked.exec, parked.result) : scheduler.finish(parked.exec, parked.result);
1452
+ if (!result.isError && result.content.some((block) => block.type === "image")) exec.deferContext(createUserMessage({
1453
+ content: result.content,
1454
+ source: { kind: "ptc-mode" }
1455
+ }));
1456
+ for (const context of result.additionalContexts ?? []) exec.deferContext(context);
1457
+ if (result.concludesTurn) exec.concludeTurn();
1458
+ settle(result);
1459
+ while (logWork.size > maxParallel) await Promise.race(logWork);
1372
1460
  }
1373
- parked = {
1374
- kind: prepared.kind,
1375
- exec: prepared.exec,
1376
- result: prepared.result
1377
- };
1378
- this.settled = true;
1379
- },
1380
- async commit() {
1381
- /* v8 ignore next -- commit() runs only after `settled` flipped, which set parked. */
1382
- if (parked === void 0) return;
1383
- const result = parked.kind === "post-result" ? await scheduler.finalize(parked.exec, parked.result) : scheduler.finish(parked.exec, parked.result);
1384
- if (!result.isError && result.content.some((block) => block.type === "image")) exec.deferContext(createUserMessage({
1385
- content: result.content,
1386
- source: { kind: "ptc-mode" }
1387
- }));
1388
- for (const context of result.additionalContexts ?? []) exec.deferContext(context);
1389
- if (result.concludesTurn) exec.concludeTurn();
1390
- settle(result);
1391
- while (logWork.size > maxParallel) await Promise.race(logWork);
1392
- }
1461
+ });
1462
+ wakeup();
1463
+ drive();
1393
1464
  });
1394
- wakeup();
1395
- drive();
1396
- });
1397
- if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} result discarded`);
1398
- if (outcome.isError) throw new Error(outcome.message);
1399
- return outcome.value;
1400
- };
1401
- const functions = Object.create(null);
1402
- for (const schema of registry.schemas(exec.agent)) {
1403
- if (schema.name === "run_code") continue;
1404
- Object.defineProperty(functions, schema.name, {
1405
- enumerable: true,
1406
- value: binding(deepFreeze(schema))
1407
- });
1408
- }
1409
- try {
1410
- let result;
1465
+ if (runOver()) throw new Error(`run_code run is over (${String(runController.signal.reason)}); ${name} result discarded`);
1466
+ if (outcome.isError) throw new Error(outcome.message);
1467
+ return outcome.value;
1468
+ };
1469
+ const functions = Object.create(null);
1470
+ for (const schema of registry.schemas(exec.agent)) {
1471
+ if (schema.name === "run_code") continue;
1472
+ Object.defineProperty(functions, schema.name, {
1473
+ enumerable: true,
1474
+ value: binding(deepFreeze(schema))
1475
+ });
1476
+ }
1411
1477
  try {
1412
- result = await runtime.run(runtime.resolve({
1413
- program: args.code,
1414
- bindings: [{
1415
- global: "tools",
1416
- functions,
1417
- errorClass: {
1418
- name: "ToolCallError",
1419
- memberNameProperty: "toolName"
1478
+ let result;
1479
+ try {
1480
+ if (exec.agent !== void 0) {
1481
+ const agent = exec.agent;
1482
+ let runtimes = stateOwners.get(agent);
1483
+ if (runtimes === void 0) stateOwners.set(agent, runtimes = /* @__PURE__ */ new WeakSet());
1484
+ let provider = runtime;
1485
+ for (;;) {
1486
+ const original = provider[symbols.original];
1487
+ if (original === void 0) break;
1488
+ provider = original;
1420
1489
  }
1421
- }],
1422
- signal: runController.signal,
1423
- ...cwd !== void 0 ? { cwd } : {},
1424
- ...policy !== void 0 ? { sandboxPolicy: policy } : {},
1425
- ...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {}
1426
- }));
1490
+ if (!runtimes.has(provider)) {
1491
+ agent.ctx.effect(() => () => runtime.release(agent), "PTC retained environment");
1492
+ runtimes.add(provider);
1493
+ }
1494
+ }
1495
+ result = await runtime.run(runtime.resolve({
1496
+ program: args.code,
1497
+ ...exec.agent === void 0 || policy?.mode !== standingPolicy?.mode ? {} : { stateOwner: exec.agent },
1498
+ bindings: [{
1499
+ global: "tools",
1500
+ functions,
1501
+ errorClass: {
1502
+ name: "ToolCallError",
1503
+ memberNameProperty: "toolName"
1504
+ }
1505
+ }],
1506
+ signal: runController.signal,
1507
+ ...cwd !== void 0 ? { cwd } : {},
1508
+ ...policy !== void 0 ? { sandboxPolicy: policy } : {},
1509
+ ...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {}
1510
+ }));
1511
+ } finally {
1512
+ runController.abort("run_code settled");
1513
+ await drainDispatches();
1514
+ }
1515
+ if (result.error) {
1516
+ const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join("\n")}` : "";
1517
+ 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" : ""}.`;
1518
+ throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}${sandboxText}${result.sandbox?.denied ? escalationGuidance(runtime) : ""}`);
1519
+ }
1520
+ return {
1521
+ logs: result.logs,
1522
+ ...result.sandbox === void 0 ? {} : { sandbox: result.sandbox },
1523
+ ...result.value !== void 0 ? { result: result.value } : {}
1524
+ };
1427
1525
  } finally {
1428
- runController.abort("run_code settled");
1429
- await drainDispatches();
1430
- }
1431
- if (result.error) {
1432
- const logsText = result.logs.length > 0 ? `\nCaptured output:\n${result.logs.join("\n")}` : "";
1433
- 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" : ""}.`;
1434
- throw new CodeRunFailedError(`code run failed (${result.error.kind}): ${result.error.message}${logsText}${sandboxText}${result.sandbox?.denied ? escalationGuidance(runtime) : ""}`);
1526
+ callSignal.removeEventListener("abort", onOuterAbort);
1435
1527
  }
1436
- return {
1437
- logs: result.logs,
1438
- ...result.sandbox === void 0 ? {} : { sandbox: result.sandbox },
1439
- ...result.value !== void 0 ? { result: result.value } : {}
1440
- };
1441
- } finally {
1442
- exec.signal.removeEventListener("abort", onOuterAbort);
1443
- }
1528
+ };
1529
+ return runtime.sandboxMode === void 0 || exec.agent === void 0 ? run(exec.signal) : calls.run(exec.agent.session.id, exec.signal, () => options.resolveSandboxPolicy(exec).mode, run);
1444
1530
  },
1445
1531
  presentCall: (args) => ({
1446
1532
  card: "generic",
@@ -1685,24 +1771,29 @@ function jsonSchemaToTs(schema, indent = 0) {
1685
1771
  return "unknown";
1686
1772
  }
1687
1773
  }
1688
- /** The fixed model-facing usage contract rendered above the declarations (see the PTC mode Agent Note's "What the model sees"). */
1689
- const SDK_INSTRUCTIONS$1 = `## Writing code for run_code
1774
+ /** Render the argument and call instructions for the selected execution syntax. */
1775
+ function renderSdkInstructions(programFormat) {
1776
+ return `## Writing code for run_code
1690
1777
 
1691
- \`run_code\` takes two required arguments: \`description\`, a short summary of what the program does, and \`code\` — the body of an async TypeScript function (erasable syntax only — no \`enum\` or namespaces; type annotations are advisory, the code runs type-stripped). The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly.`;
1692
- const SDK_PROGRAM_INSTRUCTIONS = `Inside the program:
1778
+ \`run_code\` takes two required arguments: \`description\`, a short summary of what the program does, and \`code\`, ${programFormat === "function-body" ? "the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped)" : "the program to execute"}. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly.`;
1779
+ }
1780
+ /** Render tool execution and output instructions for the selected execution syntax. */
1781
+ function renderSdkProgramInstructions(programFormat) {
1782
+ return `Inside the program:
1693
1783
 
1694
1784
  - Call tools as \`await tools.name(args)\` — quoted access for exotic names: \`tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
1695
1785
  - A FAILED tool call rejects with \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose \`message\` is human-readable — \`try/catch\` it to handle and continue.
1696
1786
  - Independent read-only calls MAY overlap under \`Promise.all\` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with \`await\`.
1697
- - Emit results with \`return\` and/or \`console.log(...)\`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
1787
+ - ${programFormat === "function-body" ? "Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output." : "Only values emitted as program output enter the conversation."} A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
1698
1788
 
1699
1789
  Program-only SDK bindings:`;
1790
+ }
1700
1791
  /** Whether one string schema accepts the literal used by the bash example. */
1701
1792
  function acceptsExampleString(schema, value) {
1702
1793
  return schema?.type === "string" && (schema.const === void 0 || schema.const === value) && (schema.enum === void 0 || schema.enum.includes(value));
1703
1794
  }
1704
1795
  /** Render the bash example only when its literal arguments satisfy the current parameter schema. */
1705
- function renderBashExample(schemas) {
1796
+ function renderBashExample(schemas, programFormat) {
1706
1797
  const bash = schemas.find((schema) => schema.name === "bash");
1707
1798
  if (bash === void 0) return "";
1708
1799
  const parameters = bash.parameters;
@@ -1712,7 +1803,8 @@ function renderBashExample(schemas) {
1712
1803
  if (!acceptsExampleString(parameters.properties?.command, "pwd")) return "";
1713
1804
  const needsDescription = required.includes("description");
1714
1805
  if (needsDescription && !acceptsExampleString(parameters.properties?.description, "Show current directory")) return "";
1715
- return ` When no separate \`bash\` schema is supplied, invoke a declared \`bash\` binding inside \`run_code\`:\n\n\`run_code({ description: "Show current directory", code: "return await tools.bash({ ${needsDescription ? "description: 'Show current directory', " : ""}command: 'pwd' })" })\``;
1806
+ const description = needsDescription ? "description: 'Show current directory', " : "";
1807
+ return ` When no separate \`bash\` schema is supplied, invoke a declared \`bash\` binding inside \`run_code\`:\n\n\`run_code({ description: "Show current directory", code: "${programFormat === "function-body" ? `return await tools.bash({ ${description}command: 'pwd' })` : `console.log(await tools.bash({ ${description}command: 'pwd' }))`}" })\``;
1716
1808
  }
1717
1809
  /**
1718
1810
  * Render the full `tools:sdk` prompt section: the fixed usage instructions
@@ -1724,9 +1816,10 @@ function renderBashExample(schemas) {
1724
1816
  * by name, so the input never carries a duplicate.
1725
1817
  * @param schemas - the tool schemas to declare (the caller excludes
1726
1818
  * `run_code` itself).
1819
+ * @param programFormat - Runtime syntax; function bodies support returning a result, REPLs print it.
1727
1820
  * @returns the complete section text.
1728
1821
  */
1729
- function renderToolsSdk(schemas) {
1822
+ function renderToolsSdk(schemas, programFormat = "function-body") {
1730
1823
  const sorted = [...schemas].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
1731
1824
  const argsMembers = [];
1732
1825
  const outputMembers = [];
@@ -1751,7 +1844,10 @@ function renderToolsSdk(schemas) {
1751
1844
  "}"
1752
1845
  ].join("\n")
1753
1846
  ].join("\n\n");
1754
- return `${SDK_INSTRUCTIONS$1}${renderBashExample(sorted)}\n\n${SDK_PROGRAM_INSTRUCTIONS}\n\n\`\`\`ts\ntype JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }\n\n${declaration}\n\`\`\``;
1847
+ const jsonValue = "type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }";
1848
+ const instructions = renderSdkInstructions(programFormat);
1849
+ const programInstructions = renderSdkProgramInstructions(programFormat);
1850
+ return `${instructions}${renderBashExample(sorted, programFormat)}\n\n${programInstructions}\n\n\`\`\`ts\n${jsonValue}\n\n${declaration}\n\`\`\``;
1755
1851
  }
1756
1852
  //#endregion
1757
1853
  //#region lib/types/py-types.js
@@ -1764,7 +1860,7 @@ function renderToolsSdk(schemas) {
1764
1860
  *
1765
1861
  * Under `mode: 'ptc'` the native tool schemas are omitted from the request, so this generated
1766
1862
  * SDK is the model's ONLY source for each tool's argument names, required fields, types,
1767
- * descriptions, and canonical output shapes. Object-shaped arguments and outputs therefore render as one
1863
+ * descriptions, and canonical output types. Object-shaped arguments and outputs render as one
1768
1864
  * named `TypedDict` per tool (and per nested object), not an opaque `dict[str, Any]`, so the
1769
1865
  * shape survives into the program under the mode that has nothing else to carry it.
1770
1866
  * @module @qilin-agent/tools/src/py-types
@@ -2402,12 +2498,12 @@ function jsonSchemaToPy(schema) {
2402
2498
  /** The fixed model-facing usage contract rendered above the declarations. */
2403
2499
  const SDK_INSTRUCTIONS = `## Writing code for run_code
2404
2500
 
2405
- \`run_code\` takes two required arguments: \`description\`, a short summary of what the program does, and \`code\` — the body of an async Python function (top-level \`await\` and \`return\` both work). At run time exactly two of the names declared below are bound: \`tools\` and \`ToolCallError\`. Everything else is a STATIC STUB describing argument and return types — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tools.name({"field": 1})\`, never \`FooArgs(field=1)\`, which raises \`NameError\`. Inside the program:
2501
+ \`run_code\` takes two required arguments: \`description\`, a short summary of what the program does, and \`code\`, the program to execute. At run time exactly two of the names declared below are bound: \`tools\` and \`ToolCallError\`. Everything else is a STATIC STUB describing argument and return types — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tools.name({"field": 1})\`, never \`FooArgs(field=1)\`, which raises \`NameError\`. Inside the program:
2406
2502
 
2407
2503
  - Call tools as \`await tools.name(args)\` — subscript access for exotic, reserved, or underscore-leading names: \`await tools["my-tool"](args)\`. Every call resolves to the tool's typed canonical JSON value (each method's return type below). Tool arguments must be lossless JSON.
2408
2504
  - A FAILED tool call raises \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose message is human-readable — wrap in \`try/except\` to handle and continue.
2409
2505
  - Independent read-only calls MAY overlap under \`asyncio.gather\` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with \`await\`.
2410
- - Emit the run's answer with \`print(...)\` and/or a top-level \`return <value>\`; the returned value must be lossless JSON. Only what you print and return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
2506
+ - Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
2411
2507
 
2412
2508
  The available tools:`;
2413
2509
  /**
@@ -2694,11 +2790,13 @@ var ToolRuntime = class extends Service {
2694
2790
  * Reserved presentation transport, kept outside the filterable registration
2695
2791
  * layers. Built on first need rather than at construction: which agents run
2696
2792
  * a PTC mode is no longer known when the service is constructed, and the
2697
- * transport is stateless beyond its closures over `this`.
2793
+ * transport owns its PTC call tracking and state-owner registrations.
2698
2794
  */
2699
2795
  ptcTransport;
2796
+ ptcContext;
2700
2797
  constructor(ctx, config = {}) {
2701
2798
  super(ctx, "tools");
2799
+ this.ptcContext = ctx;
2702
2800
  this.defaultMode = config.mode ?? "native";
2703
2801
  this.maxParallelSubCalls = resolveMaxParallelSubCalls(config.maxParallelSubCalls);
2704
2802
  ctx.systemPrompt.tools((context) => this.wireSchemas(context.scope));
@@ -2751,7 +2849,7 @@ var ToolRuntime = class extends Service {
2751
2849
  const render = SDK_RENDERERS[runtime.language];
2752
2850
  /* v8 ignore next -- requirePtcRuntime rejects an unknown language before this runs. */
2753
2851
  if (render === void 0) throw new Error(`qilin-tools: no SDK renderer for ${runtime.language}`);
2754
- return render(this.sdkSchemas(context.scope));
2852
+ return render(this.sdkSchemas(context.scope), runtime.programFormat);
2755
2853
  }
2756
2854
  };
2757
2855
  }
@@ -2796,7 +2894,7 @@ var ToolRuntime = class extends Service {
2796
2894
  peekRuntime: () => this.ctx.get("ptcRuntime"),
2797
2895
  maxParallel: this.maxParallelSubCalls,
2798
2896
  shapeDispatchLog: (dispatch) => this.shapeDispatchLog(dispatch)
2799
- });
2897
+ }, this.ptcContext);
2800
2898
  return this.ptcTransport;
2801
2899
  }
2802
2900
  /**