@tt-a1i/hive 2.0.2 → 2.1.1

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 (147) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.en.md +15 -6
  3. package/README.md +26 -4
  4. package/dist/src/cli/hive.d.ts +4 -0
  5. package/dist/src/cli/hive.js +25 -3
  6. package/dist/src/cli/team.d.ts +8 -1
  7. package/dist/src/cli/team.js +111 -11
  8. package/dist/src/server/action-center-summary.d.ts +193 -0
  9. package/dist/src/server/action-center-summary.js +188 -0
  10. package/dist/src/server/agent-command-resolver.d.ts +6 -0
  11. package/dist/src/server/agent-command-resolver.js +16 -0
  12. package/dist/src/server/agent-manager.js +11 -1
  13. package/dist/src/server/agent-run-starter.js +47 -6
  14. package/dist/src/server/agent-runtime-types.d.ts +4 -0
  15. package/dist/src/server/agent-startup-instructions.d.ts +4 -0
  16. package/dist/src/server/agent-startup-instructions.js +35 -9
  17. package/dist/src/server/agent-stdin-dispatcher.js +17 -9
  18. package/dist/src/server/diagnostics-support-bundle.d.ts +288 -0
  19. package/dist/src/server/diagnostics-support-bundle.js +179 -0
  20. package/dist/src/server/dispatch-ledger-store.d.ts +4 -1
  21. package/dist/src/server/dispatch-ledger-store.js +46 -6
  22. package/dist/src/server/hive-envelope-escape.d.ts +2 -0
  23. package/dist/src/server/hive-envelope-escape.js +2 -0
  24. package/dist/src/server/hive-team-guidance.d.ts +1 -1
  25. package/dist/src/server/hive-team-guidance.js +67 -25
  26. package/dist/src/server/message-log-store.d.ts +1 -1
  27. package/dist/src/server/post-start-input-writer.js +12 -4
  28. package/dist/src/server/preset-launch-support.d.ts +2 -0
  29. package/dist/src/server/preset-launch-support.js +65 -2
  30. package/dist/src/server/protocol-event-stats.d.ts +39 -0
  31. package/dist/src/server/protocol-event-stats.js +84 -0
  32. package/dist/src/server/recovery-summary.js +19 -14
  33. package/dist/src/server/role-template-store.d.ts +1 -1
  34. package/dist/src/server/role-templates.d.ts +1 -0
  35. package/dist/src/server/role-templates.js +43 -29
  36. package/dist/src/server/routes-action-center.d.ts +2 -0
  37. package/dist/src/server/routes-action-center.js +37 -0
  38. package/dist/src/server/routes-diagnostics.d.ts +2 -0
  39. package/dist/src/server/routes-diagnostics.js +17 -0
  40. package/dist/src/server/routes-scenarios.d.ts +25 -0
  41. package/dist/src/server/routes-scenarios.js +89 -0
  42. package/dist/src/server/routes-settings.js +2 -11
  43. package/dist/src/server/routes-team-memory.js +52 -0
  44. package/dist/src/server/routes-team.js +40 -20
  45. package/dist/src/server/routes-workspace-memory-dreams.js +8 -0
  46. package/dist/src/server/routes-workspace-uploads.d.ts +2 -0
  47. package/dist/src/server/routes-workspace-uploads.js +154 -0
  48. package/dist/src/server/routes-workspaces.js +29 -3
  49. package/dist/src/server/routes.js +8 -0
  50. package/dist/src/server/runtime-message-builders.d.ts +0 -1
  51. package/dist/src/server/runtime-message-builders.js +0 -8
  52. package/dist/src/server/runtime-store-contract.d.ts +15 -0
  53. package/dist/src/server/runtime-store-dream.d.ts +14 -1
  54. package/dist/src/server/runtime-store-dream.js +49 -1
  55. package/dist/src/server/runtime-store-helpers.d.ts +7 -0
  56. package/dist/src/server/runtime-store-helpers.js +85 -22
  57. package/dist/src/server/runtime-store-worker-mutations.d.ts +11 -0
  58. package/dist/src/server/runtime-store-worker-mutations.js +46 -0
  59. package/dist/src/server/runtime-store-workflows.js +10 -6
  60. package/dist/src/server/runtime-store.js +34 -42
  61. package/dist/src/server/scenario-presets.d.ts +25 -0
  62. package/dist/src/server/scenario-presets.js +35 -0
  63. package/dist/src/server/sentinel-heartbeat.d.ts +30 -0
  64. package/dist/src/server/sentinel-heartbeat.js +145 -0
  65. package/dist/src/server/spawn-cli-resolver.d.ts +37 -0
  66. package/dist/src/server/spawn-cli-resolver.js +70 -0
  67. package/dist/src/server/spawn-worker-defaults.d.ts +13 -0
  68. package/dist/src/server/spawn-worker-defaults.js +45 -0
  69. package/dist/src/server/sqlite-schema-v32.d.ts +2 -0
  70. package/dist/src/server/sqlite-schema-v32.js +17 -0
  71. package/dist/src/server/sqlite-schema-v33.d.ts +3 -0
  72. package/dist/src/server/sqlite-schema-v33.js +18 -0
  73. package/dist/src/server/sqlite-schema-v34.d.ts +11 -0
  74. package/dist/src/server/sqlite-schema-v34.js +19 -0
  75. package/dist/src/server/sqlite-schema-v35.d.ts +3 -0
  76. package/dist/src/server/sqlite-schema-v35.js +23 -0
  77. package/dist/src/server/sqlite-schema.d.ts +1 -1
  78. package/dist/src/server/sqlite-schema.js +35 -1
  79. package/dist/src/server/system-message.d.ts +5 -2
  80. package/dist/src/server/system-message.js +5 -2
  81. package/dist/src/server/tasks-file-watcher.d.ts +8 -0
  82. package/dist/src/server/tasks-file-watcher.js +31 -2
  83. package/dist/src/server/team-authz.d.ts +9 -1
  84. package/dist/src/server/team-authz.js +24 -0
  85. package/dist/src/server/team-list-serializer.d.ts +2 -2
  86. package/dist/src/server/team-list-serializer.js +2 -1
  87. package/dist/src/server/team-memory-digest.js +4 -4
  88. package/dist/src/server/team-memory-dream-applier.js +24 -3
  89. package/dist/src/server/team-memory-dream-prompt.d.ts +13 -0
  90. package/dist/src/server/team-memory-dream-prompt.js +91 -0
  91. package/dist/src/server/team-memory-dream-run-store.d.ts +2 -0
  92. package/dist/src/server/team-memory-dream-run-store.js +14 -4
  93. package/dist/src/server/team-memory-dream-runner.d.ts +2 -21
  94. package/dist/src/server/team-memory-dream-runner.js +3 -148
  95. package/dist/src/server/team-memory-dream-store.d.ts +1 -1
  96. package/dist/src/server/team-memory-dream-store.js +1 -1
  97. package/dist/src/server/team-operations.d.ts +18 -2
  98. package/dist/src/server/team-operations.js +222 -33
  99. package/dist/src/server/team-recap.d.ts +10 -0
  100. package/dist/src/server/team-recap.js +73 -0
  101. package/dist/src/server/terminal-input-profile.js +88 -9
  102. package/dist/src/server/upload-limits.d.ts +2 -0
  103. package/dist/src/server/upload-limits.js +2 -0
  104. package/dist/src/server/workflow-cli-policy.d.ts +7 -2
  105. package/dist/src/server/workflow-cli-policy.js +15 -3
  106. package/dist/src/server/workflow-run-store.d.ts +1 -0
  107. package/dist/src/server/workflow-run-store.js +11 -1
  108. package/dist/src/server/workflow-runner.d.ts +4 -1
  109. package/dist/src/server/workflow-runner.js +418 -118
  110. package/dist/src/server/workflow-script-loader.d.ts +3 -2
  111. package/dist/src/server/workflow-script-loader.js +161 -0
  112. package/dist/src/server/workspace-store-contract.d.ts +2 -0
  113. package/dist/src/server/workspace-store.d.ts +1 -1
  114. package/dist/src/server/workspace-store.js +40 -30
  115. package/dist/src/server/workspace-upload-store.d.ts +40 -0
  116. package/dist/src/server/workspace-upload-store.js +295 -0
  117. package/dist/src/shared/scenario-presets.d.ts +32 -0
  118. package/dist/src/shared/scenario-presets.js +69 -0
  119. package/dist/src/shared/types.d.ts +12 -1
  120. package/package.json +1 -1
  121. package/web/dist/assets/AddWorkerDialog-CTOsPurT.js +2 -0
  122. package/web/dist/assets/AddWorkspaceFlow-D_fSv-cO.js +1 -0
  123. package/web/dist/assets/FirstRunWizard-C24MUssx.js +1 -0
  124. package/web/dist/assets/{MarketplaceDrawer-Dd8WIA8T.js → MarketplaceDrawer-7gVUjgAx.js} +11 -11
  125. package/web/dist/assets/TaskGraphDrawer-Beh95yUu.js +1 -0
  126. package/web/dist/assets/WhatsNewDialog-vnShQWjg.js +1 -0
  127. package/web/dist/assets/WorkerModal-DcxIBPlu.js +1 -0
  128. package/web/dist/assets/{WorkflowsDrawer-Bjf4olbR.js → WorkflowsDrawer-C8csID1I.js} +1 -1
  129. package/web/dist/assets/WorkspaceMemoryDrawer-C_Aq9-o6.js +1 -0
  130. package/web/dist/assets/{WorkspaceTaskDrawer-BIWwISvA.js → WorkspaceTaskDrawer-CIMYzlpt.js} +1 -1
  131. package/web/dist/assets/index-3Hc1oXlt.css +1 -0
  132. package/web/dist/assets/index-D9zUE0W6.js +79 -0
  133. package/web/dist/assets/{search-Bk2HQvO7.js → search-BRQbevR2.js} +1 -1
  134. package/web/dist/assets/{square-terminal-D93m9hfY.js → square-terminal-Co4pmGzn.js} +1 -1
  135. package/web/dist/index.html +2 -2
  136. package/web/dist/sw.js +1 -1
  137. package/dist/src/server/env-sync-message.d.ts +0 -9
  138. package/dist/src/server/env-sync-message.js +0 -29
  139. package/web/dist/assets/AddWorkerDialog-CbV75qUX.js +0 -2
  140. package/web/dist/assets/AddWorkspaceFlow-CwV-7wPx.js +0 -1
  141. package/web/dist/assets/FirstRunWizard-a6PWIK3x.js +0 -1
  142. package/web/dist/assets/TaskGraphDrawer-Bk5WFIk_.js +0 -1
  143. package/web/dist/assets/WhatsNewDialog-C2VZaip0.js +0 -1
  144. package/web/dist/assets/WorkerModal-DucW-9YT.js +0 -1
  145. package/web/dist/assets/WorkspaceMemoryDrawer-DglCy_5f.js +0 -1
  146. package/web/dist/assets/index-BAiLYajK.css +0 -1
  147. package/web/dist/assets/index-BV2k9Dts.js +0 -73
