@tt-a1i/hive 2.1.6 → 2.1.8

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 (41) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/src/server/agent-runtime-contract.d.ts +1 -9
  3. package/dist/src/server/agent-runtime.js +1 -7
  4. package/dist/src/server/agent-stdin-dispatcher.d.ts +2 -11
  5. package/dist/src/server/agent-stdin-dispatcher.js +14 -12
  6. package/dist/src/server/hive-team-guidance.js +12 -11
  7. package/dist/src/server/post-start-input-writer.js +1 -1
  8. package/dist/src/server/report-outbox-store.d.ts +10 -8
  9. package/dist/src/server/report-outbox-store.js +10 -1
  10. package/dist/src/server/role-templates.js +8 -4
  11. package/dist/src/server/routes-remote.js +46 -14
  12. package/dist/src/server/routes-team.js +1 -1
  13. package/dist/src/server/routes-workspaces.js +3 -6
  14. package/dist/src/server/runtime-store-contract.d.ts +2 -2
  15. package/dist/src/server/runtime-store-helpers.d.ts +4 -0
  16. package/dist/src/server/runtime-store-helpers.js +34 -11
  17. package/dist/src/server/runtime-store-worker-mutations.js +12 -6
  18. package/dist/src/server/runtime-store-workflows.js +7 -1
  19. package/dist/src/server/team-operations.d.ts +12 -5
  20. package/dist/src/server/team-operations.js +218 -159
  21. package/dist/src/shared/remote-bridge-routing.js +3 -1
  22. package/package.json +1 -1
  23. package/web/dist/assets/{AddWorkerDialog-DFNkbR_Q.js → AddWorkerDialog-Cd53ifv2.js} +2 -2
  24. package/web/dist/assets/{AddWorkspaceFlow-BjpjCuRe.js → AddWorkspaceFlow-3sR25gDy.js} +1 -1
  25. package/web/dist/assets/{FirstRunWizard-BuLgUAny.js → FirstRunWizard-DU8PipxA.js} +1 -1
  26. package/web/dist/assets/{MarketplaceDrawer-BALuhrnd.js → MarketplaceDrawer-BkNcknv0.js} +1 -1
  27. package/web/dist/assets/{TaskGraphDrawer-DQTS6JPI.js → TaskGraphDrawer-9mXNimSE.js} +1 -1
  28. package/web/dist/assets/{WhatsNewDialog-DpgjEIaA.js → WhatsNewDialog-C7awVYub.js} +1 -1
  29. package/web/dist/assets/WorkerModal-BzgrOKzF.js +1 -0
  30. package/web/dist/assets/{WorkflowsDrawer-DPWlZs4S.js → WorkflowsDrawer-C4yjTdY7.js} +1 -1
  31. package/web/dist/assets/{WorkspaceMemoryDrawer-C0vny4zK.js → WorkspaceMemoryDrawer-BlDznlGm.js} +1 -1
  32. package/web/dist/assets/{WorkspaceTaskDrawer-CJcFZh8C.js → WorkspaceTaskDrawer-CpGGZkp3.js} +1 -1
  33. package/web/dist/assets/index-BEyvsWk6.js +84 -0
  34. package/web/dist/assets/index-VHK8pAJq.css +1 -0
  35. package/web/dist/assets/{search-Bl5O-SfR.js → search-Cv0KnMjw.js} +1 -1
  36. package/web/dist/assets/{square-terminal-DCDaflg3.js → square-terminal-BZ7EavxU.js} +1 -1
  37. package/web/dist/index.html +2 -2
  38. package/web/dist/sw.js +1 -1
  39. package/web/dist/assets/WorkerModal-1y-Xdwv-.js +0 -1
  40. package/web/dist/assets/index-CEP_bvOg.css +0 -1
  41. package/web/dist/assets/index-CR1SPGBZ.js +0 -84
package/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  All notable user-facing changes will be documented in this file.
4
4
 
5
+ ## 2.1.8 - 2026-06-23
6
+
7
+ Codex terminal editing and member-window polish.
8
+
9
+ - Fixes Codex terminal input repainting for Chinese/CJK text so pressing
10
+ backspace updates the visible input line immediately instead of leaving stale
11
+ characters on screen.
12
+ - Removes the visible desktop member-window close button from the terminal
13
+ corner and adds an outside-click close hint, giving the terminal more clean
14
+ space while keeping mobile's full-screen close affordance.
15
+
16
+ ## 2.1.7 - 2026-06-23
17
+
18
+ Team delivery recovery and remote-access hardening.
19
+
20
+ - Makes worker reports durable when the Orchestrator is offline or restarting,
21
+ so completed work is queued for redelivery instead of being silently lost.
22
+ - Tightens cancel, worker-dismiss, and queued-dispatch recovery paths so stale
23
+ work gets a clear dropped/cancelled outcome instead of leaving agents waiting.
24
+ - Makes user input delivery truthful: if the Orchestrator terminal is offline,
25
+ Hive rejects the input instead of recording a message that was never sent.
26
+ - Standardizes remote-access HTTP responses on snake_case fields and keeps the
27
+ web UI mapping them back into its internal camelCase model.
28
+ - Blocks remote devices from reading or approving desktop-only pairing
29
+ approvals through the tunnel, while preserving normal equal-authority device
30
+ management routes.
31
+
5
32
  ## 2.1.6 - 2026-06-21
6
33
 
7
34
  Team UX polish and release visibility.
