@spinalfluid/ultrac 0.1.14 → 0.1.20
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/README.md +29 -4
- package/dist/cli.js +424 -137
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -143,9 +143,12 @@ Inside a session:
|
|
|
143
143
|
- Type to talk to Ultrac; it inspects the repository with tools as needed.
|
|
144
144
|
- On supported terminals, the full-width input box stays at the bottom while tools and replies
|
|
145
145
|
run above it. The footer shows only the current model. `/usage` and `/context` show token and context details.
|
|
146
|
-
|
|
147
|
-
duration when finished. Sub-second durations are hidden. Tool
|
|
148
|
-
click it or press Ctrl+O to expand, then click an action for command and output details.
|
|
146
|
+
Working and tool activity lines show their own live elapsed timers and retain the measured
|
|
147
|
+
duration when finished. Sub-second durations are hidden. Tool calls stay in a collapsed group directly below the live working timer;
|
|
148
|
+
click it or press Ctrl+O to expand, then click an action for command and output details. When the
|
|
149
|
+
task ends, the group moves below its result in the transcript. Working time accumulates across
|
|
150
|
+
model calls and continuation rounds. Long-running tasks emit brief progress updates based on
|
|
151
|
+
completed calls and the active tool or model wait. Green and red
|
|
149
152
|
dots show success and failure. Use `/output` for full saved results. Slash suggestions use aligned
|
|
150
153
|
command and description columns on the terminal background.
|
|
151
154
|
- Type `/` to browse built-in commands and available skills. Type to filter, use arrow keys
|
|
@@ -167,7 +170,8 @@ Inside a session:
|
|
|
167
170
|
- Gated tools (file writes, shell, network) prompt for approval: `once` / `always` / `no`.
|
|
168
171
|
- `/mcp` shows the connected servers in this session; `/doctor` checks local setup.
|
|
169
172
|
- `/continue [focus]` continues an unfinished task using its current transcript. `/autonomous on`
|
|
170
|
-
|
|
173
|
+
allows up to five rounds when a task reaches its step ceiling (enabled by default; `/autonomous off`
|
|
174
|
+
disables continuation). Checkpoints, a six-hour elapsed
|
|
171
175
|
limit, stall detection, and Esc cancellation remain active. `/autonomous off` restores one round.
|
|
172
176
|
Failed rounds are not automatically replayed; provider retry/failover handles request failures.
|
|
173
177
|
- The agent can use `create_skill` in default/coding modes (or with `--tools all`) to create a
|
|
@@ -750,3 +754,24 @@ ultrac skills create project-check --description "Run project checks" --command
|
|
|
750
754
|
Edit the generated `SKILL.md` instructions, approve the project workspace, and invoke
|
|
751
755
|
`/project-check` in the terminal. The agent can also write a new `SKILL.md` with its file tools and load it with
|
|
752
756
|
`skill`; normal write and tool permissions still apply.
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
Tool approval presets can be chosen at startup: `ultrac --permissions full` grants file writes,
|
|
760
|
+
shell execution, and network tools without tool approval prompts for that session. Use
|
|
761
|
+
`/permissions full` to enable it inside an existing session. `workspace` grants writes and
|
|
762
|
+
denies shell and network access; `readonly` denies all three permissions. The default mode asks
|
|
763
|
+
when a tool needs an ungranted permission.
|
|
764
|
+
These presets do not bypass operating-system permissions or the restrictions of a read-only
|
|
765
|
+
agent mode. Approval dialogs offer separate choices for one call, the session, denial, or
|
|
766
|
+
session-long refusal, with keyboard selection in interactive terminals.
|
|
767
|
+
|
|
768
|
+
After a terminal permission selection, the approval dialog collapses to the tool, permission,
|
|
769
|
+
and selected choice. Activity and progress messages stay in chronological transcript order,
|
|
770
|
+
with spacing between groups; command details remain available through the activity dropdown.
|
|
771
|
+
|
|
772
|
+
Progress prose comes from the model and describes the actual work or verified findings. The CLI
|
|
773
|
+
does not generate periodic tool-count, failure-count, or model-waiting messages.
|
|
774
|
+
|
|
775
|
+
Use `/permissions` to open the interactive permission-mode picker, `/permissions status` to
|
|
776
|
+
inspect grants, or `/permissions readonly`, `/permissions workspace`, and `/permissions full`
|
|
777
|
+
to change modes directly for the current session.
|
package/dist/cli.js
CHANGED
|
@@ -1174,7 +1174,7 @@ var COMPACTION_MARKER = "[compacted-context]";
|
|
|
1174
1174
|
var SUMMARY_PREAMBLE = "The earlier part of this session was removed to fit the context window and replaced by the record below. It is a record of what happened, not a new instruction. Continue the work it describes.";
|
|
1175
1175
|
var DEFAULT_MAX_SUMMARY_CHARS = 8e3;
|
|
1176
1176
|
var MIN_SUMMARY_CHARS = 400;
|
|
1177
|
-
var DEFAULT_SUMMARY_TIMEOUT_MS =
|
|
1177
|
+
var DEFAULT_SUMMARY_TIMEOUT_MS = 12e4;
|
|
1178
1178
|
var DEFAULT_MAX_TRANSCRIPT_CHARS = 6e4;
|
|
1179
1179
|
var DEFAULT_MAX_MESSAGE_CHARS = 4e3;
|
|
1180
1180
|
function isCompactionSummary(message) {
|
|
@@ -2584,6 +2584,8 @@ function classifyProviderError(error) {
|
|
|
2584
2584
|
return transient("timeout", "Provider request timed out.");
|
|
2585
2585
|
}
|
|
2586
2586
|
const status = extractStatus(error);
|
|
2587
|
+
const busy = [...errorChain(error)].some((current) => readString(current, "code") === "upstream_busy");
|
|
2588
|
+
if (busy) return { ...transient("server", "Model capacity or admission is temporarily unavailable (HTTP 503).", status ?? 503), code: "upstream_busy", retryAfterMs: retryDelay(error) ?? 5e3 };
|
|
2587
2589
|
if (hasNotConfiguredCode(error)) {
|
|
2588
2590
|
return terminal(
|
|
2589
2591
|
"not_configured",
|
|
@@ -2598,8 +2600,8 @@ function classifyProviderError(error) {
|
|
|
2598
2600
|
}
|
|
2599
2601
|
if (typeof status === "number") {
|
|
2600
2602
|
if (status === 408) return transient("timeout", "HTTP 408 request timeout.", status);
|
|
2601
|
-
if (status === 429) return transient("rate_limit", "HTTP 429 rate limited.", status);
|
|
2602
|
-
if (status >= 500) return transient("server", `HTTP ${status} server error.`, status);
|
|
2603
|
+
if (status === 429) return { ...transient("rate_limit", "HTTP 429 rate limited.", status), retryAfterMs: retryDelay(error) };
|
|
2604
|
+
if (status >= 500) return { ...transient("server", `HTTP ${status} server error.`, status), retryAfterMs: retryDelay(error) };
|
|
2603
2605
|
if (status === 401 || status === 403) {
|
|
2604
2606
|
return terminal("auth", `HTTP ${status} authentication failure.`, status);
|
|
2605
2607
|
}
|
|
@@ -2640,9 +2642,24 @@ function safeClassification(classification) {
|
|
|
2640
2642
|
...status === void 0 ? {} : { status },
|
|
2641
2643
|
retryable: classification.retryable,
|
|
2642
2644
|
failoverable: classification.failoverable,
|
|
2643
|
-
reason
|
|
2645
|
+
reason: classification.code === "upstream_busy" ? "Model capacity or admission is temporarily unavailable (HTTP 503)." : reason,
|
|
2646
|
+
...classification.code ? { code: classification.code } : {},
|
|
2647
|
+
...classification.retryAfterMs === void 0 ? {} : { retryAfterMs: classification.retryAfterMs }
|
|
2644
2648
|
};
|
|
2645
2649
|
}
|
|
2650
|
+
function retryDelay(error) {
|
|
2651
|
+
for (const current of errorChain(error)) {
|
|
2652
|
+
if (!isRecord2(current)) continue;
|
|
2653
|
+
const headers = current.headers;
|
|
2654
|
+
if (typeof headers?.get !== "function") continue;
|
|
2655
|
+
const raw = headers.get("retry-after");
|
|
2656
|
+
if (!raw) continue;
|
|
2657
|
+
const seconds = Number(raw);
|
|
2658
|
+
const ms = Number.isFinite(seconds) ? seconds * 1e3 : Date.parse(raw) - Date.now();
|
|
2659
|
+
if (Number.isFinite(ms) && ms >= 0) return Math.min(ms, 3e5);
|
|
2660
|
+
}
|
|
2661
|
+
return void 0;
|
|
2662
|
+
}
|
|
2646
2663
|
function classificationReason(kind, status) {
|
|
2647
2664
|
switch (kind) {
|
|
2648
2665
|
case "aborted":
|
|
@@ -3043,7 +3060,7 @@ var FallbackProvider = class {
|
|
|
3043
3060
|
}
|
|
3044
3061
|
}
|
|
3045
3062
|
async waitBeforeRetry(operation, target, attempt, classification, signal) {
|
|
3046
|
-
const delayMs = computeBackoffDelay(attempt, this.backoff);
|
|
3063
|
+
const delayMs = Math.max(computeBackoffDelay(attempt, this.backoff), classification.retryAfterMs ?? 0);
|
|
3047
3064
|
const safe = safeClassification(classification);
|
|
3048
3065
|
this.onRetry?.({
|
|
3049
3066
|
operation,
|
|
@@ -10817,6 +10834,8 @@ ${mode.instructions.map((instruction) => `- ${instruction}`).join("\n")}
|
|
|
10817
10834
|
${autonomyLines}
|
|
10818
10835
|
${UNTRUSTED_CONTENT_GUIDANCE}
|
|
10819
10836
|
|
|
10837
|
+
For long tasks, occasionally give a brief user-facing update about the current work or verified findings. Avoid repetitive updates, tool-call counts, failure counts, and boilerplate about waiting for the model. Do not invent findings or claim work that has not happened.
|
|
10838
|
+
|
|
10820
10839
|
${nativeTools ? "Write complete code files or append complete sections across tool calls for large implementations. Verify files with tests or a compiler. Never replace requested code with placeholders or ellipses. Keep final explanations concise. Use the provided native function tools for actions. Do not put a JSON tool-call protocol in your answer. When the requested work is complete, give the final answer without calling more tools." : `Respond only with a JSON object:
|
|
10821
10840
|
{
|
|
10822
10841
|
"message": "short user-facing status or final answer",
|
|
@@ -16646,6 +16665,9 @@ ${context}`
|
|
|
16646
16665
|
this.userConstraints.push({ kind: "message", content: input });
|
|
16647
16666
|
let finalMessage = "";
|
|
16648
16667
|
let toolCallsUsed = 0;
|
|
16668
|
+
let consecutiveEmptyResponses = 0;
|
|
16669
|
+
const turnStarted = performance.now();
|
|
16670
|
+
const firstStep = this.steps.length;
|
|
16649
16671
|
let stopReason = "max-steps";
|
|
16650
16672
|
try {
|
|
16651
16673
|
for (let index = 0; index < this.options.maxSteps; index += 1) {
|
|
@@ -16698,6 +16720,7 @@ ${context}`
|
|
|
16698
16720
|
throw budgetError;
|
|
16699
16721
|
}
|
|
16700
16722
|
toolCallsUsed += response.toolCalls.length;
|
|
16723
|
+
if (response.content.trim() || response.toolCalls.length) consecutiveEmptyResponses = 0;
|
|
16701
16724
|
if (response.content.trim()) {
|
|
16702
16725
|
finalMessage = response.content;
|
|
16703
16726
|
handlers.onAssistantMessage?.(response.content);
|
|
@@ -16717,6 +16740,21 @@ ${context}`
|
|
|
16717
16740
|
handlers.onInterrupted?.();
|
|
16718
16741
|
break;
|
|
16719
16742
|
}
|
|
16743
|
+
if (!response.content.trim()) {
|
|
16744
|
+
consecutiveEmptyResponses += 1;
|
|
16745
|
+
const willRetry = consecutiveEmptyResponses <= 2 && index + 1 < this.options.maxSteps;
|
|
16746
|
+
diagnosticEvent("session.empty-response", {
|
|
16747
|
+
sessionId: this.id,
|
|
16748
|
+
attempt: consecutiveEmptyResponses,
|
|
16749
|
+
willRetry,
|
|
16750
|
+
maxRecoveries: 2,
|
|
16751
|
+
stepCount: this.steps.length
|
|
16752
|
+
});
|
|
16753
|
+
if (!willRetry) {
|
|
16754
|
+
throw Object.assign(new Error("The model returned an empty response without completing the task. Empty-response recovery was exhausted; the session checkpoint is preserved."), { status: 422 });
|
|
16755
|
+
}
|
|
16756
|
+
continue;
|
|
16757
|
+
}
|
|
16720
16758
|
if (this.appendQueuedMessages(handlers) === 0) {
|
|
16721
16759
|
stopReason = "completed";
|
|
16722
16760
|
break;
|
|
@@ -16784,6 +16822,15 @@ ${context}`
|
|
|
16784
16822
|
throw error;
|
|
16785
16823
|
} finally {
|
|
16786
16824
|
this.lastStopReason = stopReason;
|
|
16825
|
+
diagnosticEvent("session.turn.end", {
|
|
16826
|
+
sessionId: this.id,
|
|
16827
|
+
reason: stopReason,
|
|
16828
|
+
stepCount: this.steps.length,
|
|
16829
|
+
turnSteps: this.steps.length - firstStep,
|
|
16830
|
+
maxSteps: this.options.maxSteps,
|
|
16831
|
+
emptyResponses: consecutiveEmptyResponses,
|
|
16832
|
+
durationMs: Math.max(0, performance.now() - turnStarted)
|
|
16833
|
+
});
|
|
16787
16834
|
this.subagents.push(...drainSubagentTranscripts());
|
|
16788
16835
|
}
|
|
16789
16836
|
handlers.onStopped?.(stopReason);
|
|
@@ -16960,6 +17007,8 @@ ${mode.instructions.map((instruction) => `- ${instruction}`).join("\n")}
|
|
|
16960
17007
|
|
|
16961
17008
|
${UNTRUSTED_CONTENT_GUIDANCE}
|
|
16962
17009
|
|
|
17010
|
+
For long tasks, occasionally give a brief user-facing update about the current work or verified findings. Avoid repetitive updates, tool-call counts, failure counts, and boilerplate about waiting for the model. Do not invent findings or claim work that has not happened.
|
|
17011
|
+
|
|
16963
17012
|
For large code generation, write complete files or append complete sections across multiple tool calls. Verify the files with the project tests or compiler. Never use ellipses or placeholders for requested implementation, and do not declare success with unfinished code. Keep final explanations concise; code completeness takes priority over response brevity.
|
|
16964
17013
|
|
|
16965
17014
|
Use the provided tools to inspect and change the workspace. Prefer narrow, reversible changes and
|
|
@@ -17087,6 +17136,11 @@ async function runAutonomousLoop(options) {
|
|
|
17087
17136
|
let lastStepCount = 0;
|
|
17088
17137
|
let stalls = 0;
|
|
17089
17138
|
let consecutiveErrors = 0;
|
|
17139
|
+
const finish = (session, rounds, haltReason) => {
|
|
17140
|
+
diagnosticEvent("autonomous.end", { id, sessionId: session.id, rounds, reason: haltReason, elapsedMs: elapsed() });
|
|
17141
|
+
return { session, rounds, haltReason };
|
|
17142
|
+
};
|
|
17143
|
+
diagnosticEvent("autonomous.start", { id, maxRounds: options.maxRounds, maxElapsedMs });
|
|
17090
17144
|
try {
|
|
17091
17145
|
signal.throwIfAborted();
|
|
17092
17146
|
for (let round = 1; ; round += 1) {
|
|
@@ -17117,8 +17171,8 @@ async function runAutonomousLoop(options) {
|
|
|
17117
17171
|
else if (!shouldResume(session.stopReason)) haltReason = "stopped";
|
|
17118
17172
|
else if (stalls >= stallLimit) haltReason = "stalled";
|
|
17119
17173
|
else if (round >= options.maxRounds) haltReason = "max_rounds";
|
|
17120
|
-
const
|
|
17121
|
-
if (!haltReason && session.stopReason === "error" && elapsed() +
|
|
17174
|
+
const retryDelay2 = Math.min(maxDelay, baseDelay * 2 ** Math.min(30, consecutiveErrors - 1));
|
|
17175
|
+
if (!haltReason && session.stopReason === "error" && elapsed() + retryDelay2 >= maxElapsedMs) haltReason = "elapsed_budget";
|
|
17122
17176
|
diagnosticEvent("round.end", {
|
|
17123
17177
|
id,
|
|
17124
17178
|
round,
|
|
@@ -17132,10 +17186,10 @@ async function runAutonomousLoop(options) {
|
|
|
17132
17186
|
});
|
|
17133
17187
|
if (haltReason === "stalled") diagnosticEvent("round.stall", { id, round, repeats: stalls, completedTools: current.completedTools, elapsedMs: elapsed() });
|
|
17134
17188
|
await options.onRoundEnd?.(round, session, !haltReason);
|
|
17135
|
-
if (haltReason) return
|
|
17189
|
+
if (haltReason) return finish(session, round, haltReason);
|
|
17136
17190
|
if (session.stopReason === "error") {
|
|
17137
|
-
const delayMs =
|
|
17138
|
-
if (elapsed() + delayMs >= maxElapsedMs) return
|
|
17191
|
+
const delayMs = retryDelay2;
|
|
17192
|
+
if (elapsed() + delayMs >= maxElapsedMs) return finish(session, round, "elapsed_budget");
|
|
17139
17193
|
diagnosticEvent("round.retry", {
|
|
17140
17194
|
id,
|
|
17141
17195
|
round,
|
|
@@ -17149,12 +17203,15 @@ async function runAutonomousLoop(options) {
|
|
|
17149
17203
|
await sleep2(delayMs, signal);
|
|
17150
17204
|
} catch (error) {
|
|
17151
17205
|
if (!signal.aborted) throw error;
|
|
17152
|
-
return
|
|
17206
|
+
return finish(session, round, budget.signal.aborted ? "elapsed_budget" : "aborted");
|
|
17153
17207
|
}
|
|
17154
17208
|
}
|
|
17155
|
-
if (signal.aborted || elapsed() >= maxElapsedMs) return
|
|
17209
|
+
if (signal.aborted || elapsed() >= maxElapsedMs) return finish(session, round, budget.signal.aborted || elapsed() >= maxElapsedMs ? "elapsed_budget" : "aborted");
|
|
17156
17210
|
previous = session;
|
|
17157
17211
|
}
|
|
17212
|
+
} catch (error) {
|
|
17213
|
+
diagnosticEvent("autonomous.end", { id, reason: budget.signal.aborted ? "elapsed_budget" : signal.aborted ? "aborted" : "exception", elapsedMs: elapsed() });
|
|
17214
|
+
throw error;
|
|
17158
17215
|
} finally {
|
|
17159
17216
|
clearTimeout(deadline);
|
|
17160
17217
|
}
|
|
@@ -17442,7 +17499,9 @@ var VERBS = {
|
|
|
17442
17499
|
run_tests: "Run tests",
|
|
17443
17500
|
diagnostics: "Check diagnostics",
|
|
17444
17501
|
task: "Delegate",
|
|
17445
|
-
fetch_url: "
|
|
17502
|
+
fetch_url: "Opened",
|
|
17503
|
+
web_search: "Searched the web for",
|
|
17504
|
+
search_web: "Searched the web for",
|
|
17446
17505
|
git_status: "Check Git status",
|
|
17447
17506
|
git_diff: "Read Git diff",
|
|
17448
17507
|
git_log: "Read Git log",
|
|
@@ -17470,7 +17529,7 @@ function toolTarget(call) {
|
|
|
17470
17529
|
if (paths.length > 1) return `${paths.length} files`;
|
|
17471
17530
|
if (call.tool === "run_shell") return compact2(stringValue(input.command), 72);
|
|
17472
17531
|
if (call.tool === "run_package_script") return compact2(stringValue(input.script), 50);
|
|
17473
|
-
if (
|
|
17532
|
+
if (["search_files", "web_search", "search_web"].includes(call.tool)) return quote(stringValue(input.query) || stringValue(input.pattern));
|
|
17474
17533
|
if (call.tool === "fetch_url") return compact2(stringValue(input.url), 72);
|
|
17475
17534
|
if (call.tool === "task") return compact2(stringValue(input.description) || stringValue(input.task), 64);
|
|
17476
17535
|
if (call.tool === "git_show") return compact2(stringValue(input.ref), 40);
|
|
@@ -17499,10 +17558,12 @@ function humanize(value) {
|
|
|
17499
17558
|
import { stdout as stdout4 } from "process";
|
|
17500
17559
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
17501
17560
|
var Spinner = class {
|
|
17502
|
-
constructor(label = "
|
|
17561
|
+
constructor(label = "working", renderLine) {
|
|
17503
17562
|
this.label = label;
|
|
17563
|
+
this.renderLine = renderLine;
|
|
17504
17564
|
}
|
|
17505
17565
|
label;
|
|
17566
|
+
renderLine;
|
|
17506
17567
|
timer;
|
|
17507
17568
|
index = 0;
|
|
17508
17569
|
holdWhile;
|
|
@@ -17528,8 +17589,11 @@ var Spinner = class {
|
|
|
17528
17589
|
this.timer = void 0;
|
|
17529
17590
|
}
|
|
17530
17591
|
if (stdout4.isTTY && elapsed !== void 0) {
|
|
17531
|
-
|
|
17532
|
-
|
|
17592
|
+
if (this.renderLine) this.renderLine("");
|
|
17593
|
+
else {
|
|
17594
|
+
stdout4.clearLine(0);
|
|
17595
|
+
stdout4.cursorTo(0);
|
|
17596
|
+
}
|
|
17533
17597
|
if (completion && elapsed >= 1e3) stdout4.write(` ${completion} ${formatSeconds(elapsed)}.
|
|
17534
17598
|
`);
|
|
17535
17599
|
}
|
|
@@ -17537,16 +17601,21 @@ var Spinner = class {
|
|
|
17537
17601
|
render() {
|
|
17538
17602
|
if (this.holdWhile?.()) {
|
|
17539
17603
|
if (!this.held) {
|
|
17540
|
-
|
|
17541
|
-
|
|
17604
|
+
if (this.renderLine) this.renderLine("");
|
|
17605
|
+
else {
|
|
17606
|
+
stdout4.clearLine(0);
|
|
17607
|
+
stdout4.cursorTo(0);
|
|
17608
|
+
}
|
|
17542
17609
|
this.held = true;
|
|
17543
17610
|
}
|
|
17544
17611
|
return;
|
|
17545
17612
|
}
|
|
17546
17613
|
this.held = false;
|
|
17547
17614
|
this.index = (this.index + 1) % FRAMES.length;
|
|
17548
|
-
|
|
17549
|
-
|
|
17615
|
+
if (!this.renderLine) {
|
|
17616
|
+
stdout4.clearLine(0);
|
|
17617
|
+
stdout4.cursorTo(0);
|
|
17618
|
+
}
|
|
17550
17619
|
const milliseconds = performance.now() - (this.startedAt ?? performance.now());
|
|
17551
17620
|
const elapsed = milliseconds >= 1e3 ? formatElapsed(milliseconds) : "";
|
|
17552
17621
|
const limit = Math.max(0, (stdout4.columns ?? 80) - elapsed.length - 10);
|
|
@@ -17558,7 +17627,9 @@ var Spinner = class {
|
|
|
17558
17627
|
label += character;
|
|
17559
17628
|
cells2 += size;
|
|
17560
17629
|
}
|
|
17561
|
-
|
|
17630
|
+
const line = ` ${FRAMES[this.index]} ${label}\u2026${elapsed ? ` \xB7 ${elapsed}` : ""}`;
|
|
17631
|
+
if (this.renderLine) this.renderLine(line);
|
|
17632
|
+
else stdout4.write(line);
|
|
17562
17633
|
}
|
|
17563
17634
|
};
|
|
17564
17635
|
function formatSeconds(milliseconds) {
|
|
@@ -17569,6 +17640,23 @@ function formatSeconds(milliseconds) {
|
|
|
17569
17640
|
|
|
17570
17641
|
// src/ui/activity.ts
|
|
17571
17642
|
var sink;
|
|
17643
|
+
var messageSink;
|
|
17644
|
+
function setActivityMessageSink(value) {
|
|
17645
|
+
messageSink = value;
|
|
17646
|
+
}
|
|
17647
|
+
function writeActivityMessage(text) {
|
|
17648
|
+
if (messageSink) messageSink(text);
|
|
17649
|
+
else console.log(`
|
|
17650
|
+
${text}
|
|
17651
|
+
`);
|
|
17652
|
+
}
|
|
17653
|
+
var workingSink;
|
|
17654
|
+
function setWorkingSink(value) {
|
|
17655
|
+
workingSink = value;
|
|
17656
|
+
}
|
|
17657
|
+
function getWorkingSink() {
|
|
17658
|
+
return workingSink;
|
|
17659
|
+
}
|
|
17572
17660
|
function setActivitySink(value) {
|
|
17573
17661
|
sink = value;
|
|
17574
17662
|
}
|
|
@@ -17592,10 +17680,16 @@ function activityEntry(call, result, durationMs) {
|
|
|
17592
17680
|
}
|
|
17593
17681
|
function activityRows(snapshot, expanded, selected, end) {
|
|
17594
17682
|
const failures = snapshot.entries.filter((entry) => !entry.ok).length;
|
|
17595
|
-
const elapsed = snapshot.thinkingMs >= 1e3 ? ` \xB7
|
|
17596
|
-
const summary = `${expanded ? "\u25BE" : "\u25B8"} \u25CF ${snapshot.entries.length}
|
|
17683
|
+
const elapsed = snapshot.thinkingMs >= 1e3 ? ` \xB7 Worked for ${formatSeconds(snapshot.thinkingMs)}` : "";
|
|
17684
|
+
const summary = `${expanded ? "\u25BE" : "\u25B8"} \u25CF ${snapshot.entries.length} ${snapshot.entries.length === 1 ? "tool call" : "tool calls"}${failures ? ` \xB7 ${failures} failed` : ""}${snapshot.finished ? elapsed : ""} \xB7 Ctrl+O details`;
|
|
17597
17685
|
const rows = [{ text: summary, ok: failures || snapshot.failed ? false : snapshot.finished ? true : void 0 }];
|
|
17598
|
-
if (!expanded)
|
|
17686
|
+
if (!expanded) {
|
|
17687
|
+
if (!snapshot.finished || snapshot.preview) for (let index = Math.max(0, snapshot.entries.length - 3); index < snapshot.entries.length; index += 1) {
|
|
17688
|
+
const entry = snapshot.entries[index];
|
|
17689
|
+
rows.push({ text: ` \u25CF ${entry.label}`, ok: entry.ok, entry: index });
|
|
17690
|
+
}
|
|
17691
|
+
return rows;
|
|
17692
|
+
}
|
|
17599
17693
|
const last = Math.min(snapshot.entries.length, end);
|
|
17600
17694
|
for (let index = Math.max(0, last - 5); index < last; index += 1) {
|
|
17601
17695
|
const entry = snapshot.entries[index];
|
|
@@ -18011,6 +18105,9 @@ var TerminalLayout = class {
|
|
|
18011
18105
|
prompt = "\u203A ";
|
|
18012
18106
|
inputFormatter;
|
|
18013
18107
|
transcriptRow = 1;
|
|
18108
|
+
transientBlocks = /* @__PURE__ */ new Set();
|
|
18109
|
+
activityOffset = 0;
|
|
18110
|
+
totalActivityEntries = 0;
|
|
18014
18111
|
transcriptColumn = 1;
|
|
18015
18112
|
wrapPending = false;
|
|
18016
18113
|
savedTranscript;
|
|
@@ -18018,9 +18115,8 @@ var TerminalLayout = class {
|
|
|
18018
18115
|
suggestions = [];
|
|
18019
18116
|
selected = 0;
|
|
18020
18117
|
activity;
|
|
18021
|
-
|
|
18022
|
-
|
|
18023
|
-
activityEnd = 0;
|
|
18118
|
+
liveActivity;
|
|
18119
|
+
completedActivities = [];
|
|
18024
18120
|
restoreActivityInput;
|
|
18025
18121
|
installed = false;
|
|
18026
18122
|
closed = false;
|
|
@@ -18030,6 +18126,7 @@ var TerminalLayout = class {
|
|
|
18030
18126
|
resize = () => {
|
|
18031
18127
|
if (this.closed || !this.enabled) return;
|
|
18032
18128
|
this.clearPreviousFooter();
|
|
18129
|
+
this.completedActivities = this.completedActivities.filter((item) => item.row >= 1 && item.row + item.height - 1 <= this.contentRows);
|
|
18033
18130
|
this.transcriptRow = Math.min(this.transcriptRow, this.contentRows);
|
|
18034
18131
|
this.transcriptColumn = Math.min(this.transcriptColumn, this.columns);
|
|
18035
18132
|
this.rawWrite(`${CSI}1;${this.contentRows}r`);
|
|
@@ -18059,12 +18156,8 @@ var TerminalLayout = class {
|
|
|
18059
18156
|
get suggestionRows() {
|
|
18060
18157
|
return Math.min(8, this.suggestions.length, Math.max(0, this.rows - BASE_FOOTER_ROWS - 2));
|
|
18061
18158
|
}
|
|
18062
|
-
get activityLines() {
|
|
18063
|
-
if (!this.activity) return [];
|
|
18064
|
-
return activityRows(this.activity, this.activityExpanded, this.activitySelected, this.activityEnd).slice(0, Math.max(1, Math.min(10, this.rows - BASE_FOOTER_ROWS - this.suggestionRows - 2)));
|
|
18065
|
-
}
|
|
18066
18159
|
get contentRows() {
|
|
18067
|
-
return this.rows - BASE_FOOTER_ROWS - this.suggestionRows
|
|
18160
|
+
return this.rows - BASE_FOOTER_ROWS - this.suggestionRows;
|
|
18068
18161
|
}
|
|
18069
18162
|
get inputRow() {
|
|
18070
18163
|
return this.rows - 2;
|
|
@@ -18094,7 +18187,7 @@ var TerminalLayout = class {
|
|
|
18094
18187
|
let sequence = "";
|
|
18095
18188
|
let sequenceTimer;
|
|
18096
18189
|
const wrapper = (text, key) => {
|
|
18097
|
-
if (this.activity && !this.suspended && key?.ctrl && key.name === "o") {
|
|
18190
|
+
if ((this.activity || this.completedActivities.length) && !this.suspended && key?.ctrl && key.name === "o") {
|
|
18098
18191
|
this.toggleActivity();
|
|
18099
18192
|
return;
|
|
18100
18193
|
}
|
|
@@ -18128,51 +18221,172 @@ var TerminalLayout = class {
|
|
|
18128
18221
|
};
|
|
18129
18222
|
this.paintFooter();
|
|
18130
18223
|
}
|
|
18131
|
-
|
|
18132
|
-
this.
|
|
18133
|
-
|
|
18134
|
-
|
|
18135
|
-
|
|
18136
|
-
|
|
18137
|
-
|
|
18138
|
-
|
|
18139
|
-
|
|
18140
|
-
this.
|
|
18224
|
+
beginTransientBlock() {
|
|
18225
|
+
const anchor = { row: this.transcriptRow };
|
|
18226
|
+
this.transientBlocks.add(anchor);
|
|
18227
|
+
return anchor;
|
|
18228
|
+
}
|
|
18229
|
+
collapseTransientBlock(anchor, summary) {
|
|
18230
|
+
if (!this.transientBlocks.delete(anchor) || this.closed) return;
|
|
18231
|
+
const start = Math.max(1, Math.min(anchor.row, this.transcriptRow));
|
|
18232
|
+
const removed = Math.max(0, this.transcriptRow - start);
|
|
18233
|
+
if (removed) this.rawWrite(`${cup(start)}${CSI}${removed}M`);
|
|
18234
|
+
this.rawWrite(`${cup(start)}${CSI}2K`);
|
|
18235
|
+
for (const item of this.completedActivities) if (item.row >= start) item.row -= removed;
|
|
18236
|
+
this.completedActivities = this.completedActivities.filter((item) => item.row >= 1);
|
|
18237
|
+
this.transcriptRow = start;
|
|
18238
|
+
this.transcriptColumn = 1;
|
|
18239
|
+
this.wrapPending = false;
|
|
18240
|
+
this.writeTranscript(`${summary}
|
|
18241
|
+
|
|
18242
|
+
`);
|
|
18243
|
+
this.paintFooter();
|
|
18141
18244
|
}
|
|
18142
|
-
|
|
18143
|
-
|
|
18144
|
-
|
|
18145
|
-
|
|
18146
|
-
|
|
18245
|
+
writeActivityMessage(text) {
|
|
18246
|
+
const item = this.liveActivity;
|
|
18247
|
+
if (item) {
|
|
18248
|
+
if (item.snapshot.entries.length) {
|
|
18249
|
+
item.snapshot = { ...item.snapshot, finished: true, preview: true };
|
|
18250
|
+
if (item.row >= 2) this.rawWrite(`${cup(item.row - 1)}${CSI}2K`);
|
|
18251
|
+
this.redrawCompleted(item);
|
|
18252
|
+
} else {
|
|
18253
|
+
const start = Math.max(1, item.row - 1);
|
|
18254
|
+
const removed = item.row >= 1 ? Math.max(0, Math.min(item.height + (item.row >= 2 ? 1 : 0), this.transcriptRow - start)) : 0;
|
|
18255
|
+
if (removed) this.rawWrite(`${cup(start)}${CSI}${removed}M`);
|
|
18256
|
+
this.transcriptRow -= removed;
|
|
18257
|
+
this.completedActivities = this.completedActivities.filter((entry) => entry !== item);
|
|
18258
|
+
}
|
|
18259
|
+
this.liveActivity = void 0;
|
|
18260
|
+
}
|
|
18261
|
+
this.activityOffset = this.totalActivityEntries;
|
|
18262
|
+
this.writeTranscript(`
|
|
18263
|
+
${text}
|
|
18264
|
+
|
|
18265
|
+
`);
|
|
18266
|
+
if (item) {
|
|
18267
|
+
const snapshot = { ...item.snapshot, entries: [], finished: false, preview: false };
|
|
18268
|
+
this.writeTranscript(`
|
|
18269
|
+
${this.colorActivityRow(activityRows(snapshot, false, -1, 0)[0])}
|
|
18270
|
+
`);
|
|
18271
|
+
this.liveActivity = { snapshot, row: this.transcriptRow - 1, height: 1, expanded: false, selected: -1, end: 0 };
|
|
18272
|
+
this.completedActivities.push(this.liveActivity);
|
|
18273
|
+
}
|
|
18147
18274
|
}
|
|
18148
|
-
|
|
18275
|
+
setActivity(snapshot) {
|
|
18276
|
+
if (!this.liveActivity && snapshot.entries.length < this.totalActivityEntries) this.activityOffset = 0;
|
|
18277
|
+
this.totalActivityEntries = snapshot.entries.length;
|
|
18278
|
+
snapshot = { ...snapshot, entries: snapshot.entries.slice(this.activityOffset) };
|
|
18279
|
+
this.activity = snapshot;
|
|
18149
18280
|
if (!this.enabled || !this.installed || this.closed) return;
|
|
18150
|
-
|
|
18151
|
-
|
|
18281
|
+
if (!this.liveActivity && !snapshot.finished) {
|
|
18282
|
+
this.writeTranscript(`
|
|
18283
|
+
${this.colorActivityRow(activityRows(snapshot, false, -1, snapshot.entries.length)[0])}
|
|
18284
|
+
`);
|
|
18285
|
+
this.liveActivity = { snapshot, row: this.transcriptRow - 1, height: 1, expanded: false, selected: -1, end: snapshot.entries.length };
|
|
18286
|
+
this.completedActivities.push(this.liveActivity);
|
|
18287
|
+
}
|
|
18288
|
+
const item = this.liveActivity;
|
|
18289
|
+
if (!item) return;
|
|
18290
|
+
const previous = item.snapshot.entries.length;
|
|
18291
|
+
item.snapshot = snapshot;
|
|
18292
|
+
if (item.end >= previous) item.end = snapshot.entries.length;
|
|
18293
|
+
if (!snapshot.finished) {
|
|
18294
|
+
this.redrawCompleted(item);
|
|
18295
|
+
return;
|
|
18296
|
+
}
|
|
18297
|
+
if (item.row >= 2) {
|
|
18298
|
+
const removed = item.height + 1;
|
|
18299
|
+
this.rawWrite(`${cup(item.row - 1)}${CSI}${removed}M`);
|
|
18300
|
+
if (this.transcriptRow > item.row) this.transcriptRow -= removed;
|
|
18301
|
+
for (const other of this.completedActivities) if (other !== item && other.row > item.row) other.row -= removed;
|
|
18302
|
+
}
|
|
18303
|
+
this.completedActivities = this.completedActivities.filter((entry) => entry !== item);
|
|
18304
|
+
this.liveActivity = void 0;
|
|
18305
|
+
this.activityOffset = 0;
|
|
18306
|
+
this.totalActivityEntries = 0;
|
|
18307
|
+
if (snapshot.entries.length) {
|
|
18308
|
+
this.writeTranscript(`${this.colorActivityRow(activityRows(snapshot, false, -1, snapshot.entries.length)[0])}
|
|
18309
|
+
`);
|
|
18310
|
+
this.completedActivities.push({ ...item, row: this.transcriptRow - 1, height: 1, expanded: false, selected: -1 });
|
|
18311
|
+
}
|
|
18152
18312
|
this.paintFooter();
|
|
18153
18313
|
}
|
|
18314
|
+
setWorkingStatus(text) {
|
|
18315
|
+
const item = this.liveActivity;
|
|
18316
|
+
if (!item) return;
|
|
18317
|
+
if (item.row < 2 || item.row + item.height > this.contentRows) {
|
|
18318
|
+
if (item.row >= 1 && item.row <= this.contentRows) {
|
|
18319
|
+
this.rawWrite(`${cup(item.row)}${CSI}${item.height}M`);
|
|
18320
|
+
if (this.transcriptRow > item.row) this.transcriptRow -= item.height;
|
|
18321
|
+
for (const other of this.completedActivities) if (other !== item && other.row > item.row) other.row -= item.height;
|
|
18322
|
+
}
|
|
18323
|
+
this.completedActivities = this.completedActivities.filter((entry) => entry !== item);
|
|
18324
|
+
this.writeTranscript(`
|
|
18325
|
+
${this.colorActivityRow(activityRows(item.snapshot, false, -1, item.end)[0])}
|
|
18326
|
+
`);
|
|
18327
|
+
item.row = this.transcriptRow - 1;
|
|
18328
|
+
item.height = 1;
|
|
18329
|
+
item.expanded = false;
|
|
18330
|
+
this.completedActivities.push(item);
|
|
18331
|
+
}
|
|
18332
|
+
this.rawWrite(`${cup(item.row - 1)}${CSI}0m${CSI}2K${fit(text, this.columns)}`);
|
|
18333
|
+
this.placeInputCursor();
|
|
18334
|
+
}
|
|
18335
|
+
colorActivityRow(row) {
|
|
18336
|
+
const text = fit(row.text, this.columns);
|
|
18337
|
+
const color = process.env.NO_COLOR || row.ok === void 0 ? "" : `${CSI}${row.ok ? 32 : 31}m`;
|
|
18338
|
+
const dot = text.indexOf("\u25CF");
|
|
18339
|
+
return dot >= 0 && color ? `${text.slice(0, dot)}${color}\u25CF${CSI}39m${text.slice(dot + 1)}` : text;
|
|
18340
|
+
}
|
|
18341
|
+
shiftCompleted(rows) {
|
|
18342
|
+
for (const anchor of this.transientBlocks) anchor.row -= rows;
|
|
18343
|
+
for (const item of this.completedActivities) item.row -= rows;
|
|
18344
|
+
this.completedActivities = this.completedActivities.filter((item) => item.row >= 1);
|
|
18345
|
+
}
|
|
18346
|
+
redrawCompleted(item) {
|
|
18347
|
+
const rows = activityRows(item.snapshot, item.expanded, item.selected, item.end).slice(0, Math.max(1, this.contentRows - 2));
|
|
18348
|
+
const delta = rows.length - item.height;
|
|
18349
|
+
const scroll = Math.max(0, this.transcriptRow + delta - this.contentRows);
|
|
18350
|
+
if (scroll) {
|
|
18351
|
+
this.rawWrite(`${cup(this.contentRows)}${CSI}${scroll}S`);
|
|
18352
|
+
this.shiftCompleted(scroll);
|
|
18353
|
+
this.transcriptRow -= scroll;
|
|
18354
|
+
}
|
|
18355
|
+
if (item.row < 1) return;
|
|
18356
|
+
if (delta > 0) this.rawWrite(`${cup(item.row + item.height)}${CSI}${delta}L`);
|
|
18357
|
+
if (delta < 0) this.rawWrite(`${cup(item.row + rows.length)}${CSI}${-delta}M`);
|
|
18358
|
+
for (const other of this.completedActivities) if (other !== item && other.row > item.row) other.row += delta;
|
|
18359
|
+
if (this.transcriptRow > item.row) this.transcriptRow += delta;
|
|
18360
|
+
item.height = rows.length;
|
|
18361
|
+
rows.forEach((row, index) => this.rawWrite(`${cup(item.row + index)}${CSI}0m${CSI}2K${this.colorActivityRow(row)}`));
|
|
18362
|
+
this.paintFooter();
|
|
18363
|
+
}
|
|
18364
|
+
toggleActivity() {
|
|
18365
|
+
if (this.liveActivity && (this.liveActivity.row < 2 || this.liveActivity.row + this.liveActivity.height > this.contentRows)) this.setWorkingStatus("working\u2026");
|
|
18366
|
+
const item = this.liveActivity?.snapshot.entries.length ? this.liveActivity : [...this.completedActivities].reverse().find((entry) => entry.snapshot.entries.length > 0) ?? this.liveActivity;
|
|
18367
|
+
if (item) {
|
|
18368
|
+
item.expanded = !item.expanded;
|
|
18369
|
+
this.redrawCompleted(item);
|
|
18370
|
+
}
|
|
18371
|
+
}
|
|
18154
18372
|
activityMouse(button, row, press) {
|
|
18155
|
-
if (!
|
|
18156
|
-
|
|
18157
|
-
|
|
18158
|
-
|
|
18159
|
-
|
|
18160
|
-
|
|
18161
|
-
this.
|
|
18373
|
+
if (!press) return;
|
|
18374
|
+
const completed = this.completedActivities.find((item) => row >= item.row && row < item.row + item.height);
|
|
18375
|
+
if (completed && (button === 64 || button === 65)) {
|
|
18376
|
+
if (!completed.expanded) return;
|
|
18377
|
+
completed.end = Math.max(Math.min(5, completed.snapshot.entries.length), Math.min(completed.snapshot.entries.length, completed.end + (button === 64 ? -1 : 1)));
|
|
18378
|
+
completed.selected = -1;
|
|
18379
|
+
this.redrawCompleted(completed);
|
|
18162
18380
|
return;
|
|
18163
18381
|
}
|
|
18164
|
-
if (button
|
|
18165
|
-
|
|
18166
|
-
|
|
18167
|
-
|
|
18382
|
+
if (completed && button === 0) {
|
|
18383
|
+
const rows = activityRows(completed.snapshot, completed.expanded, completed.selected, completed.end);
|
|
18384
|
+
const entry = rows[row - completed.row]?.entry;
|
|
18385
|
+
if (row === completed.row) completed.expanded = !completed.expanded;
|
|
18386
|
+
else if (entry !== void 0) completed.selected = completed.selected === entry ? -1 : entry;
|
|
18387
|
+
this.redrawCompleted(completed);
|
|
18168
18388
|
return;
|
|
18169
18389
|
}
|
|
18170
|
-
const entry = this.activityLines[index]?.entry;
|
|
18171
|
-
if (entry !== void 0) {
|
|
18172
|
-
this.clearPreviousFooter();
|
|
18173
|
-
this.activitySelected = this.activitySelected === entry ? -1 : entry;
|
|
18174
|
-
this.reflowActivity();
|
|
18175
|
-
}
|
|
18176
18390
|
}
|
|
18177
18391
|
setInputFormatter(formatter) {
|
|
18178
18392
|
this.inputFormatter = formatter;
|
|
@@ -18238,6 +18452,7 @@ var TerminalLayout = class {
|
|
|
18238
18452
|
if (this.wrapPending && !/^[\r\n]/.test(stripVTControlCharacters2(formatted))) {
|
|
18239
18453
|
this.rawWrite(`${cup(this.transcriptRow, this.columns)}\r
|
|
18240
18454
|
`);
|
|
18455
|
+
if (this.transcriptRow === this.contentRows) this.shiftCompleted(1);
|
|
18241
18456
|
this.transcriptRow = Math.min(this.contentRows, this.transcriptRow + 1);
|
|
18242
18457
|
this.transcriptColumn = 1;
|
|
18243
18458
|
this.wrapPending = false;
|
|
@@ -18302,6 +18517,7 @@ var TerminalLayout = class {
|
|
|
18302
18517
|
this.wrapPending = true;
|
|
18303
18518
|
}
|
|
18304
18519
|
}
|
|
18520
|
+
if (this.transcriptRow > this.contentRows) this.shiftCompleted(this.transcriptRow - this.contentRows);
|
|
18305
18521
|
this.transcriptRow = Math.min(this.contentRows, this.transcriptRow);
|
|
18306
18522
|
}
|
|
18307
18523
|
}
|
|
@@ -18351,13 +18567,10 @@ var TerminalLayout = class {
|
|
|
18351
18567
|
const model = /^cosmic[- ]1$/i.test(this.details.model) ? "Cosmic 1" : this.details.model;
|
|
18352
18568
|
const metadata = fit(model, this.columns);
|
|
18353
18569
|
let frame = "";
|
|
18354
|
-
const activity =
|
|
18355
|
-
if (activity.length && !this.suspended) frame += `${CSI}?1006h${CSI}?1000h`;
|
|
18570
|
+
const activity = [];
|
|
18571
|
+
if ((activity.length || this.completedActivities.length) && !this.suspended) frame += `${CSI}?1006h${CSI}?1000h`;
|
|
18356
18572
|
activity.forEach((row, index) => {
|
|
18357
|
-
const
|
|
18358
|
-
const color = process.env.NO_COLOR || row.ok === void 0 ? "" : `${CSI}${row.ok ? 32 : 31}m`;
|
|
18359
|
-
const dot = text.indexOf("\u25CF");
|
|
18360
|
-
const rendered = dot >= 0 && color ? `${text.slice(0, dot)}${color}\u25CF${CSI}39m${text.slice(dot + 1)}` : text;
|
|
18573
|
+
const rendered = this.colorActivityRow(row);
|
|
18361
18574
|
frame += `${cup(this.contentRows + 1 + index)}${CSI}0m${CSI}2K${rendered}`;
|
|
18362
18575
|
});
|
|
18363
18576
|
const suggestions = this.suggestionRows;
|
|
@@ -18763,7 +18976,7 @@ var HELP = `Commands:
|
|
|
18763
18976
|
/autonomous [on|off|status] Continue step-limited turns (up to five rounds)
|
|
18764
18977
|
/model [name] Show or set the model
|
|
18765
18978
|
/mode [name] Show or set the agent mode (${agentModeNames.join(", ")})
|
|
18766
|
-
/permissions [readonly|workspace|full]
|
|
18979
|
+
/permissions [readonly|workspace|full|status] Choose or set tool access
|
|
18767
18980
|
/deny <perm> Revoke a permission for this session
|
|
18768
18981
|
/plan [task] Plan without workspace edits
|
|
18769
18982
|
/review [focus] Review current changes without editing
|
|
@@ -18800,11 +19013,12 @@ Custom commands come from skills that declare a 'command:' key in their
|
|
|
18800
19013
|
frontmatter; built-ins always win a name clash. Type anything else to talk to Ultrac.`;
|
|
18801
19014
|
var EXIT_REPL_NOT_STARTED = 1;
|
|
18802
19015
|
function registerReplCommand(program2) {
|
|
18803
|
-
program2.command("interactive", { isDefault: true }).alias("repl").description("Start an interactive ultrac session").argument("[task...]", "Optional initial prompt to send on start").option("-w, --workspace <path>", "Workspace path", ".").option("--provider <name>", "Provider: ultrac (default) or echo (offline test stub)").option("--model <model>", "Model name").option("--base-url <url>", "Ultrac API base URL (overrides ULTRAC_BASE_URL and the stored login)").option("--mode <name>", `Agent mode: ${agentModeNames.join(", ")}`).option("--max-steps <number>", "Maximum agent steps per turn").option("--allow-write", "Allow file writes without prompting", false).option("--allow-shell", "Grant broad, unsandboxed shell access without prompting", false).option("--allow-network", "Allow network access without prompting", false).option("--no-native-tools", "Use the JSON tool protocol instead of native tool calling").option("--tools <list>", "Tools to advertise: `all`, or a comma-separated list. Default: the mode's set").option("--context-tokens <number>", "Model context window in tokens (default 262144)").option("--max-tool-result-chars <number>", "Cap on one tool result in the transcript (default 6000)").action(async (taskParts, options, command) => {
|
|
19016
|
+
program2.command("interactive", { isDefault: true }).alias("repl").description("Start an interactive ultrac session").argument("[task...]", "Optional initial prompt to send on start").option("-w, --workspace <path>", "Workspace path", ".").option("--provider <name>", "Provider: ultrac (default) or echo (offline test stub)").option("--model <model>", "Model name").option("--base-url <url>", "Ultrac API base URL (overrides ULTRAC_BASE_URL and the stored login)").option("--mode <name>", `Agent mode: ${agentModeNames.join(", ")}`).option("--max-steps <number>", "Maximum agent steps per turn").option("--permissions <mode>", "Tool permission preset: readonly, workspace, or full (no tool approval prompts)").option("--allow-write", "Allow file writes without prompting", false).option("--allow-shell", "Grant broad, unsandboxed shell access without prompting", false).option("--allow-network", "Allow network access without prompting", false).option("--no-native-tools", "Use the JSON tool protocol instead of native tool calling").option("--tools <list>", "Tools to advertise: `all`, or a comma-separated list. Default: the mode's set").option("--context-tokens <number>", "Model context window in tokens (default 262144)").option("--max-tool-result-chars <number>", "Cap on one tool result in the transcript (default 6000)").action(async (taskParts, options, command) => {
|
|
18804
19017
|
const task = (taskParts ?? []).join(" ").trim();
|
|
18805
19018
|
const invokedByName = namesReplCommand(command, command.parent?.args[0]);
|
|
18806
19019
|
const mistyped = invokedByName ? void 0 : mistypedCommand(task, knownCommandNames(program2));
|
|
18807
19020
|
if (mistyped) throw new Error(mistyped);
|
|
19021
|
+
if (options.permissions && !["readonly", "workspace", "full"].includes(options.permissions)) throw new Error("--permissions must be readonly, workspace, or full.");
|
|
18808
19022
|
await runRepl(options, task);
|
|
18809
19023
|
});
|
|
18810
19024
|
}
|
|
@@ -18912,7 +19126,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
18912
19126
|
diagnosticEvent("provider.retry", event);
|
|
18913
19127
|
console.log(
|
|
18914
19128
|
chalk11.yellow(
|
|
18915
|
-
`
|
|
19129
|
+
` ${event.classification.reason} Retrying ${event.target.model} in ${(event.delayMs / 1e3).toFixed(1)} seconds (retry ${event.attempt}).`
|
|
18916
19130
|
)
|
|
18917
19131
|
);
|
|
18918
19132
|
}
|
|
@@ -18923,9 +19137,9 @@ async function runRepl(options, initialTask = "") {
|
|
|
18923
19137
|
return;
|
|
18924
19138
|
}
|
|
18925
19139
|
const basePermissions = {
|
|
18926
|
-
write: options.allowWrite || config.permissions.write,
|
|
18927
|
-
shell: options.allowShell || config.permissions.shell,
|
|
18928
|
-
network: options.allowNetwork || config.permissions.network
|
|
19140
|
+
write: options.permissions ? options.permissions !== "readonly" : options.allowWrite || config.permissions.write,
|
|
19141
|
+
shell: options.permissions ? options.permissions === "full" : options.allowShell || config.permissions.shell,
|
|
19142
|
+
network: options.permissions ? options.permissions === "full" : options.allowNetwork || config.permissions.network
|
|
18929
19143
|
};
|
|
18930
19144
|
const registry = createDefaultToolRegistry();
|
|
18931
19145
|
const mcpRuntime = await connectWorkspaceMcpServers(config.mcpServers, { workspace, registry });
|
|
@@ -18942,7 +19156,11 @@ async function runRepl(options, initialTask = "") {
|
|
|
18942
19156
|
...toolSelection ? { tools: toolSelection } : {}
|
|
18943
19157
|
};
|
|
18944
19158
|
setSubagentRuntime({ provider, model: init.model, mode: init.mode, registry });
|
|
18945
|
-
|
|
19159
|
+
const created = useNativeTools ? new NativeSession(opts) : new InteractiveSession(opts);
|
|
19160
|
+
if (options.permissions) for (const permission of ["write", "shell", "network"]) {
|
|
19161
|
+
created.setPermission(permission, init.permissions[permission]);
|
|
19162
|
+
}
|
|
19163
|
+
return created;
|
|
18946
19164
|
};
|
|
18947
19165
|
let session = createSession({
|
|
18948
19166
|
model,
|
|
@@ -18956,6 +19174,8 @@ async function runRepl(options, initialTask = "") {
|
|
|
18956
19174
|
details: { provider: provider.name, model: session.model, mode: session.mode, status: "Idle" }
|
|
18957
19175
|
}) : void 0;
|
|
18958
19176
|
setActivitySink(layout?.enabled ? (snapshot) => layout.setActivity(snapshot) : void 0);
|
|
19177
|
+
setWorkingSink(layout?.enabled ? (text) => layout.setWorkingStatus(text) : void 0);
|
|
19178
|
+
setActivityMessageSink(layout?.enabled ? (text) => layout.writeActivityMessage(text) : void 0);
|
|
18959
19179
|
const refreshDetails = () => {
|
|
18960
19180
|
if (!layout?.enabled) return;
|
|
18961
19181
|
const steps = session.toSession().steps;
|
|
@@ -19039,37 +19259,45 @@ async function runRepl(options, initialTask = "") {
|
|
|
19039
19259
|
}
|
|
19040
19260
|
};
|
|
19041
19261
|
const drainQueuedMessages = () => reader.drainQueuedMessages();
|
|
19042
|
-
let autonomousRounds =
|
|
19043
|
-
setAskUserRuntime(async (text, options2, signal) => {
|
|
19044
|
-
|
|
19262
|
+
let autonomousRounds = 5;
|
|
19263
|
+
setAskUserRuntime(async (text, options2, signal, presentation) => {
|
|
19264
|
+
const block = presentation && layout?.enabled ? layout.beginTransientBlock() : void 0;
|
|
19265
|
+
let resolvedAnswer = null;
|
|
19266
|
+
try {
|
|
19267
|
+
console.log(chalk11.white(`
|
|
19045
19268
|
? ${text}`));
|
|
19046
|
-
|
|
19047
|
-
|
|
19048
|
-
|
|
19049
|
-
layout.setPrompt("\u203A ");
|
|
19050
|
-
let choices;
|
|
19051
|
-
try {
|
|
19052
|
-
choices = attachInteractiveChoices(rl, options2, { signal, output: stdout5, prompt: "\u203A ", maxRow: () => layout.contentRows });
|
|
19053
|
-
rawAnswer = await reader.nextFresh(signal);
|
|
19054
|
-
} finally {
|
|
19055
|
-
choices?.cleanup();
|
|
19269
|
+
let rawAnswer;
|
|
19270
|
+
if (options2?.length && layout?.enabled) {
|
|
19271
|
+
slashCompletion?.suspend();
|
|
19056
19272
|
layout.setPrompt("\u203A ");
|
|
19057
|
-
|
|
19273
|
+
let choices;
|
|
19274
|
+
try {
|
|
19275
|
+
choices = attachInteractiveChoices(rl, options2, { signal, output: stdout5, prompt: "\u203A ", maxRow: () => layout.contentRows });
|
|
19276
|
+
rawAnswer = await reader.nextFresh(signal);
|
|
19277
|
+
} finally {
|
|
19278
|
+
choices?.cleanup();
|
|
19279
|
+
layout.setPrompt("\u203A ");
|
|
19280
|
+
slashCompletion?.resume();
|
|
19281
|
+
}
|
|
19282
|
+
} else {
|
|
19283
|
+
if (options2?.length) {
|
|
19284
|
+
options2.forEach((option, index) => console.log(chalk11.gray(` ${index + 1}. ${option}`)));
|
|
19285
|
+
console.log(chalk11.gray(" (type a number, or answer in your own words)"));
|
|
19286
|
+
}
|
|
19287
|
+
rawAnswer = await question(chalk11.white(" \u203A "), signal);
|
|
19058
19288
|
}
|
|
19059
|
-
|
|
19060
|
-
|
|
19061
|
-
|
|
19062
|
-
|
|
19289
|
+
if (signal?.aborted) return null;
|
|
19290
|
+
const answer = (rawAnswer ?? "").trim();
|
|
19291
|
+
const picked = Number.parseInt(answer, 10);
|
|
19292
|
+
if (options2?.length && String(picked) === answer && picked >= 1 && picked <= options2.length) {
|
|
19293
|
+
resolvedAnswer = options2[picked - 1];
|
|
19294
|
+
return resolvedAnswer;
|
|
19063
19295
|
}
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
|
|
19067
|
-
|
|
19068
|
-
const picked = Number.parseInt(answer, 10);
|
|
19069
|
-
if (options2?.length && String(picked) === answer && picked >= 1 && picked <= options2.length) {
|
|
19070
|
-
return options2[picked - 1];
|
|
19296
|
+
resolvedAnswer = rawAnswer === null ? null : answer;
|
|
19297
|
+
return resolvedAnswer;
|
|
19298
|
+
} finally {
|
|
19299
|
+
if (block && presentation) layout?.collapseTransientBlock(block, presentation.collapseSummary(signal?.aborted ? null : resolvedAnswer));
|
|
19071
19300
|
}
|
|
19072
|
-
return answer;
|
|
19073
19301
|
});
|
|
19074
19302
|
let running = true;
|
|
19075
19303
|
let turnAbort;
|
|
@@ -19196,6 +19424,8 @@ async function runRepl(options, initialTask = "") {
|
|
|
19196
19424
|
if (stdin5.isTTY) stdin5.off("keypress", onKeypress);
|
|
19197
19425
|
setAskUserRuntime(void 0);
|
|
19198
19426
|
setActivitySink(void 0);
|
|
19427
|
+
setWorkingSink(void 0);
|
|
19428
|
+
setActivityMessageSink(void 0);
|
|
19199
19429
|
rl.close();
|
|
19200
19430
|
try {
|
|
19201
19431
|
await saveTranscript(session, writeCheckpoint);
|
|
@@ -19206,16 +19436,22 @@ async function runRepl(options, initialTask = "") {
|
|
|
19206
19436
|
}
|
|
19207
19437
|
console.log(chalk11.gray("Goodbye."));
|
|
19208
19438
|
}
|
|
19209
|
-
async function runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing, allowImageDrop = true) {
|
|
19439
|
+
async function runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing, allowImageDrop = true, activityState) {
|
|
19210
19440
|
const activity = isGhostEnabled(session) ? void 0 : getActivitySink();
|
|
19211
|
-
const entries = [];
|
|
19212
|
-
let thinkingMs = 0;
|
|
19441
|
+
const entries = activityState?.entries ?? [];
|
|
19442
|
+
let thinkingMs = activityState?.thinkingMs ?? 0;
|
|
19213
19443
|
let thinkingStarted;
|
|
19214
19444
|
let turnFailed = false;
|
|
19215
|
-
const refreshActivity = (finished = false) =>
|
|
19445
|
+
const refreshActivity = (finished = false) => {
|
|
19446
|
+
if (activityState) {
|
|
19447
|
+
activityState.thinkingMs = thinkingMs;
|
|
19448
|
+
activityState.failed ||= turnFailed;
|
|
19449
|
+
}
|
|
19450
|
+
activity?.({ entries: [...entries], thinkingMs, finished: finished && !activityState, failed: turnFailed });
|
|
19451
|
+
};
|
|
19216
19452
|
refreshActivity();
|
|
19217
|
-
const spinner = new Spinner();
|
|
19218
|
-
const workSpinner = new Spinner("working");
|
|
19453
|
+
const spinner = new Spinner("working", activity ? getWorkingSink() : void 0);
|
|
19454
|
+
const workSpinner = new Spinner("working", activity ? getWorkingSink() : void 0);
|
|
19219
19455
|
const activeTools = /* @__PURE__ */ new Map();
|
|
19220
19456
|
let permissionPrompts = 0;
|
|
19221
19457
|
const renderWorking = () => {
|
|
@@ -19247,24 +19483,23 @@ async function runTurn(session, input, question, signal, drainQueuedMessages, pe
|
|
|
19247
19483
|
deltaChars = 0;
|
|
19248
19484
|
thinkingStarted = performance.now();
|
|
19249
19485
|
if (!quiet) {
|
|
19250
|
-
spinner.update("
|
|
19251
|
-
spinner.start(isComposing);
|
|
19486
|
+
spinner.update("working");
|
|
19487
|
+
spinner.start(isComposing, performance.now() - thinkingMs);
|
|
19252
19488
|
}
|
|
19253
19489
|
},
|
|
19254
19490
|
onModelDelta: (text) => {
|
|
19255
19491
|
deltaChars += text.length;
|
|
19256
|
-
if (!quiet) spinner.update(`
|
|
19492
|
+
if (!quiet) spinner.update(`working (${deltaChars} chars)`);
|
|
19257
19493
|
},
|
|
19258
19494
|
onModelSettled: () => {
|
|
19259
19495
|
if (thinkingStarted !== void 0) thinkingMs += performance.now() - thinkingStarted;
|
|
19260
19496
|
thinkingStarted = void 0;
|
|
19261
|
-
spinner.stop(activity ? void 0 : signal?.aborted ? "
|
|
19497
|
+
spinner.stop(activity ? void 0 : signal?.aborted ? "Work stopped after" : "Worked for");
|
|
19262
19498
|
refreshActivity();
|
|
19263
19499
|
},
|
|
19264
19500
|
onAssistantMessage: (message) => {
|
|
19265
19501
|
if (message.trim()) {
|
|
19266
|
-
|
|
19267
|
-
${message.trim()}`);
|
|
19502
|
+
writeActivityMessage(message.trim());
|
|
19268
19503
|
}
|
|
19269
19504
|
},
|
|
19270
19505
|
onToolStart: (call) => {
|
|
@@ -19300,7 +19535,7 @@ ${message.trim()}`);
|
|
|
19300
19535
|
console.log(chalk11.yellow(` ! ${notice}`));
|
|
19301
19536
|
},
|
|
19302
19537
|
onStopped: (reason) => {
|
|
19303
|
-
if (reason === "aborted") return;
|
|
19538
|
+
if (reason === "aborted" || reason === "max-steps" && activityState?.continuing) return;
|
|
19304
19539
|
const line = stopReasonLine(reason);
|
|
19305
19540
|
const hint = turnStopHint(reason, session.maxSteps);
|
|
19306
19541
|
if (!line || !hint) return;
|
|
@@ -19312,13 +19547,29 @@ ${message.trim()}`);
|
|
|
19312
19547
|
permissionPrompts += 1;
|
|
19313
19548
|
workSpinner.stop();
|
|
19314
19549
|
try {
|
|
19315
|
-
const prompt = permission === "shell" && call.tool === "run_shell" ? shellApprovalPrompt(call, session.toSession().workspace) : permission === "shell" ? ` ${call.tool} wants shell access.
|
|
19316
|
-
|
|
19550
|
+
const prompt = permission === "shell" && call.tool === "run_shell" ? shellApprovalPrompt(call, session.toSession().workspace) : permission === "shell" ? ` ${call.tool} wants shell access.
|
|
19551
|
+
1. [o]nce (this call only)
|
|
19552
|
+
2. [a]lways (this session)
|
|
19553
|
+
3. [n]o (this call)
|
|
19554
|
+
4. n[e]ver (this session)
|
|
19555
|
+
Answer: ` : ` ${call.tool} wants ${permission} access.
|
|
19556
|
+
1. [o]nce
|
|
19557
|
+
2. [a]lways (this session)
|
|
19558
|
+
3. [n]o
|
|
19559
|
+
4. n[e]ver (this session)
|
|
19560
|
+
Answer: `;
|
|
19561
|
+
const choices = ["Allow once", "Allow for this session", "Deny this call", "Never allow this permission this session"];
|
|
19562
|
+
const asker = getAskUserRuntime();
|
|
19563
|
+
const rawAnswer = asker ? await asker(prompt.split("\n").slice(0, -5).join("\n"), choices, signal, { collapseSummary: (value) => {
|
|
19564
|
+
const answer2 = value?.trim().toLowerCase();
|
|
19565
|
+
const label = !answer2 ? "Cancelled" : ["a", "always", "2", choices[1].toLowerCase()].includes(answer2) ? choices[1] : ["o", "once", "y", "yes", "1", choices[0].toLowerCase()].includes(answer2) ? choices[0] : ["e", "never", "4", choices[3].toLowerCase()].includes(answer2) ? choices[3] : choices[2];
|
|
19566
|
+
return ` ${call.tool} \xB7 ${permission} permission: ${label}`;
|
|
19567
|
+
} }) : await question(chalk11.yellow(prompt), signal);
|
|
19317
19568
|
if (signal?.aborted || rawAnswer === null) return "deny";
|
|
19318
19569
|
const answer = rawAnswer.trim().toLowerCase();
|
|
19319
|
-
if (answer === "a" || answer === "always") return "always";
|
|
19320
|
-
if (answer === "o" || answer === "once" || answer === "y" || answer === "yes") return "once";
|
|
19321
|
-
if (answer === "e" || answer === "never") {
|
|
19570
|
+
if (answer === "a" || answer === "always" || answer === "2" || answer === choices[1].toLowerCase()) return "always";
|
|
19571
|
+
if (answer === "o" || answer === "once" || answer === "y" || answer === "yes" || answer === "1" || answer === choices[0].toLowerCase()) return "once";
|
|
19572
|
+
if (answer === "e" || answer === "never" || answer === "4" || answer === choices[3].toLowerCase()) {
|
|
19322
19573
|
console.log(
|
|
19323
19574
|
chalk11.gray(
|
|
19324
19575
|
` ${permission} refused for the rest of the session \u2014 nothing will ask again. /allow ${permission} undoes it.`
|
|
@@ -19349,15 +19600,16 @@ ${message.trim()}`);
|
|
|
19349
19600
|
}
|
|
19350
19601
|
}
|
|
19351
19602
|
async function runAndCheckpointTurn(session, input, question, signal, persist, drainQueuedMessages, isComposing, maxRounds = 1, allowImageDrop = true) {
|
|
19603
|
+
const activityState = { entries: [], thinkingMs: 0, continuing: maxRounds > 1 };
|
|
19352
19604
|
try {
|
|
19353
|
-
if (maxRounds === 1) return await runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing, allowImageDrop);
|
|
19605
|
+
if (maxRounds === 1) return await runTurn(session, input, question, signal, drainQueuedMessages, persist, isComposing, allowImageDrop, activityState);
|
|
19354
19606
|
let successful = true;
|
|
19355
19607
|
const result = await runAutonomousLoop({
|
|
19356
19608
|
maxRounds,
|
|
19357
19609
|
signal,
|
|
19358
19610
|
classifyFailure: () => ({ kind: "unknown", retryable: false, failoverable: false, reason: "Interactive provider retries already exhausted." }),
|
|
19359
19611
|
runRound: async (round, _previous, roundSignal) => {
|
|
19360
|
-
successful = await runTurn(session, round === 1 ? input : AUTONOMOUS_CONTINUE_TASK, question, roundSignal, drainQueuedMessages, persist, isComposing, allowImageDrop);
|
|
19612
|
+
successful = await runTurn(session, round === 1 ? input : AUTONOMOUS_CONTINUE_TASK, question, roundSignal, drainQueuedMessages, persist, isComposing, allowImageDrop, activityState);
|
|
19361
19613
|
await persist(session);
|
|
19362
19614
|
const snapshot = session.toSession();
|
|
19363
19615
|
return successful ? snapshot : { ...snapshot, stopReason: roundSignal.aborted ? "aborted" : "error" };
|
|
@@ -19369,6 +19621,7 @@ async function runAndCheckpointTurn(session, input, question, signal, persist, d
|
|
|
19369
19621
|
if (result.haltReason !== "completed" && result.haltReason !== "aborted") console.log(chalk11.gray(`Automatic continuation stopped: ${result.haltReason}. Session preserved.`));
|
|
19370
19622
|
return successful;
|
|
19371
19623
|
} finally {
|
|
19624
|
+
if (!isGhostEnabled(session)) getActivitySink()?.({ ...activityState, entries: [...activityState.entries], finished: true });
|
|
19372
19625
|
await persist(session);
|
|
19373
19626
|
}
|
|
19374
19627
|
}
|
|
@@ -19620,15 +19873,43 @@ ${extra}`);
|
|
|
19620
19873
|
return "ok";
|
|
19621
19874
|
}
|
|
19622
19875
|
case "permissions": {
|
|
19623
|
-
|
|
19624
|
-
|
|
19625
|
-
|
|
19876
|
+
let selectedMode = argument.toLowerCase();
|
|
19877
|
+
const modes = ["readonly", "workspace", "full"];
|
|
19878
|
+
const choices = ["Readonly: no write, shell, or network access", "Workspace: file writes; shell and network denied", "Full access: no tool approval prompts"];
|
|
19879
|
+
const resolveMode = (value) => {
|
|
19880
|
+
if (value === null) return void 0;
|
|
19881
|
+
const text = value.trim().toLowerCase();
|
|
19882
|
+
const index = choices.findIndex((choice) => choice.toLowerCase() === text);
|
|
19883
|
+
if (index >= 0) return modes[index];
|
|
19884
|
+
if (["1", "2", "3"].includes(text)) return modes[Number(text) - 1];
|
|
19885
|
+
return modes.includes(text) ? text : void 0;
|
|
19886
|
+
};
|
|
19887
|
+
const asker = getAskUserRuntime();
|
|
19888
|
+
if (!selectedMode && asker) {
|
|
19889
|
+
const answer = await asker("Choose tool permissions for this session.", choices, signal, {
|
|
19890
|
+
collapseSummary: (value) => {
|
|
19891
|
+
const mode2 = resolveMode(value);
|
|
19892
|
+
return mode2 ? ` Permission mode: ${mode2}` : " Permission mode unchanged.";
|
|
19893
|
+
}
|
|
19894
|
+
});
|
|
19895
|
+
if (signal?.aborted || answer === null) return "ok";
|
|
19896
|
+
const mode = resolveMode(answer);
|
|
19897
|
+
if (!mode) {
|
|
19898
|
+
console.log("Permission mode unchanged. Use /permissions readonly|workspace|full.");
|
|
19899
|
+
return "ok";
|
|
19900
|
+
}
|
|
19901
|
+
selectedMode = mode;
|
|
19902
|
+
}
|
|
19903
|
+
if (selectedMode && selectedMode !== "status") {
|
|
19904
|
+
if (!modes.includes(selectedMode)) {
|
|
19905
|
+
console.log("Usage: /permissions [readonly|workspace|full|status]");
|
|
19626
19906
|
return "ok";
|
|
19627
19907
|
}
|
|
19628
19908
|
for (const permission of ["write", "shell", "network"]) {
|
|
19629
|
-
session.setPermission(permission,
|
|
19909
|
+
session.setPermission(permission, selectedMode === "full" || selectedMode === "workspace" && permission === "write");
|
|
19630
19910
|
}
|
|
19631
|
-
console.log("
|
|
19911
|
+
if (argument) console.log(selectedMode === "full" ? "Full access: file, shell, and network tools run without approval prompts for this session." : `Permission mode: ${selectedMode}. Ungranted tools are denied without prompting.`);
|
|
19912
|
+
return "ok";
|
|
19632
19913
|
}
|
|
19633
19914
|
const p = session.currentPermissions;
|
|
19634
19915
|
console.log(`write: ${p.write} shell: ${p.shell} network: ${p.network}`);
|
|
@@ -19919,11 +20200,16 @@ function shellApprovalPrompt(call, workspace) {
|
|
|
19919
20200
|
const timeoutMs = typeof call.input.timeoutMs === "number" ? call.input.timeoutMs : 3e4;
|
|
19920
20201
|
return [
|
|
19921
20202
|
" run_shell wants shell access.",
|
|
19922
|
-
"
|
|
20203
|
+
" Runs unsandboxed as your user.",
|
|
20204
|
+
" Can read or write your files and use your network connection.",
|
|
19923
20205
|
` Command: ${JSON.stringify(command)}`,
|
|
19924
20206
|
` Resolved cwd: ${JSON.stringify(resolvedCwd)}`,
|
|
19925
20207
|
` Timeout: ${timeoutMs.toLocaleString("en-US")} ms`,
|
|
19926
|
-
" [o]nce runs only this command
|
|
20208
|
+
" 1. [o]nce runs only this command",
|
|
20209
|
+
" 2. [a]lways grants shell access for this whole session, including other shell-using tools",
|
|
20210
|
+
" 3. [n]o (this call)",
|
|
20211
|
+
" 4. n[e]ver (this session)",
|
|
20212
|
+
" Answer: "
|
|
19927
20213
|
].join("\n");
|
|
19928
20214
|
}
|
|
19929
20215
|
function indent3(value) {
|
|
@@ -19943,7 +20229,7 @@ function parsePositiveInt(value, flag) {
|
|
|
19943
20229
|
var EXIT_INCOMPLETE_RUN = 2;
|
|
19944
20230
|
var NOTIFY_SUMMARY_CHARS = 1200;
|
|
19945
20231
|
function registerRunCommand(program2) {
|
|
19946
|
-
program2.command("run").alias("chat").description("Run a one-shot coding agent task").argument("<task...>", "Task for the agent").option("-w, --workspace <path>", "Workspace path", ".").option("--provider <name>", "Provider: ultrac (default) or echo (offline test stub)").option("--model <model>", "Model name").option("--base-url <url>", "Ultrac API base URL (overrides ULTRAC_BASE_URL and the stored login)").option("--mode <name>", `Agent mode: ${agentModeNames.join(", ")}`).option("--max-steps <number>", "Maximum agent steps").option("--no-native-tools", "Use the legacy JSON tool protocol").option("--allow-write", "Allow file writes", false).option("--allow-shell", "Grant the model broad, unsandboxed shell access", false).option("--allow-network", "Allow HTTP(S) URL fetches", false).option("--continue <id>", "Continue from a saved session id").option("--tools <list>", "Tools to advertise: `all`, or a comma-separated list. Default: the mode's set").option("--context-tokens <number>", "Model context window in tokens (default 262144)").option("--max-tool-result-chars <number>", "Cap on one tool result in the transcript (default 6000)").option(
|
|
20232
|
+
program2.command("run").alias("chat").description("Run a one-shot coding agent task").argument("<task...>", "Task for the agent").option("-w, --workspace <path>", "Workspace path", ".").option("--provider <name>", "Provider: ultrac (default) or echo (offline test stub)").option("--model <model>", "Model name").option("--base-url <url>", "Ultrac API base URL (overrides ULTRAC_BASE_URL and the stored login)").option("--mode <name>", `Agent mode: ${agentModeNames.join(", ")}`).option("--max-steps <number>", "Maximum agent steps").option("--no-native-tools", "Use the legacy JSON tool protocol").option("--permissions <mode>", "Tool permission preset: readonly, workspace, or full").option("--allow-write", "Allow file writes", false).option("--allow-shell", "Grant the model broad, unsandboxed shell access", false).option("--allow-network", "Allow HTTP(S) URL fetches", false).option("--continue <id>", "Continue from a saved session id").option("--tools <list>", "Tools to advertise: `all`, or a comma-separated list. Default: the mode's set").option("--context-tokens <number>", "Model context window in tokens (default 262144)").option("--max-tool-result-chars <number>", "Cap on one tool result in the transcript (default 6000)").option(
|
|
19947
20233
|
"--allow-incomplete",
|
|
19948
20234
|
`Exit 0 even when the run stops without finishing. By default an unfinished run (step cap, abort, error) exits ${EXIT_INCOMPLETE_RUN} so a script or a \`&&\` chain can tell a partial answer from a real one; pass this when hitting --max-steps on a long task is an acceptable outcome. The stop reason is still printed either way.`,
|
|
19949
20235
|
false
|
|
@@ -20060,7 +20346,8 @@ function registerRunCommand(program2) {
|
|
|
20060
20346
|
const abortRun = () => controller.abort();
|
|
20061
20347
|
process.on("SIGINT", abortRun);
|
|
20062
20348
|
process.on("SIGTERM", abortRun);
|
|
20063
|
-
|
|
20349
|
+
if (options.permissions && !["readonly", "workspace", "full"].includes(options.permissions)) throw new Error("--permissions must be readonly, workspace, or full.");
|
|
20350
|
+
const permissions = options.permissions ? { write: options.permissions !== "readonly", shell: options.permissions === "full", network: options.permissions === "full" } : autonomous ? { write: true, shell: true, network: true } : {
|
|
20064
20351
|
write: options.allowWrite || config.permissions.write,
|
|
20065
20352
|
shell: options.allowShell || config.permissions.shell,
|
|
20066
20353
|
network: options.allowNetwork || config.permissions.network
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spinalfluid/ultrac",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "Ultrac terminal agent harness for Cosmic 1, with local tools, browser sign-in, streamed answers, and resumable sessions.",
|
|
6
6
|
"author": "Aditya Pundir",
|