@tt-a1i/hive 1.5.0 → 1.6.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 +25 -0
- package/README.en.md +21 -0
- package/README.md +8 -0
- package/dist/src/cli/team.js +17 -0
- package/dist/src/server/agent-run-starter.d.ts +6 -7
- package/dist/src/server/agent-run-starter.js +3 -3
- 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 +5 -3
- 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/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 +14 -13
- 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/sqlite-schema.js +13 -0
- 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-output-schema.d.ts +18 -0
- package/dist/src/server/workflow-output-schema.js +41 -0
- package/dist/src/server/workflow-runner.js +11 -1
- package/package.json +1 -1
- package/web/dist/assets/{AddWorkerDialog-CcC-7kgG.js → AddWorkerDialog-CGbaxu0T.js} +2 -2
- package/web/dist/assets/{AddWorkspaceDialog-BDpOTfmt.js → AddWorkspaceDialog-CNgExu6b.js} +1 -1
- package/web/dist/assets/{FirstRunWizard-BYX_ocQn.js → FirstRunWizard-DxGApUNc.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-DUxSk7db.js → MarketplaceDrawer-Bk6cpukn.js} +1 -1
- package/web/dist/assets/{WhatsNewDialog-B_RlCXcV.js → WhatsNewDialog-CSGzk-2U.js} +1 -1
- package/web/dist/assets/{WorkerModal-D9-7YfZZ.js → WorkerModal-i2F3n3nZ.js} +1 -1
- package/web/dist/assets/{WorkspaceTaskDrawer-BCKoF7qc.js → WorkspaceTaskDrawer-C_Ta_K13.js} +1 -1
- package/web/dist/assets/WorkspaceTerminalPanels-VdDxtrQF.js +1 -0
- package/web/dist/assets/index-5zh61jMg.css +1 -0
- package/web/dist/assets/index-CAgGM6nb.js +75 -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
|
@@ -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,7 +17,7 @@ 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
23
|
'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. ' +
|
|
@@ -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", 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
|
|
@@ -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,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>;
|
|
@@ -2,21 +2,22 @@ import { createAgentRunStore } from './agent-run-store.js';
|
|
|
2
2
|
import { createAgentRuntime } from './agent-runtime.js';
|
|
3
3
|
import { createAgentSessionStore } from './agent-session-store.js';
|
|
4
4
|
import { createDispatchLedgerStore } from './dispatch-ledger-store.js';
|
|
5
|
+
import { readFeatureFlags } from './feature-flags.js';
|
|
5
6
|
import { createMessageLogStore } from './message-log-store.js';
|
|
6
7
|
import { seedOrchestratorLaunchConfig } from './orchestrator-launch.js';
|
|
8
|
+
import { createReportOutboxStore } from './report-outbox-store.js';
|
|
7
9
|
import { openRuntimeDatabase } from './runtime-database.js';
|
|
8
10
|
import { buildRuntimeRestartPolicy } from './runtime-restart-policy.js';
|
|
9
11
|
import { createSettingsStore } from './settings-store.js';
|
|
10
12
|
import { createTasksFileService } from './tasks-file.js';
|
|
11
13
|
import { createTasksFileWatcher } from './tasks-file-watcher.js';
|
|
12
|
-
import { AUTOSTAFF_ENABLED_KEY, readAutostaffEnabled } from './team-autostaff.js';
|
|
13
14
|
import { createTeamOperations } from './team-operations.js';
|
|
14
15
|
import { resolveTerminalInputProfile } from './terminal-input-profile.js';
|
|
15
16
|
import { createUiAuth } from './ui-auth.js';
|
|
17
|
+
import { createWebhookNotifier, WEBHOOK_URL_KEY } from './webhook-notifier.js';
|
|
16
18
|
import { createWorkerOutputTracker } from './worker-output-tracker.js';
|
|
17
19
|
import { readWorkflowCliPolicy, WORKFLOW_CLI_POLICY_KEY } from './workflow-cli-policy.js';
|
|
18
20
|
import { createWorkflowDispatchAwaiter, } from './workflow-dispatch-awaiter.js';
|
|
19
|
-
import { readWorkflowEnabled, WORKFLOW_ENABLED_KEY } from './workflow-feature.js';
|
|
20
21
|
import { createWorkflowRunLogStore } from './workflow-run-log-store.js';
|
|
21
22
|
import { createWorkflowRunStore } from './workflow-run-store.js';
|
|
22
23
|
import { createWorkflowScheduleStore } from './workflow-schedule-store.js';
|
|
@@ -35,6 +36,7 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
35
36
|
const db = openRuntimeDatabase(options.dataDir);
|
|
36
37
|
const messageLogStore = createMessageLogStore(db);
|
|
37
38
|
const dispatchLedgerStore = createDispatchLedgerStore(db);
|
|
39
|
+
const reportOutbox = createReportOutboxStore(db);
|
|
38
40
|
const workflowDispatchAwaiter = createWorkflowDispatchAwaiter();
|
|
39
41
|
const workflowRunStore = createWorkflowRunStore(db);
|
|
40
42
|
const workflowRunLogStore = createWorkflowRunLogStore(db);
|
|
@@ -42,8 +44,10 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
42
44
|
const agentRunStore = createAgentRunStore(db);
|
|
43
45
|
const agentSessionStore = createAgentSessionStore(db);
|
|
44
46
|
const settings = createSettingsStore(db);
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
+
const getFlags = () => readFeatureFlags(settings);
|
|
48
|
+
const webhookNotifier = createWebhookNotifier({
|
|
49
|
+
getUrl: () => settings.getAppState(WEBHOOK_URL_KEY)?.value ?? null,
|
|
50
|
+
});
|
|
47
51
|
const tasksFileService = createTasksFileService();
|
|
48
52
|
const tasksFileWatchCallbacks = new Set();
|
|
49
53
|
const tasksFileWatcher = createTasksFileWatcher({
|
|
@@ -51,8 +55,7 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
51
55
|
notifyTasksUpdated(tasksFileWatchCallbacks, workspaceId, content);
|
|
52
56
|
},
|
|
53
57
|
getWorkflowCliPolicy: () => readWorkflowCliPolicy(settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null),
|
|
54
|
-
|
|
55
|
-
getAutostaffEnabled,
|
|
58
|
+
getFlags,
|
|
56
59
|
});
|
|
57
60
|
const uiAuth = createUiAuth();
|
|
58
61
|
const shellRuntime = createWorkspaceShellRuntime(options.agentManager);
|
|
@@ -71,8 +74,7 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
71
74
|
messageLogStore,
|
|
72
75
|
tasksFileService,
|
|
73
76
|
workspaceStore,
|
|
74
|
-
|
|
75
|
-
getAutostaffEnabled,
|
|
77
|
+
getFlags,
|
|
76
78
|
});
|
|
77
79
|
const workerOutputTracker = options.agentManager
|
|
78
80
|
? createWorkerOutputTracker(options.agentManager.getOutputBus())
|
|
@@ -119,7 +121,7 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
119
121
|
for (const child of children)
|
|
120
122
|
removeWorkerCompletely(workspaceId, child.id);
|
|
121
123
|
}
|
|
122
|
-
}, restartPolicy, (workspaceId, agentId) => workspaceStore.getAgent(workspaceId, agentId),
|
|
124
|
+
}, restartPolicy, (workspaceId, agentId) => workspaceStore.getAgent(workspaceId, agentId), getFlags);
|
|
123
125
|
// Mirrors runtime-store.deleteWorker (stop run → drop launch config → drop
|
|
124
126
|
// dispatches → drop worker row, transactionally). Hoisted `function` so the
|
|
125
127
|
// onAgentExit closure above can reference it; only invoked at runtime, after
|
|
@@ -157,6 +159,8 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
157
159
|
markDispatchCancelled: dispatchLedgerStore.markCancelled,
|
|
158
160
|
markDispatchReportedByWorker: dispatchLedgerStore.markReportedByWorker,
|
|
159
161
|
markDispatchSubmitted: dispatchLedgerStore.markSubmitted,
|
|
162
|
+
reportOutbox,
|
|
163
|
+
notifyWebhook: webhookNotifier.notify,
|
|
160
164
|
workflowDispatchAwaiter,
|
|
161
165
|
workspaceStore,
|
|
162
166
|
dismissEphemeralWorker: (workspaceId, workerId) => removeWorkerCompletely(workspaceId, workerId),
|
|
@@ -176,6 +180,7 @@ export const createRuntimeStoreServices = (options = {}) => {
|
|
|
176
180
|
tasksFileService,
|
|
177
181
|
teamOps,
|
|
178
182
|
uiAuth,
|
|
183
|
+
webhookNotifier,
|
|
179
184
|
workerOutputTracker,
|
|
180
185
|
workflowDispatchAwaiter,
|
|
181
186
|
workflowRunLogStore,
|
|
@@ -78,6 +78,14 @@ export const createRuntimeStoreWorkflowRuntime = (services, store) => {
|
|
|
78
78
|
catch (error) {
|
|
79
79
|
console.error('[hive] workflow.notifyTrigger failed', error);
|
|
80
80
|
}
|
|
81
|
+
// Outbound completion webhook (best-effort) so the user is pinged when a
|
|
82
|
+
// long fan-out finishes without watching the drawer.
|
|
83
|
+
services.webhookNotifier.notify({
|
|
84
|
+
type: 'workflow_finished',
|
|
85
|
+
workspaceId,
|
|
86
|
+
summary: `${finalRecord.name}: ${finalRecord.status}${finalRecord.error ? ` — ${finalRecord.error}` : ''}`,
|
|
87
|
+
at: Date.now(),
|
|
88
|
+
});
|
|
81
89
|
},
|
|
82
90
|
store: {
|
|
83
91
|
addWorkerWithLaunch: (ws, input, launch) => store.addWorkerWithLaunch(ws, input, launch),
|
|
@@ -93,6 +93,7 @@ export const createRuntimeStore = (options = {}) => {
|
|
|
93
93
|
dispatchTask: services.teamOps.dispatchTask,
|
|
94
94
|
dispatchTaskByWorkerName: services.teamOps.dispatchTaskByWorkerName,
|
|
95
95
|
reportTask: services.teamOps.reportTask,
|
|
96
|
+
drainReportOutbox: services.teamOps.drainReportOutbox,
|
|
96
97
|
statusTask: services.teamOps.statusTask,
|
|
97
98
|
listDispatches: services.dispatchLedgerStore.listWorkspaceDispatches,
|
|
98
99
|
listWorkers: (workspaceId) => services.workspaceStore.listWorkers(workspaceId),
|
|
@@ -118,6 +118,19 @@ export const initializeRuntimeDatabase = (db) => {
|
|
|
118
118
|
CREATE INDEX IF NOT EXISTS idx_dispatches_open_by_worker
|
|
119
119
|
ON dispatches (workspace_id, to_agent_id, status, sequence);
|
|
120
120
|
|
|
121
|
+
CREATE TABLE IF NOT EXISTS report_outbox (
|
|
122
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
123
|
+
workspace_id TEXT NOT NULL,
|
|
124
|
+
target_agent_id TEXT NOT NULL,
|
|
125
|
+
dispatch_id TEXT NOT NULL UNIQUE,
|
|
126
|
+
payload TEXT NOT NULL,
|
|
127
|
+
created_at INTEGER NOT NULL,
|
|
128
|
+
delivered_at INTEGER
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_report_outbox_pending
|
|
132
|
+
ON report_outbox (workspace_id, target_agent_id, delivered_at, created_at);
|
|
133
|
+
|
|
121
134
|
CREATE TABLE IF NOT EXISTS workflow_runs (
|
|
122
135
|
id TEXT PRIMARY KEY,
|
|
123
136
|
workspace_id TEXT NOT NULL,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional, read-only task-dependency support for `.hive/tasks.md`.
|
|
3
|
+
*
|
|
4
|
+
* A task line may carry a trailing `[needs: #2, #5]` annotation declaring that
|
|
5
|
+
* it depends on other tasks (referenced by their 1-based position among the
|
|
6
|
+
* GFM task-list items). `team next` uses this to surface the tasks that are
|
|
7
|
+
* currently runnable — not done, and with every dependency already checked off.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately scoped: parsing happens here on the server only and is purely a
|
|
10
|
+
* READ — Hive never auto-dispatches a runnable task and never writes the
|
|
11
|
+
* annotation back into tasks.md (that file stays a human/orchestrator-edited,
|
|
12
|
+
* git-mergeable artifact). The web task renderer is intentionally left
|
|
13
|
+
* untouched, so the annotation just shows as literal text in the UI.
|
|
14
|
+
*/
|
|
15
|
+
export interface ParsedTaskLine {
|
|
16
|
+
/** 1-based position among task-list items — the `#n` that `[needs:]` uses. */
|
|
17
|
+
index: number;
|
|
18
|
+
done: boolean;
|
|
19
|
+
text: string;
|
|
20
|
+
needs: number[];
|
|
21
|
+
}
|
|
22
|
+
export interface RunnableTask {
|
|
23
|
+
index: number;
|
|
24
|
+
text: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const parseTasksWithDeps: (markdown: string) => ParsedTaskLine[];
|
|
27
|
+
/**
|
|
28
|
+
* Tasks that can be worked right now: not yet done, and every `[needs:]`
|
|
29
|
+
* dependency is a task that exists AND is checked off. A dependency on a
|
|
30
|
+
* non-existent `#n`, or on a task that isn't done, withholds the task.
|
|
31
|
+
*/
|
|
32
|
+
export declare const computeRunnableTasks: (markdown: string) => RunnableTask[];
|