@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.
Files changed (180) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.en.md +5 -4
  3. package/README.md +9 -1
  4. package/assets/qq-group.jpg +0 -0
  5. package/dist/bin/team.cmd +1 -0
  6. package/dist/src/cli/hive-update.d.ts +57 -0
  7. package/dist/src/cli/hive-update.js +92 -15
  8. package/dist/src/cli/hive.d.ts +57 -0
  9. package/dist/src/cli/hive.js +113 -20
  10. package/dist/src/cli/team.d.ts +1 -0
  11. package/dist/src/cli/team.js +215 -7
  12. package/dist/src/server/agent-command-resolver.d.ts +10 -1
  13. package/dist/src/server/agent-command-resolver.js +32 -4
  14. package/dist/src/server/agent-launch-resolver.js +9 -3
  15. package/dist/src/server/agent-manager-support.d.ts +28 -0
  16. package/dist/src/server/agent-manager-support.js +138 -10
  17. package/dist/src/server/agent-run-bootstrap.d.ts +17 -1
  18. package/dist/src/server/agent-run-bootstrap.js +30 -2
  19. package/dist/src/server/agent-run-starter.d.ts +7 -1
  20. package/dist/src/server/agent-run-starter.js +9 -2
  21. package/dist/src/server/agent-run-store.d.ts +1 -1
  22. package/dist/src/server/agent-runtime-close.d.ts +1 -0
  23. package/dist/src/server/agent-runtime-close.js +25 -1
  24. package/dist/src/server/agent-runtime-contract.d.ts +2 -1
  25. package/dist/src/server/agent-runtime.d.ts +1 -1
  26. package/dist/src/server/agent-runtime.js +8 -2
  27. package/dist/src/server/agent-startup-instructions.d.ts +8 -1
  28. package/dist/src/server/agent-startup-instructions.js +15 -9
  29. package/dist/src/server/agent-stdin-dispatcher.d.ts +12 -5
  30. package/dist/src/server/agent-stdin-dispatcher.js +129 -40
  31. package/dist/src/server/app.d.ts +1 -0
  32. package/dist/src/server/app.js +12 -2
  33. package/dist/src/server/cron-util.d.ts +7 -0
  34. package/dist/src/server/cron-util.js +19 -0
  35. package/dist/src/server/dispatch-ledger-store.d.ts +22 -0
  36. package/dist/src/server/dispatch-ledger-store.js +51 -3
  37. package/dist/src/server/env-sync-message.js +9 -9
  38. package/dist/src/server/fs-browse.d.ts +14 -1
  39. package/dist/src/server/fs-browse.js +48 -5
  40. package/dist/src/server/fs-pick-folder.js +58 -11
  41. package/dist/src/server/fs-sandbox.js +36 -7
  42. package/dist/src/server/hive-team-guidance.d.ts +11 -6
  43. package/dist/src/server/hive-team-guidance.js +252 -70
  44. package/dist/src/server/live-run-registry.d.ts +1 -0
  45. package/dist/src/server/live-run-registry.js +1 -1
  46. package/dist/src/server/open-target-commands.js +29 -4
  47. package/dist/src/server/orchestrator-autostart.d.ts +12 -0
  48. package/dist/src/server/orchestrator-autostart.js +15 -13
  49. package/dist/src/server/path-canonicalization.d.ts +3 -0
  50. package/dist/src/server/path-canonicalization.js +29 -0
  51. package/dist/src/server/platform-path.d.ts +3 -0
  52. package/dist/src/server/platform-path.js +13 -0
  53. package/dist/src/server/post-start-input-writer.d.ts +1 -1
  54. package/dist/src/server/post-start-input-writer.js +116 -16
  55. package/dist/src/server/preset-launch-support.d.ts +1 -1
  56. package/dist/src/server/preset-launch-support.js +33 -2
  57. package/dist/src/server/recovery-summary.d.ts +6 -1
  58. package/dist/src/server/recovery-summary.js +17 -17
  59. package/dist/src/server/restart-policy-support.d.ts +6 -1
  60. package/dist/src/server/restart-policy-support.js +9 -1
  61. package/dist/src/server/restart-policy.d.ts +2 -2
  62. package/dist/src/server/restart-policy.js +3 -1
  63. package/dist/src/server/role-template-store.d.ts +1 -0
  64. package/dist/src/server/role-template-store.js +11 -1
  65. package/dist/src/server/route-types.d.ts +43 -0
  66. package/dist/src/server/routes-runtime.js +2 -1
  67. package/dist/src/server/routes-settings.js +76 -0
  68. package/dist/src/server/routes-team.js +221 -2
  69. package/dist/src/server/routes-workflow-schedules.d.ts +2 -0
  70. package/dist/src/server/routes-workflow-schedules.js +58 -0
  71. package/dist/src/server/routes-workflows.d.ts +2 -0
  72. package/dist/src/server/routes-workflows.js +83 -0
  73. package/dist/src/server/routes.js +4 -0
  74. package/dist/src/server/runtime-restart-policy.d.ts +3 -1
  75. package/dist/src/server/runtime-restart-policy.js +3 -1
  76. package/dist/src/server/runtime-store-contract.d.ts +122 -0
  77. package/dist/src/server/runtime-store-contract.js +1 -0
  78. package/dist/src/server/runtime-store-helpers.d.ts +9 -0
  79. package/dist/src/server/runtime-store-helpers.js +101 -2
  80. package/dist/src/server/runtime-store-workflows.d.ts +6 -0
  81. package/dist/src/server/runtime-store-workflows.js +100 -0
  82. package/dist/src/server/runtime-store.d.ts +3 -70
  83. package/dist/src/server/runtime-store.js +70 -4
  84. package/dist/src/server/session-capture-claude.d.ts +23 -0
  85. package/dist/src/server/session-capture-claude.js +24 -1
  86. package/dist/src/server/session-capture-codex.d.ts +3 -3
  87. package/dist/src/server/session-capture-codex.js +9 -7
  88. package/dist/src/server/session-capture-gemini.d.ts +1 -1
  89. package/dist/src/server/session-capture-gemini.js +6 -3
  90. package/dist/src/server/session-capture-opencode.d.ts +18 -0
  91. package/dist/src/server/session-capture-opencode.js +27 -2
  92. package/dist/src/server/settings-store.d.ts +3 -0
  93. package/dist/src/server/settings-store.js +1 -0
  94. package/dist/src/server/sqlite-schema-v19.d.ts +2 -0
  95. package/dist/src/server/sqlite-schema-v19.js +17 -0
  96. package/dist/src/server/sqlite-schema-v20.d.ts +2 -0
  97. package/dist/src/server/sqlite-schema-v20.js +20 -0
  98. package/dist/src/server/sqlite-schema-v21.d.ts +2 -0
  99. package/dist/src/server/sqlite-schema-v21.js +20 -0
  100. package/dist/src/server/sqlite-schema.d.ts +1 -1
  101. package/dist/src/server/sqlite-schema.js +97 -1
  102. package/dist/src/server/startup-command-parser.d.ts +15 -0
  103. package/dist/src/server/startup-command-parser.js +33 -2
  104. package/dist/src/server/system-message.d.ts +7 -0
  105. package/dist/src/server/system-message.js +8 -1
  106. package/dist/src/server/tasks-file-watcher.d.ts +39 -1
  107. package/dist/src/server/tasks-file-watcher.js +155 -25
  108. package/dist/src/server/tasks-file.d.ts +2 -1
  109. package/dist/src/server/tasks-file.js +32 -9
  110. package/dist/src/server/tasks-websocket-server.js +13 -14
  111. package/dist/src/server/team-authz.d.ts +1 -1
  112. package/dist/src/server/team-authz.js +9 -1
  113. package/dist/src/server/team-autostaff.d.ts +16 -0
  114. package/dist/src/server/team-autostaff.js +16 -0
  115. package/dist/src/server/team-list-serializer.d.ts +1 -1
  116. package/dist/src/server/team-list-serializer.js +3 -1
  117. package/dist/src/server/team-operations.d.ts +20 -2
  118. package/dist/src/server/team-operations.js +160 -14
  119. package/dist/src/server/terminal-input-profile.js +2 -8
  120. package/dist/src/server/terminal-protocol.js +9 -3
  121. package/dist/src/server/terminal-stream-hub.js +16 -10
  122. package/dist/src/server/terminal-ws-server.js +36 -16
  123. package/dist/src/server/websocket-upgrade-safety.d.ts +10 -0
  124. package/dist/src/server/websocket-upgrade-safety.js +35 -0
  125. package/dist/src/server/windows-command-line.d.ts +3 -0
  126. package/dist/src/server/windows-command-line.js +9 -0
  127. package/dist/src/server/windows-filename.d.ts +2 -0
  128. package/dist/src/server/windows-filename.js +33 -0
  129. package/dist/src/server/workflow-cli-policy.d.ts +60 -0
  130. package/dist/src/server/workflow-cli-policy.js +110 -0
  131. package/dist/src/server/workflow-dispatch-awaiter.d.ts +12 -0
  132. package/dist/src/server/workflow-dispatch-awaiter.js +80 -0
  133. package/dist/src/server/workflow-feature.d.ts +15 -0
  134. package/dist/src/server/workflow-feature.js +15 -0
  135. package/dist/src/server/workflow-http-serializers.d.ts +64 -0
  136. package/dist/src/server/workflow-http-serializers.js +58 -0
  137. package/dist/src/server/workflow-run-log-store.d.ts +19 -0
  138. package/dist/src/server/workflow-run-log-store.js +45 -0
  139. package/dist/src/server/workflow-run-store.d.ts +50 -0
  140. package/dist/src/server/workflow-run-store.js +103 -0
  141. package/dist/src/server/workflow-runner.d.ts +147 -0
  142. package/dist/src/server/workflow-runner.js +401 -0
  143. package/dist/src/server/workflow-schedule-create.d.ts +14 -0
  144. package/dist/src/server/workflow-schedule-create.js +41 -0
  145. package/dist/src/server/workflow-schedule-store.d.ts +43 -0
  146. package/dist/src/server/workflow-schedule-store.js +112 -0
  147. package/dist/src/server/workflow-scheduler.d.ts +36 -0
  148. package/dist/src/server/workflow-scheduler.js +97 -0
  149. package/dist/src/server/workflow-script-loader.d.ts +34 -0
  150. package/dist/src/server/workflow-script-loader.js +106 -0
  151. package/dist/src/server/workspace-path-validation.js +16 -4
  152. package/dist/src/server/workspace-shell-runtime.d.ts +5 -0
  153. package/dist/src/server/workspace-shell-runtime.js +24 -2
  154. package/dist/src/server/workspace-store-contract.d.ts +4 -1
  155. package/dist/src/server/workspace-store-hydration.js +23 -7
  156. package/dist/src/server/workspace-store-mutations.js +2 -5
  157. package/dist/src/server/workspace-store-support.d.ts +4 -0
  158. package/dist/src/server/workspace-store-support.js +13 -1
  159. package/dist/src/server/workspace-store.js +38 -4
  160. package/dist/src/shared/types.d.ts +16 -1
  161. package/package.json +4 -2
  162. package/web/dist/assets/{AddWorkerDialog-DmkDOdp6.js → AddWorkerDialog-CcC-7kgG.js} +2 -2
  163. package/web/dist/assets/AddWorkspaceDialog-BDpOTfmt.js +1 -0
  164. package/web/dist/assets/{FirstRunWizard-SAd1wsH4.js → FirstRunWizard-BYX_ocQn.js} +1 -1
  165. package/web/dist/assets/{MarketplaceDrawer-B_8aG2uT.js → MarketplaceDrawer-DUxSk7db.js} +1 -1
  166. package/web/dist/assets/WhatsNewDialog-B_RlCXcV.js +1 -0
  167. package/web/dist/assets/WorkerModal-D9-7YfZZ.js +1 -0
  168. package/web/dist/assets/WorkspaceTaskDrawer-BCKoF7qc.js +1 -0
  169. package/web/dist/assets/{WorkspaceTerminalPanels-BReWh1YL.js → WorkspaceTerminalPanels-Dq8y91t2.js} +1 -1
  170. package/web/dist/assets/index-BiOvKIVw.css +1 -0
  171. package/web/dist/assets/index-DMRUklT3.js +73 -0
  172. package/web/dist/assets/path-join-7MR1s7b1.js +1 -0
  173. package/web/dist/index.html +2 -2
  174. package/web/dist/sw.js +1 -1
  175. package/web/dist/assets/AddWorkspaceDialog-BsVnH3Xe.js +0 -1
  176. package/web/dist/assets/WorkerModal-CQmjiPme.js +0 -1
  177. package/web/dist/assets/WorkspaceTaskDrawer-B0DmCWcV.js +0 -1
  178. package/web/dist/assets/chevron-right-CtLjVEl7.js +0 -1
  179. package/web/dist/assets/index-BEsTmfrO.css +0 -1
  180. package/web/dist/assets/index-Cn8X3get.js +0 -76
