@pugi/sdk 0.1.0-alpha.3 → 0.1.0-alpha.6
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/audit-trace.d.ts +828 -54
- package/dist/audit-trace.js +141 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/subagent-contracts.d.ts +549 -0
- package/dist/subagent-contracts.js +230 -0
- package/package.json +1 -1
|
@@ -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
|