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.
- package/CLAUDE.md +39 -5
- package/README.md +6 -2
- package/dashboard/dist/assets/{index-D6Xyje_n.js → index-jmYe6rmS.js} +2 -2
- package/dashboard/dist/assets/index-jmYe6rmS.js.map +1 -0
- package/dashboard/dist/index.html +1 -1
- package/dashboard/package.json +1 -1
- package/dist/activities/outbox.d.ts +30 -1
- package/dist/activities/outbox.js +96 -3
- package/dist/adapters/base.js +5 -0
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +7 -0
- package/dist/adapters/pi/adapter.d.ts +2 -0
- package/dist/adapters/pi/adapter.js +43 -0
- package/dist/adapters/pi/index.d.ts +16 -0
- package/dist/adapters/pi/index.js +10 -0
- package/dist/client/core.js +9 -2
- package/dist/client/interface.d.ts +6 -0
- package/dist/config.d.ts +79 -0
- package/dist/config.js +74 -0
- package/dist/daemon.js +32 -1
- package/dist/http/aggregate.d.ts +22 -1
- package/dist/http/aggregate.js +41 -0
- package/dist/http/auth.d.ts +94 -8
- package/dist/http/auth.js +93 -9
- package/dist/http/body.d.ts +4 -1
- package/dist/http/body.js +6 -3
- package/dist/http/event-bus.js +1 -0
- package/dist/http/event-types.d.ts +34 -2
- package/dist/http/event-types.js +1 -0
- package/dist/http/gate-audit.d.ts +12 -0
- package/dist/http/gate-audit.js +95 -0
- package/dist/http/gate-registry.d.ts +167 -0
- package/dist/http/gate-registry.js +163 -0
- package/dist/http/gate-routes.d.ts +48 -0
- package/dist/http/gate-routes.js +102 -0
- package/dist/http/ingest-registry.d.ts +30 -0
- package/dist/http/ingest-registry.js +108 -0
- package/dist/http/inner-loop-routes.d.ts +66 -0
- package/dist/http/inner-loop-routes.js +182 -0
- package/dist/http/inner-loop.d.ts +92 -0
- package/dist/http/inner-loop.js +155 -0
- package/dist/http/server.d.ts +38 -3
- package/dist/http/server.js +211 -6
- package/dist/http/snapshot.d.ts +6 -0
- package/dist/http/snapshot.js +6 -0
- package/dist/pi/cue-pump.d.ts +61 -0
- package/dist/pi/cue-pump.js +95 -0
- package/dist/pi/extension.d.ts +45 -0
- package/dist/pi/extension.js +407 -0
- package/dist/pi/gate-client.d.ts +54 -0
- package/dist/pi/gate-client.js +136 -0
- package/dist/pi/headless.d.ts +85 -0
- package/dist/pi/headless.js +250 -0
- package/dist/pi/index.d.ts +28 -0
- package/dist/pi/index.js +43 -0
- package/dist/pi/inner-loop-client.d.ts +67 -0
- package/dist/pi/inner-loop-client.js +164 -0
- package/dist/pi/inner-loop-publisher.d.ts +187 -0
- package/dist/pi/inner-loop-publisher.js +236 -0
- package/dist/pi/lazy-proxy.d.ts +37 -0
- package/dist/pi/lazy-proxy.js +55 -0
- package/dist/pi/mission-control/actions.d.ts +48 -0
- package/dist/pi/mission-control/actions.js +98 -0
- package/dist/pi/mission-control/board.d.ts +88 -0
- package/dist/pi/mission-control/board.js +141 -0
- package/dist/pi/mission-control/extension.d.ts +51 -0
- package/dist/pi/mission-control/extension.js +330 -0
- package/dist/pi/mission-control/index.d.ts +15 -0
- package/dist/pi/mission-control/index.js +32 -0
- package/dist/pi/mission-control/inner-tail.d.ts +48 -0
- package/dist/pi/mission-control/inner-tail.js +76 -0
- package/dist/pi/mission-control/pi-ui.d.ts +43 -0
- package/dist/pi/mission-control/pi-ui.js +10 -0
- package/dist/pi/mission-control/render.d.ts +6 -0
- package/dist/pi/mission-control/render.js +98 -0
- package/dist/pi/phase-driver.d.ts +74 -0
- package/dist/pi/phase-driver.js +122 -0
- package/dist/pi/pi-types.d.ts +222 -0
- package/dist/pi/pi-types.js +21 -0
- package/dist/pi/probe.d.ts +99 -0
- package/dist/pi/probe.js +179 -0
- package/dist/pi/render-tools.d.ts +17 -0
- package/dist/pi/render-tools.js +56 -0
- package/dist/pi/reset-pump.d.ts +47 -0
- package/dist/pi/reset-pump.js +85 -0
- package/dist/pi/session-seed.d.ts +74 -0
- package/dist/pi/session-seed.js +103 -0
- package/dist/pi/tool-capability.d.ts +60 -0
- package/dist/pi/tool-capability.js +156 -0
- package/dist/pi/workflow-client.d.ts +158 -0
- package/dist/pi/workflow-client.js +289 -0
- package/dist/pi/zod-to-typebox.d.ts +74 -0
- package/dist/pi/zod-to-typebox.js +191 -0
- package/dist/server-tools.d.ts +2 -0
- package/dist/server-tools.js +50 -46
- package/dist/spawn.d.ts +55 -0
- package/dist/spawn.js +72 -0
- package/dist/tools/agent-types.d.ts +2 -2
- package/dist/tools/agent-types.js +22 -17
- package/dist/tools/attachment-info.d.ts +2 -2
- package/dist/tools/attachment-info.js +38 -33
- package/dist/tools/broadcast.d.ts +2 -2
- package/dist/tools/broadcast.js +69 -64
- package/dist/tools/cancel-stage.d.ts +2 -2
- package/dist/tools/cancel-stage.js +20 -15
- package/dist/tools/clear-state.d.ts +2 -2
- package/dist/tools/clear-state.js +25 -20
- package/dist/tools/coat-check-evict.d.ts +2 -2
- package/dist/tools/coat-check-evict.js +29 -24
- package/dist/tools/coat-check-get.d.ts +2 -2
- package/dist/tools/coat-check-get.js +38 -33
- package/dist/tools/coat-check-list.d.ts +2 -2
- package/dist/tools/coat-check-list.js +48 -43
- package/dist/tools/coat-check-put.d.ts +2 -2
- package/dist/tools/coat-check-put.js +38 -33
- package/dist/tools/cue.d.ts +2 -2
- package/dist/tools/cue.js +57 -52
- package/dist/tools/descriptor.d.ts +72 -0
- package/dist/tools/descriptor.js +39 -0
- package/dist/tools/destroy.d.ts +2 -2
- package/dist/tools/destroy.js +153 -148
- package/dist/tools/ensemble.d.ts +2 -2
- package/dist/tools/ensemble.js +71 -66
- package/dist/tools/evaluate-gate.d.ts +2 -2
- package/dist/tools/evaluate-gate.js +33 -27
- package/dist/tools/fetch-state.d.ts +2 -2
- package/dist/tools/fetch-state.js +42 -37
- package/dist/tools/gates.d.ts +2 -2
- package/dist/tools/gates.js +39 -34
- package/dist/tools/hosts.d.ts +2 -2
- package/dist/tools/hosts.js +25 -20
- package/dist/tools/listen.d.ts +2 -2
- package/dist/tools/listen.js +23 -18
- package/dist/tools/load-lineup.d.ts +2 -2
- package/dist/tools/load-lineup.js +324 -319
- package/dist/tools/migrate.d.ts +2 -2
- package/dist/tools/migrate.js +45 -40
- package/dist/tools/pause.d.ts +2 -2
- package/dist/tools/pause.js +34 -29
- package/dist/tools/play.d.ts +2 -2
- package/dist/tools/play.js +53 -48
- package/dist/tools/quality-gate.d.ts +2 -2
- package/dist/tools/quality-gate.js +26 -21
- package/dist/tools/recall.d.ts +2 -2
- package/dist/tools/recall.js +32 -27
- package/dist/tools/recruit.d.ts +2 -2
- package/dist/tools/recruit.js +340 -256
- package/dist/tools/release.d.ts +2 -2
- package/dist/tools/release.js +85 -80
- package/dist/tools/report.d.ts +2 -2
- package/dist/tools/report.js +28 -23
- package/dist/tools/reset.d.ts +3 -0
- package/dist/tools/reset.js +51 -0
- package/dist/tools/restart.d.ts +2 -2
- package/dist/tools/restart.js +51 -46
- package/dist/tools/restore.d.ts +2 -2
- package/dist/tools/restore.js +76 -71
- package/dist/tools/save-lineup.d.ts +2 -2
- package/dist/tools/save-lineup.js +32 -27
- package/dist/tools/save-state.d.ts +2 -2
- package/dist/tools/save-state.js +31 -26
- package/dist/tools/schedule.d.ts +2 -2
- package/dist/tools/schedule.js +133 -128
- package/dist/tools/schedules.d.ts +2 -2
- package/dist/tools/schedules.js +41 -36
- package/dist/tools/set-ensemble-description.d.ts +2 -2
- package/dist/tools/set-ensemble-description.js +26 -21
- package/dist/tools/set-name.d.ts +2 -2
- package/dist/tools/set-name.js +38 -33
- package/dist/tools/set-part.d.ts +2 -2
- package/dist/tools/set-part.js +20 -15
- package/dist/tools/shutdown.d.ts +2 -2
- package/dist/tools/shutdown.js +39 -34
- package/dist/tools/stage.d.ts +2 -2
- package/dist/tools/stage.js +28 -23
- package/dist/tools/stages.d.ts +2 -2
- package/dist/tools/stages.js +36 -31
- package/dist/tools/unschedule.d.ts +2 -2
- package/dist/tools/unschedule.js +30 -25
- package/dist/tools/who-am-i.d.ts +2 -2
- package/dist/tools/who-am-i.js +36 -31
- package/dist/tools/worktree.d.ts +2 -2
- package/dist/tools/worktree.js +134 -129
- package/dist/tui/index.js +6 -6
- package/dist/types.d.ts +47 -2
- package/dist/types.js +1 -1
- package/dist/utils/default-part.js +1 -0
- package/dist/utils/sdk-probe.d.ts +23 -0
- package/dist/utils/sdk-probe.js +46 -7
- package/dist/worker.d.ts +3 -1
- package/dist/worker.js +6 -2
- package/dist/workflows/session.js +70 -2
- package/dist/workflows/signals.d.ts +32 -2
- package/dist/workflows/signals.js +25 -2
- package/package.json +4 -1
- package/workflow-bundle.js +97 -6
- package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
- package/dist/tools/helpers.d.ts +0 -21
- package/dist/tools/helpers.js +0 -25
package/dist/tools/worktree.js
CHANGED
|
@@ -33,149 +33,154 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
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
|
|
40
|
+
const descriptor_1 = require("./descriptor");
|
|
41
41
|
const worktree_1 = require("../utils/worktree");
|
|
42
42
|
const validation_1 = require("../utils/validation");
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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:
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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),
|
|
79
|
-
// paths. If the user's resolved default is one
|
|
80
|
-
// `claude` for the TUI default; they can still
|
|
81
|
-
// via the CLI (e.g. `agent-tempo recruit ... --agent
|
|
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
|
-
|
|
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
|
|
@@ -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;
|
package/dist/utils/sdk-probe.js
CHANGED
|
@@ -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
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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
|
|
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
|
|
46
|
+
return candidate;
|
|
40
47
|
const parent = (0, path_1.dirname)(dir);
|
|
41
48
|
if (parent === dir)
|
|
42
|
-
return
|
|
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
|
-
|
|
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';
|