@tt-a1i/hive 1.5.0 → 1.7.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 (82) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.en.md +23 -1
  3. package/README.md +10 -1
  4. package/dist/src/cli/team.js +19 -2
  5. package/dist/src/server/agent-run-bootstrap.d.ts +6 -1
  6. package/dist/src/server/agent-run-bootstrap.js +5 -2
  7. package/dist/src/server/agent-run-starter.d.ts +6 -7
  8. package/dist/src/server/agent-run-starter.js +18 -4
  9. package/dist/src/server/agent-runtime-contract.d.ts +10 -0
  10. package/dist/src/server/agent-runtime-stop-run.d.ts +1 -1
  11. package/dist/src/server/agent-runtime-stop-run.js +4 -1
  12. package/dist/src/server/agent-runtime.d.ts +2 -1
  13. package/dist/src/server/agent-runtime.js +10 -5
  14. package/dist/src/server/agent-startup-instructions.d.ts +7 -8
  15. package/dist/src/server/agent-startup-instructions.js +6 -4
  16. package/dist/src/server/agent-stdin-dispatcher.d.ts +20 -7
  17. package/dist/src/server/agent-stdin-dispatcher.js +22 -10
  18. package/dist/src/server/command-preset-defaults.js +12 -0
  19. package/dist/src/server/feature-flags.d.ts +42 -0
  20. package/dist/src/server/feature-flags.js +24 -0
  21. package/dist/src/server/hive-team-guidance.d.ts +4 -3
  22. package/dist/src/server/hive-team-guidance.js +17 -16
  23. package/dist/src/server/post-start-input-writer.js +2 -2
  24. package/dist/src/server/preset-launch-support.js +2 -1
  25. package/dist/src/server/recovery-summary.d.ts +5 -6
  26. package/dist/src/server/recovery-summary.js +3 -2
  27. package/dist/src/server/report-outbox-store.d.ts +36 -0
  28. package/dist/src/server/report-outbox-store.js +33 -0
  29. package/dist/src/server/restart-policy-support.d.ts +4 -5
  30. package/dist/src/server/restart-policy.d.ts +5 -1
  31. package/dist/src/server/restart-policy.js +51 -33
  32. package/dist/src/server/routes-settings.js +3 -3
  33. package/dist/src/server/routes-tasks.js +23 -0
  34. package/dist/src/server/routes-workspaces.js +5 -0
  35. package/dist/src/server/runtime-restart-policy.d.ts +3 -3
  36. package/dist/src/server/runtime-restart-policy.js +2 -3
  37. package/dist/src/server/runtime-store-contract.d.ts +3 -0
  38. package/dist/src/server/runtime-store-helpers.d.ts +2 -0
  39. package/dist/src/server/runtime-store-helpers.js +14 -9
  40. package/dist/src/server/runtime-store-workflows.js +8 -0
  41. package/dist/src/server/runtime-store.js +1 -0
  42. package/dist/src/server/session-capture.d.ts +6 -0
  43. package/dist/src/server/session-capture.js +32 -0
  44. package/dist/src/server/sqlite-schema-v22.d.ts +2 -0
  45. package/dist/src/server/sqlite-schema-v22.js +27 -0
  46. package/dist/src/server/sqlite-schema.d.ts +1 -1
  47. package/dist/src/server/sqlite-schema.js +19 -1
  48. package/dist/src/server/task-deps.d.ts +32 -0
  49. package/dist/src/server/task-deps.js +40 -0
  50. package/dist/src/server/tasks-file-watcher.d.ts +6 -7
  51. package/dist/src/server/tasks-file-watcher.js +3 -2
  52. package/dist/src/server/tasks-file.d.ts +2 -1
  53. package/dist/src/server/tasks-file.js +3 -2
  54. package/dist/src/server/team-authz.d.ts +1 -1
  55. package/dist/src/server/team-authz.js +1 -0
  56. package/dist/src/server/team-operations.d.ts +7 -1
  57. package/dist/src/server/team-operations.js +81 -19
  58. package/dist/src/server/webhook-notifier.d.ts +34 -0
  59. package/dist/src/server/webhook-notifier.js +47 -0
  60. package/dist/src/server/workflow-cli-policy.d.ts +1 -1
  61. package/dist/src/server/workflow-cli-policy.js +1 -1
  62. package/dist/src/server/workflow-output-schema.d.ts +18 -0
  63. package/dist/src/server/workflow-output-schema.js +41 -0
  64. package/dist/src/server/workflow-runner.js +12 -2
  65. package/dist/src/shared/types.d.ts +2 -2
  66. package/package.json +1 -1
  67. package/web/dist/assets/{AddWorkerDialog-CcC-7kgG.js → AddWorkerDialog-BRUxpa3f.js} +2 -2
  68. package/web/dist/assets/{AddWorkspaceDialog-BDpOTfmt.js → AddWorkspaceDialog-D56x5JCb.js} +1 -1
  69. package/web/dist/assets/{FirstRunWizard-BYX_ocQn.js → FirstRunWizard-BFVaMIsE.js} +1 -1
  70. package/web/dist/assets/{MarketplaceDrawer-DUxSk7db.js → MarketplaceDrawer-DeEZ35dN.js} +1 -1
  71. package/web/dist/assets/{WhatsNewDialog-B_RlCXcV.js → WhatsNewDialog-CHkZeINH.js} +1 -1
  72. package/web/dist/assets/{WorkerModal-D9-7YfZZ.js → WorkerModal-BBCuMLIa.js} +1 -1
  73. package/web/dist/assets/{WorkspaceTaskDrawer-BCKoF7qc.js → WorkspaceTaskDrawer-CpZHAcj1.js} +1 -1
  74. package/web/dist/assets/WorkspaceTerminalPanels-7If2mDyp.js +1 -0
  75. package/web/dist/assets/index-5zh61jMg.css +1 -0
  76. package/web/dist/assets/index-CxNL0O-C.js +73 -0
  77. package/web/dist/cli-icons/hermes.png +0 -0
  78. package/web/dist/index.html +2 -2
  79. package/web/dist/sw.js +1 -1
  80. package/web/dist/assets/WorkspaceTerminalPanels-Dq8y91t2.js +0 -1
  81. package/web/dist/assets/index-BiOvKIVw.css +0 -1
  82. package/web/dist/assets/index-DMRUklT3.js +0 -73
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Every experimental feature flag resolved into one snapshot.
3
+ *
4
+ * Threading this bag through the prompt-build call graph — instead of one
5
+ * `getX` accessor per flag — is the whole point of this module: a new flag adds
6
+ * a field here and is read at the one site that needs it, without touching any
7
+ * of the carriers in between (agent-runtime → run-starter / restart-policy /
8
+ * tasks-watcher → the pure builders in hive-team-guidance).
9
+ *
10
+ * Each flag keeps its own storage semantics and doc in its own file
11
+ * ([[workflow-feature]], [[team-autostaff]]); this module only composes them.
12
+ */
13
+ export interface FeatureFlags {
14
+ /** Workflow runtime — `team workflow`, the scheduler, the Drawer, and the
15
+ * orchestrator guidance that teaches them. Default OFF. */
16
+ workflowsEnabled: boolean;
17
+ /** Auto-staff orchestrator guidance — size the team to the task up front
18
+ * rather than one worker at a time. Default ON. */
19
+ autostaffEnabled: boolean;
20
+ }
21
+ /**
22
+ * Conservative all-off snapshot. Use as the parameter default wherever a
23
+ * builder is called without flags — it preserves the per-flag `= false`
24
+ * defaults the builders carried before this registry existed.
25
+ *
26
+ * This is NOT the storage default: an ABSENT `team.autostaff` key reads back
27
+ * ON via {@link readFeatureFlags}. Only an OMITTED argument falls back to off,
28
+ * which is the safe choice (never inject guidance for a flag nobody resolved).
29
+ */
30
+ export declare const FEATURE_FLAGS_ALL_OFF: FeatureFlags;
31
+ interface AppStateReader {
32
+ getAppState: (key: string) => {
33
+ value: string | null;
34
+ } | undefined;
35
+ }
36
+ /**
37
+ * Read every flag from app_state into one snapshot, each with its own storage
38
+ * default (workflows off-when-absent, auto-staff on-when-absent). Called fresh
39
+ * each time so a Settings toggle takes effect without restarting the runtime.
40
+ */
41
+ export declare const readFeatureFlags: (settings: AppStateReader) => FeatureFlags;
42
+ export {};
@@ -0,0 +1,24 @@
1
+ import { AUTOSTAFF_ENABLED_KEY, readAutostaffEnabled } from './team-autostaff.js';
2
+ import { readWorkflowEnabled, WORKFLOW_ENABLED_KEY } from './workflow-feature.js';
3
+ /**
4
+ * Conservative all-off snapshot. Use as the parameter default wherever a
5
+ * builder is called without flags — it preserves the per-flag `= false`
6
+ * defaults the builders carried before this registry existed.
7
+ *
8
+ * This is NOT the storage default: an ABSENT `team.autostaff` key reads back
9
+ * ON via {@link readFeatureFlags}. Only an OMITTED argument falls back to off,
10
+ * which is the safe choice (never inject guidance for a flag nobody resolved).
11
+ */
12
+ export const FEATURE_FLAGS_ALL_OFF = {
13
+ workflowsEnabled: false,
14
+ autostaffEnabled: false,
15
+ };
16
+ /**
17
+ * Read every flag from app_state into one snapshot, each with its own storage
18
+ * default (workflows off-when-absent, auto-staff on-when-absent). Called fresh
19
+ * each time so a Settings toggle takes effect without restarting the runtime.
20
+ */
21
+ export const readFeatureFlags = (settings) => ({
22
+ workflowsEnabled: readWorkflowEnabled(settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null),
23
+ autostaffEnabled: readAutostaffEnabled(settings.getAppState(AUTOSTAFF_ENABLED_KEY)?.value ?? null),
24
+ });
@@ -1,4 +1,5 @@
1
1
  import type { AgentSummary } from '../shared/types.js';
