@zergai/ztc 0.3.0-beta.1 → 0.4.0-beta.1
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/bin/zmux-darwin-arm64 +0 -0
- package/bin/ztc-tui-darwin-arm64 +0 -0
- package/bin/zterm-darwin-arm64 +0 -0
- package/dist/App.js +16 -2
- package/dist/agent/agent.d.ts +20 -8
- package/dist/agent/agent.js +92 -40
- package/dist/agent/backends/gemini.js +1 -1
- package/dist/agent/backends/openai_format.js +2 -2
- package/dist/agent/backends/openai_responses.js +1 -1
- package/dist/agent/backends/types.d.ts +1 -0
- package/dist/agent/commands/config.js +15 -1
- package/dist/agent/commands/settings.js +23 -0
- package/dist/agent/commands/status.js +3 -0
- package/dist/agent/commands/types.d.ts +2 -0
- package/dist/agent/completion_state.js +2 -2
- package/dist/agent/factory.js +7 -1
- package/dist/agent/request_encoder.js +3 -0
- package/dist/agent/runtime/command_jobs.d.ts +47 -0
- package/dist/agent/runtime/command_jobs.js +268 -0
- package/dist/agent/runtime/execution_context.d.ts +16 -0
- package/dist/agent/runtime/execution_context.js +20 -0
- package/dist/agent/runtime/recovery.d.ts +9 -0
- package/dist/agent/runtime/recovery.js +71 -0
- package/dist/agent/runtime/tool_runtime.js +3 -1
- package/dist/agent/runtime/tool_scheduler.d.ts +6 -0
- package/dist/agent/runtime/tool_scheduler.js +57 -0
- package/dist/agent/subagent.js +30 -12
- package/dist/agent/tool_response_store.d.ts +2 -0
- package/dist/agent/tool_response_store.js +1 -0
- package/dist/agent/tools/deferred.js +2 -0
- package/dist/agent/tools/file.js +2 -0
- package/dist/agent/tools/pane.js +139 -42
- package/dist/agent/tools/recall_tool_response.js +1 -0
- package/dist/agent/tools/registry.js +3 -1
- package/dist/agent/tools/search.js +1 -0
- package/dist/agent/tools/shell.d.ts +2 -0
- package/dist/agent/tools/shell.js +57 -2
- package/dist/agent/tools/types.d.ts +4 -0
- package/dist/app/agent_runner.js +1 -1
- package/dist/app/ordered_transcript.d.ts +13 -0
- package/dist/app/ordered_transcript.js +55 -0
- package/dist/cli_ratatui.d.ts +2 -7
- package/dist/cli_ratatui.js +53 -34
- package/dist/config/execution.d.ts +12 -0
- package/dist/config/execution.js +13 -0
- package/dist/config/types.d.ts +1 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +5 -0
- package/dist/emulation/tool_adapters.js +5 -2
- package/dist/headless.d.ts +1 -18
- package/dist/headless.js +15 -0
- package/dist/plugin/runtime.d.ts +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/ui/ratatui/headless_pane_manager.d.ts +2 -19
- package/dist/ui/ratatui/headless_pane_manager.js +41 -34
- package/dist/ui/ratatui/pane_input.d.ts +4 -0
- package/dist/ui/ratatui/pane_input.js +15 -0
- package/dist/ui/ratatui/pane_relay.d.ts +3 -0
- package/dist/ui/ratatui/pane_relay.js +20 -2
- package/dist/ui/ratatui/protocol.d.ts +17 -0
- package/dist/ui/ratatui/protocol.js +1 -0
- package/dist/utils/shell.d.ts +2 -0
- package/dist/utils/shell.js +54 -11
- package/dist/web/mirror_stream.d.ts +2 -1
- package/dist/web/mirror_stream.js +7 -0
- package/dist/web/pane_registry.js +8 -2
- package/dist/web/view_plugin.d.ts +1 -0
- package/dist/zlm-embed.mjs +384 -118
- package/package.json +2 -2
package/dist/zlm-embed.mjs
CHANGED
|
@@ -555,9 +555,9 @@ function incompleteModelResponseMessage(stopReason, maxTokens, outputTokens) {
|
|
|
555
555
|
const exhausted = reason === "incomplete" && maxTokens !== void 0 && outputTokens !== void 0 && outputTokens >= maxTokens;
|
|
556
556
|
if (limited || exhausted) {
|
|
557
557
|
if (maxTokens === void 0) {
|
|
558
|
-
return "Model reached the provider's output limit (including reasoning).
|
|
558
|
+
return "Model reached the provider's output limit (including reasoning). Use /config max-tokens <number> within the model's limit or lower reasoning effort, then retry; completed tool work is retained and the incomplete response was not accepted.";
|
|
559
559
|
}
|
|
560
|
-
return `Model reached the output limit (maxTokens: ${maxTokens}, including reasoning).
|
|
560
|
+
return `Model reached the output limit (maxTokens: ${maxTokens}, including reasoning). Set /config max-tokens <number> or /config max-tokens default, then retry. Completed tool work is retained; the incomplete response was not accepted.`;
|
|
561
561
|
}
|
|
562
562
|
return INCOMPLETE_MODEL_RESPONSE_MESSAGE;
|
|
563
563
|
}
|
|
@@ -825,7 +825,7 @@ function buildOpenAIMessages(request) {
|
|
|
825
825
|
} else if (block.type === "tool_use") {
|
|
826
826
|
toolUses.push({ id: block.id, name: block.name, input: block.input });
|
|
827
827
|
} else if (block.type === "tool_result") {
|
|
828
|
-
toolResults.push({ tool_use_id: block.tool_use_id, content: block.content });
|
|
828
|
+
toolResults.push({ tool_use_id: block.tool_use_id, content: block.content, is_error: block.is_error });
|
|
829
829
|
} else if (block.type === "image") {
|
|
830
830
|
mediaParts.push({
|
|
831
831
|
type: "image_url",
|
|
@@ -863,7 +863,7 @@ function buildOpenAIMessages(request) {
|
|
|
863
863
|
out.push({
|
|
864
864
|
role: "tool",
|
|
865
865
|
tool_call_id: tr.tool_use_id,
|
|
866
|
-
content: resultText
|
|
866
|
+
content: tr.is_error && !/^error\b/i.test(resultText) ? `Error: ${resultText}` : resultText
|
|
867
867
|
});
|
|
868
868
|
}
|
|
869
869
|
if (textParts.length > 0 || mediaParts.length > 0) {
|
|
@@ -1385,7 +1385,7 @@ function buildResponsesInput(messages) {
|
|
|
1385
1385
|
input.push({
|
|
1386
1386
|
type: "function_call_output",
|
|
1387
1387
|
call_id: block.tool_use_id,
|
|
1388
|
-
output: resultText
|
|
1388
|
+
output: block.is_error && !/^error\b/i.test(resultText) ? `Error: ${resultText}` : resultText
|
|
1389
1389
|
});
|
|
1390
1390
|
break;
|
|
1391
1391
|
}
|
|
@@ -1808,7 +1808,7 @@ var init_gemini = __esm({
|
|
|
1808
1808
|
}
|
|
1809
1809
|
if (block.type === "tool_result") {
|
|
1810
1810
|
const resultText = typeof block.content === "string" ? block.content : block.content.map((b) => b.type === "text" ? b.text : "[image]").join("\n");
|
|
1811
|
-
return { text: `Function result: ${resultText}` };
|
|
1811
|
+
return { text: `Function ${block.tool_use_id} ${block.is_error ? "error" : "result"}: ${resultText}` };
|
|
1812
1812
|
}
|
|
1813
1813
|
if (block.type === "tool_use") {
|
|
1814
1814
|
return { functionCall: { name: block.name, args: block.input } };
|
|
@@ -5661,6 +5661,8 @@ var init_deferred = __esm({
|
|
|
5661
5661
|
"list_directory",
|
|
5662
5662
|
"search",
|
|
5663
5663
|
"run_command",
|
|
5664
|
+
"read_command",
|
|
5665
|
+
"stop_command",
|
|
5664
5666
|
"apply_patch",
|
|
5665
5667
|
// Emulation compatibility aliases. They only appear in the request when
|
|
5666
5668
|
// the active emulation adds matching tools to the runtime tool list.
|
|
@@ -5917,6 +5919,7 @@ var init_tool_response_store = __esm({
|
|
|
5917
5919
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5918
5920
|
turn: this.turnCounter++,
|
|
5919
5921
|
partial,
|
|
5922
|
+
...context?.isError === void 0 ? {} : { is_error: context.isError },
|
|
5920
5923
|
preview
|
|
5921
5924
|
};
|
|
5922
5925
|
const sourcePath = context?.sourcePath ?? null;
|
|
@@ -7235,6 +7238,10 @@ var init_config = __esm({
|
|
|
7235
7238
|
...this.cliOverrides.model !== void 0 ? { model: this.cliOverrides.model } : {}
|
|
7236
7239
|
};
|
|
7237
7240
|
}
|
|
7241
|
+
getMaxTokensSource() {
|
|
7242
|
+
if (Object.prototype.hasOwnProperty.call(this.sessionOverrides, "maxTokens")) return "session override";
|
|
7243
|
+
return this.config.maxTokens === void 0 ? "provider default" : `saved profile (${this.getConfigPath()})`;
|
|
7244
|
+
}
|
|
7238
7245
|
getConfigScope() {
|
|
7239
7246
|
return this.configScope;
|
|
7240
7247
|
}
|
|
@@ -9308,8 +9315,8 @@ init_backends();
|
|
|
9308
9315
|
init_token_usage();
|
|
9309
9316
|
init_transport_errors();
|
|
9310
9317
|
init_defaults();
|
|
9311
|
-
import { randomUUID as
|
|
9312
|
-
import { existsSync as existsSync5, readFileSync as
|
|
9318
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
9319
|
+
import { existsSync as existsSync5, readFileSync as readFileSync8 } from "fs";
|
|
9313
9320
|
import { extname } from "path";
|
|
9314
9321
|
|
|
9315
9322
|
// src/config/api_retry.ts
|
|
@@ -9348,9 +9355,244 @@ function resolveApiRetrySettings(config) {
|
|
|
9348
9355
|
};
|
|
9349
9356
|
}
|
|
9350
9357
|
|
|
9358
|
+
// src/config/execution.ts
|
|
9359
|
+
function resolveExecutionSettings(settings = {}) {
|
|
9360
|
+
const count = (value, fallback, max, min = 1) => Number.isSafeInteger(value) && value >= min && value <= max ? value : fallback;
|
|
9361
|
+
return {
|
|
9362
|
+
shellPanePolicy: settings.shellPanePolicy === "auto" ? "auto" : "on_request",
|
|
9363
|
+
readConcurrency: count(settings.readConcurrency, 4, 32),
|
|
9364
|
+
maxCallsPerResponse: count(settings.maxCallsPerResponse, 16, 256),
|
|
9365
|
+
recovery: settings.recovery === "pause" ? "pause" : "automatic",
|
|
9366
|
+
repeatedFailureLimit: count(settings.repeatedFailureLimit, 3, 20),
|
|
9367
|
+
recoveryAttempts: count(settings.recoveryAttempts, 1, 5, 0),
|
|
9368
|
+
interruptBackgroundJobs: settings.interruptBackgroundJobs === true,
|
|
9369
|
+
maxBackgroundJobs: count(settings.maxBackgroundJobs, 4, 32)
|
|
9370
|
+
};
|
|
9371
|
+
}
|
|
9372
|
+
|
|
9373
|
+
// src/agent/runtime/recovery.ts
|
|
9374
|
+
function normalizeFailure(content) {
|
|
9375
|
+
const text = content.toLowerCase();
|
|
9376
|
+
if (/not_executed/.test(text)) return "";
|
|
9377
|
+
for (const [pattern, cause] of [
|
|
9378
|
+
[/heredoc|unexpected end of file|unterminated/, "unfinished shell input"],
|
|
9379
|
+
[/permission denied|eacces/, "permission denied"],
|
|
9380
|
+
[/not found|enoent|unknown tool|unavailable/, "capability or file unavailable"],
|
|
9381
|
+
[/timeout|timed out/, "timeout"],
|
|
9382
|
+
[/syntaxerror|syntax error|invalid.*syntax/, "syntax error"]
|
|
9383
|
+
]) if (pattern.test(text)) return cause;
|
|
9384
|
+
const exit = text.match(/command failed \(exit (\d+)\)/);
|
|
9385
|
+
if (exit) return `command exit ${exit[1]}`;
|
|
9386
|
+
return text.split("\n")[0].replace(/['"`][^'"`]*['"`]/g, "<value>").replace(/\d+/g, "#").trim().slice(0, 240);
|
|
9387
|
+
}
|
|
9388
|
+
function createRecoveryGuard(settings) {
|
|
9389
|
+
const limits = resolveExecutionSettings(settings);
|
|
9390
|
+
let cause = "";
|
|
9391
|
+
let failures = 0;
|
|
9392
|
+
let attempts = 0;
|
|
9393
|
+
let neutralSteps = 0;
|
|
9394
|
+
const observations = /* @__PURE__ */ new Set();
|
|
9395
|
+
return (results) => {
|
|
9396
|
+
let failed = false;
|
|
9397
|
+
let progress = false;
|
|
9398
|
+
let neutral = false;
|
|
9399
|
+
for (const result of results) {
|
|
9400
|
+
if (result.isError) {
|
|
9401
|
+
const next = normalizeFailure(result.content);
|
|
9402
|
+
if (!next) continue;
|
|
9403
|
+
failed = true;
|
|
9404
|
+
failures = next === cause ? failures + 1 : 1;
|
|
9405
|
+
cause = next;
|
|
9406
|
+
} else {
|
|
9407
|
+
const signature = `${result.name}\0${result.content}`;
|
|
9408
|
+
const acknowledgement = /^(Input (?:delivered|queued|sent)|Sent input|\(Terminal buffer is empty\))/.test(result.content) || /"status":"running"/.test(result.content) && /"output":""/.test(result.content);
|
|
9409
|
+
neutral ||= acknowledgement;
|
|
9410
|
+
if (result.content.trim() && !acknowledgement && !observations.has(signature)) progress = true;
|
|
9411
|
+
observations.add(signature);
|
|
9412
|
+
}
|
|
9413
|
+
}
|
|
9414
|
+
if (progress && !failed) {
|
|
9415
|
+
failures = 0;
|
|
9416
|
+
cause = "";
|
|
9417
|
+
attempts = 0;
|
|
9418
|
+
neutralSteps = 0;
|
|
9419
|
+
}
|
|
9420
|
+
if (failures < limits.repeatedFailureLimit) return "continue";
|
|
9421
|
+
if (!failed && neutral && attempts > 0 && neutralSteps++ < 1) return "continue";
|
|
9422
|
+
if (limits.recovery === "pause" || attempts >= limits.recoveryAttempts) return "stop";
|
|
9423
|
+
attempts++;
|
|
9424
|
+
neutralSteps = 0;
|
|
9425
|
+
return "recover";
|
|
9426
|
+
};
|
|
9427
|
+
}
|
|
9428
|
+
|
|
9429
|
+
// src/agent/runtime/command_jobs.ts
|
|
9430
|
+
import { randomUUID as randomUUID5, createHash as createHash3 } from "node:crypto";
|
|
9431
|
+
import { appendFileSync as appendFileSync4, closeSync, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync6, readSync, readdirSync, rmSync, rmdirSync, statSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9432
|
+
import { homedir as homedir6 } from "node:os";
|
|
9433
|
+
import { join as join11 } from "node:path";
|
|
9434
|
+
|
|
9435
|
+
// src/utils/subprocess_env.ts
|
|
9436
|
+
var USER_SUBPROCESS_STRIPPED_ZTC_ENV = [
|
|
9437
|
+
"ZERG_ROUTER_BASE_URL",
|
|
9438
|
+
"ZTC_AUTH_TOKEN",
|
|
9439
|
+
"ZTC_CHILD_ENTRYPOINT",
|
|
9440
|
+
"ZTC_COLONY_CHILD",
|
|
9441
|
+
"ZTC_COLONY_PARENT_MIRROR_PORT",
|
|
9442
|
+
"ZTC_COLONY_WORKTREE_ID",
|
|
9443
|
+
"ZTC_MIRROR_PORT",
|
|
9444
|
+
"ZTC_MIRROR_INTERNAL_TOKEN",
|
|
9445
|
+
"ZTC_MIRROR_REQUIRE_AUTH",
|
|
9446
|
+
"ZTC_MIRROR_SHARED_SECRET",
|
|
9447
|
+
"ZTC_NAVIGATOR_GROUP",
|
|
9448
|
+
"ZTC_PANE_ID",
|
|
9449
|
+
"ZTC_PANE_RELAY",
|
|
9450
|
+
"ZTC_PARENT_PID"
|
|
9451
|
+
];
|
|
9452
|
+
function buildUserSubprocessEnv(source = process.env) {
|
|
9453
|
+
const env = { ...source };
|
|
9454
|
+
for (const key of USER_SUBPROCESS_STRIPPED_ZTC_ENV) {
|
|
9455
|
+
delete env[key];
|
|
9456
|
+
}
|
|
9457
|
+
return env;
|
|
9458
|
+
}
|
|
9459
|
+
|
|
9460
|
+
// src/utils/shell.ts
|
|
9461
|
+
var DOCKER_CONTAINER = process.env.ZTC_DOCKER_CONTAINER;
|
|
9462
|
+
var DOCKER_WORKDIR = process.env.ZTC_DOCKER_WORKDIR || "/app";
|
|
9463
|
+
|
|
9464
|
+
// src/agent/runtime/command_jobs.ts
|
|
9465
|
+
var MAX_LOG_BYTES = 16 * 1024 * 1024;
|
|
9466
|
+
var RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
9467
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
9468
|
+
function directory(sessionId) {
|
|
9469
|
+
return join11(
|
|
9470
|
+
process.env.ZTC_CONFIG_DIR || join11(homedir6(), ".ztc"),
|
|
9471
|
+
"command-jobs",
|
|
9472
|
+
createHash3("sha256").update(sessionId).digest("hex").slice(0, 32)
|
|
9473
|
+
);
|
|
9474
|
+
}
|
|
9475
|
+
function save(sessionId, receipt) {
|
|
9476
|
+
writeFileSync5(join11(directory(sessionId), `${receipt.job_id}.json`), JSON.stringify(receipt), { mode: 384 });
|
|
9477
|
+
}
|
|
9478
|
+
function resolveJob(sessionId, jobId) {
|
|
9479
|
+
if (!/^[0-9a-f-]{36}$/.test(jobId)) throw new Error("Command job not found in this session");
|
|
9480
|
+
const live = sessions.get(sessionId)?.get(jobId);
|
|
9481
|
+
if (live) return live;
|
|
9482
|
+
let receipt;
|
|
9483
|
+
try {
|
|
9484
|
+
receipt = JSON.parse(readFileSync6(join11(directory(sessionId), `${jobId}.json`), "utf8"));
|
|
9485
|
+
} catch {
|
|
9486
|
+
throw new Error("Command job not found in this session");
|
|
9487
|
+
}
|
|
9488
|
+
if (receipt.status === "running") {
|
|
9489
|
+
receipt = { ...receipt, status: "interrupted", ended_at: (/* @__PURE__ */ new Date()).toISOString(), error: "Owning ZTC process ended; inspect output before retrying." };
|
|
9490
|
+
save(sessionId, receipt);
|
|
9491
|
+
}
|
|
9492
|
+
return { receipt, controller: new AbortController(), done: Promise.resolve(), loggedBytes: statSync(receipt.log_path).size };
|
|
9493
|
+
}
|
|
9494
|
+
function listCommandJobs(sessionId) {
|
|
9495
|
+
const jobs = [...sessions.get(sessionId)?.values() ?? []];
|
|
9496
|
+
return [...jobs.filter((job) => job.receipt.status === "running"), ...jobs.filter((job) => job.receipt.status !== "running").slice(-28)].map((job) => ({ ...job.receipt }));
|
|
9497
|
+
}
|
|
9498
|
+
async function stopCommandJob(sessionId, jobId) {
|
|
9499
|
+
const job = resolveJob(sessionId, jobId);
|
|
9500
|
+
if (job.receipt.status === "running") {
|
|
9501
|
+
job.controller.abort();
|
|
9502
|
+
await job.done;
|
|
9503
|
+
}
|
|
9504
|
+
return { ...job.receipt };
|
|
9505
|
+
}
|
|
9506
|
+
async function interruptCommandJobs(sessionId) {
|
|
9507
|
+
await Promise.all(listCommandJobs(sessionId).filter((job) => job.status === "running").map((job) => stopCommandJob(sessionId, job.job_id)));
|
|
9508
|
+
}
|
|
9509
|
+
|
|
9510
|
+
// src/agent/runtime/execution_context.ts
|
|
9511
|
+
function describeExecutionContext(context) {
|
|
9512
|
+
const names = new Set(context.tools);
|
|
9513
|
+
const shell = ["run_command", "Bash", "exec_command", "shell_command"].filter((name) => names.has(name));
|
|
9514
|
+
const panes = ["create_terminal_pane", "read_terminal_pane", "write_terminal_pane"].filter((name) => names.has(name));
|
|
9515
|
+
return [
|
|
9516
|
+
"## Current execution capabilities (host facts; refreshed each request)",
|
|
9517
|
+
`Mode: ${context.mode}. Cwd: ${context.cwd}. Config write scope: ${context.configScope}.`,
|
|
9518
|
+
`Output cap: ${context.maxTokens ?? "provider default"} (source: ${context.outputLimitSource}). Use /config max-tokens <number|default> to change it.`,
|
|
9519
|
+
`Shell: ${shell.length ? shell.join(", ") : "unavailable in the current tool surface"}.`,
|
|
9520
|
+
`Mux: ${context.mux ? "connected" : "unavailable"}. Pane tools: ${panes.join(", ") || "unavailable in this mode"}.`,
|
|
9521
|
+
`Shell-pane policy: ${context.settings.shellPanePolicy === "on_request" ? "create a shell pane only when the user explicitly requests a visible or interactive shell; otherwise use run_command" : "may create shell panes automatically when useful"}.`,
|
|
9522
|
+
`Registered tools: ${context.tools.slice(0, 80).join(", ")}. Deferred schemas can be discovered with tool_search when available.`,
|
|
9523
|
+
`Batch limit: ${context.settings.maxCallsPerResponse}. Read concurrency: ${context.settings.readConcurrency}. Inspect a failed or running command before submitting dependent work.`,
|
|
9524
|
+
"For multiline scripts use run_command stdin or write_file and execute the file. Pane text supports multiline submission; raw Ctrl+C (U+0003, submit=false) cancels stuck shell input. A write acknowledgement does not prove command success.",
|
|
9525
|
+
`Managed commands: ${context.jobs.length ? JSON.stringify(context.jobs.map(({ job_id, status, cwd, exit_code }) => ({ job_id, status, cwd, exit_code }))) : "none"}. Background commands ${context.settings.interruptBackgroundJobs ? "are cancelled" : "survive"} on turn interruption; application shutdown stops owned local jobs. Remote cleanup belongs to the launched command.`,
|
|
9526
|
+
"If a tool is unavailable, inspect these current facts and mode restrictions. Do not infer that the session never had shell access from a previous mode or a failed invocation."
|
|
9527
|
+
].join("\n");
|
|
9528
|
+
}
|
|
9529
|
+
|
|
9530
|
+
// src/agent/runtime/tool_effects.ts
|
|
9531
|
+
init_capabilities();
|
|
9532
|
+
function resolveToolEffect(tool) {
|
|
9533
|
+
if (tool.effect) return tool.effect;
|
|
9534
|
+
const capabilities = new Set(tool.capabilities ?? []);
|
|
9535
|
+
if (capabilities.has("shell_exec" /* SHELL_EXEC */) || capabilities.has("network" /* NETWORK */) || capabilities.has("computer_control" /* COMPUTER_CONTROL */) || capabilities.has("ml_inference" /* ML_INFERENCE */)) {
|
|
9536
|
+
return "external";
|
|
9537
|
+
}
|
|
9538
|
+
if (capabilities.has("file_write" /* FILE_WRITE */)) return "write";
|
|
9539
|
+
return "read";
|
|
9540
|
+
}
|
|
9541
|
+
|
|
9542
|
+
// src/agent/runtime/tool_scheduler.ts
|
|
9543
|
+
async function scheduleToolBatch(calls, tools, settings, execute, blocksFollowing, signal, exclusiveId) {
|
|
9544
|
+
const limits = resolveExecutionSettings(settings);
|
|
9545
|
+
const registered = new Map(tools.map((tool) => [tool.definition.name, tool]));
|
|
9546
|
+
const parallel = (call) => {
|
|
9547
|
+
const tool = registered.get(call.name);
|
|
9548
|
+
return tool?.parallelSafe === true && resolveToolEffect(tool) === "read";
|
|
9549
|
+
};
|
|
9550
|
+
const results = [];
|
|
9551
|
+
let effectsBlocked = false;
|
|
9552
|
+
let stopped = calls.length > limits.maxCallsPerResponse ? `batch_limit: ${calls.length} calls exceeds ${limits.maxCallsPerResponse}; submit a smaller batch after inspecting results.` : "";
|
|
9553
|
+
for (let index = 0; index < calls.length; ) {
|
|
9554
|
+
const call = calls[index];
|
|
9555
|
+
if (signal?.aborted) stopped = "cancelled: the turn was interrupted.";
|
|
9556
|
+
if (stopped) {
|
|
9557
|
+
results.push(await execute(call, `not_executed: ${stopped}`));
|
|
9558
|
+
index++;
|
|
9559
|
+
continue;
|
|
9560
|
+
}
|
|
9561
|
+
if (effectsBlocked && !parallel(call)) {
|
|
9562
|
+
results.push(await execute(call, "not_executed: a previous call failed or is still running; inspect its result before continuing effects."));
|
|
9563
|
+
index++;
|
|
9564
|
+
continue;
|
|
9565
|
+
}
|
|
9566
|
+
if (exclusiveId && call.id !== exclusiveId) {
|
|
9567
|
+
results.push(await execute(call, "not_executed: exclusive safety transition must run alone."));
|
|
9568
|
+
index++;
|
|
9569
|
+
continue;
|
|
9570
|
+
}
|
|
9571
|
+
let end = index + 1;
|
|
9572
|
+
if (!exclusiveId && parallel(call)) {
|
|
9573
|
+
while (end < calls.length && end - index < limits.readConcurrency && parallel(calls[end])) end++;
|
|
9574
|
+
}
|
|
9575
|
+
const batch = await Promise.all(calls.slice(index, end).map((item) => execute(item)));
|
|
9576
|
+
results.push(...batch);
|
|
9577
|
+
if (batch.some(blocksFollowing)) {
|
|
9578
|
+
effectsBlocked = true;
|
|
9579
|
+
}
|
|
9580
|
+
index = end;
|
|
9581
|
+
}
|
|
9582
|
+
return results;
|
|
9583
|
+
}
|
|
9584
|
+
function commandStillRunning(content) {
|
|
9585
|
+
try {
|
|
9586
|
+
const result = JSON.parse(content);
|
|
9587
|
+
return result.status === "running" && typeof result.job_id === "string";
|
|
9588
|
+
} catch {
|
|
9589
|
+
return false;
|
|
9590
|
+
}
|
|
9591
|
+
}
|
|
9592
|
+
|
|
9351
9593
|
// src/agent/runtime/policy.ts
|
|
9352
9594
|
init_capabilities();
|
|
9353
|
-
import { homedir as
|
|
9595
|
+
import { homedir as homedir7 } from "os";
|
|
9354
9596
|
import { resolve as resolve4, isAbsolute as isAbsolute2, sep } from "path";
|
|
9355
9597
|
var AllowAllPolicy = class {
|
|
9356
9598
|
evaluateTool() {
|
|
@@ -9497,7 +9739,7 @@ function extractPathArgs(args) {
|
|
|
9497
9739
|
return paths;
|
|
9498
9740
|
}
|
|
9499
9741
|
function normalizeRoot(path, cwd) {
|
|
9500
|
-
const expanded = path === "~" ?
|
|
9742
|
+
const expanded = path === "~" ? homedir7() : path.replace(/^~(?=\/|$)/, homedir7());
|
|
9501
9743
|
return resolve4(isAbsolute2(expanded) ? expanded : resolve4(cwd, expanded));
|
|
9502
9744
|
}
|
|
9503
9745
|
function isWithinRoot(path, root) {
|
|
@@ -9617,8 +9859,8 @@ init_tool_response_store();
|
|
|
9617
9859
|
|
|
9618
9860
|
// src/utils/mirror_media.ts
|
|
9619
9861
|
import { tmpdir as tmpdir2 } from "os";
|
|
9620
|
-
import { join as
|
|
9621
|
-
var MIRROR_MEDIA_ROOT =
|
|
9862
|
+
import { join as join12, resolve as resolve5, sep as sep2 } from "path";
|
|
9863
|
+
var MIRROR_MEDIA_ROOT = join12(tmpdir2(), "ztc-mirror-media");
|
|
9622
9864
|
function isMirrorMediaPath(localPath) {
|
|
9623
9865
|
if (!localPath) return false;
|
|
9624
9866
|
const root = resolve5(MIRROR_MEDIA_ROOT);
|
|
@@ -9711,6 +9953,7 @@ var RequestEncoder = class {
|
|
|
9711
9953
|
const verdict = this.deps.selectorVerdict;
|
|
9712
9954
|
const rerendered = toolResults.map((r) => ({
|
|
9713
9955
|
tool_use_id: r.tool_use_id,
|
|
9956
|
+
...r.is_error === void 0 ? {} : { is_error: r.is_error },
|
|
9714
9957
|
// Coerce to '' as a last resort: a tool_result entry that carries a
|
|
9715
9958
|
// tool_use_id but no content must still encode as a valid tool_result
|
|
9716
9959
|
// block (content must be a string for the API), not as undefined —
|
|
@@ -9807,6 +10050,7 @@ var RequestEncoder = class {
|
|
|
9807
10050
|
return {
|
|
9808
10051
|
type: "tool_result",
|
|
9809
10052
|
tool_use_id: result.tool_use_id,
|
|
10053
|
+
...result.is_error === void 0 ? {} : { is_error: result.is_error },
|
|
9810
10054
|
content: [
|
|
9811
10055
|
{ type: "text", text: parsed.text },
|
|
9812
10056
|
{
|
|
@@ -9830,6 +10074,7 @@ var RequestEncoder = class {
|
|
|
9830
10074
|
return {
|
|
9831
10075
|
type: "tool_result",
|
|
9832
10076
|
tool_use_id: result.tool_use_id,
|
|
10077
|
+
...result.is_error === void 0 ? {} : { is_error: result.is_error },
|
|
9833
10078
|
content
|
|
9834
10079
|
};
|
|
9835
10080
|
});
|
|
@@ -9844,13 +10089,13 @@ init_tool_compression();
|
|
|
9844
10089
|
// src/agent/runtime/tool_runtime.ts
|
|
9845
10090
|
init_capabilities();
|
|
9846
10091
|
init_config();
|
|
9847
|
-
import { basename as basename3, dirname as dirname6, join as
|
|
10092
|
+
import { basename as basename3, dirname as dirname6, join as join14, relative as relative2, resolve as resolve7 } from "path";
|
|
9848
10093
|
import { realpathSync } from "node:fs";
|
|
9849
10094
|
import { appendFile } from "node:fs/promises";
|
|
9850
10095
|
|
|
9851
10096
|
// src/context/intents_loader.ts
|
|
9852
|
-
import { appendFileSync as
|
|
9853
|
-
import { basename as basename2, dirname as dirname5, join as
|
|
10097
|
+
import { appendFileSync as appendFileSync5, existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync7, statSync as statSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
10098
|
+
import { basename as basename2, dirname as dirname5, join as join13, relative, resolve as resolve6 } from "path";
|
|
9854
10099
|
var DEFAULT_MAX_BYTES = 32 * 1024;
|
|
9855
10100
|
var DEFAULT_FILE_INTENT_THRESHOLD_LINES = 400;
|
|
9856
10101
|
function loadIntentsContext(cwd = process.cwd(), settings, options = {}) {
|
|
@@ -9910,7 +10155,7 @@ function maybeAutoUpdateIntents(options) {
|
|
|
9910
10155
|
const relChanged = relative(options.cwd, absolutePath) || basename2(absolutePath);
|
|
9911
10156
|
const note = `- ${(/* @__PURE__ */ new Date()).toISOString()}: tool \`${options.toolName}\` changed \`${relChanged}\`. Keep this file's higher-level intent current if behavior changed.
|
|
9912
10157
|
`;
|
|
9913
|
-
|
|
10158
|
+
appendFileSync5(target, note, "utf-8");
|
|
9914
10159
|
updated.add(relative(options.cwd, target) || basename2(target));
|
|
9915
10160
|
}
|
|
9916
10161
|
return [...updated];
|
|
@@ -9921,7 +10166,7 @@ function buildIntentCandidates(cwd, relatedPaths, includeHierarchy) {
|
|
|
9921
10166
|
if (includeHierarchy) {
|
|
9922
10167
|
const roots = findRootToCwd(resolve6(cwd));
|
|
9923
10168
|
for (const dir of roots) {
|
|
9924
|
-
pushUnique(candidates, seen,
|
|
10169
|
+
pushUnique(candidates, seen, join13(dir, ".ztc-intents.md"));
|
|
9925
10170
|
}
|
|
9926
10171
|
}
|
|
9927
10172
|
for (const relatedPath of relatedPaths) {
|
|
@@ -9935,7 +10180,7 @@ function findRootToCwd(cwd) {
|
|
|
9935
10180
|
let current = cwd;
|
|
9936
10181
|
for (; ; ) {
|
|
9937
10182
|
chain.push(current);
|
|
9938
|
-
if (existsSync4(
|
|
10183
|
+
if (existsSync4(join13(current, ".git"))) {
|
|
9939
10184
|
break;
|
|
9940
10185
|
}
|
|
9941
10186
|
const parent = dirname5(current);
|
|
@@ -9950,21 +10195,21 @@ function selectIntentTarget(cwd, absolutePath, threshold) {
|
|
|
9950
10195
|
if (isLargeFile(absolutePath, threshold)) {
|
|
9951
10196
|
return fileIntentPath(absolutePath);
|
|
9952
10197
|
}
|
|
9953
|
-
const dir = existsSync4(absolutePath) &&
|
|
10198
|
+
const dir = existsSync4(absolutePath) && statSync2(absolutePath).isDirectory() ? absolutePath : dirname5(absolutePath);
|
|
9954
10199
|
const normalizedCwd = resolve6(cwd);
|
|
9955
10200
|
const normalizedDir = resolve6(dir);
|
|
9956
10201
|
if (!isInside(normalizedDir, normalizedCwd)) {
|
|
9957
|
-
return
|
|
10202
|
+
return join13(normalizedCwd, ".ztc-intents.md");
|
|
9958
10203
|
}
|
|
9959
|
-
return
|
|
10204
|
+
return join13(normalizedDir, ".ztc-intents.md");
|
|
9960
10205
|
}
|
|
9961
10206
|
function ensureIntentFile(path) {
|
|
9962
|
-
|
|
10207
|
+
mkdirSync4(dirname5(path), { recursive: true });
|
|
9963
10208
|
if (existsSync4(path)) {
|
|
9964
10209
|
return;
|
|
9965
10210
|
}
|
|
9966
10211
|
const title = basename2(path) === ".ztc-intents.md" ? "ZTC Folder Intents" : `ZTC File Intents for ${basename2(path).replace(/^\.ztc-intents-/, "").replace(/\.md$/, "")}`;
|
|
9967
|
-
|
|
10212
|
+
writeFileSync6(path, [
|
|
9968
10213
|
`# ${title}`,
|
|
9969
10214
|
"",
|
|
9970
10215
|
"Purpose:",
|
|
@@ -9974,14 +10219,14 @@ function ensureIntentFile(path) {
|
|
|
9974
10219
|
].join("\n") + "\n", "utf-8");
|
|
9975
10220
|
}
|
|
9976
10221
|
function fileIntentPath(absolutePath) {
|
|
9977
|
-
return
|
|
10222
|
+
return join13(dirname5(absolutePath), `.ztc-intents-${basename2(absolutePath)}.md`);
|
|
9978
10223
|
}
|
|
9979
10224
|
function isLargeFile(path, threshold) {
|
|
9980
10225
|
try {
|
|
9981
|
-
if (!existsSync4(path) ||
|
|
10226
|
+
if (!existsSync4(path) || statSync2(path).isDirectory()) {
|
|
9982
10227
|
return false;
|
|
9983
10228
|
}
|
|
9984
|
-
const raw =
|
|
10229
|
+
const raw = readFileSync7(path, "utf-8");
|
|
9985
10230
|
return raw.split("\n").length >= threshold;
|
|
9986
10231
|
} catch {
|
|
9987
10232
|
return false;
|
|
@@ -9992,7 +10237,7 @@ function loadBudgetedFile(path, maxBytes) {
|
|
|
9992
10237
|
return null;
|
|
9993
10238
|
}
|
|
9994
10239
|
try {
|
|
9995
|
-
const raw =
|
|
10240
|
+
const raw = readFileSync7(path, "utf-8");
|
|
9996
10241
|
const bytes = Buffer.byteLength(raw, "utf-8");
|
|
9997
10242
|
if (bytes <= maxBytes) {
|
|
9998
10243
|
return {
|
|
@@ -10028,18 +10273,6 @@ function isInside(path, root) {
|
|
|
10028
10273
|
return path === root || path.startsWith(`${root}/`);
|
|
10029
10274
|
}
|
|
10030
10275
|
|
|
10031
|
-
// src/agent/runtime/tool_effects.ts
|
|
10032
|
-
init_capabilities();
|
|
10033
|
-
function resolveToolEffect(tool) {
|
|
10034
|
-
if (tool.effect) return tool.effect;
|
|
10035
|
-
const capabilities = new Set(tool.capabilities ?? []);
|
|
10036
|
-
if (capabilities.has("shell_exec" /* SHELL_EXEC */) || capabilities.has("network" /* NETWORK */) || capabilities.has("computer_control" /* COMPUTER_CONTROL */) || capabilities.has("ml_inference" /* ML_INFERENCE */)) {
|
|
10037
|
-
return "external";
|
|
10038
|
-
}
|
|
10039
|
-
if (capabilities.has("file_write" /* FILE_WRITE */)) return "write";
|
|
10040
|
-
return "read";
|
|
10041
|
-
}
|
|
10042
|
-
|
|
10043
10276
|
// src/agent/runtime/tool_runtime.ts
|
|
10044
10277
|
function getTool(name, tools) {
|
|
10045
10278
|
return tools.find((t) => t.definition.name === name);
|
|
@@ -10049,7 +10282,7 @@ function isHostControlPlaneWrite(tool, args, cwd) {
|
|
|
10049
10282
|
try {
|
|
10050
10283
|
const noFollowLeaf = (path) => {
|
|
10051
10284
|
const absolute = resolve7(path);
|
|
10052
|
-
return
|
|
10285
|
+
return join14(realpathSync(dirname6(absolute)), basename3(absolute));
|
|
10053
10286
|
};
|
|
10054
10287
|
return noFollowLeaf(resolve7(cwd, args.path)) === noFollowLeaf(tool.hostControlPlaneWritePath);
|
|
10055
10288
|
} catch {
|
|
@@ -10058,7 +10291,7 @@ function isHostControlPlaneWrite(tool, args, cwd) {
|
|
|
10058
10291
|
}
|
|
10059
10292
|
async function executeTool(name, args, tools, context) {
|
|
10060
10293
|
const tool = getTool(name, tools);
|
|
10061
|
-
if (!tool) throw new Error(
|
|
10294
|
+
if (!tool) throw new Error(`Unknown tool: ${name}. It is unavailable in the current mode or provider surface. Inspect current execution capabilities and use an available tool; shell execution requires run_command or a registered alias.`);
|
|
10062
10295
|
return tool.execute(args, context);
|
|
10063
10296
|
}
|
|
10064
10297
|
function buildHookText(messages) {
|
|
@@ -10117,7 +10350,7 @@ ${intentContext}`;
|
|
|
10117
10350
|
async function recordToolTrace(entry) {
|
|
10118
10351
|
const configured = process.env.ZTC_TOOL_TRACE;
|
|
10119
10352
|
if (!configured) return;
|
|
10120
|
-
const target = configured === "1" ?
|
|
10353
|
+
const target = configured === "1" ? join14(process.cwd(), "ztc-tool-trace.jsonl") : configured;
|
|
10121
10354
|
try {
|
|
10122
10355
|
await appendFile(target, `${JSON.stringify(entry)}
|
|
10123
10356
|
`, "utf-8");
|
|
@@ -10325,6 +10558,7 @@ async function executeToolWithRuntime(name, args, options) {
|
|
|
10325
10558
|
if (timer) clearTimeout(timer);
|
|
10326
10559
|
});
|
|
10327
10560
|
}
|
|
10561
|
+
if (tool?.isErrorResult?.(output)) throw new Error(output);
|
|
10328
10562
|
const TOOL_OUTPUT_CAP = 8e4;
|
|
10329
10563
|
output = augmentReadResultWithIntent(name, effectiveArgs, output, cwd);
|
|
10330
10564
|
let resultContent = output;
|
|
@@ -10399,31 +10633,6 @@ async function executeToolWithRuntime(name, args, options) {
|
|
|
10399
10633
|
}
|
|
10400
10634
|
}
|
|
10401
10635
|
|
|
10402
|
-
// src/utils/subprocess_env.ts
|
|
10403
|
-
var USER_SUBPROCESS_STRIPPED_ZTC_ENV = [
|
|
10404
|
-
"ZERG_ROUTER_BASE_URL",
|
|
10405
|
-
"ZTC_AUTH_TOKEN",
|
|
10406
|
-
"ZTC_CHILD_ENTRYPOINT",
|
|
10407
|
-
"ZTC_COLONY_CHILD",
|
|
10408
|
-
"ZTC_COLONY_PARENT_MIRROR_PORT",
|
|
10409
|
-
"ZTC_COLONY_WORKTREE_ID",
|
|
10410
|
-
"ZTC_MIRROR_PORT",
|
|
10411
|
-
"ZTC_MIRROR_INTERNAL_TOKEN",
|
|
10412
|
-
"ZTC_MIRROR_REQUIRE_AUTH",
|
|
10413
|
-
"ZTC_MIRROR_SHARED_SECRET",
|
|
10414
|
-
"ZTC_NAVIGATOR_GROUP",
|
|
10415
|
-
"ZTC_PANE_ID",
|
|
10416
|
-
"ZTC_PANE_RELAY",
|
|
10417
|
-
"ZTC_PARENT_PID"
|
|
10418
|
-
];
|
|
10419
|
-
function buildUserSubprocessEnv(source = process.env) {
|
|
10420
|
-
const env = { ...source };
|
|
10421
|
-
for (const key of USER_SUBPROCESS_STRIPPED_ZTC_ENV) {
|
|
10422
|
-
delete env[key];
|
|
10423
|
-
}
|
|
10424
|
-
return env;
|
|
10425
|
-
}
|
|
10426
|
-
|
|
10427
10636
|
// src/utils/compaction.ts
|
|
10428
10637
|
init_provider_ids();
|
|
10429
10638
|
init_context();
|
|
@@ -10593,6 +10802,9 @@ var Agent = class _Agent {
|
|
|
10593
10802
|
maxTokens: config.maxTokens,
|
|
10594
10803
|
routePlan: config.routePlan,
|
|
10595
10804
|
maxIterations: config.maxIterations || 10,
|
|
10805
|
+
execution: resolveExecutionSettings(config.execution),
|
|
10806
|
+
sessionId: config.sessionId ?? randomUUID6(),
|
|
10807
|
+
runtimeFacts: config.runtimeFacts ?? {},
|
|
10596
10808
|
apiRetry: resolveApiRetrySettings({ apiRetry: config.apiRetry }),
|
|
10597
10809
|
llmRequestTimeoutMs: resolveLlmRequestTimeoutMs(
|
|
10598
10810
|
config.llmRequestTimeoutMs,
|
|
@@ -10821,7 +11033,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
10821
11033
|
const ext = extname(path).toLowerCase();
|
|
10822
11034
|
const mediaType = this.getImageMediaType(ext);
|
|
10823
11035
|
if (!mediaType) return null;
|
|
10824
|
-
const buffer =
|
|
11036
|
+
const buffer = readFileSync8(path);
|
|
10825
11037
|
return {
|
|
10826
11038
|
type: "image",
|
|
10827
11039
|
mediaType,
|
|
@@ -10881,7 +11093,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
10881
11093
|
const ext = extname(path).toLowerCase();
|
|
10882
11094
|
const mediaType = this.getVideoMediaType(ext);
|
|
10883
11095
|
if (!mediaType) return null;
|
|
10884
|
-
const buffer =
|
|
11096
|
+
const buffer = readFileSync8(path);
|
|
10885
11097
|
return {
|
|
10886
11098
|
type: "video",
|
|
10887
11099
|
mediaType,
|
|
@@ -10915,7 +11127,8 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
10915
11127
|
if (!Array.isArray(parsed)) return null;
|
|
10916
11128
|
return parsed.filter((item) => typeof item.tool_use_id === "string").map((item) => ({
|
|
10917
11129
|
tool_use_id: item.tool_use_id,
|
|
10918
|
-
content: typeof item.content === "string" ? item.content : String(item.content ?? "")
|
|
11130
|
+
content: typeof item.content === "string" ? item.content : String(item.content ?? ""),
|
|
11131
|
+
is_error: item.is_error
|
|
10919
11132
|
}));
|
|
10920
11133
|
} catch {
|
|
10921
11134
|
return null;
|
|
@@ -10996,7 +11209,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
10996
11209
|
[truncated \u2014 ${content.length} chars total]`;
|
|
10997
11210
|
}
|
|
10998
11211
|
emittedToolResultIds.add(tc.id);
|
|
10999
|
-
return { tool_use_id: tc.id, content };
|
|
11212
|
+
return { tool_use_id: tc.id, content, is_error: Boolean(tc.error) || tc.result == null };
|
|
11000
11213
|
});
|
|
11001
11214
|
if (toolResults.length === 0) continue;
|
|
11002
11215
|
const syntheticReason = hasStoredResults ? "synth_tui_tool_results" : "repair_orphaned_tool_use";
|
|
@@ -11011,12 +11224,30 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11011
11224
|
return repaired;
|
|
11012
11225
|
}
|
|
11013
11226
|
buildRequest(messages, availableTools = this.config.tools) {
|
|
11227
|
+
if (this.config.pinTaskHeader) {
|
|
11228
|
+
const facts = describeExecutionContext({ ...this.getExecutionContext(), tools: availableTools.map((tool) => tool.definition.name) });
|
|
11229
|
+
const latestFacts = [...messages].reverse().find((message) => typeof message.metadata?.executionContext === "string")?.metadata?.executionContext;
|
|
11230
|
+
if (facts !== latestFacts) {
|
|
11231
|
+
const latestUser = [...messages].reverse().find((message) => message.role === "user");
|
|
11232
|
+
if (latestUser) latestUser.metadata = { ...latestUser.metadata, executionContext: facts };
|
|
11233
|
+
}
|
|
11234
|
+
}
|
|
11014
11235
|
const repairedMessages = this.repairConversationHistory(messages);
|
|
11236
|
+
const contextMessages = this.config.pinTaskHeader ? repairedMessages.flatMap((message) => {
|
|
11237
|
+
const facts = message.metadata?.executionContext;
|
|
11238
|
+
return typeof facts === "string" ? [message, {
|
|
11239
|
+
id: `${message.id}_execution`,
|
|
11240
|
+
role: "user",
|
|
11241
|
+
content: facts,
|
|
11242
|
+
timestamp: message.timestamp,
|
|
11243
|
+
metadata: { synthetic: true }
|
|
11244
|
+
}] : [message];
|
|
11245
|
+
}) : repairedMessages;
|
|
11015
11246
|
return {
|
|
11016
11247
|
model: this.config.model,
|
|
11017
11248
|
maxTokens: this.config.maxTokens,
|
|
11018
11249
|
system: this.systemWithTaskHeader(messages),
|
|
11019
|
-
messages: this.formatMessages(
|
|
11250
|
+
messages: this.formatMessages(contextMessages),
|
|
11020
11251
|
tools: getRequestToolDefinitions(
|
|
11021
11252
|
availableTools,
|
|
11022
11253
|
this.discoveredTools,
|
|
@@ -11211,11 +11442,9 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11211
11442
|
*/
|
|
11212
11443
|
_taskHeader = null;
|
|
11213
11444
|
/**
|
|
11214
|
-
* Drop the pinned task
|
|
11215
|
-
* message. Wired into the /clear-only path (clearTransientState), never into
|
|
11445
|
+
* Drop the explicitly pinned task for the next conversation. Wired into the /clear-only path (clearTransientState), never into
|
|
11216
11446
|
* /compact — the pin must survive compaction (that is the whole point of R7).
|
|
11217
|
-
* Null
|
|
11218
|
-
* "no task found."
|
|
11447
|
+
* Null represents no explicitly pinned goal.
|
|
11219
11448
|
*/
|
|
11220
11449
|
clearPinnedTask() {
|
|
11221
11450
|
this._taskHeader = null;
|
|
@@ -11230,23 +11459,28 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11230
11459
|
* for the whole run. A driver that starts or switches a goal calls this so
|
|
11231
11460
|
* request.system and the goal's per-turn re-injection name the same objective.
|
|
11232
11461
|
*
|
|
11233
|
-
* A blank value
|
|
11462
|
+
* A blank value clears the pin, matching clearPinnedTask.
|
|
11234
11463
|
*/
|
|
11235
11464
|
setPinnedTask(task) {
|
|
11236
11465
|
const trimmed = task.trim();
|
|
11237
11466
|
this._taskHeader = trimmed.length > 0 ? trimmed : null;
|
|
11238
11467
|
}
|
|
11239
11468
|
/** Append the pinned task header (R7) to the static system prompt. */
|
|
11240
|
-
|
|
11241
|
-
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
this.
|
|
11249
|
-
|
|
11469
|
+
getExecutionContext() {
|
|
11470
|
+
return {
|
|
11471
|
+
mode: this.config.runtimeFacts.mode ?? "normal",
|
|
11472
|
+
cwd: this._cwd,
|
|
11473
|
+
tools: this.getConfiguredToolNames(),
|
|
11474
|
+
mux: this.config.runtimeFacts.mux ?? false,
|
|
11475
|
+
configScope: this.config.runtimeFacts.configScope ?? "host configuration",
|
|
11476
|
+
maxTokens: this.config.maxTokens,
|
|
11477
|
+
outputLimitSource: this.config.runtimeFacts.outputLimitSource ?? (this.config.maxTokens === void 0 ? "provider default" : "explicit configuration"),
|
|
11478
|
+
settings: resolveExecutionSettings(this.config.execution),
|
|
11479
|
+
jobs: listCommandJobs(this.config.sessionId)
|
|
11480
|
+
};
|
|
11481
|
+
}
|
|
11482
|
+
systemWithTaskHeader(_messages) {
|
|
11483
|
+
if (!this.config.pinTaskHeader) return this.config.systemPrompt;
|
|
11250
11484
|
return pinnedTaskSystem(this.config.systemPrompt, this._taskHeader);
|
|
11251
11485
|
}
|
|
11252
11486
|
/**
|
|
@@ -11255,6 +11489,17 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11255
11489
|
* the same core loop used by unhosted ZTC.
|
|
11256
11490
|
*/
|
|
11257
11491
|
async run(messages, signal, takeSteering) {
|
|
11492
|
+
const onInterrupt = () => {
|
|
11493
|
+
if (this.config.execution.interruptBackgroundJobs) void interruptCommandJobs(this.config.sessionId);
|
|
11494
|
+
};
|
|
11495
|
+
signal?.addEventListener("abort", onInterrupt, { once: true });
|
|
11496
|
+
try {
|
|
11497
|
+
return await this.runBound(messages, signal, takeSteering);
|
|
11498
|
+
} finally {
|
|
11499
|
+
signal?.removeEventListener("abort", onInterrupt);
|
|
11500
|
+
}
|
|
11501
|
+
}
|
|
11502
|
+
async runBound(messages, signal, takeSteering) {
|
|
11258
11503
|
const binding = this.#conversationBinding;
|
|
11259
11504
|
if (!binding) return this.runCore(messages, signal, takeSteering);
|
|
11260
11505
|
const userMessage = [...messages].reverse().find(
|
|
@@ -11293,7 +11538,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11293
11538
|
}
|
|
11294
11539
|
const result = await this.runCore(workingMessages, signal);
|
|
11295
11540
|
const assistantMessage = {
|
|
11296
|
-
id: `host_assistant_${
|
|
11541
|
+
id: `host_assistant_${randomUUID6()}`,
|
|
11297
11542
|
role: "assistant",
|
|
11298
11543
|
content: result.content,
|
|
11299
11544
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -11334,7 +11579,6 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11334
11579
|
}
|
|
11335
11580
|
const allToolCalls = [];
|
|
11336
11581
|
const turnMessages = [];
|
|
11337
|
-
let acceptedSteering = false;
|
|
11338
11582
|
let iterations = 0;
|
|
11339
11583
|
this._consecutiveAutoContinues = 0;
|
|
11340
11584
|
let finalContent = "";
|
|
@@ -11344,6 +11588,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11344
11588
|
let runError = null;
|
|
11345
11589
|
let stopReason = "max_iterations";
|
|
11346
11590
|
const applyRepeatGuard = createRepeatGuard();
|
|
11591
|
+
const recovery = createRecoveryGuard(this.config.execution);
|
|
11347
11592
|
try {
|
|
11348
11593
|
while (iterations < this.config.maxIterations) {
|
|
11349
11594
|
if (signal?.aborted) {
|
|
@@ -11445,7 +11690,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11445
11690
|
data: { tool: toolBlock.name }
|
|
11446
11691
|
});
|
|
11447
11692
|
const guarded = applyRepeatGuard(toolBlock.name, toolBlock.input, content);
|
|
11448
|
-
return { toolCall, toolResult: { tool_use_id: toolBlock.id, content: guarded } };
|
|
11693
|
+
return { toolCall, toolResult: { tool_use_id: toolBlock.id, content: guarded, is_error: false } };
|
|
11449
11694
|
};
|
|
11450
11695
|
const recordToolError = (toolBlock, toolCall, error, effectiveArgs) => {
|
|
11451
11696
|
toolCall.completedAt = /* @__PURE__ */ new Date();
|
|
@@ -11462,7 +11707,8 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11462
11707
|
toolCall,
|
|
11463
11708
|
toolResult: {
|
|
11464
11709
|
tool_use_id: toolBlock.id,
|
|
11465
|
-
content: applyRepeatGuard(toolBlock.name, toolBlock.input, `Error: ${error}`)
|
|
11710
|
+
content: applyRepeatGuard(toolBlock.name, toolBlock.input, `Error: ${error}`),
|
|
11711
|
+
is_error: true
|
|
11466
11712
|
}
|
|
11467
11713
|
};
|
|
11468
11714
|
};
|
|
@@ -11475,7 +11721,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11475
11721
|
const exclusiveTransitionId = toolUseBlocks.find(
|
|
11476
11722
|
(toolBlock) => toolBlock.name === "plan_mode"
|
|
11477
11723
|
)?.id ?? null;
|
|
11478
|
-
const
|
|
11724
|
+
const combined = await scheduleToolBatch(toolUseBlocks, this.config.tools, this.config.execution, async (toolBlock, skipReason) => {
|
|
11479
11725
|
const toolCall = {
|
|
11480
11726
|
id: toolBlock.id,
|
|
11481
11727
|
name: toolBlock.name,
|
|
@@ -11489,6 +11735,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11489
11735
|
data: { tool: toolBlock.name }
|
|
11490
11736
|
});
|
|
11491
11737
|
this.emit({ type: "tool_start", tool: toolBlock.name, args: toolBlock.input, toolCallId: toolBlock.id });
|
|
11738
|
+
if (skipReason) return recordToolError(toolBlock, toolCall, skipReason);
|
|
11492
11739
|
if (exclusiveTransitionId !== null && toolBlock.id !== exclusiveTransitionId) {
|
|
11493
11740
|
return recordToolError(
|
|
11494
11741
|
toolBlock,
|
|
@@ -11530,6 +11777,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11530
11777
|
context: {
|
|
11531
11778
|
cwd: this._cwd,
|
|
11532
11779
|
toolCallId: toolBlock.id,
|
|
11780
|
+
sessionId: this.config.sessionId,
|
|
11533
11781
|
signal,
|
|
11534
11782
|
toolResponseStore: this.toolResponseStore,
|
|
11535
11783
|
discoveredTools: this.discoveredTools,
|
|
@@ -11556,6 +11804,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11556
11804
|
context: {
|
|
11557
11805
|
cwd: this._cwd,
|
|
11558
11806
|
toolCallId: toolBlock.id,
|
|
11807
|
+
sessionId: this.config.sessionId,
|
|
11559
11808
|
signal,
|
|
11560
11809
|
toolResponseStore: this.toolResponseStore,
|
|
11561
11810
|
discoveredTools: this.discoveredTools,
|
|
@@ -11579,8 +11828,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11579
11828
|
const error = err.message;
|
|
11580
11829
|
return recordToolError(toolBlock, toolCall, error);
|
|
11581
11830
|
}
|
|
11582
|
-
});
|
|
11583
|
-
const combined = await Promise.all(toolPromises);
|
|
11831
|
+
}, (result) => result.toolCall.status === "error" || commandStillRunning(result.toolResult.content), signal, exclusiveTransitionId);
|
|
11584
11832
|
allToolCalls.push(...combined.map((r) => r.toolCall));
|
|
11585
11833
|
const partial = Boolean(signal?.aborted);
|
|
11586
11834
|
const toolResults = combined.map((r) => {
|
|
@@ -11591,20 +11839,14 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11591
11839
|
partial,
|
|
11592
11840
|
{
|
|
11593
11841
|
sourcePath: extractSourcePath(r.toolCall.name, r.toolCall.args),
|
|
11594
|
-
dedupable: r.toolCall.status === "complete"
|
|
11842
|
+
dedupable: r.toolCall.status === "complete",
|
|
11843
|
+
isError: r.toolResult.is_error
|
|
11595
11844
|
}
|
|
11596
11845
|
);
|
|
11597
|
-
return { tool_use_id: r.toolResult.tool_use_id, content: rendered };
|
|
11846
|
+
return { tool_use_id: r.toolResult.tool_use_id, content: rendered, is_error: r.toolResult.is_error };
|
|
11598
11847
|
});
|
|
11599
|
-
if (signal?.aborted) {
|
|
11600
|
-
throw new AgentError("Cancelled", "CANCELLED");
|
|
11601
|
-
}
|
|
11602
11848
|
this._consecutiveAutoContinues = 0;
|
|
11603
11849
|
const textContent = response.content.filter(this.isTextBlock).map((b) => b.text).join("\n");
|
|
11604
|
-
if (textContent && textContent.trim()) {
|
|
11605
|
-
if (!this._lastResponseStreamed) await this.emitStreamText(textContent, signal);
|
|
11606
|
-
lastToolTurnText = textContent;
|
|
11607
|
-
}
|
|
11608
11850
|
const toolUseMessage = {
|
|
11609
11851
|
id: `assistant_${Date.now()}`,
|
|
11610
11852
|
role: "assistant",
|
|
@@ -11624,6 +11866,30 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11624
11866
|
};
|
|
11625
11867
|
messages.push(toolResultMessage);
|
|
11626
11868
|
turnMessages.push(toolResultMessage);
|
|
11869
|
+
this.emit({ type: "history_checkpoint", messages: [...turnMessages] });
|
|
11870
|
+
if (signal?.aborted) throw new AgentError("Cancelled", "CANCELLED");
|
|
11871
|
+
if (textContent && textContent.trim() && !signal?.aborted) {
|
|
11872
|
+
if (!this._lastResponseStreamed) await this.emitStreamText(textContent, signal);
|
|
11873
|
+
lastToolTurnText = textContent;
|
|
11874
|
+
}
|
|
11875
|
+
const recoveryAction = recovery(combined.map((result) => ({
|
|
11876
|
+
name: result.toolCall.name,
|
|
11877
|
+
content: result.toolResult.content,
|
|
11878
|
+
isError: result.toolResult.is_error
|
|
11879
|
+
})));
|
|
11880
|
+
if (recoveryAction === "stop") throw new AgentError("Automatic recovery budget exhausted after repeated tool failures. Inspect the retained results and current capabilities before retrying.", "RECOVERY_EXHAUSTED");
|
|
11881
|
+
if (recoveryAction === "recover") {
|
|
11882
|
+
const recoveryMessage = {
|
|
11883
|
+
id: `recovery_${randomUUID6()}`,
|
|
11884
|
+
role: "user",
|
|
11885
|
+
content: "[Recovery] Repeated failures share the same cause. Stop that sequence. Inspect current capabilities and active jobs, then make one changed recovery attempt. Use file/stdin tools for scripts and raw Ctrl+C for stuck shell input, then read the pane in the same response or immediately afterward. A silent running-job poll allows one further inspection before stopping; do not repeat empty polls. Continue authorized work; use ask_user only for a concrete missing prerequisite. Do not repeat an acknowledgement as proof of success.",
|
|
11886
|
+
metadata: { synthetic: true },
|
|
11887
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
11888
|
+
};
|
|
11889
|
+
messages.push(recoveryMessage);
|
|
11890
|
+
turnMessages.push(recoveryMessage);
|
|
11891
|
+
this.emit({ type: "history_checkpoint", messages: [...turnMessages] });
|
|
11892
|
+
}
|
|
11627
11893
|
if (combined.length === 1 && combined[0].toolCall.status === "complete") {
|
|
11628
11894
|
const call = combined[0].toolCall;
|
|
11629
11895
|
const tool = this.config.tools.find((tool2) => tool2.definition.name === call.name);
|
|
@@ -11651,7 +11917,6 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11651
11917
|
if (steering.length > 0) {
|
|
11652
11918
|
messages.push(...steering);
|
|
11653
11919
|
turnMessages.push(...steering);
|
|
11654
|
-
acceptedSteering = true;
|
|
11655
11920
|
}
|
|
11656
11921
|
}
|
|
11657
11922
|
} catch (err) {
|
|
@@ -11694,7 +11959,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11694
11959
|
usage: totalUsage || void 0,
|
|
11695
11960
|
stopReason,
|
|
11696
11961
|
...finalReasoning ? { reasoning: finalReasoning } : {},
|
|
11697
|
-
|
|
11962
|
+
turnMessages
|
|
11698
11963
|
};
|
|
11699
11964
|
}
|
|
11700
11965
|
/**
|
|
@@ -11732,6 +11997,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11732
11997
|
context: {
|
|
11733
11998
|
cwd: this._cwd,
|
|
11734
11999
|
toolCallId,
|
|
12000
|
+
sessionId: this.config.sessionId,
|
|
11735
12001
|
signal: options.signal,
|
|
11736
12002
|
toolResponseStore: this.toolResponseStore,
|
|
11737
12003
|
discoveredTools: this.discoveredTools,
|
|
@@ -11776,7 +12042,7 @@ INVESTIGATION STRATEGY \u2014 before making tool calls to explore or debug, foll
|
|
|
11776
12042
|
const { maxAttempts, baseDelayMs: baseDelay } = this.config.apiRetry;
|
|
11777
12043
|
const timeoutMs = this.config.llmRequestTimeoutMs;
|
|
11778
12044
|
if (this.backend.handlesTransportRetries?.(request) === true) {
|
|
11779
|
-
request = { ...request, transportTurnId: request.transportTurnId ??
|
|
12045
|
+
request = { ...request, transportTurnId: request.transportTurnId ?? randomUUID6() };
|
|
11780
12046
|
}
|
|
11781
12047
|
const useStream = process.env.ZTC_NO_STREAM !== "1" && !this.backend.streamsSyntheticallyFor?.(request);
|
|
11782
12048
|
let answerOutputEmitted = false;
|
|
@@ -12007,20 +12273,20 @@ init_capabilities();
|
|
|
12007
12273
|
// src/skills/loader.ts
|
|
12008
12274
|
init_logger();
|
|
12009
12275
|
init_extension_discovery();
|
|
12010
|
-
import { homedir as
|
|
12011
|
-
import { join as
|
|
12276
|
+
import { homedir as homedir8 } from "os";
|
|
12277
|
+
import { join as join15, resolve as resolve8, basename as basename4 } from "path";
|
|
12012
12278
|
import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
|
|
12013
12279
|
var MAX_DEPTH = 4;
|
|
12014
12280
|
function defaultMarkdownRoots() {
|
|
12015
12281
|
const explicitRoots = extensionPathsFromEnvironment("ZTC_SKILL_PATHS");
|
|
12016
12282
|
if (isExplicitExtensionDiscovery()) return explicitRoots;
|
|
12017
|
-
const home =
|
|
12283
|
+
const home = homedir8();
|
|
12018
12284
|
return [
|
|
12019
12285
|
...explicitRoots,
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12286
|
+
join15(process.cwd(), ".ztc", "skills"),
|
|
12287
|
+
join15(process.cwd(), ".claude", "skills"),
|
|
12288
|
+
join15(home, ".ztc", "skills"),
|
|
12289
|
+
join15(home, ".claude", "skills")
|
|
12024
12290
|
];
|
|
12025
12291
|
}
|
|
12026
12292
|
async function walk(dir, depth = 0) {
|
|
@@ -12030,7 +12296,7 @@ async function walk(dir, depth = 0) {
|
|
|
12030
12296
|
const results = [];
|
|
12031
12297
|
for (const entry of entries) {
|
|
12032
12298
|
if (entry.name.startsWith(".")) continue;
|
|
12033
|
-
const full =
|
|
12299
|
+
const full = join15(dir, entry.name);
|
|
12034
12300
|
if (entry.isDirectory()) {
|
|
12035
12301
|
results.push(...await walk(full, depth + 1));
|
|
12036
12302
|
} else if (entry.isFile() && entry.name.toLowerCase() === "skill.md") {
|
|
@@ -12177,7 +12443,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
12177
12443
|
|
|
12178
12444
|
// src/skills/bundled/init-context.ts
|
|
12179
12445
|
import { readdir as readdir3, readFile as readFile5, stat } from "fs/promises";
|
|
12180
|
-
import { join as
|
|
12446
|
+
import { join as join16 } from "path";
|
|
12181
12447
|
var MAX_ENTRIES = 60;
|
|
12182
12448
|
var TOP_LEVEL_DIRS_OF_INTEREST = [
|
|
12183
12449
|
"src",
|
|
@@ -12238,7 +12504,7 @@ async function safeReadDir(path) {
|
|
|
12238
12504
|
async function detectManifests(cwd) {
|
|
12239
12505
|
const found = [];
|
|
12240
12506
|
for (const { file, label } of MANIFESTS) {
|
|
12241
|
-
if (await fileExists(
|
|
12507
|
+
if (await fileExists(join16(cwd, file))) {
|
|
12242
12508
|
found.push(`- ${file} \u2014 ${label}`);
|
|
12243
12509
|
}
|
|
12244
12510
|
}
|
|
@@ -12246,7 +12512,7 @@ async function detectManifests(cwd) {
|
|
|
12246
12512
|
}
|
|
12247
12513
|
async function readClaudeMd(cwd) {
|
|
12248
12514
|
for (const name of CLAUDE_MD_CANDIDATES) {
|
|
12249
|
-
const path =
|
|
12515
|
+
const path = join16(cwd, name);
|
|
12250
12516
|
if (await fileExists(path)) {
|
|
12251
12517
|
try {
|
|
12252
12518
|
const contents = await readFile5(path, "utf-8");
|
|
@@ -12505,7 +12771,7 @@ var invokeSkillTool = makeInvokeSkillTool(resolveAmbientSkills);
|
|
|
12505
12771
|
// src/zlm.ts
|
|
12506
12772
|
var ZLM_EMBED_API_VERSION = "zerglang.ztc-embed/1";
|
|
12507
12773
|
var ZLM_STDIO_PROTOCOL = "zerglang.ztc-host/1";
|
|
12508
|
-
var ZLM_ZTC_VERSION = true ? "0.
|
|
12774
|
+
var ZLM_ZTC_VERSION = true ? "0.4.0-beta.1" : "development";
|
|
12509
12775
|
var ZLM_MAX_HOST_TOOLS = 64;
|
|
12510
12776
|
var ZLM_MAX_SYSTEM_PROMPT_BYTES = 128 * 1024;
|
|
12511
12777
|
var ZLM_HOST_APPROVAL_CAPABILITIES = Object.freeze([
|