@tt-a1i/hive 1.4.3 → 1.5.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 +44 -0
- package/README.en.md +5 -4
- package/README.md +9 -1
- package/assets/qq-group.jpg +0 -0
- package/dist/bin/team.cmd +1 -0
- package/dist/src/cli/hive-update.d.ts +57 -0
- package/dist/src/cli/hive-update.js +92 -15
- package/dist/src/cli/hive.d.ts +57 -0
- package/dist/src/cli/hive.js +113 -20
- package/dist/src/cli/team.d.ts +1 -0
- package/dist/src/cli/team.js +215 -7
- package/dist/src/server/agent-command-resolver.d.ts +10 -1
- package/dist/src/server/agent-command-resolver.js +32 -4
- package/dist/src/server/agent-launch-resolver.js +9 -3
- package/dist/src/server/agent-manager-support.d.ts +28 -0
- package/dist/src/server/agent-manager-support.js +138 -10
- package/dist/src/server/agent-run-bootstrap.d.ts +17 -1
- package/dist/src/server/agent-run-bootstrap.js +30 -2
- package/dist/src/server/agent-run-starter.d.ts +7 -1
- package/dist/src/server/agent-run-starter.js +9 -2
- package/dist/src/server/agent-run-store.d.ts +1 -1
- package/dist/src/server/agent-runtime-close.d.ts +1 -0
- package/dist/src/server/agent-runtime-close.js +25 -1
- package/dist/src/server/agent-runtime-contract.d.ts +2 -1
- package/dist/src/server/agent-runtime.d.ts +1 -1
- package/dist/src/server/agent-runtime.js +8 -2
- package/dist/src/server/agent-startup-instructions.d.ts +8 -1
- package/dist/src/server/agent-startup-instructions.js +15 -9
- package/dist/src/server/agent-stdin-dispatcher.d.ts +12 -5
- package/dist/src/server/agent-stdin-dispatcher.js +129 -40
- package/dist/src/server/app.d.ts +1 -0
- package/dist/src/server/app.js +12 -2
- package/dist/src/server/cron-util.d.ts +7 -0
- package/dist/src/server/cron-util.js +19 -0
- package/dist/src/server/dispatch-ledger-store.d.ts +22 -0
- package/dist/src/server/dispatch-ledger-store.js +51 -3
- package/dist/src/server/env-sync-message.js +9 -9
- package/dist/src/server/fs-browse.d.ts +14 -1
- package/dist/src/server/fs-browse.js +48 -5
- package/dist/src/server/fs-pick-folder.js +58 -11
- package/dist/src/server/fs-sandbox.js +36 -7
- package/dist/src/server/hive-team-guidance.d.ts +11 -6
- package/dist/src/server/hive-team-guidance.js +252 -70
- package/dist/src/server/live-run-registry.d.ts +1 -0
- package/dist/src/server/live-run-registry.js +1 -1
- package/dist/src/server/open-target-commands.js +29 -4
- package/dist/src/server/orchestrator-autostart.d.ts +12 -0
- package/dist/src/server/orchestrator-autostart.js +15 -13
- package/dist/src/server/path-canonicalization.d.ts +3 -0
- package/dist/src/server/path-canonicalization.js +29 -0
- package/dist/src/server/platform-path.d.ts +3 -0
- package/dist/src/server/platform-path.js +13 -0
- package/dist/src/server/post-start-input-writer.d.ts +1 -1
- package/dist/src/server/post-start-input-writer.js +116 -16
- package/dist/src/server/preset-launch-support.d.ts +1 -1
- package/dist/src/server/preset-launch-support.js +33 -2
- package/dist/src/server/recovery-summary.d.ts +6 -1
- package/dist/src/server/recovery-summary.js +17 -17
- package/dist/src/server/restart-policy-support.d.ts +6 -1
- package/dist/src/server/restart-policy-support.js +9 -1
- package/dist/src/server/restart-policy.d.ts +2 -2
- package/dist/src/server/restart-policy.js +3 -1
- package/dist/src/server/role-template-store.d.ts +1 -0
- package/dist/src/server/role-template-store.js +11 -1
- package/dist/src/server/route-types.d.ts +43 -0
- package/dist/src/server/routes-runtime.js +2 -1
- package/dist/src/server/routes-settings.js +76 -0
- package/dist/src/server/routes-team.js +221 -2
- package/dist/src/server/routes-workflow-schedules.d.ts +2 -0
- package/dist/src/server/routes-workflow-schedules.js +58 -0
- package/dist/src/server/routes-workflows.d.ts +2 -0
- package/dist/src/server/routes-workflows.js +83 -0
- package/dist/src/server/routes.js +4 -0
- package/dist/src/server/runtime-restart-policy.d.ts +3 -1
- package/dist/src/server/runtime-restart-policy.js +3 -1
- package/dist/src/server/runtime-store-contract.d.ts +122 -0
- package/dist/src/server/runtime-store-contract.js +1 -0
- package/dist/src/server/runtime-store-helpers.d.ts +9 -0
- package/dist/src/server/runtime-store-helpers.js +101 -2
- package/dist/src/server/runtime-store-workflows.d.ts +6 -0
- package/dist/src/server/runtime-store-workflows.js +100 -0
- package/dist/src/server/runtime-store.d.ts +3 -70
- package/dist/src/server/runtime-store.js +70 -4
- package/dist/src/server/session-capture-claude.d.ts +23 -0
- package/dist/src/server/session-capture-claude.js +24 -1
- package/dist/src/server/session-capture-codex.d.ts +3 -3
- package/dist/src/server/session-capture-codex.js +9 -7
- package/dist/src/server/session-capture-gemini.d.ts +1 -1
- package/dist/src/server/session-capture-gemini.js +6 -3
- package/dist/src/server/session-capture-opencode.d.ts +18 -0
- package/dist/src/server/session-capture-opencode.js +27 -2
- package/dist/src/server/settings-store.d.ts +3 -0
- package/dist/src/server/settings-store.js +1 -0
- package/dist/src/server/sqlite-schema-v19.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v19.js +17 -0
- package/dist/src/server/sqlite-schema-v20.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v20.js +20 -0
- package/dist/src/server/sqlite-schema-v21.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v21.js +20 -0
- package/dist/src/server/sqlite-schema.d.ts +1 -1
- package/dist/src/server/sqlite-schema.js +97 -1
- package/dist/src/server/startup-command-parser.d.ts +15 -0
- package/dist/src/server/startup-command-parser.js +33 -2
- package/dist/src/server/system-message.d.ts +7 -0
- package/dist/src/server/system-message.js +8 -1
- package/dist/src/server/tasks-file-watcher.d.ts +39 -1
- package/dist/src/server/tasks-file-watcher.js +155 -25
- package/dist/src/server/tasks-file.d.ts +2 -1
- package/dist/src/server/tasks-file.js +32 -9
- package/dist/src/server/tasks-websocket-server.js +13 -14
- package/dist/src/server/team-authz.d.ts +1 -1
- package/dist/src/server/team-authz.js +9 -1
- package/dist/src/server/team-autostaff.d.ts +16 -0
- package/dist/src/server/team-autostaff.js +16 -0
- package/dist/src/server/team-list-serializer.d.ts +1 -1
- package/dist/src/server/team-list-serializer.js +3 -1
- package/dist/src/server/team-operations.d.ts +20 -2
- package/dist/src/server/team-operations.js +160 -14
- package/dist/src/server/terminal-input-profile.js +2 -8
- package/dist/src/server/terminal-protocol.js +9 -3
- package/dist/src/server/terminal-stream-hub.js +16 -10
- package/dist/src/server/terminal-ws-server.js +36 -16
- package/dist/src/server/websocket-upgrade-safety.d.ts +10 -0
- package/dist/src/server/websocket-upgrade-safety.js +35 -0
- package/dist/src/server/windows-command-line.d.ts +3 -0
- package/dist/src/server/windows-command-line.js +9 -0
- package/dist/src/server/windows-filename.d.ts +2 -0
- package/dist/src/server/windows-filename.js +33 -0
- package/dist/src/server/workflow-cli-policy.d.ts +60 -0
- package/dist/src/server/workflow-cli-policy.js +110 -0
- package/dist/src/server/workflow-dispatch-awaiter.d.ts +12 -0
- package/dist/src/server/workflow-dispatch-awaiter.js +80 -0
- package/dist/src/server/workflow-feature.d.ts +15 -0
- package/dist/src/server/workflow-feature.js +15 -0
- package/dist/src/server/workflow-http-serializers.d.ts +64 -0
- package/dist/src/server/workflow-http-serializers.js +58 -0
- package/dist/src/server/workflow-run-log-store.d.ts +19 -0
- package/dist/src/server/workflow-run-log-store.js +45 -0
- package/dist/src/server/workflow-run-store.d.ts +50 -0
- package/dist/src/server/workflow-run-store.js +103 -0
- package/dist/src/server/workflow-runner.d.ts +147 -0
- package/dist/src/server/workflow-runner.js +401 -0
- package/dist/src/server/workflow-schedule-create.d.ts +14 -0
- package/dist/src/server/workflow-schedule-create.js +41 -0
- package/dist/src/server/workflow-schedule-store.d.ts +43 -0
- package/dist/src/server/workflow-schedule-store.js +112 -0
- package/dist/src/server/workflow-scheduler.d.ts +36 -0
- package/dist/src/server/workflow-scheduler.js +97 -0
- package/dist/src/server/workflow-script-loader.d.ts +34 -0
- package/dist/src/server/workflow-script-loader.js +106 -0
- package/dist/src/server/workspace-path-validation.js +16 -4
- package/dist/src/server/workspace-shell-runtime.d.ts +5 -0
- package/dist/src/server/workspace-shell-runtime.js +24 -2
- package/dist/src/server/workspace-store-contract.d.ts +4 -1
- package/dist/src/server/workspace-store-hydration.js +23 -7
- package/dist/src/server/workspace-store-mutations.js +2 -5
- package/dist/src/server/workspace-store-support.d.ts +4 -0
- package/dist/src/server/workspace-store-support.js +13 -1
- package/dist/src/server/workspace-store.js +38 -4
- package/dist/src/shared/types.d.ts +16 -1
- package/package.json +4 -2
- package/web/dist/assets/{AddWorkerDialog-DmkDOdp6.js → AddWorkerDialog-CcC-7kgG.js} +2 -2
- package/web/dist/assets/AddWorkspaceDialog-BDpOTfmt.js +1 -0
- package/web/dist/assets/{FirstRunWizard-SAd1wsH4.js → FirstRunWizard-BYX_ocQn.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-B_8aG2uT.js → MarketplaceDrawer-DUxSk7db.js} +1 -1
- package/web/dist/assets/WhatsNewDialog-B_RlCXcV.js +1 -0
- package/web/dist/assets/WorkerModal-D9-7YfZZ.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-BCKoF7qc.js +1 -0
- package/web/dist/assets/{WorkspaceTerminalPanels-BReWh1YL.js → WorkspaceTerminalPanels-Dq8y91t2.js} +1 -1
- package/web/dist/assets/index-BiOvKIVw.css +1 -0
- package/web/dist/assets/index-DMRUklT3.js +73 -0
- package/web/dist/assets/path-join-7MR1s7b1.js +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/AddWorkspaceDialog-BsVnH3Xe.js +0 -1
- package/web/dist/assets/WorkerModal-CQmjiPme.js +0 -1
- package/web/dist/assets/WorkspaceTaskDrawer-B0DmCWcV.js +0 -1
- package/web/dist/assets/chevron-right-CtLjVEl7.js +0 -1
- package/web/dist/assets/index-BEsTmfrO.css +0 -1
- package/web/dist/assets/index-Cn8X3get.js +0 -76
|
@@ -9,38 +9,61 @@ import { buildRuntimeRestartPolicy } from './runtime-restart-policy.js';
|
|
|
9
9
|
import { createSettingsStore } from './settings-store.js';
|
|
10
10
|
import { createTasksFileService } from './tasks-file.js';
|
|
11
11
|
import { createTasksFileWatcher } from './tasks-file-watcher.js';
|
|
12
|
+
import { AUTOSTAFF_ENABLED_KEY, readAutostaffEnabled } from './team-autostaff.js';
|
|
12
13
|
import { createTeamOperations } from './team-operations.js';
|
|
13
14
|
import { resolveTerminalInputProfile } from './terminal-input-profile.js';
|
|
14
15
|
import { createUiAuth } from './ui-auth.js';
|
|
15
16
|
import { createWorkerOutputTracker } from './worker-output-tracker.js';
|
|
17
|
+
import { readWorkflowCliPolicy, WORKFLOW_CLI_POLICY_KEY } from './workflow-cli-policy.js';
|
|
18
|
+
import { createWorkflowDispatchAwaiter, } from './workflow-dispatch-awaiter.js';
|
|
19
|
+
import { readWorkflowEnabled, WORKFLOW_ENABLED_KEY } from './workflow-feature.js';
|
|
20
|
+
import { createWorkflowRunLogStore } from './workflow-run-log-store.js';
|
|
21
|
+
import { createWorkflowRunStore } from './workflow-run-store.js';
|
|
22
|
+
import { createWorkflowScheduleStore } from './workflow-schedule-store.js';
|
|
16
23
|
import { createWorkspaceShellRuntime } from './workspace-shell-runtime.js';
|
|
17
24
|
import { createWorkspaceStore } from './workspace-store.js';
|
|
25
|
+
import { getOrchestratorId } from './workspace-store-support.js';
|
|
18
26
|
const notifyTasksUpdated = (callbacks, workspaceId, content) => {
|
|
19
27
|
for (const callback of callbacks) {
|
|
20
28
|
callback(workspaceId, content);
|
|
21
29
|
}
|
|
22
30
|
};
|
|
31
|
+
export const logTasksFileWatchStartError = (workspaceId, error) => {
|
|
32
|
+
console.error(`[hive] failed to start tasks watcher for workspace ${workspaceId}`, error);
|
|
33
|
+
};
|
|
23
34
|
export const createRuntimeStoreServices = (options = {}) => {
|
|
24
35
|
const db = openRuntimeDatabase(options.dataDir);
|
|
25
36
|
const messageLogStore = createMessageLogStore(db);
|
|
26
37
|
const dispatchLedgerStore = createDispatchLedgerStore(db);
|
|
38
|
+
const workflowDispatchAwaiter = createWorkflowDispatchAwaiter();
|
|
39
|
+
const workflowRunStore = createWorkflowRunStore(db);
|
|
40
|
+
const workflowRunLogStore = createWorkflowRunLogStore(db);
|
|
41
|
+
const workflowScheduleStore = createWorkflowScheduleStore(db);
|
|
27
42
|
const agentRunStore = createAgentRunStore(db);
|
|
28
43
|
const agentSessionStore = createAgentSessionStore(db);
|
|
29
44
|
const settings = createSettingsStore(db);
|
|
45
|
+
const getWorkflowsEnabled = () => readWorkflowEnabled(settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null);
|
|
46
|
+
const getAutostaffEnabled = () => readAutostaffEnabled(settings.getAppState(AUTOSTAFF_ENABLED_KEY)?.value ?? null);
|
|
30
47
|
const tasksFileService = createTasksFileService();
|
|
31
48
|
const tasksFileWatchCallbacks = new Set();
|
|
32
49
|
const tasksFileWatcher = createTasksFileWatcher({
|
|
33
50
|
onTasksUpdated: (workspaceId, content) => {
|
|
34
51
|
notifyTasksUpdated(tasksFileWatchCallbacks, workspaceId, content);
|
|
35
52
|
},
|
|
53
|
+
getWorkflowCliPolicy: () => readWorkflowCliPolicy(settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null),
|
|
54
|
+
getWorkflowsEnabled,
|
|
55
|
+
getAutostaffEnabled,
|
|
36
56
|
});
|
|
37
57
|
const uiAuth = createUiAuth();
|
|
38
58
|
const shellRuntime = createWorkspaceShellRuntime(options.agentManager);
|
|
39
59
|
agentRunStore.markUnfinishedRunsStale();
|
|
60
|
+
workflowRunStore.markUnfinishedRunsInterrupted();
|
|
40
61
|
const workspaceStore = createWorkspaceStore(db, dispatchLedgerStore.listOpenDispatchKinds());
|
|
41
62
|
const startExistingWorkspaceWatches = () => {
|
|
42
63
|
for (const workspace of workspaceStore.listWorkspaces()) {
|
|
43
|
-
void tasksFileWatcher
|
|
64
|
+
void tasksFileWatcher
|
|
65
|
+
.start(workspace.id, workspace.path)
|
|
66
|
+
.catch((error) => logTasksFileWatchStartError(workspace.id, error));
|
|
44
67
|
}
|
|
45
68
|
};
|
|
46
69
|
const restartPolicy = buildRuntimeRestartPolicy({
|
|
@@ -48,6 +71,8 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
48
71
|
messageLogStore,
|
|
49
72
|
tasksFileService,
|
|
50
73
|
workspaceStore,
|
|
74
|
+
getWorkflowsEnabled,
|
|
75
|
+
getAutostaffEnabled,
|
|
51
76
|
});
|
|
52
77
|
const workerOutputTracker = options.agentManager
|
|
53
78
|
? createWorkerOutputTracker(options.agentManager.getOutputBus())
|
|
@@ -57,7 +82,70 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
57
82
|
if (!workspaceStore.hasAgent(workspaceId, agentId))
|
|
58
83
|
return;
|
|
59
84
|
workspaceStore.markAgentStopped(workspaceId, agentId);
|
|
60
|
-
|
|
85
|
+
// TIER 1 #1 — if the exiting worker had any open workflow dispatches,
|
|
86
|
+
// tell the workflow awaiter so the runner's `awaitReport` rejects
|
|
87
|
+
// immediately instead of hanging until DEFAULT_TIMEOUT_MS (10 min).
|
|
88
|
+
// This is the only signal the runtime has that an ephemeral
|
|
89
|
+
// workflow worker died without calling `team report`.
|
|
90
|
+
const openWorkflowDispatches = dispatchLedgerStore.listOpenWorkflowDispatchesForWorker(workspaceId, agentId);
|
|
91
|
+
for (const { dispatchId } of openWorkflowDispatches) {
|
|
92
|
+
try {
|
|
93
|
+
const cancelled = dispatchLedgerStore.markCancelled({
|
|
94
|
+
dispatchId,
|
|
95
|
+
reason: 'worker PTY exited before report',
|
|
96
|
+
workspaceId,
|
|
97
|
+
});
|
|
98
|
+
if (!cancelled)
|
|
99
|
+
continue;
|
|
100
|
+
try {
|
|
101
|
+
workspaceStore.markTaskCancelled(workspaceId, agentId);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error('[hive] onAgentExit.markTaskCancelled failed', dispatchId, error);
|
|
105
|
+
}
|
|
106
|
+
workflowDispatchAwaiter.notifyCancel(dispatchId, 'worker PTY exited before report');
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error('[hive] onAgentExit.markCancelled failed', dispatchId, error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Cascade: when the orchestrator's PTY exits, dismiss the ephemeral
|
|
113
|
+
// workers it spawned via `team spawn` (spec §6.3). Workflow-spawned
|
|
114
|
+
// workers are owned by the runner, not the orchestrator.
|
|
115
|
+
if (agentId === getOrchestratorId(workspaceId)) {
|
|
116
|
+
const children = workspaceStore
|
|
117
|
+
.getWorkspaceSnapshot(workspaceId)
|
|
118
|
+
.agents.filter((agent) => agent.ephemeral === true && agent.spawnedBy === 'orchestrator');
|
|
119
|
+
for (const child of children)
|
|
120
|
+
removeWorkerCompletely(workspaceId, child.id);
|
|
121
|
+
}
|
|
122
|
+
}, restartPolicy, (workspaceId, agentId) => workspaceStore.getAgent(workspaceId, agentId), getWorkflowsEnabled, getAutostaffEnabled);
|
|
123
|
+
// Mirrors runtime-store.deleteWorker (stop run → drop launch config → drop
|
|
124
|
+
// dispatches → drop worker row, transactionally). Hoisted `function` so the
|
|
125
|
+
// onAgentExit closure above can reference it; only invoked at runtime, after
|
|
126
|
+
// agentRuntime is assigned.
|
|
127
|
+
function removeWorkerCompletely(workspaceId, workerId) {
|
|
128
|
+
const activeRun = agentRuntime.getActiveRunByAgentId(workspaceId, workerId);
|
|
129
|
+
if (activeRun)
|
|
130
|
+
agentRuntime.stopAgentRun(activeRun.runId);
|
|
131
|
+
agentRuntime.deleteAgentLaunchConfig(workspaceId, workerId);
|
|
132
|
+
db.transaction(() => {
|
|
133
|
+
dispatchLedgerStore.deleteWorkerDispatches(workspaceId, workerId);
|
|
134
|
+
workspaceStore.deleteWorker(workspaceId, workerId);
|
|
135
|
+
})();
|
|
136
|
+
}
|
|
137
|
+
// Boot cleanup: after a runtime restart every ephemeral worker is an orphan
|
|
138
|
+
// (its spawner — a workflow run or an orchestrator PTY — is gone). Remove
|
|
139
|
+
// them so they don't accumulate (spec §6.3).
|
|
140
|
+
const cleanupOrphanEphemeralWorkers = () => {
|
|
141
|
+
for (const workspace of workspaceStore.listWorkspaces()) {
|
|
142
|
+
const ephemeral = workspaceStore
|
|
143
|
+
.getWorkspaceSnapshot(workspace.id)
|
|
144
|
+
.agents.filter((agent) => agent.ephemeral === true);
|
|
145
|
+
for (const agent of ephemeral)
|
|
146
|
+
removeWorkerCompletely(workspace.id, agent.id);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
61
149
|
const teamOps = createTeamOperations({
|
|
62
150
|
agentRuntime,
|
|
63
151
|
createDispatch: dispatchLedgerStore.createDispatch,
|
|
@@ -69,8 +157,11 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
69
157
|
markDispatchCancelled: dispatchLedgerStore.markCancelled,
|
|
70
158
|
markDispatchReportedByWorker: dispatchLedgerStore.markReportedByWorker,
|
|
71
159
|
markDispatchSubmitted: dispatchLedgerStore.markSubmitted,
|
|
160
|
+
workflowDispatchAwaiter,
|
|
72
161
|
workspaceStore,
|
|
162
|
+
dismissEphemeralWorker: (workspaceId, workerId) => removeWorkerCompletely(workspaceId, workerId),
|
|
73
163
|
});
|
|
164
|
+
cleanupOrphanEphemeralWorkers();
|
|
74
165
|
startExistingWorkspaceWatches();
|
|
75
166
|
return {
|
|
76
167
|
agentRunStore,
|
|
@@ -86,6 +177,10 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
86
177
|
teamOps,
|
|
87
178
|
uiAuth,
|
|
88
179
|
workerOutputTracker,
|
|
180
|
+
workflowDispatchAwaiter,
|
|
181
|
+
workflowRunLogStore,
|
|
182
|
+
workflowRunStore,
|
|
183
|
+
workflowScheduleStore,
|
|
89
184
|
workspaceStore,
|
|
90
185
|
};
|
|
91
186
|
};
|
|
@@ -143,6 +238,10 @@ export const createRuntimeStoreLifecycle = ({ agentManager, services, }) => {
|
|
|
143
238
|
};
|
|
144
239
|
return {
|
|
145
240
|
close: async () => {
|
|
241
|
+
// Fail in-flight workflow awaiters BEFORE their workers vanish, so the
|
|
242
|
+
// runner's `await agent(...)` rejects with a clear shutdown error
|
|
243
|
+
// instead of hanging on a Promise that can never resolve.
|
|
244
|
+
services.workflowDispatchAwaiter.cancelAll('runtime closing');
|
|
146
245
|
services.shellRuntime.close();
|
|
147
246
|
await services.agentRuntime.close();
|
|
148
247
|
await services.tasksFileWatcher.close();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RuntimeStore } from './runtime-store-contract.js';
|
|
2
|
+
import type { RuntimeStoreServices } from './runtime-store-helpers.js';
|
|
3
|
+
export declare const createRuntimeStoreWorkflowRuntime: (services: RuntimeStoreServices, store: RuntimeStore) => {
|
|
4
|
+
runner: import("./workflow-runner.js").WorkflowRunner;
|
|
5
|
+
scheduler: import("./workflow-scheduler.js").WorkflowScheduler;
|
|
6
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { readWorkflowCliPolicy, WORKFLOW_CLI_POLICY_KEY } from './workflow-cli-policy.js';
|
|
2
|
+
import { readWorkflowEnabled, WORKFLOW_ENABLED_KEY } from './workflow-feature.js';
|
|
3
|
+
import { createWorkflowRunner } from './workflow-runner.js';
|
|
4
|
+
import { createWorkflowScheduler } from './workflow-scheduler.js';
|
|
5
|
+
export const createRuntimeStoreWorkflowRuntime = (services, store) => {
|
|
6
|
+
const runner = createWorkflowRunner({
|
|
7
|
+
awaiter: services.workflowDispatchAwaiter,
|
|
8
|
+
workflowRunStore: services.workflowRunStore,
|
|
9
|
+
dispatchPort: {
|
|
10
|
+
listOpenDispatchIdsForRun: (runId) => services.dispatchLedgerStore.listOpenDispatchIdsForRun(runId),
|
|
11
|
+
},
|
|
12
|
+
resolveWorkspacePath: (workspaceId) => services.workspaceStore.getWorkspaceSnapshot(workspaceId).summary.path,
|
|
13
|
+
logStore: {
|
|
14
|
+
append: (runId, message, ts) => services.workflowRunLogStore.append(runId, message, ts),
|
|
15
|
+
},
|
|
16
|
+
getWorkflowCliPolicy: () => readWorkflowCliPolicy(services.settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null),
|
|
17
|
+
roleTemplateResolver: {
|
|
18
|
+
findByName: (name) => {
|
|
19
|
+
const t = services.settings.findRoleTemplateByName(name);
|
|
20
|
+
if (!t)
|
|
21
|
+
return undefined;
|
|
22
|
+
return {
|
|
23
|
+
name: t.name,
|
|
24
|
+
roleType: t.roleType,
|
|
25
|
+
defaultCommand: t.defaultCommand,
|
|
26
|
+
defaultArgs: t.defaultArgs,
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
onRunFinished: ({ runId, triggeredByAgentId, finalRecord }) => {
|
|
31
|
+
const workspaceId = finalRecord.workspaceId;
|
|
32
|
+
const dispatches = services.dispatchLedgerStore.listWorkflowRunDispatches(runId);
|
|
33
|
+
const errorLine = finalRecord.error ? `\nerror: ${finalRecord.error}` : '';
|
|
34
|
+
const dispatchSummary = dispatches.length === 0
|
|
35
|
+
? '\n(no agent() calls in this run)'
|
|
36
|
+
: '\n' +
|
|
37
|
+
dispatches
|
|
38
|
+
.map((d) => {
|
|
39
|
+
const reply = d.reportText
|
|
40
|
+
? d.reportText.length > 200
|
|
41
|
+
? `${d.reportText.slice(0, 197).trim()}...`
|
|
42
|
+
: d.reportText.trim()
|
|
43
|
+
: `(no report; status=${d.status})`;
|
|
44
|
+
return ` #${d.stepIndex ?? '?'} -> ${reply}`;
|
|
45
|
+
})
|
|
46
|
+
.join('\n');
|
|
47
|
+
const resultBlock = (() => {
|
|
48
|
+
if (finalRecord.result === null || finalRecord.result === undefined)
|
|
49
|
+
return '';
|
|
50
|
+
const serialized = typeof finalRecord.result === 'string'
|
|
51
|
+
? finalRecord.result
|
|
52
|
+
: JSON.stringify(finalRecord.result, null, 2);
|
|
53
|
+
const truncated = serialized.length > 4000
|
|
54
|
+
? `${serialized.slice(0, 4000)}\n...(truncated; ${serialized.length - 4000} more chars)`
|
|
55
|
+
: serialized;
|
|
56
|
+
return `\nResult (workflow return value):\n${truncated}`;
|
|
57
|
+
})();
|
|
58
|
+
const logTail = services.workflowRunLogStore.tailForRun(runId, 8);
|
|
59
|
+
const logBlock = logTail.length === 0
|
|
60
|
+
? ''
|
|
61
|
+
: `\nNarrator (last ${logTail.length} log line${logTail.length === 1 ? '' : 's'}):\n` +
|
|
62
|
+
logTail.map((line) => ` - ${line}`).join('\n');
|
|
63
|
+
const payload = '<hive-system-reminder>\n' +
|
|
64
|
+
`Hive workflow \`${finalRecord.name}\` finished: status=${finalRecord.status}` +
|
|
65
|
+
` (run_id=${runId}, ${dispatches.length} agent call${dispatches.length === 1 ? '' : 's'}).` +
|
|
66
|
+
errorLine +
|
|
67
|
+
resultBlock +
|
|
68
|
+
logBlock +
|
|
69
|
+
dispatchSummary +
|
|
70
|
+
'\nThis result is what you should synthesize back to the user. ' +
|
|
71
|
+
'Per-agent transcripts are available via `team workflow show ' +
|
|
72
|
+
runId +
|
|
73
|
+
'` if needed.\n' +
|
|
74
|
+
'</hive-system-reminder>\n';
|
|
75
|
+
try {
|
|
76
|
+
services.agentRuntime.writeSystemMessageToAgent(workspaceId, triggeredByAgentId, payload);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error('[hive] workflow.notifyTrigger failed', error);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
store: {
|
|
83
|
+
addWorkerWithLaunch: (ws, input, launch) => store.addWorkerWithLaunch(ws, input, launch),
|
|
84
|
+
deleteWorker: (ws, workerId) => store.deleteWorker(ws, workerId),
|
|
85
|
+
dispatchTaskByWorkerName: async (ws, name, text, input) => {
|
|
86
|
+
const dispatch = await store.dispatchTaskByWorkerName(ws, name, text, input);
|
|
87
|
+
return { id: dispatch.id };
|
|
88
|
+
},
|
|
89
|
+
startAgent: (ws, agentId, input) => store.startAgent(ws, agentId, input),
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
const scheduler = createWorkflowScheduler({
|
|
93
|
+
schedules: services.workflowScheduleStore,
|
|
94
|
+
startWorkflow: (input) => store.startWorkflow(input),
|
|
95
|
+
workspaceExists: (workspaceId) => services.workspaceStore.hasWorkspace(workspaceId),
|
|
96
|
+
isWorkflowEnabled: () => readWorkflowEnabled(services.settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null),
|
|
97
|
+
});
|
|
98
|
+
scheduler.start();
|
|
99
|
+
return { runner, scheduler };
|
|
100
|
+
};
|
|
@@ -1,71 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
import type { LiveAgentRun } from './agent-runtime-types.js';
|
|
5
|
-
import type { DispatchRecord, ListDispatchesOptions } from './dispatch-ledger-store.js';
|
|
6
|
-
import type { RecoveryMessage } from './message-log-store.js';
|
|
7
|
-
import type { PtyOutputBus } from './pty-output-bus.js';
|
|
8
|
-
import type { SettingsStore } from './settings-store.js';
|
|
9
|
-
import type { CancelTaskInput, DispatchTaskInput, ReportTaskInput, ReportTaskResult, StatusTaskInput } from './team-operations.js';
|
|
10
|
-
import type { TerminalRunSummary } from './terminal-input-profile.js';
|
|
11
|
-
import type { WorkerInput, WorkspaceRecord } from './workspace-store.js';
|
|
12
|
-
interface RuntimeStore {
|
|
13
|
-
close: () => Promise<void>;
|
|
14
|
-
createWorkspace: (path: string, name: string) => WorkspaceSummary;
|
|
15
|
-
deleteWorkspace: (workspaceId: string) => Promise<void>;
|
|
16
|
-
listWorkspaces: () => WorkspaceSummary[];
|
|
17
|
-
addWorker: (workspaceId: string, input: WorkerInput) => AgentSummary;
|
|
18
|
-
deleteWorker: (workspaceId: string, workerId: string) => void;
|
|
19
|
-
renameWorker: (workspaceId: string, workerId: string, name: string) => AgentSummary;
|
|
20
|
-
recordUserInput: (workspaceId: string, orchestratorId: string, text: string) => void;
|
|
21
|
-
dispatchTask: (workspaceId: string, workerId: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
|
|
22
|
-
dispatchTaskByWorkerName: (workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
|
|
23
|
-
reportTask: (workspaceId: string, workerId: string, input?: ReportTaskInput) => ReportTaskResult;
|
|
24
|
-
statusTask: (workspaceId: string, workerId: string, input?: StatusTaskInput) => ReportTaskResult;
|
|
25
|
-
cancelTask: (workspaceId: string, dispatchId: string, input: CancelTaskInput) => ReportTaskResult;
|
|
26
|
-
listDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
|
|
27
|
-
listWorkers: (workspaceId: string) => TeamListItem[];
|
|
28
|
-
getLastPtyLineForAgent: (workspaceId: string, agentId: string) => string | null;
|
|
29
|
-
getWorkspaceSnapshot: (workspaceId: string) => WorkspaceRecord;
|
|
30
|
-
getWorker: (workspaceId: string, workerId: string) => AgentSummary;
|
|
31
|
-
getAgent: (workspaceId: string, agentId: string) => AgentSummary;
|
|
32
|
-
getPtyOutputBus: () => PtyOutputBus;
|
|
33
|
-
listTerminalRuns: (workspaceId: string) => TerminalRunSummary[];
|
|
34
|
-
closeWorkspaceShell: (workspaceId: string, runId: string) => boolean;
|
|
35
|
-
startWorkspaceShell: (workspaceId: string) => Promise<LiveAgentRun>;
|
|
36
|
-
configureAgentLaunch: (workspaceId: string, agentId: string, input: AgentLaunchConfigInput) => void;
|
|
37
|
-
peekAgentLaunchConfig: (workspaceId: string, agentId: string) => AgentLaunchConfigInput | undefined;
|
|
38
|
-
startAgent: (workspaceId: string, agentId: string, input: StartAgentOptions) => Promise<LiveAgentRun>;
|
|
39
|
-
autostartConfiguredAgents: (input: StartAgentOptions) => Promise<Array<{
|
|
40
|
-
agent_id: string;
|
|
41
|
-
error: string | null;
|
|
42
|
-
ok: boolean;
|
|
43
|
-
run_id: string | null;
|
|
44
|
-
workspace_id: string;
|
|
45
|
-
}>>;
|
|
46
|
-
startWorkspaceWatch: (workspaceId: string) => Promise<void>;
|
|
47
|
-
getLiveRun: (runId: string) => LiveAgentRun;
|
|
48
|
-
getActiveRunByAgentId: (workspaceId: string, agentId: string) => LiveAgentRun | undefined;
|
|
49
|
-
registerTasksListener: (listener: (workspaceId: string, content: string) => void) => () => void;
|
|
50
|
-
listAgentRuns: (agentId: string) => PersistedAgentRun[];
|
|
51
|
-
listMessagesForRecovery: (workspaceId: string, sinceMs: number) => RecoveryMessage[];
|
|
52
|
-
peekAgentToken: (agentId: string) => string | undefined;
|
|
53
|
-
pauseTerminalRun: (runId: string) => void;
|
|
54
|
-
resizeAgentRun: (runId: string, cols: number, rows: number) => void;
|
|
55
|
-
resumeTerminalRun: (runId: string) => void;
|
|
56
|
-
settings: SettingsStore;
|
|
57
|
-
writeRunInput: (runId: string, input: Buffer | string) => void;
|
|
58
|
-
getUiToken: () => string;
|
|
59
|
-
stopAgentRun: (runId: string) => void;
|
|
60
|
-
validateAgentToken: (agentId: string, token: string | undefined) => boolean;
|
|
61
|
-
validateUiToken: (token: string | undefined) => boolean;
|
|
62
|
-
}
|
|
63
|
-
interface RuntimeStoreOptions {
|
|
64
|
-
dataDir?: string;
|
|
65
|
-
agentManager?: AgentManager;
|
|
66
|
-
}
|
|
67
|
-
interface StartAgentOptions {
|
|
68
|
-
hivePort: string;
|
|
69
|
-
}
|
|
70
|
-
export type { RuntimeStore };
|
|
1
|
+
import type { RuntimeStore, RuntimeStoreOptions } from './runtime-store-contract.js';
|
|
2
|
+
import type { WorkflowRunRecord } from './workflow-run-store.js';
|
|
3
|
+
export type { RuntimeStore, WorkflowRunRecord };
|
|
71
4
|
export declare const createRuntimeStore: (options?: RuntimeStoreOptions) => RuntimeStore;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { createRuntimeStoreLifecycle, createRuntimeStoreServices } from './runtime-store-helpers.js';
|
|
1
|
+
import { createRuntimeStoreLifecycle, createRuntimeStoreServices, logTasksFileWatchStartError, } from './runtime-store-helpers.js';
|
|
2
|
+
import { createRuntimeStoreWorkflowRuntime } from './runtime-store-workflows.js';
|
|
3
|
+
import { persistWorkflowSchedule } from './workflow-schedule-create.js';
|
|
2
4
|
export const createRuntimeStore = (options = {}) => {
|
|
3
5
|
const services = createRuntimeStoreServices(options);
|
|
4
6
|
const lifecycle = createRuntimeStoreLifecycle(options.agentManager ? { agentManager: options.agentManager, services } : { services });
|
|
@@ -9,11 +11,22 @@ export const createRuntimeStore = (options = {}) => {
|
|
|
9
11
|
}
|
|
10
12
|
services.db.transaction(mutation)();
|
|
11
13
|
};
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
let workflowRuntime;
|
|
15
|
+
const getWorkflowRuntime = () => {
|
|
16
|
+
if (!workflowRuntime)
|
|
17
|
+
throw new Error('Workflow runtime not initialized');
|
|
18
|
+
return workflowRuntime;
|
|
19
|
+
};
|
|
20
|
+
const store = {
|
|
21
|
+
close: async () => {
|
|
22
|
+
workflowRuntime?.scheduler.close();
|
|
23
|
+
await lifecycle.close();
|
|
24
|
+
},
|
|
14
25
|
createWorkspace: (path, name) => {
|
|
15
26
|
const workspace = services.workspaceStore.createWorkspace(path, name);
|
|
16
|
-
void lifecycle
|
|
27
|
+
void lifecycle
|
|
28
|
+
.startWorkspaceWatch(workspace.id)
|
|
29
|
+
.catch((error) => logTasksFileWatchStartError(workspace.id, error));
|
|
17
30
|
return workspace;
|
|
18
31
|
},
|
|
19
32
|
listWorkspaces: () => services.workspaceStore.listWorkspaces(),
|
|
@@ -36,6 +49,34 @@ export const createRuntimeStore = (options = {}) => {
|
|
|
36
49
|
}
|
|
37
50
|
},
|
|
38
51
|
addWorker: (workspaceId, input) => services.workspaceStore.addWorker(workspaceId, input),
|
|
52
|
+
addWorkerWithLaunch: (workspaceId, input, launchConfig) => {
|
|
53
|
+
// Atomic spawn: create the worker AND its launch config together so a
|
|
54
|
+
// failure can never persist a worker with no way to start it. The DB
|
|
55
|
+
// transaction rolls back the row; the catch prunes the in-memory worker
|
|
56
|
+
// that addWorker already pushed into the workspace record.
|
|
57
|
+
let worker;
|
|
58
|
+
try {
|
|
59
|
+
runDataMutation(() => {
|
|
60
|
+
worker = store.addWorker(workspaceId, input);
|
|
61
|
+
store.configureAgentLaunch(workspaceId, worker.id, launchConfig);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (worker) {
|
|
66
|
+
try {
|
|
67
|
+
store.deleteWorker(workspaceId, worker.id);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// The transaction already removed the DB row; this only prunes the
|
|
71
|
+
// stale in-memory entry, which may already be gone.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
if (!worker)
|
|
77
|
+
throw new Error('addWorkerWithLaunch produced no worker');
|
|
78
|
+
return worker;
|
|
79
|
+
},
|
|
39
80
|
renameWorker: (workspaceId, workerId, name) => services.workspaceStore.renameWorker(workspaceId, workerId, name),
|
|
40
81
|
deleteWorker: (workspaceId, workerId) => {
|
|
41
82
|
const activeRun = services.agentRuntime.getActiveRunByAgentId(workspaceId, workerId);
|
|
@@ -83,5 +124,30 @@ export const createRuntimeStore = (options = {}) => {
|
|
|
83
124
|
stopAgentRun: lifecycle.stopTerminalRun,
|
|
84
125
|
validateAgentToken: (agentId, token) => services.agentRuntime.validateAgentToken(agentId, token),
|
|
85
126
|
validateUiToken: (token) => services.uiAuth.validate(token),
|
|
127
|
+
getWorkflowDispatchAwaiter: () => services.workflowDispatchAwaiter,
|
|
128
|
+
runWorkflow: (input) => getWorkflowRuntime().runner.runWorkflow(input),
|
|
129
|
+
startWorkflow: (input) => getWorkflowRuntime().runner.startWorkflow(input),
|
|
130
|
+
startWorkflowInline: (input) => getWorkflowRuntime().runner.startWorkflowInline(input),
|
|
131
|
+
stopWorkflowRun: (runId) => getWorkflowRuntime().runner.stopRun(runId),
|
|
132
|
+
getWorkflowRun: (runId) => services.workflowRunStore.getRun(runId),
|
|
133
|
+
listWorkspaceWorkflowRuns: (workspaceId) => services.workflowRunStore.listWorkspaceRuns(workspaceId),
|
|
134
|
+
listWorkflowRunDispatches: (runId) => services.dispatchLedgerStore.listWorkflowRunDispatches(runId),
|
|
135
|
+
listWorkflowRunLogs: (runId) => services.workflowRunLogStore.listForRun(runId).map((row) => ({
|
|
136
|
+
id: row.id,
|
|
137
|
+
ts: row.ts,
|
|
138
|
+
message: row.message,
|
|
139
|
+
})),
|
|
140
|
+
createWorkflowSchedule: (input) => services.workflowScheduleStore.create(input),
|
|
141
|
+
scheduleWorkflowInline: (input) => persistWorkflowSchedule({
|
|
142
|
+
workspacePath: services.workspaceStore.getWorkspaceSnapshot(input.workspaceId).summary.path,
|
|
143
|
+
scheduleStore: services.workflowScheduleStore,
|
|
144
|
+
...input,
|
|
145
|
+
}),
|
|
146
|
+
updateWorkflowSchedule: (id, input) => services.workflowScheduleStore.update(id, input),
|
|
147
|
+
getWorkflowSchedule: (id) => services.workflowScheduleStore.get(id),
|
|
148
|
+
listWorkspaceWorkflowSchedules: (workspaceId) => services.workflowScheduleStore.listForWorkspace(workspaceId),
|
|
149
|
+
deleteWorkflowSchedule: (id) => services.workflowScheduleStore.deleteSchedule(id),
|
|
86
150
|
};
|
|
151
|
+
workflowRuntime = createRuntimeStoreWorkflowRuntime(services, store);
|
|
152
|
+
return store;
|
|
87
153
|
};
|
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
export declare const getClaudeProjectsRoot: (pattern?: string) => string;
|
|
2
|
+
/**
|
|
3
|
+
* Match the directory-name encoding Claude Code itself uses for its project
|
|
4
|
+
* metadata under `~/.claude/projects/`. Empirically (probed via `claude
|
|
5
|
+
* --print "x"` in directories named with each character) Claude Code
|
|
6
|
+
* replaces *every* character outside `[A-Za-z0-9-]` with a single `-`,
|
|
7
|
+
* one-for-one, preserving literal hyphens.
|
|
8
|
+
*
|
|
9
|
+
* The previous regex `[\\/:\s]` only matched `\`, `/`, `:`, and whitespace —
|
|
10
|
+
* leaving `_`, `.`, parens, brackets, `@`, `#`, `&`, `+`, and any non-ASCII
|
|
11
|
+
* character (including CJK usernames) intact, while Claude Code's own
|
|
12
|
+
* encoder replaced them. The mismatch meant Hive looked for sessions under
|
|
13
|
+
* a different directory than the one Claude Code wrote to, so session
|
|
14
|
+
* resume silently failed for any workspace path containing those chars.
|
|
15
|
+
* Windows + CJK Windows usernames (`C:\Users\张三\project`) and
|
|
16
|
+
* underscored Windows project paths (`C:\my_project`) are the most common
|
|
17
|
+
* triggers, but the bug is cross-platform.
|
|
18
|
+
*
|
|
19
|
+
* Backward compatibility: workspaces whose path contains ONLY chars that
|
|
20
|
+
* the old regex already matched (`/\:` + whitespace) get the same encoded
|
|
21
|
+
* dirname as before — no behavior change. Workspaces with any other
|
|
22
|
+
* special chars previously had broken session resume; the fix moves them
|
|
23
|
+
* to the correct (working) directory.
|
|
24
|
+
*/
|
|
2
25
|
export declare const encodeClaudeProjectPath: (cwd: string) => string;
|
|
3
26
|
interface ClaudeSessionCaptureDiscriminator {
|
|
4
27
|
contentIncludes?: string | readonly string[];
|
|
@@ -18,7 +18,30 @@ export const getClaudeProjectsRoot = (pattern) => {
|
|
|
18
18
|
}
|
|
19
19
|
return root;
|
|
20
20
|
};
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Match the directory-name encoding Claude Code itself uses for its project
|
|
23
|
+
* metadata under `~/.claude/projects/`. Empirically (probed via `claude
|
|
24
|
+
* --print "x"` in directories named with each character) Claude Code
|
|
25
|
+
* replaces *every* character outside `[A-Za-z0-9-]` with a single `-`,
|
|
26
|
+
* one-for-one, preserving literal hyphens.
|
|
27
|
+
*
|
|
28
|
+
* The previous regex `[\\/:\s]` only matched `\`, `/`, `:`, and whitespace —
|
|
29
|
+
* leaving `_`, `.`, parens, brackets, `@`, `#`, `&`, `+`, and any non-ASCII
|
|
30
|
+
* character (including CJK usernames) intact, while Claude Code's own
|
|
31
|
+
* encoder replaced them. The mismatch meant Hive looked for sessions under
|
|
32
|
+
* a different directory than the one Claude Code wrote to, so session
|
|
33
|
+
* resume silently failed for any workspace path containing those chars.
|
|
34
|
+
* Windows + CJK Windows usernames (`C:\Users\张三\project`) and
|
|
35
|
+
* underscored Windows project paths (`C:\my_project`) are the most common
|
|
36
|
+
* triggers, but the bug is cross-platform.
|
|
37
|
+
*
|
|
38
|
+
* Backward compatibility: workspaces whose path contains ONLY chars that
|
|
39
|
+
* the old regex already matched (`/\:` + whitespace) get the same encoded
|
|
40
|
+
* dirname as before — no behavior change. Workspaces with any other
|
|
41
|
+
* special chars previously had broken session resume; the fix moves them
|
|
42
|
+
* to the correct (working) directory.
|
|
43
|
+
*/
|
|
44
|
+
export const encodeClaudeProjectPath = (cwd) => cwd.replace(/[^A-Za-z0-9-]/g, '-');
|
|
22
45
|
const listSessionIds = (cwd, projectsRoot = getDefaultProjectsRoot()) => {
|
|
23
46
|
const projectDir = join(projectsRoot, encodeClaudeProjectPath(cwd));
|
|
24
47
|
try {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export declare const getCodexHome: (pattern?: string) => string;
|
|
1
|
+
export declare const getCodexHome: (pattern?: string, platform?: NodeJS.Platform) => string;
|
|
2
2
|
export declare const readCodexSessionFirstLine: (filePath: string, maxBytes?: number) => string | null;
|
|
3
|
-
export declare const hasCodexSession: (cwd: string, sessionId: string, pattern?: string) => boolean;
|
|
4
|
-
export declare const snapshotCodexSessionIds: (cwd: string, codexHome?: string) => Set<string>;
|
|
3
|
+
export declare const hasCodexSession: (cwd: string, sessionId: string, pattern?: string, platform?: NodeJS.Platform, codexHome?: string) => boolean;
|
|
4
|
+
export declare const snapshotCodexSessionIds: (cwd: string, codexHome?: string, platform?: NodeJS.Platform) => Set<string>;
|
|
5
5
|
export declare const captureCodexSessionId: (cwd: string, knownSessionIds: Set<string>, onCapture: (sessionId: string) => void, timeoutMs?: number, intervalMs?: number, codexHome?: string) => Promise<void>;
|
|
6
6
|
export declare const codexSessionStoreExists: (codexHome?: string) => boolean;
|
|
@@ -2,19 +2,21 @@ import { closeSync, existsSync, openSync, readdirSync, readSync } from 'node:fs'
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { captureSessionIdWithCoordinator } from './claude-session-coordinator.js';
|
|
5
|
+
import { arePathsEqual, indexOfPathMarker } from './platform-path.js';
|
|
5
6
|
const CODEX_SESSION_FILE = /^rollout-.*\.jsonl$/i;
|
|
7
|
+
const CODEX_SESSIONS_MARKER = '/sessions/';
|
|
6
8
|
const CODEX_HEADER_READ_CHUNK_BYTES = 4096;
|
|
7
9
|
const CODEX_HEADER_MAX_BYTES = 64 * 1024;
|
|
8
10
|
const getDefaultCodexHome = () => process.env.CODEX_HOME ?? join(homedir(), '.codex');
|
|
9
11
|
const expandHome = (path) => path === '~' || path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
|
|
10
|
-
export const getCodexHome = (pattern) => {
|
|
12
|
+
export const getCodexHome = (pattern, platform = process.platform) => {
|
|
11
13
|
if (!pattern)
|
|
12
14
|
return getDefaultCodexHome();
|
|
13
|
-
const markerIndex = pattern
|
|
15
|
+
const markerIndex = indexOfPathMarker(pattern, CODEX_SESSIONS_MARKER, platform);
|
|
14
16
|
if (markerIndex === -1)
|
|
15
17
|
return getDefaultCodexHome();
|
|
16
18
|
const rawRoot = pattern.slice(0, markerIndex);
|
|
17
|
-
if (rawRoot
|
|
19
|
+
if (arePathsEqual(rawRoot, '~/.codex', platform) || arePathsEqual(rawRoot, '~/.codex/', platform))
|
|
18
20
|
return getDefaultCodexHome();
|
|
19
21
|
const root = expandHome(rawRoot);
|
|
20
22
|
return root || getDefaultCodexHome();
|
|
@@ -78,13 +80,13 @@ const parseCodexSession = (filePath) => {
|
|
|
78
80
|
const cwd = 'cwd' in payload && typeof payload.cwd === 'string' ? payload.cwd : null;
|
|
79
81
|
return id && cwd ? { cwd, id } : null;
|
|
80
82
|
};
|
|
81
|
-
const listSessionIds = (cwd, codexHome = getDefaultCodexHome()) => {
|
|
83
|
+
const listSessionIds = (cwd, codexHome = getDefaultCodexHome(), platform = process.platform) => {
|
|
82
84
|
const sessionsRoot = join(codexHome, 'sessions');
|
|
83
85
|
return walkSessionFiles(sessionsRoot)
|
|
84
86
|
.flatMap((filePath) => {
|
|
85
87
|
try {
|
|
86
88
|
const session = parseCodexSession(filePath);
|
|
87
|
-
return session
|
|
89
|
+
return session && arePathsEqual(session.cwd, cwd, platform) ? [session.id] : [];
|
|
88
90
|
}
|
|
89
91
|
catch {
|
|
90
92
|
return [];
|
|
@@ -92,8 +94,8 @@ const listSessionIds = (cwd, codexHome = getDefaultCodexHome()) => {
|
|
|
92
94
|
})
|
|
93
95
|
.sort((left, right) => left.localeCompare(right));
|
|
94
96
|
};
|
|
95
|
-
export const hasCodexSession = (cwd, sessionId, pattern) => listSessionIds(cwd,
|
|
96
|
-
export const snapshotCodexSessionIds = (cwd, codexHome = getDefaultCodexHome()) => new Set(listSessionIds(cwd, codexHome));
|
|
97
|
+
export const hasCodexSession = (cwd, sessionId, pattern, platform = process.platform, codexHome = getCodexHome(pattern, platform)) => listSessionIds(cwd, codexHome, platform).includes(sessionId);
|
|
98
|
+
export const snapshotCodexSessionIds = (cwd, codexHome = getDefaultCodexHome(), platform = process.platform) => new Set(listSessionIds(cwd, codexHome, platform));
|
|
97
99
|
export const captureCodexSessionId = async (cwd, knownSessionIds, onCapture, timeoutMs = 5000, intervalMs = 100, codexHome = getDefaultCodexHome()) => {
|
|
98
100
|
await captureSessionIdWithCoordinator({
|
|
99
101
|
intervalMs,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const getGeminiHome: (pattern?: string) => string;
|
|
1
|
+
export declare const getGeminiHome: (pattern?: string, platform?: NodeJS.Platform) => string;
|
|
2
2
|
export declare const hasGeminiSession: (cwd: string, sessionId: string, pattern?: string) => boolean;
|
|
3
3
|
export declare const snapshotGeminiSessionIds: (cwd: string, geminiHome?: string) => Set<string>;
|
|
4
4
|
export declare const captureGeminiSessionId: (cwd: string, knownSessionIds: Set<string>, onCapture: (sessionId: string) => void, timeoutMs?: number, intervalMs?: number, geminiHome?: string) => Promise<void>;
|
|
@@ -2,13 +2,15 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { captureSessionIdWithCoordinator } from './claude-session-coordinator.js';
|
|
5
|
+
import { arePathsEqual, indexOfPathMarker } from './platform-path.js';
|
|
5
6
|
const GEMINI_SESSION_FILE = /^session-.*\.json$/i;
|
|
7
|
+
const GEMINI_TMP_MARKER = '/tmp/';
|
|
6
8
|
const getDefaultGeminiHome = () => process.env.HIVE_GEMINI_HOME ?? join(homedir(), '.gemini');
|
|
7
9
|
const expandHome = (path) => path === '~' || path.startsWith('~/') ? join(homedir(), path.slice(2)) : path;
|
|
8
|
-
export const getGeminiHome = (pattern) => {
|
|
10
|
+
export const getGeminiHome = (pattern, platform = process.platform) => {
|
|
9
11
|
if (!pattern)
|
|
10
12
|
return getDefaultGeminiHome();
|
|
11
|
-
const markerIndex = pattern
|
|
13
|
+
const markerIndex = indexOfPathMarker(pattern, GEMINI_TMP_MARKER, platform);
|
|
12
14
|
if (markerIndex === -1)
|
|
13
15
|
return getDefaultGeminiHome();
|
|
14
16
|
const rawRoot = pattern.slice(0, markerIndex);
|
|
@@ -38,7 +40,8 @@ const listSessionIds = (cwd, geminiHome = getDefaultGeminiHome()) => {
|
|
|
38
40
|
.filter((entry) => entry.isDirectory())
|
|
39
41
|
.flatMap((entry) => {
|
|
40
42
|
const projectDir = join(tmpRoot, entry.name);
|
|
41
|
-
|
|
43
|
+
const projectRoot = readProjectRoot(projectDir);
|
|
44
|
+
if (projectRoot === null || !arePathsEqual(projectRoot, cwd))
|
|
42
45
|
return [];
|
|
43
46
|
const chatsDir = join(projectDir, 'chats');
|
|
44
47
|
try {
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the path OpenCode upstream writes `opencode.db` to. Branches
|
|
3
|
+
* by platform because XDG_DATA_HOME is not a Windows convention — the
|
|
4
|
+
* previous unconditional ~/.local/share/opencode/opencode.db default
|
|
5
|
+
* pointed at a path that never exists on Windows, silently breaking
|
|
6
|
+
* Layer A native session resume for OpenCode workers there.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order:
|
|
9
|
+
* 1. HIVE_OPENCODE_DB_PATH override (any platform, for tests/users).
|
|
10
|
+
* 2. Windows: %LOCALAPPDATA%\opencode\opencode.db (with a homedir
|
|
11
|
+
* fallback when LOCALAPPDATA is missing — some shells strip env).
|
|
12
|
+
* 3. POSIX: $XDG_DATA_HOME/opencode/opencode.db, falling back to
|
|
13
|
+
* ~/.local/share/opencode/opencode.db.
|
|
14
|
+
*
|
|
15
|
+
* Exported so the path-resolution rules can be unit-tested without
|
|
16
|
+
* needing to mock the underlying SQLite open.
|
|
17
|
+
*/
|
|
18
|
+
export declare const getDefaultOpenCodeDbPath: (platform?: NodeJS.Platform) => string;
|
|
1
19
|
export declare const getOpenCodeDbPath: (pattern?: string) => string;
|
|
2
20
|
export declare const hasOpenCodeSession: (cwd: string, sessionId: string, pattern?: string) => boolean;
|
|
3
21
|
export declare const snapshotOpenCodeSessionIds: (cwd: string, dbPath?: string) => Set<string>;
|