@pugi/sdk 0.1.0-alpha.3 → 0.1.0-alpha.5

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.
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Subagent dispatch contracts (Sprint a5.4 — M1 gap remediation D).
3
+ *
4
+ * The Pugi CLI's subagent surface is a typed contract between three layers:
5
+ * 1. The orchestrator (Mira in the brand persona registry; the parent
6
+ * engine loop in the actual runtime) decides a SubagentTask.
7
+ * 2. The CLI dispatcher (apps/pugi-cli/src/core/subagents/dispatcher.ts)
8
+ * resolves role to Cyber-Zoo persona, classifies isolation, emits
9
+ * lifecycle events, and returns a SubagentResult.
10
+ * 3. Future M2 worktree + M3 remote-VM implementations swap the dispatch
11
+ * backend without changing the contract.
12
+ *
13
+ * IMPORTANT: this module does NOT re-declare the persona roster. The
14
+ * Cyber-Zoo persona identities live in @pugi/personas and are mapped to
15
+ * roles inside the CLI. The SDK only carries the role + persona-slug pair
16
+ * on the wire so the cabinet UI can render the brand persona without
17
+ * shipping the full roster.
18
+ */
19
+ import { z } from 'zod';
20
+ import { permissionDecisionSchema, permissionModeSchema } from './permission-rules.js';
21
+ /**
22
+ * Closed enumeration of CLI subagent roles. Mirrors the SubagentRole union
23
+ * declared in apps/pugi-cli/src/core/agents/registry.ts. Keep both in
24
+ * lockstep: the CLI dispatcher imports from @pugi/sdk for wire validation
25
+ * and from the local registry for persona resolution, so adding a role
26
+ * requires touching both files together.
27
+ */
28
+ export const subagentRoleSchema = z.enum([
29
+ 'orchestrator',
30
+ 'architect',
31
+ 'coder',
32
+ 'verifier',
33
+ 'reviewer',
34
+ 'researcher',
35
+ 'release',
36
+ 'devops',
37
+ 'design_qa',
38
+ ]);
39
+ /**
40
+ * Isolation classification for a dispatched subagent. M1 ships the first
41
+ * three tiers; worktree lands in alpha-6 (per-task git worktree for every
42
+ * write subagent), remote_vm in M3 (dangerous / long-running jobs).
43
+ *
44
+ * - prompt_only: reasoning/review folded into the parent
45
+ * engine context. No separate filesystem
46
+ * handle; the parent owns every tool call.
47
+ * Mira (orchestrator) uses this at M1.
48
+ * - shared_fs_readonly: read tools are allowed; write/edit/bash
49
+ * classes are denied at the permission layer.
50
+ * Marcus / Vera / Anika get this at M1.
51
+ * - shared_fs_serialized: write tools are allowed but serialized
52
+ * through a coordinator-held lock so two
53
+ * write-subagents never race on the same
54
+ * workspace. Hiroshi / Olivia / Diego / Sofia.
55
+ * - worktree: per-subagent worktree directory (M2,
56
+ * ADR-0057). Writes never touch the primary
57
+ * workspace until merge.
58
+ * - remote_vm: engine VM spawn (M3). Out of scope for the
59
+ * CLI but reserved in the enum so the wire
60
+ * format does not change between milestones.
61
+ */
62
+ export const subagentIsolationSchema = z.enum([
63
+ 'prompt_only',
64
+ 'shared_fs_readonly',
65
+ 'shared_fs_serialized',
66
+ 'worktree',
67
+ 'remote_vm',
68
+ ]);
69
+ /**
70
+ * Status of a completed subagent dispatch.
71
+ * - shipped: the subagent returned a final answer and any write tools
72
+ * it invoked succeeded.
73
+ * - blocked: the subagent stopped because the budget was hit, plan
74
+ * mode refused a write, or a permission rule denied a tool
75
+ * call.
76
+ * - failed: the subagent crashed or returned a parseable error.
77
+ * - cancelled: the parent or operator cancelled the dispatch via the
78
+ * abort signal.
79
+ */
80
+ export const subagentStatusSchema = z.enum(['shipped', 'blocked', 'failed', 'cancelled']);
81
+ /**
82
+ * Per-dispatch budget. All three limits are optional; the dispatcher
83
+ * applies its own per-role defaults when a field is missing. Setting a
84
+ * field tightens the cap; the dispatcher never relaxes a budget.
85
+ */
86
+ export const subagentBudgetSchema = z.object({
87
+ tokens: z.number().int().positive().optional(),
88
+ dollars: z.number().positive().optional(),
89
+ wallClockMs: z.number().int().positive().optional(),
90
+ });
91
+ /**
92
+ * Dispatch input. allowedPaths / deniedPaths mirror the per-task shape on
93
+ * EngineTask so callers that already build engine tasks can promote a
94
+ * subset of the fields into a subagent dispatch without a second
95
+ * translation step.
96
+ */
97
+ export const subagentTaskSchema = z.object({
98
+ id: z.string().min(1),
99
+ role: subagentRoleSchema,
100
+ prompt: z.string().min(1),
101
+ permissionMode: permissionModeSchema,
102
+ budget: subagentBudgetSchema.optional(),
103
+ allowedPaths: z.array(z.string()).optional(),
104
+ deniedPaths: z.array(z.string()).optional(),
105
+ });
106
+ /**
107
+ * Dispatch result. personaSlug is the brand persona that owned the
108
+ * dispatch (from @pugi/personas via the CLI registry); cabinet UI and
109
+ * audit replay use it to render the persona avatar / hue token without
110
+ * needing to re-resolve the role.
111
+ *
112
+ * permissionDecisions is an OPTIONAL summary of the most important
113
+ * permission decisions the dispatcher applied or recorded. Full decision
114
+ * history lives in .pugi/events.jsonl; this field is a convenience pointer
115
+ * so cabinet UI can render the "Vera was blocked from editing X" line
116
+ * without re-parsing the events log.
117
+ */
118
+ export const subagentResultSchema = z.object({
119
+ taskId: z.string().min(1),
120
+ role: subagentRoleSchema,
121
+ personaSlug: z.string().min(1),
122
+ status: subagentStatusSchema,
123
+ summary: z.string(),
124
+ filesChanged: z.array(z.string()),
125
+ toolCallCount: z.number().int().nonnegative(),
126
+ tokensIn: z.number().int().nonnegative(),
127
+ tokensOut: z.number().int().nonnegative(),
128
+ durationMs: z.number().int().nonnegative(),
129
+ permissionDecisions: z.array(permissionDecisionSchema).optional(),
130
+ });
131
+ /* ------------------------------------------------------------------ */
132
+ /* Lifecycle events */
133
+ /* ------------------------------------------------------------------ */
134
+ /**
135
+ * Emitted once at dispatch start. parentSessionId is the session that owns
136
+ * the dispatch (typically the CLI's top-level PugiSession.id); future M2
137
+ * worktree dispatches will introduce a child session id that
138
+ * cross-references back to the parent.
139
+ */
140
+ export const subagentSpawnedEventSchema = z.object({
141
+ type: z.literal('subagent.spawned'),
142
+ taskId: z.string().min(1),
143
+ role: subagentRoleSchema,
144
+ personaSlug: z.string().min(1),
145
+ parentSessionId: z.string().min(1),
146
+ isolation: subagentIsolationSchema,
147
+ timestamp: z.string().datetime(),
148
+ });
149
+ /**
150
+ * Emitted once per tool call the subagent makes. toolName is the
151
+ * registered tool slug (read, write, edit, bash, ...). Full argument
152
+ * payloads stay in the global audit log under tool_call; this event is
153
+ * the subagent-scoped index so cabinet UI can render the per-persona tool
154
+ * tree without a second join.
155
+ */
156
+ export const subagentToolCallEventSchema = z.object({
157
+ type: z.literal('subagent.tool_call'),
158
+ taskId: z.string().min(1),
159
+ role: subagentRoleSchema,
160
+ personaSlug: z.string().min(1),
161
+ toolName: z.string().min(1),
162
+ toolCallId: z.string().min(1),
163
+ timestamp: z.string().datetime(),
164
+ });
165
+ /**
166
+ * Emitted once when the subagent reaches a terminal state with status
167
+ * shipped. The numeric fields mirror the SubagentResult summary so a
168
+ * downstream consumer can build a cabinet UI activity row from a single
169
+ * event without joining back to the result envelope.
170
+ */
171
+ export const subagentCompletedEventSchema = z.object({
172
+ type: z.literal('subagent.completed'),
173
+ taskId: z.string().min(1),
174
+ role: subagentRoleSchema,
175
+ personaSlug: z.string().min(1),
176
+ toolCallCount: z.number().int().nonnegative(),
177
+ tokensIn: z.number().int().nonnegative(),
178
+ tokensOut: z.number().int().nonnegative(),
179
+ durationMs: z.number().int().nonnegative(),
180
+ timestamp: z.string().datetime(),
181
+ });
182
+ /**
183
+ * Emitted once when the subagent terminates with status blocked. reason is
184
+ * a short machine-friendly slug (budget_exhausted, plan_mode_refused,
185
+ * permission_denied, tool_unavailable) so cabinet UI can group blocked
186
+ * dispatches by cause; detail is a human-readable summary the operator
187
+ * can act on.
188
+ */
189
+ export const subagentBlockedEventSchema = z.object({
190
+ type: z.literal('subagent.blocked'),
191
+ taskId: z.string().min(1),
192
+ role: subagentRoleSchema,
193
+ personaSlug: z.string().min(1),
194
+ reason: z.enum([
195
+ 'budget_exhausted',
196
+ 'plan_mode_refused',
197
+ 'permission_denied',
198
+ 'tool_unavailable',
199
+ ]),
200
+ detail: z.string().min(1),
201
+ timestamp: z.string().datetime(),
202
+ });
203
+ /**
204
+ * Emitted once when the subagent terminates with status failed. The error
205
+ * field is a one-line summary safe to print in the operator console; full
206
+ * stack traces stay in the global audit log.
207
+ */
208
+ export const subagentFailedEventSchema = z.object({
209
+ type: z.literal('subagent.failed'),
210
+ taskId: z.string().min(1),
211
+ role: subagentRoleSchema,
212
+ personaSlug: z.string().min(1),
213
+ error: z.string().min(1),
214
+ timestamp: z.string().datetime(),
215
+ });
216
+ /**
217
+ * Discriminated union of every subagent lifecycle event. Folded into the
218
+ * broader auditEventSchema in audit-trace.ts; exported here separately so
219
+ * dispatcher unit tests can validate against just the subagent surface
220
+ * without pulling the full audit-trace dependency graph through the test
221
+ * bundle.
222
+ */
223
+ export const subagentEventSchema = z.discriminatedUnion('type', [
224
+ subagentSpawnedEventSchema,
225
+ subagentToolCallEventSchema,
226
+ subagentCompletedEventSchema,
227
+ subagentBlockedEventSchema,
228
+ subagentFailedEventSchema,
229
+ ]);
230
+ //# sourceMappingURL=subagent-contracts.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pugi/sdk",
3
- "version": "0.1.0-alpha.3",
3
+ "version": "0.1.0-alpha.5",
4
4
  "description": "Pugi CLI contracts shared by the CLI, docs, and Anvil runtime",
5
5
  "homepage": "https://pugi.io",
6
6
  "repository": {