pi-agent-replicate 0.0.0-stage → 1.4.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.
Files changed (79) hide show
  1. package/AGENTS.md +27 -0
  2. package/CHANGELOG.md +147 -0
  3. package/CONTEXT.md +63 -0
  4. package/LICENSE +21 -0
  5. package/README.md +239 -2
  6. package/RELEASING.md +130 -0
  7. package/assets/banner.png +0 -0
  8. package/config.json.example +6 -0
  9. package/docs/README.md +20 -0
  10. package/docs/adr/0003-installable-role-packs.md +29 -0
  11. package/docs/adr/0008-adopt-pi-only-subagent-execution.md +22 -0
  12. package/docs/adr/0009-durable-recursive-subagent-ownership.md +23 -0
  13. package/docs/adr/0010-public-subagent-tree-registry.md +16 -0
  14. package/docs/public-subagent-tree-api.md +58 -0
  15. package/docs/settled-lifecycle-contract.md +71 -0
  16. package/docs/worktree-subagents.md +58 -0
  17. package/examples/role-pack/extension.ts +18 -0
  18. package/examples/role-pack/package.json +16 -0
  19. package/examples/role-pack/roles/example-reviewer.md +12 -0
  20. package/index.d.ts +116 -0
  21. package/index.js +1064 -0
  22. package/index.ts +155 -0
  23. package/package.json +70 -4
  24. package/pi-extension/subagents/activity.js +575 -0
  25. package/pi-extension/subagents/activity.ts +980 -0
  26. package/pi-extension/subagents/completion.js +184 -0
  27. package/pi-extension/subagents/completion.ts +229 -0
  28. package/pi-extension/subagents/delivery-drain.js +15 -0
  29. package/pi-extension/subagents/delivery-drain.ts +20 -0
  30. package/pi-extension/subagents/delivery-files.js +86 -0
  31. package/pi-extension/subagents/delivery-files.ts +105 -0
  32. package/pi-extension/subagents/delivery-log.js +51 -0
  33. package/pi-extension/subagents/delivery-log.ts +51 -0
  34. package/pi-extension/subagents/delivery-payload.ts +55 -0
  35. package/pi-extension/subagents/delivery.ts +219 -0
  36. package/pi-extension/subagents/execution-owner.js +30 -0
  37. package/pi-extension/subagents/execution-owner.ts +38 -0
  38. package/pi-extension/subagents/herdr.js +645 -0
  39. package/pi-extension/subagents/herdr.ts +927 -0
  40. package/pi-extension/subagents/index.ts +3400 -0
  41. package/pi-extension/subagents/launch.js +774 -0
  42. package/pi-extension/subagents/launch.ts +1124 -0
  43. package/pi-extension/subagents/lifecycle.js +477 -0
  44. package/pi-extension/subagents/lifecycle.ts +582 -0
  45. package/pi-extension/subagents/lineage.js +806 -0
  46. package/pi-extension/subagents/lineage.ts +841 -0
  47. package/pi-extension/subagents/replica-background.js +32 -0
  48. package/pi-extension/subagents/replica-background.ts +38 -0
  49. package/pi-extension/subagents/replica-branch.ts +45 -0
  50. package/pi-extension/subagents/replica-directory-settings.ts +51 -0
  51. package/pi-extension/subagents/replica-history.js +22 -0
  52. package/pi-extension/subagents/replica-history.ts +21 -0
  53. package/pi-extension/subagents/replica-work.js +326 -0
  54. package/pi-extension/subagents/replica-work.ts +336 -0
  55. package/pi-extension/subagents/replica-worker.js +74 -0
  56. package/pi-extension/subagents/replica-worker.ts +71 -0
  57. package/pi-extension/subagents/replication.ts +99 -0
  58. package/pi-extension/subagents/result-delivery-queue.ts +33 -0
  59. package/pi-extension/subagents/role-catalog.js +123 -0
  60. package/pi-extension/subagents/role-catalog.ts +24 -0
  61. package/pi-extension/subagents/runtime-routing.js +154 -0
  62. package/pi-extension/subagents/runtime-routing.ts +239 -0
  63. package/pi-extension/subagents/session-binding.js +346 -0
  64. package/pi-extension/subagents/session-binding.ts +339 -0
  65. package/pi-extension/subagents/session-work.js +219 -0
  66. package/pi-extension/subagents/session-work.ts +167 -0
  67. package/pi-extension/subagents/session.js +318 -0
  68. package/pi-extension/subagents/session.ts +448 -0
  69. package/pi-extension/subagents/settled-contract.js +47 -0
  70. package/pi-extension/subagents/settled-contract.ts +175 -0
  71. package/pi-extension/subagents/source-state.js +180 -0
  72. package/pi-extension/subagents/source-state.ts +184 -0
  73. package/pi-extension/subagents/status.ts +493 -0
  74. package/pi-extension/subagents/subagent-done.js +805 -0
  75. package/pi-extension/subagents/subagent-done.ts +878 -0
  76. package/pi-extension/subagents/terminal.js +111 -0
  77. package/pi-extension/subagents/terminal.ts +208 -0
  78. package/pi-extension/subagents/watchdog.js +122 -0
  79. package/pi-extension/subagents/watchdog.ts +171 -0
