@spinalfluid/ultrac 0.1.18 → 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 +11 -0
- package/dist/cli.js +155 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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",
|
|
@@ -17005,6 +17007,8 @@ ${mode.instructions.map((instruction) => `- ${instruction}`).join("\n")}
|
|
|
17005
17007
|
|
|
17006
17008
|
${UNTRUSTED_CONTENT_GUIDANCE}
|
|
17007
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
|
+
|
|
17008
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.
|
|
17009
17013
|
|
|
17010
17014
|
Use the provided tools to inspect and change the workspace. Prefer narrow, reversible changes and
|
|
@@ -17636,6 +17640,16 @@ function formatSeconds(milliseconds) {
|
|
|
17636
17640
|
|
|
17637
17641
|
// src/ui/activity.ts
|
|
17638
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
|
+
}
|
|
17639
17653
|
var workingSink;
|
|
17640
17654
|
function setWorkingSink(value) {
|
|
17641
17655
|
workingSink = value;
|
|
@@ -17670,7 +17684,7 @@ function activityRows(snapshot, expanded, selected, end) {
|
|
|
17670
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`;
|
|
17671
17685
|
const rows = [{ text: summary, ok: failures || snapshot.failed ? false : snapshot.finished ? true : void 0 }];
|
|
17672
17686
|
if (!expanded) {
|
|
17673
|
-
if (!snapshot.finished) for (let index = Math.max(0, snapshot.entries.length - 3); index < snapshot.entries.length; index += 1) {
|
|
17687
|
+
if (!snapshot.finished || snapshot.preview) for (let index = Math.max(0, snapshot.entries.length - 3); index < snapshot.entries.length; index += 1) {
|
|
17674
17688
|
const entry = snapshot.entries[index];
|
|
17675
17689
|
rows.push({ text: ` \u25CF ${entry.label}`, ok: entry.ok, entry: index });
|
|
17676
17690
|
}
|
|
@@ -17690,32 +17704,6 @@ function activityRows(snapshot, expanded, selected, end) {
|
|
|
17690
17704
|
return rows;
|
|
17691
17705
|
}
|
|
17692
17706
|
|
|
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
17707
|
// src/ui/interactive-choice.ts
|
|
17720
17708
|
var MOUSE_ON = "\x1B[?1006h\x1B[?1000h";
|
|
17721
17709
|
var MOUSE_OFF = "\x1B[?1000l\x1B[?1006l";
|
|
@@ -18117,6 +18105,9 @@ var TerminalLayout = class {
|
|
|
18117
18105
|
prompt = "\u203A ";
|
|
18118
18106
|
inputFormatter;
|
|
18119
18107
|
transcriptRow = 1;
|
|
18108
|
+
transientBlocks = /* @__PURE__ */ new Set();
|
|
18109
|
+
activityOffset = 0;
|
|
18110
|
+
totalActivityEntries = 0;
|
|
18120
18111
|
transcriptColumn = 1;
|
|
18121
18112
|
wrapPending = false;
|
|
18122
18113
|
savedTranscript;
|
|
@@ -18230,7 +18221,61 @@ var TerminalLayout = class {
|
|
|
18230
18221
|
};
|
|
18231
18222
|
this.paintFooter();
|
|
18232
18223
|
}
|
|
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();
|
|
18244
|
+
}
|
|
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
|
+
}
|
|
18274
|
+
}
|
|
18233
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) };
|
|
18234
18279
|
this.activity = snapshot;
|
|
18235
18280
|
if (!this.enabled || !this.installed || this.closed) return;
|
|
18236
18281
|
if (!this.liveActivity && !snapshot.finished) {
|
|
@@ -18257,9 +18302,13 @@ ${this.colorActivityRow(activityRows(snapshot, false, -1, snapshot.entries.lengt
|
|
|
18257
18302
|
}
|
|
18258
18303
|
this.completedActivities = this.completedActivities.filter((entry) => entry !== item);
|
|
18259
18304
|
this.liveActivity = void 0;
|
|
18260
|
-
this.
|
|
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])}
|
|
18261
18309
|
`);
|
|
18262
|
-
|
|
18310
|
+
this.completedActivities.push({ ...item, row: this.transcriptRow - 1, height: 1, expanded: false, selected: -1 });
|
|
18311
|
+
}
|
|
18263
18312
|
this.paintFooter();
|
|
18264
18313
|
}
|
|
18265
18314
|
setWorkingStatus(text) {
|
|
@@ -18290,6 +18339,7 @@ ${this.colorActivityRow(activityRows(item.snapshot, false, -1, item.end)[0])}
|
|
|
18290
18339
|
return dot >= 0 && color ? `${text.slice(0, dot)}${color}\u25CF${CSI}39m${text.slice(dot + 1)}` : text;
|
|
18291
18340
|
}
|
|
18292
18341
|
shiftCompleted(rows) {
|
|
18342
|
+
for (const anchor of this.transientBlocks) anchor.row -= rows;
|
|
18293
18343
|
for (const item of this.completedActivities) item.row -= rows;
|
|
18294
18344
|
this.completedActivities = this.completedActivities.filter((item) => item.row >= 1);
|
|
18295
18345
|
}
|
|
@@ -18313,7 +18363,7 @@ ${this.colorActivityRow(activityRows(item.snapshot, false, -1, item.end)[0])}
|
|
|
18313
18363
|
}
|
|
18314
18364
|
toggleActivity() {
|
|
18315
18365
|
if (this.liveActivity && (this.liveActivity.row < 2 || this.liveActivity.row + this.liveActivity.height > this.contentRows)) this.setWorkingStatus("working\u2026");
|
|
18316
|
-
const item = this.liveActivity
|
|
18366
|
+
const item = this.liveActivity?.snapshot.entries.length ? this.liveActivity : [...this.completedActivities].reverse().find((entry) => entry.snapshot.entries.length > 0) ?? this.liveActivity;
|
|
18317
18367
|
if (item) {
|
|
18318
18368
|
item.expanded = !item.expanded;
|
|
18319
18369
|
this.redrawCompleted(item);
|
|
@@ -18926,7 +18976,7 @@ var HELP = `Commands:
|
|
|
18926
18976
|
/autonomous [on|off|status] Continue step-limited turns (up to five rounds)
|
|
18927
18977
|
/model [name] Show or set the model
|
|
18928
18978
|
/mode [name] Show or set the agent mode (${agentModeNames.join(", ")})
|
|
18929
|
-
/permissions [readonly|workspace|full]
|
|
18979
|
+
/permissions [readonly|workspace|full|status] Choose or set tool access
|
|
18930
18980
|
/deny <perm> Revoke a permission for this session
|
|
18931
18981
|
/plan [task] Plan without workspace edits
|
|
18932
18982
|
/review [focus] Review current changes without editing
|
|
@@ -19125,6 +19175,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
19125
19175
|
}) : void 0;
|
|
19126
19176
|
setActivitySink(layout?.enabled ? (snapshot) => layout.setActivity(snapshot) : void 0);
|
|
19127
19177
|
setWorkingSink(layout?.enabled ? (text) => layout.setWorkingStatus(text) : void 0);
|
|
19178
|
+
setActivityMessageSink(layout?.enabled ? (text) => layout.writeActivityMessage(text) : void 0);
|
|
19128
19179
|
const refreshDetails = () => {
|
|
19129
19180
|
if (!layout?.enabled) return;
|
|
19130
19181
|
const steps = session.toSession().steps;
|
|
@@ -19209,36 +19260,44 @@ async function runRepl(options, initialTask = "") {
|
|
|
19209
19260
|
};
|
|
19210
19261
|
const drainQueuedMessages = () => reader.drainQueuedMessages();
|
|
19211
19262
|
let autonomousRounds = 5;
|
|
19212
|
-
setAskUserRuntime(async (text, options2, signal) => {
|
|
19213
|
-
|
|
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(`
|
|
19214
19268
|
? ${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();
|
|
19269
|
+
let rawAnswer;
|
|
19270
|
+
if (options2?.length && layout?.enabled) {
|
|
19271
|
+
slashCompletion?.suspend();
|
|
19225
19272
|
layout.setPrompt("\u203A ");
|
|
19226
|
-
|
|
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);
|
|
19227
19288
|
}
|
|
19228
|
-
|
|
19229
|
-
|
|
19230
|
-
|
|
19231
|
-
|
|
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;
|
|
19232
19295
|
}
|
|
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];
|
|
19296
|
+
resolvedAnswer = rawAnswer === null ? null : answer;
|
|
19297
|
+
return resolvedAnswer;
|
|
19298
|
+
} finally {
|
|
19299
|
+
if (block && presentation) layout?.collapseTransientBlock(block, presentation.collapseSummary(signal?.aborted ? null : resolvedAnswer));
|
|
19240
19300
|
}
|
|
19241
|
-
return answer;
|
|
19242
19301
|
});
|
|
19243
19302
|
let running = true;
|
|
19244
19303
|
let turnAbort;
|
|
@@ -19366,6 +19425,7 @@ async function runRepl(options, initialTask = "") {
|
|
|
19366
19425
|
setAskUserRuntime(void 0);
|
|
19367
19426
|
setActivitySink(void 0);
|
|
19368
19427
|
setWorkingSink(void 0);
|
|
19428
|
+
setActivityMessageSink(void 0);
|
|
19369
19429
|
rl.close();
|
|
19370
19430
|
try {
|
|
19371
19431
|
await saveTranscript(session, writeCheckpoint);
|
|
@@ -19405,17 +19465,6 @@ async function runTurn(session, input, question, signal, drainQueuedMessages, pe
|
|
|
19405
19465
|
const detachProgress = quiet ? () => {
|
|
19406
19466
|
} : attachProgressUi();
|
|
19407
19467
|
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
19468
|
try {
|
|
19420
19469
|
const prepared = allowImageDrop ? await prepareImageInput(input) : { text: input };
|
|
19421
19470
|
await session.ask(prepared.text, {
|
|
@@ -19450,9 +19499,7 @@ ${text}`)
|
|
|
19450
19499
|
},
|
|
19451
19500
|
onAssistantMessage: (message) => {
|
|
19452
19501
|
if (message.trim()) {
|
|
19453
|
-
|
|
19454
|
-
console.log(`
|
|
19455
|
-
${message.trim()}`);
|
|
19502
|
+
writeActivityMessage(message.trim());
|
|
19456
19503
|
}
|
|
19457
19504
|
},
|
|
19458
19505
|
onToolStart: (call) => {
|
|
@@ -19513,7 +19560,11 @@ ${message.trim()}`);
|
|
|
19513
19560
|
Answer: `;
|
|
19514
19561
|
const choices = ["Allow once", "Allow for this session", "Deny this call", "Never allow this permission this session"];
|
|
19515
19562
|
const asker = getAskUserRuntime();
|
|
19516
|
-
const rawAnswer = asker ? await asker(prompt.split("\n").slice(0, -5).join("\n"), choices, signal
|
|
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);
|
|
19517
19568
|
if (signal?.aborted || rawAnswer === null) return "deny";
|
|
19518
19569
|
const answer = rawAnswer.trim().toLowerCase();
|
|
19519
19570
|
if (answer === "a" || answer === "always" || answer === "2" || answer === choices[1].toLowerCase()) return "always";
|
|
@@ -19545,7 +19596,6 @@ ${message.trim()}`);
|
|
|
19545
19596
|
activeTools.clear();
|
|
19546
19597
|
if (thinkingStarted !== void 0) thinkingMs += performance.now() - thinkingStarted;
|
|
19547
19598
|
refreshActivity(true);
|
|
19548
|
-
progress2?.stop();
|
|
19549
19599
|
detachProgress();
|
|
19550
19600
|
}
|
|
19551
19601
|
}
|
|
@@ -19823,15 +19873,43 @@ ${extra}`);
|
|
|
19823
19873
|
return "ok";
|
|
19824
19874
|
}
|
|
19825
19875
|
case "permissions": {
|
|
19826
|
-
|
|
19827
|
-
|
|
19828
|
-
|
|
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]");
|
|
19829
19906
|
return "ok";
|
|
19830
19907
|
}
|
|
19831
19908
|
for (const permission of ["write", "shell", "network"]) {
|
|
19832
|
-
session.setPermission(permission,
|
|
19909
|
+
session.setPermission(permission, selectedMode === "full" || selectedMode === "workspace" && permission === "write");
|
|
19833
19910
|
}
|
|
19834
|
-
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";
|
|
19835
19913
|
}
|
|
19836
19914
|
const p = session.currentPermissions;
|
|
19837
19915
|
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.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",
|