agent-tempo 1.3.1 → 1.4.1

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 (199) hide show
  1. package/CLAUDE.md +39 -5
  2. package/README.md +6 -2
  3. package/dashboard/dist/assets/{index-D6Xyje_n.js → index-jmYe6rmS.js} +2 -2
  4. package/dashboard/dist/assets/index-jmYe6rmS.js.map +1 -0
  5. package/dashboard/dist/index.html +1 -1
  6. package/dashboard/package.json +1 -1
  7. package/dist/activities/outbox.d.ts +30 -1
  8. package/dist/activities/outbox.js +96 -3
  9. package/dist/adapters/base.js +5 -0
  10. package/dist/adapters/index.d.ts +1 -1
  11. package/dist/adapters/index.js +7 -0
  12. package/dist/adapters/pi/adapter.d.ts +2 -0
  13. package/dist/adapters/pi/adapter.js +43 -0
  14. package/dist/adapters/pi/index.d.ts +16 -0
  15. package/dist/adapters/pi/index.js +10 -0
  16. package/dist/client/core.js +9 -2
  17. package/dist/client/interface.d.ts +6 -0
  18. package/dist/config.d.ts +79 -0
  19. package/dist/config.js +74 -0
  20. package/dist/daemon.js +32 -1
  21. package/dist/http/aggregate.d.ts +22 -1
  22. package/dist/http/aggregate.js +41 -0
  23. package/dist/http/auth.d.ts +94 -8
  24. package/dist/http/auth.js +93 -9
  25. package/dist/http/body.d.ts +4 -1
  26. package/dist/http/body.js +6 -3
  27. package/dist/http/event-bus.js +1 -0
  28. package/dist/http/event-types.d.ts +34 -2
  29. package/dist/http/event-types.js +1 -0
  30. package/dist/http/gate-audit.d.ts +12 -0
  31. package/dist/http/gate-audit.js +95 -0
  32. package/dist/http/gate-registry.d.ts +167 -0
  33. package/dist/http/gate-registry.js +163 -0
  34. package/dist/http/gate-routes.d.ts +48 -0
  35. package/dist/http/gate-routes.js +102 -0
  36. package/dist/http/ingest-registry.d.ts +30 -0
  37. package/dist/http/ingest-registry.js +108 -0
  38. package/dist/http/inner-loop-routes.d.ts +66 -0
  39. package/dist/http/inner-loop-routes.js +182 -0
  40. package/dist/http/inner-loop.d.ts +92 -0
  41. package/dist/http/inner-loop.js +155 -0
  42. package/dist/http/server.d.ts +38 -3
  43. package/dist/http/server.js +211 -6
  44. package/dist/http/snapshot.d.ts +6 -0
  45. package/dist/http/snapshot.js +6 -0
  46. package/dist/pi/cue-pump.d.ts +61 -0
  47. package/dist/pi/cue-pump.js +95 -0
  48. package/dist/pi/extension.d.ts +45 -0
  49. package/dist/pi/extension.js +407 -0
  50. package/dist/pi/gate-client.d.ts +54 -0
  51. package/dist/pi/gate-client.js +136 -0
  52. package/dist/pi/headless.d.ts +85 -0
  53. package/dist/pi/headless.js +250 -0
  54. package/dist/pi/index.d.ts +28 -0
  55. package/dist/pi/index.js +43 -0
  56. package/dist/pi/inner-loop-client.d.ts +67 -0
  57. package/dist/pi/inner-loop-client.js +164 -0
  58. package/dist/pi/inner-loop-publisher.d.ts +187 -0
  59. package/dist/pi/inner-loop-publisher.js +236 -0
  60. package/dist/pi/lazy-proxy.d.ts +37 -0
  61. package/dist/pi/lazy-proxy.js +55 -0
  62. package/dist/pi/mission-control/actions.d.ts +48 -0
  63. package/dist/pi/mission-control/actions.js +98 -0
  64. package/dist/pi/mission-control/board.d.ts +88 -0
  65. package/dist/pi/mission-control/board.js +141 -0
  66. package/dist/pi/mission-control/extension.d.ts +51 -0
  67. package/dist/pi/mission-control/extension.js +330 -0
  68. package/dist/pi/mission-control/index.d.ts +15 -0
  69. package/dist/pi/mission-control/index.js +32 -0
  70. package/dist/pi/mission-control/inner-tail.d.ts +48 -0
  71. package/dist/pi/mission-control/inner-tail.js +76 -0
  72. package/dist/pi/mission-control/pi-ui.d.ts +43 -0
  73. package/dist/pi/mission-control/pi-ui.js +10 -0
  74. package/dist/pi/mission-control/render.d.ts +6 -0
  75. package/dist/pi/mission-control/render.js +98 -0
  76. package/dist/pi/phase-driver.d.ts +74 -0
  77. package/dist/pi/phase-driver.js +122 -0
  78. package/dist/pi/pi-types.d.ts +222 -0
  79. package/dist/pi/pi-types.js +21 -0
  80. package/dist/pi/probe.d.ts +99 -0
  81. package/dist/pi/probe.js +179 -0
  82. package/dist/pi/render-tools.d.ts +17 -0
  83. package/dist/pi/render-tools.js +56 -0
  84. package/dist/pi/reset-pump.d.ts +47 -0
  85. package/dist/pi/reset-pump.js +85 -0
  86. package/dist/pi/session-seed.d.ts +74 -0
  87. package/dist/pi/session-seed.js +103 -0
  88. package/dist/pi/tool-capability.d.ts +60 -0
  89. package/dist/pi/tool-capability.js +156 -0
  90. package/dist/pi/workflow-client.d.ts +158 -0
  91. package/dist/pi/workflow-client.js +289 -0
  92. package/dist/pi/zod-to-typebox.d.ts +74 -0
  93. package/dist/pi/zod-to-typebox.js +191 -0
  94. package/dist/server-tools.d.ts +2 -0
  95. package/dist/server-tools.js +50 -46
  96. package/dist/spawn.d.ts +55 -0
  97. package/dist/spawn.js +72 -0
  98. package/dist/tools/agent-types.d.ts +2 -2
  99. package/dist/tools/agent-types.js +22 -17
  100. package/dist/tools/attachment-info.d.ts +2 -2
  101. package/dist/tools/attachment-info.js +38 -33
  102. package/dist/tools/broadcast.d.ts +2 -2
  103. package/dist/tools/broadcast.js +69 -64
  104. package/dist/tools/cancel-stage.d.ts +2 -2
  105. package/dist/tools/cancel-stage.js +20 -15
  106. package/dist/tools/clear-state.d.ts +2 -2
  107. package/dist/tools/clear-state.js +25 -20
  108. package/dist/tools/coat-check-evict.d.ts +2 -2
  109. package/dist/tools/coat-check-evict.js +29 -24
  110. package/dist/tools/coat-check-get.d.ts +2 -2
  111. package/dist/tools/coat-check-get.js +38 -33
  112. package/dist/tools/coat-check-list.d.ts +2 -2
  113. package/dist/tools/coat-check-list.js +48 -43
  114. package/dist/tools/coat-check-put.d.ts +2 -2
  115. package/dist/tools/coat-check-put.js +38 -33
  116. package/dist/tools/cue.d.ts +2 -2
  117. package/dist/tools/cue.js +57 -52
  118. package/dist/tools/descriptor.d.ts +72 -0
  119. package/dist/tools/descriptor.js +39 -0
  120. package/dist/tools/destroy.d.ts +2 -2
  121. package/dist/tools/destroy.js +153 -148
  122. package/dist/tools/ensemble.d.ts +2 -2
  123. package/dist/tools/ensemble.js +71 -66
  124. package/dist/tools/evaluate-gate.d.ts +2 -2
  125. package/dist/tools/evaluate-gate.js +33 -27
  126. package/dist/tools/fetch-state.d.ts +2 -2
  127. package/dist/tools/fetch-state.js +42 -37
  128. package/dist/tools/gates.d.ts +2 -2
  129. package/dist/tools/gates.js +39 -34
  130. package/dist/tools/hosts.d.ts +2 -2
  131. package/dist/tools/hosts.js +25 -20
  132. package/dist/tools/listen.d.ts +2 -2
  133. package/dist/tools/listen.js +23 -18
  134. package/dist/tools/load-lineup.d.ts +2 -2
  135. package/dist/tools/load-lineup.js +324 -319
  136. package/dist/tools/migrate.d.ts +2 -2
  137. package/dist/tools/migrate.js +45 -40
  138. package/dist/tools/pause.d.ts +2 -2
  139. package/dist/tools/pause.js +34 -29
  140. package/dist/tools/play.d.ts +2 -2
  141. package/dist/tools/play.js +53 -48
  142. package/dist/tools/quality-gate.d.ts +2 -2
  143. package/dist/tools/quality-gate.js +26 -21
  144. package/dist/tools/recall.d.ts +2 -2
  145. package/dist/tools/recall.js +32 -27
  146. package/dist/tools/recruit.d.ts +2 -2
  147. package/dist/tools/recruit.js +340 -256
  148. package/dist/tools/release.d.ts +2 -2
  149. package/dist/tools/release.js +85 -80
  150. package/dist/tools/report.d.ts +2 -2
  151. package/dist/tools/report.js +28 -23
  152. package/dist/tools/reset.d.ts +3 -0
  153. package/dist/tools/reset.js +51 -0
  154. package/dist/tools/restart.d.ts +2 -2
  155. package/dist/tools/restart.js +51 -46
  156. package/dist/tools/restore.d.ts +2 -2
  157. package/dist/tools/restore.js +76 -71
  158. package/dist/tools/save-lineup.d.ts +2 -2
  159. package/dist/tools/save-lineup.js +32 -27
  160. package/dist/tools/save-state.d.ts +2 -2
  161. package/dist/tools/save-state.js +31 -26
  162. package/dist/tools/schedule.d.ts +2 -2
  163. package/dist/tools/schedule.js +133 -128
  164. package/dist/tools/schedules.d.ts +2 -2
  165. package/dist/tools/schedules.js +41 -36
  166. package/dist/tools/set-ensemble-description.d.ts +2 -2
  167. package/dist/tools/set-ensemble-description.js +26 -21
  168. package/dist/tools/set-name.d.ts +2 -2
  169. package/dist/tools/set-name.js +38 -33
  170. package/dist/tools/set-part.d.ts +2 -2
  171. package/dist/tools/set-part.js +20 -15
  172. package/dist/tools/shutdown.d.ts +2 -2
  173. package/dist/tools/shutdown.js +39 -34
  174. package/dist/tools/stage.d.ts +2 -2
  175. package/dist/tools/stage.js +28 -23
  176. package/dist/tools/stages.d.ts +2 -2
  177. package/dist/tools/stages.js +36 -31
  178. package/dist/tools/unschedule.d.ts +2 -2
  179. package/dist/tools/unschedule.js +30 -25
  180. package/dist/tools/who-am-i.d.ts +2 -2
  181. package/dist/tools/who-am-i.js +36 -31
  182. package/dist/tools/worktree.d.ts +2 -2
  183. package/dist/tools/worktree.js +134 -129
  184. package/dist/tui/index.js +6 -6
  185. package/dist/types.d.ts +47 -2
  186. package/dist/types.js +1 -1
  187. package/dist/utils/default-part.js +1 -0
  188. package/dist/utils/sdk-probe.d.ts +23 -0
  189. package/dist/utils/sdk-probe.js +46 -7
  190. package/dist/worker.d.ts +3 -1
  191. package/dist/worker.js +6 -2
  192. package/dist/workflows/session.js +70 -2
  193. package/dist/workflows/signals.d.ts +32 -2
  194. package/dist/workflows/signals.js +25 -2
  195. package/package.json +4 -1
  196. package/workflow-bundle.js +97 -6
  197. package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
  198. package/dist/tools/helpers.d.ts +0 -21
  199. package/dist/tools/helpers.js +0 -25