@@ -0,0 +1,477 @@
1
+ import { settledDeliveryKey } from "./settled-contract.js";
2
+ export function createLifecycle(startedAt) {
3
+ return {
4
+ process: {
5
+ kind: "starting",
6
+ startedAt
7
+ },
8
+ turn: {
9
+ kind: "unknown"
10
+ },
11
+ activityHealth: {
12
+ kind: "unseen"
13
+ },
14
+ activityDetail: null,
15
+ pane: {
16
+ kind: "unknown"
17
+ },
18
+ hasWorked: false,
19
+ lastActivitySequence: null,
20
+ delivery: "pending",
21
+ settledDelivery: {
22
+ lastActivitySequence: null,
23
+ delivered: new Set()
24
+ }
25
+ };
26
+ }
27
+ function isTerminal(process) {
28
+ return process.kind === "completed" || process.kind === "failed";
29
+ }
30
+ function startedAt(process) {
31
+ return process.startedAt;
32
+ }
33
+ export function observePaneInspection(lifecycle, inspection, observedAt) {
34
+ if (isTerminal(lifecycle.process)) return lifecycle;
35
+ if (lifecycle.process.kind === "finalizing") return lifecycle;
36
+ if (inspection.kind === "unavailable") {
37
+ const previous = lifecycle.pane.kind === "read-error" ? lifecycle.pane : null;
38
+ return {
39
+ ...lifecycle,
40
+ pane: {
41
+ kind: "read-error",
42
+ firstFailedAt: previous?.firstFailedAt ?? observedAt,
43
+ lastFailedAt: observedAt,
44
+ consecutiveFailures: (previous?.consecutiveFailures ?? 0) + 1,
45
+ error: inspection.error
46
+ }
47
+ };
48
+ }
49
+ if (inspection.kind === "missing") {
50
+ return {
51
+ ...lifecycle,
52
+ pane: {
53
+ kind: "missing",
54
+ detectedAt: observedAt,
55
+ ...inspection.error ? {
56
+ error: inspection.error
57
+ } : {}
58
+ }
59
+ };
60
+ }
61
+ const agentStatus = inspection.agentStatus;
62
+ const hasWorked = lifecycle.hasWorked || agentStatus === "working" || agentStatus === "blocked" || agentStatus === "done";
63
+ const pane = {
64
+ kind: "present",
65
+ observedAt,
66
+ agentStatus
67
+ };
68
+ const process = lifecycle.process.kind === "starting" ? {
69
+ kind: "running",
70
+ startedAt: lifecycle.process.startedAt,
71
+ confirmedAt: observedAt
72
+ } : lifecycle.process;
73
+ if (lifecycle.turn.kind === "interrupted") {
74
+ return {
75
+ ...lifecycle,
76
+ process,
77
+ pane,
78
+ hasWorked
79
+ };
80
+ }
81
+ let turn = lifecycle.turn;
82
+ if (agentStatus === "blocked") {
83
+ turn = hasWorked ? {
84
+ kind: "blocked",
85
+ startedAt: lifecycle.turn.kind === "blocked" ? lifecycle.turn.startedAt : observedAt
86
+ } : {
87
+ kind: "starting",
88
+ observedAt: lifecycle.turn.kind === "starting" ? lifecycle.turn.observedAt : observedAt
89
+ };
90
+ } else if (agentStatus === "working") {
91
+ turn = {
92
+ kind: "active",
93
+ startedAt: lifecycle.turn.kind === "active" ? lifecycle.turn.startedAt : observedAt,
94
+ source: "herdr",
95
+ ...lifecycle.activityDetail ? {
96
+ activity: lifecycle.activityDetail
97
+ } : {}
98
+ };
99
+ } else if (agentStatus === "done" || agentStatus === "idle") {
100
+ turn = hasWorked ? {
101
+ kind: "waiting",
102
+ startedAt: lifecycle.turn.kind === "waiting" ? lifecycle.turn.startedAt : observedAt
103
+ } : {
104
+ kind: "starting",
105
+ observedAt: lifecycle.turn.kind === "starting" ? lifecycle.turn.observedAt : observedAt
106
+ };
107
+ } else if (agentStatus === "unknown") {
108
+ return {
109
+ ...lifecycle,
110
+ process,
111
+ pane
112
+ };
113
+ }
114
+ return {
115
+ ...lifecycle,
116
+ process,
117
+ turn,
118
+ pane,
119
+ hasWorked
120
+ };
121
+ }
122
+ export function observeActivity(lifecycle, read, observedAt) {
123
+ if (lifecycle.process.kind === "finalizing" || isTerminal(lifecycle.process)) return lifecycle;
124
+ const detail = (()=>{
125
+ if (!read.ok) return null;
126
+ const activity = read.activity;
127
+ if (lifecycle.lastActivitySequence != null && activity.sequence < lifecycle.lastActivitySequence) {
128
+ return null;
129
+ }
130
+ if (activity.phase !== "active") return null;
131
+ if (activity.activeScope === "compaction") {
132
+ const label = activity.compactionReason ? `compacting (${activity.compactionReason})` : "compacting";
133
+ return {
134
+ kind: "scope",
135
+ scope: "compaction",
136
+ since: activity.activeSince ?? activity.updatedAt,
137
+ observedAt: activity.updatedAt,
138
+ sequence: activity.sequence,
139
+ label
140
+ };
141
+ }
142
+ if (activity.activeScope === "tool") {
143
+ return {
144
+ kind: "scope",
145
+ scope: "tool",
146
+ since: activity.toolStartedAt ?? activity.activeSince ?? activity.updatedAt,
147
+ observedAt: activity.updatedAt,
148
+ sequence: activity.sequence,
149
+ ...activity.toolName ? {
150
+ label: activity.toolName
151
+ } : {}
152
+ };
153
+ }
154
+ if (activity.activeScope === "provider") {
155
+ return {
156
+ kind: "scope",
157
+ scope: "provider",
158
+ since: activity.activeSince ?? activity.updatedAt,
159
+ observedAt: activity.updatedAt,
160
+ sequence: activity.sequence,
161
+ label: "provider"
162
+ };
163
+ }
164
+ if (activity.activeScope === "streaming") {
165
+ return {
166
+ kind: "scope",
167
+ scope: "streaming",
168
+ since: activity.activeSince ?? activity.updatedAt,
169
+ observedAt: activity.updatedAt,
170
+ sequence: activity.sequence,
171
+ label: "streaming"
172
+ };
173
+ }
174
+ if (activity.activeScope === "agent" || activity.activeScope === "turn") {
175
+ return {
176
+ kind: "scope",
177
+ scope: activity.activeScope,
178
+ since: activity.activeSince ?? activity.updatedAt,
179
+ observedAt: activity.updatedAt,
180
+ sequence: activity.sequence
181
+ };
182
+ }
183
+ return null;
184
+ })();
185
+ if (!read.ok) {
186
+ const since = lifecycle.activityHealth.kind === "problem" ? lifecycle.activityHealth.since : observedAt;
187
+ return {
188
+ ...lifecycle,
189
+ activityHealth: {
190
+ kind: "problem",
191
+ reason: read.reason,
192
+ since,
193
+ ...read.error ? {
194
+ error: read.error
195
+ } : {}
196
+ }
197
+ };
198
+ }
199
+ if (!detail) {
200
+ return {
201
+ ...lifecycle,
202
+ activityDetail: null,
203
+ activityHealth: {
204
+ kind: "healthy",
205
+ observedAt
206
+ },
207
+ lastActivitySequence: Math.max(lifecycle.lastActivitySequence ?? -1, read.activity.sequence)
208
+ };
209
+ }
210
+ let resumesInterruptedTurn = false;
211
+ if (lifecycle.turn.kind === "interrupted") {
212
+ const staleInterruptSnapshot = detail.observedAt < lifecycle.turn.requestedAt || detail.observedAt === lifecycle.turn.requestedAt && lifecycle.turn.previousActivitySequence != null && detail.sequence <= lifecycle.turn.previousActivitySequence;
213
+ if (staleInterruptSnapshot) return lifecycle;
214
+ resumesInterruptedTurn = true;
215
+ }
216
+ const process = lifecycle.process.kind === "starting" ? {
217
+ kind: "running",
218
+ startedAt: lifecycle.process.startedAt,
219
+ confirmedAt: observedAt
220
+ } : lifecycle.process;
221
+ let turn = lifecycle.turn;
222
+ const sameDetail = lifecycle.activityDetail?.kind === "scope" && lifecycle.activityDetail.scope === detail.scope && lifecycle.activityDetail.label === detail.label;
223
+ const detailStartedAt = sameDetail && lifecycle.turn.kind === "active" ? lifecycle.turn.startedAt : detail.since;
224
+ if (resumesInterruptedTurn) {
225
+ turn = {
226
+ kind: "active",
227
+ startedAt: detailStartedAt,
228
+ source: "activity",
229
+ activity: detail
230
+ };
231
+ } else if (lifecycle.turn.kind !== "interrupted") {
232
+ if (lifecycle.pane.kind === "present" && lifecycle.pane.agentStatus === "working") {
233
+ turn = {
234
+ kind: "active",
235
+ startedAt: detailStartedAt,
236
+ source: "activity",
237
+ activity: detail
238
+ };
239
+ } else if (lifecycle.pane.kind === "unknown" || lifecycle.pane.kind === "read-error") {
240
+ turn = {
241
+ kind: "active",
242
+ startedAt: detailStartedAt,
243
+ source: "fallback",
244
+ activity: detail
245
+ };
246
+ }
247
+ }
248
+ return {
249
+ ...lifecycle,
250
+ process,
251
+ turn,
252
+ activityDetail: detail,
253
+ activityHealth: {
254
+ kind: "healthy",
255
+ observedAt
256
+ },
257
+ lastActivitySequence: detail.sequence
258
+ };
259
+ }
260
+ export function markProcessRunning(lifecycle, confirmedAt) {
261
+ if (lifecycle.process.kind !== "starting") return lifecycle;
262
+ return {
263
+ ...lifecycle,
264
+ process: {
265
+ kind: "running",
266
+ startedAt: lifecycle.process.startedAt,
267
+ confirmedAt
268
+ }
269
+ };
270
+ }
271
+ export function markInterruptRequested(lifecycle, requestedAt) {
272
+ if (lifecycle.process.kind === "finalizing" || isTerminal(lifecycle.process)) return lifecycle;
273
+ return {
274
+ ...lifecycle,
275
+ turn: {
276
+ kind: "interrupted",
277
+ requestedAt,
278
+ previousActivitySequence: lifecycle.lastActivitySequence
279
+ }
280
+ };
281
+ }
282
+ export function consumeInterruptBoundary(lifecycle, sequence) {
283
+ if (lifecycle.turn.kind !== "interrupted") return lifecycle;
284
+ return {
285
+ ...lifecycle,
286
+ interruptSettledSequence: sequence,
287
+ turn: {
288
+ kind: "waiting",
289
+ startedAt: lifecycle.turn.requestedAt
290
+ }
291
+ };
292
+ }
293
+ export function markCompletionDetected(lifecycle, completion, detectedAt) {
294
+ if (lifecycle.process.kind === "finalizing" || isTerminal(lifecycle.process)) return lifecycle;
295
+ return {
296
+ ...lifecycle,
297
+ process: {
298
+ kind: "finalizing",
299
+ startedAt: startedAt(lifecycle.process),
300
+ detectedAt: Math.max(startedAt(lifecycle.process), detectedAt),
301
+ completion
302
+ }
303
+ };
304
+ }
305
+ export function markCompleted(lifecycle, completedAt) {
306
+ if (isTerminal(lifecycle.process)) return lifecycle;
307
+ if (lifecycle.process.kind !== "finalizing") return lifecycle;
308
+ return {
309
+ ...lifecycle,
310
+ process: {
311
+ kind: "completed",
312
+ startedAt: lifecycle.process.startedAt,
313
+ detectedAt: lifecycle.process.detectedAt,
314
+ completedAt: Math.max(lifecycle.process.detectedAt, completedAt),
315
+ completion: lifecycle.process.completion
316
+ }
317
+ };
318
+ }
319
+ export function markFailed(lifecycle, error, detectedAt, exitCode) {
320
+ if (isTerminal(lifecycle.process)) return lifecycle;
321
+ const start = startedAt(lifecycle.process);
322
+ const detected = lifecycle.process.kind === "finalizing" ? lifecycle.process.detectedAt : Math.max(start, detectedAt);
323
+ return {
324
+ ...lifecycle,
325
+ process: {
326
+ kind: "failed",
327
+ startedAt: start,
328
+ detectedAt: detected,
329
+ completedAt: Math.max(detected, detectedAt),
330
+ error,
331
+ ...exitCode == null ? {} : {
332
+ exitCode
333
+ }
334
+ }
335
+ };
336
+ }
337
+ export function markDelivery(lifecycle, delivery) {
338
+ if (lifecycle.delivery !== "pending") return lifecycle;
339
+ return {
340
+ ...lifecycle,
341
+ delivery
342
+ };
343
+ }
344
+ function settledGate(lifecycle) {
345
+ return lifecycle.settledDelivery ?? {
346
+ lastActivitySequence: null,
347
+ delivered: new Set()
348
+ };
349
+ }
350
+ export function observeSettledActivity(lifecycle, activitySequence) {
351
+ const gate = settledGate(lifecycle);
352
+ if (gate.lastActivitySequence != null && activitySequence <= gate.lastActivitySequence) {
353
+ return lifecycle;
354
+ }
355
+ return {
356
+ ...lifecycle,
357
+ settledDelivery: {
358
+ ...gate,
359
+ lastActivitySequence: activitySequence
360
+ }
361
+ };
362
+ }
363
+ export function hasSettledAssistant(lifecycle, identity) {
364
+ return settledGate(lifecycle).delivered.has(settledDeliveryKey(identity));
365
+ }
366
+ export function markSettledAssistantDelivered(lifecycle, identity, activitySequence) {
367
+ const gate = settledGate(lifecycle);
368
+ const key = settledDeliveryKey(identity);
369
+ if (gate.delivered.has(key)) return lifecycle;
370
+ if (gate.lastActivitySequence != null && activitySequence < gate.lastActivitySequence) {
371
+ return lifecycle;
372
+ }
373
+ const delivered = new Set(gate.delivered);
374
+ delivered.add(key);
375
+ return {
376
+ ...lifecycle,
377
+ settledDelivery: {
378
+ lastActivitySequence: gate.lastActivitySequence == null ? activitySequence : Math.max(gate.lastActivitySequence, activitySequence),
379
+ delivered
380
+ }
381
+ };
382
+ }
383
+ export function projectLifecycle(lifecycle, now) {
384
+ const process = lifecycle.process;
385
+ if (process.kind === "finalizing") return {
386
+ kind: "finalizing",
387
+ runtimeEndedAt: process.detectedAt
388
+ };
389
+ if (process.kind === "completed") return {
390
+ kind: "completed",
391
+ runtimeEndedAt: process.completedAt
392
+ };
393
+ if (process.kind === "failed") return {
394
+ kind: "failed",
395
+ label: process.error,
396
+ runtimeEndedAt: process.completedAt
397
+ };
398
+ if (lifecycle.pane.kind === "read-error" && now - lifecycle.pane.firstFailedAt >= 60_000) {
399
+ return {
400
+ kind: "stalled",
401
+ stateDurationSince: lifecycle.pane.firstFailedAt
402
+ };
403
+ }
404
+ const turn = lifecycle.turn;
405
+ switch(turn.kind){
406
+ case "interrupted":
407
+ return {
408
+ kind: "interrupted",
409
+ stateDurationSince: turn.requestedAt
410
+ };
411
+ case "active":
412
+ {
413
+ if (turn.activity?.kind === "scope") {
414
+ const label = turn.activity.label ?? turn.activity.scope;
415
+ return {
416
+ kind: "active",
417
+ label,
418
+ stateDurationSince: turn.startedAt
419
+ };
420
+ }
421
+ return {
422
+ kind: "active",
423
+ label: turn.source === "herdr" ? "agent working" : "agent active",
424
+ stateDurationSince: turn.startedAt
425
+ };
426
+ }
427
+ case "blocked":
428
+ return {
429
+ kind: "blocked",
430
+ stateDurationSince: turn.startedAt
431
+ };
432
+ case "waiting":
433
+ return {
434
+ kind: "waiting",
435
+ stateDurationSince: turn.startedAt
436
+ };
437
+ case "starting":
438
+ return {
439
+ kind: "starting",
440
+ stateDurationSince: turn.observedAt
441
+ };
442
+ case "unknown":
443
+ return process.kind === "running" ? {
444
+ kind: "running"
445
+ } : {
446
+ kind: "starting"
447
+ };
448
+ }
449
+ }
450
+ export function lifecycleTransition(previous, next) {
451
+ if (previous !== "stalled" && next === "stalled") return "stalled";
452
+ if (previous === "stalled" && (next === "active" || next === "blocked" || next === "waiting" || next === "interrupted" || next === "running" || next === "starting")) {
453
+ return "recovered";
454
+ }
455
+ return null;
456
+ }
457
+ export function formatLifecycleTransitionLine(name, projection, transition, now, startedAt, formatElapsed) {
458
+ const runtime = formatElapsed(Math.max(0, now - startedAt));
459
+ const duration = projection.stateDurationSince == null ? "" : ` ${formatElapsed(now - projection.stateDurationSince)}`;
460
+ if (transition === "stalled") {
461
+ return `${name} running ${runtime}, stalled${duration}.`;
462
+ }
463
+ if (projection.kind === "waiting") {
464
+ return `${name} running ${runtime}, recovered; waiting${duration}.`;
465
+ }
466
+ if (projection.kind === "active") {
467
+ const detail = projection.label ? ` (${projection.label}${duration})` : duration;
468
+ return `${name} running ${runtime}, recovered; active${detail}.`;
469
+ }
470
+ if (projection.kind === "blocked") {
471
+ return `${name} running ${runtime}, recovered; blocked${duration}.`;
472
+ }
473
+ if (projection.kind === "interrupted") {
474
+ return `${name} running ${runtime}, recovered; interrupted${duration}.`;
475
+ }
476
+ return `${name} running ${runtime}, recovered; running.`;
477
+ }