@otto-code/protocol 0.6.5 → 0.6.7

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.
@@ -6,7 +6,7 @@ export declare function isRunPhaseType(value: string): value is RunPhaseType;
6
6
  export declare function defaultRoleForPhaseType(type: RunPhaseType): string | null;
7
7
  export declare const RUN_PHASE_STATUSES: readonly ["pending", "running", "blocked", "done", "failed", "skipped"];
8
8
  export type RunPhaseStatus = (typeof RUN_PHASE_STATUSES)[number];
9
- export declare const RUN_STATUSES: readonly ["pending", "running", "paused", "done", "failed", "canceled"];
9
+ export declare const RUN_STATUSES: readonly ["draft", "pending", "running", "paused", "done", "failed", "canceled"];
10
10
  export type RunStatus = (typeof RUN_STATUSES)[number];
11
11
  export declare function isRunPhaseStatus(value: string): value is RunPhaseStatus;
12
12
  export declare function isRunStatus(value: string): value is RunStatus;
@@ -102,7 +102,11 @@ export type RunPhase = z.infer<typeof RunPhaseSchema>;
102
102
  export declare const RunSchema: z.ZodObject<{
103
103
  id: z.ZodString;
104
104
  title: z.ZodString;
105
+ description: z.ZodOptional<z.ZodString>;
105
106
  status: z.ZodString;
107
+ kind: z.ZodOptional<z.ZodString>;
108
+ graphId: z.ZodOptional<z.ZodString>;
109
+ graphInputs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
106
110
  requirements: z.ZodOptional<z.ZodArray<z.ZodString>>;
107
111
  autopilot: z.ZodOptional<z.ZodBoolean>;
108
112
  phases: z.ZodDefault<z.ZodArray<z.ZodObject<{
@@ -149,4 +153,98 @@ export declare const RunSchema: z.ZodObject<{
149
153
  export type Run = z.infer<typeof RunSchema>;
150
154
  export declare const RUN_SUMMARY_STATUSES: readonly ["pending", "ready", "failed"];
151
155
  export type RunSummaryStatus = (typeof RUN_SUMMARY_STATUSES)[number];
156
+ export declare const GraphInputSchema: z.ZodObject<{
157
+ key: z.ZodString;
158
+ label: z.ZodString;
159
+ description: z.ZodOptional<z.ZodString>;
160
+ multiline: z.ZodOptional<z.ZodBoolean>;
161
+ required: z.ZodOptional<z.ZodBoolean>;
162
+ defaultValue: z.ZodOptional<z.ZodString>;
163
+ }, z.core.$loose>;
164
+ export type GraphInput = z.infer<typeof GraphInputSchema>;
165
+ export declare const GRAPH_NODE_KINDS: readonly ["orchestrator", "agent"];
166
+ export type GraphNodeKind = (typeof GRAPH_NODE_KINDS)[number];
167
+ export declare const GraphNodeLoopSchema: z.ZodObject<{
168
+ times: z.ZodOptional<z.ZodNumber>;
169
+ until: z.ZodOptional<z.ZodObject<{
170
+ criteria: z.ZodArray<z.ZodString>;
171
+ judgeRole: z.ZodOptional<z.ZodString>;
172
+ max: z.ZodNumber;
173
+ }, z.core.$loose>>;
174
+ }, z.core.$loose>;
175
+ export type GraphNodeLoop = z.infer<typeof GraphNodeLoopSchema>;
176
+ export declare const GraphNodeSchema: z.ZodObject<{
177
+ id: z.ZodString;
178
+ kind: z.ZodString;
179
+ title: z.ZodString;
180
+ role: z.ZodOptional<z.ZodString>;
181
+ prompt: z.ZodOptional<z.ZodString>;
182
+ promptFromInput: z.ZodOptional<z.ZodString>;
183
+ autonomous: z.ZodOptional<z.ZodBoolean>;
184
+ loop: z.ZodOptional<z.ZodObject<{
185
+ times: z.ZodOptional<z.ZodNumber>;
186
+ until: z.ZodOptional<z.ZodObject<{
187
+ criteria: z.ZodArray<z.ZodString>;
188
+ judgeRole: z.ZodOptional<z.ZodString>;
189
+ max: z.ZodNumber;
190
+ }, z.core.$loose>>;
191
+ }, z.core.$loose>>;
192
+ model: z.ZodOptional<z.ZodString>;
193
+ position: z.ZodOptional<z.ZodObject<{
194
+ x: z.ZodNumber;
195
+ y: z.ZodNumber;
196
+ }, z.core.$loose>>;
197
+ }, z.core.$loose>;
198
+ export type GraphNode = z.infer<typeof GraphNodeSchema>;
199
+ export declare const GraphEdgeSchema: z.ZodObject<{
200
+ id: z.ZodOptional<z.ZodString>;
201
+ from: z.ZodString;
202
+ to: z.ZodString;
203
+ }, z.core.$loose>;
204
+ export type GraphEdge = z.infer<typeof GraphEdgeSchema>;
205
+ export declare const OrchestrationGraphSchema: z.ZodObject<{
206
+ id: z.ZodString;
207
+ name: z.ZodString;
208
+ description: z.ZodOptional<z.ZodString>;
209
+ inputs: z.ZodOptional<z.ZodArray<z.ZodObject<{
210
+ key: z.ZodString;
211
+ label: z.ZodString;
212
+ description: z.ZodOptional<z.ZodString>;
213
+ multiline: z.ZodOptional<z.ZodBoolean>;
214
+ required: z.ZodOptional<z.ZodBoolean>;
215
+ defaultValue: z.ZodOptional<z.ZodString>;
216
+ }, z.core.$loose>>>;
217
+ nodes: z.ZodArray<z.ZodObject<{
218
+ id: z.ZodString;
219
+ kind: z.ZodString;
220
+ title: z.ZodString;
221
+ role: z.ZodOptional<z.ZodString>;
222
+ prompt: z.ZodOptional<z.ZodString>;
223
+ promptFromInput: z.ZodOptional<z.ZodString>;
224
+ autonomous: z.ZodOptional<z.ZodBoolean>;
225
+ loop: z.ZodOptional<z.ZodObject<{
226
+ times: z.ZodOptional<z.ZodNumber>;
227
+ until: z.ZodOptional<z.ZodObject<{
228
+ criteria: z.ZodArray<z.ZodString>;
229
+ judgeRole: z.ZodOptional<z.ZodString>;
230
+ max: z.ZodNumber;
231
+ }, z.core.$loose>>;
232
+ }, z.core.$loose>>;
233
+ model: z.ZodOptional<z.ZodString>;
234
+ position: z.ZodOptional<z.ZodObject<{
235
+ x: z.ZodNumber;
236
+ y: z.ZodNumber;
237
+ }, z.core.$loose>>;
238
+ }, z.core.$loose>>;
239
+ edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
240
+ id: z.ZodOptional<z.ZodString>;
241
+ from: z.ZodString;
242
+ to: z.ZodString;
243
+ }, z.core.$loose>>>;
244
+ builtIn: z.ZodOptional<z.ZodBoolean>;
245
+ createdAt: z.ZodOptional<z.ZodString>;
246
+ updatedAt: z.ZodOptional<z.ZodString>;
247
+ }, z.core.$loose>;
248
+ export type OrchestrationGraph = z.infer<typeof OrchestrationGraphSchema>;
249
+ export declare function validateOrchestrationGraph(graph: OrchestrationGraph): string[];
152
250
  //# sourceMappingURL=orchestration.d.ts.map
@@ -59,6 +59,7 @@ export const RUN_PHASE_STATUSES = [
59
59
  "skipped",
60
60
  ];
61
61
  export const RUN_STATUSES = [
62
+ "draft", // a user orchestration created by the dialog, graph not yet executed
62
63
  "pending",
63
64
  "running",
64
65
  "paused", // stopped at an attended gate, awaiting the user
@@ -166,7 +167,19 @@ export const RunSchema = z
166
167
  .object({
167
168
  id: z.string().min(1),
168
169
  title: z.string().min(1),
170
+ // User-authored description from the New Orchestration dialog (what this
171
+ // orchestration is for). Distinct from `summary`, which is AI-generated
172
+ // after the run settles. Absent on conductor-declared (start_run) runs.
173
+ description: z.string().optional(),
169
174
  status: z.string().min(1),
175
+ // Which engine drives this orchestration: absent/"phases" = the conductor
176
+ // -declared phase plan; "graph" = a user-authored deterministic graph
177
+ // (projects/orchestration-graphs). Open vocabulary, plain string on the wire.
178
+ kind: z.string().optional(),
179
+ // Graph runs only: the executed graph template and the fill-in values the
180
+ // user supplied for its declared inputs.
181
+ graphId: z.string().optional(),
182
+ graphInputs: z.record(z.string(), z.string()).optional(),
170
183
  // Immutable requirements block (see RunPlan.requirements).
171
184
  requirements: z.array(z.string().min(1)).optional(),
172
185
  autopilot: z.boolean().optional(),
@@ -196,4 +209,199 @@ export const RunSchema = z
196
209
  .passthrough();
197
210
  // Summary generation lifecycle (plain-string on the wire; see RunSchema.summaryStatus).
198
211
  export const RUN_SUMMARY_STATUSES = ["pending", "ready", "failed"];
212
+ // ── Orchestration graphs (user orchestrations) ──────────────────────────────
213
+ // The reusable template a User orchestration executes — authored in the graph
214
+ // designer, stored host-level, parameterized by declared inputs. Executing a
215
+ // graph starts an orchestration (a Run with kind "graph"). See
216
+ // projects/orchestration-graphs. Same wire-forward-compat posture as the Run
217
+ // schemas: open string vocabularies, `.passthrough()` objects, no transforms.
218
+ // A declared fill-in parameter. The New Orchestration dialog renders these as
219
+ // a form when the graph is picked; values substitute into node prompts via
220
+ // {{inputs.<key>}} and via GraphNode.promptFromInput.
221
+ export const GraphInputSchema = z
222
+ .object({
223
+ key: z.string().min(1),
224
+ label: z.string().min(1),
225
+ description: z.string().optional(),
226
+ multiline: z.boolean().optional(),
227
+ required: z.boolean().optional(),
228
+ defaultValue: z.string().optional(),
229
+ })
230
+ .passthrough();
231
+ // Node kinds (open vocabulary): "orchestrator" — the single root that hosts
232
+ // the orchestration chat and anchors the Visualizer; "agent" — a worker node.
233
+ export const GRAPH_NODE_KINDS = ["orchestrator", "agent"];
234
+ // Loop annotation — exactly one of `times` (fixed repeat) or `until` (bounded
235
+ // retry graded by a structured judge between iterations; self-grading is not
236
+ // an exit test). `max` is a hard cap in both readings.
237
+ export const GraphNodeLoopSchema = z
238
+ .object({
239
+ times: z.number().int().min(1).max(64).optional(),
240
+ until: z
241
+ .object({
242
+ // What the judge grades each iteration's output against.
243
+ criteria: z.array(z.string().min(1)).min(1),
244
+ // Role that fills the judge seat; defaults to "judger".
245
+ judgeRole: z.string().min(1).optional(),
246
+ max: z.number().int().min(1).max(16),
247
+ })
248
+ .passthrough()
249
+ .optional(),
250
+ })
251
+ .passthrough();
252
+ export const GraphNodeSchema = z
253
+ .object({
254
+ id: z.string().min(1),
255
+ kind: z.string().min(1),
256
+ title: z.string().min(1),
257
+ // Team role the node dispatches to (agent nodes). Resolution at execute
258
+ // time: active team fills the role → dialog personality → bare model.
259
+ role: z.string().min(1).optional(),
260
+ // Fixed prompt text; may reference {{inputs.<key>}}.
261
+ prompt: z.string().optional(),
262
+ // Key of a declared input whose value joins (or forms) the prompt.
263
+ promptFromInput: z.string().optional(),
264
+ // Leaf-only: the node may orchestrate its own agents (full otto toolset
265
+ // minus start_run). Non-autonomous nodes get no orchestration tools at all.
266
+ autonomous: z.boolean().optional(),
267
+ loop: GraphNodeLoopSchema.optional(),
268
+ // Explicit model override (otherwise the resolved personality/team decides).
269
+ model: z.string().optional(),
270
+ // Designer canvas layout.
271
+ position: z.object({ x: z.number(), y: z.number() }).passthrough().optional(),
272
+ })
273
+ .passthrough();
274
+ // A directed edge: `from`'s final output becomes labeled input material for
275
+ // `to`. Fan-in is an all-inputs barrier held by the daemon — agents never know
276
+ // about waiting.
277
+ export const GraphEdgeSchema = z
278
+ .object({
279
+ id: z.string().min(1).optional(),
280
+ from: z.string().min(1),
281
+ to: z.string().min(1),
282
+ })
283
+ .passthrough();
284
+ export const OrchestrationGraphSchema = z
285
+ .object({
286
+ id: z.string().min(1),
287
+ name: z.string().min(1),
288
+ description: z.string().optional(),
289
+ inputs: z.array(GraphInputSchema).optional(),
290
+ nodes: z.array(GraphNodeSchema),
291
+ edges: z.array(GraphEdgeSchema).optional(),
292
+ // Bundled starter graphs; copy-on-edit, never deleted in place.
293
+ builtIn: z.boolean().optional(),
294
+ createdAt: z.string().optional(),
295
+ updatedAt: z.string().optional(),
296
+ })
297
+ .passthrough();
298
+ // ── Graph structural validation ──────────────────────────────────────────────
299
+ // Shared by the daemon (hard gate before execute) and the designer (live
300
+ // feedback). Returns human-readable problems; empty ⇒ executable. Split into
301
+ // per-concern helpers to stay under the complexity ceiling.
302
+ export function validateOrchestrationGraph(graph) {
303
+ const nodeIds = new Set();
304
+ const problems = [];
305
+ for (const node of graph.nodes) {
306
+ if (nodeIds.has(node.id))
307
+ problems.push(`Duplicate node id "${node.id}".`);
308
+ nodeIds.add(node.id);
309
+ }
310
+ const roots = graph.nodes.filter((n) => n.kind === "orchestrator");
311
+ if (roots.length === 0) {
312
+ problems.push("The graph needs exactly one Orchestrator node (the root).");
313
+ }
314
+ else if (roots.length > 1) {
315
+ problems.push("The graph has more than one Orchestrator node.");
316
+ }
317
+ problems.push(...validateGraphEdges(graph, nodeIds));
318
+ const declaredInputs = new Set((graph.inputs ?? []).map((i) => i.key));
319
+ for (const node of graph.nodes) {
320
+ problems.push(...validateGraphNode(node, declaredInputs));
321
+ }
322
+ return problems;
323
+ }
324
+ function validateGraphEdges(graph, nodeIds) {
325
+ const problems = [];
326
+ const edges = graph.edges ?? [];
327
+ const rootId = graph.nodes.find((node) => node.kind === "orchestrator")?.id ?? null;
328
+ const outgoing = new Map();
329
+ const incoming = new Map();
330
+ for (const edge of edges) {
331
+ if (!nodeIds.has(edge.from))
332
+ problems.push(`Edge from unknown node "${edge.from}".`);
333
+ if (!nodeIds.has(edge.to))
334
+ problems.push(`Edge to unknown node "${edge.to}".`);
335
+ if (edge.from === edge.to)
336
+ problems.push(`Node "${edge.from}" connects to itself.`);
337
+ // Edges INTO the orchestrator are passive answer-delivery, not execution
338
+ // dependencies — excluding them here keeps "root kicks off A, A delivers
339
+ // back to root" from reading as a cycle.
340
+ if (edge.to === rootId)
341
+ continue;
342
+ outgoing.set(edge.from, [...(outgoing.get(edge.from) ?? []), edge.to]);
343
+ incoming.set(edge.to, [...(incoming.get(edge.to) ?? []), edge.from]);
344
+ }
345
+ if (hasGraphCycle(graph, outgoing, incoming)) {
346
+ problems.push("The graph contains a cycle.");
347
+ }
348
+ return problems;
349
+ }
350
+ // Cycle check (loops are node-level annotations, never cyclic edges): Kahn.
351
+ function hasGraphCycle(graph, outgoing, incoming) {
352
+ const indegree = new Map();
353
+ for (const node of graph.nodes)
354
+ indegree.set(node.id, incoming.get(node.id)?.length ?? 0);
355
+ const queue = graph.nodes.filter((n) => (indegree.get(n.id) ?? 0) === 0).map((n) => n.id);
356
+ let visited = 0;
357
+ while (queue.length > 0) {
358
+ const id = queue.shift();
359
+ visited += 1;
360
+ for (const next of outgoing.get(id) ?? []) {
361
+ const d = (indegree.get(next) ?? 0) - 1;
362
+ indegree.set(next, d);
363
+ if (d === 0)
364
+ queue.push(next);
365
+ }
366
+ }
367
+ return visited !== graph.nodes.length;
368
+ }
369
+ const GRAPH_INPUT_REF = /\{\{\s*inputs\.([A-Za-z0-9_-]+)\s*\}\}/g;
370
+ function validateGraphNode(node, declaredInputs) {
371
+ const isRoot = node.kind === "orchestrator";
372
+ if (!isRoot && node.kind !== "agent")
373
+ return []; // unknown kinds pass through
374
+ const problems = [];
375
+ // Autonomous nodes may feed results onward via edges; what they must not
376
+ // do is orchestrate deterministic children — which edges don't express, so
377
+ // autonomy is allowed on any node except the root.
378
+ if (node.autonomous && isRoot) {
379
+ problems.push("The Orchestrator node can't be autonomous.");
380
+ }
381
+ if (!isRoot && !node.prompt?.trim() && !node.promptFromInput) {
382
+ problems.push(`Node "${node.title}" has no prompt and no prompt input.`);
383
+ }
384
+ if (node.promptFromInput && !declaredInputs.has(node.promptFromInput)) {
385
+ problems.push(`Node "${node.title}" reads input "${node.promptFromInput}", which isn't declared.`);
386
+ }
387
+ for (const match of (node.prompt ?? "").matchAll(GRAPH_INPUT_REF)) {
388
+ if (!declaredInputs.has(match[1])) {
389
+ problems.push(`Node "${node.title}" references {{inputs.${match[1]}}}, which isn't declared.`);
390
+ }
391
+ }
392
+ problems.push(...validateGraphNodeLoop(node));
393
+ return problems;
394
+ }
395
+ function validateGraphNodeLoop(node) {
396
+ if (!node.loop) {
397
+ return [];
398
+ }
399
+ if (node.loop.times === undefined && node.loop.until === undefined) {
400
+ return [`Node "${node.title}" has a loop with neither "times" nor "until".`];
401
+ }
402
+ if (node.loop.times !== undefined && node.loop.until !== undefined) {
403
+ return [`Node "${node.title}" has both loop forms — pick "times" or "until".`];
404
+ }
405
+ return [];
406
+ }
199
407
  //# sourceMappingURL=orchestration.js.map