@@ -0,0 +1,188 @@
1
+ const MAX_RECENT_ACTIVITY = 12;
2
+ const MAX_REMOTE_ATTENTION_SCAN = 25;
3
+ const MAX_REMOTE_ATTENTION_ITEMS = 3;
4
+ const MAX_WAITING_REPORT_ATTENTION_ITEMS = 3;
5
+ const PREVIEW_MAX = 180;
6
+ const TERMINAL_HINT_MAX = 120;
7
+ // A quiet dispatch is surfaced as an FYI, never an alarm: Hive deliberately
8
+ // has no timeout/stall detection (design §3.6 — the user judges a silent
9
+ // worker). Ordinary coding tasks routinely run past any threshold we could
10
+ // pick, so the age is generous and the severity stays at `info`.
11
+ const WAITING_REPORT_MS = 10 * 60 * 1000;
12
+ const truncateText = (text, max) => {
13
+ if (!text)
14
+ return null;
15
+ const normalized = text.replace(/\s+/gu, ' ').trim();
16
+ if (!normalized)
17
+ return null;
18
+ return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
19
+ };
20
+ const dispatchTimestamp = (dispatch) => dispatch.reportedAt ?? dispatch.submittedAt ?? dispatch.deliveredAt ?? dispatch.createdAt;
21
+ const dispatchActivityKind = (status) => {
22
+ if (status === 'reported')
23
+ return 'reported';
24
+ if (status === 'cancelled')
25
+ return 'cancelled';
26
+ if (status === 'submitted')
27
+ return 'submitted';
28
+ return 'queued';
29
+ };
30
+ const workerName = (workersById, workerId) => workersById.get(workerId)?.name ?? null;
31
+ const workspaceIdFromEndpoint = (endpoint) => {
32
+ const match = endpoint?.match(/^\/api\/(?:ui\/)?workspaces\/([^/?#]+)/u);
33
+ if (!match?.[1])
34
+ return null;
35
+ try {
36
+ return decodeURIComponent(match[1]);
37
+ }
38
+ catch {
39
+ return match[1];
40
+ }
41
+ };
42
+ const remoteAuditBelongsToWorkspace = (entry, workspaceId) => {
43
+ if (entry.workspaceId !== null)
44
+ return entry.workspaceId === workspaceId;
45
+ const endpointWorkspaceId = workspaceIdFromEndpoint(entry.endpoint);
46
+ return endpointWorkspaceId === null || endpointWorkspaceId === workspaceId;
47
+ };
48
+ const waitingForReportAge = (dispatch, now) => {
49
+ if (dispatch.status !== 'submitted' || dispatch.submittedAt === null)
50
+ return null;
51
+ const age = now - dispatch.submittedAt;
52
+ return age >= WAITING_REPORT_MS ? age : null;
53
+ };
54
+ const buildAttentionItems = (input) => {
55
+ const attention = [];
56
+ const workersById = new Map(input.workers.map((worker) => [worker.id, worker]));
57
+ if (input.workers.length === 0) {
58
+ attention.push({ kind: 'no_workers', severity: 'info' });
59
+ }
60
+ for (const worker of input.workers) {
61
+ if (worker.status !== 'stopped' || worker.pendingTaskCount <= 0)
62
+ continue;
63
+ const openCount = input.openDispatches.filter((dispatch) => dispatch.toAgentId === worker.id).length;
64
+ attention.push({
65
+ kind: 'stopped_with_queue',
66
+ open_dispatches: openCount,
67
+ pending_task_count: worker.pendingTaskCount,
68
+ severity: 'warning',
69
+ worker_id: worker.id,
70
+ worker_name: worker.name,
71
+ });
72
+ }
73
+ for (const dispatch of input.waitingReportDispatches.slice(0, MAX_WAITING_REPORT_ATTENTION_ITEMS)) {
74
+ const age = waitingForReportAge(dispatch, input.now);
75
+ if (age === null)
76
+ continue;
77
+ attention.push({
78
+ dispatch_id: dispatch.id,
79
+ kind: 'dispatch_waiting_report',
80
+ minutes_ago: Math.floor(age / 60_000),
81
+ severity: 'info',
82
+ submitted_at: dispatch.submittedAt,
83
+ worker_id: dispatch.toAgentId,
84
+ worker_name: workerName(workersById, dispatch.toAgentId),
85
+ });
86
+ }
87
+ const remoteProblems = input.store
88
+ .getRemoteAuditStore()
89
+ .list(MAX_REMOTE_ATTENTION_SCAN)
90
+ .filter((entry) => entry.result !== 'ok' && remoteAuditBelongsToWorkspace(entry, input.workspaceId))
91
+ .slice(0, MAX_REMOTE_ATTENTION_ITEMS);
92
+ for (const entry of remoteProblems) {
93
+ attention.push({
94
+ action: entry.action,
95
+ endpoint: entry.endpoint,
96
+ kind: entry.result === 'error' ? 'remote_error' : 'remote_rejected',
97
+ reason: entry.rejectReason,
98
+ severity: entry.result === 'error' ? 'error' : 'warning',
99
+ ts: entry.ts,
100
+ });
101
+ }
102
+ return attention;
103
+ };
104
+ const serializeDispatchEvidence = (dispatch, workersById, includeTextEvidence) => {
105
+ const textEvidence = includeTextEvidence
106
+ ? {
107
+ label: dispatch.label,
108
+ phase: dispatch.phase,
109
+ report_preview: truncateText(dispatch.reportText, PREVIEW_MAX),
110
+ task_preview: truncateText(dispatch.text, PREVIEW_MAX),
111
+ }
112
+ : {
113
+ // Workflow-authored label/phase are task-title-class free text (the
114
+ // first-party guidance even puts file paths in labels), so the
115
+ // shareable support bundle reduces them to presence flags.
116
+ has_label: dispatch.label !== null,
117
+ has_phase: dispatch.phase !== null,
118
+ };
119
+ return {
120
+ created_at: dispatch.createdAt,
121
+ id: dispatch.id,
122
+ ...textEvidence,
123
+ status: dispatch.status,
124
+ submitted_at: dispatch.submittedAt,
125
+ timestamp: dispatchTimestamp(dispatch),
126
+ to_agent_id: dispatch.toAgentId,
127
+ to_worker_name: workerName(workersById, dispatch.toAgentId),
128
+ workflow_run_id: dispatch.workflowRunId,
129
+ };
130
+ };
131
+ export const buildActionCenterSummary = (input) => {
132
+ const includeTextEvidence = input.includeTextEvidence !== false;
133
+ const now = input.now ?? Date.now();
134
+ const workers = input.store.listWorkers(input.workspaceId);
135
+ const workersById = new Map(workers.map((worker) => [worker.id, worker]));
136
+ const openDispatches = input.store.listOpenDispatches(input.workspaceId);
137
+ const waitingReportDispatches = openDispatches.filter((dispatch) => waitingForReportAge(dispatch, now) !== null);
138
+ const recentDispatches = input.store.listRecentDispatches(input.workspaceId, MAX_RECENT_ACTIVITY);
139
+ return {
140
+ attention: buildAttentionItems({
141
+ now,
142
+ openDispatches,
143
+ store: input.store,
144
+ waitingReportDispatches,
145
+ workers,
146
+ workspaceId: input.workspaceId,
147
+ }),
148
+ generated_at: now,
149
+ recent_activity: recentDispatches.map((dispatch) => ({
150
+ ...serializeDispatchEvidence(dispatch, workersById, includeTextEvidence),
151
+ kind: dispatchActivityKind(dispatch.status),
152
+ })),
153
+ summary: {
154
+ idle_workers: workers.filter((worker) => worker.status === 'idle').length,
155
+ open_dispatches: openDispatches.length,
156
+ recent_reports: recentDispatches.filter((dispatch) => dispatch.status === 'reported').length,
157
+ stopped_with_queue: workers.filter((worker) => worker.status === 'stopped' && worker.pendingTaskCount > 0).length,
158
+ stopped_workers: workers.filter((worker) => worker.status === 'stopped').length,
159
+ total_workers: workers.length,
160
+ waiting_reports: waitingReportDispatches.length,
161
+ working_workers: workers.filter((worker) => worker.status === 'working').length,
162
+ },
163
+ workers: workers.map((worker) => {
164
+ const workerOpenDispatches = openDispatches.filter((dispatch) => dispatch.toAgentId === worker.id);
165
+ const latestReport = recentDispatches.find((dispatch) => dispatch.toAgentId === worker.id && dispatch.status === 'reported') ?? null;
166
+ const terminalHint = includeTextEvidence
167
+ ? {
168
+ terminal_hint: truncateText(input.store.getLastPtyLineForAgent(input.workspaceId, worker.id), TERMINAL_HINT_MAX),
169
+ }
170
+ : {};
171
+ return {
172
+ current_dispatch: workerOpenDispatches[0]
173
+ ? serializeDispatchEvidence(workerOpenDispatches[0], workersById, includeTextEvidence)
174
+ : null,
175
+ id: worker.id,
176
+ latest_report: latestReport
177
+ ? serializeDispatchEvidence(latestReport, workersById, includeTextEvidence)
178
+ : null,
179
+ name: worker.name,
180
+ pending_task_count: worker.pendingTaskCount,
181
+ role: worker.role,
182
+ status: worker.status,
183
+ ...terminalHint,
184
+ };
185
+ }),
186
+ workspace_id: input.workspaceId,
187
+ };
188
+ };
@@ -14,4 +14,10 @@ interface ResolvedSpawnCommand {
14
14
  export declare const resolveCommandPath: (command: string, cwd: string, env: NodeJS.ProcessEnv, platform?: NodeJS.Platform) => string;
15
15
  export declare const resolveSpawnCommand: (command: string, cwd: string, env: NodeJS.ProcessEnv, args?: string[], platform?: NodeJS.Platform) => ResolvedSpawnCommand;
16
16
  export declare const assertCommandIsExecutable: (command: string, cwd: string, env: NodeJS.ProcessEnv) => void;
17
+ /**
18
+ * PATH-availability probe shared by Settings preset serialization and
19
+ * `team spawn` CLI selection. True when `command` resolves to an executable
20
+ * with the preset's env overlayed on the current process env. Never throws.
21
+ */
22
+ export declare const isCommandAvailableOnPath: (command: string, env?: Record<string, string>) => boolean;
17
23
  export type { ResolvedSpawnCommand };
@@ -104,3 +104,19 @@ export const resolveSpawnCommand = (command, cwd, env, args = [], platform = pro
104
104
  export const assertCommandIsExecutable = (command, cwd, env) => {
105
105
  resolveCommandPath(command, cwd, env);
106
106
  };
107
+ /**
108
+ * PATH-availability probe shared by Settings preset serialization and
109
+ * `team spawn` CLI selection. True when `command` resolves to an executable
110
+ * with the preset's env overlayed on the current process env. Never throws.
111
+ */
112
+ export const isCommandAvailableOnPath = (command, env = {}) => {
113
+ if (!command.trim())
114
+ return false;
115
+ try {
116
+ resolveCommandPath(command, process.cwd(), { ...process.env, ...env });
117
+ return true;
118
+ }
119
+ catch {
120
+ return false;
121
+ }
122
+ };
@@ -4,6 +4,15 @@ import { resolveSpawnCommand } from './agent-command-resolver.js';
4
4
  import { attachAgentPty, toAgentRunSnapshot } from './agent-manager-support.js';
5
5
  import { createPtyOutputBus } from './pty-output-bus.js';
6
6
  const createRunId = () => randomUUID();
7
+ const CLAUDE_AGENT_SESSION_ENV_KEYS = new Set([
8
+ 'AI_AGENT',
9
+ 'CLAUDECODE',
10
+ 'CLAUDE_CODE_ENTRYPOINT',
11
+ 'CLAUDE_CODE_EXECPATH',
12
+ 'CLAUDE_CODE_SESSION_ID',
13
+ 'CLAUDE_EFFORT',
14
+ ]);
15
+ const isClaudeAgentSessionEnvKey = (key, platform) => CLAUDE_AGENT_SESSION_ENV_KEYS.has(platform === 'win32' ? key.toUpperCase() : key);
7
16
  const getWindowsEnvKey = (env, key) => {
8
17
  if (Object.hasOwn(env, key))
9
18
  return key;
@@ -18,7 +27,8 @@ export const createSpawnEnv = (inputEnv, platform = process.platform, parentEnv
18
27
  env[targetKey] = value;
19
28
  }
20
29
  for (const key of Object.keys(env)) {
21
- if (env[key] === undefined)
30
+ // Final spawn boundary: strip outer Claude session identity, not Claude runtime config.
31
+ if (env[key] === undefined || isClaudeAgentSessionEnvKey(key, platform))
22
32
  delete env[key];
23
33
  }
24
34
  return env;
@@ -1,9 +1,10 @@
1
1
  import { classifyCompletedRunStatus, shouldClearResumedSessionOnExit, } from './agent-exit-classification.js';
2
2
  import { buildAgentRunBootstrap, startAgentRunCapture } from './agent-run-bootstrap.js';
3
3
  import { handleAgentRunExit } from './agent-run-exit-handler.js';
4
- import { buildAgentStartupInstructions } from './agent-startup-instructions.js';
4
+ import { buildAgentStartupInstructions, buildWorkflowAgentStartupInstructions, } from './agent-startup-instructions.js';
5
5
  import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
6
6
  import { createPostStartInputWriter, isInteractiveAgentCommand } from './post-start-input-writer.js';
7
+ import { isResumeLaunchConfig } from './preset-launch-support.js';
7
8
  import { buildMemoryDigestSafely, logMemoryDigestInjection, rollbackMemoryDigestInjection, } from './team-memory-injection.js';
8
9
  export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, store, sessionStore, tokenRegistry, getCommandPreset, getAgent, restartPolicy, getFlags, memoryInjection, }) => async (workspace, agentId, config, hivePort) => {
9
10
  if (!agentManager)
@@ -57,9 +58,21 @@ export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, sto
57
58
  tokenRegistry.revokeIfMatches(agentId, token);
58
59
  throw error;
59
60
  }
61
+ let markPostStartInputReady = () => { };
62
+ const postStartInputReady = new Promise((resolve) => {
63
+ markPostStartInputReady = resolve;
64
+ });
65
+ let postStartInputReadyMarked = false;
66
+ const finishPostStartInput = () => {
67
+ if (postStartInputReadyMarked)
68
+ return;
69
+ postStartInputReadyMarked = true;
70
+ markPostStartInputReady();
71
+ };
60
72
  const liveRun = {
61
73
  ...run,
62
74
  exitCode: run.status === 'error' ? run.exitCode : null,
75
+ postStartInputReady,
63
76
  startedAt,
64
77
  status: run.status === 'error' ? 'error' : 'starting',
65
78
  };
@@ -86,6 +99,7 @@ export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, sto
86
99
  onAgentExit(workspace.id, agentId);
87
100
  registry.resolveExit(run.runId);
88
101
  registry.clearPendingExitCode(run.runId);
102
+ finishPostStartInput();
89
103
  return liveRun;
90
104
  }
91
105
  startAgentRunCapture({
@@ -106,16 +120,38 @@ export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, sto
106
120
  const postStartWriter = createPostStartInputWriter(agentManager, startConfig.interactiveCommand ?? startConfig.command);
107
121
  queueMicrotask(() => {
108
122
  try {
123
+ let restartWrite = null;
109
124
  const injectedRestartMessage = restartPolicy.injectPostStartMessage({
110
125
  agentId,
111
126
  runId: run.runId,
112
127
  startConfig,
113
128
  workspace,
114
- writeToRun: postStartWriter,
129
+ writeToRun: (targetRunId, text) => {
130
+ restartWrite = postStartWriter(targetRunId, text);
131
+ return restartWrite;
132
+ },
115
133
  });
116
- if (!injectedRestartMessage &&
117
- agent &&
134
+ if (injectedRestartMessage) {
135
+ void (restartWrite ?? Promise.resolve()).catch(() => { }).finally(finishPostStartInput);
136
+ return;
137
+ }
138
+ if (isResumeLaunchConfig(startConfig)) {
139
+ finishPostStartInput();
140
+ return;
141
+ }
142
+ if (agent &&
118
143
  isInteractiveAgentCommand(startConfig.interactiveCommand ?? startConfig.command)) {
144
+ if (agent.spawnedBy === 'workflow') {
145
+ void postStartWriter(run.runId, buildWorkflowAgentStartupInstructions({
146
+ agent,
147
+ workspace,
148
+ }))
149
+ .catch(() => {
150
+ // The agent may have exited before post-start guidance could be written.
151
+ })
152
+ .finally(finishPostStartInput);
153
+ return;
154
+ }
119
155
  const memoryDigest = buildMemoryDigestSafely({
120
156
  contextType: 'startup',
121
157
  memoryInjection,
@@ -134,13 +170,18 @@ export const createAgentRunStarter = ({ agentManager, registry, onAgentExit, sto
134
170
  memoryDigest: auditedMemoryDigest?.text,
135
171
  workspace,
136
172
  flags: getFlags?.() ?? FEATURE_FLAGS_ALL_OFF,
137
- })).catch(() => {
173
+ }))
174
+ .catch(() => {
138
175
  rollbackMemoryDigestInjection({ injectionIds, memoryInjection });
139
176
  // The agent may have exited before post-start guidance could be written.
140
- });
177
+ })
178
+ .finally(finishPostStartInput);
179
+ return;
141
180
  }
181
+ finishPostStartInput();
142
182
  }
143
183
  catch {
184
+ finishPostStartInput();
144
185
  // The agent may have exited before post-start guidance could be written.
145
186
  }
146
187
  });
@@ -1,5 +1,9 @@
1
1
  import type { AgentRunSnapshot } from './agent-manager.js';
2
2
  export interface LiveAgentRun extends AgentRunSnapshot {
3
+ /** Resolves after the startup / recovery input queued right after PTY start
4
+ * has either been written or intentionally skipped. Workflow workers use
5
+ * this to avoid racing their first dispatch ahead of startup instructions. */
6
+ postStartInputReady?: Promise<void>;
3
7
  startedAt: number;
4
8
  userStopped?: boolean;
5
9
  }
@@ -18,3 +18,7 @@ export declare const buildAgentStartupInstructions: ({ agent, memoryDigest, work
18
18
  * team-sizing rule. Omitted → all off. */
19
19
  flags?: FeatureFlags;
20
20
  }) => string;
21
+ export declare const buildWorkflowAgentStartupInstructions: ({ agent, workspace, }: {
22
+ agent: AgentSummary;
23
+ workspace: WorkspaceSummary;
24
+ }) => string;
@@ -1,37 +1,63 @@
1
1
  import { BUILTIN_COMMAND_PRESET_CLI_LIST } from './command-preset-defaults.js';
2
2
  import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
3
+ import { escapeHiveEnvelopeText } from './hive-envelope-escape.js';
3
4
  import { getHiveTeamRules } from './hive-team-guidance.js';
4
5
  import { TASKS_RELATIVE_PATH } from './tasks-file.js';
5
6
  export const buildAgentSessionBindingMarker = ({ agent, workspace, }) => `Hive session binding: workspace_id=${workspace.id}; agent_id=${agent.id}`;
6
- export const buildAgentLegacyIdentityMarker = ({ agent, workspace, }) => `You are ${agent.name} (${agent.role}) in workspace ${workspace.name}.`;
7
+ export const buildAgentLegacyIdentityMarker = ({ agent, workspace, }) => `You are ${escapeHiveEnvelopeText(agent.name)} (${agent.role}) in workspace ${escapeHiveEnvelopeText(workspace.name)}.`;
7
8
  export const buildAgentStartupInstructions = ({ agent, memoryDigest, workspace, flags = FEATURE_FLAGS_ALL_OFF, }) => {
8
9
  const { workflowsEnabled } = flags;
9
10
  const lines = [
10
11
  '<hive-message kind="startup">',
11
12
  '',
12
13
  buildAgentLegacyIdentityMarker({ agent, workspace }),
13
- `Current workspace: ${workspace.name}`,
14
- `Project path: ${workspace.path}`,
14
+ `Current workspace: ${escapeHiveEnvelopeText(workspace.name)}`,
15
+ `Project path: ${escapeHiveEnvelopeText(workspace.path)}`,
15
16
  buildAgentSessionBindingMarker({ agent, workspace }),
16
17
  '',
17
- // agent.description is the role contract it follows the user's chosen
18
- // language / custom edit and is intentionally left as authored.
19
- `Your role: ${agent.description}`,
18
+ // agent.description follows the user's chosen language / custom edit, but
19
+ // still travels inside a Hive envelope, so XML-like delimiters are escaped.
20
+ `Your role: ${escapeHiveEnvelopeText(agent.description)}`,
20
21
  '',
21
22
  ];
22
23
  if (memoryDigest) {
23
24
  lines.push(memoryDigest, '');
24
25
  }
25
26
  if (agent.role === 'orchestrator') {
26
- lines.push('Your responsibilities:', '- Respond to the user directly; clarify the goal and break it into dispatchable tasks', `- Maintain ${TASKS_RELATIVE_PATH}`, '- Dispatch by worker name and drive the next step from each report', '', 'Available team commands:', '- team list', '- team recall "<query>" [--limit <n>] [--window <n>]', '- team memory add "<body>" [--kind fact|preference|decision|pitfall|procedure_ref] [--tag <tag>]', '- team memory show <memory-id>', '- team memory search "<query>" [--limit <n>]', '- team memory forget <memory-id>', '- team send "<worker-name>" "<task>"', `- team spawn <role> [--name <n>] [--cli <${BUILTIN_COMMAND_PRESET_CLI_LIST}>] [--ephemeral]`, '- team cancel --dispatch <id> "<reason>"', ...(workflowsEnabled
27
+ lines.push('Your responsibilities:', '- Respond to the user directly; clarify the goal and break it into dispatchable tasks', `- Maintain ${TASKS_RELATIVE_PATH} (GFM task list; mark dependencies with a trailing \`[needs: #2, #5]\` — 1-based positions; \`team next\` lists the tasks unblocked right now)`, '- Dispatch by worker name and drive the next step from each report', '', 'Available team commands:', '- team list', '- team next', '- team recall "<query>" [--limit <n>] [--window <n>]', '- team memory add "<body>" [--kind fact|preference|decision|pitfall|procedure_ref] [--tag <tag>]', '- team memory show <memory-id>', '- team memory search "<query>" [--limit <n>]', '- team memory dream show <dream-run-id>', '- team memory apply --run <dream-run-id> --stdin', '- team memory forget <memory-id>', '- team send "<worker-name>" "<task>"', `- team spawn <role> [--name <n>] [--cli <${BUILTIN_COMMAND_PRESET_CLI_LIST}>] [--ephemeral]`, '- team dismiss <worker-name>', '- team cancel --dispatch <id> "<reason>"', ...(workflowsEnabled
27
28
  ? [
28
29
  '- team workflow run --stdin (fan-out / staged work — see .hive/PROTOCOL.md for the DSL)',
29
30
  ]
30
- : []), '', 'Always dispatch by worker name, never by worker id.', 'Always cancel an open dispatch by its dispatch id.', 'Search memory before adding: use `team memory search "<query>"` to avoid duplicates.', 'Use `team memory add` only for rare, evidence-backed workspace facts/decisions/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` only to archive obsolete workspace memory; it does not physically delete evidence.', '', 'Hive worker dispatch rules:', ...getHiveTeamRules(agent, flags));
31
+ : []), '', 'Always dispatch by worker name, never by worker id.', 'Always cancel an open dispatch by its dispatch id.', '', 'Hive worker dispatch rules:', ...getHiveTeamRules(agent, flags));
32
+ }
33
+ else if (agent.role === 'sentinel') {
34
+ // Observe-only role: `team report` is 403'd for sentinels and they never
35
+ // receive dispatches, so the worker command list (which mandates report)
36
+ // must not be injected here — list only what the authz matrix allows.
37
+ lines.push('Available team commands:', '- team status "<finding>" [--artifact <path>] escalate a patrol finding to the Orchestrator', '- team status --stdin [--artifact <path>] same, body read from stdin', '- team recall "<query>" [--limit <n>] [--window <n>] search prior team messages/reports in this workspace', '- team memory show <memory-id> inspect a memory entry and its evidence', '- team memory search "<query>" [--limit <n>] search active workspace memory', '- team --help command syntax only', '', 'You observe only: no dispatches are ever assigned to you, and `team report` is not available to your role.', '', 'Hive sentinel boundaries:', ...getHiveTeamRules(agent, flags));
31
38
  }
32
39
  else {
33
- lines.push('Available team commands:', '- team report "<result>" [--dispatch <id>] [--artifact <path>] report done / failed / blocked', '- team report --stdin [--dispatch <id>] [--artifact <path>] same, body read from stdin (multi-line / quotes / special chars)', '- team status "<state>" [--artifact <path>] mid-task progress / standby / connected status', '- team status --stdin [--artifact <path>] same, body read from stdin', '- team recall "<query>" [--limit <n>] [--window <n>] search prior team messages/reports in this workspace', '- team memory show <memory-id> inspect a memory entry and its evidence', '- team memory search "<query>" [--limit <n>] search active workspace memory', '- team list list the workspace workers (with status)', '- team --help command syntax only; NOT a way to report', '', 'Syntax notes:', '- The body is the first positional argument; flag order is free: `team report "result" --dispatch X` and `team report --dispatch X "result"` are both valid.', "- Long bodies (multi-line / quotes / shell metacharacters) always go through `--stdin`, piped in via your shell (POSIX: a quoted heredoc `<<'EOF' … EOF` to stop $var / backtick expansion; Windows cmd: `type body.txt |` or `< body.txt`; PowerShell: `Get-Content -Raw -Encoding utf8 body.txt |`). `--stdin` only reads from stdin and relies on no shell syntax of its own.", '- On error the CLI also prints USAGE — fix the arguments against it.', '', 'When a task is done you MUST run `team report "<result>"`.', 'Failure, blocked, or partial completion are also reported with `team report "<current state and reason>"`.', 'When no task is in progress, report connection / standby / blocked state with `team status "<state>"`.', 'Use `team recall "<query>"` when prior team messages or reports may contain useful evidence.', 'Use `team memory search "<query>"` to look up active durable workspace memory.', 'Include durable findings in `team report`; workers cannot add or forget memory.', 'Do not call team send; workers cannot dispatch to each other.', '', 'Hive worker boundaries:', ...getHiveTeamRules(agent, flags));
40
+ lines.push('Available team commands:', '- team report "<result>" [--dispatch <id>] [--artifact <path>] report done / failed / blocked', '- team report --stdin [--dispatch <id>] [--artifact <path>] same, body read from stdin (multi-line / quotes / special chars)', '- team status "<state>" [--artifact <path>] progress / standby update at any time; never closes a dispatch', '- team status --stdin [--artifact <path>] same, body read from stdin', '- team recall "<query>" [--limit <n>] [--window <n>] search prior team messages/reports in this workspace', '- team memory show <memory-id> inspect a memory entry and its evidence', '- team memory search "<query>" [--limit <n>] search active workspace memory', '- team memory dream show <dream-run-id> inspect a pending Dream run only when the Orchestrator assigns memory review', '- team --help command syntax only; NOT a way to report', '', 'Syntax notes:', '- The body is the first positional argument; flag order is free: `team report "result" --dispatch X` and `team report --dispatch X "result"` are both valid.', "- Long bodies (multi-line / quotes / shell metacharacters) always go through `--stdin`, piped in via your shell (POSIX: a quoted heredoc `<<'EOF' … EOF` to stop $var / backtick expansion; Windows cmd: `type body.txt |` or `< body.txt`; PowerShell: `Get-Content -Raw -Encoding utf8 body.txt |`). `--stdin` only reads from stdin and relies on no shell syntax of its own.", '- On error the CLI also prints USAGE — fix the arguments against it.', '', 'Hive worker boundaries:', ...getHiveTeamRules(agent, flags));
34
41
  }
35
42
  lines.push('', '</hive-message>', '');
36
43
  return lines.join('\n');
37
44
  };
45
+ export const buildWorkflowAgentStartupInstructions = ({ agent, workspace, }) => [
46
+ '<hive-message kind="startup">',
47
+ '',
48
+ buildAgentLegacyIdentityMarker({ agent, workspace }),
49
+ `Current workspace: ${escapeHiveEnvelopeText(workspace.name)}`,
50
+ `Project path: ${escapeHiveEnvelopeText(workspace.path)}`,
51
+ buildAgentSessionBindingMarker({ agent, workspace }),
52
+ '',
53
+ `Your role: ${escapeHiveEnvelopeText(agent.description)}`,
54
+ '',
55
+ 'You are a one-shot Hive workflow worker. Finish only the dispatch that follows this startup message.',
56
+ 'Do not launch nested CLI subagents or workflow runners; do the work in this PTY.',
57
+ 'All workflow workers share the same workspace filesystem root. Stay inside the dispatch scope you receive; do not edit unrelated files or assume isolated worktrees.',
58
+ 'The user talks to the Orchestrator, not to you. Never wait for terminal input — if blocked or missing information, report the blocker with `team report`.',
59
+ 'Do not call `team send` or `team workflow`. When done, use the `team report ... --dispatch <id>` syntax from the dispatch message.',
60
+ '',
61
+ '</hive-message>',
62
+ '',
63
+ ].join('\n');
@@ -1,49 +1,57 @@
1
1
  import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
2
+ import { escapeHiveEnvelopeAttribute, escapeHiveEnvelopeText } from './hive-envelope-escape.js';
2
3
  import { buildOrchestratorReminderTail, buildWorkerReminderTail } from './hive-team-guidance.js';
3
4
  import { PtyInactiveError } from './http-errors.js';
4
5
  import { createPostStartInputWriter } from './post-start-input-writer.js';
5
6
  import { buildDispatchMemoryDigestSafely, logMemoryDigestInjection, rollbackMemoryDigestInjection, } from './team-memory-injection.js';
6
7
  export const buildOrchestratorReportPayload = (workerName, text, artifacts, flags = FEATURE_FLAGS_ALL_OFF) => {
7
- const lines = [`<hive-message kind="report" from="@${workerName}">`, text];
8
+ const lines = [
9
+ `<hive-message kind="report" from="@${escapeHiveEnvelopeAttribute(workerName)}">`,
10
+ escapeHiveEnvelopeText(text),
11
+ ];
8
12
  for (const artifact of artifacts)
9
- lines.push(`artifact: ${artifact}`);
13
+ lines.push(`artifact: ${escapeHiveEnvelopeText(artifact)}`);
10
14
  lines.push('</hive-message>', '', buildOrchestratorReminderTail(flags), '');
11
15
  return lines.join('\n');
12
16
  };
13
17
  export const buildOrchestratorStatusPayload = (workerName, text, artifacts, flags = FEATURE_FLAGS_ALL_OFF) => {
14
- const lines = [`<hive-message kind="status" from="@${workerName}">`, text];
18
+ const lines = [
19
+ `<hive-message kind="status" from="@${escapeHiveEnvelopeAttribute(workerName)}">`,
20
+ escapeHiveEnvelopeText(text),
21
+ ];
15
22
  for (const artifact of artifacts)
16
- lines.push(`artifact: ${artifact}`);
23
+ lines.push(`artifact: ${escapeHiveEnvelopeText(artifact)}`);
17
24
  lines.push('</hive-message>', '', buildOrchestratorReminderTail(flags), '');
18
25
  return lines.join('\n');
19
26
  };
20
27
  export const buildOrchestratorUserInputPayload = (text, flags = FEATURE_FLAGS_ALL_OFF) => [text, '', buildOrchestratorReminderTail(flags), ''].join('\n');
21
28
  export const buildWorkerDispatchPayload = (fromAgentName, workerDescription, dispatchId, text, memoryDigest) => [
22
- `<hive-message kind="dispatch" from="@${fromAgentName}">`,
29
+ `<hive-message kind="dispatch" from="@${escapeHiveEnvelopeAttribute(fromAgentName)}">`,
23
30
  '',
24
- `Your role: ${workerDescription}`,
31
+ `Your role: ${escapeHiveEnvelopeText(workerDescription)}`,
25
32
  '',
26
33
  'You must:',
27
34
  `- When the task is done, failed, blocked, or partially done, run \`team report "<result>" --dispatch ${dispatchId}\``,
28
35
  '- Do not do unrelated work; report as soon as you are done',
36
+ '- Never stop to wait for terminal input — if you need a decision, report the question as blocked instead',
29
37
  '',
30
38
  `dispatch_id: ${dispatchId}`,
31
39
  '',
32
40
  ...(memoryDigest ? [memoryDigest, ''] : []),
33
41
  'Task:',
34
- text,
42
+ escapeHiveEnvelopeText(text),
35
43
  '</hive-message>',
36
44
  '',
37
45
  buildWorkerReminderTail(dispatchId),
38
46
  '',
39
47
  ].join('\n');
40
48
  export const buildWorkerCancelPayload = (dispatchId, reason) => [
41
- `<hive-message kind="cancel" dispatch="${dispatchId}">`,
49
+ `<hive-message kind="cancel" dispatch="${escapeHiveEnvelopeAttribute(dispatchId)}">`,
42
50
  '',
43
51
  'Stop working on this dispatch and do not call team report for it.',
44
52
  '',
45
53
  'Cancellation reason:',
46
- reason,
54
+ escapeHiveEnvelopeText(reason),
47
55
  '</hive-message>',
48
56
  '',
49
57
  ].join('\n');