@otto-code/cli 0.8.18 → 0.9.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/bin/otto +2 -2
- package/dist/cli.js +13 -0
- package/dist/commands/architectural-view/deliver.d.ts +18 -0
- package/dist/commands/architectural-view/deliver.js +60 -0
- package/dist/commands/architectural-view/draft.d.ts +33 -0
- package/dist/commands/architectural-view/draft.js +128 -0
- package/dist/commands/architectural-view/index.d.ts +3 -0
- package/dist/commands/architectural-view/index.js +48 -0
- package/dist/commands/architectural-view/shared.d.ts +7 -0
- package/dist/commands/architectural-view/shared.js +27 -0
- package/dist/commands/artifact/cancel.d.ts +13 -0
- package/dist/commands/artifact/cancel.js +38 -0
- package/dist/commands/artifact/create.d.ts +21 -0
- package/dist/commands/artifact/create.js +56 -0
- package/dist/commands/artifact/get-data.d.ts +5 -0
- package/dist/commands/artifact/get-data.js +24 -0
- package/dist/commands/artifact/index.d.ts +3 -0
- package/dist/commands/artifact/index.js +55 -0
- package/dist/commands/artifact/ls.d.ts +20 -0
- package/dist/commands/artifact/ls.js +72 -0
- package/dist/commands/artifact/move.d.ts +15 -0
- package/dist/commands/artifact/move.js +41 -0
- package/dist/commands/artifact/regenerate.d.ts +12 -0
- package/dist/commands/artifact/regenerate.js +32 -0
- package/dist/commands/artifact/repair.d.ts +13 -0
- package/dist/commands/artifact/repair.js +38 -0
- package/dist/commands/artifact/shared.d.ts +10 -0
- package/dist/commands/artifact/shared.js +62 -0
- package/dist/commands/artifact/update-data.d.ts +13 -0
- package/dist/commands/artifact/update-data.js +43 -0
- package/dist/commands/daemon/index.js +3 -1
- package/dist/commands/daemon/reload.d.ts +10 -0
- package/dist/commands/daemon/reload.js +36 -0
- package/dist/commands/hub/daemon-client.d.ts +7 -0
- package/dist/commands/hub/hub-client/index.d.ts +3 -2
- package/dist/commands/hub/hub-client/index.js +12 -1
- package/dist/commands/hub/hub-client/internal/contracts.d.ts +17 -0
- package/dist/commands/hub/hub-client/internal/contracts.js +14 -0
- package/dist/commands/hub/index.js +3 -1
- package/dist/commands/hub/init-plan.d.ts +3 -0
- package/dist/commands/hub/init-plan.js +8 -4
- package/dist/commands/hub/init.d.ts +18 -3
- package/dist/commands/hub/init.js +218 -78
- package/dist/commands/hub/login.d.ts +2 -0
- package/dist/commands/hub/login.js +8 -2
- package/dist/commands/hub/starter-agent-runtime.d.ts +37 -0
- package/dist/commands/hub/starter-agent-runtime.js +49 -0
- package/dist/commands/hub/starter-trigger.d.ts +10 -0
- package/dist/commands/hub/starter-trigger.js +28 -0
- package/dist/commands/plugin/index.d.ts +14 -0
- package/dist/commands/plugin/index.js +84 -0
- package/dist/commands/plugin/scaffold.d.ts +6 -0
- package/dist/commands/plugin/scaffold.js +329 -0
- package/dist/commands/plugin/shared.d.ts +4 -0
- package/dist/commands/plugin/shared.js +26 -0
- package/dist/commands/project/create.d.ts +10 -0
- package/dist/commands/project/create.js +41 -0
- package/dist/commands/project/delete.d.ts +9 -0
- package/dist/commands/project/delete.js +33 -0
- package/dist/commands/project/index.d.ts +3 -0
- package/dist/commands/project/index.js +28 -0
- package/dist/commands/project/ls.d.ts +5 -0
- package/dist/commands/project/ls.js +19 -0
- package/dist/commands/project/rename.d.ts +15 -0
- package/dist/commands/project/rename.js +50 -0
- package/dist/commands/project/shared.d.ts +11 -0
- package/dist/commands/project/shared.js +18 -0
- package/dist/commands/schedule/shared.js +3 -0
- package/dist/commands/schedule/types.d.ts +4 -0
- package/dist/commands/workflow/graph.d.ts +85 -0
- package/dist/commands/workflow/graph.js +462 -0
- package/dist/commands/workflow/index.d.ts +3 -0
- package/dist/commands/workflow/index.js +43 -0
- package/dist/output/index.d.ts +1 -1
- package/dist/output/pairing.js +1 -1
- package/dist/output/render.js +6 -0
- package/dist/output/types.d.ts +14 -0
- package/package.json +5 -5
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { buildDaemonConnectionCommandError, connectToDaemon } from "../../utils/client.js";
|
|
2
|
+
const projectRenameSchema = {
|
|
3
|
+
idField: "projectId",
|
|
4
|
+
columns: [
|
|
5
|
+
{ header: "PROJECT ID", field: "projectId", width: 20 },
|
|
6
|
+
{ header: "NAME", field: "name", width: 30 },
|
|
7
|
+
],
|
|
8
|
+
};
|
|
9
|
+
export function resolveProjectName(input) {
|
|
10
|
+
if (input.reset) {
|
|
11
|
+
if (input.name !== undefined) {
|
|
12
|
+
throw {
|
|
13
|
+
code: "INVALID_OPTIONS",
|
|
14
|
+
message: "--reset cannot be combined with a name",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const name = input.name?.trim() ?? "";
|
|
20
|
+
if (name.length === 0) {
|
|
21
|
+
throw {
|
|
22
|
+
code: "MISSING_NAME",
|
|
23
|
+
message: "Project name cannot be empty",
|
|
24
|
+
details: "Usage: otto project rename <project-id> <name> | --reset",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return name;
|
|
28
|
+
}
|
|
29
|
+
export async function runRenameCommand(projectId, nameArg, options, _command) {
|
|
30
|
+
const name = resolveProjectName({ name: nameArg, reset: options.reset });
|
|
31
|
+
const client = await connectToDaemon({ host: options.host }).catch((error) => {
|
|
32
|
+
throw buildDaemonConnectionCommandError({ host: options.host, error });
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
const applied = await client.renameProject(projectId, name);
|
|
36
|
+
return {
|
|
37
|
+
type: "single",
|
|
38
|
+
data: { projectId, name: applied.customName },
|
|
39
|
+
schema: projectRenameSchema,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
throw { code: "PROJECT_RENAME_FAILED", message };
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
await client.close().catch(() => undefined);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=rename.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { WorkspaceProjectDescriptorPayload } from "@otto-code/protocol/messages";
|
|
2
|
+
import type { OutputSchema } from "../../output/index.js";
|
|
3
|
+
export interface ProjectRow {
|
|
4
|
+
projectId: string;
|
|
5
|
+
name: string;
|
|
6
|
+
kind: "git" | "non_git" | "directory";
|
|
7
|
+
path: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const projectSchema: OutputSchema<ProjectRow>;
|
|
10
|
+
export declare function toProjectRow(project: WorkspaceProjectDescriptorPayload): ProjectRow;
|
|
11
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const projectSchema = {
|
|
2
|
+
idField: "projectId",
|
|
3
|
+
columns: [
|
|
4
|
+
{ header: "PROJECT ID", field: "projectId", width: 20 },
|
|
5
|
+
{ header: "NAME", field: "name", width: 24 },
|
|
6
|
+
{ header: "KIND", field: "kind", width: 10 },
|
|
7
|
+
{ header: "PATH", field: "path", width: 42 },
|
|
8
|
+
],
|
|
9
|
+
};
|
|
10
|
+
export function toProjectRow(project) {
|
|
11
|
+
return {
|
|
12
|
+
projectId: project.projectId,
|
|
13
|
+
name: project.projectDisplayName,
|
|
14
|
+
kind: project.projectKind,
|
|
15
|
+
path: project.projectRootPath,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -49,6 +49,9 @@ export function formatTarget(target) {
|
|
|
49
49
|
if (target.type === "agent") {
|
|
50
50
|
return `agent:${target.agentId.slice(0, 7)}`;
|
|
51
51
|
}
|
|
52
|
+
if (target.type === "workflow") {
|
|
53
|
+
return `workflow:${target.definitionId}`;
|
|
54
|
+
}
|
|
52
55
|
const modelSuffix = target.config.model ? `/${target.config.model}` : "";
|
|
53
56
|
return `new-agent:${target.config.provider}${modelSuffix}`;
|
|
54
57
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import { type OrchestrationGraph, type WorkflowGraphExport } from "@otto-code/protocol/workflow";
|
|
3
|
+
import type { CommandDiagnostic, CommandOptions, ListResult, OutputSchema, SingleResult } from "../../output/index.js";
|
|
4
|
+
export interface WorkflowGraphCommandOptions extends CommandOptions {
|
|
5
|
+
}
|
|
6
|
+
export interface WorkflowGraphRunOptions extends WorkflowGraphCommandOptions {
|
|
7
|
+
cwd?: string;
|
|
8
|
+
workspace?: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
input?: string[];
|
|
12
|
+
orchestratorProfile?: string;
|
|
13
|
+
orchestratorProvider?: string;
|
|
14
|
+
orchestratorModel?: string;
|
|
15
|
+
orchestratorThinking?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface WorkflowGraphRow {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
description: string | null;
|
|
21
|
+
nodes: number;
|
|
22
|
+
edges: number;
|
|
23
|
+
updatedAt: string | null;
|
|
24
|
+
}
|
|
25
|
+
export declare const workflowGraphSchema: OutputSchema<WorkflowGraphRow>;
|
|
26
|
+
export interface WorkflowGraphInspectRow {
|
|
27
|
+
key: string;
|
|
28
|
+
value: string;
|
|
29
|
+
}
|
|
30
|
+
export interface WorkflowGraphValidationResult {
|
|
31
|
+
file: string;
|
|
32
|
+
valid: true;
|
|
33
|
+
scope: "structural";
|
|
34
|
+
warnings: string[];
|
|
35
|
+
diagnostics: CommandDiagnostic[];
|
|
36
|
+
nodes: number;
|
|
37
|
+
edges: number;
|
|
38
|
+
}
|
|
39
|
+
export interface WorkflowGraphRunResult {
|
|
40
|
+
runId: string;
|
|
41
|
+
graphId: string;
|
|
42
|
+
agentId: string | null;
|
|
43
|
+
workspaceId: string | null;
|
|
44
|
+
}
|
|
45
|
+
export interface WorkflowGraphExportResult {
|
|
46
|
+
graphId: string;
|
|
47
|
+
output: string;
|
|
48
|
+
source: string;
|
|
49
|
+
contentHash: string;
|
|
50
|
+
}
|
|
51
|
+
export interface WorkflowGraphImportCommandResult {
|
|
52
|
+
status: string;
|
|
53
|
+
graphId: string | null;
|
|
54
|
+
source: string | null;
|
|
55
|
+
destination: string | null;
|
|
56
|
+
remediation: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function runWorkflowGraphExportCommand(id: string, options: WorkflowGraphCommandOptions & {
|
|
59
|
+
output?: string;
|
|
60
|
+
}, _command: Command): Promise<SingleResult<WorkflowGraphExportResult>>;
|
|
61
|
+
export declare function runWorkflowGraphImportCommand(file: string, options: WorkflowGraphCommandOptions & {
|
|
62
|
+
cwd?: string;
|
|
63
|
+
confirm?: boolean;
|
|
64
|
+
}, _command: Command): Promise<SingleResult<WorkflowGraphImportCommandResult>>;
|
|
65
|
+
export declare function runWorkflowGraphListCommand(options: WorkflowGraphCommandOptions, _command: Command): Promise<ListResult<WorkflowGraphRow>>;
|
|
66
|
+
export declare function runWorkflowGraphInspectCommand(id: string, options: WorkflowGraphCommandOptions, _command: Command): Promise<ListResult<WorkflowGraphInspectRow>>;
|
|
67
|
+
export declare function runWorkflowGraphValidateCommand(file: string, _options: CommandOptions, _command: Command): Promise<SingleResult<WorkflowGraphValidationResult>>;
|
|
68
|
+
export declare function runWorkflowGraphRunCommand(id: string, options: WorkflowGraphRunOptions, _command: Command): Promise<SingleResult<WorkflowGraphRunResult>>;
|
|
69
|
+
export declare function loadWorkflowGraphFile(file: string): Promise<OrchestrationGraph>;
|
|
70
|
+
export declare function loadWorkflowGraphExportFile(file: string): Promise<WorkflowGraphExport>;
|
|
71
|
+
export declare function parseWorkflowGraphRunInput(id: string, options: WorkflowGraphRunOptions): {
|
|
72
|
+
graphId: string;
|
|
73
|
+
cwd: string;
|
|
74
|
+
workspaceId?: string;
|
|
75
|
+
title?: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
graphInputs: Record<string, string>;
|
|
78
|
+
orchestratorProfile?: string;
|
|
79
|
+
orchestratorProvider?: string;
|
|
80
|
+
orchestratorModel?: string;
|
|
81
|
+
orchestratorThinking?: string;
|
|
82
|
+
};
|
|
83
|
+
export declare function parseGraphInputAssignments(values: readonly string[] | undefined): Record<string, string>;
|
|
84
|
+
export declare function resolveWorkflowGraphInputs(graph: OrchestrationGraph, supplied: Record<string, string>): Record<string, string>;
|
|
85
|
+
//# sourceMappingURL=graph.d.ts.map
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { OrchestrationGraphSchema, reviewOrchestrationGraph, validateGraphDocument, validateOrchestrationGraph, } from "@otto-code/protocol/workflow";
|
|
4
|
+
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
|
5
|
+
export const workflowGraphSchema = {
|
|
6
|
+
idField: "id",
|
|
7
|
+
columns: [
|
|
8
|
+
{ header: "ID", field: "id", width: 24 },
|
|
9
|
+
{ header: "NAME", field: "name", width: 28 },
|
|
10
|
+
{ header: "NODES", field: "nodes", width: 7, align: "right" },
|
|
11
|
+
{ header: "EDGES", field: "edges", width: 7, align: "right" },
|
|
12
|
+
{ header: "UPDATED", field: "updatedAt", width: 24 },
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
const graphValidationSchema = {
|
|
16
|
+
idField: "file",
|
|
17
|
+
columns: [
|
|
18
|
+
{ header: "FILE", field: "file", width: 48 },
|
|
19
|
+
{ header: "SCOPE", field: "scope", width: 12 },
|
|
20
|
+
{ header: "NODES", field: "nodes", width: 7, align: "right" },
|
|
21
|
+
{ header: "EDGES", field: "edges", width: 7, align: "right" },
|
|
22
|
+
{ header: "WARNINGS", field: (result) => result.warnings.length, width: 9, align: "right" },
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
const graphRunSchema = {
|
|
26
|
+
idField: "runId",
|
|
27
|
+
columns: [
|
|
28
|
+
{ header: "RUN ID", field: "runId", width: 24 },
|
|
29
|
+
{ header: "GRAPH ID", field: "graphId", width: 24 },
|
|
30
|
+
{ header: "ORCHESTRATOR CHAT", field: "agentId", width: 24 },
|
|
31
|
+
{ header: "WORKSPACE", field: "workspaceId", width: 24 },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
const graphExportSchema = {
|
|
35
|
+
idField: "graphId",
|
|
36
|
+
columns: [
|
|
37
|
+
{ header: "GRAPH ID", field: "graphId", width: 24 },
|
|
38
|
+
{ header: "OUTPUT", field: "output", width: 48 },
|
|
39
|
+
{ header: "SOURCE", field: "source", width: 30 },
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
const graphImportSchema = {
|
|
43
|
+
idField: "graphId",
|
|
44
|
+
columns: [
|
|
45
|
+
{ header: "STATUS", field: "status", width: 18 },
|
|
46
|
+
{ header: "GRAPH ID", field: "graphId", width: 24 },
|
|
47
|
+
{ header: "DESTINATION", field: "destination", width: 32 },
|
|
48
|
+
{ header: "REMEDIATION", field: "remediation", width: 72 },
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
export async function runWorkflowGraphExportCommand(id, options, _command) {
|
|
52
|
+
const graphId = id.trim();
|
|
53
|
+
const output = options.output?.trim();
|
|
54
|
+
if (!graphId)
|
|
55
|
+
throw { code: "INVALID_GRAPH_ID", message: "Graph id cannot be empty" };
|
|
56
|
+
if (!output)
|
|
57
|
+
throw {
|
|
58
|
+
code: "MISSING_EXPORT_OUTPUT",
|
|
59
|
+
message: "--output is required for Graph export",
|
|
60
|
+
};
|
|
61
|
+
const client = await connectWorkflowGraphClient(options.host);
|
|
62
|
+
try {
|
|
63
|
+
const exported = await client.exportWorkflowGraph(graphId);
|
|
64
|
+
const file = resolve(output);
|
|
65
|
+
await writeFile(file, `${JSON.stringify(exported, null, 2)}\n`, "utf8");
|
|
66
|
+
return {
|
|
67
|
+
type: "single",
|
|
68
|
+
data: {
|
|
69
|
+
graphId,
|
|
70
|
+
output: file,
|
|
71
|
+
source: exported.source.storeKey,
|
|
72
|
+
contentHash: exported.contentHash,
|
|
73
|
+
},
|
|
74
|
+
schema: graphExportSchema,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
throw toWorkflowGraphCommandError("WORKFLOW_GRAPH_EXPORT_FAILED", "export Graph", error);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
await client.close().catch(() => { });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function runWorkflowGraphImportCommand(file, options, _command) {
|
|
85
|
+
const cwd = options.cwd?.trim();
|
|
86
|
+
if (!cwd)
|
|
87
|
+
throw {
|
|
88
|
+
code: "MISSING_CWD",
|
|
89
|
+
message: "--cwd is required to select the destination project store",
|
|
90
|
+
};
|
|
91
|
+
const exported = await loadWorkflowGraphExportFile(file);
|
|
92
|
+
const client = await connectWorkflowGraphClient(options.host);
|
|
93
|
+
try {
|
|
94
|
+
const result = await client.importWorkflowGraph({
|
|
95
|
+
cwd,
|
|
96
|
+
export: exported,
|
|
97
|
+
confirmed: options.confirm === true,
|
|
98
|
+
});
|
|
99
|
+
return { type: "single", data: toGraphImportCommandResult(result), schema: graphImportSchema };
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw toWorkflowGraphCommandError("WORKFLOW_GRAPH_IMPORT_FAILED", "import Graph", error);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await client.close().catch(() => { });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export async function runWorkflowGraphListCommand(options, _command) {
|
|
109
|
+
const client = await connectWorkflowGraphClient(options.host);
|
|
110
|
+
try {
|
|
111
|
+
const graphs = await client.listOrchestrationGraphs();
|
|
112
|
+
return { type: "list", data: graphs.map(toWorkflowGraphRow), schema: workflowGraphSchema };
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw toWorkflowGraphCommandError("WORKFLOW_GRAPH_LIST_FAILED", "list Graphs", error);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
await client.close().catch(() => { });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export async function runWorkflowGraphInspectCommand(id, options, _command) {
|
|
122
|
+
const client = await connectWorkflowGraphClient(options.host);
|
|
123
|
+
try {
|
|
124
|
+
const graph = await findWorkflowGraph(client, id);
|
|
125
|
+
const rows = [
|
|
126
|
+
{ key: "Id", value: graph.id },
|
|
127
|
+
{ key: "Name", value: graph.name },
|
|
128
|
+
{ key: "Description", value: graph.description ?? "" },
|
|
129
|
+
{ key: "Nodes", value: String(graph.nodes.length) },
|
|
130
|
+
{ key: "Edges", value: String(graph.edges?.length ?? 0) },
|
|
131
|
+
{ key: "UpdatedAt", value: graph.updatedAt ?? "" },
|
|
132
|
+
];
|
|
133
|
+
return {
|
|
134
|
+
type: "list",
|
|
135
|
+
data: rows,
|
|
136
|
+
schema: {
|
|
137
|
+
idField: "key",
|
|
138
|
+
columns: [
|
|
139
|
+
{ header: "KEY", field: "key", width: 16 },
|
|
140
|
+
{ header: "VALUE", field: "value", width: 84 },
|
|
141
|
+
],
|
|
142
|
+
serialize: () => graph,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
throw toWorkflowGraphCommandError("WORKFLOW_GRAPH_INSPECT_FAILED", "inspect Graph", error);
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
await client.close().catch(() => { });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export async function runWorkflowGraphValidateCommand(file, _options, _command) {
|
|
154
|
+
const graph = await loadWorkflowGraphFile(file);
|
|
155
|
+
const diagnostics = validateGraphDocument(graph);
|
|
156
|
+
const problems = validateOrchestrationGraph(graph);
|
|
157
|
+
if (problems.length > 0) {
|
|
158
|
+
const documentErrors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
159
|
+
throw {
|
|
160
|
+
code: documentErrors.length > 0
|
|
161
|
+
? "WORKFLOW_GRAPH_DOCUMENT_INVALID"
|
|
162
|
+
: "WORKFLOW_GRAPH_NOT_STRUCTURALLY_VALID",
|
|
163
|
+
message: documentErrors.length > 0
|
|
164
|
+
? `Graph ${resolve(file)} has an unsupported document format`
|
|
165
|
+
: `Graph ${resolve(file)} is not structurally valid`,
|
|
166
|
+
details: problems,
|
|
167
|
+
diagnostics,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
type: "single",
|
|
172
|
+
data: {
|
|
173
|
+
file: resolve(file),
|
|
174
|
+
valid: true,
|
|
175
|
+
scope: "structural",
|
|
176
|
+
warnings: reviewOrchestrationGraph(graph),
|
|
177
|
+
diagnostics,
|
|
178
|
+
nodes: graph.nodes.length,
|
|
179
|
+
edges: graph.edges?.length ?? 0,
|
|
180
|
+
},
|
|
181
|
+
schema: graphValidationSchema,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
export async function runWorkflowGraphRunCommand(id, options, _command) {
|
|
185
|
+
const input = parseWorkflowGraphRunInput(id, options);
|
|
186
|
+
const client = await connectWorkflowGraphClient(options.host);
|
|
187
|
+
try {
|
|
188
|
+
const graph = await findWorkflowGraph(client, input.graphId);
|
|
189
|
+
const graphInputs = resolveWorkflowGraphInputs(graph, input.graphInputs);
|
|
190
|
+
const workspaceId = await resolveWorkflowGraphWorkspaceId(client, input);
|
|
191
|
+
const started = await client.startWorkflow({
|
|
192
|
+
flavor: "graph",
|
|
193
|
+
cwd: input.cwd,
|
|
194
|
+
workspaceId,
|
|
195
|
+
...(input.title ? { title: input.title } : {}),
|
|
196
|
+
...(input.description ? { description: input.description } : {}),
|
|
197
|
+
...(input.orchestratorProfile
|
|
198
|
+
? { orchestratorPersonalityId: input.orchestratorProfile }
|
|
199
|
+
: {}),
|
|
200
|
+
...(input.orchestratorProvider ? { orchestratorProvider: input.orchestratorProvider } : {}),
|
|
201
|
+
...(input.orchestratorModel ? { orchestratorModel: input.orchestratorModel } : {}),
|
|
202
|
+
...(input.orchestratorThinking
|
|
203
|
+
? { orchestratorThinkingOptionId: input.orchestratorThinking }
|
|
204
|
+
: {}),
|
|
205
|
+
graphId: input.graphId,
|
|
206
|
+
graphInputs,
|
|
207
|
+
});
|
|
208
|
+
if (!started.runId) {
|
|
209
|
+
throw new Error("Daemon did not return a Workflow run ID.");
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
type: "single",
|
|
213
|
+
data: {
|
|
214
|
+
runId: started.runId,
|
|
215
|
+
graphId: input.graphId,
|
|
216
|
+
agentId: started.agentId ?? null,
|
|
217
|
+
workspaceId: started.workspaceId ?? null,
|
|
218
|
+
},
|
|
219
|
+
schema: graphRunSchema,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
throw toWorkflowGraphCommandError("WORKFLOW_GRAPH_RUN_FAILED", "run Graph", error);
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
await client.close().catch(() => { });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
export async function loadWorkflowGraphFile(file) {
|
|
230
|
+
const resolved = resolve(file);
|
|
231
|
+
let raw;
|
|
232
|
+
try {
|
|
233
|
+
raw = await readFile(resolved, "utf-8");
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
throw {
|
|
237
|
+
code: "WORKFLOW_GRAPH_FILE_UNREADABLE",
|
|
238
|
+
message: `Cannot read Graph file ${resolved}`,
|
|
239
|
+
details: error instanceof Error ? error.message : String(error),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
let document;
|
|
243
|
+
try {
|
|
244
|
+
document = JSON.parse(raw);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
throw {
|
|
248
|
+
code: "WORKFLOW_GRAPH_FILE_INVALID_JSON",
|
|
249
|
+
message: `Graph file ${resolved} is not valid JSON`,
|
|
250
|
+
details: error instanceof Error ? error.message : String(error),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const parsed = OrchestrationGraphSchema.safeParse(document);
|
|
254
|
+
if (!parsed.success) {
|
|
255
|
+
throw {
|
|
256
|
+
code: "WORKFLOW_GRAPH_FILE_INVALID_SHAPE",
|
|
257
|
+
message: `Graph file ${resolved} does not match the Graph document format`,
|
|
258
|
+
details: parsed.error.issues.map((issue) => `${issue.path.join(".") || "document"}: ${issue.message}`),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return parsed.data;
|
|
262
|
+
}
|
|
263
|
+
export async function loadWorkflowGraphExportFile(file) {
|
|
264
|
+
const resolved = resolve(file);
|
|
265
|
+
let raw;
|
|
266
|
+
try {
|
|
267
|
+
raw = await readFile(resolved, "utf-8");
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
throw {
|
|
271
|
+
code: "WORKFLOW_GRAPH_EXPORT_UNREADABLE",
|
|
272
|
+
message: `Cannot read Graph export ${resolved}`,
|
|
273
|
+
details: error instanceof Error ? error.message : String(error),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
let document;
|
|
277
|
+
try {
|
|
278
|
+
document = JSON.parse(raw);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
throw {
|
|
282
|
+
code: "WORKFLOW_GRAPH_EXPORT_INVALID_JSON",
|
|
283
|
+
message: `Graph export ${resolved} is not valid JSON`,
|
|
284
|
+
details: error instanceof Error ? error.message : String(error),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const { WorkflowGraphExportSchema } = await import("@otto-code/protocol/workflow");
|
|
288
|
+
const parsed = WorkflowGraphExportSchema.safeParse(document);
|
|
289
|
+
if (!parsed.success)
|
|
290
|
+
throw {
|
|
291
|
+
code: "WORKFLOW_GRAPH_EXPORT_INVALID_SHAPE",
|
|
292
|
+
message: `Graph export ${resolved} does not match the sharing format`,
|
|
293
|
+
details: parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`),
|
|
294
|
+
};
|
|
295
|
+
return parsed.data;
|
|
296
|
+
}
|
|
297
|
+
function toGraphImportCommandResult(result) {
|
|
298
|
+
return {
|
|
299
|
+
status: result.status,
|
|
300
|
+
graphId: result.graph?.id ?? null,
|
|
301
|
+
source: result.source?.storeKey ?? null,
|
|
302
|
+
destination: result.destination?.storeKey ?? null,
|
|
303
|
+
remediation: result.remediation,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
export function parseWorkflowGraphRunInput(id, options) {
|
|
307
|
+
const graphId = id.trim();
|
|
308
|
+
if (!graphId) {
|
|
309
|
+
throw { code: "INVALID_GRAPH_ID", message: "Graph id cannot be empty" };
|
|
310
|
+
}
|
|
311
|
+
const cwdOption = options.cwd?.trim();
|
|
312
|
+
if (options.host !== undefined && !cwdOption) {
|
|
313
|
+
throw {
|
|
314
|
+
code: "MISSING_CWD",
|
|
315
|
+
message: "--cwd is required when --host is specified",
|
|
316
|
+
details: "The local working directory may not exist on the remote daemon.",
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const orchestratorProfile = trimOptional(options.orchestratorProfile);
|
|
320
|
+
const orchestratorProvider = trimOptional(options.orchestratorProvider);
|
|
321
|
+
const orchestratorModel = trimOptional(options.orchestratorModel);
|
|
322
|
+
if (orchestratorProfile && (orchestratorProvider || orchestratorModel)) {
|
|
323
|
+
throw {
|
|
324
|
+
code: "CONFLICTING_ORCHESTRATOR_SEAT",
|
|
325
|
+
message: "Use either --orchestrator-profile or --orchestrator-provider/--orchestrator-model",
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (orchestratorModel && !orchestratorProvider) {
|
|
329
|
+
throw {
|
|
330
|
+
code: "MISSING_ORCHESTRATOR_PROVIDER",
|
|
331
|
+
message: "--orchestrator-model requires --orchestrator-provider",
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
graphId,
|
|
336
|
+
cwd: cwdOption ?? process.cwd(),
|
|
337
|
+
...(trimOptional(options.workspace) ? { workspaceId: trimOptional(options.workspace) } : {}),
|
|
338
|
+
...(trimOptional(options.title) ? { title: trimOptional(options.title) } : {}),
|
|
339
|
+
...(trimOptional(options.description)
|
|
340
|
+
? { description: trimOptional(options.description) }
|
|
341
|
+
: {}),
|
|
342
|
+
graphInputs: parseGraphInputAssignments(options.input),
|
|
343
|
+
...(orchestratorProfile ? { orchestratorProfile } : {}),
|
|
344
|
+
...(orchestratorProvider ? { orchestratorProvider } : {}),
|
|
345
|
+
...(orchestratorModel ? { orchestratorModel } : {}),
|
|
346
|
+
...(trimOptional(options.orchestratorThinking)
|
|
347
|
+
? { orchestratorThinking: trimOptional(options.orchestratorThinking) }
|
|
348
|
+
: {}),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
export function parseGraphInputAssignments(values) {
|
|
352
|
+
const inputs = {};
|
|
353
|
+
for (const value of values ?? []) {
|
|
354
|
+
const separator = value.indexOf("=");
|
|
355
|
+
const key = separator >= 0 ? value.slice(0, separator).trim() : "";
|
|
356
|
+
if (!key) {
|
|
357
|
+
throw {
|
|
358
|
+
code: "INVALID_GRAPH_INPUT",
|
|
359
|
+
message: `Graph input "${value}" must use key=value form`,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
if (Object.hasOwn(inputs, key)) {
|
|
363
|
+
throw {
|
|
364
|
+
code: "DUPLICATE_GRAPH_INPUT",
|
|
365
|
+
message: `Graph input "${key}" was supplied more than once`,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
inputs[key] = value.slice(separator + 1);
|
|
369
|
+
}
|
|
370
|
+
return inputs;
|
|
371
|
+
}
|
|
372
|
+
export function resolveWorkflowGraphInputs(graph, supplied) {
|
|
373
|
+
const declared = new Map((graph.inputs ?? []).map((input) => [input.key, input]));
|
|
374
|
+
for (const key of Object.keys(supplied)) {
|
|
375
|
+
if (!declared.has(key)) {
|
|
376
|
+
throw {
|
|
377
|
+
code: "UNKNOWN_GRAPH_INPUT",
|
|
378
|
+
message: `Graph "${graph.name}" does not declare input "${key}"`,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const resolved = {};
|
|
383
|
+
for (const input of graph.inputs ?? []) {
|
|
384
|
+
const value = (supplied[input.key] ?? input.defaultValue ?? "").trim();
|
|
385
|
+
if (input.required && !value) {
|
|
386
|
+
throw {
|
|
387
|
+
code: "MISSING_GRAPH_INPUT",
|
|
388
|
+
message: `Graph "${graph.name}" requires input "${input.key}"`,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
if (value) {
|
|
392
|
+
resolved[input.key] = value;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return resolved;
|
|
396
|
+
}
|
|
397
|
+
async function connectWorkflowGraphClient(host) {
|
|
398
|
+
const resolvedHost = getDaemonHost({ host });
|
|
399
|
+
try {
|
|
400
|
+
return (await connectToDaemon({ host }));
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
404
|
+
throw {
|
|
405
|
+
code: "DAEMON_NOT_RUNNING",
|
|
406
|
+
message: `Cannot connect to daemon at ${resolvedHost}: ${message}`,
|
|
407
|
+
details: "Start the daemon with: otto daemon start",
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function findWorkflowGraph(client, id) {
|
|
412
|
+
const graph = (await client.listOrchestrationGraphs()).find((candidate) => candidate.id === id);
|
|
413
|
+
if (!graph) {
|
|
414
|
+
throw { code: "GRAPH_NOT_FOUND", message: `Graph not found: ${id}` };
|
|
415
|
+
}
|
|
416
|
+
return graph;
|
|
417
|
+
}
|
|
418
|
+
async function resolveWorkflowGraphWorkspaceId(client, input) {
|
|
419
|
+
if (input.workspaceId) {
|
|
420
|
+
return input.workspaceId;
|
|
421
|
+
}
|
|
422
|
+
let cursor;
|
|
423
|
+
do {
|
|
424
|
+
const page = await client.fetchWorkspaces({
|
|
425
|
+
page: { limit: 200, ...(cursor ? { cursor } : {}) },
|
|
426
|
+
});
|
|
427
|
+
const workspace = page.entries.find((entry) => entry.workspaceDirectory === input.cwd);
|
|
428
|
+
if (workspace) {
|
|
429
|
+
return workspace.id;
|
|
430
|
+
}
|
|
431
|
+
cursor = page.pageInfo.nextCursor ?? undefined;
|
|
432
|
+
} while (cursor);
|
|
433
|
+
throw {
|
|
434
|
+
code: "WORKFLOW_GRAPH_WORKSPACE_REQUIRED",
|
|
435
|
+
message: `No workspace is registered for ${input.cwd}`,
|
|
436
|
+
details: "Create or open the workspace first, or pass its ID with --workspace.",
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function toWorkflowGraphRow(graph) {
|
|
440
|
+
return {
|
|
441
|
+
id: graph.id,
|
|
442
|
+
name: graph.name,
|
|
443
|
+
description: graph.description ?? null,
|
|
444
|
+
nodes: graph.nodes.length,
|
|
445
|
+
edges: graph.edges?.length ?? 0,
|
|
446
|
+
updatedAt: graph.updatedAt ?? null,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function trimOptional(value) {
|
|
450
|
+
const trimmed = value?.trim();
|
|
451
|
+
return trimmed ? trimmed : undefined;
|
|
452
|
+
}
|
|
453
|
+
function toWorkflowGraphCommandError(code, action, error) {
|
|
454
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
455
|
+
return error;
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
code,
|
|
459
|
+
message: `Failed to ${action}: ${error instanceof Error ? error.message : String(error)}`,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
//# sourceMappingURL=graph.js.map
|