@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.
- package/CHANGELOG.md +39 -0
- package/README.en.md +23 -1
- package/README.md +10 -1
- package/dist/src/cli/team.js +19 -2
- package/dist/src/server/agent-run-bootstrap.d.ts +6 -1
- package/dist/src/server/agent-run-bootstrap.js +5 -2
- package/dist/src/server/agent-run-starter.d.ts +6 -7
- package/dist/src/server/agent-run-starter.js +18 -4
- package/dist/src/server/agent-runtime-contract.d.ts +10 -0
- package/dist/src/server/agent-runtime-stop-run.d.ts +1 -1
- package/dist/src/server/agent-runtime-stop-run.js +4 -1
- package/dist/src/server/agent-runtime.d.ts +2 -1
- package/dist/src/server/agent-runtime.js +10 -5
- package/dist/src/server/agent-startup-instructions.d.ts +7 -8
- package/dist/src/server/agent-startup-instructions.js +6 -4
- package/dist/src/server/agent-stdin-dispatcher.d.ts +20 -7
- package/dist/src/server/agent-stdin-dispatcher.js +22 -10
- package/dist/src/server/command-preset-defaults.js +12 -0
- package/dist/src/server/feature-flags.d.ts +42 -0
- package/dist/src/server/feature-flags.js +24 -0
- package/dist/src/server/hive-team-guidance.d.ts +4 -3
- package/dist/src/server/hive-team-guidance.js +17 -16
- package/dist/src/server/post-start-input-writer.js +2 -2
- package/dist/src/server/preset-launch-support.js +2 -1
- package/dist/src/server/recovery-summary.d.ts +5 -6
- package/dist/src/server/recovery-summary.js +3 -2
- package/dist/src/server/report-outbox-store.d.ts +36 -0
- package/dist/src/server/report-outbox-store.js +33 -0
- package/dist/src/server/restart-policy-support.d.ts +4 -5
- package/dist/src/server/restart-policy.d.ts +5 -1
- package/dist/src/server/restart-policy.js +51 -33
- package/dist/src/server/routes-settings.js +3 -3
- package/dist/src/server/routes-tasks.js +23 -0
- package/dist/src/server/routes-workspaces.js +5 -0
- package/dist/src/server/runtime-restart-policy.d.ts +3 -3
- package/dist/src/server/runtime-restart-policy.js +2 -3
- package/dist/src/server/runtime-store-contract.d.ts +3 -0
- package/dist/src/server/runtime-store-helpers.d.ts +2 -0
- package/dist/src/server/runtime-store-helpers.js +14 -9
- package/dist/src/server/runtime-store-workflows.js +8 -0
- package/dist/src/server/runtime-store.js +1 -0
- package/dist/src/server/session-capture.d.ts +6 -0
- package/dist/src/server/session-capture.js +32 -0
- package/dist/src/server/sqlite-schema-v22.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v22.js +27 -0
- package/dist/src/server/sqlite-schema.d.ts +1 -1
- package/dist/src/server/sqlite-schema.js +19 -1
- package/dist/src/server/task-deps.d.ts +32 -0
- package/dist/src/server/task-deps.js +40 -0
- package/dist/src/server/tasks-file-watcher.d.ts +6 -7
- package/dist/src/server/tasks-file-watcher.js +3 -2
- package/dist/src/server/tasks-file.d.ts +2 -1
- package/dist/src/server/tasks-file.js +3 -2
- package/dist/src/server/team-authz.d.ts +1 -1
- package/dist/src/server/team-authz.js +1 -0
- package/dist/src/server/team-operations.d.ts +7 -1
- package/dist/src/server/team-operations.js +81 -19
- package/dist/src/server/webhook-notifier.d.ts +34 -0
- package/dist/src/server/webhook-notifier.js +47 -0
- package/dist/src/server/workflow-cli-policy.d.ts +1 -1
- package/dist/src/server/workflow-cli-policy.js +1 -1
- package/dist/src/server/workflow-output-schema.d.ts +18 -0
- package/dist/src/server/workflow-output-schema.js +41 -0
- package/dist/src/server/workflow-runner.js +12 -2
- package/dist/src/shared/types.d.ts +2 -2
- package/package.json +1 -1
- package/web/dist/assets/{AddWorkerDialog-CcC-7kgG.js → AddWorkerDialog-BRUxpa3f.js} +2 -2
- package/web/dist/assets/{AddWorkspaceDialog-BDpOTfmt.js → AddWorkspaceDialog-D56x5JCb.js} +1 -1
- package/web/dist/assets/{FirstRunWizard-BYX_ocQn.js → FirstRunWizard-BFVaMIsE.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-DUxSk7db.js → MarketplaceDrawer-DeEZ35dN.js} +1 -1
- package/web/dist/assets/{WhatsNewDialog-B_RlCXcV.js → WhatsNewDialog-CHkZeINH.js} +1 -1
- package/web/dist/assets/{WorkerModal-D9-7YfZZ.js → WorkerModal-BBCuMLIa.js} +1 -1
- package/web/dist/assets/{WorkspaceTaskDrawer-BCKoF7qc.js → WorkspaceTaskDrawer-CpZHAcj1.js} +1 -1
- package/web/dist/assets/WorkspaceTerminalPanels-7If2mDyp.js +1 -0
- package/web/dist/assets/index-5zh61jMg.css +1 -0
- package/web/dist/assets/index-CxNL0O-C.js +73 -0
- package/web/dist/cli-icons/hermes.png +0 -0
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/WorkspaceTerminalPanels-Dq8y91t2.js +0 -1
- package/web/dist/assets/index-BiOvKIVw.css +0 -1
- 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:
|
|
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">,
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
|
184
|
-
() => agent(\`Independently judge: \${claim}
|
|
185
|
-
() => agent(\`Repro test: \${claim}. Did
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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,
|
|
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
|
-
/**
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
/** Experimental auto-staff gate (default on) — same, for the team-sizing rule. */
|
|
14
|
-
autostaffEnabled?: boolean;
|
|
11
|
+
/** Live experimental flags — keep 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,
|
|
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,
|
|
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
|
-
/**
|
|
15
|
-
* so it matches
|
|
16
|
-
|
|
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,
|
|
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,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
...(
|
|
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>;
|