@melra/protocol 0.3.0-alpha.10

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/index.js ADDED
@@ -0,0 +1,757 @@
1
+ // Copyright 2026 XAGI Labs Private Limited
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { z } from "zod";
4
+ export const PROTOCOL_VERSION = "2025-11-25";
5
+ export const PRODUCT_VERSION = "0.3.0-alpha.10";
6
+ const boundedPath = z.string().min(1).max(4096);
7
+ const boundedText = z.string().max(200_000);
8
+ export const FileOperationSchema = z
9
+ .object({
10
+ kind: z.literal("file"),
11
+ action: z.enum([
12
+ "list",
13
+ "read",
14
+ "stat",
15
+ "hash",
16
+ "write",
17
+ "move",
18
+ "delete",
19
+ "mkdir",
20
+ ]),
21
+ path: boundedPath,
22
+ destination: boundedPath.optional(),
23
+ content: boundedText.optional(),
24
+ encoding: z.enum(["utf8", "base64"]).default("utf8"),
25
+ recursive: z.boolean().default(false),
26
+ })
27
+ .strict();
28
+ export const TerminalOperationSchema = z
29
+ .object({
30
+ kind: z.literal("terminal"),
31
+ action: z.enum(["run", "start", "status", "output", "stop", "send"]),
32
+ command: z.string().min(1).max(512).optional(),
33
+ args: z.array(z.string().max(4096)).max(100).default([]),
34
+ jobId: z.string().uuid().optional(),
35
+ cwd: boundedPath.optional(),
36
+ env: z.record(z.string(), z.string().max(8192)).optional(),
37
+ /**
38
+ * Hold `start`'s stdin open so `send` can answer a prompt. Off by default
39
+ * because it changes what the program sees: with stdin ignored a read gets
40
+ * EOF at once, while an open pipe makes the same program wait for input
41
+ * that may never come.
42
+ */
43
+ interactive: z.boolean().default(false),
44
+ /** What `send` writes to the job's stdin. */
45
+ input: z.string().max(8_192).optional(),
46
+ /**
47
+ * Append a newline to `input`. A prompt waits for the line to end, so the
48
+ * common case is the default; turn it off to send a bare keystroke.
49
+ */
50
+ appendNewline: z.boolean().default(true),
51
+ timeoutMs: z.number().int().min(100).max(120_000).default(30_000),
52
+ maxOutputChars: z.number().int().min(1_000).max(1_000_000).default(100_000),
53
+ })
54
+ .strict();
55
+ export const BrowserTargetSchema = z
56
+ .object({
57
+ selector: z.string().min(1).max(2_000).optional(),
58
+ role: z.string().min(1).max(100).optional(),
59
+ name: z.string().max(500).optional(),
60
+ text: z.string().max(2_000).optional(),
61
+ })
62
+ .strict();
63
+ export const BrowserOperationSchema = z
64
+ .object({
65
+ kind: z.literal("browser"),
66
+ action: z.enum([
67
+ "navigate",
68
+ "back",
69
+ "forward",
70
+ "reload",
71
+ "inspect",
72
+ "wait",
73
+ "click",
74
+ "type",
75
+ "fill_form",
76
+ "select",
77
+ "press",
78
+ "scroll",
79
+ "screenshot",
80
+ "upload",
81
+ "download",
82
+ "tabs",
83
+ "tab_new",
84
+ "tab_switch",
85
+ "close",
86
+ ]),
87
+ url: z.string().url().max(8_192).optional(),
88
+ target: BrowserTargetSchema.optional(),
89
+ value: z.string().max(100_000).optional(),
90
+ values: z.array(z.string().max(2_000)).max(50).optional(),
91
+ // One task, one approval: a login form is two fields and a submit, not
92
+ // three separate governed mutations.
93
+ fields: z
94
+ .array(z
95
+ .object({
96
+ target: BrowserTargetSchema,
97
+ value: z.string().max(100_000),
98
+ })
99
+ .strict())
100
+ .min(1)
101
+ .max(50)
102
+ .optional(),
103
+ filePaths: z.array(boundedPath).min(1).max(20).optional(),
104
+ key: z.string().max(100).optional(),
105
+ direction: z.enum(["up", "down", "top", "bottom", "into_view"]).optional(),
106
+ // `wait` conditions. A URL is matched by substring rather than by equality
107
+ // because the interesting part of a post-login redirect is the path, not
108
+ // the session token stapled to it.
109
+ state: z.enum(["visible", "hidden", "attached", "detached"]).optional(),
110
+ urlContains: z.string().min(1).max(2_000).optional(),
111
+ tabIndex: z.number().int().min(0).max(100).optional(),
112
+ // Per-keystroke delay. Some widgets debounce, and some deliberately ignore
113
+ // input that arrives faster than a human could produce it.
114
+ delayMs: z.number().int().min(0).max(500).default(0),
115
+ clearFirst: z.boolean().default(true),
116
+ pixels: z.number().int().min(1).max(100_000).default(600),
117
+ fullPage: z.boolean().default(false),
118
+ timeoutMs: z.number().int().min(100).max(120_000).default(30_000),
119
+ settleQuietMs: z.number().int().min(25).max(2_000).default(180),
120
+ settleTimeoutMs: z.number().int().min(25).max(10_000).default(1_500),
121
+ maxChars: z.number().int().min(100).max(200_000).default(20_000),
122
+ })
123
+ .strict();
124
+ export const MemoryScopeSchema = z.enum([
125
+ "session",
126
+ "task",
127
+ "project",
128
+ "workspace",
129
+ "user",
130
+ "procedural",
131
+ ]);
132
+ export const MemoryOperationSchema = z
133
+ .object({
134
+ kind: z.literal("memory"),
135
+ action: z.enum(["put", "search", "list", "delete", "clear"]),
136
+ id: z.string().uuid().optional(),
137
+ scope: MemoryScopeSchema.default("workspace"),
138
+ key: z.string().min(1).max(512).optional(),
139
+ value: boundedText.optional(),
140
+ query: z.string().max(10_000).optional(),
141
+ source: z.string().max(4_096).optional(),
142
+ confidence: z.number().min(0).max(1).default(1),
143
+ tags: z.array(z.string().min(1).max(128)).max(50).default([]),
144
+ speaker: z.string().min(1).max(256).optional(),
145
+ episodeId: z.string().min(1).max(256).optional(),
146
+ sequence: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional(),
147
+ expiresAt: z.string().datetime().optional(),
148
+ supersedesId: z.string().uuid().optional(),
149
+ includeSuperseded: z.boolean().default(false),
150
+ limit: z.number().int().min(1).max(100).default(20),
151
+ })
152
+ .strict();
153
+ export const ComputerOperationSchema = z
154
+ .object({
155
+ kind: z.literal("computer"),
156
+ action: z.enum([
157
+ "capabilities",
158
+ "inspect",
159
+ "screenshot",
160
+ "click",
161
+ "move",
162
+ "drag",
163
+ "type",
164
+ "key",
165
+ "scroll",
166
+ ]),
167
+ coordinateSpace: z.enum(["normalized", "pixel"]).default("normalized"),
168
+ x: z.number().min(0).max(100_000).optional(),
169
+ y: z.number().min(0).max(100_000).optional(),
170
+ // `drag` only: where the press is released. Kept as separate fields rather
171
+ // than a nested point so `x`/`y` keep meaning "where the action starts" for
172
+ // every action that has a position.
173
+ toX: z.number().min(0).max(100_000).optional(),
174
+ toY: z.number().min(0).max(100_000).optional(),
175
+ text: z.string().max(100_000).optional(),
176
+ key: z
177
+ .enum([
178
+ "ENTER",
179
+ "TAB",
180
+ "SPACE",
181
+ "BACKSPACE",
182
+ "ESCAPE",
183
+ "LEFT",
184
+ "RIGHT",
185
+ "DOWN",
186
+ "UP",
187
+ "HOME",
188
+ "END",
189
+ "PAGEUP",
190
+ "PAGEDOWN",
191
+ "DELETE",
192
+ ])
193
+ .optional(),
194
+ deltaY: z.number().int().min(-2_000).max(2_000).optional(),
195
+ /**
196
+ * Where to act, named rather than measured. `inspect` reports the elements
197
+ * the platform can see with the roles and names that address them, and a
198
+ * caller naming one here has the runtime resolve it to a point instead of
199
+ * reading pixels off a screenshot. Coordinates still work unchanged;
200
+ * supplying both is rejected rather than silently resolved one way, because
201
+ * the two can disagree and the caller is the only one who knows which it
202
+ * meant.
203
+ */
204
+ target: z
205
+ .object({
206
+ role: z.string().min(1).max(100).optional(),
207
+ name: z.string().min(1).max(500).optional(),
208
+ })
209
+ .strict()
210
+ .optional(),
211
+ // Capped at 120s like every other operation kind rather than 30s. A
212
+ // computer action spawns a whole interpreter — `powershell.exe` plus the
213
+ // .NET assemblies it loads, `osascript`, `xdotool` — and the first such
214
+ // spawn after boot exceeded the old 30s ceiling on a cold Windows machine,
215
+ // which made the maximum itself unreachable there. The default stays 10s
216
+ // because a warm call is fast; a caller who knows it is cold can now ask
217
+ // for more instead of being denied by the schema.
218
+ timeoutMs: z.number().int().min(100).max(120_000).default(10_000),
219
+ })
220
+ .strict();
221
+ export const SystemOperationSchema = z
222
+ .object({
223
+ kind: z.literal("system"),
224
+ action: z.literal("info"),
225
+ })
226
+ .strict();
227
+ export const OperationSchema = z.discriminatedUnion("kind", [
228
+ FileOperationSchema,
229
+ TerminalOperationSchema,
230
+ BrowserOperationSchema,
231
+ MemoryOperationSchema,
232
+ ComputerOperationSchema,
233
+ SystemOperationSchema,
234
+ ]);
235
+ export const EvidencePredicateSchema = z.discriminatedUnion("type", [
236
+ z
237
+ .object({
238
+ type: z.literal("result_equals"),
239
+ path: z.string().min(1).max(512),
240
+ value: z.union([z.string(), z.number(), z.boolean(), z.null()]),
241
+ })
242
+ .strict(),
243
+ z
244
+ .object({
245
+ type: z.literal("result_contains"),
246
+ path: z.string().min(1).max(512),
247
+ value: z.string().max(10_000),
248
+ })
249
+ .strict(),
250
+ z
251
+ .object({
252
+ type: z.literal("file_exists"),
253
+ path: boundedPath,
254
+ })
255
+ .strict(),
256
+ z
257
+ .object({
258
+ type: z.literal("file_absent"),
259
+ path: boundedPath,
260
+ })
261
+ .strict(),
262
+ z
263
+ .object({
264
+ type: z.literal("file_hash"),
265
+ path: boundedPath,
266
+ sha256: z.string().regex(/^[a-f0-9]{64}$/),
267
+ })
268
+ .strict(),
269
+ z
270
+ .object({
271
+ type: z.literal("exit_code"),
272
+ value: z.number().int(),
273
+ })
274
+ .strict(),
275
+ z
276
+ .object({
277
+ type: z.literal("url_matches"),
278
+ pattern: z.string().min(1).max(2_000),
279
+ })
280
+ .strict(),
281
+ z
282
+ .object({
283
+ type: z.literal("page_contains"),
284
+ text: z.string().min(1).max(10_000),
285
+ })
286
+ .strict(),
287
+ ]);
288
+ export const PrincipalKindSchema = z.enum([
289
+ "organization",
290
+ "human",
291
+ "harness",
292
+ "agent",
293
+ "session",
294
+ "subagent",
295
+ "service",
296
+ ]);
297
+ export const PrincipalSchema = z
298
+ .object({
299
+ kind: PrincipalKindSchema,
300
+ id: z.string().min(1).max(200),
301
+ })
302
+ .strict();
303
+ /**
304
+ * Who is asking, and on whose behalf.
305
+ *
306
+ * `principal` is the immediate caller — the agent or session that dispatched
307
+ * this effect. `onBehalfOf` is the delegation chain behind it, outermost first
308
+ * (organization → human → harness → parent agent), so a receipt answers "who
309
+ * authorised this" rather than only "what ran". MELRA does not authenticate any
310
+ * of it; a link is a claim the layer above makes, and it is worth exactly as
311
+ * much as that layer's own boundary.
312
+ */
313
+ export const IdentitySchema = z
314
+ .object({
315
+ principal: PrincipalSchema,
316
+ onBehalfOf: z.array(PrincipalSchema).max(7).default([]),
317
+ })
318
+ .strict();
319
+ /** The one principal assumed when a caller declares none. */
320
+ export const LOCAL_IDENTITY = {
321
+ principal: { kind: "agent", id: "local" },
322
+ onBehalfOf: [],
323
+ };
324
+ export function principalRef(principal) {
325
+ return `${principal.kind}:${principal.id}`;
326
+ }
327
+ /**
328
+ * Records a caller the transport authenticated above whatever the request
329
+ * declared.
330
+ *
331
+ * The authenticated link sits at the outermost end, because anything the
332
+ * client names — a session, a subagent — is inside it and never above it. A
333
+ * request that declared nothing is simply that client acting for itself. This
334
+ * is the one link in a chain that is not a claim; the rest still are, so a
335
+ * capability grant matching the immediate principal is a narrowing control
336
+ * rather than an authentication boundary.
337
+ */
338
+ export function attributeTo(principal, identity) {
339
+ if (identity === undefined)
340
+ return { principal, onBehalfOf: [] };
341
+ const onBehalfOf = [principal, ...identity.onBehalfOf];
342
+ if (onBehalfOf.length > 7)
343
+ throw new Error("identity_chain_too_deep");
344
+ return { principal: identity.principal, onBehalfOf };
345
+ }
346
+ /** The delegation chain as one line, outermost first. */
347
+ export function delegationChain(identity) {
348
+ return [...identity.onBehalfOf, identity.principal]
349
+ .map(principalRef)
350
+ .join("/");
351
+ }
352
+ export const TaskBudgetSchema = z
353
+ .object({
354
+ maxSteps: z.number().int().min(1).max(100).default(10),
355
+ maxDurationMs: z.number().int().min(100).max(900_000).default(120_000),
356
+ maxRetries: z.number().int().min(0).max(10).default(2),
357
+ })
358
+ .strict();
359
+ export const TaskRequestSchema = z
360
+ .object({
361
+ goal: z
362
+ .string()
363
+ .min(1)
364
+ .max(10_000)
365
+ .describe("Why this task is being run, in one human-readable sentence."),
366
+ operation: OperationSchema,
367
+ // These three carry the rules callers get wrong, and a description is the
368
+ // only channel that reaches a model reading the tool's JSON Schema.
369
+ constraints: z
370
+ .array(z.string().max(2_000))
371
+ .max(50)
372
+ .default([])
373
+ .describe("Leave empty. Freeform prose cannot be enforced, so a non-empty array is denied with freeform_constraints_not_enforceable. Express real limits as forbiddenEffects, budget, or requiredEvidence."),
374
+ forbiddenEffects: z
375
+ .array(z.enum(["read", "mutate", "destructive"]))
376
+ .max(3)
377
+ .default([])
378
+ .describe("Effects this task must never take. Denied before the adapter runs, and honoured even in unhinged mode."),
379
+ budget: TaskBudgetSchema.default({
380
+ maxSteps: 10,
381
+ maxDurationMs: 120_000,
382
+ maxRetries: 2,
383
+ }),
384
+ requiredEvidence: z
385
+ .array(EvidencePredicateSchema)
386
+ .max(20)
387
+ .default([])
388
+ .describe("Predicates that must hold after execution for the task to be verified_success. Required for any non-read operation: a mutation with none is denied with mutation_requires_evidence. An adapter that succeeded while a predicate failed is partial, never success."),
389
+ identity: IdentitySchema.optional().describe("Who is asking, and on whose behalf. Recorded on the task and every receipt, and matched against policy.capabilities when the operator has issued grants. Omitted means the implicit local principal."),
390
+ })
391
+ .strict();
392
+ export const EffectSchema = z.enum(["read", "mutate", "destructive"]);
393
+ export const RiskSchema = z.enum(["low", "medium", "high", "critical"]);
394
+ export const PolicyOutcomeSchema = z.enum(["allow", "deny", "confirm"]);
395
+ /**
396
+ * What an operation reaches for, as opposed to what it is named. `npm ls` and
397
+ * `npm install` are the same allowlisted command; only the second one installs
398
+ * a package and talks to a registry. Traits are what `policy.deniedTraits`
399
+ * matches on and what a caller sees before it echoes an approval phrase.
400
+ */
401
+ export const CapabilityTraitSchema = z.enum(["package-install", "network"]);
402
+ export const PolicyDecisionSchema = z
403
+ .object({
404
+ outcome: PolicyOutcomeSchema,
405
+ effect: EffectSchema,
406
+ risk: RiskSchema,
407
+ reason: z.string(),
408
+ policyVersion: z.string(),
409
+ traits: z
410
+ .array(CapabilityTraitSchema)
411
+ .max(8)
412
+ .default([])
413
+ .describe("What the operation reaches for beyond the local workspace. Denied by policy.deniedTraits; shown so an approver knows a command installs packages or contacts another host before approving it."),
414
+ })
415
+ .strict();
416
+ export const ApprovalChallengeSchema = z
417
+ .object({
418
+ approvalId: z.string().uuid(),
419
+ taskId: z.string().uuid(),
420
+ actionDigest: z.string().regex(/^[a-f0-9]{64}$/),
421
+ phrase: z.string(),
422
+ expiresAt: z.string().datetime(),
423
+ })
424
+ .strict();
425
+ export const ApprovalResponseSchema = z
426
+ .object({
427
+ approvalId: z.string().uuid(),
428
+ phrase: z.string().min(1).max(256),
429
+ })
430
+ .strict();
431
+ /**
432
+ * Bounded authority to take one kind of effect, issued to a principal by the
433
+ * operator rather than derived from a credential.
434
+ *
435
+ * `capability` and `target` are matched against what `classifyOperation`
436
+ * reports, with `*` standing for any run of characters, so `file.*` over
437
+ * `/repo/build/*` grants file work inside one directory and nothing else. An
438
+ * empty grant list means no capability narrowing is configured — the rest of
439
+ * policy still applies. A non-empty list is a closed world: an effect with no
440
+ * matching grant is denied before any allowlist is consulted.
441
+ */
442
+ export const CapabilityGrantSchema = z
443
+ .object({
444
+ id: z.string().min(1).max(200),
445
+ capability: z.string().min(1).max(200),
446
+ effects: z.array(EffectSchema).min(1).max(3),
447
+ target: z.string().min(1).max(4096).default("*"),
448
+ /** `kind:id` of the holder, matched against the immediate principal. */
449
+ principal: z.string().min(1).max(200).default("*"),
450
+ validUntil: z.string().datetime().optional(),
451
+ /**
452
+ * Policy version this grant was issued against. A grant that names a
453
+ * version the running policy no longer has is refused rather than silently
454
+ * reinterpreted under different rules.
455
+ */
456
+ policyVersion: z.string().max(64).optional(),
457
+ })
458
+ .strict();
459
+ export const WorkflowNodeIdSchema = z
460
+ .string()
461
+ .regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/);
462
+ const NodeBaseSchema = z.object({
463
+ id: WorkflowNodeIdSchema,
464
+ dependsOn: z.array(WorkflowNodeIdSchema).max(100).default([]),
465
+ });
466
+ export const OperationNodeSchema = NodeBaseSchema.extend({
467
+ type: z.literal("operation"),
468
+ request: TaskRequestSchema,
469
+ }).strict();
470
+ export const ApprovalNodeSchema = NodeBaseSchema.extend({
471
+ type: z.literal("approval"),
472
+ forNodeId: WorkflowNodeIdSchema,
473
+ }).strict();
474
+ export const ConditionNodeSchema = NodeBaseSchema.extend({
475
+ type: z.literal("condition"),
476
+ sourceNodeId: WorkflowNodeIdSchema,
477
+ predicate: EvidencePredicateSchema,
478
+ whenTrue: z.array(TaskRequestSchema).max(50).default([]),
479
+ whenFalse: z.array(TaskRequestSchema).max(50).default([]),
480
+ }).strict();
481
+ export const ParallelNodeSchema = NodeBaseSchema.extend({
482
+ type: z.literal("parallel"),
483
+ branches: z
484
+ .array(z.array(TaskRequestSchema).min(1).max(50))
485
+ .min(2)
486
+ .max(20),
487
+ }).strict();
488
+ export const BoundedLoopNodeSchema = NodeBaseSchema.extend({
489
+ type: z.literal("bounded_loop"),
490
+ body: z.array(TaskRequestSchema).min(1).max(50),
491
+ maxIterations: z.number().int().min(1).max(100),
492
+ until: EvidencePredicateSchema.optional(),
493
+ }).strict();
494
+ export const CheckpointNodeSchema = NodeBaseSchema.extend({
495
+ type: z.literal("checkpoint"),
496
+ }).strict();
497
+ export const CompensationNodeSchema = NodeBaseSchema.extend({
498
+ type: z.literal("compensation"),
499
+ forNodeId: WorkflowNodeIdSchema,
500
+ request: TaskRequestSchema,
501
+ }).strict();
502
+ // A workflow that needs a person to answer something stops here. The answer is
503
+ // supplied to `advance`, recorded on the node, and readable by later nodes —
504
+ // it is data the graph waited for, not an approval to run a pending mutation.
505
+ export const HumanInputNodeSchema = NodeBaseSchema.extend({
506
+ type: z.literal("human_input"),
507
+ prompt: z.string().min(1).max(2_000),
508
+ // Free text is unconstrained by design; `choices` narrows it to a fixed set
509
+ // so a workflow can branch on an answer it can actually enumerate.
510
+ choices: z.array(z.string().min(1).max(200)).max(20).default([]),
511
+ maxLength: z.number().int().min(1).max(10_000).default(1_000),
512
+ }).strict();
513
+ // Delegation hands a named sub-goal to an outside worker (another agent, a
514
+ // person, a queue). MELRA does not execute it — it records the handoff and
515
+ // waits for a result, so the run stays governed and receipted either way.
516
+ export const DelegationNodeSchema = NodeBaseSchema.extend({
517
+ type: z.literal("delegation"),
518
+ assignee: z.string().min(1).max(200),
519
+ goal: z.string().min(1).max(2_000),
520
+ requiredEvidence: z.array(EvidencePredicateSchema).max(20).default([]),
521
+ }).strict();
522
+ export const WorkflowNodeSchema = z.discriminatedUnion("type", [
523
+ OperationNodeSchema,
524
+ ApprovalNodeSchema,
525
+ ConditionNodeSchema,
526
+ ParallelNodeSchema,
527
+ BoundedLoopNodeSchema,
528
+ CheckpointNodeSchema,
529
+ CompensationNodeSchema,
530
+ HumanInputNodeSchema,
531
+ DelegationNodeSchema,
532
+ ]);
533
+ // What an operator supplies to `advance` to satisfy a waiting human_input or
534
+ // delegation node.
535
+ export const WorkflowInputSchema = z
536
+ .object({
537
+ nodeId: WorkflowNodeIdSchema,
538
+ value: z.string().min(1).max(10_000),
539
+ })
540
+ .strict();
541
+ export const WorkflowDefinitionSchema = z
542
+ .object({
543
+ schemaVersion: z.literal("1.0.0"),
544
+ id: z.string().uuid(),
545
+ version: z.number().int().positive(),
546
+ name: z.string().min(1).max(200),
547
+ description: z.string().max(2_000).optional(),
548
+ nodes: z.array(WorkflowNodeSchema).min(1).max(500),
549
+ budget: TaskBudgetSchema.optional(),
550
+ })
551
+ .strict();
552
+ export const WorkflowStatusSchema = z.enum([
553
+ "draft",
554
+ "planned",
555
+ "awaiting_approval",
556
+ // Blocked on a person supplying input. Distinct from `paused`/`suspended`,
557
+ // which are operator halts: this one clears by answering, not by resuming.
558
+ "awaiting_input",
559
+ "running",
560
+ "paused",
561
+ "suspended",
562
+ "partially_complete",
563
+ "verified_complete",
564
+ "recovery_required",
565
+ "failed",
566
+ "cancelled",
567
+ ]);
568
+ export const WorkflowNodeStatusSchema = z.enum([
569
+ "pending",
570
+ "ready",
571
+ "awaiting_approval",
572
+ "awaiting_input",
573
+ "running",
574
+ "verifying",
575
+ "verified_complete",
576
+ "skipped",
577
+ "recovery_required",
578
+ "failed",
579
+ "cancelled",
580
+ "compensated",
581
+ ]);
582
+ export const EncryptedPayloadSchema = z
583
+ .object({
584
+ version: z.literal(1),
585
+ algorithm: z.literal("aes-256-gcm"),
586
+ iv: z.string().regex(/^[A-Za-z0-9_-]+$/),
587
+ ciphertext: z.string().regex(/^[A-Za-z0-9_-]+$/),
588
+ tag: z.string().regex(/^[A-Za-z0-9_-]+$/),
589
+ })
590
+ .strict();
591
+ export const WorkflowEventSchema = z
592
+ .object({
593
+ schemaVersion: z.literal("1.0.0"),
594
+ id: z.string().uuid(),
595
+ aggregateId: z.string().uuid(),
596
+ sequence: z.number().int().positive(),
597
+ traceId: z.string().uuid(),
598
+ type: z.string().regex(/^[a-z]+(?:\.[a-z_]+)+$/),
599
+ data: z.record(z.string(), z.unknown()),
600
+ occurredAt: z.string().datetime(),
601
+ })
602
+ .strict();
603
+ export const WorkflowNodeStateSchema = z
604
+ .object({
605
+ status: WorkflowNodeStatusSchema,
606
+ taskIds: z.array(z.string().uuid()).max(5_000).default([]),
607
+ approval: ApprovalChallengeSchema.optional(),
608
+ iterations: z.number().int().min(0).max(100).optional(),
609
+ // What a human_input or delegation node is waiting on, and what it got.
610
+ prompt: z.string().max(2_000).optional(),
611
+ input: z.string().max(10_000).optional(),
612
+ error: z.string().max(10_000).optional(),
613
+ })
614
+ .strict();
615
+ export const WorkflowRunSchema = z
616
+ .object({
617
+ schemaVersion: z.literal("1.0.0"),
618
+ id: z.string().uuid(),
619
+ definitionId: z.string().uuid(),
620
+ definitionVersion: z.number().int().positive(),
621
+ status: WorkflowStatusSchema,
622
+ stateVersion: z.number().int().positive(),
623
+ nodes: z.record(WorkflowNodeIdSchema, WorkflowNodeStateSchema),
624
+ traceId: z.string().uuid(),
625
+ error: z.string().max(10_000).optional(),
626
+ createdAt: z.string().datetime(),
627
+ updatedAt: z.string().datetime(),
628
+ })
629
+ .strict();
630
+ export const WorkflowSnapshotSchema = z
631
+ .object({
632
+ schemaVersion: z.literal("1.0.0"),
633
+ workflowId: z.string().uuid(),
634
+ sequence: z.number().int().positive(),
635
+ run: WorkflowRunSchema,
636
+ createdAt: z.string().datetime(),
637
+ })
638
+ .strict()
639
+ .refine((snapshot) => snapshot.sequence === snapshot.run.stateVersion, {
640
+ message: "sequence must match run.stateVersion",
641
+ path: ["sequence"],
642
+ });
643
+ export const WorkflowAdvanceResultSchema = z
644
+ .object({
645
+ run: WorkflowRunSchema,
646
+ tasks: z.array(z.record(z.string(), z.unknown())),
647
+ events: z.array(WorkflowEventSchema),
648
+ })
649
+ .strict();
650
+ export const WorkflowPlanInputSchema = z
651
+ .object({
652
+ definition: WorkflowDefinitionSchema,
653
+ })
654
+ .strict();
655
+ export const WorkflowAdvanceInputSchema = z
656
+ .object({
657
+ workflowId: z.string().uuid(),
658
+ approvals: z.array(ApprovalResponseSchema).max(50).default([]),
659
+ // Answers for nodes parked on a person. Supplied on the same call that
660
+ // resumes the wave, so one round trip both answers and advances.
661
+ inputs: z.array(WorkflowInputSchema).max(50).default([]),
662
+ })
663
+ .strict();
664
+ export const WorkflowIdInputSchema = z
665
+ .object({
666
+ workflowId: z.string().uuid(),
667
+ })
668
+ .strict();
669
+ // Operator halts and their reversal. One tool with an action beats three tools
670
+ // that differ only in a verb.
671
+ export const WorkflowControlInputSchema = z
672
+ .object({
673
+ workflowId: z.string().uuid(),
674
+ action: z.enum(["pause", "resume", "suspend"]),
675
+ })
676
+ .strict();
677
+ export const TaskStatusSchema = z.enum([
678
+ "planned",
679
+ "awaiting_approval",
680
+ "running",
681
+ "verifying",
682
+ "verified_success",
683
+ "partial",
684
+ "failed",
685
+ "cancelled",
686
+ "waiting_user",
687
+ "policy_blocked",
688
+ "budget_exhausted",
689
+ "recovery_required",
690
+ ]);
691
+ /**
692
+ * Assemble the contract for a planned task.
693
+ *
694
+ * `classification` comes from `classifyOperation`, which lives in the policy
695
+ * package; passing it in keeps this file free of dependencies and keeps one
696
+ * classifier authoritative instead of a second copy that could disagree.
697
+ */
698
+ export function effectContract(task, classification) {
699
+ return {
700
+ contractVersion: PROTOCOL_VERSION,
701
+ taskId: task.id,
702
+ identity: task.request.identity ?? LOCAL_IDENTITY,
703
+ capability: classification.capability,
704
+ operation: task.request.operation,
705
+ effect: task.policyDecision.effect,
706
+ risk: task.policyDecision.risk,
707
+ target: classification.target,
708
+ traits: task.policyDecision.traits,
709
+ forbiddenEffects: task.request.forbiddenEffects,
710
+ postconditions: task.request.requiredEvidence,
711
+ budget: task.request.budget,
712
+ ...(task.idempotencyKey === undefined
713
+ ? {}
714
+ : { idempotencyKey: task.idempotencyKey }),
715
+ policy: task.policyDecision,
716
+ ...(task.approval === undefined ? {} : { authorization: task.approval }),
717
+ metadata: { goal: task.request.goal },
718
+ };
719
+ }
720
+ export const MelraCapabilitiesInputSchema = z.object({}).strict();
721
+ export const MelraPlanInputSchema = TaskRequestSchema;
722
+ export const MelraExecuteInputSchema = z
723
+ .object({
724
+ taskId: z.string().uuid(),
725
+ approval: ApprovalResponseSchema.optional(),
726
+ })
727
+ .strict();
728
+ export const MelraTaskStatusInputSchema = z
729
+ .object({ taskId: z.string().uuid() })
730
+ .strict();
731
+ export const MelraTaskCancelInputSchema = z
732
+ .object({ taskId: z.string().uuid() })
733
+ .strict();
734
+ export const MelraReceiptBaseSchema = z
735
+ .object({
736
+ taskId: z.string().uuid().optional(),
737
+ receiptId: z.string().uuid().optional(),
738
+ })
739
+ .strict();
740
+ export const MelraReceiptInputSchema = MelraReceiptBaseSchema
741
+ .refine((value) => value.taskId !== undefined || value.receiptId !== undefined, {
742
+ message: "taskId or receiptId is required",
743
+ });
744
+ export const TOOL_NAMES = [
745
+ "melra_capabilities",
746
+ "melra_plan",
747
+ "melra_execute",
748
+ "melra_task_status",
749
+ "melra_task_cancel",
750
+ "melra_receipt",
751
+ "melra_workflow_plan",
752
+ "melra_workflow_advance",
753
+ "melra_workflow_status",
754
+ "melra_workflow_cancel",
755
+ "melra_workflow_control",
756
+ ];
757
+ //# sourceMappingURL=index.js.map