@@ -33,149 +33,154 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.registerWorktreeTool = registerWorktreeTool;
36
+ exports.buildWorktreeTool = buildWorktreeTool;
37
37
  const zod_1 = require("zod");
38
38
  const resolve_1 = require("./resolve");
39
39
  const signals_1 = require("../workflows/signals");
40
- const helpers_1 = require("./helpers");
40
+ const descriptor_1 = require("./descriptor");
41
41
  const worktree_1 = require("../utils/worktree");
42
42
  const validation_1 = require("../utils/validation");
43
- function registerWorktreeTool(server, client, config, handle, getPlayerId) {
44
- (0, helpers_1.defineTool)(server, 'worktree', 'Manage git worktrees for player isolation. Conductor only. Actions: create (provision worktree for a player), remove (clean up), list (show active worktrees). Use when multiple players commit to different branches of the same repo simultaneously; skip for read-only work, sequential work, or tasks under ~5 min. IMPORTANT: before `remove`, have the player stop any long-running processes inside the worktree (dev servers, file watchers) — on Windows a memory-mapped native module will block directory removal and `remove` will fail. See docs/orchestration.md#when-to-use-worktrees.', {
45
- action: zod_1.z.enum(['create', 'remove', 'list']).describe('Action to perform'),
46
- player: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional().describe('Player name (required for create/remove)'),
47
- branch: zod_1.z.string().optional().describe('Git branch for the worktree (defaults to {ensemble}/{player-name})'),
48
- }, async (args) => {
49
- const { action, player, branch } = args;
50
- try {
51
- switch (action) {
52
- case 'create': {
53
- if (!player) {
54
- return (0, helpers_1.fail)('`player` is required for create action.');
55
- }
56
- // Verify player exists
57
- const targetHandle = await (0, resolve_1.resolveSession)(client, config.ensemble, player);
58
- if (!targetHandle) {
59
- return (0, helpers_1.fail)(`No active session found for "${player}".`);
60
- }
61
- // Check target is on same host (cross-machine worktrees not supported)
62
- const targetMeta = await targetHandle.query('getMetadata');
63
- const { hostname } = await Promise.resolve().then(() => __importStar(require('os'))).then((os) => ({ hostname: os.hostname() }));
64
- if (targetMeta.hostname && targetMeta.hostname !== hostname) {
65
- return (0, helpers_1.fail)(`Cannot create worktree for "${player}" — they are on host "${targetMeta.hostname}" but worktrees must be created locally.`);
66
- }
67
- const gitRoot = process.cwd();
68
- const result = (0, worktree_1.createWorktree)({
69
- gitRoot,
70
- ensemble: config.ensemble,
71
- playerName: player,
72
- branch,
73
- });
74
- if (result.created) {
75
- (0, worktree_1.installDependencies)(result.path);
76
- }
77
- // Record in conductor's worktree state
78
- const entry = {
79
- player,
80
- path: result.path,
81
- branch: result.branch,
82
- gitRoot,
83
- createdAt: new Date().toISOString(),
84
- createdBy: getPlayerId(),
85
- };
86
- await handle.signal('setWorktree', entry);
87
- // Auto-cue the player with worktree info
88
- const cueMessage = [
89
- `\u{1f33f} **Worktree ready** for your task:`,
90
- `- **Path**: \`${result.path}\``,
91
- `- **Branch**: \`${result.branch}\``,
92
- '',
93
- `Run \`cd ${result.path}\` to switch to your isolated workspace.`,
94
- `All your changes will be on branch \`${result.branch}\`.`,
95
- `When done, commit and push \u2014 the conductor will handle cleanup.`,
96
- ].join('\n');
97
- await handle.executeUpdate(signals_1.submitOutboxUpdate, {
98
- args: [{
99
- type: 'cue',
100
- targetPlayerId: player,
101
- message: cueMessage,
102
- }],
103
- });
104
- // #261: when we reused an existing worktree directory, surface the
105
- // actual state transition (same branch / switched / created-from-main)
106
- // so the conductor sees ground truth instead of the prior silent
107
- // "reused existing" that implied the worktree was already on the
108
- // requested branch.
109
- const createdLabel = result.created
110
- ? 'new'
111
- : (() => {
112
- switch (result.switched) {
113
- case 'same':
114
- return `reused existing (already on \`${result.branch}\`)`;
115
- case 'switched':
116
- return `reused existing (switched to \`${result.branch}\`)`;
117
- case 'created-from-main':
118
- return `reused existing (created \`${result.branch}\` from origin/main)`;
119
- default:
120
- return 'reused existing';
121
- }
122
- })();
123
- return (0, helpers_1.ok)(`Worktree created for **${player}**:\n- Path: \`${result.path}\`\n- Branch: \`${result.branch}\`\n- Created: ${createdLabel}\n\nPlayer has been notified.`);
124
- }
125
- case 'remove': {
126
- if (!player) {
127
- return (0, helpers_1.fail)('`player` is required for remove action.');
128
- }
129
- // Look up worktree entry from conductor state
130
- const entries = await handle.query('worktrees');
131
- const entry = entries.find((w) => w.player === player);
132
- if (!entry) {
133
- return (0, helpers_1.fail)(`No worktree found for player "${player}".`);
134
- }
135
- // Remove from disk. #594: removeWorktree throws if the directory
136
- // survives the removal (Windows file-lock half-removal). We must
137
- // NOT signal `removeWorktree` state or cue the player until disk
138
- // removal is confirmed — otherwise Temporal state records "no
139
- // worktree" while a locked orphan directory remains on disk, and
140
- // the next `create` fails with a confusing git fatal.
141
- try {
142
- (0, worktree_1.removeWorktree)(entry.path, entry.gitRoot);
143
- }
144
- catch (err) {
145
- return (0, helpers_1.fail)(`Worktree for **${player}** could not be removed: ${(0, helpers_1.formatError)(err)}\n\n` +
146
- `Conductor state is unchanged — the worktree is still tracked. ` +
147
- `Have the player stop any long-running processes inside the worktree ` +
148
- `(dev servers, file watchers), then retry \`worktree remove\`.`);
149
- }
150
- // Remove from conductor state (only reached on confirmed disk removal)
151
- await handle.signal('removeWorktree', player);
152
- // Auto-cue the player
153
- try {
43
+ function buildWorktreeTool(client, config, handle, getPlayerId) {
44
+ return {
45
+ name: 'worktree',
46
+ description: 'Manage git worktrees for player isolation. Conductor only. Actions: create (provision worktree for a player), remove (clean up), list (show active worktrees). Use when multiple players commit to different branches of the same repo simultaneously; skip for read-only work, sequential work, or tasks under ~5 min. IMPORTANT: before `remove`, have the player stop any long-running processes inside the worktree (dev servers, file watchers) — on Windows a memory-mapped native module will block directory removal and `remove` will fail. See docs/orchestration.md#when-to-use-worktrees.',
47
+ params: {
48
+ action: zod_1.z.enum(['create', 'remove', 'list']).describe('Action to perform'),
49
+ player: zod_1.z.string().max(validation_1.PLAYER_NAME_MAX).optional().describe('Player name (required for create/remove)'),
50
+ branch: zod_1.z.string().optional().describe('Git branch for the worktree (defaults to {ensemble}/{player-name})'),
51
+ },
52
+ handler: async (args) => {
53
+ const { action, player, branch } = args;
54
+ try {
55
+ switch (action) {
56
+ case 'create': {
57
+ if (!player) {
58
+ return (0, descriptor_1.fail)('`player` is required for create action.');
59
+ }
60
+ // Verify player exists
61
+ const targetHandle = await (0, resolve_1.resolveSession)(client, config.ensemble, player);
62
+ if (!targetHandle) {
63
+ return (0, descriptor_1.fail)(`No active session found for "${player}".`);
64
+ }
65
+ // Check target is on same host (cross-machine worktrees not supported)
66
+ const targetMeta = await targetHandle.query('getMetadata');
67
+ const { hostname } = await Promise.resolve().then(() => __importStar(require('os'))).then((os) => ({ hostname: os.hostname() }));
68
+ if (targetMeta.hostname && targetMeta.hostname !== hostname) {
69
+ return (0, descriptor_1.fail)(`Cannot create worktree for "${player}" — they are on host "${targetMeta.hostname}" but worktrees must be created locally.`);
70
+ }
71
+ const gitRoot = process.cwd();
72
+ const result = (0, worktree_1.createWorktree)({
73
+ gitRoot,
74
+ ensemble: config.ensemble,
75
+ playerName: player,
76
+ branch,
77
+ });
78
+ if (result.created) {
79
+ (0, worktree_1.installDependencies)(result.path);
80
+ }
81
+ // Record in conductor's worktree state
82
+ const entry = {
83
+ player,
84
+ path: result.path,
85
+ branch: result.branch,
86
+ gitRoot,
87
+ createdAt: new Date().toISOString(),
88
+ createdBy: getPlayerId(),
89
+ };
90
+ await handle.signal('setWorktree', entry);
91
+ // Auto-cue the player with worktree info
92
+ const cueMessage = [
93
+ `\u{1f33f} **Worktree ready** for your task:`,
94
+ `- **Path**: \`${result.path}\``,
95
+ `- **Branch**: \`${result.branch}\``,
96
+ '',
97
+ `Run \`cd ${result.path}\` to switch to your isolated workspace.`,
98
+ `All your changes will be on branch \`${result.branch}\`.`,
99
+ `When done, commit and push \u2014 the conductor will handle cleanup.`,
100
+ ].join('\n');
154
101
  await handle.executeUpdate(signals_1.submitOutboxUpdate, {
155
102
  args: [{
156
103
  type: 'cue',
157
104
  targetPlayerId: player,
158
- message: `Worktree removed. You're back in the shared repository.`,
105
+ message: cueMessage,
159
106
  }],
160
107
  });
108
+ // #261: when we reused an existing worktree directory, surface the
109
+ // actual state transition (same branch / switched / created-from-main)
110
+ // so the conductor sees ground truth instead of the prior silent
111
+ // "reused existing" that implied the worktree was already on the
112
+ // requested branch.
113
+ const createdLabel = result.created
114
+ ? 'new'
115
+ : (() => {
116
+ switch (result.switched) {
117
+ case 'same':
118
+ return `reused existing (already on \`${result.branch}\`)`;
119
+ case 'switched':
120
+ return `reused existing (switched to \`${result.branch}\`)`;
121
+ case 'created-from-main':
122
+ return `reused existing (created \`${result.branch}\` from origin/main)`;
123
+ default:
124
+ return 'reused existing';
125
+ }
126
+ })();
127
+ return (0, descriptor_1.ok)(`Worktree created for **${player}**:\n- Path: \`${result.path}\`\n- Branch: \`${result.branch}\`\n- Created: ${createdLabel}\n\nPlayer has been notified.`);
161
128
  }
162
- catch {
163
- // Player may no longer be active — non-fatal
129
+ case 'remove': {
130
+ if (!player) {
131
+ return (0, descriptor_1.fail)('`player` is required for remove action.');
132
+ }
133
+ // Look up worktree entry from conductor state
134
+ const entries = await handle.query('worktrees');
135
+ const entry = entries.find((w) => w.player === player);
136
+ if (!entry) {
137
+ return (0, descriptor_1.fail)(`No worktree found for player "${player}".`);
138
+ }
139
+ // Remove from disk. #594: removeWorktree throws if the directory
140
+ // survives the removal (Windows file-lock half-removal). We must
141
+ // NOT signal `removeWorktree` state or cue the player until disk
142
+ // removal is confirmed — otherwise Temporal state records "no
143
+ // worktree" while a locked orphan directory remains on disk, and
144
+ // the next `create` fails with a confusing git fatal.
145
+ try {
146
+ (0, worktree_1.removeWorktree)(entry.path, entry.gitRoot);
147
+ }
148
+ catch (err) {
149
+ return (0, descriptor_1.fail)(`Worktree for **${player}** could not be removed: ${(0, descriptor_1.formatError)(err)}\n\n` +
150
+ `Conductor state is unchanged — the worktree is still tracked. ` +
151
+ `Have the player stop any long-running processes inside the worktree ` +
152
+ `(dev servers, file watchers), then retry \`worktree remove\`.`);
153
+ }
154
+ // Remove from conductor state (only reached on confirmed disk removal)
155
+ await handle.signal('removeWorktree', player);
156
+ // Auto-cue the player
157
+ try {
158
+ await handle.executeUpdate(signals_1.submitOutboxUpdate, {
159
+ args: [{
160
+ type: 'cue',
161
+ targetPlayerId: player,
162
+ message: `Worktree removed. You're back in the shared repository.`,
163
+ }],
164
+ });
165
+ }
166
+ catch {
167
+ // Player may no longer be active — non-fatal
168
+ }
169
+ return (0, descriptor_1.ok)(`Worktree for **${player}** removed (branch: \`${entry.branch}\`).`);
164
170
  }
165
- return (0, helpers_1.ok)(`Worktree for **${player}** removed (branch: \`${entry.branch}\`).`);
166
- }
167
- case 'list': {
168
- const entries = await handle.query('worktrees');
169
- if (entries.length === 0) {
170
- return (0, helpers_1.ok)('No active worktrees.');
171
+ case 'list': {
172
+ const entries = await handle.query('worktrees');
173
+ if (entries.length === 0) {
174
+ return (0, descriptor_1.ok)('No active worktrees.');
175
+ }
176
+ const lines = entries.map((w) => `- **${w.player}**: \`${w.path}\` (branch: \`${w.branch}\`, created: ${w.createdAt} by ${w.createdBy})`);
177
+ return (0, descriptor_1.ok)(`${entries.length} active worktree${entries.length === 1 ? '' : 's'}:\n${lines.join('\n')}`);
171
178
  }
172
- const lines = entries.map((w) => `- **${w.player}**: \`${w.path}\` (branch: \`${w.branch}\`, created: ${w.createdAt} by ${w.createdBy})`);
173
- return (0, helpers_1.ok)(`${entries.length} active worktree${entries.length === 1 ? '' : 's'}:\n${lines.join('\n')}`);
174
179
  }
175
180
  }
176
- }
177
- catch (err) {
178
- return (0, helpers_1.fail)(`Worktree operation failed: ${(0, helpers_1.formatError)(err)}`);
179
- }
180
- });
181
+ catch (err) {
182
+ return (0, descriptor_1.fail)(`Worktree operation failed: ${(0, descriptor_1.formatError)(err)}`);
183
+ }
184
+ },
185
+ };
181
186
  }
package/dist/tui/index.js CHANGED
@@ -75,12 +75,12 @@ async function run(opts) {
75
75
  const app = ink.render(
76
76
  // The TUI recruit wizard only offers `claude` / `copilot` — `mock` is
77
77
  // a dev-mode CLI-only path (ADR 0014 §7 gate 3); `claude-api` (#131),
78
- // `opencode` (#449), and `claude-code-headless` (#520) are CLI/MCP-only
79
- // paths. If the user's resolved default is one of those, fall back to
80
- // `claude` for the TUI default; they can still recruit those agents
81
- // via the CLI (e.g. `agent-tempo recruit ... --agent opencode`) or
82
- // the MCP `recruit` tool.
83
- react_1.default.createElement(ink_context_1.InkProvider, { ink, children: react_1.default.createElement(App_1.App, { api, ensemble: opts.ensemble, defaultAgent: (opts.config.defaultAgent === 'mock' || opts.config.defaultAgent === 'claude-api' || opts.config.defaultAgent === 'opencode' || opts.config.defaultAgent === 'claude-code-headless') ? 'claude' : opts.config.defaultAgent }) }));
78
+ // `opencode` (#449), `claude-code-headless` (#520), and `pi` (Phase 3a
79
+ // headless) are CLI/MCP-only paths. If the user's resolved default is one
80
+ // of those, fall back to `claude` for the TUI default; they can still
81
+ // recruit those agents via the CLI (e.g. `agent-tempo recruit ... --agent
82
+ // pi`) or the MCP `recruit` tool.
83
+ react_1.default.createElement(ink_context_1.InkProvider, { ink, children: react_1.default.createElement(App_1.App, { api, ensemble: opts.ensemble, defaultAgent: (opts.config.defaultAgent === 'mock' || opts.config.defaultAgent === 'claude-api' || opts.config.defaultAgent === 'opencode' || opts.config.defaultAgent === 'claude-code-headless' || opts.config.defaultAgent === 'pi') ? 'claude' : opts.config.defaultAgent }) }));
84
84
  await app.waitUntilExit();
85
85
  }
86
86
  finally {
package/dist/types.d.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * editing one line — see #476 (the `claude-api` allowlist drift bug
16
16
  * that motivated centralising this).
17
17
  */
18
- export declare const AGENT_TYPES: readonly ["claude", "copilot", "mock", "claude-api", "opencode", "claude-code-headless"];
18
+ export declare const AGENT_TYPES: readonly ["claude", "copilot", "mock", "claude-api", "opencode", "claude-code-headless", "pi"];
19
19
  export type AgentType = typeof AGENT_TYPES[number];
20
20
  /**
21
21
  * Mock-adapter mode (ADR 0014 §4.2). Single source of truth shared by the
@@ -349,6 +349,8 @@ export interface SessionInput {
349
349
  reportHistory?: PlayerReport[];
350
350
  /** Restored from continue-as-new (pending/processing entries only) */
351
351
  outbox?: OutboxEntry[];
352
+ /** Restored from continue-as-new (D14) — an un-acked pending reset survives a CAN. */
353
+ pendingReset?: PendingReset | null;
352
354
  autoSummary?: string;
353
355
  /** Disable stale session detection (for passive mailbox workflows like maestro) */
354
356
  disableStaleDetection?: boolean;
@@ -564,6 +566,14 @@ export interface RecruitOutboxEntry extends OutboxEntryBase {
564
566
  * when `agent !== 'claude-code-headless'`.
565
567
  */
566
568
  dangerouslySkipPermissions?: boolean;
569
+ /**
570
+ * Phase 3a / MD-C — headless Pi tool-class policy. `'restricted'` (default;
571
+ * Bash/shell/exec hard-blocked) | `'standard'` (scoped Bash) | `'full'`
572
+ * (unsandboxed; admin/force-gated at recruit). Ignored when `agent !== 'pi'`.
573
+ * Inline literal — types.ts is the V8-sandbox-safe shared module; do NOT import
574
+ * the type from src/pi or src/adapters.
575
+ */
576
+ toolAccess?: 'restricted' | 'standard' | 'full';
567
577
  }
568
578
  export interface ReleaseOutboxEntry extends OutboxEntryBase {
569
579
  type: 'release';
@@ -692,7 +702,42 @@ export interface SpawnOutboxEntry extends OutboxEntryBase {
692
702
  */
693
703
  model?: string;
694
704
  }
695
- export type OutboxEntry = CueOutboxEntry | RecruitOutboxEntry | ReportOutboxEntry | StopOutboxEntry | ReleaseOutboxEntry | SpawnOutboxEntry | DetachOutboxEntry | DestroyOutboxEntry | RestartOutboxEntry;
705
+ /**
706
+ * Reset outbox entry (D14) — enqueued by the `reset` tool so the dispatch loop
707
+ * runs `deliverReset` on the target, which sets a `pendingReset` flag the Pi
708
+ * extension polls + acts on (CLEAN-WIPE → Pi `newSession()`). POLL-delivery
709
+ * (mirrors cue/pendingMessages), NOT a direct signal into the subprocess.
710
+ * Operator-initiated → does NOT route through the MD-G tool gate.
711
+ */
712
+ export interface ResetOutboxEntry extends OutboxEntryBase {
713
+ type: 'reset';
714
+ /** Player whose context is wiped. */
715
+ targetPlayerId: string;
716
+ /** Who requested the reset (owner or conductor). Recorded for audit. */
717
+ invokerPlayerId?: string;
718
+ /** Clean-wipe (D14 default `true` → `newSession`). `false` reserved for a softer reset. */
719
+ fresh?: boolean;
720
+ /** Optional human-readable reason, surfaced to the wiped session + audit. */
721
+ reason?: string;
722
+ }
723
+ export type OutboxEntry = CueOutboxEntry | RecruitOutboxEntry | ReportOutboxEntry | StopOutboxEntry | ReleaseOutboxEntry | SpawnOutboxEntry | DetachOutboxEntry | DestroyOutboxEntry | RestartOutboxEntry | ResetOutboxEntry;
724
+ /**
725
+ * Pending reset flag set on a session workflow by `deliverReset`, polled by the
726
+ * Pi extension via `pendingResetQuery` and cleared via `ackResetSignal(resetId)`
727
+ * after the extension performs the wipe. Single-slot, latest-wins.
728
+ */
729
+ export interface PendingReset {
730
+ /** Correlation id (the originating outbox entry id). Ack clears only on match. */
731
+ resetId: string;
732
+ /** Clean-wipe (`newSession`) when true (D14 default). */
733
+ fresh: boolean;
734
+ /** Optional reason. */
735
+ reason?: string;
736
+ /** Who requested it (audit). */
737
+ requestedBy?: string;
738
+ /** ISO timestamp, stamped by the workflow (`workflow.now()`) — deterministic. */
739
+ requestedAt: string;
740
+ }
696
741
  /** Distributive Omit that works correctly on union types. */
697
742
  type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
698
743
  /** Input type for submitting outbox entries — auto-fields (id, createdAt, status, error, deliveredAt) are added by the workflow. */
package/dist/types.js CHANGED
@@ -20,7 +20,7 @@ exports.ZERO_CHAT_HIGH_WATER = exports.MOCK_MODES = exports.AGENT_TYPES = void 0
20
20
  * editing one line — see #476 (the `claude-api` allowlist drift bug
21
21
  * that motivated centralising this).
22
22
  */
23
- exports.AGENT_TYPES = ['claude', 'copilot', 'mock', 'claude-api', 'opencode', 'claude-code-headless'];
23
+ exports.AGENT_TYPES = ['claude', 'copilot', 'mock', 'claude-api', 'opencode', 'claude-code-headless', 'pi'];
24
24
  /**
25
25
  * Mock-adapter mode (ADR 0014 §4.2). Single source of truth shared by the
26
26
  * adapter, the recruit tool's zod enum (`z.enum(MOCK_MODES)`), the spawn
@@ -46,6 +46,7 @@ exports.HEADLESS_ADAPTERS = new Set([
46
46
  'opencode',
47
47
  'claude-api',
48
48
  'mock',
49
+ 'pi',
49
50
  ]);
50
51
  /**
51
52
  * Compute the default `part` text seeded into a fresh session workflow.
@@ -1,3 +1,16 @@
1
+ /**
2
+ * Locate an installed package's `package.json` by walking `node_modules`
3
+ * directories upward from `fromDir`. The single source of truth for the
4
+ * filesystem walk — {@link probeSdkInstall} (presence) and
5
+ * {@link readSdkPackageVersion} (version) both build on it.
6
+ *
7
+ * @param pkgName Bare specifier (e.g. `'@opencode-ai/sdk'`).
8
+ * @param fromDir Where to start the walk. Defaults to the caller's
9
+ * `__dirname`-equivalent — pass an explicit value to anchor elsewhere.
10
+ * @returns The absolute path to `<dir>/node_modules/<pkgName>/package.json`
11
+ * for the first match up the filesystem, or `null` if none is found.
12
+ */
13
+ export declare function findSdkPackageJson(pkgName: string, fromDir?: string): string | null;
1
14
  /**
2
15
  * @param pkgName Bare specifier (e.g. `'@opencode-ai/sdk'`).
3
16
  * @param fromDir Where to start the walk. Defaults to the caller's
@@ -7,3 +20,13 @@
7
20
  * anywhere on the walk up the filesystem.
8
21
  */
9
22
  export declare function probeSdkInstall(pkgName: string, fromDir?: string): boolean;
23
+ /**
24
+ * Read an installed package's `package.json#version` via the same filesystem
25
+ * walk as {@link probeSdkInstall}. Returns `null` when the package isn't
26
+ * installed, its `package.json` is unreadable, or its `version` field is
27
+ * absent/non-string — callers treat `null` as "version unknown".
28
+ *
29
+ * @param pkgName Bare specifier (e.g. `'@earendil-works/pi-coding-agent'`).
30
+ * @param fromDir Walk start (defaults to this module's `__dirname`).
31
+ */
32
+ export declare function readSdkPackageVersion(pkgName: string, fromDir?: string): string | null;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findSdkPackageJson = findSdkPackageJson;
3
4
  exports.probeSdkInstall = probeSdkInstall;
5
+ exports.readSdkPackageVersion = readSdkPackageVersion;
4
6
  /**
5
7
  * Filesystem-walk probe for an installed npm package.
6
8
  *
@@ -20,26 +22,63 @@ exports.probeSdkInstall = probeSdkInstall;
20
22
  * Used by:
21
23
  * - `src/adapters/opencode/adapter.ts` — module-load optional-dep gate
22
24
  * - `src/tools/recruit.ts` — recruit pre-flight check
25
+ * - `src/pi/probe.ts` — Pi / Copilot-via-Pi pre-flight (presence + version floor)
23
26
  */
24
27
  const fs_1 = require("fs");
25
28
  const path_1 = require("path");
26
29
  /**
30
+ * Locate an installed package's `package.json` by walking `node_modules`
31
+ * directories upward from `fromDir`. The single source of truth for the
32
+ * filesystem walk — {@link probeSdkInstall} (presence) and
33
+ * {@link readSdkPackageVersion} (version) both build on it.
34
+ *
27
35
  * @param pkgName Bare specifier (e.g. `'@opencode-ai/sdk'`).
28
36
  * @param fromDir Where to start the walk. Defaults to the caller's
29
- * `__dirname`-equivalent — pass an explicit value if you need to anchor
30
- * the search elsewhere.
31
- * @returns `true` if `<dir>/node_modules/<pkgName>/package.json` exists
32
- * anywhere on the walk up the filesystem.
37
+ * `__dirname`-equivalent — pass an explicit value to anchor elsewhere.
38
+ * @returns The absolute path to `<dir>/node_modules/<pkgName>/package.json`
39
+ * for the first match up the filesystem, or `null` if none is found.
33
40
  */
34
- function probeSdkInstall(pkgName, fromDir = __dirname) {
41
+ function findSdkPackageJson(pkgName, fromDir = __dirname) {
35
42
  let dir = fromDir;
36
43
  while (true) {
37
44
  const candidate = (0, path_1.join)(dir, 'node_modules', pkgName, 'package.json');
38
45
  if ((0, fs_1.existsSync)(candidate))
39
- return true;
46
+ return candidate;
40
47
  const parent = (0, path_1.dirname)(dir);
41
48
  if (parent === dir)
42
- return false;
49
+ return null;
43
50
  dir = parent;
44
51
  }
45
52
  }
53
+ /**
54
+ * @param pkgName Bare specifier (e.g. `'@opencode-ai/sdk'`).
55
+ * @param fromDir Where to start the walk. Defaults to the caller's
56
+ * `__dirname`-equivalent — pass an explicit value if you need to anchor
57
+ * the search elsewhere.
58
+ * @returns `true` if `<dir>/node_modules/<pkgName>/package.json` exists
59
+ * anywhere on the walk up the filesystem.
60
+ */
61
+ function probeSdkInstall(pkgName, fromDir = __dirname) {
62
+ return findSdkPackageJson(pkgName, fromDir) !== null;
63
+ }
64
+ /**
65
+ * Read an installed package's `package.json#version` via the same filesystem
66
+ * walk as {@link probeSdkInstall}. Returns `null` when the package isn't
67
+ * installed, its `package.json` is unreadable, or its `version` field is
68
+ * absent/non-string — callers treat `null` as "version unknown".
69
+ *
70
+ * @param pkgName Bare specifier (e.g. `'@earendil-works/pi-coding-agent'`).
71
+ * @param fromDir Walk start (defaults to this module's `__dirname`).
72
+ */
73
+ function readSdkPackageVersion(pkgName, fromDir = __dirname) {
74
+ const pkgPath = findSdkPackageJson(pkgName, fromDir);
75
+ if (!pkgPath)
76
+ return null;
77
+ try {
78
+ const parsed = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf8'));
79
+ return typeof parsed.version === 'string' ? parsed.version : null;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
package/dist/worker.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Worker } from '@temporalio/worker';
2
2
  import { Config } from './config';
3
+ import type { IngestTokenRegistry } from './http/ingest-registry';
4
+ import type { GateRegistry } from './http/gate-registry';
3
5
  export interface DualWorkers {
4
6
  /** Shared queue worker: workflows + delivery activities + schedule activities */
5
7
  sharedWorker: Worker;
@@ -11,4 +13,4 @@ export interface DualWorkers {
11
13
  * - Shared queue: workflows + all delivery activities (deliverCue, deliverReport, terminateSession, startRecruitedSession) + schedule activities
12
14
  * - Per-host queue: spawnProcess only (routes recruit spawns to the correct machine)
13
15
  */
14
- export declare function createWorkers(config: Config): Promise<DualWorkers>;
16
+ export declare function createWorkers(config: Config, ingestTokens?: IngestTokenRegistry, gate?: GateRegistry): Promise<DualWorkers>;
package/dist/worker.js CHANGED
@@ -93,13 +93,17 @@ async function getWorkflowBundle() {
93
93
  * - Shared queue: workflows + all delivery activities (deliverCue, deliverReport, terminateSession, startRecruitedSession) + schedule activities
94
94
  * - Per-host queue: spawnProcess only (routes recruit spawns to the correct machine)
95
95
  */
96
- async function createWorkers(config) {
96
+ async function createWorkers(config, ingestTokens, gate) {
97
97
  const connection = await (0, connection_1.createTemporalNativeConnection)(config);
98
98
  // Create a Client connection for activities that need to interact with Temporal
99
99
  const clientConnection = await (0, connection_2.createTemporalConnection)(config);
100
100
  const client = new client_1.Client({ connection: clientConnection, namespace: config.temporalNamespace });
101
101
  const scheduleActivities = (0, schedule_fire_1.createScheduleActivities)(client);
102
- const outboxActivities = (0, outbox_1.createOutboxActivities)(client, config);
102
+ // 3c Tier-2 thread the daemon's shared IngestTokenRegistry into the outbox
103
+ // activities so the pi spawn branch can mint per-player ingest tokens and the
104
+ // destroy path can revoke them. Same singleton the HTTP server validates
105
+ // against (both run in this daemon process).
106
+ const outboxActivities = (0, outbox_1.createOutboxActivities)(client, config, ingestTokens, gate);
103
107
  const maestroActivities = (0, maestro_1.createMaestroActivities)(client);
104
108
  const workflowBundle = await getWorkflowBundle();
105
109
  const SHUTDOWN_GRACE_TIME = '10s';