agent-tempo 1.2.0 → 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.
- package/CLAUDE.md +253 -219
- package/LICENSE +21 -21
- package/README.md +293 -289
- package/assets/icon-dark.svg +9 -9
- package/assets/icon.svg +9 -9
- package/assets/logo-dark.svg +11 -11
- package/assets/logo-light.svg +11 -11
- package/dashboard/README.md +91 -91
- 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 +20 -20
- package/dashboard/package.json +47 -47
- 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/copilot/adapter.js +12 -1
- 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/cli/global-wrapper.d.ts +19 -0
- package/dist/cli/global-wrapper.js +169 -0
- package/dist/cli/help-text.js +97 -97
- package/dist/cli/startup.js +11 -0
- package/dist/cli/upgrade-command.js +81 -81
- package/dist/cli.js +12 -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 +37 -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 +224 -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 +53 -0
- package/dist/pi/mission-control/board.js +104 -0
- package/dist/pi/mission-control/extension.d.ts +44 -0
- package/dist/pi/mission-control/extension.js +251 -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 +95 -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 +208 -0
- package/dist/pi/pi-types.js +21 -0
- package/dist/pi/probe.d.ts +80 -0
- package/dist/pi/probe.js +154 -0
- package/dist/pi/render-tools.d.ts +17 -0
- package/dist/pi/render-tools.js +51 -0
- package/dist/pi/reset-pump.d.ts +47 -0
- package/dist/pi/reset-pump.js +85 -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/scripts/verify-daemon-isolation-guard.js +24 -24
- package/dist/server-tools.d.ts +2 -0
- package/dist/server-tools.js +50 -46
- package/dist/server.js +4 -0
- package/dist/spawn.d.ts +55 -0
- package/dist/spawn.js +84 -12
- 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 +30 -25
- package/dist/tools/coat-check-get.d.ts +2 -2
- package/dist/tools/coat-check-get.js +39 -34
- 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 +41 -36
- 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 +43 -38
- 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 +325 -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 +43 -38
- 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/grpc-shutdown-guard.d.ts +52 -0
- package/dist/utils/grpc-shutdown-guard.js +88 -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/examples/agents/tempo-composer.md +56 -56
- package/examples/agents/tempo-conductor.md +117 -117
- package/examples/agents/tempo-critic.md +73 -73
- package/examples/agents/tempo-improv.md +74 -74
- package/examples/agents/tempo-liner.md +75 -75
- package/examples/agents/tempo-roadie.md +61 -61
- package/examples/agents/tempo-soloist.md +71 -71
- package/examples/agents/tempo-tuner.md +94 -94
- package/examples/ensembles/tempo-big-band.yaml +146 -146
- package/examples/ensembles/tempo-dev-team.yaml +58 -58
- package/examples/ensembles/tempo-headless-jam.yaml +77 -77
- package/examples/ensembles/tempo-jam-session.yaml +41 -41
- package/examples/ensembles/tempo-mock-jam.yaml +79 -79
- package/examples/ensembles/tempo-review-squad.yaml +32 -32
- package/package.json +176 -173
- package/packaging/launchd/com.agent.tempo.plist +46 -46
- package/packaging/systemd/agent-tempo.service +32 -32
- package/packaging/windows/install-task.ps1 +71 -71
- package/scenarios/conductor-recruit-mock.yaml +33 -33
- package/scenarios/echo-roundtrip.yaml +15 -15
- package/scenarios/multi-player-handoff.yaml +38 -38
- package/scenarios/recruit-cascade.yaml +38 -38
- package/scenarios/two-player-conversation.yaml +33 -33
- package/workflow-bundle.js +97 -6
- package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
- package/dist/activities/claude-stop.d.ts +0 -21
- package/dist/activities/claude-stop.js +0 -94
- package/dist/channel.d.ts +0 -3
- package/dist/channel.js +0 -48
- package/dist/copilot-bridge.d.ts +0 -22
- package/dist/copilot-bridge.js +0 -565
- package/dist/scripts/258-spotcheck.js +0 -303
- package/dist/tools/detach.d.ts +0 -4
- package/dist/tools/detach.js +0 -45
- package/dist/tools/encore.d.ts +0 -4
- package/dist/tools/encore.js +0 -31
- package/dist/tools/helpers.d.ts +0 -21
- package/dist/tools/helpers.js +0 -25
- package/dist/tools/pause-ensemble.d.ts +0 -4
- package/dist/tools/pause-ensemble.js +0 -58
- package/dist/tools/resume-ensemble.d.ts +0 -4
- package/dist/tools/resume-ensemble.js +0 -79
- package/dist/tools/stop.d.ts +0 -4
- package/dist/tools/stop.js +0 -29
- package/dist/tui/client.d.ts +0 -6
- package/dist/tui/client.js +0 -9
- package/dist/tui/components/ActivityLog.d.ts +0 -16
- package/dist/tui/components/ActivityLog.js +0 -36
- package/dist/tui/components/CommandOverlay.d.ts +0 -15
- package/dist/tui/components/CommandOverlay.js +0 -34
- package/dist/tui/components/ConductorChat.d.ts +0 -16
- package/dist/tui/components/ConductorChat.js +0 -32
- package/dist/tui/components/EnsembleListView.d.ts +0 -14
- package/dist/tui/components/EnsembleListView.js +0 -32
- package/dist/tui/components/EnsemblePanel.d.ts +0 -12
- package/dist/tui/components/EnsemblePanel.js +0 -40
- package/dist/tui/components/InputBar.d.ts +0 -13
- package/dist/tui/components/InputBar.js +0 -58
- package/dist/tui/components/ScheduleOverlay.d.ts +0 -13
- package/dist/tui/components/ScheduleOverlay.js +0 -113
- package/dist/tui/components/TopBar.d.ts +0 -12
- package/dist/tui/components/TopBar.js +0 -15
- package/dist/tui/core-api.d.ts +0 -26
- package/dist/tui/core-api.js +0 -67
- package/dist/tui/hooks/useEnsembleDiscovery.d.ts +0 -3
- package/dist/tui/hooks/useEnsembleDiscovery.js +0 -30
- package/dist/tui/hooks/useMaestroPoller.d.ts +0 -3
- package/dist/tui/hooks/useMaestroPoller.js +0 -36
- package/dist/tui/hooks/useSendCommand.d.ts +0 -7
- package/dist/tui/hooks/useSendCommand.js +0 -29
- package/dist/utils/bg-preflight.d.ts +0 -25
- package/dist/utils/bg-preflight.js +0 -154
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
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-level guard for the Temporal/grpc-js "Channel has been shut down"
|
|
3
|
+
* shutdown race.
|
|
4
|
+
*
|
|
5
|
+
* `@temporalio/client`'s gRPC retry interceptor (`grpc-retry.js`) schedules
|
|
6
|
+
* call retries with `setTimeout`. `Connection.close()` shuts down the
|
|
7
|
+
* underlying grpc-js channel but does NOT clear those pending timers. When a
|
|
8
|
+
* retry timer fires *after* the channel is closed, `InternalChannel.createCall`
|
|
9
|
+
* throws `Error: Channel has been shut down` **synchronously inside the timer
|
|
10
|
+
* callback** — off any awaited path, so no surrounding `try/catch` (including
|
|
11
|
+
* the one around `connection.close()`) can catch it. It escapes to
|
|
12
|
+
* `uncaughtException` and kills the process.
|
|
13
|
+
*
|
|
14
|
+
* Observed stack (the crash this guards against):
|
|
15
|
+
* InternalChannel.createCall (@grpc/grpc-js/.../internal-channel.js)
|
|
16
|
+
* ...
|
|
17
|
+
* Timeout.retry [as _onTimeout] (@temporalio/client/lib/grpc-retry.js)
|
|
18
|
+
*
|
|
19
|
+
* This artifact is always benign: it only occurs for a connection we have
|
|
20
|
+
* already finished with (its query result was captured, or we degraded), as
|
|
21
|
+
* the channel tears down. Swallowing exactly this error — and nothing else —
|
|
22
|
+
* removes the crash without masking real failures. Any other uncaught
|
|
23
|
+
* exception is re-thrown, which Node treats as fatal (print + non-zero exit),
|
|
24
|
+
* preserving normal crash semantics.
|
|
25
|
+
*
|
|
26
|
+
* Install once at CLI entry. Idempotent.
|
|
27
|
+
*
|
|
28
|
+
* Coupling notes:
|
|
29
|
+
* - The match string is grpc-js's exact `close()` error
|
|
30
|
+
* (`@grpc/grpc-js/build/src/internal-channel.js`, the `SHUTDOWN`-state throw).
|
|
31
|
+
* That state is reachable ONLY via an explicit `close()` — a live connection
|
|
32
|
+
* hitting a transient error reports `TRANSIENT_FAILURE`, never this message —
|
|
33
|
+
* so swallowing it cannot mask a failure we'd want to surface. If grpc-js ever
|
|
34
|
+
* renames the message this guard silently becomes a no-op (crash resurfaces);
|
|
35
|
+
* update `CHANNEL_SHUTDOWN_MESSAGE` to match.
|
|
36
|
+
* - This handler is registered first (it's installed at process entry). When it
|
|
37
|
+
* re-throws a non-benign error, Node exits immediately and any LATER
|
|
38
|
+
* `uncaughtException` listener is bypassed. The codebase currently registers
|
|
39
|
+
* no other `uncaughtException` listeners, so this is benign today — revisit if
|
|
40
|
+
* a crash reporter is ever added.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Register the guard on `process`. Safe to call multiple times — only the
|
|
44
|
+
* first call attaches a listener.
|
|
45
|
+
*/
|
|
46
|
+
export declare function installGrpcShutdownGuard(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Test-only escape hatch — removes the listener and resets the install latch so
|
|
49
|
+
* a fresh `installGrpcShutdownGuard()` can be exercised. Never call from
|
|
50
|
+
* production code. See docs/adr/0006-test-hooks-naming.md.
|
|
51
|
+
*/
|
|
52
|
+
export declare function __resetGrpcShutdownGuardForTests(): void;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Process-level guard for the Temporal/grpc-js "Channel has been shut down"
|
|
4
|
+
* shutdown race.
|
|
5
|
+
*
|
|
6
|
+
* `@temporalio/client`'s gRPC retry interceptor (`grpc-retry.js`) schedules
|
|
7
|
+
* call retries with `setTimeout`. `Connection.close()` shuts down the
|
|
8
|
+
* underlying grpc-js channel but does NOT clear those pending timers. When a
|
|
9
|
+
* retry timer fires *after* the channel is closed, `InternalChannel.createCall`
|
|
10
|
+
* throws `Error: Channel has been shut down` **synchronously inside the timer
|
|
11
|
+
* callback** — off any awaited path, so no surrounding `try/catch` (including
|
|
12
|
+
* the one around `connection.close()`) can catch it. It escapes to
|
|
13
|
+
* `uncaughtException` and kills the process.
|
|
14
|
+
*
|
|
15
|
+
* Observed stack (the crash this guards against):
|
|
16
|
+
* InternalChannel.createCall (@grpc/grpc-js/.../internal-channel.js)
|
|
17
|
+
* ...
|
|
18
|
+
* Timeout.retry [as _onTimeout] (@temporalio/client/lib/grpc-retry.js)
|
|
19
|
+
*
|
|
20
|
+
* This artifact is always benign: it only occurs for a connection we have
|
|
21
|
+
* already finished with (its query result was captured, or we degraded), as
|
|
22
|
+
* the channel tears down. Swallowing exactly this error — and nothing else —
|
|
23
|
+
* removes the crash without masking real failures. Any other uncaught
|
|
24
|
+
* exception is re-thrown, which Node treats as fatal (print + non-zero exit),
|
|
25
|
+
* preserving normal crash semantics.
|
|
26
|
+
*
|
|
27
|
+
* Install once at CLI entry. Idempotent.
|
|
28
|
+
*
|
|
29
|
+
* Coupling notes:
|
|
30
|
+
* - The match string is grpc-js's exact `close()` error
|
|
31
|
+
* (`@grpc/grpc-js/build/src/internal-channel.js`, the `SHUTDOWN`-state throw).
|
|
32
|
+
* That state is reachable ONLY via an explicit `close()` — a live connection
|
|
33
|
+
* hitting a transient error reports `TRANSIENT_FAILURE`, never this message —
|
|
34
|
+
* so swallowing it cannot mask a failure we'd want to surface. If grpc-js ever
|
|
35
|
+
* renames the message this guard silently becomes a no-op (crash resurfaces);
|
|
36
|
+
* update `CHANNEL_SHUTDOWN_MESSAGE` to match.
|
|
37
|
+
* - This handler is registered first (it's installed at process entry). When it
|
|
38
|
+
* re-throws a non-benign error, Node exits immediately and any LATER
|
|
39
|
+
* `uncaughtException` listener is bypassed. The codebase currently registers
|
|
40
|
+
* no other `uncaughtException` listeners, so this is benign today — revisit if
|
|
41
|
+
* a crash reporter is ever added.
|
|
42
|
+
*/
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.installGrpcShutdownGuard = installGrpcShutdownGuard;
|
|
45
|
+
exports.__resetGrpcShutdownGuardForTests = __resetGrpcShutdownGuardForTests;
|
|
46
|
+
const CHANNEL_SHUTDOWN_MESSAGE = 'Channel has been shut down';
|
|
47
|
+
let installed = false;
|
|
48
|
+
function isBenignChannelShutdown(err) {
|
|
49
|
+
return (err instanceof Error &&
|
|
50
|
+
typeof err.message === 'string' &&
|
|
51
|
+
err.message.includes(CHANNEL_SHUTDOWN_MESSAGE));
|
|
52
|
+
}
|
|
53
|
+
const handler = (err) => {
|
|
54
|
+
if (isBenignChannelShutdown(err)) {
|
|
55
|
+
// A Temporal gRPC retry timer fired after we closed the connection. The
|
|
56
|
+
// result we cared about was already captured (or degraded). Drop it.
|
|
57
|
+
if (process.env.CLAUDE_TEMPO_DEBUG) {
|
|
58
|
+
// eslint-disable-next-line no-console
|
|
59
|
+
console.error('[agent-tempo] ignored post-shutdown gRPC channel error (benign Temporal retry-after-close race)');
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Not ours — restore default crash behavior. Throwing from inside an
|
|
64
|
+
// 'uncaughtException' handler causes Node to print the error and exit with a
|
|
65
|
+
// non-zero code (exit code 7, "Uncaught Exception Handler Error", on current
|
|
66
|
+
// Node — not 1) without re-entering this handler, preserving the original
|
|
67
|
+
// failure's visibility and crash semantics.
|
|
68
|
+
throw err;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Register the guard on `process`. Safe to call multiple times — only the
|
|
72
|
+
* first call attaches a listener.
|
|
73
|
+
*/
|
|
74
|
+
function installGrpcShutdownGuard() {
|
|
75
|
+
if (installed)
|
|
76
|
+
return;
|
|
77
|
+
installed = true;
|
|
78
|
+
process.on('uncaughtException', handler);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Test-only escape hatch — removes the listener and resets the install latch so
|
|
82
|
+
* a fresh `installGrpcShutdownGuard()` can be exercised. Never call from
|
|
83
|
+
* production code. See docs/adr/0006-test-hooks-naming.md.
|
|
84
|
+
*/
|
|
85
|
+
function __resetGrpcShutdownGuardForTests() {
|
|
86
|
+
process.off('uncaughtException', handler);
|
|
87
|
+
installed = false;
|
|
88
|
+
}
|
|
@@ -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;
|