@spinalfluid/ultrac 0.1.18 → 0.1.21
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 +14 -3
- package/dist/cli.js +168 -85
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,12 +10,12 @@ between input and output. The hosted API supports streaming and native tool call
|
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npm install -g @spinalfluid/ultrac
|
|
13
|
-
ultrac login
|
|
14
|
-
ultrac doctor
|
|
15
13
|
ultrac
|
|
16
14
|
```
|
|
17
15
|
|
|
18
|
-
The npm package is public.
|
|
16
|
+
The npm package is public. The first interactive `ultrac` launch opens browser sign-in automatically
|
|
17
|
+
and continues into the same terminal session after verification. Model access requires an approved
|
|
18
|
+
Ultrac account. You can also sign in explicitly with `ultrac login`.
|
|
19
19
|
Requires Node.js >= 22.12.0. The command is `ultrac`; the package is `@spinalfluid/ultrac`.
|
|
20
20
|
The unrelated public packages named `ultrac` and `ultracode` are different products.
|
|
21
21
|
|
|
@@ -764,3 +764,14 @@ when a tool needs an ungranted permission.
|
|
|
764
764
|
These presets do not bypass operating-system permissions or the restrictions of a read-only
|
|
765
765
|
agent mode. Approval dialogs offer separate choices for one call, the session, denial, or
|
|
766
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
|
@@ -10834,6 +10834,8 @@ ${mode.instructions.map((instruction) => `- ${instruction}`).join("\n")}
|
|
|
10834
10834
|
${autonomyLines}
|
|
10835
10835
|
${UNTRUSTED_CONTENT_GUIDANCE}
|
|
10836
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
|
+
|
|
10837
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:
|
|
10838
10840
|
{
|
|
10839
10841
|
"message": "short user-facing status or final answer",
|
|
@@ -13809,13 +13811,9 @@ function registerLoginCommand(program2) {
|
|
|
13809
13811
|
);
|
|
13810
13812
|
}
|
|
13811
13813
|
let apiKey = options.key?.trim();
|
|
13812
|
-
let email;
|
|
13813
|
-
let keyId;
|
|
13814
13814
|
if (!apiKey && !options.manual) {
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
email = signedIn.email;
|
|
13818
|
-
keyId = signedIn.keyId;
|
|
13815
|
+
await signInWithBrowser({ baseUrl, browser: options.browser });
|
|
13816
|
+
return;
|
|
13819
13817
|
} else if (!apiKey) {
|
|
13820
13818
|
const loginUrl = loginUrlFor(baseUrl);
|
|
13821
13819
|
console.log(`Create an API key at ${chalk4.cyan(loginUrl)} and paste it below.`);
|
|
@@ -13828,7 +13826,7 @@ function registerLoginCommand(program2) {
|
|
|
13828
13826
|
stdin3.isTTY ? "No API key provided. Nothing was saved." : "No API key provided on stdin. Nothing was saved. Set ULTRAC_API_KEY for non-interactive use or pass an existing key with --key."
|
|
13829
13827
|
);
|
|
13830
13828
|
}
|
|
13831
|
-
await verifyAndStore({ apiKey, baseUrl,
|
|
13829
|
+
await verifyAndStore({ apiKey, baseUrl, pasted: Boolean(options.manual || options.key) });
|
|
13832
13830
|
});
|
|
13833
13831
|
program2.command("logout").description("Remove stored Ultrac credentials").action(async () => {
|
|
13834
13832
|
const stored = await readCredentials();
|
|
@@ -13859,6 +13857,11 @@ function registerLoginCommand(program2) {
|
|
|
13859
13857
|
if (stored?.createdAt) console.log(` since: ${stored.createdAt}`);
|
|
13860
13858
|
});
|
|
13861
13859
|
}
|
|
13860
|
+
async function signInWithBrowser(input) {
|
|
13861
|
+
const baseUrl = assertAllowedUltracBaseUrl(input.baseUrl);
|
|
13862
|
+
const signedIn = await browserLogin(baseUrl, input.browser !== false);
|
|
13863
|
+
await verifyAndStore({ ...signedIn, baseUrl, showStartupHint: !input.continueSession });
|
|
13864
|
+
}
|
|
13862
13865
|
async function browserLogin(baseUrl, shouldOpen) {
|
|
13863
13866
|
try {
|
|
13864
13867
|
return await loginWithBrowser({
|
|
@@ -13903,7 +13906,7 @@ async function verifyAndStore(input) {
|
|
|
13903
13906
|
});
|
|
13904
13907
|
console.log(chalk4.green(`Signed in. Credentials saved to ${path}`));
|
|
13905
13908
|
if (input.email) console.log(chalk4.gray(` ${input.email}`));
|
|
13906
|
-
console.log(chalk4.gray("Run `ultrac` to start an interactive session."));
|
|
13909
|
+
if (input.showStartupHint !== false) console.log(chalk4.gray("Run `ultrac` to start an interactive session."));
|
|
13907
13910
|
}
|
|
13908
13911
|
async function revokeStoredKey(apiKey, baseUrl) {
|
|
13909
13912
|
const url = currentKeyUrl(baseUrl);
|
|
@@ -17005,6 +17008,8 @@ ${mode.instructions.map((instruction) => `- ${instruction}`).join("\n")}
|
|
|
17005
17008
|
|
|
17006
17009
|
${UNTRUSTED_CONTENT_GUIDANCE}
|
|
17007
17010
|
|
|
17011
|
+
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.
|
|
17012
|
+
|
|
17008
17013
|
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.
|
|
17009
17014
|
|
|
17010
17015
|
Use the provided tools to inspect and change the workspace. Prefer narrow, reversible changes and
|
|
@@ -17636,6 +17641,16 @@ function formatSeconds(milliseconds) {
|
|
|
17636
17641
|
|
|
17637
17642
|
// src/ui/activity.ts
|
|
17638
17643
|
var sink;
|
|
17644
|
+
var messageSink;
|
|
17645
|
+
function setActivityMessageSink(value) {
|
|
17646
|
+
messageSink = value;
|
|
17647
|
+
}
|
|
17648
|
+
function writeActivityMessage(text) {
|
|
17649
|
+
if (messageSink) messageSink(text);
|
|
17650
|
+
else console.log(`
|
|
17651
|
+
${text}
|
|
17652
|
+
`);
|
|
17653
|
+
}
|
|
17639
17654
|
var workingSink;
|
|
17640
17655
|
function setWorkingSink(value) {
|
|
17641
17656
|
workingSink = value;
|
|
@@ -17670,7 +17685,7 @@ function activityRows(snapshot, expanded, selected, end) {
|
|
|
17670
17685
|
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`;
|
|
17671
17686
|
const rows = [{ text: summary, ok: failures || snapshot.failed ? false : snapshot.finished ? true : void 0 }];
|
|
17672
17687
|
if (!expanded) {
|
|
17673
|
-
if (!snapshot.finished) for (let index = Math.max(0, snapshot.entries.length - 3); index < snapshot.entries.length; index += 1) {
|
|
17688
|
+
if (!snapshot.finished || snapshot.preview) for (let index = Math.max(0, snapshot.entries.length - 3); index < snapshot.entries.length; index += 1) {
|
|
17674
17689
|
const entry = snapshot.entries[index];
|
|
17675
17690
|
rows.push({ text: ` \u25CF ${entry.label}`, ok: entry.ok, entry: index });
|
|
17676
17691
|
}
|
|
@@ -17690,32 +17705,6 @@ function activityRows(snapshot, expanded, selected, end) {
|
|
|
17690
17705
|
return rows;
|
|
17691
17706
|
}
|
|
17692
17707
|
|
|
17693
|
-
// src/ui/task-progress.ts
|
|
17694
|
-
function startTaskProgress(options) {
|
|
17695
|
-
let lastMessage = performance.now();
|
|
17696
|
-
let lastState = "";
|
|
17697
|
-
let stopped = false;
|
|
17698
|
-
const timer = setInterval(() => {
|
|
17699
|
-
if (stopped || options.holdWhile?.()) return;
|
|
17700
|
-
const state = options.snapshot();
|
|
17701
|
-
if (!state.activeTool && !state.waitingForModel) return;
|
|
17702
|
-
const fingerprint = `${state.completed}:${state.failed}:${state.activeTool ?? "model"}`;
|
|
17703
|
-
if (performance.now() - lastMessage < (fingerprint === lastState ? 6e4 : 3e4)) return;
|
|
17704
|
-
const completed = state.completed ? `I have completed ${state.completed} tool ${state.completed === 1 ? "call" : "calls"}.${state.failed ? ` Of those, ${state.failed} failed.` : ""} ` : "";
|
|
17705
|
-
const current = state.activeTool ? `I am running the ${state.activeTool} tool.` : "I am waiting for the model's next response.";
|
|
17706
|
-
options.write(`${completed}${current}`);
|
|
17707
|
-
lastMessage = performance.now();
|
|
17708
|
-
lastState = fingerprint;
|
|
17709
|
-
}, 5e3);
|
|
17710
|
-
timer.unref?.();
|
|
17711
|
-
return { markMessage: () => {
|
|
17712
|
-
lastMessage = performance.now();
|
|
17713
|
-
}, stop: () => {
|
|
17714
|
-
stopped = true;
|
|
17715
|
-
clearInterval(timer);
|
|
17716
|
-
} };
|
|
17717
|
-
}
|
|
17718
|
-
|
|
17719
17708
|
// src/ui/interactive-choice.ts
|
|
17720
17709
|
var MOUSE_ON = "\x1B[?1006h\x1B[?1000h";
|
|
17721
17710
|
var MOUSE_OFF = "\x1B[?1000l\x1B[?1006l";
|
|
@@ -18117,6 +18106,9 @@ var TerminalLayout = class {
|
|
|
18117
18106
|
prompt = "\u203A ";
|
|
18118
18107
|
inputFormatter;
|
|
18119
18108
|
transcriptRow = 1;
|
|
18109
|
+
transientBlocks = /* @__PURE__ */ new Set();
|
|
18110
|
+
activityOffset = 0;
|
|
18111
|
+
totalActivityEntries = 0;
|
|
18120
18112
|
transcriptColumn = 1;
|
|
18121
18113
|
wrapPending = false;
|
|
18122
18114
|
savedTranscript;
|
|
@@ -18230,7 +18222,61 @@ var TerminalLayout = class {
|
|
|
18230
18222
|
};
|
|
18231
18223
|
this.paintFooter();
|
|
18232
18224
|
}
|
|
18225
|
+
beginTransientBlock() {
|
|
18226
|
+
const anchor = { row: this.transcriptRow };
|
|
18227
|
+
this.transientBlocks.add(anchor);
|
|
18228
|
+
return anchor;
|
|
18229
|
+
}
|
|
18230
|
+
collapseTransientBlock(anchor, summary) {
|
|
18231
|
+
if (!this.transientBlocks.delete(anchor) || this.closed) return;
|
|
18232
|
+
const start = Math.max(1, Math.min(anchor.row, this.transcriptRow));
|
|
18233
|
+
const removed = Math.max(0, this.transcriptRow - start);
|
|
18234
|
+
if (removed) this.rawWrite(`${cup(start)}${CSI}${removed}M`);
|
|
18235
|
+
this.rawWrite(`${cup(start)}${CSI}2K`);
|
|
18236
|
+
for (const item of this.completedActivities) if (item.row >= start) item.row -= removed;
|
|
18237
|
+
this.completedActivities = this.completedActivities.filter((item) => item.row >= 1);
|
|
18238
|
+
this.transcriptRow = start;
|
|
18239
|
+
this.transcriptColumn = 1;
|
|
18240
|
+
this.wrapPending = false;
|
|
18241
|
+
this.writeTranscript(`${summary}
|
|
18242
|
+
|
|
18243
|
+
`);
|
|
18244
|
+
this.paintFooter();
|
|
18245
|
+
}
|
|
18246
|
+
writeActivityMessage(text) {
|
|
18247
|
+
const item = this.liveActivity;
|
|
18248
|
+
if (item) {
|
|
18249
|
+
if (item.snapshot.entries.length) {
|
|
18250
|
+
item.snapshot = { ...item.snapshot, finished: true, preview: true };
|
|
18251
|
+
if (item.row >= 2) this.rawWrite(`${cup(item.row - 1)}${CSI}2K`);
|
|
18252
|
+
this.redrawCompleted(item);
|
|
18253
|
+
} else {
|
|
18254
|
+
const start = Math.max(1, item.row - 1);
|
|
18255
|
+
const removed = item.row >= 1 ? Math.max(0, Math.min(item.height + (item.row >= 2 ? 1 : 0), this.transcriptRow - start)) : 0;
|
|
18256
|
+
if (removed) this.rawWrite(`${cup(start)}${CSI}${removed}M`);
|
|
18257
|
+
this.transcriptRow -= removed;
|
|
18258
|
+
this.completedActivities = this.completedActivities.filter((entry) => entry !== item);
|
|
18259
|
+
}
|
|
18260
|
+
this.liveActivity = void 0;
|
|
18261
|
+
}
|
|
18262
|
+
this.activityOffset = this.totalActivityEntries;
|
|
18263
|
+
this.writeTranscript(`
|
|
18264
|
+
${text}
|
|
18265
|
+
|
|
18266
|
+
`);
|
|
18267
|
+
if (item) {
|
|
18268
|
+
const snapshot = { ...item.snapshot, entries: [], finished: false, preview: false };
|
|
18269
|
+
this.writeTranscript(`
|
|
18270
|
+
${this.colorActivityRow(activityRows(snapshot, false, -1, 0)[0])}
|
|
18271
|
+
`);
|
|
18272
|
+
this.liveActivity = { snapshot, row: this.transcriptRow - 1, height: 1, expanded: false, selected: -1, end: 0 };
|
|
18273
|
+
this.completedActivities.push(this.liveActivity);
|
|
18274
|
+
}
|
|
18275
|
+
}
|
|
18233
18276
|
setActivity(snapshot) {
|
|
18277
|
+
if (!this.liveActivity && snapshot.entries.length < this.totalActivityEntries) this.activityOffset = 0;
|
|
18278
|
+
this.totalActivityEntries = snapshot.entries.length;
|
|
18279
|
+
snapshot = { ...snapshot, entries: snapshot.entries.slice(this.activityOffset) };
|
|
18234
18280
|
this.activity = snapshot;
|
|
18235
18281
|
if (!this.enabled || !this.installed || this.closed) return;
|
|
18236
18282
|
if (!this.liveActivity && !snapshot.finished) {
|
|
@@ -18257,9 +18303,13 @@ ${this.colorActivityRow(activityRows(snapshot, false, -1, snapshot.entries.lengt
|
|
|
18257
18303
|
}
|
|
18258
18304
|
this.completedActivities = this.completedActivities.filter((entry) => entry !== item);
|
|
18259
18305
|
this.liveActivity = void 0;
|
|
18260
|
-
this.
|
|
18306
|
+
this.activityOffset = 0;
|
|
18307
|
+
this.totalActivityEntries = 0;
|
|
18308
|
+
if (snapshot.entries.length) {
|
|
18309
|
+
this.writeTranscript(`${this.colorActivityRow(activityRows(snapshot, false, -1, snapshot.entries.length)[0])}
|
|
18261
18310
|
`);
|
|
18262
|
-
|
|
18311
|
+
this.completedActivities.push({ ...item, row: this.transcriptRow - 1, height: 1, expanded: false, selected: -1 });
|
|
18312
|
+
}
|
|
18263
18313
|
this.paintFooter();
|
|
18264
18314
|
}
|
|
18265
18315
|
setWorkingStatus(text) {
|
|
@@ -18290,6 +18340,7 @@ ${this.colorActivityRow(activityRows(item.snapshot, false, -1, item.end)[0])}
|
|
|
18290
18340
|
return dot >= 0 && color ? `${text.slice(0, dot)}${color}\u25CF${CSI}39m${text.slice(dot + 1)}` : text;
|
|
18291
18341
|
}
|
|
18292
18342
|
shiftCompleted(rows) {
|
|
18343
|
+
for (const anchor of this.transientBlocks) anchor.row -= rows;
|
|
18293
18344
|
for (const item of this.completedActivities) item.row -= rows;
|
|
18294
18345
|
this.completedActivities = this.completedActivities.filter((item) => item.row >= 1);
|
|
18295
18346
|
}
|
|
@@ -18313,7 +18364,7 @@ ${this.colorActivityRow(activityRows(item.snapshot, false, -1, item.end)[0])}
|
|
|
18313
18364
|
}
|
|
18314
18365
|
toggleActivity() {
|
|
18315
18366
|
if (this.liveActivity && (this.liveActivity.row < 2 || this.liveActivity.row + this.liveActivity.height > this.contentRows)) this.setWorkingStatus("working\u2026");
|
|
18316
|
-
const item = this.liveActivity
|
|
18367
|
+
const item = this.liveActivity?.snapshot.entries.length ? this.liveActivity : [...this.completedActivities].reverse().find((entry) => entry.snapshot.entries.length > 0) ?? this.liveActivity;
|
|
18317
18368
|
if (item) {
|
|
18318
18369
|
item.expanded = !item.expanded;
|
|
18319
18370
|
this.redrawCompleted(item);
|
|
@@ -18926,7 +18977,7 @@ var HELP = `Commands:
|
|
|
18926
18977
|
/autonomous [on|off|status] Continue step-limited turns (up to five rounds)
|
|
18927
18978
|
/model [name] Show or set the model
|
|
18928
18979
|
/mode [name] Show or set the agent mode (${agentModeNames.join(", ")})
|
|
18929
|
-
/permissions [readonly|workspace|full]
|
|
18980
|
+
/permissions [readonly|workspace|full|status] Choose or set tool access
|
|
18930
18981
|
/deny <perm> Revoke a permission for this session
|
|
18931
18982
|
/plan [task] Plan without workspace edits
|
|
18932
18983
|
/review [focus] Review current changes without editing
|
|
@@ -19049,6 +19100,10 @@ async function runRepl(options, initialTask = "") {
|
|
|
19049
19100
|
throw new Error("--max-steps must be a positive integer.");
|
|
19050
19101
|
}
|
|
19051
19102
|
const hasFallbackChain = Boolean(config.fallback?.chain?.length);
|
|
19103
|
+
if (providerName === "ultrac" && !hasFallbackChain && !await resolveUltracCredentials() && stdin5.isTTY && stdout5.isTTY) {
|
|
19104
|
+
console.log("Opening browser sign-in\u2026");
|
|
19105
|
+
await signInWithBrowser({ baseUrl: resolveBaseUrl().baseUrl, continueSession: true });
|
|
19106
|
+
}
|
|
19052
19107
|
if (providerName === "ultrac" && !hasFallbackChain && !await resolveUltracCredentials()) {
|
|
19053
19108
|
console.log(chalk11.yellow("You are not signed in to Ultrac."));
|
|
19054
19109
|
console.log(`Run ${chalk11.white("ultrac login")} first, or start with ${chalk11.white("--provider echo")}.`);
|
|
@@ -19125,6 +19180,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
19125
19180
|
}) : void 0;
|
|
19126
19181
|
setActivitySink(layout?.enabled ? (snapshot) => layout.setActivity(snapshot) : void 0);
|
|
19127
19182
|
setWorkingSink(layout?.enabled ? (text) => layout.setWorkingStatus(text) : void 0);
|
|
19183
|
+
setActivityMessageSink(layout?.enabled ? (text) => layout.writeActivityMessage(text) : void 0);
|
|
19128
19184
|
const refreshDetails = () => {
|
|
19129
19185
|
if (!layout?.enabled) return;
|
|
19130
19186
|
const steps = session.toSession().steps;
|
|
@@ -19209,36 +19265,44 @@ async function runRepl(options, initialTask = "") {
|
|
|
19209
19265
|
};
|
|
19210
19266
|
const drainQueuedMessages = () => reader.drainQueuedMessages();
|
|
19211
19267
|
let autonomousRounds = 5;
|
|
19212
|
-
setAskUserRuntime(async (text, options2, signal) => {
|
|
19213
|
-
|
|
19268
|
+
setAskUserRuntime(async (text, options2, signal, presentation) => {
|
|
19269
|
+
const block = presentation && layout?.enabled ? layout.beginTransientBlock() : void 0;
|
|
19270
|
+
let resolvedAnswer = null;
|
|
19271
|
+
try {
|
|
19272
|
+
console.log(chalk11.white(`
|
|
19214
19273
|
? ${text}`));
|
|
19215
|
-
|
|
19216
|
-
|
|
19217
|
-
|
|
19218
|
-
layout.setPrompt("\u203A ");
|
|
19219
|
-
let choices;
|
|
19220
|
-
try {
|
|
19221
|
-
choices = attachInteractiveChoices(rl, options2, { signal, output: stdout5, prompt: "\u203A ", maxRow: () => layout.contentRows });
|
|
19222
|
-
rawAnswer = await reader.nextFresh(signal);
|
|
19223
|
-
} finally {
|
|
19224
|
-
choices?.cleanup();
|
|
19274
|
+
let rawAnswer;
|
|
19275
|
+
if (options2?.length && layout?.enabled) {
|
|
19276
|
+
slashCompletion?.suspend();
|
|
19225
19277
|
layout.setPrompt("\u203A ");
|
|
19226
|
-
|
|
19278
|
+
let choices;
|
|
19279
|
+
try {
|
|
19280
|
+
choices = attachInteractiveChoices(rl, options2, { signal, output: stdout5, prompt: "\u203A ", maxRow: () => layout.contentRows });
|
|
19281
|
+
rawAnswer = await reader.nextFresh(signal);
|
|
19282
|
+
} finally {
|
|
19283
|
+
choices?.cleanup();
|
|
19284
|
+
layout.setPrompt("\u203A ");
|
|
19285
|
+
slashCompletion?.resume();
|
|
19286
|
+
}
|
|
19287
|
+
} else {
|
|
19288
|
+
if (options2?.length) {
|
|
19289
|
+
options2.forEach((option, index) => console.log(chalk11.gray(` ${index + 1}. ${option}`)));
|
|
19290
|
+
console.log(chalk11.gray(" (type a number, or answer in your own words)"));
|
|
19291
|
+
}
|
|
19292
|
+
rawAnswer = await question(chalk11.white(" \u203A "), signal);
|
|
19227
19293
|
}
|
|
19228
|
-
|
|
19229
|
-
|
|
19230
|
-
|
|
19231
|
-
|
|
19294
|
+
if (signal?.aborted) return null;
|
|
19295
|
+
const answer = (rawAnswer ?? "").trim();
|
|
19296
|
+
const picked = Number.parseInt(answer, 10);
|
|
19297
|
+
if (options2?.length && String(picked) === answer && picked >= 1 && picked <= options2.length) {
|
|
19298
|
+
resolvedAnswer = options2[picked - 1];
|
|
19299
|
+
return resolvedAnswer;
|
|
19232
19300
|
}
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
const picked = Number.parseInt(answer, 10);
|
|
19238
|
-
if (options2?.length && String(picked) === answer && picked >= 1 && picked <= options2.length) {
|
|
19239
|
-
return options2[picked - 1];
|
|
19301
|
+
resolvedAnswer = rawAnswer === null ? null : answer;
|
|
19302
|
+
return resolvedAnswer;
|
|
19303
|
+
} finally {
|
|
19304
|
+
if (block && presentation) layout?.collapseTransientBlock(block, presentation.collapseSummary(signal?.aborted ? null : resolvedAnswer));
|
|
19240
19305
|
}
|
|
19241
|
-
return answer;
|
|
19242
19306
|
});
|
|
19243
19307
|
let running = true;
|
|
19244
19308
|
let turnAbort;
|
|
@@ -19366,6 +19430,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
19366
19430
|
setAskUserRuntime(void 0);
|
|
19367
19431
|
setActivitySink(void 0);
|
|
19368
19432
|
setWorkingSink(void 0);
|
|
19433
|
+
setActivityMessageSink(void 0);
|
|
19369
19434
|
rl.close();
|
|
19370
19435
|
try {
|
|
19371
19436
|
await saveTranscript(session, writeCheckpoint);
|
|
@@ -19405,17 +19470,6 @@ async function runTurn(session, input, question, signal, drainQueuedMessages, pe
|
|
|
19405
19470
|
const detachProgress = quiet ? () => {
|
|
19406
19471
|
} : attachProgressUi();
|
|
19407
19472
|
let deltaChars = 0;
|
|
19408
|
-
const progress2 = quiet ? void 0 : startTaskProgress({
|
|
19409
|
-
snapshot: () => ({
|
|
19410
|
-
completed: entries.length,
|
|
19411
|
-
failed: entries.filter((entry) => !entry.ok).length,
|
|
19412
|
-
activeTool: [...activeTools.keys()].at(-1)?.tool,
|
|
19413
|
-
waitingForModel: thinkingStarted !== void 0
|
|
19414
|
-
}),
|
|
19415
|
-
holdWhile: () => permissionPrompts > 0 || [...activeTools.keys()].some((call) => call.tool === "ask_user"),
|
|
19416
|
-
write: (text) => console.log(`
|
|
19417
|
-
${text}`)
|
|
19418
|
-
});
|
|
19419
19473
|
try {
|
|
19420
19474
|
const prepared = allowImageDrop ? await prepareImageInput(input) : { text: input };
|
|
19421
19475
|
await session.ask(prepared.text, {
|
|
@@ -19450,9 +19504,7 @@ ${text}`)
|
|
|
19450
19504
|
},
|
|
19451
19505
|
onAssistantMessage: (message) => {
|
|
19452
19506
|
if (message.trim()) {
|
|
19453
|
-
|
|
19454
|
-
console.log(`
|
|
19455
|
-
${message.trim()}`);
|
|
19507
|
+
writeActivityMessage(message.trim());
|
|
19456
19508
|
}
|
|
19457
19509
|
},
|
|
19458
19510
|
onToolStart: (call) => {
|
|
@@ -19513,7 +19565,11 @@ ${message.trim()}`);
|
|
|
19513
19565
|
Answer: `;
|
|
19514
19566
|
const choices = ["Allow once", "Allow for this session", "Deny this call", "Never allow this permission this session"];
|
|
19515
19567
|
const asker = getAskUserRuntime();
|
|
19516
|
-
const rawAnswer = asker ? await asker(prompt.split("\n").slice(0, -5).join("\n"), choices, signal
|
|
19568
|
+
const rawAnswer = asker ? await asker(prompt.split("\n").slice(0, -5).join("\n"), choices, signal, { collapseSummary: (value) => {
|
|
19569
|
+
const answer2 = value?.trim().toLowerCase();
|
|
19570
|
+
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];
|
|
19571
|
+
return ` ${call.tool} \xB7 ${permission} permission: ${label}`;
|
|
19572
|
+
} }) : await question(chalk11.yellow(prompt), signal);
|
|
19517
19573
|
if (signal?.aborted || rawAnswer === null) return "deny";
|
|
19518
19574
|
const answer = rawAnswer.trim().toLowerCase();
|
|
19519
19575
|
if (answer === "a" || answer === "always" || answer === "2" || answer === choices[1].toLowerCase()) return "always";
|
|
@@ -19545,7 +19601,6 @@ ${message.trim()}`);
|
|
|
19545
19601
|
activeTools.clear();
|
|
19546
19602
|
if (thinkingStarted !== void 0) thinkingMs += performance.now() - thinkingStarted;
|
|
19547
19603
|
refreshActivity(true);
|
|
19548
|
-
progress2?.stop();
|
|
19549
19604
|
detachProgress();
|
|
19550
19605
|
}
|
|
19551
19606
|
}
|
|
@@ -19823,15 +19878,43 @@ ${extra}`);
|
|
|
19823
19878
|
return "ok";
|
|
19824
19879
|
}
|
|
19825
19880
|
case "permissions": {
|
|
19826
|
-
|
|
19827
|
-
|
|
19828
|
-
|
|
19881
|
+
let selectedMode = argument.toLowerCase();
|
|
19882
|
+
const modes = ["readonly", "workspace", "full"];
|
|
19883
|
+
const choices = ["Readonly: no write, shell, or network access", "Workspace: file writes; shell and network denied", "Full access: no tool approval prompts"];
|
|
19884
|
+
const resolveMode = (value) => {
|
|
19885
|
+
if (value === null) return void 0;
|
|
19886
|
+
const text = value.trim().toLowerCase();
|
|
19887
|
+
const index = choices.findIndex((choice) => choice.toLowerCase() === text);
|
|
19888
|
+
if (index >= 0) return modes[index];
|
|
19889
|
+
if (["1", "2", "3"].includes(text)) return modes[Number(text) - 1];
|
|
19890
|
+
return modes.includes(text) ? text : void 0;
|
|
19891
|
+
};
|
|
19892
|
+
const asker = getAskUserRuntime();
|
|
19893
|
+
if (!selectedMode && asker) {
|
|
19894
|
+
const answer = await asker("Choose tool permissions for this session.", choices, signal, {
|
|
19895
|
+
collapseSummary: (value) => {
|
|
19896
|
+
const mode2 = resolveMode(value);
|
|
19897
|
+
return mode2 ? ` Permission mode: ${mode2}` : " Permission mode unchanged.";
|
|
19898
|
+
}
|
|
19899
|
+
});
|
|
19900
|
+
if (signal?.aborted || answer === null) return "ok";
|
|
19901
|
+
const mode = resolveMode(answer);
|
|
19902
|
+
if (!mode) {
|
|
19903
|
+
console.log("Permission mode unchanged. Use /permissions readonly|workspace|full.");
|
|
19904
|
+
return "ok";
|
|
19905
|
+
}
|
|
19906
|
+
selectedMode = mode;
|
|
19907
|
+
}
|
|
19908
|
+
if (selectedMode && selectedMode !== "status") {
|
|
19909
|
+
if (!modes.includes(selectedMode)) {
|
|
19910
|
+
console.log("Usage: /permissions [readonly|workspace|full|status]");
|
|
19829
19911
|
return "ok";
|
|
19830
19912
|
}
|
|
19831
19913
|
for (const permission of ["write", "shell", "network"]) {
|
|
19832
|
-
session.setPermission(permission,
|
|
19914
|
+
session.setPermission(permission, selectedMode === "full" || selectedMode === "workspace" && permission === "write");
|
|
19833
19915
|
}
|
|
19834
|
-
console.log(
|
|
19916
|
+
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.`);
|
|
19917
|
+
return "ok";
|
|
19835
19918
|
}
|
|
19836
19919
|
const p = session.currentPermissions;
|
|
19837
19920
|
console.log(`write: ${p.write} shell: ${p.shell} network: ${p.network}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spinalfluid/ultrac",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
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",
|