@@ -1,6 +1,24 @@
1
- import { BadRequestError } from './http-errors.js';
1
+ import { randomUUID } from 'node:crypto';
2
+ import { resolveCommandPresetLaunchConfig } from './agent-launch-resolver.js';
3
+ import { validateCronNextRunAt } from './cron-util.js';
4
+ import { BadRequestError, ForbiddenError } from './http-errors.js';
2
5
  import { readJsonBody, route, sendJson } from './route-helpers.js';
3
6
  import { authenticateCliAgent, requireCommandForRole } from './team-authz.js';
7
+ import { readWorkflowEnabled, WORKFLOW_ENABLED_KEY } from './workflow-feature.js';
8
+ const WORKER_ROLES = new Set(['coder', 'reviewer', 'tester', 'custom']);
9
+ const toWorkerRole = (value) => value && WORKER_ROLES.has(value) ? value : 'coder';
10
+ // Experimental gate: `team workflow run` / `team workflow schedule` only work
11
+ // when the user has enabled workflows in Settings. Off by default. The PTY
12
+ // runner itself isn't gated (internal API + the scheduler gate handle that) —
13
+ // this is the agent-facing entry point, so the orchestrator gets a clear,
14
+ // actionable error instead of a silent no-op.
15
+ const requireWorkflowsEnabled = (store) => {
16
+ if (!readWorkflowEnabled(store.settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null)) {
17
+ throw new ForbiddenError('Workflows are an experimental Hive feature and are currently disabled. ' +
18
+ 'Enable them in Hive Settings (the gear menu, top-right) to use `team workflow`. ' +
19
+ 'Until then, dispatch work with `team send`.');
20
+ }
21
+ };
4
22
  const requireNonEmptyString = (value, field) => {
5
23
  if (typeof value !== 'string' || value.trim().length === 0) {
6
24
  throw new BadRequestError(`Missing ${field}`);
@@ -27,7 +45,208 @@ export const teamRoutes = [
27
45
  fromAgentId,
28
46
  hivePort: String(request.socket.localPort ?? ''),
29
47
  });
30
- sendJson(response, 202, { dispatch_id: dispatch.id, ok: true });
48
+ /* `restarted_worker` makes the silent auto-wake transparent — when
49
+ the worker had no active run at dispatch time, ensureWorkerRun
50
+ spawned a fresh PTY for it. The orchestrator's CLI prints a
51
+ stderr notice on this flag so the agent can explain the wake-up
52
+ to the user instead of being out of sync with worker state. */
53
+ sendJson(response, 202, {
54
+ dispatch_id: dispatch.id,
55
+ ok: true,
56
+ restarted_worker: dispatch.restartedWorker,
57
+ });
58
+ }),
59
+ route('POST', '/api/team/spawn', async ({ request, response, store }) => {
60
+ const body = await readJsonBody(request);
61
+ const projectId = requireNonEmptyString(body.project_id, 'project_id');
62
+ const fromAgentId = requireNonEmptyString(body.from_agent_id, 'from_agent_id');
63
+ const agent = authenticateCliAgent({
64
+ fromAgentId,
65
+ getAgent: store.getAgent,
66
+ token: body.token,
67
+ validateToken: store.validateAgentToken,
68
+ workspaceId: projectId,
69
+ });
70
+ requireCommandForRole(agent, 'spawn');
71
+ const role = toWorkerRole(body.role);
72
+ const name = typeof body.name === 'string' && body.name.trim().length > 0
73
+ ? body.name.trim()
74
+ : `${role}-${randomUUID()}`;
75
+ const launchConfig = (body.cli ? resolveCommandPresetLaunchConfig(store.settings, body.cli) : undefined) ??
76
+ { command: body.cli ?? 'claude', args: [] };
77
+ // M11: default is persistent (acts like a normal member); --ephemeral
78
+ // opts into auto-dismiss-after-first-report (mirrors workflow workers,
79
+ // but orchestrator-spawned). Existing M1-D cascade-on-orchestrator-exit
80
+ // still applies to whatever ephemeral workers remain.
81
+ const ephemeral = body.ephemeral === true;
82
+ const workerInput = ephemeral
83
+ ? { name, role, ephemeral: true, spawnedBy: 'orchestrator' }
84
+ : { name, role };
85
+ const worker = store.addWorkerWithLaunch(projectId, workerInput, launchConfig);
86
+ sendJson(response, 201, {
87
+ name: worker.name,
88
+ ok: true,
89
+ worker_id: worker.id,
90
+ ephemeral,
91
+ });
92
+ }),
93
+ route('POST', '/api/team/workflow/run', async ({ request, response, store }) => {
94
+ const body = await readJsonBody(request);
95
+ const projectId = requireNonEmptyString(body.project_id, 'project_id');
96
+ const fromAgentId = requireNonEmptyString(body.from_agent_id, 'from_agent_id');
97
+ const source = requireNonEmptyString(body.source, 'source');
98
+ const agent = authenticateCliAgent({
99
+ fromAgentId,
100
+ getAgent: store.getAgent,
101
+ token: body.token,
102
+ validateToken: store.validateAgentToken,
103
+ workspaceId: projectId,
104
+ });
105
+ requireCommandForRole(agent, 'workflow');
106
+ // Feature gate AFTER auth so an unauthenticated caller gets 401/400, not a
107
+ // 403 that leaks whether the experiment is on.
108
+ requireWorkflowsEnabled(store);
109
+ const hivePort = String(request.socket.localPort ?? '');
110
+ const input = {
111
+ workspaceId: projectId,
112
+ source,
113
+ hivePort,
114
+ triggeredByAgentId: fromAgentId,
115
+ ...(body.args !== undefined ? { args: body.args } : {}),
116
+ ...(typeof body.name === 'string' && body.name.trim()
117
+ ? { scriptPath: `<inline:${body.name.trim()}>` }
118
+ : {}),
119
+ };
120
+ const run = await store.startWorkflowInline(input);
121
+ sendJson(response, 202, { ok: true, run_id: run.id, name: run.name, status: run.status });
122
+ }),
123
+ /* Agent-initiated scheduling. Workflows are authored by the orchestrator,
124
+ not picked from a human script library — so registering a recurring run
125
+ is also an agent action: the orchestrator passes the workflow source +
126
+ a cron, and the runtime persists the source (so cron can fire it with no
127
+ orchestrator in the loop) and registers the schedule. The UI only
128
+ lists / pauses / deletes schedules; it cannot create them. */
129
+ route('POST', '/api/team/workflow/schedule', async ({ request, response, store }) => {
130
+ const body = await readJsonBody(request);
131
+ const projectId = requireNonEmptyString(body.project_id, 'project_id');
132
+ const fromAgentId = requireNonEmptyString(body.from_agent_id, 'from_agent_id');
133
+ const source = requireNonEmptyString(body.source, 'source');
134
+ const name = requireNonEmptyString(body.name, 'name');
135
+ const cron = requireNonEmptyString(body.cron, 'cron');
136
+ const agent = authenticateCliAgent({
137
+ fromAgentId,
138
+ getAgent: store.getAgent,
139
+ token: body.token,
140
+ validateToken: store.validateAgentToken,
141
+ workspaceId: projectId,
142
+ });
143
+ requireCommandForRole(agent, 'workflow');
144
+ // Feature gate AFTER auth (see /run above).
145
+ requireWorkflowsEnabled(store);
146
+ const nextRunAt = validateCronNextRunAt(cron);
147
+ const schedule = await store.scheduleWorkflowInline({
148
+ workspaceId: projectId,
149
+ source,
150
+ name,
151
+ cron,
152
+ nextRunAt,
153
+ ...(body.args !== undefined ? { args: body.args } : {}),
154
+ });
155
+ sendJson(response, 201, {
156
+ ok: true,
157
+ schedule_id: schedule.id,
158
+ script_path: schedule.scriptPath,
159
+ cron: schedule.cron,
160
+ next_run_at: schedule.nextRunAt,
161
+ });
162
+ }),
163
+ route('POST', '/api/team/workflow/stop', async ({ request, response, store }) => {
164
+ const body = await readJsonBody(request);
165
+ const projectId = requireNonEmptyString(body.project_id, 'project_id');
166
+ const fromAgentId = requireNonEmptyString(body.from_agent_id, 'from_agent_id');
167
+ const runId = requireNonEmptyString(body.run_id, 'run_id');
168
+ const agent = authenticateCliAgent({
169
+ fromAgentId,
170
+ getAgent: store.getAgent,
171
+ token: body.token,
172
+ validateToken: store.validateAgentToken,
173
+ workspaceId: projectId,
174
+ });
175
+ requireCommandForRole(agent, 'workflow');
176
+ const stopped = store.stopWorkflowRun(runId);
177
+ sendJson(response, stopped ? 202 : 409, { ok: stopped, run_id: runId });
178
+ }),
179
+ /* TIER 1 #6 — per-agent transcript dump. The completion reminder injected
180
+ into the orchestrator's stdin promised this command but it never
181
+ existed, so any orchestrator that followed the suggestion got a
182
+ usage error. Returns the run record + the full per-dispatch detail
183
+ (step / phase / label / prompt / status / report text) so the
184
+ orchestrator can read past the 200-char per-step summary cap in the
185
+ reminder. */
186
+ route('POST', '/api/team/workflow/show', async ({ request, response, store }) => {
187
+ const body = await readJsonBody(request);
188
+ const projectId = requireNonEmptyString(body.project_id, 'project_id');
189
+ const fromAgentId = requireNonEmptyString(body.from_agent_id, 'from_agent_id');
190
+ const runId = requireNonEmptyString(body.run_id, 'run_id');
191
+ const agent = authenticateCliAgent({
192
+ fromAgentId,
193
+ getAgent: store.getAgent,
194
+ token: body.token,
195
+ validateToken: store.validateAgentToken,
196
+ workspaceId: projectId,
197
+ });
198
+ requireCommandForRole(agent, 'workflow');
199
+ const run = store.getWorkflowRun(runId);
200
+ if (!run || run.workspaceId !== projectId) {
201
+ sendJson(response, 404, { error: `Workflow run not found: ${runId}` });
202
+ return;
203
+ }
204
+ const dispatches = store.listWorkflowRunDispatches(runId).map((d) => ({
205
+ step_index: d.stepIndex,
206
+ phase: d.phase,
207
+ label: d.label,
208
+ to_agent_id: d.toAgentId,
209
+ status: d.status,
210
+ text: d.text,
211
+ report_text: d.reportText,
212
+ submitted_at: d.submittedAt,
213
+ reported_at: d.reportedAt,
214
+ }));
215
+ sendJson(response, 200, {
216
+ ok: true,
217
+ run: {
218
+ id: run.id,
219
+ name: run.name,
220
+ status: run.status,
221
+ phase: run.phase,
222
+ started_at: run.startedAt,
223
+ finished_at: run.finishedAt,
224
+ error: run.error,
225
+ result: run.result,
226
+ },
227
+ dispatches,
228
+ });
229
+ }),
230
+ route('POST', '/api/team/dismiss', async ({ request, response, store }) => {
231
+ const body = await readJsonBody(request);
232
+ const projectId = requireNonEmptyString(body.project_id, 'project_id');
233
+ const fromAgentId = requireNonEmptyString(body.from_agent_id, 'from_agent_id');
234
+ const name = requireNonEmptyString(body.name, 'name');
235
+ const agent = authenticateCliAgent({
236
+ fromAgentId,
237
+ getAgent: store.getAgent,
238
+ token: body.token,
239
+ validateToken: store.validateAgentToken,
240
+ workspaceId: projectId,
241
+ });
242
+ requireCommandForRole(agent, 'dismiss');
243
+ const worker = store.listWorkers(projectId).find((item) => item.name === name);
244
+ if (!worker) {
245
+ sendJson(response, 404, { error: `No such worker: ${name}` });
246
+ return;
247
+ }
248
+ store.deleteWorker(projectId, worker.id);
249
+ sendJson(response, 200, { ok: true });
31
250
  }),