2
+ import { type FeatureFlags } from './feature-flags.js';
2
3
  import { type WorkflowCliPolicy } from './workflow-cli-policy.js';
3
4
  /**
4
5
  * Tail reminder appended to every message that flows INTO the orchestrator
@@ -17,7 +18,7 @@ import { type WorkflowCliPolicy } from './workflow-cli-policy.js';
17
18
  * the workflow DSL live in `.hive/PROTOCOL.md`, which agents re-read on demand.
18
19
  * A long banner on every turn is itself the noise this envelope exists to beat.
19
20
  */
20
- export declare const buildOrchestratorReminderTail: (workflowsEnabled: boolean) => string;
21
+ export declare const buildOrchestratorReminderTail: ({ workflowsEnabled }: FeatureFlags) => string;
21
22
  /**
22
23
  * Tail reminder appended to dispatches sent TO a worker. Reinforces the
23
24
  * worker identity (so the agent does not regress into its normal CLI
@@ -25,7 +26,7 @@ export declare const buildOrchestratorReminderTail: (workflowsEnabled: boolean)
25
26
  * with dispatch_id pre-bound.
26
27
  */
27
28
  export declare const buildWorkerReminderTail: (dispatchId: string) => string;
28
- export declare const getHiveTeamRules: (agent: Pick<AgentSummary, "role">, workflowsEnabled?: boolean, autostaffEnabled?: boolean) => readonly string[];
29
+ export declare const getHiveTeamRules: (agent: Pick<AgentSummary, "role">, flags?: FeatureFlags) => readonly string[];
29
30
  /**
30
31
  * Workspace-local protocol cheat sheet written to `.hive/PROTOCOL.md`. Agents
31
32
  * are explicitly trained to look at project root markdown when confused, so
@@ -35,4 +36,4 @@ export declare const getHiveTeamRules: (agent: Pick<AgentSummary, "role">, workf
35
36
  * syntax and the workflow DSL reference — the always-on injections only carry
36
37
  * the lean core rules and point here.
37
38
  */