@@ -23,26 +23,18 @@ export interface AgentRuntime {
23
23
  startAgent: (workspace: WorkspaceSummary, agentId: string, input: StartAgentOptions) => Promise<LiveAgentRun>;
24
24
  stopAgentRun: (runId: string) => void;
25
25
  validateAgentToken: AgentTokenRegistry['validate'];
26
- writeReportPrompt: (workspaceId: string, workerName: string, workerId: string, text: string, artifacts: string[], input?: {
27
- requireActiveRun?: boolean;
28
- }) => void;
29
26
  writeStatusPrompt: (workspaceId: string, workerName: string, workerId: string, text: string, artifacts: string[], input?: {
30
27
  requireActiveRun?: boolean;
31
28
  }) => void;
32
29
  writeSendPrompt: (workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string) => Promise<void>;
33
30
  writeCancelPrompt: (workspaceId: string, workerId: string, dispatchId: string, reason: string, input?: {
34
31
  requireActiveRun?: boolean;
35
- }) => void;
32
+ }) => Promise<void>;
36
33
  writeUserInputPrompt: (workspaceId: string, text: string) => void;
37
34
  writeSystemMessageToAgent: (workspaceId: string, agentId: string, text: string) => void;
38
35
  deliverUserInputToOrchestrator: (workspaceId: string, text: string, input?: {
39
36
  requireActiveRun?: boolean;
40
37
  }) => Promise<void>;
41
- /** Awaitable report delivery — rejects if the orchestrator PTY write fails,
42
- * so the caller can persist the report to the redelivery outbox. */
43
- deliverReportToOrchestrator: (workspaceId: string, workerName: string, text: string, artifacts: string[], input?: {
44
- requireActiveRun?: boolean;
45
- }) => Promise<void>;
46
38
  /** Awaitable opaque delivery — used to drain the report outbox so an entry
47
39
  * is marked delivered only after the PTY write resolves. */
48
40
  deliverSystemMessageToAgent: (workspaceId: string, agentId: string, text: string, input?: {
@@ -139,9 +139,6 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
139
139
  });
140
140
  },
141
141
  validateAgentToken: tokenRegistry.validate,
142
- writeReportPrompt(workspaceId, workerName, _workerId, text, artifacts, input = {}) {
143
- stdinDispatcher.writeReportPrompt(workspaceId, workerName, text, artifacts, input);
144
- },
145
142
  writeStatusPrompt(workspaceId, workerName, _workerId, text, artifacts, input = {}) {
146
143
  stdinDispatcher.writeStatusPrompt(workspaceId, workerName, text, artifacts, input);
147
144
  },
@@ -149,7 +146,7 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
149
146
  return stdinDispatcher.writeSendPrompt(workspaceId, workerId, dispatchId, fromAgentName, workerDescription, text);
150
147
  },
151
148
  writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input = {}) {
152
- stdinDispatcher.writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input);
149
+ return stdinDispatcher.writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input);
153
150
  },
