nexrall-code 0.5.130 → 0.5.132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +255 -156
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11208,7 +11208,7 @@ var require_securityLint = __commonJS({
|
|
|
11208
11208
|
// So the interpolation must name something that plausibly came from the
|
|
11209
11209
|
// outside: req/request/params/query/body/input/user/args, or a bare
|
|
11210
11210
|
// `'...' + ident`. This trades recall for precision on purpose — thorough SQL
|
|
11211
|
-
// review is
|
|
11211
|
+
// review is a dedicated security review's job, not an inline regex's.
|
|
11212
11212
|
//
|
|
11213
11213
|
// THE GAP IS PER-ALTERNATIVE, and that asymmetry is load-bearing. The concat
|
|
11214
11214
|
// branch originally shared the no-quote gap, which made it miss the QUOTED
|
|
@@ -12188,24 +12188,6 @@ var require_agentTypes = __commonJS({
|
|
|
12188
12188
|
"get_diagnostics"
|
|
12189
12189
|
];
|
|
12190
12190
|
var RESEARCH_TOOLS = [...READ_ONLY_TOOLS, "fetch_url", "web_search"];
|
|
12191
|
-
var WRITE_TOOLS = [
|
|
12192
|
-
...READ_ONLY_TOOLS,
|
|
12193
|
-
"write_file",
|
|
12194
|
-
"edit_file",
|
|
12195
|
-
"multi_edit",
|
|
12196
|
-
"create_directory",
|
|
12197
|
-
"move_file",
|
|
12198
|
-
"copy_file",
|
|
12199
|
-
// notebook_edit is a write tool like any other, and allowsTestOnlyWrite already
|
|
12200
|
-
// knows its shape (`source` is cell CONTENT, not a path). Omitting it just meant
|
|
12201
|
-
// test-writer silently could not touch notebooks.
|
|
12202
|
-
"notebook_edit",
|
|
12203
|
-
// Office document generation — same write-access tier as write_file, just a
|
|
12204
|
-
// structured content shape instead of raw text (see tools/executor.ts).
|
|
12205
|
-
"write_docx",
|
|
12206
|
-
"write_xlsx",
|
|
12207
|
-
"write_pptx"
|
|
12208
|
-
];
|
|
12209
12191
|
var BUILTIN_AGENTS = [
|
|
12210
12192
|
{
|
|
12211
12193
|
// The catch-all, matching Claude Code's `general-purpose`.
|
|
@@ -12238,66 +12220,6 @@ var require_agentTypes = __commonJS({
|
|
|
12238
12220
|
prompt: "You are a general-purpose engineering sub-agent. Work the task end to end: explore what you need, make the changes, and verify them with the project's own build/test commands.\n\nRules:\n- Mirror existing conventions; make the smallest correct change.\n- Verify before you claim success. If you could not verify, say so explicitly.\n- Your FINAL MESSAGE is the only thing that reaches the main agent: state what you changed (with file paths), what you ran and its outcome, and anything you deliberately left undone.",
|
|
12239
12221
|
source: "builtin"
|
|
12240
12222
|
},
|
|
12241
|
-
{
|
|
12242
|
-
name: "reviewer",
|
|
12243
|
-
description: "Read-only code reviewer \u2014 finds correctness bugs, edge cases, and security issues in a diff or file set. Cannot modify files.",
|
|
12244
|
-
tools: READ_ONLY_TOOLS,
|
|
12245
|
-
source: "builtin",
|
|
12246
|
-
prompt: [
|
|
12247
|
-
"You are a meticulous senior code reviewer. You NEVER modify files \u2014 you only read, search, and report.",
|
|
12248
|
-
"",
|
|
12249
|
-
"Method:",
|
|
12250
|
-
"1. Read the full context around every change you are asked to review; never judge a hunk in isolation.",
|
|
12251
|
-
"2. Hunt specifically for: correctness bugs, unhandled edge cases (empty/null/unicode/concurrency/timezone),",
|
|
12252
|
-
" security issues (injection, path traversal, secrets in code, unsafe deserialization), breaking API",
|
|
12253
|
-
" changes (search for callers first), silent behaviour changes, and swallowed errors.",
|
|
12254
|
-
"3. Verify test coverage: are the changed paths tested? Were assertions weakened or tests deleted?",
|
|
12255
|
-
"4. Only use bash for read-only commands (git diff/log/show, grep, test runs). Never run mutating commands.",
|
|
12256
|
-
"",
|
|
12257
|
-
"Report format: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F7E2} Suggestion, each with file:line and a concrete fix,",
|
|
12258
|
-
"then a final verdict (APPROVE or REQUEST CHANGES) with a one-paragraph rationale."
|
|
12259
|
-
].join("\n")
|
|
12260
|
-
},
|
|
12261
|
-
// Promoted from the security-audit plugin to a builtin.
|
|
12262
|
-
//
|
|
12263
|
-
// Leaving it plugin-only was indefensible next to `reviewer` being builtin:
|
|
12264
|
-
// reviewer's own prompt already tells it to look for security issues, so
|
|
12265
|
-
// security IS treated as default work — yet the specialist agent for it was
|
|
12266
|
-
// invisible unless the user happened to know the plugin existed. For an agent
|
|
12267
|
-
// that WRITES code, "you only get a security review if you knew to install
|
|
12268
|
-
// something" is the wrong default.
|
|
12269
|
-
{
|
|
12270
|
-
name: "security-auditor",
|
|
12271
|
-
description: "Read-only security auditor \u2014 hunts injection, authz, secrets, and validation flaws in a path or diff. Cannot modify files.",
|
|
12272
|
-
tools: READ_ONLY_TOOLS,
|
|
12273
|
-
// No `model` override — inherits the session's, same as general-purpose.
|
|
12274
|
-
// A previous version forced 'pro' (Claude Opus 5) here, which silently
|
|
12275
|
-
// billed Anthropic even for a session running entirely on OpenAI/DeepSeek/
|
|
12276
|
-
// Qwen. Consistency with the main conversation's provider matters more
|
|
12277
|
-
// than defaulting every specialist to a fixed tier.
|
|
12278
|
-
source: "builtin",
|
|
12279
|
-
prompt: [
|
|
12280
|
-
"You are a security auditor. You find real, exploitable flaws \u2014 not style issues.",
|
|
12281
|
-
"",
|
|
12282
|
-
"Method:",
|
|
12283
|
-
"1. Map the attack surface FIRST: entry points (HTTP routes, message handlers, CLI args, file/network",
|
|
12284
|
-
" input, deserialization), then trace user-controlled data inward to where it is used.",
|
|
12285
|
-
"2. For each finding: file:line, the flaw class, a one-line exploit scenario, and the concrete fix.",
|
|
12286
|
-
"3. Grade severity honestly: Critical = remote compromise or data breach; High = auth bypass/IDOR;",
|
|
12287
|
-
" Medium = needs unusual preconditions; Low = hardening.",
|
|
12288
|
-
"",
|
|
12289
|
-
"Classes worth the most attention, in order: injection (SQL/command/template/prototype), broken",
|
|
12290
|
-
"authz (missing ownership checks, IDOR, trusting client-supplied ids), secrets committed to source,",
|
|
12291
|
-
"path traversal, SSRF, unsafe deserialization, missing rate limits on expensive or auth endpoints,",
|
|
12292
|
-
"and crypto misuse (hand-rolled comparison, predictable randomness).",
|
|
12293
|
-
"",
|
|
12294
|
-
"Hard rules:",
|
|
12295
|
-
"- READ-ONLY: never modify, create or delete files. bash only for read-only inspection.",
|
|
12296
|
-
"- NEVER print a discovered secret's value. Report its location and advise rotation.",
|
|
12297
|
-
"- Distinguish EXPLOITABLE from theoretical, and say which one each finding is.",
|
|
12298
|
-
'- "No issues found in scope X" is a valid, useful result. Do not pad the report to look thorough.'
|
|
12299
|
-
].join("\n")
|
|
12300
|
-
},
|
|
12301
12223
|
// The gap Claude Code fills with its built-in `Explore`: read-heavy codebase
|
|
12302
12224
|
// search that would otherwise flood the parent's context. Defaults to the
|
|
12303
12225
|
// cheapest model on purpose — "find every caller of X" has no need of a
|
|
@@ -12338,10 +12260,14 @@ var require_agentTypes = __commonJS({
|
|
|
12338
12260
|
// quietly become "start editing".
|
|
12339
12261
|
{
|
|
12340
12262
|
name: "planner",
|
|
12263
|
+
// Lean prompt, as Claude Code's Plan builtin: it researches and reports, and the MAIN
|
|
12264
|
+
// agent (which has nexrall.md and the conversation) judges the plan against project
|
|
12265
|
+
// rules. Conventions that must bind the plan belong in the delegation prompt.
|
|
12266
|
+
lightPrompt: true,
|
|
12341
12267
|
description: "Read-only planning agent \u2014 researches a change and returns a concrete step-by-step implementation plan with risks and affected files. Cannot modify files.",
|
|
12342
12268
|
tools: RESEARCH_TOOLS,
|
|
12343
12269
|
// No `model` override — inherits the session's, for the same reason as
|
|
12344
|
-
// explorer
|
|
12270
|
+
// explorer above: a fixed alias silently billed Anthropic
|
|
12345
12271
|
// regardless of the user's chosen provider.
|
|
12346
12272
|
source: "builtin",
|
|
12347
12273
|
prompt: [
|
|
@@ -12361,74 +12287,6 @@ var require_agentTypes = __commonJS({
|
|
|
12361
12287
|
"- How to verify (the exact test/build command for THIS project, taken from package.json/Makefile).",
|
|
12362
12288
|
"- Anything genuinely ambiguous, stated as an open question rather than a silent assumption."
|
|
12363
12289
|
].join("\n")
|
|
12364
|
-
},
|
|
12365
|
-
// Promoted from the test-gen plugin. Needs write access — it produces test
|
|
12366
|
-
// files — but is deliberately forbidden from touching source, because "make the
|
|
12367
|
-
// tests pass" is the single most common way an agent destroys signal.
|
|
12368
|
-
{
|
|
12369
|
-
name: "test-writer",
|
|
12370
|
-
description: "Writes tests that follow the project's existing conventions. May create/edit TEST files only \u2014 never production source.",
|
|
12371
|
-
tools: WRITE_TOOLS,
|
|
12372
|
-
// Enforced, not merely requested: the permission gate refuses a write whose
|
|
12373
|
-
// path is not a test file. Without this the allowlist would grant edit_file
|
|
12374
|
-
// for every path and the rule below would be a suggestion the model is free
|
|
12375
|
-
// to rationalise its way past.
|
|
12376
|
-
testFilesOnly: true,
|
|
12377
|
-
source: "builtin",
|
|
12378
|
-
prompt: [
|
|
12379
|
-
"You write tests. You may create and edit TEST files only.",
|
|
12380
|
-
"",
|
|
12381
|
-
"Hard rules \u2014 these are the ways test-writing agents destroy value, so they are non-negotiable:",
|
|
12382
|
-
"- NEVER modify production source to make a test pass. If the code looks wrong, REPORT it and stop.",
|
|
12383
|
-
"- NEVER weaken, delete or skip an existing assertion or test.",
|
|
12384
|
-
"- A test that cannot fail is worse than no test. Every test must be able to fail for one clear reason.",
|
|
12385
|
-
"",
|
|
12386
|
-
"Method:",
|
|
12387
|
-
"1. Read the existing tests FIRST and copy their conventions exactly \u2014 runner, file naming, layout,",
|
|
12388
|
-
" assertion style, fixture/helper patterns. Never introduce a new framework.",
|
|
12389
|
-
"2. Test observable behaviour and the contract, not private internals.",
|
|
12390
|
-
"3. Cover the boring-but-real cases: empty input, null/undefined, unicode and non-BMP characters,",
|
|
12391
|
-
" boundaries, error paths, concurrency where it applies.",
|
|
12392
|
-
"4. No sleeps or wall-clock dependence \u2014 those produce the flaky tests that get deleted later.",
|
|
12393
|
-
"5. RUN the tests you wrote and report the real output. Never claim a test passes without running it."
|
|
12394
|
-
].join("\n")
|
|
12395
|
-
},
|
|
12396
|
-
// The DevOps gap — answered with a READ-ONLY advisor, not an operator.
|
|
12397
|
-
//
|
|
12398
|
-
// A "DevOps agent" with write/apply access is a genuinely different risk class
|
|
12399
|
-
// from the others here: its mistakes are `kubectl delete`, a bad `terraform
|
|
12400
|
-
// apply`, a broken deploy pipeline — often not revertible and affecting
|
|
12401
|
-
// production rather than a working tree. So this one diagnoses and proposes a
|
|
12402
|
-
// diff; a human applies it. That asymmetry is the whole design.
|
|
12403
|
-
{
|
|
12404
|
-
name: "devops-advisor",
|
|
12405
|
-
description: "Read-only CI/CD, container, and infrastructure advisor \u2014 diagnoses pipelines, Dockerfiles, and k8s manifests and proposes concrete fixes as a diff. Never applies changes.",
|
|
12406
|
-
tools: RESEARCH_TOOLS,
|
|
12407
|
-
// No `model` override — inherits the session's, for the same reason as the
|
|
12408
|
-
// other specialists above.
|
|
12409
|
-
source: "builtin",
|
|
12410
|
-
prompt: [
|
|
12411
|
-
"You are an infrastructure and delivery advisor. You DIAGNOSE and PROPOSE. You never apply changes.",
|
|
12412
|
-
"",
|
|
12413
|
-
"Hard rules:",
|
|
12414
|
-
"- READ-ONLY, and stricter than the other read-only agents: bash is for INSPECTION only",
|
|
12415
|
-
" (git log/diff, cat, grep, `kubectl get/describe`, `docker images`, `terraform plan`).",
|
|
12416
|
-
" NEVER run anything that mutates infrastructure \u2014 no apply/delete/scale/rollout/restart/push,",
|
|
12417
|
-
" no `terraform apply`, no `helm upgrade`. If a fix needs such a command, WRITE IT OUT for a human.",
|
|
12418
|
-
"- Never print secret values from env files, k8s Secrets or CI variables. Reference them by name.",
|
|
12419
|
-
"",
|
|
12420
|
-
"Method:",
|
|
12421
|
-
"1. Read what actually exists \u2014 workflow files, Dockerfiles, manifests, kustomize overlays, the",
|
|
12422
|
-
' deploy scripts \u2014 before drawing any conclusion. Never reason from what a stack "usually" looks like.',
|
|
12423
|
-
"2. Follow the real path a change takes to production, and name the step that is broken or missing.",
|
|
12424
|
-
"3. Check the failure modes that bite hardest: CI path filters that skip files a workload actually",
|
|
12425
|
-
" needs, image tags that do not match what is deployed, missing health probes, absent resource",
|
|
12426
|
-
" limits, secrets baked into images, ports/timeouts inconsistent between proxy and app, and",
|
|
12427
|
-
" migrations that must run before the new image is live.",
|
|
12428
|
-
"",
|
|
12429
|
-
"Output: the diagnosis, the evidence (file:line or command output), the proposed change as a diff or",
|
|
12430
|
-
"exact file content, and the command a human should run to apply and verify it."
|
|
12431
|
-
].join("\n")
|
|
12432
12290
|
}
|
|
12433
12291
|
];
|
|
12434
12292
|
var KNOWN_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -107812,7 +107670,7 @@ ${body}`);
|
|
|
107812
107670
|
function formatTokenCount(n) {
|
|
107813
107671
|
return n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : String(n);
|
|
107814
107672
|
}
|
|
107815
|
-
function
|
|
107673
|
+
function formatElapsed2(ms) {
|
|
107816
107674
|
const s2 = Math.round(ms / 1e3);
|
|
107817
107675
|
return s2 < 60 ? `${s2}s` : `${Math.floor(s2 / 60)}m ${s2 % 60}s`;
|
|
107818
107676
|
}
|
|
@@ -108000,7 +107858,7 @@ ${agent.prompt}` : "",
|
|
|
108000
107858
|
}
|
|
108001
107859
|
isoState = void 0;
|
|
108002
107860
|
}
|
|
108003
|
-
const stats = `[sub-agent stats: ${subToolCalls} tool call(s) \xB7 ${formatTokenCount(subTokens)} tokens \xB7 $${subCost.toFixed(3)} \xB7 ${
|
|
107861
|
+
const stats = `[sub-agent stats: ${subToolCalls} tool call(s) \xB7 ${formatTokenCount(subTokens)} tokens \xB7 $${subCost.toFixed(3)} \xB7 ${formatElapsed2(Date.now() - subStartedAt)}]`;
|
|
108004
107862
|
const out = { ...r2, ...childVerifications.length ? { childVerifications } : {} };
|
|
108005
107863
|
if (out.error !== void 0)
|
|
108006
107864
|
out.error = `${out.error}
|
|
@@ -108871,7 +108729,7 @@ Continue the work from here.` }] });
|
|
|
108871
108729
|
hasOwnAgentStore: !!options._agentMemory,
|
|
108872
108730
|
// Same derivation, same reason, for the network. The base prompt's
|
|
108873
108731
|
// "Web search" section tells the run to reach the network, but
|
|
108874
|
-
// READ_ONLY_TOOLS (explorer,
|
|
108732
|
+
// READ_ONLY_TOOLS (explorer, planner, ...) contains neither
|
|
108875
108733
|
// `fetch_url` nor `web_search`. MEASURED on the 195-trial 2026-08-10
|
|
108876
108734
|
// deepseek-v4-pro benchmark: 29 of 51 `fetch_url` failures and 3 of 14
|
|
108877
108735
|
// `web_search` failures were the permission gate refusing a sub-agent
|
|
@@ -157566,7 +157424,8 @@ function composeFooter(base, extras, columns, reserve = 0) {
|
|
|
157566
157424
|
optional.push({ keep: 4, at: 2, segment: ctxSegment(extras.ctxPct) });
|
|
157567
157425
|
if (typeof extras?.costUsd === "number")
|
|
157568
157426
|
optional.push({ keep: 2, at: 3, segment: { text: money2(extras.costUsd), tone: "dim" } });
|
|
157569
|
-
|
|
157427
|
+
let tailShown = tail.length;
|
|
157428
|
+
const build = (kept2) => [head, ...[...kept2].sort((x2, y) => x2.at - y.at).map((k) => k.segment), ...tail.slice(0, tailShown)];
|
|
157570
157429
|
const width = (segs) => visibleLen(segs.map((s2) => s2.text).join(SEP2));
|
|
157571
157430
|
let kept = [...optional];
|
|
157572
157431
|
const budget = columns - 1 - reserve;
|
|
@@ -157577,6 +157436,8 @@ function composeFooter(base, extras, columns, reserve = 0) {
|
|
|
157577
157436
|
lowest = i2;
|
|
157578
157437
|
kept.splice(lowest, 1);
|
|
157579
157438
|
}
|
|
157439
|
+
while (tailShown > 0 && width(build(kept)) > budget)
|
|
157440
|
+
tailShown--;
|
|
157580
157441
|
return build(kept);
|
|
157581
157442
|
}
|
|
157582
157443
|
|
|
@@ -157896,6 +157757,7 @@ var BUILTIN_SLASH_COMMANDS = [
|
|
|
157896
157757
|
{ name: "cost", description: "Tokens and cost of this conversation", kind: "builtin" },
|
|
157897
157758
|
{ name: "status", description: "Version, model, mode, directory and session at a glance", kind: "builtin" },
|
|
157898
157759
|
{ name: "copy", description: "Copy the last reply to the clipboard", kind: "builtin" },
|
|
157760
|
+
{ name: "bashes", description: "List, show or stop commands moved to the background (Ctrl+B)", argHint: "[show|kill <id>]", kind: "builtin" },
|
|
157899
157761
|
{ name: "expand", description: "Show the full output of a folded tool call (Ctrl+O)", argHint: "[n]", kind: "builtin" },
|
|
157900
157762
|
{ name: "export", description: "Write this conversation to a Markdown file", argHint: "[file]", kind: "builtin" },
|
|
157901
157763
|
{ name: "rename", description: "Rename this session", argHint: "<title>", requiresArg: true, kind: "builtin" },
|
|
@@ -158037,6 +157899,7 @@ function runBang(cmd, opts) {
|
|
|
158037
157899
|
let timedOut = false;
|
|
158038
157900
|
let aborted = false;
|
|
158039
157901
|
let settled = false;
|
|
157902
|
+
let detached = false;
|
|
158040
157903
|
const child = spawn("bash", ["-c", cmd], {
|
|
158041
157904
|
cwd: opts.cwd,
|
|
158042
157905
|
env: opts.env ?? process.env,
|
|
@@ -158077,11 +157940,25 @@ function runBang(cmd, opts) {
|
|
|
158077
157940
|
};
|
|
158078
157941
|
child.stdout.on("data", take);
|
|
158079
157942
|
child.stderr.on("data", take);
|
|
157943
|
+
opts.onSpawn?.({ kill });
|
|
158080
157944
|
const timer = setTimeout(() => {
|
|
158081
157945
|
timedOut = true;
|
|
158082
157946
|
kill();
|
|
158083
157947
|
}, timeoutMs);
|
|
157948
|
+
const snapshot = () => {
|
|
157949
|
+
const kept = head.length + tail.length;
|
|
157950
|
+
return { output: head + tail, droppedBytes: Math.max(0, total - kept), code: null, signal: null, timedOut, aborted };
|
|
157951
|
+
};
|
|
158084
157952
|
const poll = setInterval(() => {
|
|
157953
|
+
if (detached)
|
|
157954
|
+
return;
|
|
157955
|
+
if (opts.detach?.requested) {
|
|
157956
|
+
detached = true;
|
|
157957
|
+
clearTimeout(timer);
|
|
157958
|
+
clearInterval(poll);
|
|
157959
|
+
resolve8({ ...snapshot(), detached: true });
|
|
157960
|
+
return;
|
|
157961
|
+
}
|
|
158085
157962
|
if (opts.signal?.aborted && !aborted) {
|
|
158086
157963
|
aborted = true;
|
|
158087
157964
|
kill();
|
|
@@ -158093,8 +157970,11 @@ function runBang(cmd, opts) {
|
|
|
158093
157970
|
settled = true;
|
|
158094
157971
|
clearTimeout(timer);
|
|
158095
157972
|
clearInterval(poll);
|
|
158096
|
-
const
|
|
158097
|
-
|
|
157973
|
+
const r2 = { ...snapshot(), code, signal };
|
|
157974
|
+
if (detached)
|
|
157975
|
+
opts.onSettled?.(r2);
|
|
157976
|
+
else
|
|
157977
|
+
resolve8(r2);
|
|
158098
157978
|
};
|
|
158099
157979
|
child.on("error", (err) => {
|
|
158100
157980
|
head += `${err.message}
|
|
@@ -158105,7 +157985,7 @@ function runBang(cmd, opts) {
|
|
|
158105
157985
|
});
|
|
158106
157986
|
}
|
|
158107
157987
|
function bangTranscript(cmd, r2) {
|
|
158108
|
-
const status = r2.aborted ? "interrupted by the user" : r2.timedOut ? "timed out" : r2.code === 0 ? "exit code 0" : `exit code ${r2.code ?? r2.signal ?? "?"}`;
|
|
157988
|
+
const status = r2.detached ? "moved to the background by the user and still running \u2014 this is the output so far" : r2.aborted ? "interrupted by the user" : r2.timedOut ? "timed out" : r2.code === 0 ? "exit code 0" : `exit code ${r2.code ?? r2.signal ?? "?"}`;
|
|
158109
157989
|
const body = r2.output.length ? r2.output.replace(/\s+$/, "") : "(no output)";
|
|
158110
157990
|
const cut = r2.droppedBytes > 0 ? `
|
|
158111
157991
|
\u2026 ${r2.droppedBytes} bytes omitted from the middle \u2026` : "";
|
|
@@ -158727,6 +158607,10 @@ var App2 = ({ onReady }) => {
|
|
|
158727
158607
|
const onExpand = (0, import_react35.useCallback)((cb) => {
|
|
158728
158608
|
onExpandRef.current = cb;
|
|
158729
158609
|
}, []);
|
|
158610
|
+
const onBackgroundRef = (0, import_react35.useRef)(null);
|
|
158611
|
+
const onBackground = (0, import_react35.useCallback)((cb) => {
|
|
158612
|
+
onBackgroundRef.current = cb;
|
|
158613
|
+
}, []);
|
|
158730
158614
|
const mentionSelRef = (0, import_react35.useRef)({ query: "", idx: 0 });
|
|
158731
158615
|
const mentionDismissedRef = (0, import_react35.useRef)(null);
|
|
158732
158616
|
const mentionRootRef = (0, import_react35.useRef)(process.cwd());
|
|
@@ -158930,6 +158814,16 @@ var App2 = ({ onReady }) => {
|
|
|
158930
158814
|
onExpandRef.current?.();
|
|
158931
158815
|
return;
|
|
158932
158816
|
}
|
|
158817
|
+
if (key.ctrl && char === "b") {
|
|
158818
|
+
if (askResolverRef.current)
|
|
158819
|
+
return;
|
|
158820
|
+
if (busyRef.current) {
|
|
158821
|
+
const moved = onBackgroundRef.current?.() ?? false;
|
|
158822
|
+
if (!moved)
|
|
158823
|
+
setHintText("Ctrl+B moves a running !command to the background \u2014 none is running");
|
|
158824
|
+
}
|
|
158825
|
+
return;
|
|
158826
|
+
}
|
|
158933
158827
|
const menu = askResolverRef.current || searchRef.current || historyRef.current.browsing ? null : computeMenu(lineRef.current, cursorRef.current, slashEntriesRef.current, menuDismissedRef.current);
|
|
158934
158828
|
if (menu) {
|
|
158935
158829
|
const sel = menuSelRef.current.query === menu.query ? menuSelRef.current.idx : 0;
|
|
@@ -159301,7 +159195,8 @@ var App2 = ({ onReady }) => {
|
|
|
159301
159195
|
onCycleMode,
|
|
159302
159196
|
onRewind,
|
|
159303
159197
|
onHelp,
|
|
159304
|
-
onExpand
|
|
159198
|
+
onExpand,
|
|
159199
|
+
onBackground
|
|
159305
159200
|
});
|
|
159306
159201
|
}, []);
|
|
159307
159202
|
const slices = renderInputSlices(line, cursorPos);
|
|
@@ -159589,6 +159484,126 @@ function rewindChoices(cps) {
|
|
|
159589
159484
|
return { options, ids: newest.map((c) => c.id) };
|
|
159590
159485
|
}
|
|
159591
159486
|
|
|
159487
|
+
// src/ui/backgroundShells.ts
|
|
159488
|
+
var MAX_KEPT = 64e3;
|
|
159489
|
+
var BackgroundShells = class {
|
|
159490
|
+
nextId = 1;
|
|
159491
|
+
jobs = /* @__PURE__ */ new Map();
|
|
159492
|
+
ctls = /* @__PURE__ */ new Map();
|
|
159493
|
+
listeners = [];
|
|
159494
|
+
/** Register a detached command; returns its id. `seed` is the output it already produced. */
|
|
159495
|
+
add(cmd, ctl, seed = "", now = Date.now()) {
|
|
159496
|
+
const job = {
|
|
159497
|
+
id: this.nextId++,
|
|
159498
|
+
cmd,
|
|
159499
|
+
startedAt: now,
|
|
159500
|
+
status: "running",
|
|
159501
|
+
code: null,
|
|
159502
|
+
signal: null,
|
|
159503
|
+
tail: seed.length > MAX_KEPT ? seed.slice(seed.length - MAX_KEPT) : seed
|
|
159504
|
+
};
|
|
159505
|
+
this.jobs.set(job.id, job);
|
|
159506
|
+
this.ctls.set(job.id, ctl);
|
|
159507
|
+
this.emit();
|
|
159508
|
+
return job;
|
|
159509
|
+
}
|
|
159510
|
+
/** Append streamed output (bounded to the newest MAX_KEPT characters). */
|
|
159511
|
+
append(id, chunk) {
|
|
159512
|
+
const j = this.jobs.get(id);
|
|
159513
|
+
if (!j || j.status !== "running")
|
|
159514
|
+
return;
|
|
159515
|
+
j.tail += chunk;
|
|
159516
|
+
if (j.tail.length > MAX_KEPT)
|
|
159517
|
+
j.tail = j.tail.slice(j.tail.length - MAX_KEPT);
|
|
159518
|
+
}
|
|
159519
|
+
/** The process exited. A job the user killed stays 'killed' whatever the exit code says. */
|
|
159520
|
+
finish(id, r2, now = Date.now()) {
|
|
159521
|
+
const j = this.jobs.get(id);
|
|
159522
|
+
if (!j || j.status !== "running")
|
|
159523
|
+
return j;
|
|
159524
|
+
j.status = "done";
|
|
159525
|
+
j.code = r2.code;
|
|
159526
|
+
j.signal = r2.signal;
|
|
159527
|
+
j.endedAt = now;
|
|
159528
|
+
this.ctls.delete(id);
|
|
159529
|
+
this.emit();
|
|
159530
|
+
return j;
|
|
159531
|
+
}
|
|
159532
|
+
/** Stop a running job. False when the id is unknown or it already ended. */
|
|
159533
|
+
kill(id, now = Date.now()) {
|
|
159534
|
+
const j = this.jobs.get(id);
|
|
159535
|
+
const ctl = this.ctls.get(id);
|
|
159536
|
+
if (!j || !ctl || j.status !== "running")
|
|
159537
|
+
return false;
|
|
159538
|
+
j.status = "killed";
|
|
159539
|
+
j.endedAt = now;
|
|
159540
|
+
this.ctls.delete(id);
|
|
159541
|
+
ctl.kill();
|
|
159542
|
+
this.emit();
|
|
159543
|
+
return true;
|
|
159544
|
+
}
|
|
159545
|
+
killAll() {
|
|
159546
|
+
let n = 0;
|
|
159547
|
+
for (const id of [...this.ctls.keys()])
|
|
159548
|
+
if (this.kill(id))
|
|
159549
|
+
n++;
|
|
159550
|
+
return n;
|
|
159551
|
+
}
|
|
159552
|
+
get(id) {
|
|
159553
|
+
return this.jobs.get(id);
|
|
159554
|
+
}
|
|
159555
|
+
list() {
|
|
159556
|
+
return [...this.jobs.values()];
|
|
159557
|
+
}
|
|
159558
|
+
runningCount() {
|
|
159559
|
+
let n = 0;
|
|
159560
|
+
for (const j of this.jobs.values())
|
|
159561
|
+
if (j.status === "running")
|
|
159562
|
+
n++;
|
|
159563
|
+
return n;
|
|
159564
|
+
}
|
|
159565
|
+
onChange(cb) {
|
|
159566
|
+
this.listeners.push(cb);
|
|
159567
|
+
}
|
|
159568
|
+
emit() {
|
|
159569
|
+
for (const l2 of this.listeners)
|
|
159570
|
+
l2();
|
|
159571
|
+
}
|
|
159572
|
+
};
|
|
159573
|
+
function formatElapsed(ms) {
|
|
159574
|
+
const s2 = Math.max(0, Math.floor(ms / 1e3));
|
|
159575
|
+
if (s2 < 60)
|
|
159576
|
+
return `${s2}s`;
|
|
159577
|
+
const m2 = Math.floor(s2 / 60);
|
|
159578
|
+
if (m2 < 60)
|
|
159579
|
+
return `${m2}m ${String(s2 % 60).padStart(2, "0")}s`;
|
|
159580
|
+
return `${Math.floor(m2 / 60)}h ${String(m2 % 60).padStart(2, "0")}m`;
|
|
159581
|
+
}
|
|
159582
|
+
function describeShell(j, now = Date.now()) {
|
|
159583
|
+
const state = j.status === "running" ? `running ${formatElapsed(now - j.startedAt)}` : j.status === "killed" ? "killed" : j.code === 0 ? "exit 0" : `exit ${j.code ?? j.signal ?? "?"}`;
|
|
159584
|
+
const cmd = j.cmd.length > 60 ? j.cmd.slice(0, 59) + "\u2026" : j.cmd;
|
|
159585
|
+
return `#${j.id} ${state.padEnd(14)} ${cmd}`;
|
|
159586
|
+
}
|
|
159587
|
+
function parseBashesArgs(arg) {
|
|
159588
|
+
const parts = arg.trim().split(/\s+/).filter(Boolean);
|
|
159589
|
+
if (parts.length === 0)
|
|
159590
|
+
return { kind: "list" };
|
|
159591
|
+
const verb = parts[0].toLowerCase();
|
|
159592
|
+
if (verb === "list")
|
|
159593
|
+
return { kind: "list" };
|
|
159594
|
+
if (verb === "kill" && parts[1]?.toLowerCase() === "all" && parts.length === 2)
|
|
159595
|
+
return { kind: "kill-all" };
|
|
159596
|
+
if (verb === "show" || verb === "kill") {
|
|
159597
|
+
if (parts.length !== 2 || !/^#?\d+$/.test(parts[1]))
|
|
159598
|
+
return { kind: "error", message: `Usage: /bashes ${verb} <id>` };
|
|
159599
|
+
const id = Number(parts[1].replace("#", ""));
|
|
159600
|
+
return verb === "show" ? { kind: "show", id } : { kind: "kill", id };
|
|
159601
|
+
}
|
|
159602
|
+
if (parts.length === 1 && /^#?\d+$/.test(parts[0]))
|
|
159603
|
+
return { kind: "show", id: Number(parts[0].replace("#", "")) };
|
|
159604
|
+
return { kind: "error", message: "Usage: /bashes [show <id> | kill <id> | kill all]" };
|
|
159605
|
+
}
|
|
159606
|
+
|
|
159592
159607
|
// src/ui/toolOutputStore.ts
|
|
159593
159608
|
var MAX_ENTRIES = 20;
|
|
159594
159609
|
var MAX_ENTRY_CHARS = 4e5;
|
|
@@ -159705,6 +159720,11 @@ var CLI_VERSION = (() => {
|
|
|
159705
159720
|
})();
|
|
159706
159721
|
var sessionUsage = new UsageTotals();
|
|
159707
159722
|
var toolOutputs = new ToolOutputStore();
|
|
159723
|
+
var bgShells = new BackgroundShells();
|
|
159724
|
+
var bangDetach = null;
|
|
159725
|
+
process.on("exit", () => {
|
|
159726
|
+
bgShells.killAll();
|
|
159727
|
+
});
|
|
159708
159728
|
function printExpansion(arg) {
|
|
159709
159729
|
const r2 = resolveExpand(toolOutputs, arg);
|
|
159710
159730
|
if (!r2.entry) {
|
|
@@ -160695,6 +160715,7 @@ function printHelp() {
|
|
|
160695
160715
|
["/team [use <id|personal>]", "List your teams, or switch what usage is billed to"],
|
|
160696
160716
|
["/cost", "Tokens and cost of this conversation so far"],
|
|
160697
160717
|
["/status", "Version, model, mode, directory, session and cost at a glance"],
|
|
160718
|
+
["/bashes [show|kill <id>]", "List commands you moved to the background with Ctrl+B; show their output or stop them"],
|
|
160698
160719
|
["/expand [n]", "Show the full output of a folded tool call (newest by default; Ctrl+O does the same)"],
|
|
160699
160720
|
["/copy", "Copy the last reply to the clipboard"],
|
|
160700
160721
|
["/export [file]", "Write this conversation to a Markdown file"],
|
|
@@ -160740,6 +160761,7 @@ function printHelp() {
|
|
|
160740
160761
|
["Esc Esc", "At an empty prompt: pick a checkpoint to rewind to (same as /rewind)"],
|
|
160741
160762
|
["?", "At an empty prompt: show this list"],
|
|
160742
160763
|
["Ctrl+O", "Expand the last folded tool output (also works while it is running; /expand [n] picks an older one)"],
|
|
160764
|
+
["Ctrl+B", "While a `!command` runs: move it to the background and keep typing (/bashes lists, shows and stops them)"],
|
|
160743
160765
|
["Ctrl+L", "Redraw the screen"],
|
|
160744
160766
|
["Ctrl+D", "Delete the character under the cursor; press twice on an empty line to exit"],
|
|
160745
160767
|
["(paste)", "Multi-line paste goes into the input box \u2014 it does not send until you press Enter"]
|
|
@@ -161185,6 +161207,13 @@ ${text}` : "");
|
|
|
161185
161207
|
getInkTerminal().onExpand(() => {
|
|
161186
161208
|
printExpansion("");
|
|
161187
161209
|
});
|
|
161210
|
+
getInkTerminal().onBackground(() => {
|
|
161211
|
+
if (bangDetach && !bangDetach.requested) {
|
|
161212
|
+
bangDetach.requested = true;
|
|
161213
|
+
return true;
|
|
161214
|
+
}
|
|
161215
|
+
return false;
|
|
161216
|
+
});
|
|
161188
161217
|
getInkTerminal().onRewind(() => {
|
|
161189
161218
|
void (async () => {
|
|
161190
161219
|
const opts = rewindChoices(checkpoints.list());
|
|
@@ -161724,6 +161753,39 @@ ${dirList}`;
|
|
|
161724
161753
|
printExpansion(arg);
|
|
161725
161754
|
rl.prompt();
|
|
161726
161755
|
return;
|
|
161756
|
+
case "/bashes": {
|
|
161757
|
+
const a = parseBashesArgs(arg);
|
|
161758
|
+
if (a.kind === "error") {
|
|
161759
|
+
console.log(source_default.yellow(" " + a.message));
|
|
161760
|
+
} else if (a.kind === "list") {
|
|
161761
|
+
const jobs = bgShells.list();
|
|
161762
|
+
if (!jobs.length)
|
|
161763
|
+
console.log(source_default.dim(" No background commands. Start `!cmd`, then press Ctrl+B to move it to the background."));
|
|
161764
|
+
else
|
|
161765
|
+
for (const j of jobs)
|
|
161766
|
+
console.log(" " + (j.status === "running" ? source_default.cyan(describeShell(j)) : source_default.dim(describeShell(j))));
|
|
161767
|
+
} else if (a.kind === "show") {
|
|
161768
|
+
const j = bgShells.get(a.id);
|
|
161769
|
+
if (!j)
|
|
161770
|
+
console.log(source_default.yellow(` No background command #${a.id}.`));
|
|
161771
|
+
else {
|
|
161772
|
+
console.log(source_default.cyan(" " + describeShell(j)));
|
|
161773
|
+
const lines = j.tail.replace(/\s+$/, "").split("\n");
|
|
161774
|
+
const shownLines = lines.slice(-40);
|
|
161775
|
+
if (lines.length > shownLines.length)
|
|
161776
|
+
console.log(source_default.dim(` \u2026 ${lines.length - shownLines.length} earlier line(s) not shown`));
|
|
161777
|
+
for (const l2 of shownLines)
|
|
161778
|
+
console.log(source_default.dim(" \u2502 ") + l2.split("\r").pop().slice(0, 300));
|
|
161779
|
+
}
|
|
161780
|
+
} else if (a.kind === "kill") {
|
|
161781
|
+
console.log(bgShells.kill(a.id) ? source_default.green(` Stopped #${a.id}.`) : source_default.yellow(` #${a.id} is not running.`));
|
|
161782
|
+
} else {
|
|
161783
|
+
const n = bgShells.killAll();
|
|
161784
|
+
console.log(n ? source_default.green(` Stopped ${n} background command${n === 1 ? "" : "s"}.`) : source_default.dim(" Nothing is running."));
|
|
161785
|
+
}
|
|
161786
|
+
rl.prompt();
|
|
161787
|
+
return;
|
|
161788
|
+
}
|
|
161727
161789
|
case "/copy": {
|
|
161728
161790
|
const text = lastAssistantText(messages);
|
|
161729
161791
|
if (!text) {
|
|
@@ -162198,22 +162260,59 @@ ${text}
|
|
|
162198
162260
|
shown++;
|
|
162199
162261
|
};
|
|
162200
162262
|
let result;
|
|
162263
|
+
let ctl = null;
|
|
162264
|
+
let bgId = null;
|
|
162265
|
+
const detach = { requested: false };
|
|
162266
|
+
bangDetach = detach;
|
|
162201
162267
|
try {
|
|
162202
162268
|
result = await runBang(bang, {
|
|
162203
162269
|
cwd: workDir,
|
|
162204
162270
|
signal: abortSignal,
|
|
162271
|
+
detach,
|
|
162272
|
+
onSpawn: (c) => {
|
|
162273
|
+
ctl = c;
|
|
162274
|
+
},
|
|
162205
162275
|
onData: (chunk) => {
|
|
162276
|
+
if (bgId !== null) {
|
|
162277
|
+
bgShells.append(bgId, chunk);
|
|
162278
|
+
return;
|
|
162279
|
+
}
|
|
162206
162280
|
const parts = (partial + chunk).split("\n");
|
|
162207
162281
|
partial = parts.pop();
|
|
162208
162282
|
for (const l2 of parts)
|
|
162209
162283
|
showLine(l2);
|
|
162284
|
+
},
|
|
162285
|
+
onSettled: (final) => {
|
|
162286
|
+
if (bgId === null)
|
|
162287
|
+
return;
|
|
162288
|
+
const job = bgShells.finish(bgId, final);
|
|
162289
|
+
if (!job || job.status === "killed")
|
|
162290
|
+
return;
|
|
162291
|
+
const ok = final.code === 0;
|
|
162292
|
+
console.log((ok ? source_default.green : source_default.red)(` \u2699 background #${job.id} finished (exit ${final.code ?? final.signal}): ${job.cmd.length > 50 ? job.cmd.slice(0, 49) + "\u2026" : job.cmd}`) + source_default.dim(" /bashes show " + job.id));
|
|
162293
|
+
toolOutputs.add({ name: "bash", label: `!${job.cmd} (background #${job.id})`, output: final.output, isError: !ok, durationMs: 0 });
|
|
162210
162294
|
}
|
|
162211
162295
|
});
|
|
162212
162296
|
if (partial)
|
|
162213
162297
|
showLine(partial);
|
|
162214
162298
|
} finally {
|
|
162299
|
+
bangDetach = null;
|
|
162215
162300
|
agentRunning = false;
|
|
162216
162301
|
}
|
|
162302
|
+
if (result.detached && ctl) {
|
|
162303
|
+
const job = bgShells.add(bang, ctl, result.output);
|
|
162304
|
+
bgId = job.id;
|
|
162305
|
+
console.log(source_default.cyan(` \u2699 Moved to background as #${job.id}`) + source_default.dim(" (/bashes to list \xB7 /bashes show " + job.id + " \xB7 /bashes kill " + job.id + ")"));
|
|
162306
|
+
messages.push(
|
|
162307
|
+
{ role: "user", content: [{ type: "text", text: bangTranscript(bang, result) }] },
|
|
162308
|
+
{ role: "assistant", content: [{ type: "text", text: "Noted the command output." }] }
|
|
162309
|
+
);
|
|
162310
|
+
updateTitle();
|
|
162311
|
+
saveSession(sessionId, sessionTitle || "Untitled", workDir, messages);
|
|
162312
|
+
rl.resume();
|
|
162313
|
+
rl.prompt();
|
|
162314
|
+
return;
|
|
162315
|
+
}
|
|
162217
162316
|
const tag = result.aborted ? source_default.yellow(" interrupted") : result.timedOut ? source_default.yellow(" timed out") : result.code === 0 ? source_default.dim(" exit 0") : source_default.red(` exit ${result.code ?? result.signal}`);
|
|
162218
162317
|
console.log(tag);
|
|
162219
162318
|
if (result.output) {
|