@shardflux/cli 0.15.2 → 0.17.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 +144 -14
- package/README.md +92 -7
- package/dist/acp-models.js +5 -1
- package/dist/agent-commands.d.ts +15 -1
- package/dist/agent-commands.js +84 -23
- package/dist/agent-models.d.ts +61 -0
- package/dist/agent-models.js +79 -0
- package/dist/agent-state.d.ts +10 -0
- package/dist/agent-state.js +13 -0
- package/dist/agent-target-commands.d.ts +13 -3
- package/dist/agent-target-commands.js +129 -23
- package/dist/agent-targets.d.ts +5 -0
- package/dist/agent-targets.js +14 -5
- package/dist/agent-terminal.d.ts +8 -0
- package/dist/agent-terminal.js +35 -2
- package/dist/args.d.ts +5 -1
- package/dist/args.js +74 -11
- package/dist/cli-proxy-forward.d.ts +45 -0
- package/dist/cli-proxy-forward.js +432 -0
- package/dist/cli-proxy-hub.d.ts +273 -0
- package/dist/cli-proxy-hub.js +749 -0
- package/dist/cli-proxy.d.ts +325 -0
- package/dist/cli-proxy.js +1016 -0
- package/dist/codex-cloud.d.ts +3 -0
- package/dist/codex-cloud.js +17 -5
- package/dist/command-center-actions.js +25 -1
- package/dist/commands.d.ts +22 -0
- package/dist/commands.js +54 -14
- package/dist/conversation-commands.js +6 -0
- package/dist/conversation-local.js +2 -2
- package/dist/conversation-stash.js +4 -3
- package/dist/conversation-terminal.d.ts +5 -0
- package/dist/conversation-terminal.js +54 -6
- package/dist/conversations.d.ts +31 -3
- package/dist/conversations.js +53 -10
- package/dist/device-approvals.d.ts +12 -0
- package/dist/device-approvals.js +73 -0
- package/dist/device-helper.d.ts +3 -1
- package/dist/device-helper.js +14 -4
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +12 -2
- package/dist/files-mirror.d.ts +164 -0
- package/dist/files-mirror.js +761 -0
- package/dist/handlers.js +5 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/linux-helper.d.ts +12 -0
- package/dist/linux-helper.js +88 -0
- package/dist/linux-terminal.d.ts +11 -0
- package/dist/linux-terminal.js +42 -0
- package/dist/main.js +38 -8
- package/dist/onboarding.d.ts +2 -0
- package/dist/onboarding.js +53 -5
- package/dist/registry.d.ts +2 -42
- package/dist/registry.js +6 -90
- package/dist/t3-launch.d.ts +37 -0
- package/dist/t3-launch.js +133 -0
- package/dist/t3-mode.d.ts +121 -0
- package/dist/t3-mode.js +654 -0
- package/dist/t3-phone.d.ts +17 -0
- package/dist/t3-phone.js +15 -0
- package/dist/t3-thread-script.d.ts +18 -0
- package/dist/t3-thread-script.js +245 -0
- package/dist/terminal-onboarding.d.ts +11 -0
- package/dist/terminal-onboarding.js +75 -0
- package/dist/thread-on-computer.d.ts +35 -0
- package/dist/thread-on-computer.js +206 -0
- package/dist/update.d.ts +1 -1
- package/dist/update.js +12 -1
- package/dist/wait-status.d.ts +22 -0
- package/dist/wait-status.js +59 -0
- package/dist/worktree-twin-engine.d.ts +125 -0
- package/dist/worktree-twin-engine.js +615 -0
- package/dist/worktree-twin-land.py +331 -0
- package/dist/worktree-twin.d.ts +313 -0
- package/dist/worktree-twin.js +843 -0
- package/package.json +3 -3
package/dist/codex-cloud.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import type { CodexCloudTask } from '@shardflux/sdk';
|
|
2
2
|
import type { Ctx, Handler } from './commands.js';
|
|
3
3
|
import type { Values } from './args.js';
|
|
4
|
+
import type { ModelChoice } from './agent-models.js';
|
|
4
5
|
import type { Conversation } from './conversations.js';
|
|
5
6
|
import type { InitialImage } from './initial-prompt.js';
|
|
6
7
|
/** Only provider-returned task ids enter the registered Codex deep-link route. */
|
|
7
8
|
export declare function openCodexThread(task: CodexCloudTask, env: Ctx['io']['env']): Promise<void>;
|
|
8
9
|
export declare function submitCloudInitialPrompt(ctx: Ctx, c: Conversation, prompt: string, workspaceKey: string): Promise<void>;
|
|
10
|
+
/** Prints the Cloud thread (and its link) and opens it in the Codex app; --json prints it instead. */
|
|
11
|
+
export declare function printCloudThread(ctx: Ctx, c: Conversation, task: CodexCloudTask, submitted?: boolean, choice?: ModelChoice): Promise<void>;
|
|
9
12
|
/** How long the account's Codex remote device may take to come up before it counts as stuck. */
|
|
10
13
|
export declare const CODEX_STARTING_LIMIT_MS: number;
|
|
11
14
|
export type CodexConnectorView = {
|
package/dist/codex-cloud.js
CHANGED
|
@@ -2,7 +2,8 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { CodedError, UsageError } from "./errors.js";
|
|
3
3
|
import { json } from "./format.js";
|
|
4
4
|
import { syncAgentCredentials } from "./agent-credentials.js";
|
|
5
|
-
import { createConversation, resumeConversation } from "./conversations.js";
|
|
5
|
+
import { createConversation, getConversation, recordedChoice, resumeConversation } from "./conversations.js";
|
|
6
|
+
import { CHOICE_PENDING, choiceReport, hasChoice, modelChoice } from "./agent-models.js";
|
|
6
7
|
import { AgentStateStore } from "./agent-state.js";
|
|
7
8
|
import { ensureProjectGit } from "./project-git.js";
|
|
8
9
|
import { gitRunner } from "./agent-git.js";
|
|
@@ -32,15 +33,25 @@ export async function submitCloudInitialPrompt(ctx, c, prompt, workspaceKey) {
|
|
|
32
33
|
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 });
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
/** Prints the Cloud thread (and its link) and opens it in the Codex app; --json prints it instead. */
|
|
37
|
+
export async function printCloudThread(ctx, c, task, submitted = false, choice) { return output(ctx, c, task, submitted, choice); }
|
|
38
|
+
async function output(ctx, c, task, submitted = false, choice) {
|
|
36
39
|
const url = threadLink(task);
|
|
40
|
+
// model_choice (0.17.0+): an admitted thread's record keeps the model and effort only when Cloud started it with them.
|
|
41
|
+
const applied = hasChoice(choice) ? await cloudChoiceApplied(ctx, c, choice) : true;
|
|
37
42
|
if (ctx.json) {
|
|
38
|
-
ctx.io.stdout.write(json({ conversation: c, cloud: task, url, ...(submitted ? { initial_prompt: { submitted: true } } : {}) }));
|
|
43
|
+
ctx.io.stdout.write(json({ conversation: c, cloud: task, url, ...(submitted ? { initial_prompt: { submitted: true } } : {}), ...(hasChoice(choice) ? { model_choice: choiceReport(choice, applied) } : {}) }));
|
|
39
44
|
return;
|
|
40
45
|
}
|
|
46
|
+
if (!applied)
|
|
47
|
+
ctx.io.stderr.write(`${CHOICE_PENDING}\n`);
|
|
41
48
|
ctx.io.stdout.write(`${c.key}\n${task.desktop_url}\n`);
|
|
42
49
|
await openCodexThread(task, ctx.io.env);
|
|
43
50
|
}
|
|
51
|
+
async function cloudChoiceApplied(ctx, c, choice) {
|
|
52
|
+
const kept = recordedChoice(await getConversation(ctx, c.id));
|
|
53
|
+
return kept.model === choice.model && kept.effort === choice.effort;
|
|
54
|
+
}
|
|
44
55
|
/** How long the account's Codex remote device may take to come up before it counts as stuck. */
|
|
45
56
|
export const CODEX_STARTING_LIMIT_MS = 5 * 60_000;
|
|
46
57
|
/** When a connector generation started: its UUIDv7 time. */
|
|
@@ -112,11 +123,12 @@ export async function launchCodexCloud(ctx, values, path, images = []) {
|
|
|
112
123
|
throw new UsageError('New Codex conversations open in Codex Cloud. Omit --in or use --in codex-app.');
|
|
113
124
|
if (values.like !== undefined)
|
|
114
125
|
throw new UsageError('Choose the project directory with --path; new Codex conversations use its latest machine template.');
|
|
126
|
+
const choice = modelChoice(values, 'codex');
|
|
115
127
|
const project = projectIdentity(path);
|
|
116
128
|
await ensureProjectGit(ctx, project.folder);
|
|
117
129
|
await gitRunner(project.folder, { ...process.env, ...ctx.io.env }, ctx.signal)(['rev-parse', 'HEAD']);
|
|
118
130
|
await ensureCodexCloud(ctx);
|
|
119
|
-
const created = await createConversation(ctx, path, 'codex', 'codex-cloud', typeof values.name === 'string' ? values.name : undefined);
|
|
131
|
+
const created = await createConversation(ctx, path, 'codex', 'codex-cloud', typeof values.name === 'string' ? values.name : undefined, choice);
|
|
120
132
|
const c = created.conversation;
|
|
121
133
|
// --computer: the conversation VM's desktop is on before Codex starts, so the thread gets the computer tools.
|
|
122
134
|
if (values.computer === true)
|
|
@@ -130,7 +142,7 @@ export async function launchCodexCloud(ctx, values, path, images = []) {
|
|
|
130
142
|
const submitted = typeof values.initialPrompt === 'string';
|
|
131
143
|
if (submitted)
|
|
132
144
|
await submitCloudInitialPrompt(ctx, c, values.initialPrompt, created.workspace.key);
|
|
133
|
-
await output(ctx, c, task, submitted);
|
|
145
|
+
await output(ctx, c, task, submitted, choice);
|
|
134
146
|
return 0;
|
|
135
147
|
}
|
|
136
148
|
export async function openCodexConversation(ctx, c) {
|
|
@@ -11,7 +11,9 @@ import { agentName as gitAgentName } from "./agent-git.js";
|
|
|
11
11
|
import { randomUUID } from 'node:crypto';
|
|
12
12
|
import { prepareAgentMachine } from "./agent-machine.js";
|
|
13
13
|
import { OperationFailedError } from '@shardflux/sdk';
|
|
14
|
-
import { launchConversationProject } from "./conversation-terminal.js";
|
|
14
|
+
import { launchConversationProject, launchProjectCommand } from "./conversation-terminal.js";
|
|
15
|
+
import { startThreadOn } from "./thread-on-computer.js";
|
|
16
|
+
import { launchT3 } from "./t3-launch.js";
|
|
15
17
|
/** L9 can register target-specific handlers after detecting the installed applications. */
|
|
16
18
|
export async function availableTargets(ctx) {
|
|
17
19
|
const { HANDLERS } = await import("./handlers.js");
|
|
@@ -34,6 +36,28 @@ export async function agentAction(ctx, action, values, args) {
|
|
|
34
36
|
}
|
|
35
37
|
export const CENTER_HANDLERS = {
|
|
36
38
|
async 'agents new'(ctx, values, [ref]) {
|
|
39
|
+
const fresh = !ref && values.name === undefined && values.like === undefined;
|
|
40
|
+
// T3 Code mode (0.17.0+): --in t3 starts the first message as a T3 thread (--on: on that computer's T3 server);
|
|
41
|
+
// --in auto opens where open_in says.
|
|
42
|
+
if (fresh && values.in === 't3')
|
|
43
|
+
return launchT3(ctx, values, str(values.path) ?? './', str(values.app));
|
|
44
|
+
if (fresh && values.in === 'auto') {
|
|
45
|
+
const next = { ...values };
|
|
46
|
+
delete next.in;
|
|
47
|
+
return launchProjectCommand(ctx, next, [str(values.path) ?? './', ...(str(values.app) ? [str(values.app)] : [])]);
|
|
48
|
+
}
|
|
49
|
+
if (values.in === 'auto') {
|
|
50
|
+
values = { ...values };
|
|
51
|
+
delete values.in;
|
|
52
|
+
}
|
|
53
|
+
// A thread on an existing conversation's computer (0.16.0+).
|
|
54
|
+
if (values.on !== undefined) {
|
|
55
|
+
if (ref || values.like !== undefined)
|
|
56
|
+
throw new UsageError('--on starts a thread on that conversation’s computer; omit the agent argument and --like.');
|
|
57
|
+
return startThreadOn(ctx, values, str(values.path) ?? './');
|
|
58
|
+
}
|
|
59
|
+
if (values.review === true)
|
|
60
|
+
throw new UsageError('--review needs --on <conversation>.');
|
|
37
61
|
if ((values.prompt !== undefined || values['prompt-file'] !== undefined || values.image !== undefined) && (ref || values.name !== undefined || values.like !== undefined || values.in !== undefined)) {
|
|
38
62
|
throw new UsageError('An initial prompt needs a new conversation: use agents new --path <folder> --app claude|codex.');
|
|
39
63
|
}
|
package/dist/commands.d.ts
CHANGED
|
@@ -31,6 +31,11 @@ export interface Ctx {
|
|
|
31
31
|
wake: boolean;
|
|
32
32
|
/** Bound on lifecycle waits per cell call, wakes included (--wake-timeout / SHARDFLUX_WAKE_TIMEOUT_MS). */
|
|
33
33
|
wakeTimeoutMs: number;
|
|
34
|
+
/**
|
|
35
|
+
* The wake bound was given (--wake-timeout or SHARDFLUX_WAKE_TIMEOUT_MS; 0.16.0+): it is kept as given. Otherwise a
|
|
36
|
+
* resume queued for capacity is waited for until its deadline, its place shown on stderr.
|
|
37
|
+
*/
|
|
38
|
+
wakeTimeoutExplicit?: boolean;
|
|
34
39
|
signal: AbortSignal;
|
|
35
40
|
/** --timing: print the SDK's timing of the command's open, wait or wake (stderr; a `timing` field with --json). */
|
|
36
41
|
timing: boolean;
|
|
@@ -127,4 +132,21 @@ export declare function ttlSeconds(values: Values, b: {
|
|
|
127
132
|
max: number;
|
|
128
133
|
range: string;
|
|
129
134
|
}): number | undefined;
|
|
135
|
+
type ThreadRow = import('@shardflux/sdk').Agent & {
|
|
136
|
+
conversation_id?: string | null;
|
|
137
|
+
conversation_key?: string;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Each session's own conversation: the one whose harness session it is, else the computer's only live conversation.
|
|
141
|
+
* Rows that cannot be told apart keep no conversation rather than another thread's.
|
|
142
|
+
*/
|
|
143
|
+
export declare function withConversations(rows: import('@shardflux/sdk').Agent[], conversations: {
|
|
144
|
+
id: string;
|
|
145
|
+
key: string;
|
|
146
|
+
workspace_id: string;
|
|
147
|
+
harness_session_id: string | null;
|
|
148
|
+
state: string;
|
|
149
|
+
ended_at?: string | null;
|
|
150
|
+
}[] | null): ThreadRow[];
|
|
130
151
|
export declare const KEY_HANDLERS: Record<string, Handler>;
|
|
152
|
+
export {};
|
package/dist/commands.js
CHANGED
|
@@ -12,7 +12,7 @@ import { agentConfigLikeSecrets, syncAgentConfig } from "./agent-config-sync.js"
|
|
|
12
12
|
import { randomUUID } from 'node:crypto';
|
|
13
13
|
import { access, readFile, writeFile } from 'node:fs/promises';
|
|
14
14
|
import { basename, dirname, resolve as resolvePath } from 'node:path';
|
|
15
|
-
import { EXECUTION_ID, ExecStartError, FEEDBACK_CATEGORIES, FEEDBACK_MESSAGE_MAX_LENGTH, NotSupportedForModeError, SDK_VERSION, ShardfluxApiError, ShardfluxProtocolError, newExecutionId, exitCodePosix } from '@shardflux/sdk';
|
|
15
|
+
import { EXECUTION_ID, ExecStartError, capacityOf, FEEDBACK_CATEGORIES, FEEDBACK_MESSAGE_MAX_LENGTH, NotSupportedForModeError, SDK_VERSION, ShardfluxApiError, ShardfluxProtocolError, newExecutionId, exitCodePosix } from '@shardflux/sdk';
|
|
16
16
|
import { parse as parseYaml } from 'yaml';
|
|
17
17
|
import { CLI_VERSION, SECRET_NAME, UUID, parseDuration, parseEnvPairs, parsePositiveInt } from "./args.js";
|
|
18
18
|
import { ExecSessionRunningError, ExecutionFailedError, ExecutionPendingError, InterruptedError, NotFoundError, UsageError } from "./errors.js";
|
|
@@ -32,7 +32,15 @@ export const str = (v) => (typeof v === 'string' ? v : undefined);
|
|
|
32
32
|
* The workspace's cell client as this invocation configures it: the CLI's agent label, wake on use unless turned off,
|
|
33
33
|
* and the wake timeout (an OperationTimeoutError past it exits 5).
|
|
34
34
|
*/
|
|
35
|
-
const cellOf = (ctx, ws) => ws.cell({
|
|
35
|
+
const cellOf = (ctx, ws) => ws.cell({
|
|
36
|
+
agentLabel: ctx.agentLabel,
|
|
37
|
+
transitionTimeoutMs: ctx.wakeTimeoutMs,
|
|
38
|
+
...(!ctx.wake
|
|
39
|
+
? { wake: null }
|
|
40
|
+
: ctx.wakeTimeoutExplicit
|
|
41
|
+
? { wake: (timeoutMs, signal) => ws.wake({ timeoutMs, ...(signal ? { signal } : {}), agentLabel: ctx.agentLabel, untilCapacityDeadline: false }) }
|
|
42
|
+
: {}),
|
|
43
|
+
});
|
|
36
44
|
export const strs = (v) => (Array.isArray(v) ? v : typeof v === 'string' ? [v] : []);
|
|
37
45
|
const MAX_SECRET_VALUE_BYTES = 65_536;
|
|
38
46
|
/** `files patch --content-file`: a patch request is at most 7 MiB; larger files go through `files write`. */
|
|
@@ -357,10 +365,17 @@ async function writeNew(path, text, force) {
|
|
|
357
365
|
throw new UsageError(`${path} exists; pass --force to overwrite it`);
|
|
358
366
|
await writeFile(path, text);
|
|
359
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* An explicit `--timeout` is kept as given (0.16.0+): without one, a wait whose start is queued for capacity keeps waiting
|
|
370
|
+
* until the start's deadline (the SDK's `untilCapacityDeadline`), printing where it stands on stderr.
|
|
371
|
+
*/
|
|
372
|
+
function capacityWait(values) {
|
|
373
|
+
return values !== undefined && str(values.timeout) !== undefined ? { untilCapacityDeadline: false } : {};
|
|
374
|
+
}
|
|
360
375
|
/** Waits with the SDK's backoff; Ctrl-C reports the operation that keeps running. */
|
|
361
|
-
async function waitOperation(ctx, operationId, timeoutMs) {
|
|
376
|
+
async function waitOperation(ctx, operationId, timeoutMs, values) {
|
|
362
377
|
try {
|
|
363
|
-
return await ctx.cloud().workspaces.waitForOperation(operationId, { timeoutMs, signal: ctx.signal });
|
|
378
|
+
return await ctx.cloud().workspaces.waitForOperation(operationId, { timeoutMs, signal: ctx.signal, ...capacityWait(values) });
|
|
364
379
|
}
|
|
365
380
|
catch (err) {
|
|
366
381
|
if (ctx.signal.aborted)
|
|
@@ -782,7 +797,28 @@ function reportExecution(ctx, ref, r, opts) {
|
|
|
782
797
|
}
|
|
783
798
|
function lifecycleText(verb, w, op) {
|
|
784
799
|
const finished = op.state === 'succeeded' || op.state === 'failed' || op.state === 'canceled';
|
|
785
|
-
|
|
800
|
+
// A start queued for capacity (0.16.0+): its place in the queue.
|
|
801
|
+
const position = capacityOf(op)?.queuePosition;
|
|
802
|
+
const why = [op.state_reason, position ? `position ${position} in the queue` : null].filter(Boolean).join('; ');
|
|
803
|
+
return `${verb} ${w.key !== undefined ? `${w.key} (${w.id})` : w.id}: operation ${op.id} ${op.kind} ${op.state}${why ? ` (${why})` : ''}\n${finished ? durableText(op) : `wait with: shard operations wait ${op.id}\n`}`;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Each session's own conversation: the one whose harness session it is, else the computer's only live conversation.
|
|
807
|
+
* Rows that cannot be told apart keep no conversation rather than another thread's.
|
|
808
|
+
*/
|
|
809
|
+
export function withConversations(rows, conversations) {
|
|
810
|
+
if (!conversations)
|
|
811
|
+
return rows;
|
|
812
|
+
const live = conversations.filter(c => c.state !== 'done' && c.state !== 'archived' && !c.ended_at);
|
|
813
|
+
return rows.map(a => {
|
|
814
|
+
const session = a.id.slice(a.workspace_id.length + 1);
|
|
815
|
+
const bound = live.filter(c => c.workspace_id === a.workspace_id);
|
|
816
|
+
// An API that names the session's conversation (0.16.0+) decides; older ones are matched here by the same rule.
|
|
817
|
+
const named = a.conversation_id;
|
|
818
|
+
const c = (named ? conversations.find(c => c.id === named) : undefined)
|
|
819
|
+
?? conversations.find(c => c.workspace_id === a.workspace_id && c.harness_session_id === session) ?? (bound.length === 1 ? bound[0] : undefined);
|
|
820
|
+
return c ? { ...a, conversation_id: c.id, conversation_key: c.key } : a;
|
|
821
|
+
});
|
|
786
822
|
}
|
|
787
823
|
/** The project-key commands; handlers.ts adds the account plane's. */
|
|
788
824
|
function agentGrace(text) {
|
|
@@ -807,6 +843,9 @@ export const KEY_HANDLERS = {
|
|
|
807
843
|
...(str(values.limit) ? { limit: parsePositiveInt(str(values.limit), 'limit', 200) } : {}),
|
|
808
844
|
};
|
|
809
845
|
const rows = [];
|
|
846
|
+
// Which conversation each session belongs to (0.16.0+): several threads can share one computer. Read alongside
|
|
847
|
+
// the sessions; an API without conversations leaves the rows as they are.
|
|
848
|
+
const threads = import("./conversations.js").then(m => m.listConversations(ctx)).catch(() => null);
|
|
810
849
|
let cursor = str(values.cursor);
|
|
811
850
|
let next;
|
|
812
851
|
do {
|
|
@@ -820,7 +859,8 @@ export const KEY_HANDLERS = {
|
|
|
820
859
|
rows.splice(0, rows.length, ...await withLiveAgents(ctx, rows, { ...(state ? { state } : {}), labels }));
|
|
821
860
|
// LAST MESSAGE (0.13.4+): the session's latest message, from an API that reports it.
|
|
822
861
|
const message = (a) => !a.last_message ? '-' : a.last_message.length > 60 ? `${a.last_message.slice(0, 59)}…` : a.last_message;
|
|
823
|
-
|
|
862
|
+
const enriched = withConversations(rows, await threads);
|
|
863
|
+
out(ctx, rows.length ? table(['CONVERSATION', 'COMPUTER', 'APP', 'STATE', 'TITLE', 'LAST MESSAGE'], enriched.map(a => [a.conversation_key?.split('/').at(-1) ?? '-', a.workspace_key, a.app, a.state, a.title, message(a)])) : 'No agent sessions.\n', { data: enriched, next_cursor: next });
|
|
824
864
|
if (!ctx.json && next)
|
|
825
865
|
ctx.io.stderr.write(`more: shard agents list --cursor ${next}\n`);
|
|
826
866
|
return 0;
|
|
@@ -1090,7 +1130,7 @@ export const KEY_HANDLERS = {
|
|
|
1090
1130
|
...(mode ? { mode } : {}),
|
|
1091
1131
|
...(computerUse !== undefined ? { computerUse } : {}),
|
|
1092
1132
|
agentLabel: ctx.agentLabel,
|
|
1093
|
-
wait: values.wait === false ? false : { timeoutMs, signal: ctx.signal },
|
|
1133
|
+
wait: values.wait === false ? false : { timeoutMs, signal: ctx.signal, ...capacityWait(values) },
|
|
1094
1134
|
}));
|
|
1095
1135
|
const op = ws.activeOperation;
|
|
1096
1136
|
if (ws.ready && ws.mode === 'processful' && ws.labels.shard_project)
|
|
@@ -1653,7 +1693,7 @@ export const KEY_HANDLERS = {
|
|
|
1653
1693
|
});
|
|
1654
1694
|
const op = ws.activeOperation;
|
|
1655
1695
|
if (values.wait !== false && !ws.ready && op) {
|
|
1656
|
-
await waitOperation(ctx, op.id, timeoutMs);
|
|
1696
|
+
await waitOperation(ctx, op.id, timeoutMs, values);
|
|
1657
1697
|
ws = await ctx.cloud().workspaces.get(ws.id);
|
|
1658
1698
|
}
|
|
1659
1699
|
const human = ws.ready
|
|
@@ -1849,7 +1889,7 @@ export const KEY_HANDLERS = {
|
|
|
1849
1889
|
let ws = opened.workspace;
|
|
1850
1890
|
const op = opened.operation;
|
|
1851
1891
|
if (values.wait !== false && !ws.ready && op) {
|
|
1852
|
-
await waitOperation(ctx, op.id, timeoutMs);
|
|
1892
|
+
await waitOperation(ctx, op.id, timeoutMs, values);
|
|
1853
1893
|
ws = await cloud.workspaces.get(ws.id);
|
|
1854
1894
|
}
|
|
1855
1895
|
const human = ws.ready
|
|
@@ -1875,7 +1915,7 @@ export const KEY_HANDLERS = {
|
|
|
1875
1915
|
const label = str(values.label);
|
|
1876
1916
|
let op = await draft.captureState(label === undefined ? {} : { label });
|
|
1877
1917
|
if (values.wait === true)
|
|
1878
|
-
op = await waitOperation(ctx, op.id, waitTimeout(values));
|
|
1918
|
+
op = await waitOperation(ctx, op.id, waitTimeout(values), values);
|
|
1879
1919
|
const checkpoint = typeof op.result?.checkpoint_id === 'string' ? `state ${op.result.checkpoint_id}\n` : '';
|
|
1880
1920
|
out(ctx, `Capturing a state of the ${slug} draft: operation ${op.id} ${op.kind} ${op.state}\n${checkpoint}${op.state === 'succeeded' ? '' : `wait with: shard operations wait ${op.id}\n`}`, op);
|
|
1881
1921
|
return 0;
|
|
@@ -1902,7 +1942,7 @@ export const KEY_HANDLERS = {
|
|
|
1902
1942
|
let ws = await draft.openTestInstance({ ...(state ? { stateId: state } : {}), ...(instanceKey ? { key: instanceKey } : {}), ...(c ? { caps: c } : {}), agentLabel: ctx.agentLabel, wait: false });
|
|
1903
1943
|
const op = ws.activeOperation;
|
|
1904
1944
|
if (values.wait !== false && !ws.ready && op) {
|
|
1905
|
-
await waitOperation(ctx, op.id, timeoutMs);
|
|
1945
|
+
await waitOperation(ctx, op.id, timeoutMs, values);
|
|
1906
1946
|
ws = await ctx.cloud().workspaces.get(ws.id);
|
|
1907
1947
|
}
|
|
1908
1948
|
const human = ws.ready
|
|
@@ -1931,7 +1971,7 @@ export const KEY_HANDLERS = {
|
|
|
1931
1971
|
throw new UsageError('refusing to discard without --yes (the draft is deleted and its live test instances end)');
|
|
1932
1972
|
let op = await ctx.cloud().templates.draft(slug).discard();
|
|
1933
1973
|
if (values.wait === true)
|
|
1934
|
-
op = await waitOperation(ctx, op.id, waitTimeout(values));
|
|
1974
|
+
op = await waitOperation(ctx, op.id, waitTimeout(values), values);
|
|
1935
1975
|
out(ctx, `Discarding the ${slug} draft: operation ${op.id} ${op.kind} ${op.state}\n`, op);
|
|
1936
1976
|
return 0;
|
|
1937
1977
|
},
|
|
@@ -2344,7 +2384,7 @@ export const KEY_HANDLERS = {
|
|
|
2344
2384
|
const timeoutMs = waitTimeout(values);
|
|
2345
2385
|
const op = values.durable === true
|
|
2346
2386
|
? await waited(ctx, `operation ${id} continues server side.`, () => ctx.cloud().workspaces.waitForDurable(id, { timeoutMs, signal: ctx.signal }))
|
|
2347
|
-
: await waitOperation(ctx, id, timeoutMs);
|
|
2387
|
+
: await waitOperation(ctx, id, timeoutMs, values);
|
|
2348
2388
|
out(ctx, operationDetail(op), op);
|
|
2349
2389
|
return 0;
|
|
2350
2390
|
},
|
|
@@ -2488,7 +2528,7 @@ async function resolveSecret(ctx, values, ref) {
|
|
|
2488
2528
|
}
|
|
2489
2529
|
/** `--wait`: the SDK's own wait (the call resolves once the operation finished; one trace covers request and wait). */
|
|
2490
2530
|
function lifecycleOptions(ctx, values, timeoutMs) {
|
|
2491
|
-
return values.wait === true ? { wait: { timeoutMs, signal: ctx.signal } } : {};
|
|
2531
|
+
return values.wait === true ? { wait: { timeoutMs, signal: ctx.signal, ...capacityWait(values) } } : {};
|
|
2492
2532
|
}
|
|
2493
2533
|
async function lifecycle(ctx, values, ref, verb, start, processfulOnly) {
|
|
2494
2534
|
const timeoutMs = waitTimeout(values);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { out, str } from "./commands.js";
|
|
2
2
|
import { UsageError } from "./errors.js";
|
|
3
|
+
import { FilesMirror, humanBytes, listMirrorConversations, workspaceHub } from "./files-mirror.js";
|
|
3
4
|
export function conversationClient(ctx) {
|
|
4
5
|
const key = Boolean(ctx.io.env.SHARDFLUX_API_KEY?.trim()) || ctx.store.profile(ctx.apiUrl).api_key !== undefined;
|
|
5
6
|
const session = Boolean(ctx.io.env.SHARDFLUX_SESSION_TOKEN?.trim()) || ctx.store.profile(ctx.apiUrl).session !== undefined;
|
|
@@ -32,5 +33,10 @@ export const CONVERSATION_HANDLERS = {
|
|
|
32
33
|
out(ctx, `${r.title}: ${r.landed.how}\n`, r);
|
|
33
34
|
return 0;
|
|
34
35
|
},
|
|
36
|
+
async 'files pull'(ctx, _values, [conversation, path]) {
|
|
37
|
+
const r = await new FilesMirror(ctx, workspaceHub(ctx)).pull(conversation, path, () => listMirrorConversations(ctx));
|
|
38
|
+
out(ctx, r.already ? `${r.local_path} is already downloaded.\n` : `Downloaded ${r.path} (${humanBytes(r.bytes)}) to ${r.local_path}\n`, r);
|
|
39
|
+
return 0;
|
|
40
|
+
},
|
|
35
41
|
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; },
|
|
36
42
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
-
import { getConversation, resolveConversation, conversationWorkspace, localConversations } from "./conversations.js";
|
|
2
|
+
import { conversationEnded, getConversation, resolveConversation, conversationWorkspace, localConversations } from "./conversations.js";
|
|
3
3
|
import { ensureConversationWorktree, devicePort } from "./device-bridge.js";
|
|
4
4
|
import { startProjectSync } from "./project-sync.js";
|
|
5
5
|
import { projectIdentity } from "./project-identity.js";
|
|
@@ -48,7 +48,7 @@ export const CONVERSATION_HANDLERS = {
|
|
|
48
48
|
try {
|
|
49
49
|
while (!ctx.signal.aborted) {
|
|
50
50
|
const next = await getConversation(ctx, c.id);
|
|
51
|
-
if (next
|
|
51
|
+
if (conversationEnded(next) || next.workspace_id !== c.workspace_id)
|
|
52
52
|
break;
|
|
53
53
|
await delay(1000, undefined, { signal: ctx.signal }).catch(() => undefined);
|
|
54
54
|
}
|
|
@@ -182,7 +182,7 @@ export async function mirrorProjectConversations(ctx, state = new ConversationMi
|
|
|
182
182
|
const c = all.find(c => c.id === record.conversation.id);
|
|
183
183
|
if (!c || record.handed_off || !c.harness_session_id)
|
|
184
184
|
continue;
|
|
185
|
-
const revision = JSON.stringify([c.workspace_id, c.harness_session_id, c.state, c.durable, c.last_activity_at, c.summary]);
|
|
185
|
+
const revision = JSON.stringify([c.workspace_id, c.harness_session_id, c.state, c.durable, c.last_activity_at, c.summary, c.ended_at]);
|
|
186
186
|
if (state.mirrored.get(c.id) === revision)
|
|
187
187
|
continue;
|
|
188
188
|
let retry = state.retries.get(c.id);
|
|
@@ -220,8 +220,9 @@ export async function mirrorProjectConversations(ctx, state = new ConversationMi
|
|
|
220
220
|
await mirrorConversation(ctx, { ...record, conversation: c }, source, skipped);
|
|
221
221
|
retry.failures = 0;
|
|
222
222
|
retry.retryAt = 0;
|
|
223
|
-
// An ended record may precede its VM tombstone; retry until the handoff receipt is written.
|
|
224
|
-
|
|
223
|
+
// An ended record may precede its VM tombstone; retry until the handoff receipt is written. A finished turn is
|
|
224
|
+
// `done` while its VM lives: mirrored once per revision, and again when it ends.
|
|
225
|
+
if (c.state !== 'archived' && !(c.state === 'done' && c.ended_at))
|
|
225
226
|
state.mirrored.set(c.id, revision);
|
|
226
227
|
}
|
|
227
228
|
catch (e) {
|
|
@@ -4,5 +4,10 @@ import type { Values } from './args.js';
|
|
|
4
4
|
import type { Conversation } from './conversations.js';
|
|
5
5
|
/** Every terminal entry point uses the durable conversation's native harness, worktree and session. */
|
|
6
6
|
export declare function conversationTerminal(ctx: Ctx, ws: Workspace, c: Conversation, project: string, values: Values): Promise<number>;
|
|
7
|
+
/**
|
|
8
|
+
* `shard ./` (and `agents new --in auto`): new conversations open where `agents targets` says (open_in, 0.17.0+): T3
|
|
9
|
+
* Code mode, Claude Desktop or claude.ai for Claude (the conversation's Remote Control session), else this terminal.
|
|
10
|
+
*/
|
|
11
|
+
export declare const launchProjectCommand: Handler;
|
|
7
12
|
/** Select a durable conversation, then use the harness's native terminal for all interaction. */
|
|
8
13
|
export declare const launchConversationProject: Handler;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { linuxTerminalSession } from "./linux-terminal.js";
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { AgentStateStore } from "./agent-state.js";
|
|
3
4
|
import { projectIdentity, projectLabels } from "./project-identity.js";
|
|
4
|
-
import { createConversation, conversationName, conversationWorkspace, conversationsUnavailable, listConversations, localConversations, rememberConversation, resumeConversation, restoreConversationBinding } from "./conversations.js";
|
|
5
|
+
import { createConversation, conversationName, conversationWorkspace, conversationsUnavailable, listConversations, localConversations, recordedChoice, rememberConversation, resumeConversation, restoreConversationBinding } from "./conversations.js";
|
|
6
|
+
import { modelChoice } from "./agent-models.js";
|
|
5
7
|
import { terminal } from "./agent-commands.js";
|
|
6
8
|
import { cloudClaudeLogin, loginClaudeInWorkspace, requireClaudeTerminal } from "./claude-cloud-login.js";
|
|
7
9
|
import { syncAgentCredentials } from "./agent-credentials.js";
|
|
@@ -15,15 +17,47 @@ import { localResumeTerminal } from "./conversation-local-resume.js";
|
|
|
15
17
|
import { agentReachability } from "./agent-reachability.js";
|
|
16
18
|
import { promptWithImages, readInitialImages, readInitialPrompt, uploadInitialImages } from "./initial-prompt.js";
|
|
17
19
|
import { launchCodexCloud, openCodexConversation } from "./codex-cloud.js";
|
|
20
|
+
import { startThreadOn } from "./thread-on-computer.js";
|
|
21
|
+
import { openInChoices } from "./t3-mode.js";
|
|
22
|
+
import { launchT3 } from "./t3-launch.js";
|
|
23
|
+
import { openClaudeDesktopSession, remoteControlUrl } from "./agent-target-commands.js";
|
|
24
|
+
import { openUrl } from "./browser.js";
|
|
25
|
+
import { out } from "./commands.js";
|
|
18
26
|
/** Every terminal entry point uses the durable conversation's native harness, worktree and session. */
|
|
19
27
|
export async function conversationTerminal(ctx, ws, c, project, values) {
|
|
20
28
|
if (c.route === 'codex-cloud')
|
|
21
29
|
return openCodexConversation(ctx, c);
|
|
22
30
|
await restoreConversationBinding(ctx, c, ws);
|
|
23
|
-
|
|
31
|
+
// A thread on another conversation's computer has its own PTY there, never the VM's own (0.16.0+).
|
|
32
|
+
const pty = ws.labels.shard_conversation && ws.labels.shard_conversation !== c.id ? conversationName(c) : undefined;
|
|
33
|
+
return terminal(ctx, ws, c.harness, conversationName(c), project, values, undefined, c.worktree_path, { id: c.id, session: c.harness_session_id, title: c.title, ...(pty ? { pty } : {}), choice: recordedChoice(c) });
|
|
24
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* `shard ./` (and `agents new --in auto`): new conversations open where `agents targets` says (open_in, 0.17.0+): T3
|
|
37
|
+
* Code mode, Claude Desktop or claude.ai for Claude (the conversation's Remote Control session), else this terminal.
|
|
38
|
+
*/
|
|
39
|
+
export const launchProjectCommand = async (ctx, values, [path = './', explicit]) => {
|
|
40
|
+
const named = explicit ?? (values.claude === true ? 'claude' : values.codex === true ? 'codex' : undefined);
|
|
41
|
+
if (values.resume !== true && values.review !== true) {
|
|
42
|
+
const project = projectIdentity(path);
|
|
43
|
+
const app = named === 'claude' || named === 'codex' ? named : new AgentStateStore(ctx.store.dir).app(project.remote ?? project.folder);
|
|
44
|
+
const where = process.platform === 'linux' && ctx.io.stdin.isTTY ? 'terminal' : openInChoices(ctx, project.slug)[app];
|
|
45
|
+
if (where === 't3')
|
|
46
|
+
return launchT3(ctx, values, path, app);
|
|
47
|
+
if (values.on === undefined && app === 'claude' && (where === 'claude-desktop' || where === 'web'))
|
|
48
|
+
return startConversation(ctx, values, [path, ...(explicit ? [explicit] : [])], where);
|
|
49
|
+
}
|
|
50
|
+
return launchConversationProject(ctx, values, [path, ...(explicit ? [explicit] : [])]);
|
|
51
|
+
};
|
|
25
52
|
/** Select a durable conversation, then use the harness's native terminal for all interaction. */
|
|
26
|
-
export const launchConversationProject =
|
|
53
|
+
export const launchConversationProject = (ctx, values, args) => startConversation(ctx, values, args);
|
|
54
|
+
async function startConversation(ctx, values, [path = './', explicit], openIn) {
|
|
55
|
+
if (values.on !== undefined) {
|
|
56
|
+
const app = explicit ?? (values.claude === true ? 'claude' : values.codex === true ? 'codex' : undefined);
|
|
57
|
+
return startThreadOn(ctx, values, path, app);
|
|
58
|
+
}
|
|
59
|
+
if (values.review === true)
|
|
60
|
+
throw new UsageError('--review needs --on <conversation>.');
|
|
27
61
|
const text = await readInitialPrompt(ctx, values);
|
|
28
62
|
// Images are read and validated before allocation, held in memory, and written only into the new VM.
|
|
29
63
|
const images = await readInitialImages(ctx, values, text);
|
|
@@ -33,6 +67,8 @@ export const launchConversationProject = async (ctx, values, [path = './', expli
|
|
|
33
67
|
const app = explicit === 'claude' || explicit === 'codex' ? explicit : store.app(project.remote ?? project.folder);
|
|
34
68
|
if (explicit && explicit !== app)
|
|
35
69
|
throw new UsageError('Choose claude or codex.');
|
|
70
|
+
// Checked before a VM is allocated; the new conversation's record carries it to its guest (0.17.0+).
|
|
71
|
+
const choice = modelChoice(values, app);
|
|
36
72
|
if (values.resume === true) {
|
|
37
73
|
// The native pickers are VM-local. C-K6's project picker must select the binding first.
|
|
38
74
|
const records = localConversations(ctx);
|
|
@@ -53,7 +89,7 @@ export const launchConversationProject = async (ctx, values, [path = './', expli
|
|
|
53
89
|
const ws = await conversationWorkspace(ctx, next);
|
|
54
90
|
return conversationTerminal(ctx, ws, next, project.remote ?? project.folder, values);
|
|
55
91
|
}
|
|
56
|
-
if (app === 'codex')
|
|
92
|
+
if (app === 'codex' && !linuxTerminalSession(ctx, values))
|
|
57
93
|
return launchCodexCloud(ctx, values, path, images);
|
|
58
94
|
await syncAgentCredentials(ctx.cloud(), { home: ctx.io.env.HOME, redact: value => ctx.addSecret(value) });
|
|
59
95
|
const signedIn = app === 'claude' ? await cloudClaudeLogin(ctx) : true;
|
|
@@ -61,7 +97,7 @@ export const launchConversationProject = async (ctx, values, [path = './', expli
|
|
|
61
97
|
requireClaudeTerminal(ctx);
|
|
62
98
|
let created;
|
|
63
99
|
try {
|
|
64
|
-
created = await createConversation(ctx, project.folder, app, 'terminal');
|
|
100
|
+
created = await createConversation(ctx, project.folder, app, 'terminal', undefined, choice);
|
|
65
101
|
}
|
|
66
102
|
catch (e) {
|
|
67
103
|
if (!conversationsUnavailable(e))
|
|
@@ -93,5 +129,17 @@ export const launchConversationProject = async (ctx, values, [path = './', expli
|
|
|
93
129
|
if (!(await savedMachine(ctx))?.template)
|
|
94
130
|
await syncAgentConfig(ctx, ws);
|
|
95
131
|
await ensureMenubar(ctx.store.dir, ctx.apiUrl, new MenubarManager({ home: ctx.io.env.HOME, signal: ctx.signal }));
|
|
132
|
+
if (openIn) {
|
|
133
|
+
// The agent starts detached in its VM; the conversation then opens in Claude Desktop or claude.ai (Remote Control).
|
|
134
|
+
const quiet = { ...ctx, io: { ...ctx.io, stdout: { write: () => true } } };
|
|
135
|
+
await conversationTerminal(quiet, ws, c, project.remote ?? project.folder, { ...values, detach: true });
|
|
136
|
+
if (openIn === 'claude-desktop')
|
|
137
|
+
return openClaudeDesktopSession(ctx, ws, c);
|
|
138
|
+
const url = await remoteControlUrl(ctx, ws, c);
|
|
139
|
+
if (!ctx.json && !openUrl(url, ctx.io.env))
|
|
140
|
+
throw new Error(`could not open ${url}`);
|
|
141
|
+
out(ctx, `${url}\n`, { target: 'web', workspace: ws.data, conversation_id: c.id, url });
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
96
144
|
return conversationTerminal(ctx, ws, c, project.remote ?? project.folder, values);
|
|
97
|
-
}
|
|
145
|
+
}
|
package/dist/conversations.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Workspace, WorkspaceView } from '@shardflux/sdk';
|
|
2
2
|
import type { Ctx } from './commands.js';
|
|
3
|
+
import type { ModelChoice } from './agent-models.js';
|
|
3
4
|
export type Harness = 'claude' | 'codex';
|
|
4
5
|
export interface Conversation {
|
|
5
6
|
id: string;
|
|
@@ -17,6 +18,11 @@ export interface Conversation {
|
|
|
17
18
|
state: 'working' | 'needs_you' | 'unresolved' | 'done' | 'archived';
|
|
18
19
|
created_at: string;
|
|
19
20
|
ended_at: string | null;
|
|
21
|
+
landed?: {
|
|
22
|
+
how: string;
|
|
23
|
+
at: string;
|
|
24
|
+
ref: string;
|
|
25
|
+
} | null;
|
|
20
26
|
last_activity_at?: string;
|
|
21
27
|
durable?: {
|
|
22
28
|
branch_sha: string;
|
|
@@ -26,6 +32,14 @@ export interface Conversation {
|
|
|
26
32
|
} | null;
|
|
27
33
|
ports?: number[];
|
|
28
34
|
restore?: Record<string, unknown> | null;
|
|
35
|
+
/** The repository and the branch it started from, from APIs that return them. */
|
|
36
|
+
repo_remote?: string;
|
|
37
|
+
base_branch?: string | null;
|
|
38
|
+
role?: string;
|
|
39
|
+
join_mode?: string | null;
|
|
40
|
+
/** The model and effort its harness starts with; null is the agent's own default (0.17.0+, APIs that keep them). */
|
|
41
|
+
model?: string | null;
|
|
42
|
+
effort?: string | null;
|
|
29
43
|
}
|
|
30
44
|
export interface LocalConversation {
|
|
31
45
|
api_url: string;
|
|
@@ -33,21 +47,35 @@ export interface LocalConversation {
|
|
|
33
47
|
project_id: string;
|
|
34
48
|
conversation: Conversation;
|
|
35
49
|
handed_off?: boolean;
|
|
50
|
+
/** The branch the conversation started from (0.16.0+), for APIs that do not return base_branch yet. */
|
|
51
|
+
base_branch?: string;
|
|
36
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether the conversation has ended: its VM was reclaimed, or it was archived or discarded. A finished turn is `done`
|
|
55
|
+
* while its VM still takes the next turn; on Codex Cloud, `done` ends the conversation.
|
|
56
|
+
*/
|
|
57
|
+
export declare const conversationEnded: (c: Pick<Conversation, "state" | "ended_at" | "route" | "landed">) => boolean;
|
|
37
58
|
export declare const conversationName: (c: Conversation) => string;
|
|
38
59
|
export declare function validateConversation(c: Conversation): Conversation;
|
|
39
60
|
export declare function localConversations(ctx: Pick<Ctx, 'store' | 'apiUrl'>): Record<string, LocalConversation>;
|
|
40
|
-
export declare function rememberConversation(ctx: Pick<Ctx, 'store' | 'apiUrl'>, c: Conversation, folder: string, handedOff?: boolean): void;
|
|
61
|
+
export declare function rememberConversation(ctx: Pick<Ctx, 'store' | 'apiUrl'>, c: Conversation, folder: string, handedOff?: boolean, baseBranch?: string): void;
|
|
41
62
|
export declare function conversationsUnavailable(e: unknown): boolean;
|
|
42
63
|
export declare function getConversation(ctx: Ctx, id: string): Promise<Conversation>;
|
|
43
64
|
export declare function listConversations(ctx: Ctx, projectId?: string): Promise<Conversation[]>;
|
|
44
65
|
export declare function resolveConversation(ctx: Ctx, ref: string, folder?: string): Promise<Conversation>;
|
|
45
66
|
/** Re-resolve the binding on every attach: a move changes the VM, never the conversation identity. */
|
|
46
67
|
export declare function conversationWorkspace(ctx: Ctx, c: Conversation): Promise<Workspace>;
|
|
68
|
+
/**
|
|
69
|
+
* A thread on an existing computer (a join) is prepared by that computer's guest: its worktree, then its harness. Waits
|
|
70
|
+
* until the guest confirms it (the conversation no longer carries restore instructions), within the wake timeout.
|
|
71
|
+
*/
|
|
72
|
+
export declare function waitThreadReady(ctx: Ctx, c: Conversation, pollMs?: number): Promise<Conversation>;
|
|
47
73
|
export declare function restoreConversationBinding(ctx: Ctx, c: Conversation, ws: Workspace): Promise<void>;
|
|
48
|
-
export declare function createConversationRecord(ctx: Ctx, folder: string, harness: Harness, route: Conversation['route'], name?: string, allocated?: (workspace: WorkspaceView) => void, idempotencyKey?: string): Promise<Conversation>;
|
|
49
|
-
export declare function createConversation(ctx: Ctx, folder: string, harness: Harness, route: Conversation['route'], name?: string): Promise<{
|
|
74
|
+
export declare function createConversationRecord(ctx: Ctx, folder: string, harness: Harness, route: Conversation['route'], name?: string, allocated?: (workspace: WorkspaceView) => void, idempotencyKey?: string, choice?: ModelChoice): Promise<Conversation>;
|
|
75
|
+
export declare function createConversation(ctx: Ctx, folder: string, harness: Harness, route: Conversation['route'], name?: string, choice?: ModelChoice): Promise<{
|
|
50
76
|
conversation: Conversation;
|
|
51
77
|
workspace: Workspace;
|
|
52
78
|
}>;
|
|
53
79
|
export declare function resumeConversation(ctx: Ctx, c: Conversation): Promise<Conversation>;
|
|
80
|
+
/** The model and effort a conversation's harness starts with, as its record keeps them. */
|
|
81
|
+
export declare const recordedChoice: (c: Pick<Conversation, "model" | "effort">) => ModelChoice;
|