154
151
  writeUserInputPrompt(workspaceId, text) {
155
152
  stdinDispatcher.writeUserInputPrompt(workspaceId, text);
@@ -160,9 +157,6 @@ export const createAgentRuntime = (agentManager, agentRunStore, sessionStore, ge
160
157
  deliverUserInputToOrchestrator(workspaceId, text, input = {}) {
161
158
  return stdinDispatcher.deliverUserInputToOrchestrator(workspaceId, text, input);
162
159
  },
163
- deliverReportToOrchestrator(workspaceId, workerName, text, artifacts, input = {}) {
164
- return stdinDispatcher.deliverReportToOrchestrator(workspaceId, workerName, text, artifacts, input);
165
- },
166
160
  deliverSystemMessageToAgent(workspaceId, agentId, text, input = {}) {
167
161
  return stdinDispatcher.deliverSystemMessageToAgent(workspaceId, agentId, text, input);
168
162
  },
@@ -16,22 +16,19 @@ interface AgentStdinDispatcherInput {
16
16
  * Optional; omitted → all flags off. */
17
17
  getFlags?: () => FeatureFlags;
18
18
  }
19
- export declare const buildOrchestratorReportPayload: (workerName: string, text: string, artifacts: string[], flags?: FeatureFlags) => string;
19
+ export declare const buildOrchestratorReportPayload: (workerName: string, text: string, artifacts: string[], flags?: FeatureFlags, dispatchId?: string) => string;
20
20
  export declare const buildOrchestratorStatusPayload: (workerName: string, text: string, artifacts: string[], flags?: FeatureFlags) => string;
21
21
  export declare const buildOrchestratorUserInputPayload: (text: string, flags?: FeatureFlags) => string;
22
22
  export declare const buildWorkerDispatchPayload: (fromAgentName: string, workerDescription: string, dispatchId: string, text: string, memoryDigest?: string | null) => string;
23
23
  export declare const buildWorkerCancelPayload: (dispatchId: string, reason: string) => string;
24
24
  export declare const createAgentStdinDispatcher: ({ agentManager, getLaunchConfig, getWorkspaceId, registry, syncRun, memoryInjection, getFlags, }: AgentStdinDispatcherInput) => {
25
- writeReportPrompt(workspaceId: string, workerName: string, text: string, artifacts: string[], input?: {
26
- requireActiveRun?: boolean;
27
- }): void;
28
25
  writeStatusPrompt(workspaceId: string, workerName: string, text: string, artifacts: string[], input?: {
29
26
  requireActiveRun?: boolean;
30
27
  }): void;
31
28
  writeSendPrompt(workspaceId: string, workerId: string, dispatchId: string, fromAgentName: string, workerDescription: string, text: string): Promise<void>;
32
29
  writeCancelPrompt(workspaceId: string, workerId: string, dispatchId: string, reason: string, input?: {
33
30
  requireActiveRun?: boolean;
34
- }): void;
31
+ }): Promise<void>;
35
32
  writeUserInputPrompt(workspaceId: string, text: string): void;
36
33
  deliverUserInputToOrchestrator(workspaceId: string, text: string, input?: {
37
34
  requireActiveRun?: boolean;
@@ -40,12 +37,6 @@ export declare const createAgentStdinDispatcher: ({ agentManager, getLaunchConfi
40
37
  * Used by the workflow runner to notify the triggering orchestrator
41
38
  * when a run finishes (mirrors Claude Code's <task-notification>). */
42
39
  writeSystemMessageToAgent(workspaceId: string, agentId: string, text: string): void;
43
- /** Awaitable report delivery — same payload as writeReportPrompt but the
44
- * promise is returned so the caller can persist the report to the
45
- * redelivery outbox if the orchestrator PTY write rejects. */
46
- deliverReportToOrchestrator(workspaceId: string, workerName: string, text: string, artifacts: string[], input?: {
47
- requireActiveRun?: boolean;
48
- }): Promise<void>;
49
40
  /** Awaitable opaque delivery — used to drain the report outbox so an entry
50
41
  * is marked delivered only after the PTY write actually resolves. */
51
42
  deliverSystemMessageToAgent(workspaceId: string, agentId: string, text: string, input?: {
@@ -4,9 +4,20 @@ import { buildOrchestratorReminderTail, buildWorkerReminderTail } from './hive-t
4
4
  import { PtyInactiveError } from './http-errors.js';
5
5
  import { createPostStartInputWriter } from './post-start-input-writer.js';
6
6
  import { buildDispatchMemoryDigestSafely, logMemoryDigestInjection, rollbackMemoryDigestInjection, } from './team-memory-injection.js';
7
- export const buildOrchestratorReportPayload = (workerName, text, artifacts, flags = FEATURE_FLAGS_ALL_OFF) => {
7
+ export const buildOrchestratorReportPayload = (workerName, text, artifacts, flags = FEATURE_FLAGS_ALL_OFF, dispatchId) => {
8
+ const dispatchAttribute = dispatchId
9
+ ? ` dispatch="${escapeHiveEnvelopeAttribute(dispatchId)}"`
10
+ : '';
8
11
  const lines = [
9
- `<hive-message kind="report" from="@${escapeHiveEnvelopeAttribute(workerName)}">`,
12
+ `<hive-message kind="report" from="@${escapeHiveEnvelopeAttribute(workerName)}"${dispatchAttribute}>`,
13
+ ...(dispatchId
14
+ ? [
15
+ '',
16
+ `dispatch_id: ${escapeHiveEnvelopeText(dispatchId)}`,
17
+ 'If this dispatch_id report was already processed, treat this as a duplicate redelivery and ignore it.',
18
+ '',
19
+ ]
20
+ : []),
10
21
  escapeHiveEnvelopeText(text),
11
22
  ];
12
23
  for (const artifact of artifacts)
@@ -166,9 +177,6 @@ export const createAgentStdinDispatcher = ({ agentManager, getLaunchConfig, getW
166
177
  });
167
178
  };
168
179
  return {
169
- writeReportPrompt(workspaceId, workerName, text, artifacts, input = {}) {
170
- swallowQueuedFailure(writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorReportPayload(workerName, text, artifacts, flags()), input));
171
- },
172
180
  writeStatusPrompt(workspaceId, workerName, text, artifacts, input = {}) {
173
181
  swallowQueuedFailure(writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorStatusPayload(workerName, text, artifacts, flags()), input));
174
182
  },
@@ -206,7 +214,7 @@ export const createAgentStdinDispatcher = ({ agentManager, getLaunchConfig, getW
206
214
  }
207
215
  },
208
216
  writeCancelPrompt(workspaceId, workerId, dispatchId, reason, input = {}) {
209
- swallowQueuedFailure(writeToActiveAgentRun(workspaceId, workerId, buildWorkerCancelPayload(dispatchId, reason), input));
217
+ return writeToActiveAgentRun(workspaceId, workerId, buildWorkerCancelPayload(dispatchId, reason), input);
210
218
  },
211
219
  writeUserInputPrompt(workspaceId, text) {
212
220
  swallowQueuedFailure(writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorUserInputPayload(text, flags())));
@@ -220,12 +228,6 @@ export const createAgentStdinDispatcher = ({ agentManager, getLaunchConfig, getW
220
228
  writeSystemMessageToAgent(workspaceId, agentId, text) {
221
229
  swallowQueuedFailure(writeToActiveAgentRun(workspaceId, agentId, text));
222
230
  },
223
- /** Awaitable report delivery — same payload as writeReportPrompt but the
224
- * promise is returned so the caller can persist the report to the
225
- * redelivery outbox if the orchestrator PTY write rejects. */
226
- deliverReportToOrchestrator(workspaceId, workerName, text, artifacts, input = {}) {
227
- return writeToActiveAgentRun(workspaceId, `${workspaceId}:orchestrator`, buildOrchestratorReportPayload(workerName, text, artifacts, flags()), input);
228
- },
229
231
  /** Awaitable opaque delivery — used to drain the report outbox so an entry
230
232
  * is marked delivered only after the PTY write actually resolves. */
231
233
  deliverSystemMessageToAgent(workspaceId, agentId, text, input = {}) {
@@ -20,8 +20,8 @@ import { DEFAULT_WORKFLOW_CLI_POLICY } from './workflow-cli-policy.js';
20
20
  */
21
21
  export const buildOrchestratorReminderTail = ({ workflowsEnabled }) => {
22
22
  const body = 'You are the Hive Orchestrator. Reply with one of: ' +
23
- '(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). ' +
24
- `If no worker fits, the roster is empty, or independent branches need more workers, \`team spawn <role> [--cli <${BUILTIN_COMMAND_PRESET_CLI_LIST}>]\` to create one (add \`--ephemeral\` for a one-shot), then dispatch; do not take a branch yourself or ask the user to add workers. ` +
23
+ '(a) `team list`, then `team send "<worker-name>" "<task>"` to dispatch to a suitable existing worker by name. Prefer user-created/user-managed workers already in the roster; stale names fail. ' +
24
+ `If no existing worker fits, a worker is stopped/queued/not prompt-ready, or independent branches need more workers, report that condition and recommend what worker the user may start or add; do not take a branch yourself. Use \`team spawn <role> [--cli <${BUILTIN_COMMAND_PRESET_CLI_LIST}>] [--ephemeral]\` only when the user explicitly authorizes it or workspace policy permits autonomous staffing for this task. ` +
25
25
  '(b) `team cancel --dispatch <id> "<reason>"` to close an obsolete dispatch. ' +
26
26
  (workflowsEnabled
27
27
  ? '(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 only for clarification, status/final synthesis, or the explicitly allowed trivial check. '
@@ -52,16 +52,17 @@ export const buildWorkerReminderTail = (dispatchId) => '<hive-system-reminder>\n
52
52
  const CORE_ORCHESTRATOR_RULES = [
53
53
  'A Hive worker is a real CLI agent shown as a card on the right — NOT a subagent of your own CLI.',
54
54
  'Dispatch against the current roster: re-run `team list` before a `team send` whenever a user reply or one of your own dispatches/cancels has happened since your last list. Hive never pushes membership changes, so do not dispatch from a remembered roster — a renamed or removed worker fails.',
55
- 'Default to dispatching: parallelism, long execution, audit/review/test/validation work, multi-file code changes, test-writing, a dedicated role, or the user asking for a worker all mean `team send` or `team spawn` then `team send`. The only work you may do yourself is this closed exception: a trivial single-file read-only inspection or config value check that needs no tests, no audit/review/validation judgement, no edits, and no parallel branch. Any edit, even a typo fix, goes through a Hive worker or Hive workflow. If you would write tests, inspect a diff for correctness/security, touch files, or run a review/fix chain, dispatch it.',
56
- 'When work splits into independent branches and the roster lacks enough suitable workers, spawn additional workers (usually `--ephemeral`) and dispatch each branch; do not keep one branch for yourself as a way to parallelize.',
55
+ 'Default to dispatching through the existing roster: parallelism, long execution, audit/review/test/validation work, multi-file code changes, test-writing, a dedicated role, or the user asking for a worker all mean `team list` then `team send` to suitable current workers first. The only work you may do yourself is this closed exception: a trivial single-file read-only inspection or config value check that needs no tests, no audit/review/validation judgement, no edits, and no parallel branch. Any edit, even a typo fix, goes through a Hive worker or Hive workflow. If you would write tests, inspect a diff for correctness/security, touch files, or run a review/fix chain, dispatch it.',
56
+ 'Prefer user-created/user-managed workers. If you cannot see ownership in the current output, treat existing named workers as user-managed unless the name was just returned by your own `team spawn`.',
57
+ 'When work splits into independent branches and the roster lacks enough suitable workers, explain the role/capacity gap and recommend additional workers the user may add or start; spawn additional workers yourself only when the user explicitly authorizes it or workspace policy permits autonomous staffing for this task; do not keep one branch for yourself as a way to parallelize.',
57
58
  '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.',
58
59
  'When the user says "have a worker do X", dispatch it with `team send <worker-name> "<task>"`.',
59
- `If the roster is empty or lacks the role the task needs, build the team yourself: \`team spawn <role> [--name <n>] [--cli <${BUILTIN_COMMAND_PRESET_CLI_LIST}>]\`, then immediately dispatch. Do not stop to ask the user to add members that call is yours. Without \`--name\` the worker is named after the bare role label when that name is free (\`team spawn researcher\` → \`team send researcher "<task>"\`); on a collision the name gets a uuid suffix — the spawn response always echoes the final \`name\`, so dispatch to the echoed name.`,
60
- '`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`.',
60
+ `If the roster is empty or lacks the role the task needs, report the gap to the user and recommend the missing worker role. Use \`team spawn <role> [--name <n>] [--cli <${BUILTIN_COMMAND_PRESET_CLI_LIST}>]\` only when the user explicitly asks you to create workers, the user has already granted permission for autonomous staffing, or workspace policy clearly permits it. Without \`--name\` the worker is named after the bare role label when that name is free (\`team spawn researcher\` → \`team send researcher "<task>"\`); on a collision the name gets a uuid suffix — the spawn response always echoes the final \`name\`, so dispatch to the echoed name.`,
61
+ '`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`. If you are authorized to spawn for one task, prefer `--ephemeral` unless the user asks for persistent capacity.',
61
62
  'All Hive workers in one workspace share the same filesystem root with no per-worker isolation. Split dispatches so no two workers edit the same files/modules at the same time; if work would collide, serialize it or assign one owner.',
62
- 'When you spawn a reviewer to audit work another CLI produced, prefer a different `--cli` than the implementer used — cross-provider review catches blind spots a model shares with itself.',
63
+ 'When you are authorized to spawn a reviewer to audit work another CLI produced, prefer a different `--cli` than the implementer used — cross-provider review catches blind spots a model shares with itself.',
63
64
  '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.',
64
- 'A `team send` to a user-added STOPPED worker parks the dispatch (the response carries `queued: true`): it is delivered automatically when the user starts that worker. Workers you created with `team spawn` are woken by the follow-up `team send`. If a user-added stopped worker should run now, tell the user the worker needs a start, or `team cancel` it and dispatch to a running worker.',
65
+ 'A `team send` to a user-added STOPPED worker parks the dispatch (the response carries `queued: true`): it is delivered automatically when the user starts that worker. Treat stopped, queued, delivery-failed, or prompt-readiness-timeout states as runtime/roster conditions to report or retry against existing workers; do not treat them as proof the worker is unsuitable. Workers you created with `team spawn` are woken by the follow-up `team send`.',
65
66
  '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.',
66
67
  'Only orchestrators can add memory directly; workers should report durable findings for the orchestrator or Dream to capture. Search memory before adding: use `team memory search "<query>"` to avoid duplicates. Use `team memory add "<body>"` only for rare, evidence-backed workspace facts, decisions, and pitfalls that should help future Hive agents across sessions. Nothing worth saving is a normal outcome; do not add memory just to prove you used it. Use `team memory forget <memory-id>` only to archive obsolete memory.',
67
68
  "Never substitute your CLI's own built-in background agents, exploration helpers, subagent runners, or workflow runners for Hive workers or Hive workflows — they run inside your CLI process, bypass Hive's PTY fleet, never appear in the UI or `team list`, and the stop button cannot reach them.",
@@ -89,7 +90,7 @@ const WORKFLOW_ORCHESTRATOR_RULES = [
89
90
  * workflows are available it defers one-shot batch fan-out to `team workflow
90
91
  * run` so the two experiments don't give the orchestrator competing advice.
91
92
  */
92
- const buildAutostaffRule = (workflowsEnabled) => 'You may size the team to the task instead of adding workers one at a time: judge what mix of roles runs it fastest (e.g. a couple of coders plus a reviewer and a tester) and issue the individual `team spawn <role> --ephemeral` commands you need up front — one command per member (there is no batch form), this call is yours, do not ask the user. Match the count to the work: more agents is not faster once they would collide or sit idle. Use `--ephemeral` so this task-scoped staff auto-dismiss after reporting and do not pile up.' +
93
+ const buildAutostaffRule = (workflowsEnabled) => 'Auto-staff guidance is available: you may size the team to the task after checking the current roster, but existing user-created/user-managed workers still have priority. If extra parallel capacity would materially help, state the missing role/count and prefer recommending those additions to the user. Issue individual `team spawn <role> --ephemeral` commands yourself only when the user has authorized autonomous staffing or workspace policy explicitly permits it — one command per member (there is no batch form). Match the count to the work: more agents is not faster once they would collide or sit idle. Use `--ephemeral` so task-scoped staff auto-dismiss after reporting and do not pile up.' +
93
94
  (workflowsEnabled
94
95
  ? ' 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.'
95
96
  : '');
@@ -327,8 +328,8 @@ export const buildProtocolDoc = (cliPolicy = DEFAULT_WORKFLOW_CLI_POLICY, flags
327
328
  '- `team memory apply --run <dream-run-id> --stdin` — apply strict JSON Dream ops for a pending maintenance run',
328
329
  '- `team memory forget <memory-id>` — archive obsolete memory (orchestrator only; does not physically delete evidence)',
329
330
  '- `team send "<worker-name>" "<task>"` — dispatch to a worker by name (never id)',
330
- `- \`team spawn <role> [--name <name>] [--cli <${BUILTIN_COMMAND_PRESET_CLI_LIST}>]\` — create a PERSISTENT member when none fits (or when the roster is empty)`,
331
- '- `team spawn <role> --ephemeral [other-flags]` — create a one-shot worker that auto-dismisses after its first `team report`',
331
+ `- \`team spawn <role> [--name <name>] [--cli <${BUILTIN_COMMAND_PRESET_CLI_LIST}>]\` — create a PERSISTENT member only when the user or workspace policy has authorized autonomous staffing`,
332
+ '- `team spawn <role> --ephemeral [other-flags]` — create an authorized one-shot worker that auto-dismisses after its first `team report`',
332
333
  '- `team dismiss <worker-name>` — remove a worker that is no longer needed (works on any worker, including persistent ones; ephemeral workers auto-dismiss after their first report)',
333
334
  '- `team cancel --dispatch <id> "<reason>"` — cancel an obsolete open dispatch',
334
335
  ...workflowCliCommands,
@@ -8,7 +8,7 @@ const MIN_SUBMIT_AFTER_PASTE_DELAY_MS = 600;
8
8
  const MAX_SUBMIT_AFTER_PASTE_DELAY_MS = 1500;
9
9
  const PASTE_CHARS_PER_DELAY_MS = 4;
10
10
  const PASTE_ACK_CHECK_INTERVAL_MS = 50;
11
- const PASTE_ACK_SETTLE_DELAY_MS = 100;
11
+ const PASTE_ACK_SETTLE_DELAY_MS = 250;
12
12
  const CODEX_PASTE_ACK_SETTLE_DELAY_MS = 250;
13
13
  const PASTE_ACK_TIMEOUT_MS = 3000;
14
14
  const CLAUDE_HIVE_PASTE_ACK_TIMEOUT_MS = 10000;
@@ -1,15 +1,16 @@
1
1
  import type { Database } from 'better-sqlite3';
2
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.
3
+ * Durable redelivery queue for protocol messages a live agent could not
4
+ * receive. The primary producer is `team report`: it persists the dispatch as
5
+ * reported and forwards the report into the orchestrator's stdin; if that write
6
+ * fails (the PTY exited / is mid-restart, or the orchestrator was down at report
7
+ * time) the report would otherwise be lost — the ledger says "reported" yet the
8
+ * orchestrator waits forever, indistinguishable from a hang. Dispatch-drop
9
+ * notifications use the same queue for their issuer.
9
10
  *
10
11
  * 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.
12
+ * and drain marks them delivered only after the target PTY write actually
13
+ * resolves, so a failed redelivery stays pending for the next drain.
13
14
  */
14
15
  export interface ReportOutboxEntry {
15
16
  id: number;
@@ -27,6 +28,7 @@ interface EnqueueInput {
27
28
  payload: string;
28
29
  }
29
30
  export declare const createReportOutboxStore: (db: Database) => {
31
+ deletePendingForDispatch: (dispatchId: string) => void;
30
32
  enqueue: (input: EnqueueInput) => void;
31
33
  listPending: (workspaceId: string, targetAgentId: string) => ReportOutboxEntry[];
32
34
  markDelivered: (id: number) => void;
@@ -25,9 +25,18 @@ export const createReportOutboxStore = (db) => {
25
25
  const markDelivered = (id) => {
26
26
  db.prepare(`UPDATE report_outbox SET delivered_at = ? WHERE id = ? AND delivered_at IS NULL`).run(Date.now(), id);
27
27
  };
28
+ const deletePendingForDispatch = (dispatchId) => {
29
+ db.prepare('DELETE FROM report_outbox WHERE dispatch_id = ? AND delivered_at IS NULL').run(dispatchId);
30
+ };
28
31
  const pendingCount = (workspaceId, targetAgentId) => db
29
32
  .prepare(`SELECT COUNT(*) AS n FROM report_outbox
30
33
  WHERE workspace_id = ? AND target_agent_id = ? AND delivered_at IS NULL`)
31
34
  .get(workspaceId, targetAgentId).n;
32
- return { enqueue, listPending, markDelivered, pendingCount };
35
+ return {
36
+ deletePendingForDispatch,
37
+ enqueue,
38
+ listPending,
39
+ markDelivered,
40
+ pendingCount,
41
+ };
33
42
  };
@@ -1,9 +1,11 @@
1
1
  import { TASKS_RELATIVE_PATH } from './tasks-file.js';
2
2
  export const ORCHESTRATOR_ROLE_DESCRIPTION = [
3
- 'You are the Hive Orchestrator. You reply to the user directly and coordinate real workers shown on the right.',
3
+ 'You are the Hive Orchestrator. You interface with the user and coordinate the other agent members in the current Hive workspace to get the work done.',
4
4
  'How to work:',
5
5
  '- Clarify the goal and split it into dispatchable tasks.',
6
- '- Default to Hive dispatch: use `team send`, `team spawn`, or Hive `team workflow` for implementation, audit/review, test, validation, multi-file, or parallel work; do not use your CLI built-in subagents/workflows or keep a non-trivial branch for yourself.',
6
+ '- Default to Hive dispatch through the existing roster: use `team list`, then `team send` to suitable user-created/user-managed workers for implementation, audit/review, test, validation, multi-file, or parallel work.',
7
+ '- Do not create new members by default. Based on the task goal, if the current team lacks a suitable member, lacks enough parallel capacity, or adding a member would clearly improve speed and efficiency, explain the gap and recommend which agent member the user should add or start; use `team spawn` only when explicitly authorized by the user or the current workspace rules allow it.',
8
+ '- Do not use your CLI built-in subagents/workflows or keep a non-trivial branch for yourself.',
7
9
  `- Maintain ${TASKS_RELATIVE_PATH} so plan, progress, and blockers stay trackable.`,
8
10
  '- Drive the next step from worker reports; do not bounce choices back to the user unless a real decision is missing.',
9
11
  ].join('\n');
@@ -51,10 +53,12 @@ export const CUSTOM_ROLE_DESCRIPTION = [
51
53
  '- Done criteria: what results, risks, and blockers to report.',
52
54
  ].join('\n');
53
55
  export const ORCHESTRATOR_ROLE_DESCRIPTION_ZH = [
54
- '你是 Hive Orchestrator。你直接回复用户,并协调右侧真实 worker。',
56
+ '你是 Hive Orchestrator,负责对接用户,并组织协调当前 Hive workspace 中的其他 agent 成员协作完成任务。',
55
57
  '工作方式:',
56
58
  '- 澄清目标,把需求拆成可派发的小任务。',
57
- '- 默认走 Hive 派单:实现、审计/复核、测试、验证、多文件或并行任务用 `team send`、`team spawn` Hive `team workflow`;不要用当前 CLI 内建子代理/工作流,也不要把非平凡分支留给自己。',
59
+ '- 默认通过当前团队中已有的 agent 成员走 Hive 派单:先用 `team list` 刷新成员列表,再把实现、审计/复核、测试、验证、多文件或并行任务用 `team send` 派给合适的用户创建/用户管理成员。',
60
+ '- 不要默认创建新成员。根据任务目标,若当前团队缺少合适成员、并行能力不足,或添加成员能明显提高效率和速度时,先说明缺口,并建议用户添加或启动对应的 agent 成员;只有用户明确授权或当前 workspace 规则允许时,才使用 `team spawn`。',
61
+ '- 不要用当前 CLI 内建子代理/工作流,也不要把非平凡分支留给自己。',
58
62
  `- 维护 ${TASKS_RELATIVE_PATH},让当前计划、进度和阻塞可追踪。`,
59
63
  '- 根据成员汇报推进下一步,不把选择题无谓丢回给用户。',
60
64
  ].join('\n');
@@ -1,5 +1,6 @@
1
1
  import { ForbiddenError } from './http-errors.js';
2
2
  import { REMOTE_DAEMON_TOKEN_KEY, REMOTE_ENABLED_KEY, REMOTE_GATEWAY_URL_KEY, } from './remote-config-keys.js';
3
+ import { HIVE_REMOTE_DEVICE_HEADER } from './remote-loopback-auth.js';
3
4
  import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
4
5
  import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
5
6
  // Remote-access device-management + pairing routes (M4). Two gate classes:
@@ -23,8 +24,11 @@ const gateUi = ({ request, store }) => {
23
24
  // check, so even the short-circuit can't admit a tunnel request that slipped a cookie too.
24
25
  const gateLocalDesktopOnly = ({ request, store }) => {
25
26
  if (store.authorizeRemoteTunnelRequest(request)) {
27
+ const rawDeviceId = request.headers[HIVE_REMOTE_DEVICE_HEADER];
26
28
  store.getRemoteAuditStore().enqueue({
27
29
  action: 'reject',
30
+ deviceId: typeof rawDeviceId === 'string' ? rawDeviceId : null,
31
+ endpoint: request.url ?? null,
28
32
  result: 'rejected',
29
33
  rejectReason: 'pairing_confirm_forbidden',
30
34
  });
@@ -38,8 +42,8 @@ const remoteStatus = (store) => {
38
42
  const status = store.getRemoteTunnelStatus();
39
43
  return {
40
44
  enabled: store.settings.getAppState(REMOTE_ENABLED_KEY)?.value === 'true',
41
- loggedIn: isLoggedIn(store),
42
- gatewayUrl: store.settings.getAppState(REMOTE_GATEWAY_URL_KEY)?.value ?? null,
45
+ logged_in: isLoggedIn(store),
46
+ gateway_url: store.settings.getAppState(REMOTE_GATEWAY_URL_KEY)?.value ?? null,
43
47
  connected: status === 'online',
44
48
  // The FULL tunnel state, not just online/offline — the desktop dot was stuck looking "connected or
45
49
  // not" and couldn't show connecting / reconnecting / revoked / logged-out. (connecting+reconnecting
@@ -47,15 +51,41 @@ const remoteStatus = (store) => {
47
51
  connection: status,
48
52
  };
49
53
  };
50
- // RemoteDeviceRecord is already camelCase + metadata-only (no key material). Identity-mapped so the
51
- // response shape stays pinned to the store's invariant-7 projection.
54
+ // HTTP responses stay snake_case even though the store uses camelCase records.
55
+ const toPairingTicketView = (ticket) => ({
56
+ pairing_id: ticket.pairingId,
57
+ qr: ticket.qr,
58
+ code: ticket.code,
59
+ expires_at: ticket.expiresAt,
60
+ });
61
+ const toPendingPairingView = (pending) => ({
62
+ pairing_id: pending.pairingId,
63
+ device_name: pending.deviceName,
64
+ sas: pending.sas,
65
+ expires_at: pending.expiresAt,
66
+ });
52
67
  const toDeviceView = (record) => ({
53
68
  id: record.id,
54
69
  name: record.name,
55
- createdAt: record.createdAt,
56
- lastActive: record.lastActive,
57
- revokedAt: record.revokedAt,
70
+ created_at: record.createdAt,
71
+ last_active: record.lastActive,
72
+ revoked_at: record.revokedAt,
73
+ });
74
+ const toAuditView = (record) => ({
75
+ id: record.id,
76
+ device_id: record.deviceId,
77
+ ts: record.ts,
78
+ workspace_id: record.workspaceId,
79
+ action: record.action,
80
+ endpoint: record.endpoint,
81
+ result: record.result,
82
+ reject_reason: record.rejectReason,
83
+ byte_count: record.byteCount,
84
+ preview: record.preview,
58
85
  });
86
+ const getSearchParams = (url) => new URL(url ?? '', 'http://x').searchParams;
87
+ const hasCompatFlag = (params, snakeName, camelName) => params.has(snakeName) || params.has(camelName);
88
+ const getCompatParam = (params, snakeName, camelName) => params.get(snakeName) ?? params.get(camelName);
59
89
  export const remoteRoutes = [
60
90
  route('GET', '/api/remote/status', (ctx) => {
61
91
  gateUi(ctx);
@@ -83,7 +113,7 @@ export const remoteRoutes = [
83
113
  }
84
114
  try {
85
115
  const ticket = ctx.store.getRemotePairing().beginPairing();
86
- sendJson(ctx.response, 200, ticket);
116
+ sendJson(ctx.response, 200, toPairingTicketView(ticket));
87
117
  }
88
118
  catch {
89
119
  // beginPairing throws if gateway/daemon id vanished between the status read and here.
@@ -94,7 +124,7 @@ export const remoteRoutes = [
94
124
  // HARDEN minor: desktop-only. The confirm dialog backs this; a phone has no use for another
95
125
  // device's in-flight SAS, and exposing it would needlessly widen the secret surface (invariant 7).
96
126
  gateLocalDesktopOnly(ctx);
97
- sendJson(ctx.response, 200, ctx.store.getRemotePairing().listPending());
127
+ sendJson(ctx.response, 200, ctx.store.getRemotePairing().listPending().map(toPendingPairingView));
98
128
  }),
99
129
  route('POST', '/api/remote/pairings/:pairingId/confirm', async (ctx) => {
100
130
  const { params, request, response, store } = ctx;
@@ -139,7 +169,8 @@ export const remoteRoutes = [
139
169
  // ── equal-authority device management ──────────────────────────────────────────────────────────
140
170
  route('GET', '/api/remote/devices', (ctx) => {
141
171
  gateUi(ctx);
142
- const includeRevoked = new URL(ctx.request.url ?? '', 'http://x').searchParams.has('includeRevoked');
172
+ const params = getSearchParams(ctx.request.url);
173
+ const includeRevoked = hasCompatFlag(params, 'include_revoked', 'includeRevoked');
143
174
  sendJson(ctx.response, 200, ctx.store.getRemoteDeviceStore().list(includeRevoked).map(toDeviceView));
144
175
  }),
145
176
  route('POST', '/api/remote/devices/:deviceId/revoke', (ctx) => {
@@ -156,11 +187,12 @@ export const remoteRoutes = [
156
187
  }),
157
188
  route('GET', '/api/remote/audit', (ctx) => {
158
189
  gateUi(ctx);
159
- const url = new URL(ctx.request.url ?? '', 'http://x');
160
- const rawLimit = Number(url.searchParams.get('limit'));
190
+ const params = getSearchParams(ctx.request.url);
191
+ const rawLimit = Number(params.get('limit'));
161
192
  const limit = Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, 500) : 100;
162
- const deviceId = url.searchParams.get('deviceId');
193
+ const deviceId = getCompatParam(params, 'device_id', 'deviceId');
163
194
  const audit = ctx.store.getRemoteAuditStore();
164
- sendJson(ctx.response, 200, deviceId ? audit.listForDevice(deviceId, limit) : audit.list(limit));
195
+ const records = deviceId ? audit.listForDevice(deviceId, limit) : audit.list(limit);
196
+ sendJson(ctx.response, 200, records.map(toAuditView));
165
197
  }),
166
198
  ];
@@ -306,7 +306,7 @@ export const teamRoutes = [
306
306
  workspaceId: projectId,
307
307
  });
308
308
  requireCommandForRole(agent, 'cancel');
309
- const result = store.cancelTask(projectId, dispatchId, { fromAgentId, reason });
309
+ const result = await store.cancelTask(projectId, dispatchId, { fromAgentId, reason });
310
310
  sendJson(response, 202, {
311
311
  dispatch_id: result.dispatch?.id ?? null,
312
312
  forward_error: result.forwardError,
@@ -109,11 +109,9 @@ export const workspaceRoutes = [
109
109
  workspaceId,
110
110
  });
111
111
  requireCommandForRole(agent, 'list');
112
- // The orchestrator polling `team list` is its natural post-restart wakeup:
113
- // flush any reports a prior outage stranded back into its stdin now that
114
- // it is reachable again. Cheap (indexed, usually empty) and a safe no-op
115
- // for non-orchestrator callers.
116
- store.drainReportOutbox(workspaceId);
112
+ // Polling `team list` is a natural post-restart wakeup: flush any durable
113
+ // notices stranded for the caller now that its PTY is reachable again.
114
+ store.drainReportOutbox(workspaceId, agent.id);
117
115
  sendJson(response, 200, serializeTeamListWithOpenDispatches(store, workspaceId));
118
116
  }),
119
117
  route('POST', '/api/workspaces/:workspaceId/workers', async ({ params, request, response, store }) => {
@@ -198,7 +196,6 @@ export const workspaceRoutes = [
198
196
  }
199
197
  const orchestratorId = getOrchestratorId(workspaceId);
200
198
  if (!store.getActiveRunByAgentId(workspaceId, orchestratorId)) {
201
- store.recordUserInput(workspaceId, orchestratorId, body.text);
202
199
  throw new PtyInactiveError(`No active run for agent: ${orchestratorId}`);
203
200
  }
204
201
  await store.deliverUserInput(workspaceId, orchestratorId, body.text);
@@ -44,9 +44,9 @@ export interface RuntimeStore {
44
44
  reportTask: (workspaceId: string, workerId: string, input?: ReportTaskInput) => ReportTaskResult;
45
45
  /** Flush any reports stranded by a prior orchestrator outage. Safe no-op
46
46
  * when the orchestrator is down or the outbox is empty. */
47
- drainReportOutbox: (workspaceId: string) => void;
47
+ drainReportOutbox: (workspaceId: string, targetAgentId?: string) => void;
48
48
  statusTask: (workspaceId: string, workerId: string, input?: StatusTaskInput) => ReportTaskResult;
49
- cancelTask: (workspaceId: string, dispatchId: string, input: CancelTaskInput) => ReportTaskResult;
49
+ cancelTask: (workspaceId: string, dispatchId: string, input: CancelTaskInput) => Promise<ReportTaskResult>;
50
50
  listDispatches: (workspaceId: string, options?: ListDispatchesOptions) => DispatchRecord[];
51
51
  listOpenDispatches: (workspaceId: string) => DispatchRecord[];
52
52
  listRecentDispatches: (workspaceId: string, limit?: number) => DispatchRecord[];
@@ -10,6 +10,7 @@ import { type RemoteAuditStore } from './remote-audit-store.js';
10
10
  import type { DeviceSessionProvider } from './remote-device-session.js';
11
11
  import { type RemoteDeviceStore } from './remote-device-store.js';
12
12
  import { type RemotePairing } from './remote-pairing.js';
13
+ import { createReportOutboxStore } from './report-outbox-store.js';
13
14
  import { openRuntimeDatabase } from './runtime-database.js';
14
15
  import { createSentinelHeartbeatService } from './sentinel-heartbeat.js';
15
16
  import { createSettingsStore } from './settings-store.js';
@@ -39,6 +40,8 @@ export interface RuntimeStoreServices {
39
40
  agentRuntime: ReturnType<typeof createAgentRuntime>;
40
41
  db: ReturnType<typeof openRuntimeDatabase>;
41
42
  dispatchLedgerStore: ReturnType<typeof createDispatchLedgerStore>;
43
+ isRuntimeClosing: () => boolean;
44
+ markRuntimeClosing: () => void;
42
45
  messageLogStore: ReturnType<typeof createMessageLogStore>;
43
46
  remoteAuditStore: RemoteAuditStore;
44
47
  remoteDeviceSessions: DeviceSessionProvider;
@@ -53,6 +56,7 @@ export interface RuntimeStoreServices {
53
56
  teamMemoryDreamScheduler: ReturnType<typeof createTeamMemoryDreamScheduler>;
54
57
  sentinelHeartbeat: ReturnType<typeof createSentinelHeartbeatService>;
55
58
  protocolEventStats: ReturnType<typeof createProtocolEventStats>;
59
+ reportOutbox: ReturnType<typeof createReportOutboxStore>;
56
60
  teamMemoryDreamStore: ReturnType<typeof createTeamMemoryDreamStore>;
57
61
  teamMemoryDiagnostics: ReturnType<typeof createTeamMemoryDiagnostics>;
58
62
  teamMemoryStore: ReturnType<typeof createTeamMemoryStore>;