@otto-code/cli 0.7.5 → 0.8.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/dist/cli.js +22 -2
- package/dist/commands/agent/delete.d.ts +1 -1
- package/dist/commands/agent/delete.js +2 -2
- package/dist/commands/agent/detach.d.ts +9 -0
- package/dist/commands/agent/detach.js +38 -0
- package/dist/commands/agent/index.js +9 -1
- package/dist/commands/agent/open.d.ts +11 -0
- package/dist/commands/agent/open.js +61 -0
- package/dist/commands/agent/run.d.ts +35 -0
- package/dist/commands/agent/run.js +142 -49
- package/dist/commands/agent/send.js +1 -1
- package/dist/commands/agent/update.d.ts +33 -0
- package/dist/commands/agent/update.js +72 -25
- package/dist/commands/clone.d.ts +17 -0
- package/dist/commands/clone.js +65 -0
- package/dist/commands/daemon/local-daemon.d.ts +15 -1
- package/dist/commands/daemon/local-daemon.js +14 -7
- package/dist/commands/daemon/pair.js +6 -2
- package/dist/commands/heartbeat/index.d.ts +3 -0
- package/dist/commands/heartbeat/index.js +139 -0
- package/dist/commands/hub/cloud-device-authorization.d.ts +45 -0
- package/dist/commands/hub/cloud-device-authorization.js +92 -0
- package/dist/commands/hub/device-authorization.d.ts +37 -0
- package/dist/commands/hub/device-authorization.js +87 -0
- package/dist/commands/hub/index.d.ts +30 -0
- package/dist/commands/hub/index.js +85 -0
- package/dist/commands/hub-disabled.d.ts +22 -0
- package/dist/commands/hub-disabled.js +34 -0
- package/dist/commands/onboard.js +6 -2
- package/dist/commands/open.d.ts +2 -0
- package/dist/commands/open.js +22 -17
- package/dist/commands/provider/ls.js +1 -1
- package/dist/commands/schedule/create.d.ts +1 -0
- package/dist/commands/schedule/create.js +1 -0
- package/dist/commands/schedule/index.js +6 -6
- package/dist/commands/schedule/inspect.js +3 -0
- package/dist/commands/schedule/logs.js +2 -1
- package/dist/commands/schedule/ls.js +3 -1
- package/dist/commands/schedule/pause.js +2 -1
- package/dist/commands/schedule/resume.js +2 -1
- package/dist/commands/schedule/run-once.js +2 -1
- package/dist/commands/schedule/shared.d.ts +3 -0
- package/dist/commands/schedule/shared.js +35 -23
- package/dist/commands/schedule/update.js +2 -1
- package/dist/commands/script/index.d.ts +3 -0
- package/dist/commands/script/index.js +19 -0
- package/dist/commands/script/ls.d.ts +6 -0
- package/dist/commands/script/ls.js +20 -0
- package/dist/commands/script/schema.d.ts +5 -0
- package/dist/commands/script/schema.js +13 -0
- package/dist/commands/script/shared.d.ts +11 -0
- package/dist/commands/script/shared.js +59 -0
- package/dist/commands/script/start.d.ts +6 -0
- package/dist/commands/script/start.js +23 -0
- package/dist/commands/script/stop.d.ts +6 -0
- package/dist/commands/script/stop.js +23 -0
- package/dist/commands/workspace/archive.d.ts +12 -0
- package/dist/commands/workspace/archive.js +41 -0
- package/dist/commands/workspace/create.d.ts +49 -0
- package/dist/commands/workspace/create.js +114 -0
- package/dist/commands/workspace/index.d.ts +3 -0
- package/dist/commands/workspace/index.js +30 -0
- package/dist/commands/workspace/ls.d.ts +7 -0
- package/dist/commands/workspace/ls.js +28 -0
- package/dist/commands/workspace/shared.d.ts +12 -0
- package/dist/commands/workspace/shared.js +20 -0
- package/dist/output/pairing.d.ts +8 -0
- package/dist/output/pairing.js +24 -0
- package/dist/utils/client.d.ts +9 -0
- package/dist/utils/client.js +9 -0
- package/dist/utils/duration.d.ts +1 -1
- package/dist/utils/duration.js +8 -7
- package/dist/utils/paths.js +1 -1
- package/dist/utils/provider-model.d.ts +1 -1
- package/dist/utils/provider-model.js +1 -1
- package/package.json +9 -7
package/dist/cli.js
CHANGED
|
@@ -7,8 +7,13 @@ import { createPermitCommand } from "./commands/permit/index.js";
|
|
|
7
7
|
import { createProviderCommand } from "./commands/provider/index.js";
|
|
8
8
|
import { createScheduleCommand } from "./commands/schedule/index.js";
|
|
9
9
|
import { createSpeechCommand } from "./commands/speech/index.js";
|
|
10
|
+
import { createScriptCommand } from "./commands/script/index.js";
|
|
10
11
|
import { createTerminalCommand } from "./commands/terminal/index.js";
|
|
11
12
|
import { createWorktreeCommand } from "./commands/worktree/index.js";
|
|
13
|
+
import { createWorkspaceCommand } from "./commands/workspace/index.js";
|
|
14
|
+
import { createHeartbeatCommand } from "./commands/heartbeat/index.js";
|
|
15
|
+
// DISABLED(hub): specifier redirected away from "./commands/hub/index.js".
|
|
16
|
+
import { createHubCommand } from "./commands/hub-disabled.js";
|
|
12
17
|
import { createHooksCommand } from "./commands/hooks.js";
|
|
13
18
|
import { createBrainCommand } from "@otto-code/brain";
|
|
14
19
|
import { startCommand as daemonStartCommand } from "./commands/daemon/start.js";
|
|
@@ -26,6 +31,7 @@ import { addArchiveOptions, runArchiveCommand } from "./commands/agent/archive.j
|
|
|
26
31
|
import { addAttachOptions, runAttachCommand } from "./commands/agent/attach.js";
|
|
27
32
|
import { addImportOptions, runImportCommand } from "./commands/agent/import.js";
|
|
28
33
|
import { withOutput } from "./output/index.js";
|
|
34
|
+
import { runCloneCommand } from "./commands/clone.js";
|
|
29
35
|
import { onboardCommand } from "./commands/onboard.js";
|
|
30
36
|
import { addDaemonHostOption, addJsonAndDaemonHostOptions, addJsonOption, } from "./utils/command-options.js";
|
|
31
37
|
import { resolveCliVersion } from "./version.js";
|
|
@@ -53,6 +59,13 @@ export function createCli() {
|
|
|
53
59
|
addJsonAndDaemonHostOptions(addLsOptions(program.command("ls"))).action(withOutput(runLsCommand));
|
|
54
60
|
addJsonAndDaemonHostOptions(addRunOptions(program.command("run"))).action(withOutput(runRunCommand));
|
|
55
61
|
addJsonAndDaemonHostOptions(addImportOptions(program.command("import"))).action(withOutput(runImportCommand));
|
|
62
|
+
addJsonAndDaemonHostOptions(program
|
|
63
|
+
.command("clone")
|
|
64
|
+
.description("Clone a GitHub repo and register it as a Otto workspace")
|
|
65
|
+
.argument("<repo>", "GitHub repo in owner/repo format or a full git remote URL")
|
|
66
|
+
.requiredOption("--dir <path>", "Parent directory to clone into (for example: ~/workspace)"))
|
|
67
|
+
.addOption(new Option("--protocol <protocol>", "Protocol for owner/repo shorthand repositories").choices(["https", "ssh"]))
|
|
68
|
+
.action(withOutput(runCloneCommand));
|
|
56
69
|
addDaemonHostOption(addAttachOptions(program.command("attach"))).action(runAttachCommand);
|
|
57
70
|
addDaemonHostOption(addLogsOptions(program.command("logs"))).action(runLogsCommand);
|
|
58
71
|
addJsonAndDaemonHostOptions(addStopOptions(program.command("stop"))).action(withOutput(runStopCommand));
|
|
@@ -93,22 +106,29 @@ export function createCli() {
|
|
|
93
106
|
program.addCommand(createAgentCommand());
|
|
94
107
|
// Daemon commands
|
|
95
108
|
program.addCommand(createDaemonCommand());
|
|
109
|
+
program.addCommand(createHubCommand());
|
|
96
110
|
// Chat commands
|
|
97
111
|
program.addCommand(createChatCommand());
|
|
98
112
|
// Terminal commands
|
|
99
113
|
program.addCommand(createTerminalCommand());
|
|
114
|
+
// Workspace script commands
|
|
115
|
+
program.addCommand(createScriptCommand());
|
|
100
116
|
// Loop commands
|
|
101
117
|
program.addCommand(createLoopCommand());
|
|
102
118
|
// Schedule commands
|
|
103
119
|
program.addCommand(createScheduleCommand());
|
|
120
|
+
program.addCommand(createHeartbeatCommand());
|
|
104
121
|
// Permission commands
|
|
105
122
|
program.addCommand(createPermitCommand());
|
|
106
123
|
// Provider commands
|
|
107
124
|
program.addCommand(createProviderCommand());
|
|
108
125
|
// Speech model commands
|
|
109
126
|
program.addCommand(createSpeechCommand());
|
|
110
|
-
//
|
|
111
|
-
program.addCommand(
|
|
127
|
+
// Workspace commands
|
|
128
|
+
program.addCommand(createWorkspaceCommand());
|
|
129
|
+
// COMPAT(worktreeCli): legacy command alias added before workspace was the product unit.
|
|
130
|
+
// Added in v0.2.0; remove after 2027-01-17.
|
|
131
|
+
program.addCommand(createWorktreeCommand(), { hidden: true });
|
|
112
132
|
// Local AI (otto-brain)
|
|
113
133
|
program.addCommand(createBrainCommand());
|
|
114
134
|
return program;
|
|
@@ -22,7 +22,7 @@ export type AgentDeleteScope = "active" | "archived" | "both";
|
|
|
22
22
|
* `--all` / `--cwd` still mean active-only.
|
|
23
23
|
*
|
|
24
24
|
* Both flags at once is contradictory ("only archived" and "also archived"), so
|
|
25
|
-
* it is refused rather than guessed
|
|
25
|
+
* it is refused rather than guessed - this command is irreversible.
|
|
26
26
|
*/
|
|
27
27
|
export declare function resolveAgentDeleteScope(options: {
|
|
28
28
|
archived?: boolean;
|
|
@@ -20,7 +20,7 @@ export const deleteSchema = {
|
|
|
20
20
|
* `--all` / `--cwd` still mean active-only.
|
|
21
21
|
*
|
|
22
22
|
* Both flags at once is contradictory ("only archived" and "also archived"), so
|
|
23
|
-
* it is refused rather than guessed
|
|
23
|
+
* it is refused rather than guessed - this command is irreversible.
|
|
24
24
|
*/
|
|
25
25
|
export function resolveAgentDeleteScope(options) {
|
|
26
26
|
if (options.archived && options.includeArchived) {
|
|
@@ -97,7 +97,7 @@ export async function runDeleteCommand(id, options, _command) {
|
|
|
97
97
|
const deleteResults = await Promise.all(agents.map(async (agent) => {
|
|
98
98
|
try {
|
|
99
99
|
if (agent.status === "running") {
|
|
100
|
-
await client.cancelAgent(agent.id);
|
|
100
|
+
await client.cancelAgent(agent.id).catch(() => { });
|
|
101
101
|
}
|
|
102
102
|
await client.deleteAgent(agent.id);
|
|
103
103
|
return { ok: true, id: agent.id };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { CommandOptions, SingleResult } from "../../output/index.js";
|
|
3
|
+
interface AgentDetachResult {
|
|
4
|
+
agentId: string;
|
|
5
|
+
status: "detached";
|
|
6
|
+
}
|
|
7
|
+
export declare function runDetachCommand(agentIdArg: string, options: CommandOptions, _command: Command): Promise<SingleResult<AgentDetachResult>>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=detach.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { connectToDaemon, getDaemonHost, resolveAgentId } from "../../utils/client.js";
|
|
2
|
+
const detachSchema = {
|
|
3
|
+
idField: "agentId",
|
|
4
|
+
columns: [
|
|
5
|
+
{ header: "AGENT ID", field: "agentId" },
|
|
6
|
+
{ header: "STATUS", field: "status" },
|
|
7
|
+
],
|
|
8
|
+
};
|
|
9
|
+
export async function runDetachCommand(agentIdArg, options, _command) {
|
|
10
|
+
const host = getDaemonHost({ host: options.host });
|
|
11
|
+
let client;
|
|
12
|
+
try {
|
|
13
|
+
client = await connectToDaemon({ host: options.host });
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
17
|
+
throw {
|
|
18
|
+
code: "DAEMON_NOT_RUNNING",
|
|
19
|
+
message: `Cannot connect to daemon at ${host}: ${message}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const payload = await client.fetchAgents({ filter: { includeArchived: true } });
|
|
24
|
+
const agentId = resolveAgentId(agentIdArg, payload.entries.map((entry) => entry.agent));
|
|
25
|
+
if (!agentId) {
|
|
26
|
+
throw {
|
|
27
|
+
code: "AGENT_NOT_FOUND",
|
|
28
|
+
message: `Agent not found: ${agentIdArg}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
await client.detachAgent(agentId);
|
|
32
|
+
return { type: "single", data: { agentId, status: "detached" }, schema: detachSchema };
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
await client.close().catch(() => undefined);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=detach.js.map
|
|
@@ -13,6 +13,8 @@ import { addAttachOptions, runAttachCommand } from "./attach.js";
|
|
|
13
13
|
import { addReloadOptions, runReloadCommand } from "./reload.js";
|
|
14
14
|
import { addImportOptions, runImportCommand } from "./import.js";
|
|
15
15
|
import { runUpdateCommand } from "./update.js";
|
|
16
|
+
import { runDetachCommand } from "./detach.js";
|
|
17
|
+
import { addOpenOptions, runOpenCommand } from "./open.js";
|
|
16
18
|
import { withOutput } from "../../output/index.js";
|
|
17
19
|
import { addDaemonHostOption, addJsonAndDaemonHostOptions, collectMultiple, } from "../../utils/command-options.js";
|
|
18
20
|
export function createAgentCommand() {
|
|
@@ -23,6 +25,7 @@ export function createAgentCommand() {
|
|
|
23
25
|
addJsonAndDaemonHostOptions(addImportOptions(agent.command("import"))).action(withOutput(runImportCommand));
|
|
24
26
|
addDaemonHostOption(addAttachOptions(agent.command("attach"))).action(runAttachCommand);
|
|
25
27
|
addDaemonHostOption(addLogsOptions(agent.command("logs"))).action(runLogsCommand);
|
|
28
|
+
addJsonAndDaemonHostOptions(addOpenOptions(agent.command("open"))).action(withOutput(runOpenCommand));
|
|
26
29
|
addJsonAndDaemonHostOptions(addStopOptions(agent.command("stop"))).action(withOutput(runStopCommand));
|
|
27
30
|
addJsonAndDaemonHostOptions(addDeleteOptions(agent.command("delete"))).action(withOutput(runDeleteCommand));
|
|
28
31
|
addJsonAndDaemonHostOptions(addSendOptions(agent.command("send"))).action(withOutput(runSendCommand));
|
|
@@ -37,11 +40,16 @@ export function createAgentCommand() {
|
|
|
37
40
|
.option("--list", "List available modes for this agent")).action(withOutput(runModeCommand));
|
|
38
41
|
addJsonAndDaemonHostOptions(addArchiveOptions(agent.command("archive"))).action(withOutput(runArchiveCommand));
|
|
39
42
|
addJsonAndDaemonHostOptions(addReloadOptions(agent.command("reload"))).action(withOutput(runReloadCommand));
|
|
43
|
+
addJsonAndDaemonHostOptions(agent
|
|
44
|
+
.command("detach")
|
|
45
|
+
.description("Make a subagent independent without stopping or moving it")
|
|
46
|
+
.argument("<id>", "Agent ID, prefix, or name")).action(withOutput(runDetachCommand));
|
|
40
47
|
addJsonAndDaemonHostOptions(agent
|
|
41
48
|
.command("update")
|
|
42
|
-
.description("Update an agent's metadata")
|
|
49
|
+
.description("Update an agent's settings or metadata")
|
|
43
50
|
.argument("<id>", "Agent ID (or prefix)")
|
|
44
51
|
.option("--name <name>", "Update the agent's display name")
|
|
52
|
+
.option("--thinking <id>", "Update the agent's thinking option ID")
|
|
45
53
|
.option("--label <label>", "Add/set label(s) on the agent (can be used multiple times or comma-separated)", collectMultiple, [])).action(withOutput(runUpdateCommand));
|
|
46
54
|
return agent;
|
|
47
55
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { CommandOptions, SingleResult } from "../../output/index.js";
|
|
3
|
+
interface OpenAgentResult {
|
|
4
|
+
agentId: string;
|
|
5
|
+
serverId: string;
|
|
6
|
+
status: "opened";
|
|
7
|
+
}
|
|
8
|
+
export declare function addOpenOptions(command: Command): Command;
|
|
9
|
+
export declare function runOpenCommand(agentIdArg: string, options: CommandOptions, _command: Command): Promise<SingleResult<OpenAgentResult>>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=open.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { buildDaemonConnectionCommandError, connectToDaemon } from "../../utils/client.js";
|
|
2
|
+
import { openDesktopWithAgent } from "../open.js";
|
|
3
|
+
const openAgentSchema = {
|
|
4
|
+
idField: "agentId",
|
|
5
|
+
columns: [
|
|
6
|
+
{ header: "AGENT ID", field: "agentId" },
|
|
7
|
+
{ header: "SERVER ID", field: "serverId" },
|
|
8
|
+
{ header: "STATUS", field: "status" },
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
export function addOpenOptions(command) {
|
|
12
|
+
return command
|
|
13
|
+
.description("Open an existing agent in Otto Desktop")
|
|
14
|
+
.argument("<agent-id>", "Existing agent ID")
|
|
15
|
+
.option("--server <server-id>", "Server ID (defaults to the local daemon)");
|
|
16
|
+
}
|
|
17
|
+
async function resolveServerId(options) {
|
|
18
|
+
const explicitServerId = typeof options.server === "string" ? options.server.trim() : "";
|
|
19
|
+
if (explicitServerId) {
|
|
20
|
+
return explicitServerId;
|
|
21
|
+
}
|
|
22
|
+
let client;
|
|
23
|
+
try {
|
|
24
|
+
client = await connectToDaemon({ host: options.host });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw buildDaemonConnectionCommandError({ host: options.host, error });
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const serverId = client.getLastServerInfoMessage()?.serverId.trim();
|
|
31
|
+
if (!serverId) {
|
|
32
|
+
const error = {
|
|
33
|
+
code: "SERVER_ID_UNAVAILABLE",
|
|
34
|
+
message: "The daemon did not report a server ID.",
|
|
35
|
+
};
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
return serverId;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await client.close().catch(() => { });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function runOpenCommand(agentIdArg, options, _command) {
|
|
45
|
+
const agentId = agentIdArg.trim();
|
|
46
|
+
if (!agentId) {
|
|
47
|
+
const error = {
|
|
48
|
+
code: "MISSING_AGENT_ID",
|
|
49
|
+
message: "Agent ID is required.",
|
|
50
|
+
};
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
const serverId = await resolveServerId(options);
|
|
54
|
+
await openDesktopWithAgent({ serverId, agentId });
|
|
55
|
+
return {
|
|
56
|
+
type: "single",
|
|
57
|
+
data: { agentId, serverId, status: "opened" },
|
|
58
|
+
schema: openAgentSchema,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=open.js.map
|
|
@@ -14,6 +14,7 @@ export interface AgentRunResult {
|
|
|
14
14
|
/** Schema for agent run output */
|
|
15
15
|
export declare const agentRunSchema: OutputSchema<AgentRunResult>;
|
|
16
16
|
export interface AgentRunOptions extends CommandOptions {
|
|
17
|
+
background?: boolean;
|
|
17
18
|
detach?: boolean;
|
|
18
19
|
title?: string;
|
|
19
20
|
name?: string;
|
|
@@ -22,8 +23,15 @@ export interface AgentRunOptions extends CommandOptions {
|
|
|
22
23
|
thinking?: string;
|
|
23
24
|
mode?: string;
|
|
24
25
|
personality?: string;
|
|
26
|
+
newWorkspace?: string;
|
|
25
27
|
worktree?: string;
|
|
28
|
+
worktreeMode?: string;
|
|
29
|
+
worktreeSlug?: string;
|
|
30
|
+
newBranch?: string;
|
|
26
31
|
base?: string;
|
|
32
|
+
branch?: string;
|
|
33
|
+
prNumber?: string;
|
|
34
|
+
forge?: string;
|
|
27
35
|
workspace?: string;
|
|
28
36
|
image?: string[];
|
|
29
37
|
cwd?: string;
|
|
@@ -41,5 +49,32 @@ export declare function resolveStructuredResponseMessage(options: {
|
|
|
41
49
|
agentId: string;
|
|
42
50
|
lastMessage: string | null;
|
|
43
51
|
}): Promise<string | null>;
|
|
52
|
+
export declare function validateRunOptions(prompt: string, options: AgentRunOptions, outputSchema: unknown): void;
|
|
53
|
+
interface RunWorkspace {
|
|
54
|
+
id?: string;
|
|
55
|
+
cwd: string;
|
|
56
|
+
}
|
|
57
|
+
export interface RunWorkspaceLookupClient {
|
|
58
|
+
fetchWorkspaces(options: {
|
|
59
|
+
filter: {
|
|
60
|
+
query: string;
|
|
61
|
+
};
|
|
62
|
+
page: {
|
|
63
|
+
limit: number;
|
|
64
|
+
};
|
|
65
|
+
}): Promise<{
|
|
66
|
+
entries: Array<{
|
|
67
|
+
id: string;
|
|
68
|
+
workspaceDirectory: string;
|
|
69
|
+
}>;
|
|
70
|
+
pageInfo: {
|
|
71
|
+
nextCursor: string | null;
|
|
72
|
+
};
|
|
73
|
+
}>;
|
|
74
|
+
}
|
|
75
|
+
export declare function resolveExistingRunWorkspace(client: RunWorkspaceLookupClient, workspaceId: string): Promise<RunWorkspace>;
|
|
44
76
|
export declare function runRunCommand(prompt: string, options: AgentRunOptions, _command: Command): Promise<SingleResult<AgentRunResult>>;
|
|
77
|
+
export declare function resolveRunCallerAgentId(env?: {
|
|
78
|
+
OTTO_AGENT_ID?: string;
|
|
79
|
+
}): string | undefined;
|
|
45
80
|
//# sourceMappingURL=run.d.ts.map
|
|
@@ -7,28 +7,41 @@ import { lookup } from "mime-types";
|
|
|
7
7
|
import { parseDuration } from "../../utils/duration.js";
|
|
8
8
|
import { collectMultiple } from "../../utils/command-options.js";
|
|
9
9
|
import { assertNoConflictingModelInputs, resolveProviderAndModel, } from "../../utils/provider-model.js";
|
|
10
|
+
import { buildWorkspaceSource } from "../workspace/create.js";
|
|
10
11
|
export { resolveProviderAndModel } from "../../utils/provider-model.js";
|
|
11
12
|
export function addRunOptions(cmd) {
|
|
12
|
-
return cmd
|
|
13
|
+
return (cmd
|
|
13
14
|
.description("Create and start an agent with a task")
|
|
14
15
|
.argument("<prompt>", "The task/prompt for the agent")
|
|
15
|
-
.option("-d, --
|
|
16
|
+
.option("-d, --background", "Run in background")
|
|
17
|
+
// COMPAT(detachRunFlag): --detach used to mean background execution, not
|
|
18
|
+
// ownership transfer. Added in v0.2.0; remove after 2027-01-17.
|
|
19
|
+
.addOption(new Option("--detach", "Legacy alias for --background").hideHelp())
|
|
16
20
|
.option("--title <title>", "Assign a title to the agent")
|
|
17
21
|
.addOption(new Option("--name <name>", "Hidden alias for --title").hideHelp())
|
|
18
22
|
.option("--provider <provider>", "Agent provider, or provider/model (e.g. codex or codex/gpt-5.4)")
|
|
19
23
|
.option("--model <model>", "Model to use (e.g., claude-sonnet-4-20250514, claude-3-5-haiku-20241022)")
|
|
20
24
|
.option("--thinking <id>", "Thinking option ID to use for this run")
|
|
21
25
|
.option("--mode <mode>", "Provider-specific mode (e.g., plan, default, bypass)")
|
|
26
|
+
// Otto-only: personalities are a fork feature, so this option has no
|
|
27
|
+
// upstream counterpart. See docs/agent-personalities.md.
|
|
22
28
|
.option("--personality <name>", "Spawn as a host personality (name or id): applies its provider/model/mode and identity/prompt. Explicit --provider/--model/--mode override.")
|
|
23
|
-
.option("--
|
|
24
|
-
.
|
|
25
|
-
.option("--
|
|
29
|
+
.option("--new-workspace <local|worktree>", "Create a separate local or worktree workspace")
|
|
30
|
+
.addOption(new Option("--worktree <name>", "Legacy workspace isolation alias").hideHelp())
|
|
31
|
+
.option("--worktree-mode <mode>", "Worktree mode: branch-off, checkout-branch, or checkout-pr")
|
|
32
|
+
.option("--worktree-slug <slug>", "Managed worktree path slug")
|
|
33
|
+
.option("--new-branch <name>", "New branch name for branch-off mode")
|
|
34
|
+
.option("--base <ref>", "Base ref for branch-off mode")
|
|
35
|
+
.option("--branch <name>", "Existing branch for checkout-branch mode")
|
|
36
|
+
.option("--pr-number <n>", "Pull request or change request number for checkout-pr mode")
|
|
37
|
+
.option("--forge <forge>", "Forge for checkout-pr mode")
|
|
38
|
+
.option("--workspace <id>", "Run in an existing workspace (defaults to the caller workspace when agent-scoped)")
|
|
26
39
|
.option("--image <path>", "Attach image(s) to the initial prompt (can be used multiple times)", collectMultiple, [])
|
|
27
40
|
.option("--cwd <path>", "Working directory (default: current)")
|
|
28
41
|
.option("--env <key=value>", "Set environment variable(s) for the agent process (can be used multiple times)", collectMultiple, [])
|
|
29
42
|
.option("--label <key=value>", "Add label(s) to the agent (can be used multiple times)", collectMultiple, [])
|
|
30
43
|
.option("--wait-timeout <duration>", "Maximum time to wait for agent to finish (e.g., 30s, 5m, 1h). Default: no limit")
|
|
31
|
-
.option("--output-schema <schema>", "Output JSON matching the provided schema file path or inline JSON schema");
|
|
44
|
+
.option("--output-schema <schema>", "Output JSON matching the provided schema file path or inline JSON schema"));
|
|
32
45
|
}
|
|
33
46
|
/** Schema for agent run output */
|
|
34
47
|
export const agentRunSchema = {
|
|
@@ -41,6 +54,23 @@ export const agentRunSchema = {
|
|
|
41
54
|
{ header: "TITLE", field: "title", width: 20 },
|
|
42
55
|
],
|
|
43
56
|
};
|
|
57
|
+
function resolveNewWorkspaceKind(options) {
|
|
58
|
+
return options.newWorkspace ?? (options.worktree ? "worktree" : undefined);
|
|
59
|
+
}
|
|
60
|
+
function buildRunWorkspaceSource(options, cwd) {
|
|
61
|
+
const newWorkspace = resolveNewWorkspaceKind(options) ?? "local";
|
|
62
|
+
return buildWorkspaceSource({
|
|
63
|
+
isolation: newWorkspace,
|
|
64
|
+
path: cwd,
|
|
65
|
+
mode: options.worktreeMode,
|
|
66
|
+
worktreeSlug: options.worktreeSlug ?? options.worktree,
|
|
67
|
+
newBranch: options.newBranch,
|
|
68
|
+
base: options.base,
|
|
69
|
+
branch: options.branch,
|
|
70
|
+
prNumber: options.prNumber,
|
|
71
|
+
forge: options.forge,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
44
74
|
function toRunResult(agent, statusOverride) {
|
|
45
75
|
return {
|
|
46
76
|
agentId: agent.id,
|
|
@@ -209,47 +239,92 @@ function structuredRunSchema(output) {
|
|
|
209
239
|
serialize: () => output,
|
|
210
240
|
};
|
|
211
241
|
}
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!prompt || prompt.trim().length === 0) {
|
|
242
|
+
function validateRunWorkspaceOptions(options) {
|
|
243
|
+
const newWorkspace = resolveNewWorkspaceKind(options);
|
|
244
|
+
if (options.newWorkspace &&
|
|
245
|
+
options.newWorkspace !== "local" &&
|
|
246
|
+
options.newWorkspace !== "worktree") {
|
|
218
247
|
throw {
|
|
219
|
-
code: "
|
|
220
|
-
message:
|
|
221
|
-
details: "
|
|
248
|
+
code: "INVALID_OPTIONS",
|
|
249
|
+
message: `Unsupported new workspace kind: ${options.newWorkspace}`,
|
|
250
|
+
details: "Use --new-workspace local or --new-workspace worktree",
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (options.newWorkspace && options.worktree) {
|
|
254
|
+
throw {
|
|
255
|
+
code: "INVALID_OPTIONS",
|
|
256
|
+
message: "--new-workspace and --worktree cannot be combined",
|
|
257
|
+
details: "Use --new-workspace worktree and the supported worktree options",
|
|
222
258
|
};
|
|
223
259
|
}
|
|
224
|
-
|
|
260
|
+
const hasWorktreeCreationOptions = [
|
|
261
|
+
options.worktreeMode,
|
|
262
|
+
options.worktreeSlug,
|
|
263
|
+
options.newBranch,
|
|
264
|
+
options.base,
|
|
265
|
+
options.branch,
|
|
266
|
+
options.prNumber,
|
|
267
|
+
options.forge,
|
|
268
|
+
].some((value) => value !== undefined);
|
|
269
|
+
if (hasWorktreeCreationOptions && newWorkspace !== "worktree") {
|
|
270
|
+
throw {
|
|
271
|
+
code: "INVALID_OPTIONS",
|
|
272
|
+
message: "Worktree options require --new-workspace worktree",
|
|
273
|
+
details: "Usage: otto run --new-workspace worktree [worktree options] <prompt>",
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (newWorkspace === "worktree") {
|
|
277
|
+
try {
|
|
278
|
+
buildRunWorkspaceSource(options, options.cwd ?? process.cwd());
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
throw {
|
|
282
|
+
code: "INVALID_OPTIONS",
|
|
283
|
+
message: error instanceof Error ? error.message : String(error),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (options.newWorkspace && options.workspace) {
|
|
225
288
|
throw {
|
|
226
289
|
code: "INVALID_OPTIONS",
|
|
227
|
-
message: "--
|
|
228
|
-
details: "
|
|
290
|
+
message: "--new-workspace and --workspace cannot be combined",
|
|
291
|
+
details: "Select an existing workspace or explicitly create a new one",
|
|
229
292
|
};
|
|
230
293
|
}
|
|
294
|
+
// COMPAT(worktreeRunFlag): --worktree implies a new worktree-isolated workspace.
|
|
295
|
+
// Added in v0.2.0; remove after 2027-01-17.
|
|
231
296
|
if (options.worktree && options.workspace) {
|
|
232
297
|
throw {
|
|
233
298
|
code: "INVALID_OPTIONS",
|
|
234
299
|
message: "--worktree and --workspace cannot be combined",
|
|
235
|
-
details: "--workspace
|
|
300
|
+
details: "Use --new-workspace worktree instead of the legacy --worktree flag",
|
|
236
301
|
};
|
|
237
302
|
}
|
|
238
|
-
|
|
303
|
+
}
|
|
304
|
+
export function validateRunOptions(prompt, options, outputSchema) {
|
|
305
|
+
// Before anything opens a socket: a contradictory command line should say so, not report that
|
|
306
|
+
// the daemon is unreachable. Full provider/model resolution still happens after connecting,
|
|
307
|
+
// because it needs the host.
|
|
308
|
+
assertNoConflictingModelInputs(options.provider, options.model);
|
|
309
|
+
if (!prompt || prompt.trim().length === 0) {
|
|
239
310
|
throw {
|
|
240
|
-
code: "
|
|
241
|
-
message: "
|
|
242
|
-
details: "
|
|
311
|
+
code: "MISSING_PROMPT",
|
|
312
|
+
message: "A prompt is required",
|
|
313
|
+
details: "Usage: otto agent run [options] <prompt>",
|
|
243
314
|
};
|
|
244
315
|
}
|
|
245
|
-
|
|
316
|
+
validateRunWorkspaceOptions(options);
|
|
317
|
+
if (outputSchema && runsInBackground(options)) {
|
|
246
318
|
throw {
|
|
247
319
|
code: "INVALID_OPTIONS",
|
|
248
|
-
message: "--output-schema cannot be used with --
|
|
320
|
+
message: "--output-schema cannot be used with --background",
|
|
249
321
|
details: "Structured output requires waiting for the agent to finish",
|
|
250
322
|
};
|
|
251
323
|
}
|
|
252
324
|
}
|
|
325
|
+
function runsInBackground(options) {
|
|
326
|
+
return Boolean(options.background || options.detach);
|
|
327
|
+
}
|
|
253
328
|
function parseWaitTimeoutOption(waitTimeout) {
|
|
254
329
|
if (!waitTimeout)
|
|
255
330
|
return 0;
|
|
@@ -338,36 +413,45 @@ async function connectToDaemonOrThrow(hostOption, host) {
|
|
|
338
413
|
};
|
|
339
414
|
}
|
|
340
415
|
}
|
|
416
|
+
export async function resolveExistingRunWorkspace(client, workspaceId) {
|
|
417
|
+
const result = await client.fetchWorkspaces({
|
|
418
|
+
filter: { query: workspaceId },
|
|
419
|
+
page: { limit: 200 },
|
|
420
|
+
});
|
|
421
|
+
const workspace = result.entries.find((entry) => entry.id === workspaceId);
|
|
422
|
+
if (workspace) {
|
|
423
|
+
return { id: workspace.id, cwd: workspace.workspaceDirectory };
|
|
424
|
+
}
|
|
425
|
+
throw {
|
|
426
|
+
code: "WORKSPACE_NOT_FOUND",
|
|
427
|
+
message: `Workspace not found: ${workspaceId}`,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
341
430
|
// Workspace policy for `otto run`. Precedence:
|
|
342
431
|
// 1. --workspace <id> -> run in that existing workspace
|
|
343
|
-
// 2. $
|
|
344
|
-
// 3.
|
|
345
|
-
// 4.
|
|
346
|
-
//
|
|
347
|
-
// $OTTO_WORKSPACE_ID (validateRunOptions), so worktree resolution here never
|
|
348
|
-
// races an existing-workspace selection.
|
|
432
|
+
// 2. $OTTO_AGENT_ID -> daemon resolves the caller's workspace
|
|
433
|
+
// 3. $OTTO_WORKSPACE_ID -> exported by workspace terminals
|
|
434
|
+
// 4. --new-workspace <kind> -> mint a new workspace explicitly
|
|
435
|
+
// 5. bare run -> mint a new local-backed workspace for cwd
|
|
349
436
|
async function resolveRunWorkspace(client, options, cwd) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
const explicit = options.worktree
|
|
353
|
-
? undefined
|
|
354
|
-
: options.workspace?.trim() || process.env.OTTO_WORKSPACE_ID?.trim();
|
|
437
|
+
const newWorkspace = resolveNewWorkspaceKind(options);
|
|
438
|
+
const explicit = newWorkspace ? undefined : options.workspace?.trim();
|
|
355
439
|
if (explicit) {
|
|
356
440
|
console.error(`Using workspace ${explicit}`);
|
|
357
|
-
return
|
|
441
|
+
return resolveExistingRunWorkspace(client, explicit);
|
|
442
|
+
}
|
|
443
|
+
if (!newWorkspace && resolveRunCallerAgentId()) {
|
|
444
|
+
return { cwd };
|
|
445
|
+
}
|
|
446
|
+
const ambientWorkspaceId = newWorkspace ? undefined : process.env.OTTO_WORKSPACE_ID?.trim();
|
|
447
|
+
if (ambientWorkspaceId) {
|
|
448
|
+
console.error(`Using workspace ${ambientWorkspaceId}`);
|
|
449
|
+
return resolveExistingRunWorkspace(client, ambientWorkspaceId);
|
|
358
450
|
}
|
|
359
451
|
// TODO: thread the run `prompt` as firstAgentContext so workspace-level
|
|
360
452
|
// title/branch generation picks up the task description (U8/U6 deferred).
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
source: {
|
|
364
|
-
kind: "worktree",
|
|
365
|
-
cwd,
|
|
366
|
-
worktreeSlug: options.worktree,
|
|
367
|
-
baseBranch: options.base,
|
|
368
|
-
},
|
|
369
|
-
})
|
|
370
|
-
: await client.createWorkspace({ source: { kind: "directory", path: cwd } });
|
|
453
|
+
const source = buildRunWorkspaceSource(options, cwd);
|
|
454
|
+
const result = await client.createWorkspace({ source });
|
|
371
455
|
if (!result.workspace) {
|
|
372
456
|
throw {
|
|
373
457
|
code: "WORKSPACE_CREATE_FAILED",
|
|
@@ -388,6 +472,9 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
388
472
|
const resolvedTitle = options.title ?? options.name;
|
|
389
473
|
const client = await connectToDaemonOrThrow(options.host, host);
|
|
390
474
|
try {
|
|
475
|
+
// --personality reads the host roster, so the brain can only be resolved
|
|
476
|
+
// once a client exists. validateRunOptions has already rejected a
|
|
477
|
+
// contradictory --provider/--model pair without needing one.
|
|
391
478
|
const { providerModel: resolvedProviderModel, modeId: resolvedMode, personalityId, } = await resolveRunBrain(client, options);
|
|
392
479
|
// Resolve working directory
|
|
393
480
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -406,6 +493,7 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
406
493
|
const requestEnv = Object.keys(env).length > 0 ? env : undefined;
|
|
407
494
|
const workspace = await resolveRunWorkspace(client, options, cwd);
|
|
408
495
|
const workspaceId = workspace.id;
|
|
496
|
+
const callerAgentId = resolveRunCallerAgentId();
|
|
409
497
|
const runCwd = workspace.cwd;
|
|
410
498
|
if (outputSchema) {
|
|
411
499
|
let structuredAgent = null;
|
|
@@ -415,6 +503,7 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
415
503
|
provider: resolvedProviderModel.provider,
|
|
416
504
|
cwd: runCwd,
|
|
417
505
|
workspaceId,
|
|
506
|
+
callerAgentId,
|
|
418
507
|
title: resolvedTitle,
|
|
419
508
|
modeId: resolvedMode,
|
|
420
509
|
model: resolvedProviderModel.model,
|
|
@@ -470,6 +559,7 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
470
559
|
provider: resolvedProviderModel.provider,
|
|
471
560
|
cwd: runCwd,
|
|
472
561
|
workspaceId,
|
|
562
|
+
callerAgentId,
|
|
473
563
|
title: resolvedTitle,
|
|
474
564
|
modeId: resolvedMode,
|
|
475
565
|
model: resolvedProviderModel.model,
|
|
@@ -480,8 +570,8 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
480
570
|
env: requestEnv,
|
|
481
571
|
labels: Object.keys(labels).length > 0 ? labels : undefined,
|
|
482
572
|
});
|
|
483
|
-
// Default run behavior is foreground: wait for completion unless
|
|
484
|
-
if (!options
|
|
573
|
+
// Default run behavior is foreground: wait for completion unless background execution is set.
|
|
574
|
+
if (!runsInBackground(options)) {
|
|
485
575
|
const state = await client.waitForFinish(agent.id, waitTimeoutMs);
|
|
486
576
|
await client.close();
|
|
487
577
|
const finalAgent = state.final ?? agent;
|
|
@@ -512,4 +602,7 @@ export async function runRunCommand(prompt, options, _command) {
|
|
|
512
602
|
throw error;
|
|
513
603
|
}
|
|
514
604
|
}
|
|
605
|
+
export function resolveRunCallerAgentId(env = process.env) {
|
|
606
|
+
return env.OTTO_AGENT_ID?.trim() || undefined;
|
|
607
|
+
}
|
|
515
608
|
//# sourceMappingURL=run.js.map
|
|
@@ -153,7 +153,7 @@ export async function runSendCommand(agentIdArg, prompt, options, _command) {
|
|
|
153
153
|
images,
|
|
154
154
|
...(options.queue ? { delivery: "queue" } : {}),
|
|
155
155
|
});
|
|
156
|
-
// A queued message has no run to wait for yet
|
|
156
|
+
// A queued message has no run to wait for yet - it starts when the current
|
|
157
157
|
// turn ends, so report it and return rather than blocking on --wait.
|
|
158
158
|
if (dispatch.queued) {
|
|
159
159
|
await client.close();
|