@shardflux/cli 0.24.1 → 0.28.0
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/CHANGELOG.md +159 -1
- package/README.md +182 -35
- package/dist/account-pool.d.ts +11 -1
- package/dist/account-pool.js +48 -0
- package/dist/acp-journal.js +23 -1
- package/dist/agent-commands.js +4 -3
- package/dist/agent-models.d.ts +31 -1
- package/dist/agent-models.js +27 -4
- package/dist/args.d.ts +1 -1
- package/dist/args.js +97 -14
- package/dist/board-args.js +26 -4
- package/dist/board-commands.d.ts +9 -5
- package/dist/board-commands.js +43 -16
- package/dist/brain-commands.d.ts +2 -0
- package/dist/brain-commands.js +78 -0
- package/dist/client-settings.d.ts +2 -1
- package/dist/client-settings.js +14 -2
- package/dist/codex-cloud.d.ts +2 -0
- package/dist/codex-cloud.js +14 -11
- package/dist/commands.js +8 -0
- package/dist/conversation-commands.d.ts +5 -1
- package/dist/conversation-commands.js +76 -8
- package/dist/conversation-terminal.js +2 -2
- package/dist/conversations.d.ts +2 -1
- package/dist/desktop-linux.d.ts +12 -1
- package/dist/desktop-linux.js +47 -18
- package/dist/device-approvals.js +24 -2
- package/dist/device-bridge.d.ts +21 -4
- package/dist/device-bridge.js +173 -7
- package/dist/device-folders.d.ts +14 -1
- package/dist/device-folders.js +20 -8
- package/dist/device-helper.d.ts +8 -0
- package/dist/device-helper.js +83 -13
- package/dist/device-mode.d.ts +44 -0
- package/dist/device-mode.js +119 -0
- package/dist/drive-commands.d.ts +2 -2
- package/dist/drive-commands.js +1 -1
- package/dist/errors.js +2 -1
- package/dist/format.js +6 -2
- package/dist/handlers.js +5 -2
- package/dist/land.d.ts +121 -0
- package/dist/land.js +284 -0
- package/dist/mac-app.d.ts +11 -0
- package/dist/mac-app.js +11 -0
- package/dist/main.js +9 -1
- package/dist/menubar.d.ts +18 -13
- package/dist/menubar.js +60 -50
- package/dist/onboarding.d.ts +4 -4
- package/dist/orchestrator-args.d.ts +7 -0
- package/dist/orchestrator-args.js +64 -0
- package/dist/orchestrator-commands.d.ts +43 -0
- package/dist/orchestrator-commands.js +262 -0
- package/dist/person-scope.d.ts +20 -0
- package/dist/person-scope.js +61 -0
- package/dist/project-hub-seed.d.ts +20 -0
- package/dist/project-hub-seed.js +59 -0
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +2 -1
- package/dist/router-commands.d.ts +12 -10
- package/dist/router-commands.js +5 -59
- package/dist/t3-launch.js +2 -2
- package/dist/task-land-hub.py +165 -0
- package/dist/this-computer.d.ts +4 -2
- package/dist/this-computer.js +20 -2
- package/dist/thread-on-computer.js +2 -2
- package/dist/timing.js +3 -1
- package/dist/transcribe-commands.d.ts +7 -0
- package/dist/transcribe-commands.js +84 -0
- package/dist/update.js +4 -2
- package/dist/wait-status.d.ts +1 -1
- package/dist/wait-status.js +2 -0
- package/dist/worktree-twin-engine.d.ts +8 -0
- package/dist/worktree-twin-engine.js +29 -23
- package/dist/worktree-twin.d.ts +6 -0
- package/dist/worktree-twin.js +8 -1
- package/package.json +6 -5
package/dist/board-commands.js
CHANGED
|
@@ -3,6 +3,7 @@ import { detailLine, pickAnswers } from "./conversation-commands.js";
|
|
|
3
3
|
import { NotFoundError, UsageError } from "./errors.js";
|
|
4
4
|
import { time } from "./format.js";
|
|
5
5
|
import { progress } from "./progress.js";
|
|
6
|
+
import { landDeps, landTask, landedText } from "./land.js";
|
|
6
7
|
const BUCKETS = ['now', 'next', 'later'];
|
|
7
8
|
const BUCKET_TITLES = { now: 'Now', next: 'Next', later: 'Later' };
|
|
8
9
|
const MODES = ['ask', 'routine', 'trusted'];
|
|
@@ -16,18 +17,26 @@ const oneLine = (s, max) => {
|
|
|
16
17
|
export function taskLabel(t, max = 80) {
|
|
17
18
|
return oneLine(t.title?.trim() || t.text, max);
|
|
18
19
|
}
|
|
20
|
+
/** Why a task waits to start, or what a working one does now: "Getting its computer ready", "Fixing the login cookie". */
|
|
21
|
+
function stateNote(t, max) {
|
|
22
|
+
const note = t.waiting?.message.trim() || (t.state === 'working' ? t.activity?.trim() : '') || '';
|
|
23
|
+
return note ? ` (${oneLine(note, max)})` : '';
|
|
24
|
+
}
|
|
19
25
|
/**
|
|
20
|
-
* One task as `shard board` lists it: id, state (with why it waits to start
|
|
21
|
-
* title or words (a follow-up ↳, a suggestion marked), project.
|
|
26
|
+
* One task as `shard board` lists it: id, state (with why it waits to start, "queued (Getting its computer ready)", or
|
|
27
|
+
* what it does now, "working (Fixing the login cookie)"), title or words (a follow-up ↳, a suggestion marked), project.
|
|
22
28
|
*/
|
|
23
29
|
export function taskLine(t) {
|
|
24
|
-
const state = `${t.state}${
|
|
30
|
+
const state = `${t.state}${stateNote(t, 60)}`;
|
|
25
31
|
const label = `${t.parent_task_id ? '↳ ' : ''}${taskLabel(t)}${t.suggested ? ' (suggested)' : ''}`;
|
|
26
32
|
return `${t.id}\t${state}\t${label}\t${t.project_id}${t.home ? ' (home)' : ''}`;
|
|
27
33
|
}
|
|
28
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* One item that waits on you: its id, what is asked, then the command, the suggested answers or the branch behind it. A
|
|
36
|
+
* request your orchestrator made to your computer while it was offline reads "Waiting for your computer: list ~/code".
|
|
37
|
+
*/
|
|
29
38
|
export function needsLine(n) {
|
|
30
|
-
const detail = n.permission ? detailLine(n.permission.detail) : n.options?.length ? n.options.join(' / ') : n.land ? n.land.branch : '';
|
|
39
|
+
const detail = n.permission ? detailLine(n.permission.detail) : n.options?.length ? n.options.join(' / ') : n.land ? n.land.branch : n.device ? 'cancel drops it' : '';
|
|
31
40
|
return `${n.id}\t${oneLine(n.ask, 100)}${detail ? `\t${detail}` : ''}`;
|
|
32
41
|
}
|
|
33
42
|
/** A sealed report as --follow prints it: id, milestone, title, then its evidence labels. */
|
|
@@ -106,28 +115,30 @@ const evidenceText = (e) => {
|
|
|
106
115
|
const behind = e.detail ?? e.path;
|
|
107
116
|
return behind ? `${e.label} (${detailLine(behind, 60)})` : e.label;
|
|
108
117
|
};
|
|
109
|
-
/** `shard task show`: the task, its plan and conversations, then every report oldest first with its evidence.
|
|
118
|
+
/** `shard task show`: the task, its plan and conversations, then every report oldest first with its evidence. Every
|
|
119
|
+
* title, step and outcome prints whole (0.27.0+), however long the agent wrote it; the terminal wraps it. */
|
|
110
120
|
export function taskDetailText(d) {
|
|
111
121
|
const t = d.task;
|
|
122
|
+
const whole = (s) => oneLine(s, Infinity);
|
|
112
123
|
const titled = Boolean(t.title?.trim());
|
|
113
|
-
const lines = [titled ?
|
|
114
|
-
lines.push([`${t.state}${
|
|
124
|
+
const lines = [titled ? whole(t.title) : whole(t.text.split('\n').find((l) => l.trim()) ?? t.text)];
|
|
125
|
+
lines.push([`${t.state}${stateNote(t, 120)}`, t.bucket, `${t.project_id}${t.home ? ' (home)' : ''}`, ...(t.suggested ? ['suggested'] : []), ...(t.deleted_at ? ['deleted'] : t.cleared_at ? ['cleared'] : [])].join('\t'));
|
|
115
126
|
// Your words exactly, when the first line does not already show them whole.
|
|
116
127
|
if (titled || t.text.trim() !== lines[0])
|
|
117
128
|
lines.push('', t.text);
|
|
118
129
|
if (t.plan) {
|
|
119
130
|
lines.push('', `Plan: ${t.plan.summary}`);
|
|
120
131
|
for (const s of t.plan.steps)
|
|
121
|
-
lines.push(` ${s.id}\t${s.state}\t${oneLine(s.
|
|
132
|
+
lines.push(` ${s.id}\t${s.state}\t${whole(s.text)}${s.now?.trim() ? `\t(${oneLine(s.now, 80)})` : ''}${s.evidence.length ? `\t${s.evidence.join(', ')}` : ''}`);
|
|
122
133
|
}
|
|
123
134
|
if (t.conversations.length)
|
|
124
135
|
lines.push('', `Conversations: ${t.conversations.map((c) => `${c.id} (${c.role}${c.step_id ? ` ${c.step_id}` : ''})`).join(', ')}`);
|
|
125
136
|
if (d.reports.length) {
|
|
126
137
|
lines.push('', 'Reports');
|
|
127
138
|
for (const r of d.reports) {
|
|
128
|
-
lines.push(`${r.id}\t${r.milestone}\t${time(r.sealed_at)}\t${
|
|
139
|
+
lines.push(`${r.id}\t${r.milestone}\t${time(r.sealed_at)}\t${whole(r.title)}`);
|
|
129
140
|
if (r.outcome.trim())
|
|
130
|
-
lines.push(` ${
|
|
141
|
+
lines.push(` ${whole(r.outcome)}`);
|
|
131
142
|
if (r.options?.length)
|
|
132
143
|
lines.push(` Options: ${r.options.join(' / ')}`);
|
|
133
144
|
if (r.evidence.length)
|
|
@@ -155,13 +166,13 @@ function orderOf(values) {
|
|
|
155
166
|
return first ? null : after;
|
|
156
167
|
}
|
|
157
168
|
// ---- needs you -------------------------------------------------------------------------------
|
|
158
|
-
const KINDS = { perm: 'permission', cq: 'question', ask: 'question', cred: 'credential', land: 'land', prune: 'prune' };
|
|
159
|
-
/** An item's kind from its id (perm_, cq_, ask_, cred_, land_, prune_); undefined for another id. */
|
|
169
|
+
const KINDS = { perm: 'permission', cq: 'question', ask: 'question', oq: 'question', cred: 'credential', land: 'land', prune: 'prune', dev: 'device' };
|
|
170
|
+
/** An item's kind from its id (perm_, cq_, ask_, oq_, cred_, land_, prune_, dev_); undefined for another id. */
|
|
160
171
|
export function itemKind(id) {
|
|
161
172
|
return KINDS[id.slice(0, Math.max(0, id.indexOf('_')))];
|
|
162
173
|
}
|
|
163
|
-
/** The answer words: allow, always and deny (or the API's names), land, keep and
|
|
164
|
-
const WORDS = { allow: 'allow_once', allow_once: 'allow_once', always: 'allow_always', allow_always: 'allow_always', deny: 'deny', land: 'land', keep: 'keep', discard: 'discard' };
|
|
174
|
+
/** The answer words: allow, always and deny (or the API's names), land, keep, discard and cancel. */
|
|
175
|
+
const WORDS = { allow: 'allow_once', allow_once: 'allow_once', always: 'allow_always', allow_always: 'allow_always', deny: 'deny', land: 'land', keep: 'keep', discard: 'discard', cancel: 'cancel' };
|
|
165
176
|
/** Whether the answer needs the item itself: the questions of a permission request, every task of a prune prompt. */
|
|
166
177
|
export const answerNeedsItem = (id, a) => a.picks.length > 0 || (itemKind(id) === 'prune' && a.word?.toLowerCase() === 'keep');
|
|
167
178
|
/**
|
|
@@ -208,6 +219,11 @@ export function needsAnswer(id, a, item) {
|
|
|
208
219
|
if (decision !== 'deny')
|
|
209
220
|
throw new UsageError('Provide the value through the credential request, sealed for its computer; deny declines it here.');
|
|
210
221
|
return { decision };
|
|
222
|
+
case 'device':
|
|
223
|
+
only([], 'It runs when your computer is back (the Shardflux app open); cancel drops it.');
|
|
224
|
+
if (decision !== 'cancel')
|
|
225
|
+
throw new UsageError('It runs when your computer is back (the Shardflux app open); cancel drops it.');
|
|
226
|
+
return { decision };
|
|
211
227
|
case 'land':
|
|
212
228
|
only([], 'Choose land, keep or discard.');
|
|
213
229
|
if (decision !== 'land' && decision !== 'keep' && decision !== 'discard')
|
|
@@ -242,7 +258,7 @@ export function needsAnswer(id, a, item) {
|
|
|
242
258
|
}
|
|
243
259
|
}
|
|
244
260
|
}
|
|
245
|
-
const ANSWERED = { allow_once: 'Allowed once', allow_always: 'Always allowed', deny: 'Denied', land: 'Landing', keep: 'Kept', discard: 'Discarded' };
|
|
261
|
+
const ANSWERED = { allow_once: 'Allowed once', allow_always: 'Always allowed', deny: 'Denied', land: 'Landing', keep: 'Kept', discard: 'Discarded', cancel: 'Canceled' };
|
|
246
262
|
/** What an answer did, in one line. */
|
|
247
263
|
export function answeredText(r, a) {
|
|
248
264
|
const item = r.item;
|
|
@@ -332,6 +348,11 @@ export const BOARD_HANDLERS = {
|
|
|
332
348
|
out(ctx, `Steered: ${taskLabel(task)}\n`, { task });
|
|
333
349
|
return 0;
|
|
334
350
|
},
|
|
351
|
+
async 'task land'(ctx, values, [id]) {
|
|
352
|
+
const o = await landTask(landDeps(ctx), id, { path: str(values.path) });
|
|
353
|
+
out(ctx, landedText(o), o);
|
|
354
|
+
return 0;
|
|
355
|
+
},
|
|
335
356
|
async 'task clear-done'(ctx, values) {
|
|
336
357
|
const cleared = await boardClient(ctx).tasks.clearDone(projectScope(values), { signal: ctx.signal });
|
|
337
358
|
out(ctx, cleared.length ? `Cleared ${cleared.length} finished task${cleared.length === 1 ? '' : 's'}.\n` : 'No finished tasks to clear.\n', { cleared });
|
|
@@ -345,6 +366,12 @@ export const BOARD_HANDLERS = {
|
|
|
345
366
|
async 'needs-you answer'(ctx, values, [id, word]) {
|
|
346
367
|
const input = { word, text: str(values.text), picks: strs(values.pick), keep: strs(values.keep), everywhere: values.everywhere === true };
|
|
347
368
|
const cloud = boardClient(ctx);
|
|
369
|
+
// Land merges here (shard task land) and records itself after; the board only takes keep and discard.
|
|
370
|
+
if (itemKind(id) === 'land' && word?.toLowerCase() === 'land') {
|
|
371
|
+
const o = await landTask(landDeps(ctx), id.replace(/^land_/, ''), {});
|
|
372
|
+
out(ctx, landedText(o), o);
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
348
375
|
// Checked first, so a wrong combination is a usage error before any request.
|
|
349
376
|
let answer = needsAnswer(id, input);
|
|
350
377
|
if (answerNeedsItem(id, input)) {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your brain (0.27.0+, SDK 0.29.0): `shard brain ls|get`, read-only, through @shardflux/sdk's `brain` namespace. The
|
|
3
|
+
* signed-in session reads the person's brain in an organization (--org, else the saved one, else their only one);
|
|
4
|
+
* SHARDFLUX_API_KEY in the environment (or a saved key without a session) reads the brain of the member who created
|
|
5
|
+
* that key (person-scope.ts). Output is shaped like `shard drive ls|get`.
|
|
6
|
+
*/
|
|
7
|
+
import { writeFile } from 'node:fs/promises';
|
|
8
|
+
import { encodeBrainPath } from '@shardflux/sdk';
|
|
9
|
+
import { out, str } from "./commands.js";
|
|
10
|
+
import { driveTable } from "./drive-commands.js";
|
|
11
|
+
import { EXIT, UsageError } from "./errors.js";
|
|
12
|
+
import { humanBytes } from "./files-mirror.js";
|
|
13
|
+
import { json } from "./format.js";
|
|
14
|
+
import { inScope } from "./person-scope.js";
|
|
15
|
+
const SHA256 = /^[0-9a-f]{64}$/i;
|
|
16
|
+
const orgOnly = (scope) => (scope.organizationId ? { organizationId: scope.organizationId } : {});
|
|
17
|
+
/** A brain path checked as the SDK sends it: relative, no empty, `.` or `..` segments (exit 2 before any request). */
|
|
18
|
+
function brainPath(path) {
|
|
19
|
+
try {
|
|
20
|
+
encodeBrainPath(path);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
if (err instanceof TypeError)
|
|
24
|
+
throw new UsageError(`brain path: ${err.message}`);
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
return path;
|
|
28
|
+
}
|
|
29
|
+
export const BRAIN_HANDLERS = {
|
|
30
|
+
async 'brain ls'(ctx, values, [prefix]) {
|
|
31
|
+
const limitText = str(values.limit);
|
|
32
|
+
const limit = limitText === undefined ? undefined : Number(limitText);
|
|
33
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 1 || limit > 1000))
|
|
34
|
+
throw new UsageError('--limit must be 1 to 1000.');
|
|
35
|
+
const { rows, next, version } = await inScope(ctx, values, async (client, scope) => {
|
|
36
|
+
const rows = [];
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
let cursor = str(values.cursor), next, version;
|
|
39
|
+
do {
|
|
40
|
+
const page = await client.brain.list({ ...orgOnly(scope), ...(prefix ? { prefix } : {}), ...(limit ? { limit } : values.all === true ? { limit: 1000 } : {}), ...(cursor ? { cursor } : {}), signal: ctx.signal });
|
|
41
|
+
rows.push(...page.data);
|
|
42
|
+
next = page.nextCursor;
|
|
43
|
+
version = page.version;
|
|
44
|
+
cursor = next ?? undefined;
|
|
45
|
+
if (values.all === true && cursor) {
|
|
46
|
+
if (seen.has(cursor))
|
|
47
|
+
throw new UsageError('The brain listing repeated its cursor. Try again.');
|
|
48
|
+
seen.add(cursor);
|
|
49
|
+
}
|
|
50
|
+
} while (values.all === true && cursor);
|
|
51
|
+
return { rows, next, version };
|
|
52
|
+
});
|
|
53
|
+
out(ctx, rows.length ? driveTable(rows) : 'No files.\n', { data: rows, next_cursor: next, version });
|
|
54
|
+
if (!ctx.json && next)
|
|
55
|
+
ctx.io.stderr.write(`more: shard brain ls${prefix ? ` ${prefix}` : ''} --cursor ${next}\n`);
|
|
56
|
+
return EXIT.ok;
|
|
57
|
+
},
|
|
58
|
+
async 'brain get'(ctx, values, [path]) {
|
|
59
|
+
brainPath(path);
|
|
60
|
+
const sha256 = str(values.sha256)?.toLowerCase();
|
|
61
|
+
if (sha256 !== undefined && !SHA256.test(sha256))
|
|
62
|
+
throw new UsageError('--sha256 takes the 64 hex characters of a version’s SHA-256 (shard brain ls --json shows them).');
|
|
63
|
+
const file = await inScope(ctx, values, (client, scope) => client.brain.read(path, { ...orgOnly(scope), ...(sha256 ? { sha256 } : {}), signal: ctx.signal }));
|
|
64
|
+
const dest = str(values.out);
|
|
65
|
+
const meta = { path, sha256: file.sha256, size: file.size, content_type: file.contentType };
|
|
66
|
+
if (dest !== undefined) {
|
|
67
|
+
await writeFile(dest, file.bytes);
|
|
68
|
+
out(ctx, `Wrote ${path} to ${dest} (${humanBytes(file.size)}, sha256 ${file.sha256})\n`, { ...meta, out: dest });
|
|
69
|
+
}
|
|
70
|
+
else if (ctx.json) {
|
|
71
|
+
ctx.io.stdout.write(json({ ...meta, content_base64: Buffer.from(file.bytes).toString('base64') }));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
ctx.io.stdout.write(file.bytes);
|
|
75
|
+
}
|
|
76
|
+
return EXIT.ok;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -21,7 +21,7 @@ export declare function parseTitlePrefix(spec: string): string | null;
|
|
|
21
21
|
export declare function storedTitlePrefix(dir: string): string | undefined;
|
|
22
22
|
export declare function saveTitlePrefix(dir: string, choice: string | null): void;
|
|
23
23
|
/** `shard settings --json`. title_prefix: null is the default, "" none. open_in and terminal_app are the saved
|
|
24
|
-
* choices (null: automatic). */
|
|
24
|
+
* choices (null: automatic). auto_land (0.27.0+): finished cloud work lands in your folders on its own (default on). */
|
|
25
25
|
export declare function clientSettings(dir: string): {
|
|
26
26
|
title_prefix: string | null;
|
|
27
27
|
title_prefix_default: string;
|
|
@@ -34,6 +34,7 @@ export declare function clientSettings(dir: string): {
|
|
|
34
34
|
id: import("./terminal-apps.js").TerminalId;
|
|
35
35
|
mode: import("./terminal-apps.js").TerminalMode;
|
|
36
36
|
} | null;
|
|
37
|
+
auto_land: boolean;
|
|
37
38
|
};
|
|
38
39
|
export declare function settingsText(s: ReturnType<typeof clientSettings>): string;
|
|
39
40
|
export declare const SETTINGS_HANDLERS: Record<string, Handler>;
|
package/dist/client-settings.js
CHANGED
|
@@ -4,6 +4,7 @@ import { UsageError } from "./errors.js";
|
|
|
4
4
|
import { objectValue } from "./acp-rpc.js";
|
|
5
5
|
import { openInTargets } from "./t3-mode.js";
|
|
6
6
|
import { storedTerminal, terminalName } from "./terminal-apps.js";
|
|
7
|
+
import { TwinLedger } from "./worktree-twin.js";
|
|
7
8
|
/** The platform's marker (the guest's "☁ " without its space). */
|
|
8
9
|
export const DEFAULT_TITLE_PREFIX = '☁';
|
|
9
10
|
/** At most this many characters on one line, as the API takes it (POST /v1/conversations title_prefix). */
|
|
@@ -43,7 +44,7 @@ export function saveTitlePrefix(dir, choice) {
|
|
|
43
44
|
doc.title_prefix = choice; });
|
|
44
45
|
}
|
|
45
46
|
/** `shard settings --json`. title_prefix: null is the default, "" none. open_in and terminal_app are the saved
|
|
46
|
-
* choices (null: automatic). */
|
|
47
|
+
* choices (null: automatic). auto_land (0.27.0+): finished cloud work lands in your folders on its own (default on). */
|
|
47
48
|
export function clientSettings(dir) {
|
|
48
49
|
const doc = new AgentStateStore(dir).read();
|
|
49
50
|
const saved = objectValue(doc.open_in);
|
|
@@ -58,6 +59,7 @@ export function clientSettings(dir) {
|
|
|
58
59
|
title_prefix_default: DEFAULT_TITLE_PREFIX,
|
|
59
60
|
open_in: { claude: openIn('claude'), codex: openIn('codex') },
|
|
60
61
|
terminal_app: terminal ? { ...terminal, name: terminalName(terminal.id) } : null,
|
|
62
|
+
auto_land: new TwinLedger(dir).autoLand(),
|
|
61
63
|
};
|
|
62
64
|
}
|
|
63
65
|
const sampleTitle = (prefix) => `${prefix === null ? DEFAULT_TITLE_PREFIX + ' ' : prefix ? prefix + ' ' : ''}${SAMPLE}`;
|
|
@@ -68,11 +70,21 @@ export function settingsText(s) {
|
|
|
68
70
|
`Title marker ${markerLine(s.title_prefix)} "${sampleTitle(s.title_prefix)}"`,
|
|
69
71
|
`Open in Claude: ${target(s.open_in.claude)} · Codex: ${target(s.open_in.codex)}`,
|
|
70
72
|
`Terminal app ${s.terminal_app ? `${s.terminal_app.name}, ${s.terminal_app.mode === 'tab' ? 'new tab' : 'new window'}` : 'automatic'}`,
|
|
73
|
+
`Auto land ${s.auto_land ? 'on: finished cloud work lands in your folder' : 'off'}`,
|
|
71
74
|
'',
|
|
72
|
-
'Change the marker with shard settings --title-prefix <text|none|default
|
|
75
|
+
'Change the marker with shard settings --title-prefix <text|none|default>, auto land with --auto-land on|off; where conversations open with shard agents targets --open-in and --terminal.',
|
|
73
76
|
].join('\n') + '\n';
|
|
74
77
|
}
|
|
75
78
|
function settings(ctx, values) {
|
|
79
|
+
const land = values['auto-land'];
|
|
80
|
+
if (land !== undefined) {
|
|
81
|
+
if (land !== 'on' && land !== 'off')
|
|
82
|
+
throw new UsageError('--auto-land takes on or off.');
|
|
83
|
+
new TwinLedger(ctx.store.dir).setAutoLand(land === 'on');
|
|
84
|
+
const s = clientSettings(ctx.store.dir);
|
|
85
|
+
out(ctx, s.auto_land ? 'Finished cloud work lands in your folder automatically.\n' : 'Cloud work stays in the cloud until you land it (shard task land, shard conversations land).\n', s);
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
76
88
|
const spec = values['title-prefix'];
|
|
77
89
|
if (spec !== undefined && typeof spec !== 'string')
|
|
78
90
|
throw new UsageError('--title-prefix takes text, none or default.');
|
package/dist/codex-cloud.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ import type { InitialImage } from './initial-prompt.js';
|
|
|
8
8
|
/** Cloud threads open in the browser, preserving the durable provider destination on every OS. */
|
|
9
9
|
export declare function openCodexThread(task: CodexCloudTask, env: Ctx['io']['env'], platform?: NodeJS.Platform): Promise<void>;
|
|
10
10
|
export declare function submitCloudInitialPrompt(ctx: Ctx, c: Conversation, prompt: string, workspaceKey: string): Promise<void>;
|
|
11
|
+
/** Admit the already seeded executor and hand its first prompt to the durable inbox. */
|
|
12
|
+
export declare function startCloudTask(ctx: Ctx, c: Conversation, workspaceKey: string, prompt?: string): Promise<CodexCloudTask>;
|
|
11
13
|
/** Prints the Cloud thread (and its link) and opens it in your browser; --json prints it instead. */
|
|
12
14
|
export declare function printCloudThread(ctx: Ctx, c: Conversation, task: CodexCloudTask, submitted?: boolean, choice?: ModelChoice): Promise<void>;
|
|
13
15
|
/**
|
package/dist/codex-cloud.js
CHANGED
|
@@ -5,7 +5,7 @@ import { CodedError, UsageError } from "./errors.js";
|
|
|
5
5
|
import { json } from "./format.js";
|
|
6
6
|
import { syncAgentCredentials } from "./agent-credentials.js";
|
|
7
7
|
import { createConversation, conversationWorkspace, getConversation, localConversations, recordedChoice, resumeConversation, seedConversation } from "./conversations.js";
|
|
8
|
-
import { CHOICE_PENDING, choiceReport, hasChoice, modelChoice } from "./agent-models.js";
|
|
8
|
+
import { CHOICE_PENDING, choiceReport, hasChoice, liveModels, modelChoice } from "./agent-models.js";
|
|
9
9
|
import { AgentStateStore } from "./agent-state.js";
|
|
10
10
|
import { installedTargets } from "./agent-targets.js";
|
|
11
11
|
import { ensureProjectGit } from "./project-git.js";
|
|
@@ -39,6 +39,15 @@ export async function submitCloudInitialPrompt(ctx, c, prompt, workspaceKey) {
|
|
|
39
39
|
throw new CodedError('initial_prompt_failed', `Created ${workspaceKey}, but the initial prompt could not be confirmed. Open that conversation before starting another.`, undefined, { workspace_key: workspaceKey, conversation_id: c.id, submitted: null });
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
/** Admit the already seeded executor and hand its first prompt to the durable inbox. */
|
|
43
|
+
export async function startCloudTask(ctx, c, workspaceKey, prompt) {
|
|
44
|
+
await ctx.cloud().codex.admit(c.id, { idempotencyKey: `codex-${c.id}`, signal: ctx.signal });
|
|
45
|
+
// The durable inbox holds this input until the executor is active. Setup
|
|
46
|
+
// continues while the prompt is queued; a lost send retains the stable key.
|
|
47
|
+
if (prompt !== undefined)
|
|
48
|
+
await submitCloudInitialPrompt(ctx, c, prompt, workspaceKey);
|
|
49
|
+
return ctx.cloud().codex.waitTask(c.id, { timeoutMs: ctx.wakeTimeoutMs, signal: ctx.signal });
|
|
50
|
+
}
|
|
42
51
|
/** Prints the Cloud thread (and its link) and opens it in your browser; --json prints it instead. */
|
|
43
52
|
export async function printCloudThread(ctx, c, task, submitted = false, choice) { return output(ctx, c, task, submitted, choice); }
|
|
44
53
|
/** Whether this computer has the Codex app, which opens a Cloud thread's codex:// link (on Linux 0.23.0+). */
|
|
@@ -215,7 +224,7 @@ export async function launchCodexCloud(ctx, values, path, images = []) {
|
|
|
215
224
|
throw new UsageError('New Codex conversations open in Codex Cloud. Omit --in or use --in codex-app.');
|
|
216
225
|
if (values.like !== undefined)
|
|
217
226
|
throw new UsageError('Choose the project directory with --path; new Codex conversations use its latest machine template.');
|
|
218
|
-
const choice = modelChoice(values, 'codex');
|
|
227
|
+
const choice = modelChoice(values, 'codex', await liveModels(ctx, values, 'codex'));
|
|
219
228
|
const project = projectIdentity(path);
|
|
220
229
|
await ensureProjectGit(ctx, project.folder);
|
|
221
230
|
await gitRunner(project.folder, { ...process.env, ...ctx.io.env }, ctx.signal)(['rev-parse', 'HEAD']);
|
|
@@ -247,12 +256,9 @@ export async function launchCodexCloud(ctx, values, path, images = []) {
|
|
|
247
256
|
// --computer: the conversation VM's desktop is on before Codex starts, so the thread gets the computer tools.
|
|
248
257
|
// The thread's tools run in this VM, so its first message can name the images by their VM paths.
|
|
249
258
|
// Idempotency follows the durable conversation, including a lost response.
|
|
250
|
-
await ctx.cloud().codex.admit(c.id, { idempotencyKey: `codex-${c.id}`, signal: ctx.signal });
|
|
251
|
-
const task = await ctx.cloud().codex.waitTask(c.id, { timeoutMs: ctx.wakeTimeoutMs, signal: ctx.signal });
|
|
252
|
-
new AgentStateStore(ctx.store.dir).save(project.remote ?? project.folder, 'codex');
|
|
253
259
|
const submitted = typeof values.initialPrompt === 'string';
|
|
254
|
-
|
|
255
|
-
|
|
260
|
+
const task = await startCloudTask(ctx, c, created.workspace.key, submitted ? values.initialPrompt : undefined);
|
|
261
|
+
new AgentStateStore(ctx.store.dir).save(project.remote ?? project.folder, 'codex');
|
|
256
262
|
patch(c.id, { state: 'active', submitted, prompt: undefined });
|
|
257
263
|
await output(ctx, c, task, submitted, choice);
|
|
258
264
|
return 0;
|
|
@@ -325,10 +331,7 @@ export async function resumeCloudStartup(ctx, c) {
|
|
|
325
331
|
const ws = await conversationWorkspace(ctx, c);
|
|
326
332
|
// The same seed the start began: its own captured revision (never a newer checkout), or the copy seed.
|
|
327
333
|
await seedConversation(ctx, c, ws, local.folder);
|
|
328
|
-
await ctx
|
|
329
|
-
const task = await ctx.cloud().codex.waitTask(c.id, { timeoutMs: ctx.wakeTimeoutMs, signal: ctx.signal });
|
|
330
|
-
if (start.prompt !== undefined && !start.submitted)
|
|
331
|
-
await submitCloudInitialPrompt(ctx, c, start.prompt, ws.key);
|
|
334
|
+
const task = await startCloudTask(ctx, c, ws.key, start.submitted ? undefined : start.prompt);
|
|
332
335
|
new AgentStateStore(ctx.store.dir).update(doc => {
|
|
333
336
|
const records = doc.conversations;
|
|
334
337
|
if (records[c.id]?.cloud_start)
|
package/dist/commands.js
CHANGED
|
@@ -1425,6 +1425,14 @@ export const KEY_HANDLERS = {
|
|
|
1425
1425
|
out(ctx, `wrote ${r.bytes_written} bytes to ${r.path}${r.sha256 ? ` (sha256 ${r.sha256})` : ''}${tree !== null ? `, tree revision ${tree}` : ''}\n`, tree !== null ? { ...r, tree_revision: tree } : r);
|
|
1426
1426
|
return 0;
|
|
1427
1427
|
},
|
|
1428
|
+
async 'files delete'(ctx, values, [ref, path]) {
|
|
1429
|
+
const ws = await resolveWorkspace(ctx, ref);
|
|
1430
|
+
const ifRev = ifRevision(values);
|
|
1431
|
+
fileFirstOnly(ws, ifRev !== undefined ? ['--if-revision'] : []);
|
|
1432
|
+
await cellOf(ctx, ws).files.remove(path, { recursive: values.recursive === true, ...(ifRev !== undefined ? { ifTreeRevision: ifRev } : {}) });
|
|
1433
|
+
out(ctx, `deleted ${path}\n`, { path, removed: true });
|
|
1434
|
+
return 0;
|
|
1435
|
+
},
|
|
1428
1436
|
async 'files ls'(ctx, values, [ref, path]) {
|
|
1429
1437
|
const limitText = str(values.limit);
|
|
1430
1438
|
const ws = await resolveWorkspace(ctx, ref);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Ctx, Handler } from './commands.js';
|
|
2
|
-
import type { CellClient, ConversationPermission, ListeningPort, MessageAttachment, PermissionDecision } from '@shardflux/sdk';
|
|
2
|
+
import type { CellClient, ConversationPermission, ListeningPort, MessageAttachment, MessageDelivery, PermissionDecision } from '@shardflux/sdk';
|
|
3
3
|
export declare function conversationClient(ctx: Ctx): import("@shardflux/sdk").Shardflux | import("@shardflux/sdk").ShardfluxAccount;
|
|
4
4
|
/** Why a conversation needs you, as one line of at most `max` characters; undefined in other states (0.18.0+). */
|
|
5
5
|
export declare function questionLine(c: {
|
|
@@ -17,6 +17,10 @@ export declare function displayTitle(c: {
|
|
|
17
17
|
* keeps its file name in the conversation's computer.
|
|
18
18
|
*/
|
|
19
19
|
export declare function readAttachments(paths: readonly string[]): Promise<MessageAttachment[]>;
|
|
20
|
+
/** How long `conversations send --wait` waits for the agent to have the message (0.27.0+). */
|
|
21
|
+
export declare const SEND_WAIT_MS = 60000;
|
|
22
|
+
/** What happened to a sent message, as `conversations send --wait` prints it (0.27.0+). */
|
|
23
|
+
export declare function deliveryLine(d: MessageDelivery): string;
|
|
20
24
|
/** A permission request's command, path or URL as one line of at most `max` characters: its first line (0.25.0+). */
|
|
21
25
|
export declare function detailLine(detail: string, max?: number): string;
|
|
22
26
|
/** One permission request as a list row (0.25.0+): id, state, title, detail, and the decision once answered. */
|
|
@@ -2,13 +2,14 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { open } from 'node:fs/promises';
|
|
3
3
|
import { basename } from 'node:path';
|
|
4
4
|
import { out, str, strs } from "./commands.js";
|
|
5
|
-
import { CodedError, UsageError } from "./errors.js";
|
|
6
|
-
import { MAX_ATTACHMENT_BYTES, MAX_ATTACHMENTS, PtyConnection, planAttachments } from '@shardflux/sdk';
|
|
5
|
+
import { CodedError, EXIT, UsageError } from "./errors.js";
|
|
6
|
+
import { MAX_ATTACHMENT_BYTES, MAX_ATTACHMENTS, MAX_MARK_IDS, PtyConnection, planAttachments } from '@shardflux/sdk';
|
|
7
7
|
import { FilesMirror, humanBytes, listMirrorConversations, workspaceHub } from "./files-mirror.js";
|
|
8
|
-
import { modelChoice } from "./agent-models.js";
|
|
8
|
+
import { liveModels, modelChoice } from "./agent-models.js";
|
|
9
9
|
import { storedTitlePrefix } from "./client-settings.js";
|
|
10
10
|
import { PtyOutputCursor, passiveUnavailable, sleepingAttach } from "./agent-terminal.js";
|
|
11
11
|
import { progress } from "./progress.js";
|
|
12
|
+
import { landConversation, landDeps, landedText } from "./land.js";
|
|
12
13
|
import { UUID } from "./args.js";
|
|
13
14
|
import { findOrg } from "./account-scope.js";
|
|
14
15
|
export function conversationClient(ctx) {
|
|
@@ -82,6 +83,16 @@ function checkMessage(text, files) {
|
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
const sentLines = (r) => `${r.restored ? 'Restored\n' : ''}${(r.files ?? []).map(f => `Attached ${f.path}\n`).join('')}Message sent (${r.seq}).\n`;
|
|
86
|
+
/** How long `conversations send --wait` waits for the agent to have the message (0.27.0+). */
|
|
87
|
+
export const SEND_WAIT_MS = 60_000;
|
|
88
|
+
/** What happened to a sent message, as `conversations send --wait` prints it (0.27.0+). */
|
|
89
|
+
export function deliveryLine(d) {
|
|
90
|
+
if (d.state === 'pending')
|
|
91
|
+
return 'Not delivered yet.';
|
|
92
|
+
if (d.state === 'uncertain')
|
|
93
|
+
return 'Delivery not confirmed yet: check the conversation before sending it again.';
|
|
94
|
+
return d.steered === true ? 'Steered into the running turn.' : d.steered === false ? 'Started the next turn.' : 'Delivered to the agent.';
|
|
95
|
+
}
|
|
85
96
|
/** A permission request's command, path or URL as one line of at most `max` characters: its first line (0.25.0+). */
|
|
86
97
|
export function detailLine(detail, max = 80) {
|
|
87
98
|
const line = detail.split('\n').find(l => l.trim())?.replace(/\s+/g, ' ').trim() ?? '';
|
|
@@ -190,6 +201,29 @@ export async function watchTerminal(ctx, cell, ptyId) {
|
|
|
190
201
|
ctx.io.stdout.write('\x1b[0m\x1b[?25h\r\n');
|
|
191
202
|
}
|
|
192
203
|
}
|
|
204
|
+
/** A conversation id as the marks commands take it: conv_ and 26 letters a-z and digits 2-7 (0.28.0+). */
|
|
205
|
+
const CONVERSATION_ID = /^conv_[a-z2-7]{26}$/;
|
|
206
|
+
/** What `conversations archive`, `unarchive` and `seen` print before each conversation they marked (0.28.0+). */
|
|
207
|
+
const MARKED = { archive: 'Archived', unarchive: 'Unarchived', seen: 'Marked seen' };
|
|
208
|
+
/**
|
|
209
|
+
* Your own marks (0.28.0+, SDK 0.30.0), the same on every device: every id in one request, each once, checked before
|
|
210
|
+
* it is sent. Prints one line per conversation marked and, on stderr, "Not found: <id>" for each one skipped; --json
|
|
211
|
+
* prints the API's answer, {data, skipped}. Exit 3 when none was marked and some were skipped (not found), else 0.
|
|
212
|
+
*/
|
|
213
|
+
async function markConversations(ctx, kind, ids) {
|
|
214
|
+
const unique = [...new Set(ids)], invalid = unique.filter(id => !CONVERSATION_ID.test(id));
|
|
215
|
+
if (invalid.length)
|
|
216
|
+
throw new UsageError(`Not a conversation id: ${invalid.join(', ')}`, 'conversation ids look like conv_k3m5q7s2u4w6y3b5d7f2h4j6ln (shard conversations list shows them)');
|
|
217
|
+
if (unique.length > MAX_MARK_IDS)
|
|
218
|
+
throw new UsageError(`${unique.length} conversations named: one command takes up to ${MAX_MARK_IDS}.`, `run it again for the rest, ${MAX_MARK_IDS} at a time`);
|
|
219
|
+
const client = conversationClient(ctx), o = { signal: ctx.signal };
|
|
220
|
+
const r = kind === 'archive' ? await client.conversations.archive(unique, o) : kind === 'unarchive' ? await client.conversations.unarchive(unique, o) : await client.conversations.markSeen(unique, o);
|
|
221
|
+
out(ctx, r.data.map(c => `${MARKED[kind]} ${c.id}\t${displayTitle(c)}\n`).join(''), r);
|
|
222
|
+
if (!ctx.json)
|
|
223
|
+
for (const id of r.skipped)
|
|
224
|
+
ctx.io.stderr.write(`Not found: ${id}\n`);
|
|
225
|
+
return !r.data.length && r.skipped.length ? EXIT.notFound : EXIT.ok;
|
|
226
|
+
}
|
|
193
227
|
export const CONVERSATION_HANDLERS = {
|
|
194
228
|
async 'conversations resume'(ctx, _values, [id]) { const r = await conversationClient(ctx).conversations.resume(id, { signal: ctx.signal }); out(ctx, `${displayTitle(r.conversation)}: resumed.\n`, r); return 0; },
|
|
195
229
|
async 'conversations reader'(ctx, _values, [id]) { const r = await conversationClient(ctx).conversations.spaceReader(id, { signal: ctx.signal }); out(ctx, `${r.workspace.workspace_key}\n`, r); return 0; },
|
|
@@ -224,8 +258,9 @@ export const CONVERSATION_HANDLERS = {
|
|
|
224
258
|
const { client, organizationId } = await listScope(ctx, values);
|
|
225
259
|
let cursor = str(values.cursor);
|
|
226
260
|
for await (const c of client.conversations.watch({ projectId: str(values.project), organizationId, cursor, signal: ctx.signal, onCursor: next => { cursor = next; } })) {
|
|
227
|
-
|
|
228
|
-
|
|
261
|
+
// A deleted conversation comes once, its content cleared: its line says deleted (0.25.0+).
|
|
262
|
+
const q = questionLine(c), line = c.deleted_at ? `${c.id}\tDeleted conversation\tdeleted` : `${c.id}\t${displayTitle(c)}\t${c.state}${q ? `\t${q}` : ''}`;
|
|
263
|
+
ctx.io.stdout.write(ctx.json ? JSON.stringify({ conversation: c, cursor }) + '\n' : `${line}\n`);
|
|
229
264
|
}
|
|
230
265
|
return 0;
|
|
231
266
|
},
|
|
@@ -309,8 +344,23 @@ export const CONVERSATION_HANDLERS = {
|
|
|
309
344
|
throw new UsageError('missing <text> for "shard conversations send"');
|
|
310
345
|
if (files.length)
|
|
311
346
|
checkMessage(text ?? '', files);
|
|
312
|
-
const
|
|
313
|
-
|
|
347
|
+
const client = conversationClient(ctx), r = await client.conversations.send(id, text ?? '', { signal: ctx.signal, ...(files.length ? { files } : {}) });
|
|
348
|
+
if (values.wait !== true) {
|
|
349
|
+
out(ctx, sentLines(r), r);
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
// --wait (0.27.0+): a working agent takes it into its running turn, an idle one starts its next turn with it. The
|
|
353
|
+
// message is sent either way, so a delivery that cannot be read is said on stderr and the command still succeeds.
|
|
354
|
+
let delivery = null;
|
|
355
|
+
try {
|
|
356
|
+
delivery = await client.conversations.waitForDelivery(id, r.seq, { timeoutMs: SEND_WAIT_MS, signal: ctx.signal });
|
|
357
|
+
}
|
|
358
|
+
catch (e) {
|
|
359
|
+
if (ctx.signal.aborted)
|
|
360
|
+
throw e;
|
|
361
|
+
progress(ctx, `Where the message stands could not be read: ${e instanceof Error ? e.message : String(e)}`);
|
|
362
|
+
}
|
|
363
|
+
out(ctx, `${sentLines(r)}${delivery ? `${deliveryLine(delivery)}\n` : ''}`, { ...r, delivery });
|
|
314
364
|
return 0;
|
|
315
365
|
},
|
|
316
366
|
/**
|
|
@@ -324,7 +374,7 @@ export const CONVERSATION_HANDLERS = {
|
|
|
324
374
|
const app = str(values.app) ?? 'claude';
|
|
325
375
|
if (app !== 'claude' && app !== 'codex')
|
|
326
376
|
throw new UsageError('Choose --app claude or codex.');
|
|
327
|
-
const choice = modelChoice(values, app);
|
|
377
|
+
const choice = modelChoice(values, app, await liveModels(ctx, values, app));
|
|
328
378
|
if (prompt !== undefined && !prompt.trim())
|
|
329
379
|
throw new UsageError('--prompt needs text.');
|
|
330
380
|
const files = await readAttachments(strs(values.file));
|
|
@@ -360,6 +410,12 @@ export const CONVERSATION_HANDLERS = {
|
|
|
360
410
|
async 'conversations land'(ctx, values, [id, action]) {
|
|
361
411
|
if (!['merge', 'pr', 'keep', 'discard'].includes(action))
|
|
362
412
|
throw new UsageError('Choose merge, pr, keep or discard.');
|
|
413
|
+
// merge lands for real (land.ts) and records only after the commit is in the branch; the others record a decision.
|
|
414
|
+
if (action === 'merge') {
|
|
415
|
+
const o = await landConversation(landDeps(ctx), id, { path: str(values.path) });
|
|
416
|
+
out(ctx, landedText(o), o);
|
|
417
|
+
return 0;
|
|
418
|
+
}
|
|
363
419
|
const r = await conversationClient(ctx).conversations.land(id, action, { into: str(values.into), signal: ctx.signal });
|
|
364
420
|
out(ctx, `${displayTitle(r)}: ${r.landed.how}\n`, r);
|
|
365
421
|
return 0;
|
|
@@ -370,4 +426,16 @@ export const CONVERSATION_HANDLERS = {
|
|
|
370
426
|
return 0;
|
|
371
427
|
},
|
|
372
428
|
async 'conversations move'(ctx, _values, [id, to]) { const r = await conversationClient(ctx).conversations.move(id, to, { signal: ctx.signal }); out(ctx, `Move scheduled for the end of the turn.\n`, r); return 0; },
|
|
429
|
+
// Your inbox, the same on every device (0.28.0+): archive, unarchive and seen, all ids in one request.
|
|
430
|
+
async 'conversations archive'(ctx, _values, ids) { return markConversations(ctx, 'archive', ids); },
|
|
431
|
+
async 'conversations unarchive'(ctx, _values, ids) { return markConversations(ctx, 'unarchive', ids); },
|
|
432
|
+
async 'conversations seen'(ctx, _values, ids) { return markConversations(ctx, 'seen', ids); },
|
|
433
|
+
// Everywhere at once, its computer included (0.25.0+); refused without --yes, as every deletion is. --json: the API's answer.
|
|
434
|
+
async 'conversations delete'(ctx, values, [id]) {
|
|
435
|
+
if (values.yes !== true)
|
|
436
|
+
throw new UsageError('refusing to delete without --yes (the conversation, its computer, messages, transcript and branch are deleted everywhere)');
|
|
437
|
+
const r = await conversationClient(ctx).conversations.delete(id, { signal: ctx.signal });
|
|
438
|
+
out(ctx, `Deleted: ${r.id}\n`, r);
|
|
439
|
+
return 0;
|
|
440
|
+
},
|
|
373
441
|
};
|
|
@@ -5,7 +5,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
5
5
|
import { AgentStateStore } from "./agent-state.js";
|
|
6
6
|
import { projectIdentity, projectLabels } from "./project-identity.js";
|
|
7
7
|
import { createConversation, conversationName, conversationWorkspace, conversationsUnavailable, listConversations, localConversations, recordedChoice, rememberConversation, resumeConversation, restoreConversationBinding } from "./conversations.js";
|
|
8
|
-
import { modelChoice } from "./agent-models.js";
|
|
8
|
+
import { liveModels, modelChoice } from "./agent-models.js";
|
|
9
9
|
import { terminal } from "./agent-commands.js";
|
|
10
10
|
import { cloudClaudeLogin, loginClaudeInWorkspace, requireClaudeTerminal } from "./claude-cloud-login.js";
|
|
11
11
|
import { syncAgentCredentials } from "./agent-credentials.js";
|
|
@@ -138,7 +138,7 @@ async function startConversation(ctx, values, [path = './', explicit], openIn) {
|
|
|
138
138
|
if (explicit && explicit !== app)
|
|
139
139
|
throw new UsageError('Choose claude or codex.');
|
|
140
140
|
// Checked before a VM is allocated; the new conversation's record carries it to its guest (0.17.0+).
|
|
141
|
-
const choice = modelChoice(values, app);
|
|
141
|
+
const choice = modelChoice(values, app, await liveModels(ctx, values, app));
|
|
142
142
|
if (values.resume === true) {
|
|
143
143
|
// The native pickers are VM-local. C-K6's project picker must select the binding first.
|
|
144
144
|
const records = localConversations(ctx);
|
package/dist/conversations.d.ts
CHANGED
|
@@ -17,7 +17,8 @@ export interface Conversation {
|
|
|
17
17
|
worktree_path: string;
|
|
18
18
|
branch: string;
|
|
19
19
|
base_commit: string;
|
|
20
|
-
|
|
20
|
+
/** `deleted`: the one feed delivery of a deleted conversation (0.25.0+). */
|
|
21
|
+
state: 'working' | 'needs_you' | 'unresolved' | 'done' | 'archived' | 'deleted';
|
|
21
22
|
created_at: string;
|
|
22
23
|
ended_at: string | null;
|
|
23
24
|
landed?: {
|
package/dist/desktop-linux.d.ts
CHANGED
|
@@ -10,6 +10,17 @@ export interface DesktopStatus {
|
|
|
10
10
|
}
|
|
11
11
|
export declare function linuxDesktopPackage(arch?: NodeJS.Architecture): string | null;
|
|
12
12
|
export declare function graphicalSession(env: NodeJS.ProcessEnv): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Whether `pid` runs the app installed at `app`: the GTK tray app (shardflux-desktop) or the one Shardflux app
|
|
15
|
+
* (shardflux-desktop-bin behind its launcher). By /proc/<pid>/exe, because Chromium rewrites an Electron process's
|
|
16
|
+
* cmdline into one space-joined title; a recycled PID runs something else, and a zombie has exited.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isAppProcess(pid: number, app: string, proc?: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The libraries ldd could not find, named as the loader names them: the app in the package decides which it needs (the
|
|
21
|
+
* GTK tray app's WebKit and app indicator, the one app's GTK, NSS and ALSA).
|
|
22
|
+
*/
|
|
23
|
+
export declare function missingLibrariesMessage(ldd: string): string;
|
|
13
24
|
export declare class LinuxDesktopManager {
|
|
14
25
|
#private;
|
|
15
26
|
readonly home: string;
|
|
@@ -20,7 +31,7 @@ export declare class LinuxDesktopManager {
|
|
|
20
31
|
env?: NodeJS.ProcessEnv;
|
|
21
32
|
exec?: typeof exec;
|
|
22
33
|
spawn?: typeof spawn;
|
|
23
|
-
isProcess?: (pid: number,
|
|
34
|
+
isProcess?: (pid: number, app: string) => boolean;
|
|
24
35
|
});
|
|
25
36
|
status(dir: string): Promise<DesktopStatus>;
|
|
26
37
|
packagedVersion(): string | null;
|