38
- export declare const buildProtocolDoc: (cliPolicy?: WorkflowCliPolicy, workflowsEnabled?: boolean, autostaffEnabled?: boolean) => string;
39
+ export declare const buildProtocolDoc: (cliPolicy?: WorkflowCliPolicy, flags?: FeatureFlags) => string;
@@ -1,3 +1,4 @@
1
+ import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
1
2
  import { DEFAULT_WORKFLOW_CLI_POLICY } from './workflow-cli-policy.js';
2
3
  /**
3
4
  * Tail reminder appended to every message that flows INTO the orchestrator
@@ -16,10 +17,10 @@ import { DEFAULT_WORKFLOW_CLI_POLICY } from './workflow-cli-policy.js';
16
17
  * the workflow DSL live in `.hive/PROTOCOL.md`, which agents re-read on demand.
17
18
  * A long banner on every turn is itself the noise this envelope exists to beat.
18
19
  */
19
- export const buildOrchestratorReminderTail = (workflowsEnabled) => {
20
+ export const buildOrchestratorReminderTail = ({ workflowsEnabled }) => {
20
21
  const body = 'You are the Hive Orchestrator. Reply with one of: ' +
21
22
  '(a) `team send "<worker-name>" "<task>"` to dispatch — run `team list` first if the roster may have changed since your last view (Hive does not push membership changes; stale names fail). ' +
22
- 'If no worker fits or the roster is empty, `team spawn <role> [--cli claude|codex|opencode|gemini]` to create one (add `--ephemeral` for a one-shot), then dispatch; do not ask the user to add workers. ' +
23
+ 'If no worker fits or the roster is empty, `team spawn <role> [--cli claude|codex|opencode|gemini|hermes]` to create one (add `--ephemeral` for a one-shot), then dispatch; do not ask the user to add workers. ' +
23
24
  '(b) `team cancel --dispatch <id> "<reason>"` to close an obsolete dispatch. ' +
24
25
  (workflowsEnabled
25
26
  ? '(c) `team workflow run --stdin` to fan out across 3+ workers or run a staged review→fix — never a loop of `team send` (no barrier, no UI group, no stop button). (d) plain text to the user. '
@@ -52,7 +53,7 @@ const CORE_ORCHESTRATOR_RULES = [
52
53
  'Small, low-risk tasks you can finish in a couple of minutes: do them yourself; do not dispatch for the sake of form. Reach for `team send` when the work needs parallelism, long execution, independent review/test, a dedicated role, or the user explicitly asked for a worker.',
53
54
  'If exactly one worker is available, dispatch to it directly with `team send <worker-name> "<task>"` — do not bounce the choice back to the user.',
54
55
  'When the user says "have a worker do X", dispatch it with `team send <worker-name> "<task>"`.',
55
- 'If the roster is empty or lacks the role the task needs, build the team yourself: `team spawn <role> [--name <n>] [--cli claude|codex|opencode|gemini]`, then immediately dispatch. Do not stop to ask the user to add members — that call is yours.',
56
+ 'If the roster is empty or lacks the role the task needs, build the team yourself: `team spawn <role> [--name <n>] [--cli claude|codex|opencode|gemini|hermes]`, then immediately dispatch. Do not stop to ask the user to add members — that call is yours.',
56
57
  '`team spawn` is PERSISTENT by default (a normal member that stays in the workspace); add `--ephemeral` for a one-shot worker that auto-dismisses after its first `team report`. Rule of thumb: will you reuse this role in the next 10 minutes? Yes → persistent; no → `--ephemeral`.',
57
58
  'When the user cancels or changes direction on an open dispatch, close it explicitly with `team cancel --dispatch <id> "<reason>"` — do not just say "cancel" in prose.',
58
59
  'Each `team send` opens a SEPARATE dispatch with its own pending count and its own required report — it is not a way to tack context onto a dispatch already in flight. Send the same worker twice and it owes you TWO reports; one report closes only one dispatch (the one its `--dispatch <id>` names, or the oldest open one if the flag is omitted), so the other stays open and the worker is pinned on `working`. To change or extend an in-flight task, `team cancel --dispatch <id> "<reason>"` the stale dispatch and re-send the whole task, or wait for the worker to report and dispatch the follow-up after.',
@@ -85,7 +86,7 @@ const buildAutostaffRule = (workflowsEnabled) => 'You may size the team to the t
85
86
  (workflowsEnabled
86
87
  ? ' When the work is a one-shot fan-out across 3+ workers or a staged review→fix that runs to completion, prefer `team workflow run` (barrier, phase tree, stop button) over a batch of `team spawn`; reserve up-front staffing for a standing team you direct interactively across several turns.'
87
88
  : '');
88
- const orchestratorRules = (workflowsEnabled, autostaffEnabled) => [
89
+ const orchestratorRules = ({ workflowsEnabled, autostaffEnabled, }) => [
89
90
  ...CORE_ORCHESTRATOR_RULES,
90
91
  ...(workflowsEnabled ? WORKFLOW_ORCHESTRATOR_RULES : []),
91
92
  ...(autostaffEnabled ? [buildAutostaffRule(workflowsEnabled)] : []),
@@ -98,9 +99,7 @@ const WORKER_RULES = [
98
99
  '`team --help` only prints command syntax — it is NOT a way to report; its output never reaches the Orchestrator. You still owe a real `team report` / `team status` afterward.',
99
100
  'If `team report` / `team status` errors, it also prints USAGE — fix the arguments per USAGE and retry; do not use `team --help` as a stand-in for reporting.',
100
101
  ];
101
- export const getHiveTeamRules = (agent, workflowsEnabled = false, autostaffEnabled = false) => agent.role === 'orchestrator'
102
- ? orchestratorRules(workflowsEnabled, autostaffEnabled)
103
- : WORKER_RULES;
102
+ export const getHiveTeamRules = (agent, flags = FEATURE_FLAGS_ALL_OFF) => (agent.role === 'orchestrator' ? orchestratorRules(flags) : WORKER_RULES);
104
103
  const renderRules = (rules) => rules.map((line) => `- ${line}`).join('\n');
105
104
  /**
106
105
  * The workflow DSL teaching: agent()/parallel()/pipeline() semantics, the
@@ -122,7 +121,7 @@ Host functions injected into the script: \`agent(prompt, opts)\`,
122
121
  \`parallel(thunks)\`, \`pipeline(items, ...stages)\`, \`phase(title)\`,
123
122
  \`log(msg)\`, plus the \`args\` global.
124
123
 
125
- \`agent(prompt, opts)\` opts: \`{ agentType?: "coder"|"reviewer"|"tester"|"custom"|<custom-role-name>, cli?: "claude"|"codex"|"opencode"|"gemini", model?: string, label?: string, timeoutMs?: number }\` — other fields are silently ignored. \`agentType\` also accepts the name of a workspace custom role template (case-insensitive); a typo throws rather than silently falling back to coder. \`model\` is passed through to the worker launch config (\`--model <id>\`), so a 100-way fan-out can use a cheap model and the synthesizer a strong one. The worker auto-dismisses when its \`agent()\` call resolves — you never dismiss it.
124
+ \`agent(prompt, opts)\` opts: \`{ agentType?: "coder"|"reviewer"|"tester"|"custom"|<custom-role-name>, cli?: "claude"|"codex"|"opencode"|"gemini"|"hermes", model?: string, outputSchema?: object, label?: string, timeoutMs?: number }\` — other fields are silently ignored. \`agentType\` also accepts the name of a workspace custom role template (case-insensitive); a typo throws rather than silently falling back to coder. \`model\` is passed through to the worker launch config (\`--model <id>\`), so a 100-way fan-out can use a cheap model and the synthesizer a strong one. \`outputSchema\` makes \`agent()\` resolve to a parsed object instead of a string: the worker is told to end its report with a fenced \`\`\`json block whose keys match the schema. On a parse miss it falls back to \`{ text: "<raw report>" }\`, so ALWAYS treat a missing field as the SAFE default (never assume success). The worker auto-dismisses when its \`agent()\` call resolves — you never dismiss it.
126
125
 
127
126
  \`parallel()\` takes an array of THUNKS (\`() => agent(...)\`), NOT already-started promises: \`parallel([agent(...), agent(...)])\` degrades to unordered concurrency counted as a single step (a no-op grouping), because the promises already started at construction time. \`pipeline(items, ...stages)\` stages are also functions, shape \`(prev, item, i) => agent(...)\`.
128
127
 
@@ -176,15 +175,16 @@ while (dry < 2) {
176
175
  }
177
176
  return confirmed
178
177
  \`\`\`
179
- - **judge panel** — N independent verifiers per finding; majority confirms. Catches plausible-but-wrong findings:
178
+ - **judge panel** — N independent verifiers per finding; majority confirms. \`outputSchema\` makes each vote a typed object instead of fragile yes/no string-matching. Note the safe default: a parse miss has no \`refuted\` key, so \`v.refuted === false\` is false → the finding stays refuted and a malformed reply can never flip it to "confirmed":
180
179
  \`\`\`
181
180
  phase('Verify')
182
181
  const votes = await parallel([
183
- () => agent(\`Try to REFUTE: \${claim}. Default refuted=true if uncertain. Reply yes/no.\`),
184
- () => agent(\`Independently judge: \${claim}. Reply yes/no.\`),
185
- () => agent(\`Repro test: \${claim}. Did you confirm? yes/no.\`),
182
+ () => agent(\`Try to REFUTE: \${claim}. Default refuted=true if uncertain.\`, { outputSchema: { refuted: 'boolean' } }),
183
+ () => agent(\`Independently judge: \${claim}.\`, { outputSchema: { refuted: 'boolean' } }),
184
+ () => agent(\`Repro test: \${claim}. Did it reproduce?\`, { outputSchema: { refuted: 'boolean' } }),
186
185
  ])
187
- const real = votes.filter((v) => v?.toLowerCase().startsWith('y')).length >= 2
186
+ // Only an explicit refuted:false counts as "survived"; missing/unparseable stays safely refuted.
187
+ const real = votes.filter((v) => v && v.refuted === false).length >= 2
188
188
  \`\`\`
189
189
  - **pipeline** — multi-item, multi-stage, no barrier between stages (item A can be in stage 3 while item B is in stage 1); wall-clock = slowest single item, not sum-of-slowest-per-stage. Each stage gets \`(prevResult, originalItem, index)\`; a stage that throws drops that item to null and skips its remaining stages.
190
190
 
@@ -198,7 +198,8 @@ On completion Hive injects \`<hive-system-reminder>Hive workflow ... finished: s
198
198
  * syntax and the workflow DSL reference — the always-on injections only carry
199
199
  * the lean core rules and point here.
200
200
  */
201
- export const buildProtocolDoc = (cliPolicy = DEFAULT_WORKFLOW_CLI_POLICY, workflowsEnabled = false, autostaffEnabled = false) => {
201
+ export const buildProtocolDoc = (cliPolicy = DEFAULT_WORKFLOW_CLI_POLICY, flags = FEATURE_FLAGS_ALL_OFF) => {
202
+ const { workflowsEnabled } = flags;
202
203
  // The `team workflow …` subcommands + the DSL reference + the per-workspace
203
204
  // CLI-policy section only appear when the experimental workflow feature is
204
205
  // ON. While off, the orchestrator's canonical reference never mentions
@@ -252,7 +253,7 @@ export const buildProtocolDoc = (cliPolicy = DEFAULT_WORKFLOW_CLI_POLICY, workfl
252
253
  '',
253
254
  '- `team list` — show workspace members and their status',
254
255
  '- `team send "<worker-name>" "<task>"` — dispatch to a worker by name (never id)',
255
- '- `team spawn <role> [--name <name>] [--cli claude|codex|opencode|gemini]` — create a PERSISTENT member when none fits (or when the roster is empty)',
256
+ '- `team spawn <role> [--name <name>] [--cli claude|codex|opencode|gemini|hermes]` — create a PERSISTENT member when none fits (or when the roster is empty)',
256
257
  '- `team spawn <role> --ephemeral [other-flags]` — create a one-shot worker that auto-dismisses after its first `team report`',
257
258
  '- `team dismiss <worker-name>` — remove an ephemeral worker you spawned',
258
259
  '- `team cancel --dispatch <id> "<reason>"` — cancel an obsolete open dispatch',
@@ -266,7 +267,7 @@ export const buildProtocolDoc = (cliPolicy = DEFAULT_WORKFLOW_CLI_POLICY, workfl
266
267
  '',
267
268
  '## Orchestrator rules',
268
269
  '',
269
- renderRules(orchestratorRules(workflowsEnabled, autostaffEnabled)),
270
+ renderRules(orchestratorRules(flags)),
270
271
  '',
271
272
  '## Worker rules',
272
273
  '',
@@ -1,5 +1,5 @@
1
1
  import { normalizeExecutableToken } from './startup-command-parser.js';
2
- const INTERACTIVE_COMMANDS = new Set(['claude', 'codex', 'gemini', 'opencode']);
2
+ const INTERACTIVE_COMMANDS = new Set(['claude', 'codex', 'gemini', 'hermes', 'opencode']);
3
3
  const READY_CHECK_INTERVAL_MS = 50;
4
4
  const READY_TIMEOUT_MS = 3000;
5
5
  const MIN_SUBMIT_AFTER_PASTE_DELAY_MS = 600;
@@ -19,7 +19,7 @@ const CODEX_PASTE_ACK_TIMEOUT_MS = 10000;
19
19
  // timeout before Enter is sent.
20
20
  const CODEX_PASTE_ACK_MIN_CHARS = 2000;
21
21
  const CODEX_SUBMIT_RETRY_DELAY_MS = 500;
22
- const COMMANDS_WITH_BRACKETED_PASTE = new Set(['claude', 'codex', 'opencode']);
22
+ const COMMANDS_WITH_BRACKETED_PASTE = new Set(['claude', 'codex', 'hermes', 'opencode']);
23
23
  const COMMANDS_WAITING_FOR_PASTE_ACK = new Set(['claude', 'codex']);
24
24
  const BRACKETED_PASTE_END = '\u001b[201~';
25
25
  const PASTE_ACK_PATTERN = /(?:^|[\r\n])\s*(?:[❯›]\s*)?\[(?:Pasted text #\d+[^\]]*|Pasted Content [\d,]+ chars)\]/u;
@@ -63,7 +63,8 @@ const shouldVerifySessionBeforeResume = (capture) => {
63
63
  const supportsPresetResume = (capture) => capture?.source === 'claude_project_jsonl_dir' ||
64
64
  capture?.source === 'codex_session_jsonl_dir' ||
65
65
  capture?.source === 'gemini_session_json_dir' ||
66
- capture?.source === 'opencode_session_db';
66
+ capture?.source === 'opencode_session_db' ||
67
+ capture?.source === 'stdout_regex';
67
68
  export const withPresetResumeArgs = (config, preset, lastSessionId, cwd, discriminator, onInvalidSessionId) => {
68
69
  const launchConfig = normalizeCodexNodeEntrypoint(config, preset);
69
70
  let nextConfig = withPresetYoloArgs(launchConfig, preset);
@@ -1,15 +1,14 @@
1
1
  import type { AgentSummary, WorkspaceSummary } from '../shared/types.js';
2
+ import { type FeatureFlags } from './feature-flags.js';
2
3
  import type { RecoveryMessage } from './message-log-store.js';
3
- export declare const buildRecoverySummary: ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, workflowsEnabled, autostaffEnabled, }: {
4
+ export declare const buildRecoverySummary: ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, flags, }: {
4
5
  agent: AgentSummary;
5
6
  allTaskMessages?: RecoveryMessage[];
6
7
  messages: RecoveryMessage[];
7
8
  tasksContent: string;
8
9
  workers: AgentSummary[];
9
10
  workspace: WorkspaceSummary;
10
- /** Experimental workflow gatekeeps the recovered handover prompt
11
- * consistent with the live flag (omits the workflow rule when off). */
12
- workflowsEnabled?: boolean;
13
- /** Experimental auto-staff gate (default on) — same, for the team-sizing rule. */
14
- autostaffEnabled?: boolean;
11
+ /** Live experimental flagskeep the recovered handover prompt consistent
12
+ * with what a fresh startup would inject (workflow + team-sizing rules). */
13
+ flags?: FeatureFlags;
15
14
  }) => string;
@@ -1,3 +1,4 @@
1
+ import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
1
2
  import { getHiveTeamRules } from './hive-team-guidance.js';
2
3
  import { wrapSystemMessage } from './system-message.js';
3
4
  import { TASKS_RELATIVE_PATH } from './tasks-file.js';
@@ -64,7 +65,7 @@ const formatWorkers = (workers) => {
64
65
  return workers.map((worker) => `- ${worker.name} (${worker.role}, ${worker.status}, pending_task_count: ${worker.pendingTaskCount})`);
65
66
  };
66
67
  const getTaskSectionTitle = (agent) => agent.role === 'orchestrator' ? '## Tasks you dispatched' : '## Tasks recently sent to you';
67
- export const buildRecoverySummary = ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, workflowsEnabled = false, autostaffEnabled = false, }) => wrapSystemMessage([
68
+ export const buildRecoverySummary = ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, flags = FEATURE_FLAGS_ALL_OFF, }) => wrapSystemMessage([
68
69
  `You are ${agent.name} (${agent.role}) in workspace ${workspace.name}.`,
69
70
  'Hive just restarted you and could not recover via native session resume. Here is the handover context.',
70
71
  '',
@@ -84,7 +85,7 @@ export const buildRecoverySummary = ({ agent, allTaskMessages, messages, tasksCo
84
85
  ...formatWorkers(workers),
85
86
  '',
86
87
  agent.role === 'orchestrator' ? '## Hive worker dispatch rules' : '## Hive worker boundaries',
87
- ...getHiveTeamRules(agent, workflowsEnabled, autostaffEnabled),
88
+ ...getHiveTeamRules(agent, flags),
88
89
  '',
89
90
  'Continue from here. If unsure, ask the user.',
90
91
  ].join('\n'));
@@ -0,0 +1,36 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ /**
3
+ * Durable redelivery queue for worker reports the orchestrator could not
4
+ * receive. `team report` persists the dispatch as reported and forwards the
5
+ * report into the orchestrator's stdin best-effort; if that write fails (the
6
+ * PTY exited / is mid-restart, or the orchestrator was down at report time)
7
+ * the report would otherwise be lost — the ledger says "reported" yet the
8
+ * orchestrator waits forever, indistinguishable from a hang.
9
+ *
10
+ * Entries are keyed by dispatch id (UNIQUE) so a report enqueues at most once,
11
+ * and drain marks them delivered only after the orchestrator PTY write
12
+ * actually resolves, so a failed redelivery stays pending for the next drain.
13
+ */
14
+ export interface ReportOutboxEntry {
15
+ id: number;
16
+ workspaceId: string;
17
+ targetAgentId: string;
18
+ dispatchId: string;
19
+ payload: string;
20
+ createdAt: number;
21
+ deliveredAt: number | null;
22
+ }
23
+ interface EnqueueInput {
24
+ workspaceId: string;
25
+ targetAgentId: string;
26
+ dispatchId: string;
27
+ payload: string;
28
+ }
29
+ export declare const createReportOutboxStore: (db: Database) => {
30
+ enqueue: (input: EnqueueInput) => void;
31
+ listPending: (workspaceId: string, targetAgentId: string) => ReportOutboxEntry[];
32
+ markDelivered: (id: number) => void;
33
+ pendingCount: (workspaceId: string, targetAgentId: string) => number;
34
+ };
35
+ export type ReportOutboxStore = ReturnType<typeof createReportOutboxStore>;
36
+ export {};
@@ -0,0 +1,33 @@
1
+ const rowToEntry = (row) => ({
2
+ id: row.id,
3
+ workspaceId: row.workspace_id,
4
+ targetAgentId: row.target_agent_id,
5
+ dispatchId: row.dispatch_id,
6
+ payload: row.payload,
7
+ createdAt: row.created_at,
8
+ deliveredAt: row.delivered_at,
9
+ });
10
+ export const createReportOutboxStore = (db) => {
11
+ // INSERT OR IGNORE on the UNIQUE dispatch_id: a dispatch reports once, so a
12
+ // second enqueue for the same dispatch (e.g. a retry path) is a no-op rather
13
+ // than a duplicate the orchestrator would see twice.
14
+ const enqueue = (input) => {
15
+ db.prepare(`INSERT OR IGNORE INTO report_outbox
16
+ (workspace_id, target_agent_id, dispatch_id, payload, created_at)
17
+ VALUES (?, ?, ?, ?, ?)`).run(input.workspaceId, input.targetAgentId, input.dispatchId, input.payload, Date.now());
18
+ };
19
+ const listPending = (workspaceId, targetAgentId) => db
20
+ .prepare(`SELECT id, workspace_id, target_agent_id, dispatch_id, payload, created_at, delivered_at
21
+ FROM report_outbox
22
+ WHERE workspace_id = ? AND target_agent_id = ? AND delivered_at IS NULL
23
+ ORDER BY created_at ASC, id ASC`)
24
+ .all(workspaceId, targetAgentId).map(rowToEntry);
25
+ const markDelivered = (id) => {
26
+ db.prepare(`UPDATE report_outbox SET delivered_at = ? WHERE id = ? AND delivered_at IS NULL`).run(Date.now(), id);
27
+ };
28
+ const pendingCount = (workspaceId, targetAgentId) => db
29
+ .prepare(`SELECT COUNT(*) AS n FROM report_outbox
30
+ WHERE workspace_id = ? AND target_agent_id = ? AND delivered_at IS NULL`)
31
+ .get(workspaceId, targetAgentId).n;
32
+ return { enqueue, listPending, markDelivered, pendingCount };
33
+ };
@@ -1,5 +1,6 @@
1
1
  import type { AgentSummary, WorkspaceSummary } from '../shared/types.js';
2
2
  import type { PersistedAgentRun } from './agent-run-store.js';
3
+ import type { FeatureFlags } from './feature-flags.js';
3
4
  import type { MessageLogHandle, MessageLogRecord, RecoveryMessage } from './message-log-store.js';
4
5
  export interface RestartPolicyInput {
5
6
  deleteMessage: (handle: MessageLogHandle) => void;
@@ -11,11 +12,9 @@ export interface RestartPolicyInput {
11
12
  listAgentRuns: (agentId: string) => PersistedAgentRun[];
12
13
  listMessagesForRecovery: (workspaceId: string, sinceMs: number) => RecoveryMessage[];
13
14
  readTasks: (workspacePath: string) => string;
14
- /** Experimental workflow gate threaded into the recovery handover prompt
15
- * so it matches the live flag. Optional; omitted → off. */
16
- getWorkflowsEnabled?: () => boolean;
17
- /** Experimental auto-staff gate (default on) — same. */
18
- getAutostaffEnabled?: () => boolean;
15
+ /** Resolves the live experimental flags, threaded into the recovery handover
16
+ * prompt so it matches a fresh startup. Optional; omitted → all off. */
17
+ getFlags?: () => FeatureFlags;
19
18
  }
20
19
  export declare const findPreviousRun: (runs: PersistedAgentRun[], currentRunId: string) => PersistedAgentRun | undefined;
21
20
  export declare const writeSystemMessage: ({ deleteMessage, insertMessage, record, runId, text, writeToRun, }: {
@@ -9,6 +9,10 @@ export interface RestartPolicy {
9
9
  workspace: WorkspaceSummary;
10
10
  writeToRun: (runId: string, text: string) => Promise<void>;
11
11
  }) => boolean;
12
+ /** Record that `runId` was killed at the user's request (Stop button), so a
13
+ * subsequent restart does not inject the crash-recovery handover for a run
14
+ * the user deliberately ended. */
15
+ markUserStopped: (runId: string) => void;
12
16
  }
13
17
  export declare const createNoopRestartPolicy: () => RestartPolicy;
14
- export declare const createRestartPolicy: ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, getWorkflowsEnabled, getAutostaffEnabled, }: RestartPolicyInput) => RestartPolicy;
18
+ export declare const createRestartPolicy: ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, getFlags, }: RestartPolicyInput) => RestartPolicy;
@@ -1,3 +1,4 @@
1
+ import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
1
2
  import { buildRecoverySummary } from './recovery-summary.js';
2
3
  import { findPreviousRun, writeSystemMessage, } from './restart-policy-support.js';
3
4
  import { createSystemRecoverySummaryMessage } from './runtime-message-builders.js';
@@ -6,38 +7,55 @@ export const createNoopRestartPolicy = () => ({
6
7
  injectPostStartMessage() {
7
8
  return false;
8
9
  },
10
+ markUserStopped() { },
9
11
  });
10
- export const createRestartPolicy = ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, getWorkflowsEnabled, getAutostaffEnabled, }) => ({
11
- injectPostStartMessage({ agentId, runId, startConfig, workspace, writeToRun }) {
12
- const previousRun = findPreviousRun(listAgentRuns(agentId), runId);
13
- if (!previousRun)
14
- return false;
15
- const snapshot = getWorkspaceSnapshot(workspace.id);
16
- const agent = snapshot.agents.find((item) => item.id === agentId);
17
- if (!agent)
18
- return false;
19
- const workers = snapshot.agents.filter((item) => item.role !== 'orchestrator' && item.id !== agentId);
20
- const tasksContent = readTasks(snapshot.summary.path);
21
- if (startConfig.resumedSessionId)
12
+ export const createRestartPolicy = ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, getFlags, }) => {
13
+ // Runs the user killed via the Stop button. A deliberate stop is otherwise
14
+ // byte-identical to a crash (both end status 'error'), so without this a
15
+ // stop+Restart would inject the "could not recover" handover with stale open
16
+ // tasks the user may have meant to abandon. In-process only: after a runtime
17
+ // restart this is empty, which is correct — that case IS a recovery.
18
+ const userStoppedRuns = new Set();
19
+ return {
20
+ markUserStopped(runId) {
21
+ userStoppedRuns.add(runId);
22
+ },
23
+ injectPostStartMessage({ agentId, runId, startConfig, workspace, writeToRun }) {
24
+ const previousRun = findPreviousRun(listAgentRuns(agentId), runId);
25
+ if (!previousRun)
26
+ return false;
27
+ // Consume the marker whether or not we end up skipping, so it never
28
+ // lingers to suppress a later genuine crash on the same run id.
29
+ const wasUserStopped = userStoppedRuns.delete(previousRun.runId);
30
+ const snapshot = getWorkspaceSnapshot(workspace.id);
31
+ const agent = snapshot.agents.find((item) => item.id === agentId);
32
+ if (!agent)
33
+ return false;
34
+ const workers = snapshot.agents.filter((item) => item.role !== 'orchestrator' && item.id !== agentId);
35
+ const tasksContent = readTasks(snapshot.summary.path);
36
+ if (startConfig.resumedSessionId)
37
+ return true;
38
+ // Deliberate stop + restart: start fresh, no crash handover.
39
+ if (wasUserStopped)
40
+ return false;
41
+ const text = buildRecoverySummary({
42
+ agent,
43
+ allTaskMessages: listMessagesForRecovery(workspace.id, 0),
44
+ messages: listMessagesForRecovery(workspace.id, Date.now() - RECOVERY_WINDOW_MS),
45
+ tasksContent,
46
+ workers,
47
+ workspace,
48
+ flags: getFlags?.() ?? FEATURE_FLAGS_ALL_OFF,
49
+ });
50
+ writeSystemMessage({
51
+ deleteMessage,
52
+ insertMessage,
53
+ record: createSystemRecoverySummaryMessage(workspace.id, agentId, text),
54
+ runId,
55
+ text,
56
+ writeToRun,
57
+ });
22
58
  return true;
23
- const text = buildRecoverySummary({
24
- agent,
25
- allTaskMessages: listMessagesForRecovery(workspace.id, 0),
26
- messages: listMessagesForRecovery(workspace.id, Date.now() - RECOVERY_WINDOW_MS),
27
- tasksContent,
28
- workers,
29
- workspace,
30
- workflowsEnabled: getWorkflowsEnabled?.() ?? false,
31
- autostaffEnabled: getAutostaffEnabled?.() ?? false,
32
- });
33
- writeSystemMessage({
34
- deleteMessage,
35
- insertMessage,
36
- record: createSystemRecoverySummaryMessage(workspace.id, agentId, text),
37
- runId,
38
- text,
39
- writeToRun,
40
- });
41
- return true;
42
- },
43
- });
59
+ },
60
+ };
61
+ };
@@ -1,4 +1,5 @@
1
1
  import { resolveCommandPath } from './agent-command-resolver.js';
2
+ import { readFeatureFlags } from './feature-flags.js';
2
3
  import { BadRequestError } from './http-errors.js';
3
4
  import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
4
5
  import { ensureProtocolFile } from './tasks-file.js';
@@ -49,11 +50,10 @@ const serializeRoleTemplate = (template) => ({
49
50
  */
50
51
  const refreshWorkflowProtocolDocs = (store) => {
51
52
  const policy = readWorkflowCliPolicy(store.settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null);
52
- const enabled = readWorkflowEnabled(store.settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null);
53
- const autostaff = readAutostaffEnabled(store.settings.getAppState(AUTOSTAFF_ENABLED_KEY)?.value ?? null);
53
+ const flags = readFeatureFlags(store.settings);
54
54
  for (const workspace of store.listWorkspaces()) {
55
55
  try {
56
- ensureProtocolFile(workspace.path, policy, enabled, autostaff);
56
+ ensureProtocolFile(workspace.path, policy, flags);
57
57
  }
58
58
  catch (error) {
59
59
  console.error('[hive] swallowed:settings.refreshProtocol', error);
@@ -1,6 +1,29 @@
1
1
  import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
2
+ import { computeRunnableTasks } from './task-deps.js';
3
+ import { authenticateCliAgent, requireCommandForRole } from './team-authz.js';
2
4
  import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
3
5
  export const taskRoutes = [
6
+ route('GET', '/api/workspaces/:workspaceId/tasks/next', ({ params, request, response, store, tasksFileService }) => {
7
+ const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id is required');
8
+ if (!workspaceId) {
9
+ return;
10
+ }
11
+ // CLI-agent auth (mirrors `team list`): `team next` is an orchestrator
12
+ // planning query, not a UI call.
13
+ const agentId = request.headers['x-hive-agent-id'];
14
+ const token = request.headers['x-hive-agent-token'];
15
+ const agent = authenticateCliAgent({
16
+ fromAgentId: typeof agentId === 'string' ? agentId : undefined,
17
+ getAgent: store.getAgent,
18
+ token: typeof token === 'string' ? token : undefined,
19
+ validateToken: store.validateAgentToken,
20
+ workspaceId,
21
+ });
22
+ requireCommandForRole(agent, 'next');
23
+ const workspace = store.getWorkspaceSnapshot(workspaceId);
24
+ const tasks = computeRunnableTasks(tasksFileService.readTasks(workspace.summary.path));
25
+ sendJson(response, 200, { tasks });
26
+ }),
4
27
  route('GET', '/api/workspaces/:workspaceId/tasks', ({ params, request, response, store, tasksFileService }) => {
5
28
  const workspaceId = getRequiredParam(response, params, 'workspaceId', 'Workspace id is required');
6
29
  if (!workspaceId) {
@@ -78,6 +78,11 @@ export const workspaceRoutes = [
78
78
  workspaceId,
79
79
  });
80
80
  requireCommandForRole(agent, 'list');
81
+ // The orchestrator polling `team list` is its natural post-restart wakeup:
82
+ // flush any reports a prior outage stranded back into its stdin now that
83
+ // it is reachable again. Cheap (indexed, usually empty) and a safe no-op
84
+ // for non-orchestrator callers.
85
+ store.drainReportOutbox(workspaceId);
81
86
  sendJson(response, 200, enrichTeamList(workspaceId, store, store.listWorkers(workspaceId)).map(serializeTeamListItem));
82
87
  }),
83
88
  route('POST', '/api/workspaces/:workspaceId/workers', async ({ params, request, response, store }) => {
@@ -1,8 +1,9 @@
1
1
  import type { AgentRunStorePort } from './agent-runtime-ports.js';
2
+ import type { FeatureFlags } from './feature-flags.js';
2
3
  import type { MessageLogHandle, MessageLogRecord, RecoveryMessage } from './message-log-store.js';
3
4
  import type { TasksFileService } from './tasks-file.js';
4
5
  import type { WorkspaceStore } from './workspace-store.js';
5
- export declare const buildRuntimeRestartPolicy: ({ agentRunStore, messageLogStore, tasksFileService, workspaceStore, getWorkflowsEnabled, getAutostaffEnabled, }: {
6
+ export declare const buildRuntimeRestartPolicy: ({ agentRunStore, messageLogStore, tasksFileService, workspaceStore, getFlags, }: {
6
7
  agentRunStore: Pick<AgentRunStorePort, "listAgentRuns">;
7
8
  messageLogStore: {
8
9
  deleteMessage: (handle: MessageLogHandle) => void;
@@ -11,6 +12,5 @@ export declare const buildRuntimeRestartPolicy: ({ agentRunStore, messageLogStor
11
12
  };
12
13
  tasksFileService: Pick<TasksFileService, "readTasks">;
13
14
  workspaceStore: Pick<WorkspaceStore, "getWorkspaceSnapshot">;
14
- getWorkflowsEnabled?: () => boolean;
15
- getAutostaffEnabled?: () => boolean;
15
+ getFlags?: () => FeatureFlags;
16
16
  }) => import("./restart-policy.js").RestartPolicy;
@@ -1,12 +1,11 @@
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, getWorkflowsEnabled, getAutostaffEnabled, }) => createRestartPolicy({
3
+ export const buildRuntimeRestartPolicy = ({ agentRunStore, messageLogStore, tasksFileService, workspaceStore, getFlags, }) => 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
+ ...(getFlags ? { getFlags } : {}),
12
11
  });
@@ -28,6 +28,9 @@ export interface RuntimeStore {
28
28
  restartedWorker: boolean;
29
29
  }>;
30
30
  reportTask: (workspaceId: string, workerId: string, input?: ReportTaskInput) => ReportTaskResult;
31
+ /** Flush any reports stranded by a prior orchestrator outage. Safe no-op
32
+ * when the orchestrator is down or the outbox is empty. */
33
+ drainReportOutbox: (workspaceId: string) => void;
31
34
  statusTask: (workspaceId: string, workerId: string, input?: StatusTaskInput) => ReportTaskResult;
32
35
  cancelTask: (workspaceId: string, dispatchId: string, input: CancelTaskInput) => ReportTaskResult;
33
36
  listDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
@@ -11,6 +11,7 @@ import { createTasksFileService } from './tasks-file.js';
11
11
  import { createTasksFileWatcher } from './tasks-file-watcher.js';
12
12
  import { createTeamOperations } from './team-operations.js';
13
13
  import { createUiAuth } from './ui-auth.js';
14
+ import { createWebhookNotifier } from './webhook-notifier.js';
14
15
  import { type WorkerOutputTracker } from './worker-output-tracker.js';
15
16
  import { type WorkflowDispatchAwaiter } from './workflow-dispatch-awaiter.js';
16
17
  import { createWorkflowRunLogStore } from './workflow-run-log-store.js';
@@ -31,6 +32,7 @@ export interface RuntimeStoreServices {
31
32
  tasksFileService: ReturnType<typeof createTasksFileService>;
32
33
  teamOps: ReturnType<typeof createTeamOperations>;
33
34
  uiAuth: ReturnType<typeof createUiAuth>;
35
+ webhookNotifier: ReturnType<typeof createWebhookNotifier>;
34
36
  workerOutputTracker: WorkerOutputTracker | null;
35
37
  workflowDispatchAwaiter: WorkflowDispatchAwaiter;
36
38
  workflowRunLogStore: ReturnType<typeof createWorkflowRunLogStore>;