@tt-a1i/hive 2.0.1 → 2.1.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/CHANGELOG.md +42 -0
- package/README.en.md +15 -6
- package/README.md +26 -4
- package/dist/src/cli/hive.d.ts +4 -0
- package/dist/src/cli/hive.js +25 -3
- package/dist/src/cli/team.d.ts +8 -1
- package/dist/src/cli/team.js +111 -11
- package/dist/src/server/action-center-summary.d.ts +193 -0
- package/dist/src/server/action-center-summary.js +188 -0
- package/dist/src/server/agent-command-resolver.d.ts +6 -0
- package/dist/src/server/agent-command-resolver.js +16 -0
- package/dist/src/server/agent-manager.js +11 -1
- package/dist/src/server/agent-run-starter.js +47 -6
- package/dist/src/server/agent-runtime-types.d.ts +4 -0
- package/dist/src/server/agent-startup-instructions.d.ts +4 -0
- package/dist/src/server/agent-startup-instructions.js +35 -9
- package/dist/src/server/agent-stdin-dispatcher.js +17 -9
- package/dist/src/server/diagnostics-support-bundle.d.ts +288 -0
- package/dist/src/server/diagnostics-support-bundle.js +179 -0
- package/dist/src/server/dispatch-ledger-store.d.ts +4 -1
- package/dist/src/server/dispatch-ledger-store.js +46 -6
- package/dist/src/server/hive-envelope-escape.d.ts +2 -0
- package/dist/src/server/hive-envelope-escape.js +2 -0
- package/dist/src/server/hive-team-guidance.d.ts +1 -1
- package/dist/src/server/hive-team-guidance.js +67 -25
- package/dist/src/server/message-log-store.d.ts +1 -1
- package/dist/src/server/post-start-input-writer.js +8 -2
- package/dist/src/server/preset-launch-support.d.ts +2 -0
- package/dist/src/server/preset-launch-support.js +65 -2
- package/dist/src/server/protocol-event-stats.d.ts +39 -0
- package/dist/src/server/protocol-event-stats.js +84 -0
- package/dist/src/server/recovery-summary.js +19 -14
- package/dist/src/server/role-template-store.d.ts +1 -1
- package/dist/src/server/role-templates.d.ts +1 -0
- package/dist/src/server/role-templates.js +43 -29
- package/dist/src/server/routes-action-center.d.ts +2 -0
- package/dist/src/server/routes-action-center.js +37 -0
- package/dist/src/server/routes-diagnostics.d.ts +2 -0
- package/dist/src/server/routes-diagnostics.js +17 -0
- package/dist/src/server/routes-scenarios.d.ts +25 -0
- package/dist/src/server/routes-scenarios.js +89 -0
- package/dist/src/server/routes-settings.js +2 -11
- package/dist/src/server/routes-team-memory.js +52 -0
- package/dist/src/server/routes-team.js +40 -20
- package/dist/src/server/routes-workspace-memory-dreams.js +8 -0
- package/dist/src/server/routes-workspace-uploads.d.ts +2 -0
- package/dist/src/server/routes-workspace-uploads.js +154 -0
- package/dist/src/server/routes-workspaces.js +29 -3
- package/dist/src/server/routes.js +8 -0
- package/dist/src/server/runtime-message-builders.d.ts +0 -1
- package/dist/src/server/runtime-message-builders.js +0 -8
- package/dist/src/server/runtime-store-contract.d.ts +15 -0
- package/dist/src/server/runtime-store-dream.d.ts +14 -1
- package/dist/src/server/runtime-store-dream.js +49 -1
- package/dist/src/server/runtime-store-helpers.d.ts +7 -0
- package/dist/src/server/runtime-store-helpers.js +85 -22
- package/dist/src/server/runtime-store-worker-mutations.d.ts +11 -0
- package/dist/src/server/runtime-store-worker-mutations.js +46 -0
- package/dist/src/server/runtime-store-workflows.js +10 -6
- package/dist/src/server/runtime-store.js +34 -42
- package/dist/src/server/scenario-presets.d.ts +25 -0
- package/dist/src/server/scenario-presets.js +35 -0
- package/dist/src/server/sentinel-heartbeat.d.ts +30 -0
- package/dist/src/server/sentinel-heartbeat.js +145 -0
- package/dist/src/server/spawn-cli-resolver.d.ts +37 -0
- package/dist/src/server/spawn-cli-resolver.js +70 -0
- package/dist/src/server/spawn-worker-defaults.d.ts +13 -0
- package/dist/src/server/spawn-worker-defaults.js +45 -0
- package/dist/src/server/sqlite-schema-v32.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v32.js +17 -0
- package/dist/src/server/sqlite-schema-v33.d.ts +3 -0
- package/dist/src/server/sqlite-schema-v33.js +18 -0
- package/dist/src/server/sqlite-schema-v34.d.ts +11 -0
- package/dist/src/server/sqlite-schema-v34.js +19 -0
- package/dist/src/server/sqlite-schema-v35.d.ts +3 -0
- package/dist/src/server/sqlite-schema-v35.js +23 -0
- package/dist/src/server/sqlite-schema.d.ts +1 -1
- package/dist/src/server/sqlite-schema.js +35 -1
- package/dist/src/server/system-message.d.ts +5 -2
- package/dist/src/server/system-message.js +5 -2
- package/dist/src/server/tasks-file-watcher.d.ts +8 -0
- package/dist/src/server/tasks-file-watcher.js +31 -2
- package/dist/src/server/team-authz.d.ts +9 -1
- package/dist/src/server/team-authz.js +24 -0
- package/dist/src/server/team-list-serializer.d.ts +2 -2
- package/dist/src/server/team-list-serializer.js +2 -1
- package/dist/src/server/team-memory-digest.js +4 -4
- package/dist/src/server/team-memory-dream-applier.js +24 -3
- package/dist/src/server/team-memory-dream-prompt.d.ts +13 -0
- package/dist/src/server/team-memory-dream-prompt.js +91 -0
- package/dist/src/server/team-memory-dream-run-store.d.ts +2 -0
- package/dist/src/server/team-memory-dream-run-store.js +14 -4
- package/dist/src/server/team-memory-dream-runner.d.ts +2 -21
- package/dist/src/server/team-memory-dream-runner.js +3 -148
- package/dist/src/server/team-memory-dream-store.d.ts +1 -1
- package/dist/src/server/team-memory-dream-store.js +1 -1
- package/dist/src/server/team-operations.d.ts +18 -2
- package/dist/src/server/team-operations.js +222 -33
- package/dist/src/server/team-recap.d.ts +10 -0
- package/dist/src/server/team-recap.js +73 -0
- package/dist/src/server/terminal-input-profile.js +95 -6
- package/dist/src/server/upload-limits.d.ts +2 -0
- package/dist/src/server/upload-limits.js +2 -0
- package/dist/src/server/workflow-cli-policy.d.ts +7 -2
- package/dist/src/server/workflow-cli-policy.js +15 -3
- package/dist/src/server/workflow-run-store.d.ts +1 -0
- package/dist/src/server/workflow-run-store.js +11 -1
- package/dist/src/server/workflow-runner.d.ts +4 -1
- package/dist/src/server/workflow-runner.js +418 -118
- package/dist/src/server/workflow-script-loader.d.ts +3 -2
- package/dist/src/server/workflow-script-loader.js +161 -0
- package/dist/src/server/workspace-store-contract.d.ts +2 -0
- package/dist/src/server/workspace-store.d.ts +1 -1
- package/dist/src/server/workspace-store.js +40 -30
- package/dist/src/server/workspace-upload-store.d.ts +40 -0
- package/dist/src/server/workspace-upload-store.js +295 -0
- package/dist/src/shared/scenario-presets.d.ts +32 -0
- package/dist/src/shared/scenario-presets.js +69 -0
- package/dist/src/shared/types.d.ts +12 -1
- package/package.json +1 -1
- package/web/dist/assets/AddWorkerDialog-DBLhwb91.js +2 -0
- package/web/dist/assets/AddWorkspaceFlow-cxvhVAsT.js +1 -0
- package/web/dist/assets/FirstRunWizard-DlEPnWWw.js +1 -0
- package/web/dist/assets/{MarketplaceDrawer-BFfGT8hH.js → MarketplaceDrawer-CfSiRi8e.js} +11 -11
- package/web/dist/assets/TaskGraphDrawer-C2JufcPs.js +1 -0
- package/web/dist/assets/WhatsNewDialog-vP7buLos.js +1 -0
- package/web/dist/assets/WorkerModal-CSorwcdP.js +1 -0
- package/web/dist/assets/{WorkflowsDrawer-CiIdHS6_.js → WorkflowsDrawer-BXS3w9Uq.js} +1 -1
- package/web/dist/assets/WorkspaceMemoryDrawer-D71ivohr.js +1 -0
- package/web/dist/assets/{WorkspaceTaskDrawer-CyhhEB1Z.js → WorkspaceTaskDrawer-CGCTSHKa.js} +1 -1
- package/web/dist/assets/index-BcwN8cCw.js +79 -0
- package/web/dist/assets/index-StXTPHls.css +1 -0
- package/web/dist/assets/{search-BtRkkEmS.js → search-BZw4T67h.js} +1 -1
- package/web/dist/assets/{square-terminal-lEeQUWb3.js → square-terminal-B7E57In1.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +1 -1
- package/dist/src/server/env-sync-message.d.ts +0 -9
- package/dist/src/server/env-sync-message.js +0 -29
- package/web/dist/assets/AddWorkerDialog-C86CwNgQ.js +0 -2
- package/web/dist/assets/AddWorkspaceFlow-Bm2Jz34D.js +0 -1
- package/web/dist/assets/FirstRunWizard-XzBoEpA5.js +0 -1
- package/web/dist/assets/TaskGraphDrawer-_uVH_0C1.js +0 -1
- package/web/dist/assets/WhatsNewDialog-DkJHmkMs.js +0 -1
- package/web/dist/assets/WorkerModal-BtMJEOG9.js +0 -1
- package/web/dist/assets/WorkspaceMemoryDrawer-C6sNocl_.js +0 -1
- package/web/dist/assets/index-BAiLYajK.css +0 -1
- package/web/dist/assets/index-K-GG8UwR.js +0 -73
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { buildOrchestratorReportPayload } from './agent-stdin-dispatcher.js';
|
|
2
|
+
import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
|
|
3
|
+
import { escapeHiveEnvelopeText } from './hive-envelope-escape.js';
|
|
2
4
|
import { ConflictError } from './http-errors.js';
|
|
3
5
|
import { createReportMessage, createSendMessage, createStatusMessage, createUserInputMessage, } from './runtime-message-builders.js';
|
|
6
|
+
import { assertWorkerDispatchable } from './team-authz.js';
|
|
4
7
|
import { getWorkflowAgentId } from './workspace-store-support.js';
|
|
5
8
|
/* Roster snapshot embedded in the 409 the orchestrator sees when it
|
|
6
9
|
dispatches to a missing name. Format prioritizes the orchestrator's
|
|
@@ -14,7 +17,7 @@ export const formatUnknownWorkerError = (workerName, roster) => {
|
|
|
14
17
|
if (roster.length === 0) {
|
|
15
18
|
return [
|
|
16
19
|
`Unknown worker "${workerName}": this workspace currently has no workers.`,
|
|
17
|
-
'
|
|
20
|
+
'Run `team spawn <role> [--name <name>] [--ephemeral]`, then `team list` and retry with the spawned worker name. Do not ask the user to add workers unless a real role decision is missing.',
|
|
18
21
|
].join('\n');
|
|
19
22
|
}
|
|
20
23
|
const lines = roster.map((entry) => ` - ${entry.name} (${entry.role}, ${entry.status}, ${entry.pendingTaskCount} pending)`);
|
|
@@ -24,12 +27,30 @@ export const formatUnknownWorkerError = (workerName, roster) => {
|
|
|
24
27
|
'Retry with `team send "<one of the names above>" "<task>"`, or run `team list` to refresh the roster.',
|
|
25
28
|
].join('\n');
|
|
26
29
|
};
|
|
30
|
+
/* Worker-facing 409 for `team report`. The same generic "no open dispatch"
|
|
31
|
+
used to cover two very different states: (a) the worker passed a --dispatch
|
|
32
|
+
id that is wrong or already closed while OTHER dispatches are still open —
|
|
33
|
+
recoverable in one retry if we list the real ids; (b) the worker truly has
|
|
34
|
+
nothing open — the right move is `team status`, not a report retry loop. */
|
|
35
|
+
export const formatNoOpenDispatchError = (workerName, requestedDispatchId, openDispatches) => {
|
|
36
|
+
if (requestedDispatchId !== undefined && openDispatches.length > 0) {
|
|
37
|
+
const lines = openDispatches.map((dispatch) => ` - ${dispatch.id} (${dispatch.status}): ${dispatch.text.slice(0, 60)}${dispatch.text.length > 60 ? '…' : ''}`);
|
|
38
|
+
return [
|
|
39
|
+
`Dispatch ${requestedDispatchId} is not open for worker ${workerName}. Your open dispatches:`,
|
|
40
|
+
...lines,
|
|
41
|
+
'Re-run `team report --dispatch <one of the ids above>`, or omit --dispatch to close the oldest.',
|
|
42
|
+
].join('\n');
|
|
43
|
+
}
|
|
44
|
+
return (`No open dispatch for worker: ${workerName}. Nothing is awaiting your report — ` +
|
|
45
|
+
'if you have progress or standby info to share, use `team status "<state>"` instead.');
|
|
46
|
+
};
|
|
27
47
|
const reportForwardErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
28
48
|
const buildPendingWarning = (workerName, pendingTaskCount, action) => pendingTaskCount > 0
|
|
29
49
|
? `Hive recorded the ${action}, but ${workerName} still has ${pendingTaskCount} open dispatch${pendingTaskCount === 1 ? '' : 'es'}. ` +
|
|
30
50
|
'A worker stays working until every dispatch is closed with `team report --dispatch <id>` or `team cancel --dispatch <id>`.'
|
|
31
51
|
: undefined;
|
|
32
|
-
export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, insertMessage, markDispatchCancelled, markDispatchReportedByWorker,
|
|
52
|
+
export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispatch, deleteMessage, findOpenDispatch, findOpenDispatchById, listOpenWorkspaceDispatches, insertMessage, markDispatchCancelled, markDispatchReportedByWorker, claimQueuedDispatch, reparkClaimedDispatch, reportOutbox, notifyWebhook, workflowDispatchAwaiter, workspaceStore, dismissEphemeralWorker, recordProtocolEvent, getFlags, }) => {
|
|
53
|
+
const flags = () => getFlags?.() ?? FEATURE_FLAGS_ALL_OFF;
|
|
33
54
|
// Best-effort redelivery of reports a prior orchestrator outage stranded.
|
|
34
55
|
// Called when a fresh report confirms the orchestrator is reachable and
|
|
35
56
|
// when the orchestrator polls `team list` (its natural post-restart wakeup).
|
|
@@ -71,7 +92,10 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
71
92
|
throw error;
|
|
72
93
|
}
|
|
73
94
|
};
|
|
74
|
-
const cancelUndeliveredDispatch = (workspaceId, workerId, dispatchId, reason, workflowRunId
|
|
95
|
+
const cancelUndeliveredDispatch = (workspaceId, workerId, dispatchId, reason, workflowRunId,
|
|
96
|
+
/** Non-workflow issuer to notify — without this the orchestrator already
|
|
97
|
+
* holds an ok:true for a dispatch that silently died (audit #34). */
|
|
98
|
+
notifyAgentId) => {
|
|
75
99
|
const cancelled = markDispatchCancelled({ dispatchId, reason, workspaceId });
|
|
76
100
|
if (!cancelled)
|
|
77
101
|
return;
|
|
@@ -84,11 +108,141 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
84
108
|
if (workflowRunId !== undefined) {
|
|
85
109
|
workflowDispatchAwaiter.notifyCancel(dispatchId, reason);
|
|
86
110
|
}
|
|
111
|
+
if (notifyAgentId !== undefined) {
|
|
112
|
+
notifyIssuerDurably(workspaceId, notifyAgentId, dispatchId, `Dispatch ${dispatchId} to @${workerNameOrId(workspaceId, workerId)} was CANCELLED: delivery failed (${reason}). ` +
|
|
113
|
+
'The worker likely exited before receiving it — re-send after the worker is started, or dispatch to another worker.');
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const workerNameOrId = (workspaceId, workerId) => {
|
|
117
|
+
try {
|
|
118
|
+
return workspaceStore.getWorker(workspaceId, workerId).name;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Worker row already gone (e.g. dismissed) — fall back to the id.
|
|
122
|
+
return workerId;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
/** Issuer notification that survives the issuer being down: try the live
|
|
126
|
+
* PTY first; if the issuer has no active run (or the write rejects), park
|
|
127
|
+
* the notice in the report outbox — it drains on the issuer's next
|
|
128
|
+
* `team list` / start, the same redelivery path worker reports use.
|
|
129
|
+
* (A fire-and-forget writeSystemMessageToAgent silently no-ops without an
|
|
130
|
+
* active run, which is exactly when failures cluster — post-restart.) */
|
|
131
|
+
const notifyIssuerDurably = (workspaceId, issuerAgentId, dispatchId, text) => {
|
|
132
|
+
const payload = `<hive-system-reminder>\n${escapeHiveEnvelopeText(text)}\n</hive-system-reminder>\n`;
|
|
133
|
+
const park = () => {
|
|
134
|
+
try {
|
|
135
|
+
reportOutbox.enqueue({
|
|
136
|
+
workspaceId,
|
|
137
|
+
targetAgentId: issuerAgentId,
|
|
138
|
+
dispatchId,
|
|
139
|
+
payload,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error('[hive] swallowed:teamDispatch.notifyOutbox', error);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
agentRuntime
|
|
148
|
+
.deliverSystemMessageToAgent(workspaceId, issuerAgentId, payload, {
|
|
149
|
+
requireActiveRun: true,
|
|
150
|
+
})
|
|
151
|
+
.catch(park);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
park();
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
/** Called before a worker (and its dispatch rows) are deleted: open
|
|
158
|
+
* non-workflow dispatches would otherwise vanish without the issuer ever
|
|
159
|
+
* hearing — a third silent outcome next to delivered/cancelled. */
|
|
160
|
+
const notifyIssuersOfDroppedDispatches = (workspaceId, workerId, reason) => {
|
|
161
|
+
let open;
|
|
162
|
+
try {
|
|
163
|
+
open = listOpenWorkspaceDispatches(workspaceId).filter((item) => item.toAgentId === workerId &&
|
|
164
|
+
item.workflowRunId === null &&
|
|
165
|
+
item.fromAgentId !== null &&
|
|
166
|
+
item.fromAgentId !== getWorkflowAgentId(workspaceId));
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error('[hive] swallowed:teamDismiss.listOpen', error);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const workerName = workerNameOrId(workspaceId, workerId);
|
|
173
|
+
for (const item of open) {
|
|
174
|
+
if (!item.fromAgentId)
|
|
175
|
+
continue;
|
|
176
|
+
notifyIssuerDurably(workspaceId, item.fromAgentId, item.id, `Dispatch ${item.id} to @${workerName} was DROPPED: ${reason}. ` +
|
|
177
|
+
'It will not run and cannot be reported — re-dispatch the task to another worker if it still matters.');
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
/** #33: deliver dispatches parked while their worker was stopped. Called on
|
|
181
|
+
* worker run start (lifecycle) and after dispatchTask auto-starts a worker.
|
|
182
|
+
* `claimQueuedDispatch` makes each delivery single-shot under races (UI
|
|
183
|
+
* start vs auto-start send); failures compensate via
|
|
184
|
+
* cancelUndeliveredDispatch and notify the issuer. Workflow dispatches are
|
|
185
|
+
* excluded — the runner owns their lifecycle and its awaiters must not see
|
|
186
|
+
* a late delivery after the run already ended. */
|
|
187
|
+
const replayQueuedDispatches = (workspaceId, workerId, options = {}) => {
|
|
188
|
+
if (!agentRuntime.getActiveRunByAgentId(workspaceId, workerId))
|
|
189
|
+
return;
|
|
190
|
+
const queued = listOpenWorkspaceDispatches(workspaceId).filter((item) => item.toAgentId === workerId &&
|
|
191
|
+
item.status === 'queued' &&
|
|
192
|
+
item.workflowRunId === null &&
|
|
193
|
+
item.fromAgentId !== getWorkflowAgentId(workspaceId) &&
|
|
194
|
+
item.id !== options.excludeDispatchId);
|
|
195
|
+
if (queued.length === 0)
|
|
196
|
+
return;
|
|
197
|
+
let worker;
|
|
198
|
+
try {
|
|
199
|
+
worker = workspaceStore.getWorker(workspaceId, workerId);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
/* A replay write only fails when the fresh run is already dead
|
|
205
|
+
(PtyInactive) — nothing reached a live CLI, so the dispatch goes BACK
|
|
206
|
+
to parked for the next start instead of being cancelled: one bad start
|
|
207
|
+
must not destroy parked work. If the row is no longer 'submitted'
|
|
208
|
+
(reported/cancelled meanwhile), the repark loses and we leave it be. */
|
|
209
|
+
const failDispatch = (dispatchId, error) => {
|
|
210
|
+
try {
|
|
211
|
+
if (!reparkClaimedDispatch(dispatchId)) {
|
|
212
|
+
console.error('[hive] swallowed:teamReplay.reparkLost', dispatchId);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (reparkError) {
|
|
216
|
+
console.error('[hive] swallowed:teamReplay.repark', reparkError);
|
|
217
|
+
}
|
|
218
|
+
console.error('[hive] swallowed:teamReplay.writePrompt', error);
|
|
219
|
+
};
|
|
220
|
+
for (const item of queued) {
|
|
221
|
+
if (!claimQueuedDispatch(item.id))
|
|
222
|
+
continue;
|
|
223
|
+
let senderName = 'Orchestrator';
|
|
224
|
+
if (item.fromAgentId) {
|
|
225
|
+
try {
|
|
226
|
+
senderName = workspaceStore.getAgent(workspaceId, item.fromAgentId).name;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Issuer gone; the default label is still actionable for the worker.
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const writePrompt = agentRuntime.writeSendPrompt(workspaceId, workerId, item.id, senderName, worker.description, item.text);
|
|
234
|
+
void writePrompt.catch((error) => failDispatch(item.id, error));
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
failDispatch(item.id, error);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
87
240
|
};
|
|
88
241
|
const dispatchTask = async (workspaceId, workerId, text, input = {}) => {
|
|
89
242
|
const fromAgentId = input.fromAgentId;
|
|
90
243
|
const sender = fromAgentId ? workspaceStore.getAgent(workspaceId, fromAgentId) : undefined;
|
|
91
244
|
const worker = workspaceStore.getWorker(workspaceId, workerId);
|
|
245
|
+
assertWorkerDispatchable(worker);
|
|
92
246
|
const message = createSendMessage(workspaceId, workerId, text, input.fromAgentId);
|
|
93
247
|
const messageHandle = insertMessage(message);
|
|
94
248
|
let dispatch;
|
|
@@ -110,30 +264,57 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
110
264
|
if (input.label !== undefined)
|
|
111
265
|
dispatchInput.label = input.label;
|
|
112
266
|
dispatch = createDispatch(dispatchInput);
|
|
267
|
+
recordProtocolEvent?.('send');
|
|
113
268
|
const dispatchId = dispatch.id;
|
|
114
269
|
if (fromAgentId && sender) {
|
|
115
270
|
const shouldAutoStartWorker = input.autoStartWorker !== false;
|
|
116
271
|
const hadActiveRun = Boolean(agentRuntime.getActiveRunByAgentId(workspaceId, workerId));
|
|
117
272
|
if (!hadActiveRun && shouldAutoStartWorker) {
|
|
118
273
|
await ensureWorkerRun(workspaceId, workerId, input.hivePort ?? '');
|
|
274
|
+
// The fresh run may also have OLDER parked dispatches waiting —
|
|
275
|
+
// deliver them ahead of this one. Claims keep delivery single-shot;
|
|
276
|
+
// the current dispatch is claimed by this same path just below.
|
|
277
|
+
replayQueuedDispatches(workspaceId, workerId, { excludeDispatchId: dispatchId });
|
|
119
278
|
}
|
|
120
279
|
if (!shouldAutoStartWorker && !agentRuntime.getActiveRunByAgentId(workspaceId, workerId)) {
|
|
121
280
|
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
122
281
|
pendingMarked = true;
|
|
123
|
-
|
|
282
|
+
// Parked for a stopped worker: replayQueuedDispatches delivers it on
|
|
283
|
+
// the next worker start. Tag the record so the route/CLI can say so.
|
|
284
|
+
return Object.assign(dispatch, { queuedForStoppedWorker: true });
|
|
124
285
|
}
|
|
125
286
|
const isWorkflowDispatch = input.workflowRunId !== undefined || input.fromAgentId === getWorkflowAgentId(workspaceId);
|
|
126
|
-
|
|
287
|
+
// Revalidate the worker before claiming: it may have been deleted
|
|
288
|
+
// while ensureWorkerRun was awaited (R2 hardening) — a vanished worker
|
|
289
|
+
// must not leave a claimed-but-undeliverable dispatch behind.
|
|
290
|
+
workspaceStore.getWorker(workspaceId, workerId);
|
|
291
|
+
// Claim (queued → submitted) instead of a blind mark: a UI-initiated
|
|
292
|
+
// start can replay-deliver this dispatch while ensureWorkerRun above
|
|
293
|
+
// was awaited — losing the claim means delivery is already owned.
|
|
294
|
+
const claimed = claimQueuedDispatch(dispatchId);
|
|
127
295
|
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
128
296
|
pendingMarked = true;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
297
|
+
if (claimed)
|
|
298
|
+
try {
|
|
299
|
+
const writePrompt = agentRuntime.writeSendPrompt(workspaceId, workerId, dispatchId, sender.name, worker.description, text);
|
|
300
|
+
void writePrompt.catch((error) => {
|
|
301
|
+
// `team send` is intentionally asynchronous (§3.3). A worker that
|
|
302
|
+
// exits during paste-submit did not receive actionable work, so
|
|
303
|
+
// close the open dispatch instead of leaving a fake pending task.
|
|
304
|
+
try {
|
|
305
|
+
cancelUndeliveredDispatch(workspaceId, workerId, dispatchId, reportForwardErrorMessage(error), input.workflowRunId, isWorkflowDispatch ? undefined : fromAgentId);
|
|
306
|
+
}
|
|
307
|
+
catch (cancelError) {
|
|
308
|
+
if (!isWorkflowDispatch)
|
|
309
|
+
console.error('[hive] swallowed:teamDispatch.cancelUndelivered', cancelError);
|
|
310
|
+
}
|
|
311
|
+
if (!isWorkflowDispatch)
|
|
312
|
+
console.error('[hive] swallowed:teamDispatch.writePrompt', error);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
135
316
|
try {
|
|
136
|
-
cancelUndeliveredDispatch(workspaceId, workerId, dispatchId, reportForwardErrorMessage(error), input.workflowRunId);
|
|
317
|
+
cancelUndeliveredDispatch(workspaceId, workerId, dispatchId, reportForwardErrorMessage(error), input.workflowRunId, isWorkflowDispatch ? undefined : fromAgentId);
|
|
137
318
|
}
|
|
138
319
|
catch (cancelError) {
|
|
139
320
|
if (!isWorkflowDispatch)
|
|
@@ -141,19 +322,7 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
141
322
|
}
|
|
142
323
|
if (!isWorkflowDispatch)
|
|
143
324
|
console.error('[hive] swallowed:teamDispatch.writePrompt', error);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
try {
|
|
148
|
-
cancelUndeliveredDispatch(workspaceId, workerId, dispatchId, reportForwardErrorMessage(error), input.workflowRunId);
|
|
149
325
|
}
|
|
150
|
-
catch (cancelError) {
|
|
151
|
-
if (!isWorkflowDispatch)
|
|
152
|
-
console.error('[hive] swallowed:teamDispatch.cancelUndelivered', cancelError);
|
|
153
|
-
}
|
|
154
|
-
if (!isWorkflowDispatch)
|
|
155
|
-
console.error('[hive] swallowed:teamDispatch.writePrompt', error);
|
|
156
|
-
}
|
|
157
326
|
}
|
|
158
327
|
else {
|
|
159
328
|
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
@@ -193,6 +362,13 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
193
362
|
throw new ConflictError(`No open dispatch: ${dispatchId}`);
|
|
194
363
|
}
|
|
195
364
|
workspaceStore.markTaskCancelled(workspaceId, dispatch.toAgentId);
|
|
365
|
+
recordProtocolEvent?.('cancel');
|
|
366
|
+
// A workflow-owned dispatch cancelled from outside the runner (e.g. an
|
|
367
|
+
// orchestrator that got hold of the id) must still resolve the runner's
|
|
368
|
+
// awaiter — otherwise the run wedges until its step timeout.
|
|
369
|
+
if (dispatch.workflowRunId !== null) {
|
|
370
|
+
workflowDispatchAwaiter.notifyCancel(dispatch.id, input.reason);
|
|
371
|
+
}
|
|
196
372
|
let forwardError = null;
|
|
197
373
|
let forwarded = false;
|
|
198
374
|
try {
|
|
@@ -207,6 +383,8 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
207
383
|
},
|
|
208
384
|
dispatchTask,
|
|
209
385
|
drainReportOutbox,
|
|
386
|
+
replayQueuedDispatches,
|
|
387
|
+
notifyIssuersOfDroppedDispatches,
|
|
210
388
|
async dispatchTaskByWorkerName(workspaceId, workerName, text, input = {}) {
|
|
211
389
|
/* Build the roster once so a missing-name path can surface it without
|
|
212
390
|
a second store call. We deliberately don't go through
|
|
@@ -238,6 +416,7 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
238
416
|
const artifacts = input.artifacts ?? [];
|
|
239
417
|
const worker = workspaceStore.getWorker(workspaceId, workerId);
|
|
240
418
|
const messageHandle = insertMessage(createStatusMessage(workspaceId, workerId, text, artifacts));
|
|
419
|
+
recordProtocolEvent?.('status');
|
|
241
420
|
try {
|
|
242
421
|
let forwardError = null;
|
|
243
422
|
let forwarded = false;
|
|
@@ -273,7 +452,7 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
273
452
|
const worker = workspaceStore.getWorker(workspaceId, workerId);
|
|
274
453
|
const openDispatch = findOpenDispatch(workspaceId, workerId, input.dispatchId);
|
|
275
454
|
if (!openDispatch) {
|
|
276
|
-
throw new ConflictError(
|
|
455
|
+
throw new ConflictError(formatNoOpenDispatchError(worker.name, input.dispatchId, listOpenWorkspaceDispatches(workspaceId).filter((item) => item.toAgentId === workerId)));
|
|
277
456
|
}
|
|
278
457
|
const messageHandle = insertMessage(createReportMessage(workspaceId, workerId, text, status, artifacts));
|
|
279
458
|
try {
|
|
@@ -285,9 +464,12 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
285
464
|
workspaceId,
|
|
286
465
|
});
|
|
287
466
|
if (!dispatch) {
|
|
288
|
-
|
|
467
|
+
// Post-race recheck: the dispatch closed between the precheck and
|
|
468
|
+
// the ledger write — re-query so the 409 lists what is still open.
|
|
469
|
+
throw new ConflictError(formatNoOpenDispatchError(worker.name, input.dispatchId, listOpenWorkspaceDispatches(workspaceId).filter((item) => item.toAgentId === workerId)));
|
|
289
470
|
}
|
|
290
471
|
workspaceStore.markTaskReported(workspaceId, workerId);
|
|
472
|
+
recordProtocolEvent?.('report');
|
|
291
473
|
const remainingPendingTaskCount = workspaceStore.getWorker(workspaceId, workerId).pendingTaskCount;
|
|
292
474
|
let forwardError = null;
|
|
293
475
|
let forwarded = false;
|
|
@@ -331,7 +513,7 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
331
513
|
// backlog a prior outage stranded first, in arrival order, before
|
|
332
514
|
// this one (both ride the dispatcher's per-agent serial queue).
|
|
333
515
|
drainReportOutbox(workspaceId);
|
|
334
|
-
const payload = buildOrchestratorReportPayload(worker.name, text, artifacts);
|
|
516
|
+
const payload = buildOrchestratorReportPayload(worker.name, text, artifacts, flags());
|
|
335
517
|
if (agentRuntime.getActiveRunByAgentId(workspaceId, orchestratorId)) {
|
|
336
518
|
try {
|
|
337
519
|
const delivery = agentRuntime.deliverReportToOrchestrator(workspaceId, worker.name, text, artifacts, { requireActiveRun: true });
|
|
@@ -376,14 +558,21 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
376
558
|
});
|
|
377
559
|
}
|
|
378
560
|
}
|
|
379
|
-
// M11: if this worker was spawned with `team spawn --ephemeral`,
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
// the
|
|
561
|
+
// M11: if this worker was spawned with `team spawn --ephemeral`, the
|
|
562
|
+
// report that closes its LAST open dispatch triggers auto-dismiss.
|
|
563
|
+
// Gating on remainingPendingTaskCount === 0 keeps the one-shot
|
|
564
|
+
// semantics for the normal single-dispatch case while not deleting
|
|
565
|
+
// still-queued dispatches un-reported when the orchestrator stacked
|
|
566
|
+
// several sends. (If the last dispatch closes via `team cancel`
|
|
567
|
+
// instead, the worker lingers until the orchestrator-exit cascade /
|
|
568
|
+
// boot cleanup collects it.) Deferred via queueMicrotask so the
|
|
569
|
+
// orchestrator's forward write lands BEFORE the worker's PTY is torn
|
|
570
|
+
// down (otherwise the inject + dismiss race). Skipped for workflow
|
|
571
|
+
// dispatches — workflow workers are managed by the runner's own
|
|
572
|
+
// finally block.
|
|
385
573
|
if (worker.ephemeral === true &&
|
|
386
574
|
worker.spawnedBy === 'orchestrator' &&
|
|
575
|
+
remainingPendingTaskCount === 0 &&
|
|
387
576
|
dismissEphemeralWorker) {
|
|
388
577
|
queueMicrotask(() => {
|
|
389
578
|
try {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TeamListItem } from '../shared/types.js';
|
|
2
|
+
import type { DispatchRecord } from './dispatch-ledger-store.js';
|
|
3
|
+
export interface TeamRecapInput {
|
|
4
|
+
/** Recent dispatches, newest first (listRecentWorkspaceDispatches order). */
|
|
5
|
+
dispatches: DispatchRecord[];
|
|
6
|
+
now: number;
|
|
7
|
+
workers: TeamListItem[];
|
|
8
|
+
workspaceName: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const buildTeamRecapMarkdown: (input: TeamRecapInput) => string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Privacy note — recap vs. support bundle: dispatch label/phase/reportText are
|
|
2
|
+
// workflow-authored free text (the first-party guidance even puts file paths
|
|
3
|
+
// in labels). The shareable support bundle therefore reduces them to presence
|
|
4
|
+
// flags (see action-center-summary.ts `includeTextEvidence === false`). The
|
|
5
|
+
// recap is the opposite case on purpose: the user explicitly clicks "Copy
|
|
6
|
+
// recap" to share their own team's progress, so the verbatim text IS the
|
|
7
|
+
// product. Do not "sanitize" this output to match the bundle.
|
|
8
|
+
const REPORT_PREVIEW_MAX = 200;
|
|
9
|
+
/** Markdown table cells cannot contain raw pipes or newlines. */
|
|
10
|
+
const tableCell = (text) => text.replace(/\s+/gu, ' ').trim().replace(/\|/gu, '\\|');
|
|
11
|
+
const truncate = (text, max) => {
|
|
12
|
+
const normalized = text.replace(/\s+/gu, ' ').trim();
|
|
13
|
+
return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
|
|
14
|
+
};
|
|
15
|
+
const dispatchTimestamp = (dispatch) => dispatch.reportedAt ?? dispatch.submittedAt ?? dispatch.deliveredAt ?? dispatch.createdAt;
|
|
16
|
+
const formatAge = (timestamp, now) => {
|
|
17
|
+
const seconds = Math.max(0, Math.floor((now - timestamp) / 1000));
|
|
18
|
+
if (seconds < 60)
|
|
19
|
+
return `${seconds}s`;
|
|
20
|
+
if (seconds < 3600)
|
|
21
|
+
return `${Math.floor(seconds / 60)}m`;
|
|
22
|
+
if (seconds < 86400)
|
|
23
|
+
return `${Math.floor(seconds / 3600)}h`;
|
|
24
|
+
return `${Math.floor(seconds / 86400)}d`;
|
|
25
|
+
};
|
|
26
|
+
export const buildTeamRecapMarkdown = (input) => {
|
|
27
|
+
const workersById = new Map(input.workers.map((worker) => [worker.id, worker]));
|
|
28
|
+
const workerName = (agentId) => workersById.get(agentId)?.name ?? agentId;
|
|
29
|
+
const lines = [];
|
|
30
|
+
lines.push(`# Team recap — ${input.workspaceName}`);
|
|
31
|
+
lines.push('');
|
|
32
|
+
lines.push(`Generated at ${new Date(input.now).toISOString()}`);
|
|
33
|
+
lines.push('');
|
|
34
|
+
lines.push('## Team');
|
|
35
|
+
lines.push('');
|
|
36
|
+
if (input.workers.length === 0) {
|
|
37
|
+
lines.push('_No team members._');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
for (const worker of input.workers) {
|
|
41
|
+
lines.push(`- **${worker.name}** — ${worker.role} · ${worker.status}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
lines.push('');
|
|
45
|
+
lines.push('## Recent dispatches');
|
|
46
|
+
lines.push('');
|
|
47
|
+
if (input.dispatches.length === 0) {
|
|
48
|
+
lines.push('_No dispatches yet._');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
lines.push('| Label | Status | Worker | Age |');
|
|
52
|
+
lines.push('| --- | --- | --- | --- |');
|
|
53
|
+
for (const dispatch of input.dispatches) {
|
|
54
|
+
const label = dispatch.label ?? dispatch.phase ?? truncate(dispatch.text, 60);
|
|
55
|
+
const age = formatAge(dispatchTimestamp(dispatch), input.now);
|
|
56
|
+
lines.push(`| ${tableCell(label)} | ${dispatch.status} | ${tableCell(workerName(dispatch.toAgentId))} | ${age} |`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
lines.push('');
|
|
60
|
+
const reported = input.dispatches.filter((dispatch) => dispatch.status === 'reported' && dispatch.reportText);
|
|
61
|
+
if (reported.length > 0) {
|
|
62
|
+
lines.push('## Reports');
|
|
63
|
+
lines.push('');
|
|
64
|
+
for (const dispatch of reported) {
|
|
65
|
+
const heading = dispatch.label ?? dispatch.phase ?? workerName(dispatch.toAgentId);
|
|
66
|
+
lines.push(`### ${truncate(heading, 80)}`);
|
|
67
|
+
lines.push('');
|
|
68
|
+
lines.push(truncate(dispatch.reportText ?? '', REPORT_PREVIEW_MAX));
|
|
69
|
+
lines.push('');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
73
|
+
};
|
|
@@ -1,4 +1,91 @@
|
|
|
1
|
-
import { normalizeExecutableToken } from './startup-command-parser.js';
|
|
1
|
+
import { getStartupCommandExecutable, normalizeExecutableToken } from './startup-command-parser.js';
|
|
2
|
+
const PROFILE_BY_BRAND = {
|
|
3
|
+
codex: 'codex',
|
|
4
|
+
grok: 'grok',
|
|
5
|
+
opencode: 'opencode',
|
|
6
|
+
};
|
|
7
|
+
// Shells whose spawn shape wraps the real command line in their final
|
|
8
|
+
// argument (`cmd /d /s /c <line>`, `zsh -lic <line>`). Legacy launch configs
|
|
9
|
+
// persisted before interactiveCommand existed only have this shape, so the
|
|
10
|
+
// profile must see through it or those rows degrade to 'default' forever.
|
|
11
|
+
const SHELL_WRAPPERS = new Set(['bash', 'cmd', 'fish', 'ksh', 'powershell', 'pwsh', 'sh', 'zsh']);
|
|
12
|
+
const NPX_WRAPPERS = new Set(['bunx', 'npx', 'pnpx']);
|
|
13
|
+
const isCodexNpmEntrypoint = (arg) => {
|
|
14
|
+
if (!arg)
|
|
15
|
+
return false;
|
|
16
|
+
const normalized = arg.replace(/\\/gu, '/');
|
|
17
|
+
return /(?:^|\/)@openai\/codex\/bin\/codex\.js$/iu.test(normalized);
|
|
18
|
+
};
|
|
19
|
+
const brandFromToken = (token) => normalizeExecutableToken(token);
|
|
20
|
+
const stripNpmScope = (pkg) => pkg.startsWith('@') ? (pkg.split('/').at(-1) ?? pkg) : pkg;
|
|
21
|
+
const tokenizeCommandLine = (line) => line.match(/"[^"]*"|'[^']*'|[^\s'"]+/gu)?.map((token) => token.replace(/^["']|["']$/gu, '')) ?? [];
|
|
22
|
+
/**
|
|
23
|
+
* Walk a token list and resolve the CLI brand of the command that will
|
|
24
|
+
* actually own the terminal, seeing through shell wrappers (skipping their
|
|
25
|
+
* flag tokens), npx-style runners, and `node <entrypoint>` launches.
|
|
26
|
+
*/
|
|
27
|
+
const profileFromTokens = (tokens) => {
|
|
28
|
+
let index = 0;
|
|
29
|
+
while (index < tokens.length) {
|
|
30
|
+
const executable = brandFromToken(tokens[index]);
|
|
31
|
+
if (!executable)
|
|
32
|
+
return undefined;
|
|
33
|
+
const direct = PROFILE_BY_BRAND[executable];
|
|
34
|
+
if (direct)
|
|
35
|
+
return direct;
|
|
36
|
+
if (SHELL_WRAPPERS.has(executable)) {
|
|
37
|
+
index += 1;
|
|
38
|
+
while (index < tokens.length && /^[-/]/u.test(tokens[index] ?? ''))
|
|
39
|
+
index += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (NPX_WRAPPERS.has(executable)) {
|
|
43
|
+
index += 1;
|
|
44
|
+
while (index < tokens.length && tokens[index]?.startsWith('-'))
|
|
45
|
+
index += 1;
|
|
46
|
+
const pkg = tokens[index];
|
|
47
|
+
if (!pkg)
|
|
48
|
+
return undefined;
|
|
49
|
+
const brand = brandFromToken(stripNpmScope(pkg));
|
|
50
|
+
return brand ? PROFILE_BY_BRAND[brand] : undefined;
|
|
51
|
+
}
|
|
52
|
+
if (executable === 'node') {
|
|
53
|
+
return tokens.slice(index + 1).some(isCodexNpmEntrypoint) ? 'codex' : undefined;
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
const profileFromCommandText = (text) => {
|
|
60
|
+
if (!text?.trim())
|
|
61
|
+
return undefined;
|
|
62
|
+
// Fast paths: the first (possibly quoted) token, and the whole text as one
|
|
63
|
+
// token — the latter covers unquoted Windows paths with spaces such as
|
|
64
|
+
// `C:\Program Files\nodejs\opencode.cmd`.
|
|
65
|
+
for (const candidate of [getStartupCommandExecutable(text), text]) {
|
|
66
|
+
const brand = brandFromToken(candidate ?? undefined);
|
|
67
|
+
const profile = brand ? PROFILE_BY_BRAND[brand] : undefined;
|
|
68
|
+
if (profile)
|
|
69
|
+
return profile;
|
|
70
|
+
}
|
|
71
|
+
return profileFromTokens(tokenizeCommandLine(text));
|
|
72
|
+
};
|
|
73
|
+
const profileFromSpawnArgs = (config) => {
|
|
74
|
+
const executable = brandFromToken(config.command);
|
|
75
|
+
if (!executable || !config.args?.length)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (SHELL_WRAPPERS.has(executable)) {
|
|
78
|
+
// The wrapped command line rides in the shell's final argument.
|
|
79
|
+
return profileFromCommandText(config.args.at(-1));
|
|
80
|
+
}
|
|
81
|
+
if (NPX_WRAPPERS.has(executable)) {
|
|
82
|
+
return profileFromTokens([executable, ...config.args]);
|
|
83
|
+
}
|
|
84
|
+
if (executable === 'node') {
|
|
85
|
+
return config.args.some(isCodexNpmEntrypoint) ? 'codex' : undefined;
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
};
|
|
2
89
|
export const resolveTerminalInputProfile = (config) => {
|
|
3
90
|
if (!config)
|
|
4
91
|
return 'default';
|
|
@@ -8,10 +95,12 @@ export const resolveTerminalInputProfile = (config) => {
|
|
|
8
95
|
return 'grok';
|
|
9
96
|
if (config.commandPresetId === 'opencode')
|
|
10
97
|
return 'opencode';
|
|
11
|
-
|
|
12
|
-
if (executable === 'codex')
|
|
98
|
+
if (config.sessionIdCapture?.source === 'codex_session_jsonl_dir')
|
|
13
99
|
return 'codex';
|
|
14
|
-
if (
|
|
15
|
-
return '
|
|
16
|
-
return
|
|
100
|
+
if (config.sessionIdCapture?.source === 'opencode_session_db')
|
|
101
|
+
return 'opencode';
|
|
102
|
+
return (profileFromCommandText(config.interactiveCommand) ??
|
|
103
|
+
profileFromCommandText(config.command) ??
|
|
104
|
+
profileFromSpawnArgs(config) ??
|
|
105
|
+
'default');
|
|
17
106
|
};
|
|
@@ -12,8 +12,13 @@
|
|
|
12
12
|
* unrestricted and defaults to `claude` — i.e. exactly the old behavior, so
|
|
13
13
|
* upgrading without configuring anything changes nothing.
|
|
14
14
|
*/
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Canonical workflow-capable CLIs. This is intentionally narrower than every
|
|
17
|
+
* built-in command preset: editor/assistant presets that do not support the
|
|
18
|
+
* Hive worker report loop should not become workflow agent targets merely by
|
|
19
|
+
* existing in settings.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CANONICAL_WORKFLOW_CLIS: readonly ["claude", "codex", "opencode", "gemini", "hermes", "qwen", "agy"];
|
|
17
22
|
export type WorkflowCli = (typeof CANONICAL_WORKFLOW_CLIS)[number];
|
|
18
23
|
export declare const WORKFLOW_CLI_POLICY_KEY = "workflow.cli-policy";
|
|
19
24
|
export interface WorkflowCliPolicy {
|
|
@@ -12,9 +12,21 @@
|
|
|
12
12
|
* unrestricted and defaults to `claude` — i.e. exactly the old behavior, so
|
|
13
13
|
* upgrading without configuring anything changes nothing.
|
|
14
14
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Canonical workflow-capable CLIs. This is intentionally narrower than every
|
|
17
|
+
* built-in command preset: editor/assistant presets that do not support the
|
|
18
|
+
* Hive worker report loop should not become workflow agent targets merely by
|
|
19
|
+
* existing in settings.
|
|
20
|
+
*/
|
|
21
|
+
export const CANONICAL_WORKFLOW_CLIS = [
|
|
22
|
+
'claude',
|
|
23
|
+
'codex',
|
|
24
|
+
'opencode',
|
|
25
|
+
'gemini',
|
|
26
|
+
'hermes',
|
|
27
|
+
'qwen',
|
|
28
|
+
'agy',
|
|
29
|
+
];
|
|
18
30
|
export const WORKFLOW_CLI_POLICY_KEY = 'workflow.cli-policy';
|
|
19
31
|
export const DEFAULT_WORKFLOW_CLI_POLICY = {
|
|
20
32
|
default: 'claude',
|
|
@@ -45,6 +45,7 @@ export declare const createWorkflowRunStore: (db: Database) => {
|
|
|
45
45
|
updateRun: (id: string, input: UpdateRunInput) => void;
|
|
46
46
|
getRun: (id: string) => WorkflowRunRecord | undefined;
|
|
47
47
|
listWorkspaceRuns: (workspaceId: string) => WorkflowRunRecord[];
|
|
48
|
+
listChildRuns: (parentRunId: string) => WorkflowRunRecord[];
|
|
48
49
|
markUnfinishedRunsInterrupted: () => void;
|
|
49
50
|
};
|
|
50
51
|
export {};
|