@tt-a1i/hive 2.1.1 → 2.1.4
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 +44 -0
- package/README.en.md +5 -460
- package/README.md +338 -187
- package/README.zh.md +389 -0
- package/dist/src/cli/hive-update.d.ts +3 -2
- package/dist/src/cli/hive-update.js +20 -60
- package/dist/src/cli/hive.js +5 -1
- package/dist/src/server/agent-manager-support.js +16 -9
- package/dist/src/server/agent-runtime-contract.d.ts +4 -0
- package/dist/src/server/agent-runtime.js +27 -0
- package/dist/src/server/agent-stdin-dispatcher.d.ts +3 -0
- package/dist/src/server/agent-stdin-dispatcher.js +3 -0
- package/dist/src/server/diagnostics-support-bundle.d.ts +3 -0
- package/dist/src/server/diagnostics-support-bundle.js +3 -0
- package/dist/src/server/hive-team-guidance.js +34 -4
- package/dist/src/server/message-log-store.d.ts +1 -0
- package/dist/src/server/message-log-store.js +13 -0
- package/dist/src/server/orchestrator-autostart.d.ts +2 -0
- package/dist/src/server/orchestrator-autostart.js +30 -11
- package/dist/src/server/post-start-input-writer.d.ts +1 -0
- package/dist/src/server/post-start-input-writer.js +78 -12
- package/dist/src/server/role-templates.d.ts +8 -1
- package/dist/src/server/role-templates.js +102 -48
- package/dist/src/server/route-types.d.ts +6 -0
- package/dist/src/server/routes-scenarios.d.ts +2 -1
- package/dist/src/server/routes-scenarios.js +11 -6
- package/dist/src/server/routes-team.js +21 -15
- package/dist/src/server/routes-workspaces.js +22 -2
- package/dist/src/server/runtime-store-contract.d.ts +2 -0
- package/dist/src/server/runtime-store-helpers.d.ts +11 -2
- package/dist/src/server/runtime-store-helpers.js +29 -5
- package/dist/src/server/runtime-store-workflows.js +20 -0
- package/dist/src/server/runtime-store.js +2 -0
- package/dist/src/server/scenario-presets.d.ts +3 -1
- package/dist/src/server/scenario-presets.js +12 -1
- package/dist/src/server/spawn-worker-defaults.d.ts +2 -0
- package/dist/src/server/spawn-worker-defaults.js +10 -5
- package/dist/src/server/team-operations.d.ts +3 -0
- package/dist/src/server/team-operations.js +97 -29
- package/dist/src/server/terminal-input-profile.d.ts +2 -0
- package/dist/src/server/update-install-plan.d.ts +20 -0
- package/dist/src/server/update-install-plan.js +151 -0
- package/dist/src/server/version-service.d.ts +5 -0
- package/dist/src/server/version-service.js +17 -13
- package/dist/src/server/workflow-agent-call-executor.d.ts +81 -0
- package/dist/src/server/workflow-agent-call-executor.js +259 -0
- package/dist/src/server/workflow-dag-layer-tracker.d.ts +14 -0
- package/dist/src/server/workflow-dag-layer-tracker.js +37 -0
- package/dist/src/server/workflow-dag.d.ts +8 -0
- package/dist/src/server/workflow-dag.js +65 -0
- package/dist/src/server/workflow-dispatch-awaiter.d.ts +7 -1
- package/dist/src/server/workflow-dispatch-awaiter.js +60 -9
- package/dist/src/server/workflow-runner.d.ts +5 -38
- package/dist/src/server/workflow-runner.js +113 -445
- package/dist/src/server/workflow-script-host-dispatcher.d.ts +14 -0
- package/dist/src/server/workflow-script-host-dispatcher.js +42 -0
- package/dist/src/server/workflow-script-loader.d.ts +2 -2
- package/dist/src/server/workflow-script-loader.js +2 -2
- package/dist/src/server/workflow-script-worker.d.ts +14 -0
- package/dist/src/server/workflow-script-worker.js +81 -0
- package/dist/src/server/workflow-vm-worker-source.d.ts +1 -0
- package/dist/src/server/workflow-vm-worker-source.js +1 -0
- package/dist/src/server/workflow-vm-worker.cjs +238 -0
- package/dist/src/server/workspace-ui-language.d.ts +7 -0
- package/dist/src/server/workspace-ui-language.js +9 -0
- package/dist/src/shared/scenario-presets.d.ts +6 -1
- package/dist/src/shared/scenario-presets.js +80 -28
- package/dist/src/shared/types.d.ts +11 -7
- package/dist/src/shared/ui-language.d.ts +4 -0
- package/dist/src/shared/ui-language.js +3 -0
- package/package.json +6 -3
- package/web/dist/assets/{AddWorkerDialog-CTOsPurT.js → AddWorkerDialog-CkCGKa0N.js} +2 -2
- package/web/dist/assets/AddWorkspaceFlow-Cd73iChz.js +1 -0
- package/web/dist/assets/{FirstRunWizard-C24MUssx.js → FirstRunWizard-Db4HWbBQ.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-7gVUjgAx.js → MarketplaceDrawer-DHwl1if1.js} +1 -1
- package/web/dist/assets/TaskGraphDrawer-B_OibVQO.js +1 -0
- package/web/dist/assets/{WhatsNewDialog-vnShQWjg.js → WhatsNewDialog-9QXlLVfy.js} +1 -1
- package/web/dist/assets/{WorkerModal-DcxIBPlu.js → WorkerModal-BGdtte3-.js} +1 -1
- package/web/dist/assets/{WorkflowsDrawer-C8csID1I.js → WorkflowsDrawer-DRuS0Ao5.js} +1 -1
- package/web/dist/assets/WorkspaceMemoryDrawer-C8PuHo0x.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-6mbgRoOq.js +1 -0
- package/web/dist/assets/index-BiGD5NhO.js +84 -0
- package/web/dist/assets/index-CG2TurwT.css +1 -0
- package/web/dist/assets/{search-BRQbevR2.js → search-BHvXxsdK.js} +1 -1
- package/web/dist/assets/{square-terminal-Co4pmGzn.js → square-terminal-CzkQzjCU.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/AddWorkspaceFlow-D_fSv-cO.js +0 -1
- package/web/dist/assets/TaskGraphDrawer-Beh95yUu.js +0 -1
- package/web/dist/assets/WorkspaceMemoryDrawer-C_Aq9-o6.js +0 -1
- package/web/dist/assets/WorkspaceTaskDrawer-CIMYzlpt.js +0 -1
- package/web/dist/assets/index-3Hc1oXlt.css +0 -1
- package/web/dist/assets/index-D9zUE0W6.js +0 -79
|
@@ -45,6 +45,7 @@ export const formatNoOpenDispatchError = (workerName, requestedDispatchId, openD
|
|
|
45
45
|
'if you have progress or standby info to share, use `team status "<state>"` instead.');
|
|
46
46
|
};
|
|
47
47
|
const reportForwardErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
48
|
+
const isPromptReadinessTimeoutError = (error) => error instanceof Error && /^Timed out waiting for .+ prompt readiness:/u.test(error.message);
|
|
48
49
|
const buildPendingWarning = (workerName, pendingTaskCount, action) => pendingTaskCount > 0
|
|
49
50
|
? `Hive recorded the ${action}, but ${workerName} still has ${pendingTaskCount} open dispatch${pendingTaskCount === 1 ? '' : 'es'}. ` +
|
|
50
51
|
'A worker stays working until every dispatch is closed with `team report --dispatch <id>` or `team cancel --dispatch <id>`.'
|
|
@@ -72,8 +73,9 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
72
73
|
}
|
|
73
74
|
};
|
|
74
75
|
const ensureWorkerRun = async (workspaceId, workerId, hivePort) => {
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
const activeRun = agentRuntime.getActiveRunByAgentId(workspaceId, workerId);
|
|
77
|
+
if (activeRun) {
|
|
78
|
+
return activeRun;
|
|
77
79
|
}
|
|
78
80
|
const config = agentRuntime.peekAgentLaunchConfig(workspaceId, workerId);
|
|
79
81
|
if (!config) {
|
|
@@ -86,6 +88,7 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
86
88
|
workspaceStore.markAgentStopped(workspaceId, workerId);
|
|
87
89
|
throw new ConflictError(`${config.command} failed to start`);
|
|
88
90
|
}
|
|
91
|
+
return run;
|
|
89
92
|
}
|
|
90
93
|
catch (error) {
|
|
91
94
|
workspaceStore.markAgentStopped(workspaceId, workerId);
|
|
@@ -191,6 +194,9 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
191
194
|
item.status === 'queued' &&
|
|
192
195
|
item.workflowRunId === null &&
|
|
193
196
|
item.fromAgentId !== getWorkflowAgentId(workspaceId) &&
|
|
197
|
+
(options.createdBeforeMs === undefined || item.createdAt < options.createdBeforeMs) &&
|
|
198
|
+
(options.beforeSequence === undefined ||
|
|
199
|
+
(item.sequence !== null && item.sequence < options.beforeSequence)) &&
|
|
194
200
|
item.id !== options.excludeDispatchId);
|
|
195
201
|
if (queued.length === 0)
|
|
196
202
|
return;
|
|
@@ -206,10 +212,20 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
206
212
|
to parked for the next start instead of being cancelled: one bad start
|
|
207
213
|
must not destroy parked work. If the row is no longer 'submitted'
|
|
208
214
|
(reported/cancelled meanwhile), the repark loses and we leave it be. */
|
|
209
|
-
const failDispatch = (
|
|
215
|
+
const failDispatch = (item, error) => {
|
|
216
|
+
if (isPromptReadinessTimeoutError(error)) {
|
|
217
|
+
try {
|
|
218
|
+
cancelUndeliveredDispatch(workspaceId, workerId, item.id, reportForwardErrorMessage(error), item.workflowRunId ?? undefined, item.fromAgentId ?? undefined);
|
|
219
|
+
}
|
|
220
|
+
catch (cancelError) {
|
|
221
|
+
console.error('[hive] swallowed:teamReplay.cancelUndelivered', cancelError);
|
|
222
|
+
}
|
|
223
|
+
console.error('[hive] swallowed:teamReplay.writePrompt', error);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
210
226
|
try {
|
|
211
|
-
if (!reparkClaimedDispatch(
|
|
212
|
-
console.error('[hive] swallowed:teamReplay.reparkLost',
|
|
227
|
+
if (!reparkClaimedDispatch(item.id)) {
|
|
228
|
+
console.error('[hive] swallowed:teamReplay.reparkLost', item.id);
|
|
213
229
|
}
|
|
214
230
|
}
|
|
215
231
|
catch (reparkError) {
|
|
@@ -231,10 +247,10 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
231
247
|
}
|
|
232
248
|
try {
|
|
233
249
|
const writePrompt = agentRuntime.writeSendPrompt(workspaceId, workerId, item.id, senderName, worker.description, item.text);
|
|
234
|
-
void writePrompt.catch((error) => failDispatch(item
|
|
250
|
+
void writePrompt.catch((error) => failDispatch(item, error));
|
|
235
251
|
}
|
|
236
252
|
catch (error) {
|
|
237
|
-
failDispatch(item
|
|
253
|
+
failDispatch(item, error);
|
|
238
254
|
}
|
|
239
255
|
}
|
|
240
256
|
};
|
|
@@ -268,13 +284,12 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
268
284
|
const dispatchId = dispatch.id;
|
|
269
285
|
if (fromAgentId && sender) {
|
|
270
286
|
const shouldAutoStartWorker = input.autoStartWorker !== false;
|
|
271
|
-
|
|
272
|
-
if (!
|
|
273
|
-
await ensureWorkerRun(workspaceId, workerId, input.hivePort ?? '');
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
replayQueuedDispatches(workspaceId, workerId, { excludeDispatchId: dispatchId });
|
|
287
|
+
let activeRun = agentRuntime.getActiveRunByAgentId(workspaceId, workerId);
|
|
288
|
+
if (!activeRun && shouldAutoStartWorker) {
|
|
289
|
+
activeRun = await ensureWorkerRun(workspaceId, workerId, input.hivePort ?? '');
|
|
290
|
+
// Revalidate before accepting the task: a worker deleted while the
|
|
291
|
+
// PTY was starting must not leave an accepted-but-undeliverable row.
|
|
292
|
+
workspaceStore.getWorker(workspaceId, workerId);
|
|
278
293
|
}
|
|
279
294
|
if (!shouldAutoStartWorker && !agentRuntime.getActiveRunByAgentId(workspaceId, workerId)) {
|
|
280
295
|
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
@@ -284,19 +299,9 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
284
299
|
return Object.assign(dispatch, { queuedForStoppedWorker: true });
|
|
285
300
|
}
|
|
286
301
|
const isWorkflowDispatch = input.workflowRunId !== undefined || input.fromAgentId === getWorkflowAgentId(workspaceId);
|
|
287
|
-
|
|
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);
|
|
295
|
-
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
296
|
-
pendingMarked = true;
|
|
297
|
-
if (claimed)
|
|
302
|
+
const writeClaimedDispatch = (currentWorker) => {
|
|
298
303
|
try {
|
|
299
|
-
const writePrompt = agentRuntime.writeSendPrompt(workspaceId, workerId, dispatchId, sender.name,
|
|
304
|
+
const writePrompt = agentRuntime.writeSendPrompt(workspaceId, workerId, dispatchId, sender.name, currentWorker.description, text);
|
|
300
305
|
void writePrompt.catch((error) => {
|
|
301
306
|
// `team send` is intentionally asynchronous (§3.3). A worker that
|
|
302
307
|
// exits during paste-submit did not receive actionable work, so
|
|
@@ -323,6 +328,62 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
323
328
|
if (!isWorkflowDispatch)
|
|
324
329
|
console.error('[hive] swallowed:teamDispatch.writePrompt', error);
|
|
325
330
|
}
|
|
331
|
+
};
|
|
332
|
+
const claimAndWriteDispatch = () => {
|
|
333
|
+
const currentWorker = workspaceStore.getWorker(workspaceId, workerId);
|
|
334
|
+
const claimed = claimQueuedDispatch(dispatchId);
|
|
335
|
+
if (claimed)
|
|
336
|
+
writeClaimedDispatch(currentWorker);
|
|
337
|
+
};
|
|
338
|
+
// A PTY can flip from starting -> running on the first stdout chunk
|
|
339
|
+
// before Hermes/OpenCode is actually ready for our startup injection.
|
|
340
|
+
// The post-start barrier, not the coarse run status, is the source of
|
|
341
|
+
// truth for when the first task may be pasted.
|
|
342
|
+
const deferUntilPostStartReady = activeRun?.postStartInputReady;
|
|
343
|
+
if (deferUntilPostStartReady) {
|
|
344
|
+
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
345
|
+
pendingMarked = true;
|
|
346
|
+
const cancelDeferredDispatch = (error) => {
|
|
347
|
+
try {
|
|
348
|
+
cancelUndeliveredDispatch(workspaceId, workerId, dispatchId, reportForwardErrorMessage(error), input.workflowRunId, isWorkflowDispatch ? undefined : fromAgentId);
|
|
349
|
+
}
|
|
350
|
+
catch (cancelError) {
|
|
351
|
+
if (!isWorkflowDispatch)
|
|
352
|
+
console.error('[hive] swallowed:teamDispatch.cancelUndelivered', cancelError);
|
|
353
|
+
}
|
|
354
|
+
if (!isWorkflowDispatch)
|
|
355
|
+
console.error('[hive] swallowed:teamDispatch.deferredWrite', error);
|
|
356
|
+
};
|
|
357
|
+
void deferUntilPostStartReady
|
|
358
|
+
.then(() => {
|
|
359
|
+
const current = listOpenWorkspaceDispatches(workspaceId).find((item) => item.id === dispatchId);
|
|
360
|
+
if (!current || current.status !== 'queued')
|
|
361
|
+
return;
|
|
362
|
+
// Deliver older parked work first, but do not let later sends
|
|
363
|
+
// jump ahead of this dispatch while a starting PTY settles.
|
|
364
|
+
const replayOptions = {
|
|
365
|
+
excludeDispatchId: dispatchId,
|
|
366
|
+
};
|
|
367
|
+
if (current.sequence !== null)
|
|
368
|
+
replayOptions.beforeSequence = current.sequence;
|
|
369
|
+
replayQueuedDispatches(workspaceId, workerId, replayOptions);
|
|
370
|
+
claimAndWriteDispatch();
|
|
371
|
+
})
|
|
372
|
+
.catch(cancelDeferredDispatch);
|
|
373
|
+
return dispatch;
|
|
374
|
+
}
|
|
375
|
+
// Revalidate the worker before claiming: it may have been deleted
|
|
376
|
+
// while ensureWorkerRun was awaited (R2 hardening) — a vanished worker
|
|
377
|
+
// must not leave a claimed-but-undeliverable dispatch behind.
|
|
378
|
+
workspaceStore.getWorker(workspaceId, workerId);
|
|
379
|
+
// Claim (queued → submitted) instead of a blind mark: a UI-initiated
|
|
380
|
+
// start can replay-deliver this dispatch while ensureWorkerRun above
|
|
381
|
+
// was awaited — losing the claim means delivery is already owned.
|
|
382
|
+
const claimed = claimQueuedDispatch(dispatchId);
|
|
383
|
+
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
384
|
+
pendingMarked = true;
|
|
385
|
+
if (claimed)
|
|
386
|
+
writeClaimedDispatch(worker);
|
|
326
387
|
}
|
|
327
388
|
else {
|
|
328
389
|
workspaceStore.markTaskDispatched(workspaceId, workerId);
|
|
@@ -397,9 +458,9 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
397
458
|
throw new ConflictError(formatUnknownWorkerError(workerName, roster));
|
|
398
459
|
}
|
|
399
460
|
/* Capture the active-run state *before* dispatchTask runs. Internal
|
|
400
|
-
workflow calls
|
|
401
|
-
|
|
402
|
-
stopped with a queued dispatch until the user restarts
|
|
461
|
+
workflow calls and orchestrator-owned `team spawn` follow-up sends may
|
|
462
|
+
auto-start a stopped worker; user-managed stopped workers remain
|
|
463
|
+
stopped with a queued dispatch until the user restarts them. */
|
|
403
464
|
const restartedWorker = input.fromAgentId !== undefined &&
|
|
404
465
|
input.autoStartWorker !== false &&
|
|
405
466
|
!agentRuntime.getActiveRunByAgentId(workspaceId, worker.id);
|
|
@@ -411,6 +472,13 @@ export const createTeamOperations = ({ agentRuntime, createDispatch, deleteDispa
|
|
|
411
472
|
agentRuntime.writeUserInputPrompt(workspaceId, text);
|
|
412
473
|
insertMessage(createUserInputMessage(workspaceId, orchestratorId, text));
|
|
413
474
|
},
|
|
475
|
+
async deliverUserInput(workspaceId, orchestratorId, text) {
|
|
476
|
+
workspaceStore.getAgent(workspaceId, orchestratorId);
|
|
477
|
+
await agentRuntime.deliverUserInputToOrchestrator(workspaceId, text, {
|
|
478
|
+
requireActiveRun: true,
|
|
479
|
+
});
|
|
480
|
+
insertMessage(createUserInputMessage(workspaceId, orchestratorId, text));
|
|
481
|
+
},
|
|
414
482
|
statusTask(workspaceId, workerId, input = {}) {
|
|
415
483
|
const text = input.text ?? '';
|
|
416
484
|
const artifacts = input.artifacts ?? [];
|
|
@@ -3,7 +3,9 @@ export type TerminalInputProfile = 'codex' | 'default' | 'grok' | 'opencode';
|
|
|
3
3
|
export interface TerminalRunSummary {
|
|
4
4
|
agent_id: string;
|
|
5
5
|
agent_name: string;
|
|
6
|
+
has_user_input_since_start?: boolean | null;
|
|
6
7
|
run_id: string;
|
|
8
|
+
startup_blocked_reason?: 'first_run_setup' | null;
|
|
7
9
|
status: string;
|
|
8
10
|
terminal_input_profile: TerminalInputProfile;
|
|
9
11
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type UpdateInstallSource = 'bun-global' | 'bunx' | 'npm-global' | 'npm-prefix' | 'npx' | 'pnpm-dlx' | 'pnpm-global' | 'source-checkout' | 'unknown' | 'yarn-dlx' | 'yarn-global';
|
|
2
|
+
export interface UpdateInstallPlan {
|
|
3
|
+
canRunHiveUpdate: boolean;
|
|
4
|
+
installArgs: string[];
|
|
5
|
+
installCommand: string;
|
|
6
|
+
installSource: UpdateInstallSource;
|
|
7
|
+
manualCommand: string;
|
|
8
|
+
note: string;
|
|
9
|
+
}
|
|
10
|
+
interface UpdateInstallPlanOptions {
|
|
11
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
12
|
+
moduleUrl?: string | undefined;
|
|
13
|
+
platform?: NodeJS.Platform | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare const formatUpdateCommand: (command: string, args: readonly string[], platform?: NodeJS.Platform) => string;
|
|
16
|
+
export declare const findPackageRoot: (moduleUrl: string) => string | undefined;
|
|
17
|
+
export declare const findPrefixFromPackageRoot: (packageRoot: string) => string | undefined;
|
|
18
|
+
export declare const resolveHiveUpdateInstallArgs: (moduleUrl?: string) => string[];
|
|
19
|
+
export declare const createUpdateInstallPlan: (options?: UpdateInstallPlanOptions) => UpdateInstallPlan;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { INSTALL_COMMAND_ARGS, PACKAGE_NAME } from './package-version.js';
|
|
5
|
+
const shellQuote = (value) => {
|
|
6
|
+
if (/^[A-Za-z0-9_/:=.,@+-]+$/.test(value))
|
|
7
|
+
return value;
|
|
8
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
9
|
+
};
|
|
10
|
+
const windowsQuote = (value) => {
|
|
11
|
+
if (/^[-A-Za-z0-9_/:=.,@+\\]+$/.test(value))
|
|
12
|
+
return value;
|
|
13
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
14
|
+
};
|
|
15
|
+
export const formatUpdateCommand = (command, args, platform = process.platform) => `${command} ${args.map(platform === 'win32' ? windowsQuote : shellQuote).join(' ')}`;
|
|
16
|
+
const normalizePath = (value) => value.replaceAll('\\', '/');
|
|
17
|
+
const includesPathSegment = (path, segment) => normalizePath(path).split('/').includes(segment);
|
|
18
|
+
export const findPackageRoot = (moduleUrl) => {
|
|
19
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
20
|
+
for (let depth = 0; depth < 12; depth += 1) {
|
|
21
|
+
const candidate = join(dir, 'package.json');
|
|
22
|
+
if (existsSync(candidate)) {
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
25
|
+
if (parsed.name === PACKAGE_NAME)
|
|
26
|
+
return dir;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Keep walking. A malformed unrelated package.json should not break update planning.
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const parent = dirname(dir);
|
|
33
|
+
if (parent === dir)
|
|
34
|
+
return undefined;
|
|
35
|
+
dir = parent;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
};
|
|
39
|
+
export const findPrefixFromPackageRoot = (packageRoot) => {
|
|
40
|
+
let dir = packageRoot;
|
|
41
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
42
|
+
if (basename(dir) === 'node_modules') {
|
|
43
|
+
const parent = dirname(dir);
|
|
44
|
+
return basename(parent) === 'lib' ? dirname(parent) : parent;
|
|
45
|
+
}
|
|
46
|
+
const parent = dirname(dir);
|
|
47
|
+
if (parent === dir)
|
|
48
|
+
return undefined;
|
|
49
|
+
dir = parent;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
};
|
|
53
|
+
export const resolveHiveUpdateInstallArgs = (moduleUrl = import.meta.url) => {
|
|
54
|
+
const packageRoot = findPackageRoot(moduleUrl);
|
|
55
|
+
const prefix = packageRoot ? findPrefixFromPackageRoot(packageRoot) : undefined;
|
|
56
|
+
if (!prefix)
|
|
57
|
+
return [...INSTALL_COMMAND_ARGS];
|
|
58
|
+
return [...INSTALL_COMMAND_ARGS, '--prefix', prefix];
|
|
59
|
+
};
|
|
60
|
+
const hasHint = (packageRoot, env, matcher) => {
|
|
61
|
+
const hints = [
|
|
62
|
+
packageRoot,
|
|
63
|
+
env._,
|
|
64
|
+
env.npm_command,
|
|
65
|
+
env.npm_config_user_agent,
|
|
66
|
+
env.npm_execpath,
|
|
67
|
+
env.npm_lifecycle_event,
|
|
68
|
+
];
|
|
69
|
+
return hints.some((value) => typeof value === 'string' && matcher(normalizePath(value)));
|
|
70
|
+
};
|
|
71
|
+
const hasEnvHint = (env, matcher) => {
|
|
72
|
+
const hints = [
|
|
73
|
+
env._,
|
|
74
|
+
env.npm_command,
|
|
75
|
+
env.npm_config_user_agent,
|
|
76
|
+
env.npm_execpath,
|
|
77
|
+
env.npm_lifecycle_event,
|
|
78
|
+
];
|
|
79
|
+
return hints.some((value) => typeof value === 'string' && matcher(normalizePath(value)));
|
|
80
|
+
};
|
|
81
|
+
const isBunGlobalHint = (value) => value.includes('/.bun/install/global/') ||
|
|
82
|
+
value.startsWith('bun/') ||
|
|
83
|
+
value.endsWith('/bun') ||
|
|
84
|
+
value.endsWith('/bun.exe');
|
|
85
|
+
const commandPlan = (source, command, args, note, platform) => ({
|
|
86
|
+
canRunHiveUpdate: false,
|
|
87
|
+
installArgs: [],
|
|
88
|
+
installCommand: formatUpdateCommand(command, args, platform),
|
|
89
|
+
installSource: source,
|
|
90
|
+
manualCommand: formatUpdateCommand(command, args, platform),
|
|
91
|
+
note,
|
|
92
|
+
});
|
|
93
|
+
export const createUpdateInstallPlan = (options = {}) => {
|
|
94
|
+
const env = options.env ?? process.env;
|
|
95
|
+
const moduleUrl = options.moduleUrl ?? import.meta.url;
|
|
96
|
+
const platform = options.platform ?? process.platform;
|
|
97
|
+
const packageRoot = findPackageRoot(moduleUrl);
|
|
98
|
+
const packageRootPath = packageRoot ? normalizePath(packageRoot) : '';
|
|
99
|
+
if (packageRootPath.includes('/_npx/')) {
|
|
100
|
+
return commandPlan('npx', 'npx', [`${PACKAGE_NAME}@latest`], 'This Hive process is running from an npx cache; re-run npx with @latest instead of installing a global copy.', platform);
|
|
101
|
+
}
|
|
102
|
+
if (hasHint(packageRoot, env, (value) => value.includes('bunx'))) {
|
|
103
|
+
return commandPlan('bunx', 'bunx', [`${PACKAGE_NAME}@latest`], 'This Hive process is running from bunx; re-run bunx with @latest instead of installing a global copy.', platform);
|
|
104
|
+
}
|
|
105
|
+
if (hasHint(packageRoot, env, (value) => (value.includes('pnpm') && value.includes('dlx')) || value.includes('/dlx-'))) {
|
|
106
|
+
return commandPlan('pnpm-dlx', 'pnpm', ['dlx', `${PACKAGE_NAME}@latest`], 'This Hive process is running from pnpm dlx; re-run pnpm dlx with @latest instead of installing a global copy.', platform);
|
|
107
|
+
}
|
|
108
|
+
if (hasHint(packageRoot, env, (value) => value.includes('yarn') && value.includes('dlx'))) {
|
|
109
|
+
return commandPlan('yarn-dlx', 'yarn', ['dlx', `${PACKAGE_NAME}@latest`], 'This Hive process is running from yarn dlx; re-run yarn dlx with @latest instead of installing a global copy.', platform);
|
|
110
|
+
}
|
|
111
|
+
const prefix = packageRoot ? findPrefixFromPackageRoot(packageRoot) : undefined;
|
|
112
|
+
if (!prefix && packageRoot && !includesPathSegment(packageRoot, 'node_modules')) {
|
|
113
|
+
return commandPlan('source-checkout', 'sh', ['-lc', 'git pull && pnpm install && pnpm build'], 'This Hive process is running from a source checkout; update the checkout and rebuild instead of installing a global copy.', platform);
|
|
114
|
+
}
|
|
115
|
+
if (packageRootPath.includes('/.bun/install/global/') ||
|
|
116
|
+
(!prefix && hasEnvHint(env, isBunGlobalHint))) {
|
|
117
|
+
return commandPlan('bun-global', 'bun', ['add', '-g', `${PACKAGE_NAME}@latest`], 'Hive appears to be installed through bun; update it with bun so the active binary changes.', platform);
|
|
118
|
+
}
|
|
119
|
+
if (packageRootPath.includes('/.pnpm/') ||
|
|
120
|
+
(!prefix && hasEnvHint(env, (value) => value.includes('pnpm')))) {
|
|
121
|
+
return commandPlan('pnpm-global', 'pnpm', ['add', '-g', `${PACKAGE_NAME}@latest`], 'Hive appears to be installed through pnpm; update it with pnpm so npm does not create a shadow global install.', platform);
|
|
122
|
+
}
|
|
123
|
+
if (packageRootPath.includes('/yarn/global/') ||
|
|
124
|
+
packageRootPath.includes('/.config/yarn/global/') ||
|
|
125
|
+
(!prefix &&
|
|
126
|
+
hasEnvHint(env, (value) => value.includes('/yarn/global/') || value.includes('/.config/yarn/global/')))) {
|
|
127
|
+
return commandPlan('yarn-global', 'yarn', ['global', 'add', `${PACKAGE_NAME}@latest`], 'Hive appears to be installed through yarn; update it with yarn so npm does not create a shadow global install.', platform);
|
|
128
|
+
}
|
|
129
|
+
const installArgs = resolveHiveUpdateInstallArgs(moduleUrl);
|
|
130
|
+
const manualCommand = formatUpdateCommand('npm', installArgs, platform);
|
|
131
|
+
if (!packageRoot) {
|
|
132
|
+
return {
|
|
133
|
+
canRunHiveUpdate: false,
|
|
134
|
+
installArgs: [],
|
|
135
|
+
installCommand: '',
|
|
136
|
+
installSource: 'unknown',
|
|
137
|
+
manualCommand: '',
|
|
138
|
+
note: 'Hive could not determine how this process was installed; update it with the same package manager and install target you originally used.',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
canRunHiveUpdate: true,
|
|
143
|
+
installArgs,
|
|
144
|
+
installCommand: 'hive update',
|
|
145
|
+
installSource: prefix ? 'npm-prefix' : 'npm-global',
|
|
146
|
+
manualCommand,
|
|
147
|
+
note: prefix
|
|
148
|
+
? 'Hive appears to be installed through npm with a custom prefix; hive update will update that same prefix.'
|
|
149
|
+
: 'Hive appears to be installed through npm; hive update will run the matching npm upgrade.',
|
|
150
|
+
};
|
|
151
|
+
};
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { type UpdateInstallPlan, type UpdateInstallSource } from './update-install-plan.js';
|
|
1
2
|
export interface VersionInfoPayload {
|
|
3
|
+
can_run_hive_update: boolean;
|
|
2
4
|
current_version: string;
|
|
3
5
|
install_hint: string;
|
|
6
|
+
install_source: UpdateInstallSource;
|
|
4
7
|
latest_version: string;
|
|
5
8
|
package_name: string;
|
|
6
9
|
release_url: string;
|
|
10
|
+
update_note: string;
|
|
7
11
|
update_available: boolean;
|
|
8
12
|
}
|
|
9
13
|
export interface VersionService {
|
|
@@ -11,6 +15,7 @@ export interface VersionService {
|
|
|
11
15
|
}
|
|
12
16
|
export declare const compareVersions: (left: string, right: string) => number;
|
|
13
17
|
export declare const createVersionService: (options?: {
|
|
18
|
+
createUpdateInstallPlan?: () => UpdateInstallPlan;
|
|
14
19
|
fetchLatestVersion?: () => Promise<string>;
|
|
15
20
|
now?: () => number;
|
|
16
21
|
}) => VersionService;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PACKAGE_NAME, readPackageVersion } from './package-version.js';
|
|
2
|
+
import { createUpdateInstallPlan, } from './update-install-plan.js';
|
|
2
3
|
const VERSION_CACHE_MS = 6 * 60 * 60 * 1000;
|
|
3
4
|
const REGISTRY_URL = 'https://registry.npmjs.org/@tt-a1i%2Fhive/latest';
|
|
4
5
|
const parseVersion = (version) => {
|
|
@@ -25,20 +26,23 @@ export const compareVersions = (left, right) => {
|
|
|
25
26
|
return -1;
|
|
26
27
|
return a.prerelease.localeCompare(b.prerelease, undefined, { numeric: true });
|
|
27
28
|
};
|
|
28
|
-
const buildVersionInfo = (currentVersion, latestVersion) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
const buildVersionInfo = (currentVersion, latestVersion, updatePlan) => {
|
|
30
|
+
return {
|
|
31
|
+
can_run_hive_update: updatePlan.canRunHiveUpdate,
|
|
32
|
+
current_version: currentVersion,
|
|
33
|
+
install_hint: updatePlan.installCommand,
|
|
34
|
+
install_source: updatePlan.installSource,
|
|
35
|
+
latest_version: latestVersion,
|
|
36
|
+
package_name: PACKAGE_NAME,
|
|
37
|
+
release_url: `https://www.npmjs.com/package/${PACKAGE_NAME}/v/${latestVersion}`,
|
|
38
|
+
update_available: compareVersions(latestVersion, currentVersion) > 0,
|
|
39
|
+
update_note: updatePlan.note,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
39
42
|
export const createVersionService = (options = {}) => {
|
|
40
43
|
const currentVersion = readPackageVersion();
|
|
41
44
|
const now = options.now ?? Date.now;
|
|
45
|
+
const planUpdateInstall = options.createUpdateInstallPlan ?? createUpdateInstallPlan;
|
|
42
46
|
const fetchLatestVersion = options.fetchLatestVersion ??
|
|
43
47
|
(async () => {
|
|
44
48
|
const response = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(1500) });
|
|
@@ -58,12 +62,12 @@ export const createVersionService = (options = {}) => {
|
|
|
58
62
|
return cached.info;
|
|
59
63
|
try {
|
|
60
64
|
const latestVersion = await fetchLatestVersion();
|
|
61
|
-
const info = buildVersionInfo(currentVersion, latestVersion);
|
|
65
|
+
const info = buildVersionInfo(currentVersion, latestVersion, planUpdateInstall());
|
|
62
66
|
cached = { expiresAt: currentTime + VERSION_CACHE_MS, info };
|
|
63
67
|
return info;
|
|
64
68
|
}
|
|
65
69
|
catch {
|
|
66
|
-
const info = buildVersionInfo(currentVersion, currentVersion);
|
|
70
|
+
const info = buildVersionInfo(currentVersion, currentVersion, planUpdateInstall());
|
|
67
71
|
cached = { expiresAt: currentTime + VERSION_CACHE_MS, info };
|
|
68
72
|
return info;
|
|
69
73
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { WorkerRole } from '../shared/types.js';
|
|
2
|
+
import type { AgentLaunchConfigInput } from './agent-run-store.js';
|
|
3
|
+
import { type WorkflowCliPolicy } from './workflow-cli-policy.js';
|
|
4
|
+
import type { WorkflowDispatchAwaiter } from './workflow-dispatch-awaiter.js';
|
|
5
|
+
export interface WorkflowAgentOptions {
|
|
6
|
+
label?: string;
|
|
7
|
+
/** Built-in role name OR the name of a workspace-defined custom role template. */
|
|
8
|
+
agentType?: WorkerRole | string;
|
|
9
|
+
cli?: string;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
/** Per-call model override, appended as `--model <id>` after template args. */
|
|
12
|
+
model?: string;
|
|
13
|
+
outputSchema?: Record<string, unknown>;
|
|
14
|
+
__hiveDagLayerId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface WorkflowRoleTemplateResolver {
|
|
17
|
+
findByName(name: string): {
|
|
18
|
+
name: string;
|
|
19
|
+
roleType: WorkerRole | 'orchestrator';
|
|
20
|
+
defaultCommand: string;
|
|
21
|
+
defaultArgs: string[];
|
|
22
|
+
} | undefined;
|
|
23
|
+
}
|
|
24
|
+
export interface WorkflowAgentExecutorStorePort {
|
|
25
|
+
addWorkerWithLaunch: (workspaceId: string, input: {
|
|
26
|
+
name: string;
|
|
27
|
+
role: WorkerRole;
|
|
28
|
+
ephemeral: true;
|
|
29
|
+
spawnedBy: 'workflow';
|
|
30
|
+
}, launchConfig: AgentLaunchConfigInput) => {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
};
|
|
34
|
+
startAgent: (workspaceId: string, agentId: string, input: {
|
|
35
|
+
hivePort: string;
|
|
36
|
+
}) => Promise<{
|
|
37
|
+
postStartInputReady?: Promise<void>;
|
|
38
|
+
}>;
|
|
39
|
+
dispatchTaskByWorkerName: (workspaceId: string, workerName: string, text: string, input: {
|
|
40
|
+
fromAgentId: string;
|
|
41
|
+
hivePort: string;
|
|
42
|
+
workflowRunId: string;
|
|
43
|
+
stepIndex: number;
|
|
44
|
+
phase?: string;
|
|
45
|
+
label?: string;
|
|
46
|
+
}) => Promise<{
|
|
47
|
+
id: string;
|
|
48
|
+
}>;
|
|
49
|
+
deleteWorker: (workspaceId: string, workerId: string) => void;
|
|
50
|
+
}
|
|
51
|
+
interface CreateWorkflowAgentCallExecutorInput {
|
|
52
|
+
assertRunActive: () => void;
|
|
53
|
+
awaiter: WorkflowDispatchAwaiter;
|
|
54
|
+
cancelOpenDispatch: (dispatchId: string, reason: string) => void;
|
|
55
|
+
cliPolicy: WorkflowCliPolicy;
|
|
56
|
+
getCurrentPhaseTitle: () => string | null;
|
|
57
|
+
hivePort: string;
|
|
58
|
+
isRunStopped: () => boolean;
|
|
59
|
+
maxAgentCalls?: number;
|
|
60
|
+
maxConcurrentAgents?: number;
|
|
61
|
+
registerDagDispatch: (layerId: string | null, dispatchId: string) => void;
|
|
62
|
+
resolveCliLaunchConfig: (cli: string) => AgentLaunchConfigInput | undefined;
|
|
63
|
+
roleTemplateResolver: WorkflowRoleTemplateResolver;
|
|
64
|
+
runId: string;
|
|
65
|
+
store: WorkflowAgentExecutorStorePort;
|
|
66
|
+
workflowAgentId: string;
|
|
67
|
+
workflowName: string;
|
|
68
|
+
workspaceId: string;
|
|
69
|
+
}
|
|
70
|
+
export declare const createWorkflowAgentCallExecutor: ({ assertRunActive, awaiter, cancelOpenDispatch, cliPolicy, getCurrentPhaseTitle, hivePort, isRunStopped, maxAgentCalls, maxConcurrentAgents: configuredMaxConcurrentAgents, registerDagDispatch, resolveCliLaunchConfig, roleTemplateResolver, runId, store, workflowAgentId, workflowName, workspaceId, }: CreateWorkflowAgentCallExecutorInput) => {
|
|
71
|
+
agent: (prompt: string, opts?: WorkflowAgentOptions) => Promise<string | Record<string, unknown>>;
|
|
72
|
+
cancelQueuedAgentCallsForDagLayer: (layerId: string, reason: string) => void;
|
|
73
|
+
catchPerItem: <U>(value: unknown) => U | null;
|
|
74
|
+
deleteSpawnedWorkers: () => void;
|
|
75
|
+
forceCancelActiveDispatchWaiters: (reason: string) => void;
|
|
76
|
+
waitForActiveCalls: (timeoutMs?: number) => Promise<{
|
|
77
|
+
settled: boolean;
|
|
78
|
+
activeCount: number;
|
|
79
|
+
}>;
|
|
80
|
+
};
|
|
81
|
+
export {};
|