32
251
  route('POST', '/api/team/cancel', async ({ request, response, store }) => {
33
252
  const body = await readJsonBody(request);
@@ -0,0 +1,2 @@
1
+ import type { RouteDefinition } from './route-types.js';
2
+ export declare const workflowScheduleRoutes: RouteDefinition[];
@@ -0,0 +1,58 @@
1
+ import { validateCronNextRunAt } from './cron-util.js';
2
+ import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
3
+ import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
4
+ import { serializeWorkflowSchedule } from './workflow-http-serializers.js';
5
+ // Schedules are CREATED by the orchestrator agent (`team workflow schedule`),
6
+ // which persists the workflow source so cron can fire it with no orchestrator
7
+ // in the loop. The UI only LISTS / PAUSES / RESUMES / DELETES them — there is
8
+ // no human create route here.
9
+ export const workflowScheduleRoutes = [
10
+ route('GET', '/api/workspaces/:workspaceId/workflow-schedules', ({ params, request, response, store }) => {
11
+ requireUiTokenFromRequest(request, store.validateUiToken);
12
+ const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Missing workspaceId');
13
+ if (!workspaceId)
14
+ return;
15
+ sendJson(response, 200, {
16
+ schedules: store.listWorkspaceWorkflowSchedules(workspaceId).map(serializeWorkflowSchedule),
17
+ });
18
+ }),
19
+ route('PATCH', '/api/workflow-schedules/:scheduleId', async ({ params, request, response, store }) => {
20
+ requireUiTokenFromRequest(request, store.validateUiToken);
21
+ const scheduleId = getRequiredParam(response, params, 'scheduleId', 'Missing scheduleId');
22
+ if (!scheduleId)
23
+ return;
24
+ const existing = store.getWorkflowSchedule(scheduleId);
25
+ if (!existing) {
26
+ sendJson(response, 404, { error: `Schedule not found: ${scheduleId}` });
27
+ return;
28
+ }
29
+ const body = await readJsonBody(request);
30
+ const update = {};
31
+ if (typeof body.cron === 'string') {
32
+ update.cron = body.cron;
33
+ update.nextRunAt = validateCronNextRunAt(body.cron);
34
+ }
35
+ if (body.args !== undefined)
36
+ update.args = body.args;
37
+ if (typeof body.enabled === 'boolean')
38
+ update.enabled = body.enabled;
39
+ store.updateWorkflowSchedule(scheduleId, update);
40
+ const refreshed = store.getWorkflowSchedule(scheduleId);
41
+ sendJson(response, 200, {
42
+ schedule: refreshed ? serializeWorkflowSchedule(refreshed) : null,
43
+ });
44
+ }),
45
+ route('DELETE', '/api/workflow-schedules/:scheduleId', ({ params, request, response, store }) => {
46
+ requireUiTokenFromRequest(request, store.validateUiToken);
47
+ const scheduleId = getRequiredParam(response, params, 'scheduleId', 'Missing scheduleId');
48
+ if (!scheduleId)
49
+ return;
50
+ const existing = store.getWorkflowSchedule(scheduleId);
51
+ if (!existing) {
52
+ sendJson(response, 404, { error: `Schedule not found: ${scheduleId}` });
53
+ return;
54
+ }
55
+ store.deleteWorkflowSchedule(scheduleId);
56
+ sendJson(response, 200, { ok: true });
57
+ }),
58
+ ];
@@ -0,0 +1,2 @@
1
+ import type { RouteDefinition } from './route-types.js';
2
+ export declare const workflowRoutes: RouteDefinition[];
@@ -0,0 +1,83 @@
1
+ import { getRequiredParam, route, sendJson } from './route-helpers.js';
2
+ import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
3
+ import { serializeWorkflowDispatch, serializeWorkflowRun } from './workflow-http-serializers.js';
4
+ // Workflows are authored and fired by the orchestrator agent (`team workflow
5
+ // run` / `team workflow schedule`), never from a human script library — so the
6
+ // UI surface here is purely OBSERVABILITY + run control (list / get / stop /
7
+ // per-agent dispatch timeline / narrator logs). There is no list-scripts,
8
+ // start-by-path, template-install, or source-editor route.
9
+ export const workflowRoutes = [
10
+ route('GET', '/api/workspaces/:workspaceId/workflows/runs', ({ params, request, response, store }) => {
11
+ requireUiTokenFromRequest(request, store.validateUiToken);
12
+ const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Missing workspaceId');
13
+ if (!workspaceId)
14
+ return;
15
+ sendJson(response, 200, {
16
+ runs: store.listWorkspaceWorkflowRuns(workspaceId).map(serializeWorkflowRun),
17
+ });
18
+ }),
19
+ route('GET', '/api/workflows/runs/:runId', ({ params, request, response, store }) => {
20
+ requireUiTokenFromRequest(request, store.validateUiToken);
21
+ const runId = getRequiredParam(response, params, 'runId', 'Missing runId');
22
+ if (!runId)
23
+ return;
24
+ const run = store.getWorkflowRun(runId);
25
+ if (!run) {
26
+ sendJson(response, 404, { error: `Workflow run not found: ${runId}` });
27
+ return;
28
+ }
29
+ sendJson(response, 200, { run: serializeWorkflowRun(run) });
30
+ }),
31
+ route('POST', '/api/workflows/runs/:runId/stop', ({ params, request, response, store }) => {
32
+ requireUiTokenFromRequest(request, store.validateUiToken);
33
+ const runId = getRequiredParam(response, params, 'runId', 'Missing runId');
34
+ if (!runId)
35
+ return;
36
+ const run = store.getWorkflowRun(runId);
37
+ if (!run) {
38
+ sendJson(response, 404, { error: `Workflow run not found: ${runId}` });
39
+ return;
40
+ }
41
+ const stopped = store.stopWorkflowRun(runId);
42
+ sendJson(response, stopped ? 202 : 409, {
43
+ ok: stopped,
44
+ ...(stopped ? {} : { error: `Run is not running (status=${run.status})` }),
45
+ });
46
+ }),
47
+ route('GET', '/api/workflows/runs/:runId/dispatches', ({ params, request, response, store }) => {
48
+ requireUiTokenFromRequest(request, store.validateUiToken);
49
+ const runId = getRequiredParam(response, params, 'runId', 'Missing runId');
50
+ if (!runId)
51
+ return;
52
+ const run = store.getWorkflowRun(runId);
53
+ if (!run) {
54
+ sendJson(response, 404, { error: `Workflow run not found: ${runId}` });
55
+ return;
56
+ }
57
+ /* TIER 2 #6 — enrich `submitted` dispatches with the worker's
58
+ lastPtyLine so the Drawer can show what each ephemeral worker is
59
+ currently doing (without forcing the user to jump to the
60
+ worker's terminal pane). For reported/cancelled rows the report
61
+ text is already authoritative, no need to also surface PTY noise. */
62
+ const dispatches = store.listWorkflowRunDispatches(runId).map((d) => {
63
+ if (d.status !== 'submitted')
64
+ return d;
65
+ const lastPtyLine = store.getLastPtyLineForAgent(run.workspaceId, d.toAgentId);
66
+ return lastPtyLine === null ? d : { ...d, lastPtyLine };
67
+ });
68
+ sendJson(response, 200, { dispatches: dispatches.map(serializeWorkflowDispatch) });
69
+ }),
70
+ /* TIER 2 #3 — narrator lane. Drawer polls this alongside dispatches.
71
+ Same auth path; same 404 semantics. */
72
+ route('GET', '/api/workflows/runs/:runId/logs', ({ params, request, response, store }) => {
73
+ requireUiTokenFromRequest(request, store.validateUiToken);
74
+ const runId = getRequiredParam(response, params, 'runId', 'Missing runId');
75
+ if (!runId)
76
+ return;
77
+ if (!store.getWorkflowRun(runId)) {
78
+ sendJson(response, 404, { error: `Workflow run not found: ${runId}` });
79
+ return;
80
+ }
81
+ sendJson(response, 200, { logs: store.listWorkflowRunLogs(runId) });
82
+ }),
83
+ ];
@@ -9,6 +9,8 @@ import { taskRoutes } from './routes-tasks.js';
9
9
  import { teamRoutes } from './routes-team.js';
10
10
  import { uiRoutes } from './routes-ui.js';
11
11
  import { versionRoutes } from './routes-version.js';
12
+ import { workflowScheduleRoutes } from './routes-workflow-schedules.js';
13
+ import { workflowRoutes } from './routes-workflows.js';
12
14
  import { workspaceRoutes } from './routes-workspaces.js';
13
15
  const routes = [
14
16
  ...workspaceRoutes,
@@ -22,6 +24,8 @@ const routes = [
22
24
  ...teamRoutes,
23
25
  ...fsRoutes,
24
26
  ...marketplaceRoutes,
27
+ ...workflowRoutes,
28
+ ...workflowScheduleRoutes,
25
29
  ];
26
30
  export const matchRoute = (method, pathname) => {
27
31
  for (const routeDefinition of routes) {
@@ -2,7 +2,7 @@ import type { AgentRunStorePort } from './agent-runtime-ports.js';
2
2
  import type { MessageLogHandle, MessageLogRecord, RecoveryMessage } from './message-log-store.js';
3
3
  import type { TasksFileService } from './tasks-file.js';
4
4
  import type { WorkspaceStore } from './workspace-store.js';
5
- export declare const buildRuntimeRestartPolicy: ({ agentRunStore, messageLogStore, tasksFileService, workspaceStore, }: {
5
+ export declare const buildRuntimeRestartPolicy: ({ agentRunStore, messageLogStore, tasksFileService, workspaceStore, getWorkflowsEnabled, getAutostaffEnabled, }: {
6
6
  agentRunStore: Pick<AgentRunStorePort, "listAgentRuns">;
7
7
  messageLogStore: {
8
8
  deleteMessage: (handle: MessageLogHandle) => void;
@@ -11,4 +11,6 @@ export declare const buildRuntimeRestartPolicy: ({ agentRunStore, messageLogStor
11
11
  };
12
12
  tasksFileService: Pick<TasksFileService, "readTasks">;
13
13
  workspaceStore: Pick<WorkspaceStore, "getWorkspaceSnapshot">;
14
+ getWorkflowsEnabled?: () => boolean;
15
+ getAutostaffEnabled?: () => boolean;
14
16
  }) => import("./restart-policy.js").RestartPolicy;
@@ -1,10 +1,12 @@
1
1
  import { createRestartPolicy } from './restart-policy.js';
2
2
  // Narrow helper keeps runtime-store under the hard line cap.
3
- export const buildRuntimeRestartPolicy = ({ agentRunStore, messageLogStore, tasksFileService, workspaceStore, }) => createRestartPolicy({
3
+ export const buildRuntimeRestartPolicy = ({ agentRunStore, messageLogStore, tasksFileService, workspaceStore, getWorkflowsEnabled, getAutostaffEnabled, }) => createRestartPolicy({
4
4
  deleteMessage: messageLogStore.deleteMessage,
5
5
  getWorkspaceSnapshot: workspaceStore.getWorkspaceSnapshot,
6
6
  insertMessage: messageLogStore.insertMessage,
7
7
  listAgentRuns: agentRunStore.listAgentRuns,
8
8
  listMessagesForRecovery: messageLogStore.listMessagesForRecovery,
9
9
  readTasks: tasksFileService.readTasks,
10
+ ...(getWorkflowsEnabled ? { getWorkflowsEnabled } : {}),
11
+ ...(getAutostaffEnabled ? { getAutostaffEnabled } : {}),
10
12
  });
@@ -0,0 +1,122 @@
1
+ import type { AgentSummary, TeamListItem, WorkspaceSummary } from '../shared/types.js';
2
+ import type { AgentManager } from './agent-manager.js';
3
+ import type { AgentLaunchConfigInput, PersistedAgentRun } from './agent-run-store.js';
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 { WorkflowDispatchAwaiter } from './workflow-dispatch-awaiter.js';
12
+ import type { WorkflowRunRecord } from './workflow-run-store.js';
13
+ import type { RunInlineWorkflowInput, RunWorkflowInput, WorkflowRunner } from './workflow-runner.js';
14
+ import type { WorkflowScheduleRecord } from './workflow-schedule-store.js';
15
+ import type { WorkerInput, WorkspaceRecord } from './workspace-store.js';
16
+ export interface RuntimeStore {
17
+ close: () => Promise<void>;
18
+ createWorkspace: (path: string, name: string) => WorkspaceSummary;
19
+ deleteWorkspace: (workspaceId: string) => Promise<void>;
20
+ listWorkspaces: () => WorkspaceSummary[];
21
+ addWorker: (workspaceId: string, input: WorkerInput) => AgentSummary;
22
+ addWorkerWithLaunch: (workspaceId: string, input: WorkerInput, launchConfig: AgentLaunchConfigInput) => AgentSummary;
23
+ deleteWorker: (workspaceId: string, workerId: string) => void;
24
+ renameWorker: (workspaceId: string, workerId: string, name: string) => AgentSummary;
25
+ recordUserInput: (workspaceId: string, orchestratorId: string, text: string) => void;
26
+ dispatchTask: (workspaceId: string, workerId: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord>;
27
+ dispatchTaskByWorkerName: (workspaceId: string, workerName: string, text: string, input?: DispatchTaskInput) => Promise<DispatchRecord & {
28
+ restartedWorker: boolean;
29
+ }>;
30
+ reportTask: (workspaceId: string, workerId: string, input?: ReportTaskInput) => ReportTaskResult;
31
+ statusTask: (workspaceId: string, workerId: string, input?: StatusTaskInput) => ReportTaskResult;
32
+ cancelTask: (workspaceId: string, dispatchId: string, input: CancelTaskInput) => ReportTaskResult;
33
+ listDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
34
+ listWorkers: (workspaceId: string) => TeamListItem[];
35
+ getLastPtyLineForAgent: (workspaceId: string, agentId: string) => string | null;
36
+ getWorkspaceSnapshot: (workspaceId: string) => WorkspaceRecord;
37
+ getWorker: (workspaceId: string, workerId: string) => AgentSummary;
38
+ getAgent: (workspaceId: string, agentId: string) => AgentSummary;
39
+ getPtyOutputBus: () => PtyOutputBus;
40
+ listTerminalRuns: (workspaceId: string) => TerminalRunSummary[];
41
+ closeWorkspaceShell: (workspaceId: string, runId: string) => boolean;
42
+ startWorkspaceShell: (workspaceId: string) => Promise<LiveAgentRun>;
43
+ configureAgentLaunch: (workspaceId: string, agentId: string, input: AgentLaunchConfigInput) => void;
44
+ peekAgentLaunchConfig: (workspaceId: string, agentId: string) => AgentLaunchConfigInput | undefined;
45
+ startAgent: (workspaceId: string, agentId: string, input: StartAgentOptions) => Promise<LiveAgentRun>;
46
+ autostartConfiguredAgents: (input: StartAgentOptions) => Promise<Array<{
47
+ agent_id: string;
48
+ error: string | null;
49
+ ok: boolean;
50
+ run_id: string | null;
51
+ workspace_id: string;
52
+ }>>;
53
+ startWorkspaceWatch: (workspaceId: string) => Promise<void>;
54
+ getLiveRun: (runId: string) => LiveAgentRun;
55
+ getActiveRunByAgentId: (workspaceId: string, agentId: string) => LiveAgentRun | undefined;
56
+ registerTasksListener: (listener: (workspaceId: string, content: string) => void) => () => void;
57
+ listAgentRuns: (agentId: string) => PersistedAgentRun[];
58
+ listMessagesForRecovery: (workspaceId: string, sinceMs: number) => RecoveryMessage[];
59
+ peekAgentToken: (agentId: string) => string | undefined;
60
+ pauseTerminalRun: (runId: string) => void;
61
+ resizeAgentRun: (runId: string, cols: number, rows: number) => void;
62
+ resumeTerminalRun: (runId: string) => void;
63
+ settings: SettingsStore;
64
+ writeRunInput: (runId: string, input: Buffer | string) => void;
65
+ getUiToken: () => string;
66
+ stopAgentRun: (runId: string) => void;
67
+ validateAgentToken: (agentId: string, token: string | undefined) => boolean;
68
+ validateUiToken: (token: string | undefined) => boolean;
69
+ getWorkflowDispatchAwaiter: () => WorkflowDispatchAwaiter;
70
+ runWorkflow: (input: RunWorkflowInput) => Promise<WorkflowRunRecord>;
71
+ startWorkflow: (input: RunWorkflowInput) => Promise<WorkflowRunRecord>;
72
+ startWorkflowInline: (input: RunInlineWorkflowInput) => Promise<WorkflowRunRecord>;
73
+ stopWorkflowRun: (runId: string) => boolean;
74
+ getWorkflowRun: (runId: string) => WorkflowRunRecord | undefined;
75
+ listWorkspaceWorkflowRuns: (workspaceId: string) => WorkflowRunRecord[];
76
+ listWorkflowRunDispatches: (runId: string) => DispatchRecord[];
77
+ listWorkflowRunLogs: (runId: string) => Array<{
78
+ id: number;
79
+ ts: number;
80
+ message: string;
81
+ }>;
82
+ createWorkflowSchedule: (input: {
83
+ workspaceId: string;
84
+ scriptPath: string;
85
+ cron: string;
86
+ nextRunAt: number;
87
+ args?: unknown;
88
+ enabled?: boolean;
89
+ }) => WorkflowScheduleRecord;
90
+ scheduleWorkflowInline: (input: {
91
+ workspaceId: string;
92
+ source: string;
93
+ name: string;
94
+ cron: string;
95
+ nextRunAt: number;
96
+ args?: unknown;
97
+ }) => Promise<WorkflowScheduleRecord>;
98
+ updateWorkflowSchedule: (id: string, input: {
99
+ cron?: string;
100
+ args?: unknown;
101
+ enabled?: boolean;
102
+ lastRunAt?: number;
103
+ nextRunAt?: number;
104
+ }) => void;
105
+ getWorkflowSchedule: (id: string) => WorkflowScheduleRecord | undefined;
106
+ listWorkspaceWorkflowSchedules: (workspaceId: string) => WorkflowScheduleRecord[];
107
+ deleteWorkflowSchedule: (id: string) => void;
108
+ }
109
+ export interface RuntimeStoreOptions {
110
+ dataDir?: string;
111
+ agentManager?: AgentManager;
112
+ }
113
+ export interface StartAgentOptions {
114
+ hivePort: string;
115
+ }
116
+ export type RuntimeWorkflowRuntime = {
117
+ runner: WorkflowRunner;
118
+ scheduler: {
119
+ close: () => void;
120
+ start: () => void;
121
+ };
122
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -12,6 +12,10 @@ import { createTasksFileWatcher } from './tasks-file-watcher.js';
12
12
  import { createTeamOperations } from './team-operations.js';
13
13
  import { createUiAuth } from './ui-auth.js';
14
14
  import { type WorkerOutputTracker } from './worker-output-tracker.js';
15
+ import { type WorkflowDispatchAwaiter } from './workflow-dispatch-awaiter.js';
16
+ import { createWorkflowRunLogStore } from './workflow-run-log-store.js';
17
+ import { createWorkflowRunStore } from './workflow-run-store.js';
18
+ import { createWorkflowScheduleStore } from './workflow-schedule-store.js';
15
19
  import { createWorkspaceShellRuntime } from './workspace-shell-runtime.js';
16
20
  import { createWorkspaceStore } from './workspace-store.js';
17
21
  export interface RuntimeStoreServices {
@@ -28,6 +32,10 @@ export interface RuntimeStoreServices {
28
32
  teamOps: ReturnType<typeof createTeamOperations>;
29
33
  uiAuth: ReturnType<typeof createUiAuth>;
30
34
  workerOutputTracker: WorkerOutputTracker | null;
35
+ workflowDispatchAwaiter: WorkflowDispatchAwaiter;
36
+ workflowRunLogStore: ReturnType<typeof createWorkflowRunLogStore>;
37
+ workflowRunStore: ReturnType<typeof createWorkflowRunStore>;
38
+ workflowScheduleStore: ReturnType<typeof createWorkflowScheduleStore>;
31
39
  workspaceStore: ReturnType<typeof createWorkspaceStore>;
32
40
  }
33
41
  interface CreateRuntimeStoreServicesOptions {
@@ -38,6 +46,7 @@ interface CreateRuntimeStoreLifecycleOptions {
38
46
  agentManager?: AgentManager;
39
47
  services: RuntimeStoreServices;
40
48
  }
49
+ export declare const logTasksFileWatchStartError: (workspaceId: string, error: unknown) => void;
41
50
  export declare const createRuntimeStoreServices: (options?: CreateRuntimeStoreServicesOptions) => RuntimeStoreServices;
42
51
  export declare const createRuntimeStoreLifecycle: ({ agentManager, services, }: CreateRuntimeStoreLifecycleOptions) => {
43
52
  close: () => Promise<void>;