@tt-a1i/hive 2.0.1 → 2.1.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 +42 -0
- package/README.en.md +15 -6
- package/README.md +26 -4
- package/dist/src/cli/hive.d.ts +4 -0
- package/dist/src/cli/hive.js +25 -3
- package/dist/src/cli/team.d.ts +8 -1
- package/dist/src/cli/team.js +111 -11
- package/dist/src/server/action-center-summary.d.ts +193 -0
- package/dist/src/server/action-center-summary.js +188 -0
- package/dist/src/server/agent-command-resolver.d.ts +6 -0
- package/dist/src/server/agent-command-resolver.js +16 -0
- package/dist/src/server/agent-manager.js +11 -1
- package/dist/src/server/agent-run-starter.js +47 -6
- package/dist/src/server/agent-runtime-types.d.ts +4 -0
- package/dist/src/server/agent-startup-instructions.d.ts +4 -0
- package/dist/src/server/agent-startup-instructions.js +35 -9
- package/dist/src/server/agent-stdin-dispatcher.js +17 -9
- package/dist/src/server/diagnostics-support-bundle.d.ts +288 -0
- package/dist/src/server/diagnostics-support-bundle.js +179 -0
- package/dist/src/server/dispatch-ledger-store.d.ts +4 -1
- package/dist/src/server/dispatch-ledger-store.js +46 -6
- package/dist/src/server/hive-envelope-escape.d.ts +2 -0
- package/dist/src/server/hive-envelope-escape.js +2 -0
- package/dist/src/server/hive-team-guidance.d.ts +1 -1
- package/dist/src/server/hive-team-guidance.js +67 -25
- package/dist/src/server/message-log-store.d.ts +1 -1
- package/dist/src/server/post-start-input-writer.js +8 -2
- package/dist/src/server/preset-launch-support.d.ts +2 -0
- package/dist/src/server/preset-launch-support.js +65 -2
- package/dist/src/server/protocol-event-stats.d.ts +39 -0
- package/dist/src/server/protocol-event-stats.js +84 -0
- package/dist/src/server/recovery-summary.js +19 -14
- package/dist/src/server/role-template-store.d.ts +1 -1
- package/dist/src/server/role-templates.d.ts +1 -0
- package/dist/src/server/role-templates.js +43 -29
- package/dist/src/server/routes-action-center.d.ts +2 -0
- package/dist/src/server/routes-action-center.js +37 -0
- package/dist/src/server/routes-diagnostics.d.ts +2 -0
- package/dist/src/server/routes-diagnostics.js +17 -0
- package/dist/src/server/routes-scenarios.d.ts +25 -0
- package/dist/src/server/routes-scenarios.js +89 -0
- package/dist/src/server/routes-settings.js +2 -11
- package/dist/src/server/routes-team-memory.js +52 -0
- package/dist/src/server/routes-team.js +40 -20
- package/dist/src/server/routes-workspace-memory-dreams.js +8 -0
- package/dist/src/server/routes-workspace-uploads.d.ts +2 -0
- package/dist/src/server/routes-workspace-uploads.js +154 -0
- package/dist/src/server/routes-workspaces.js +29 -3
- package/dist/src/server/routes.js +8 -0
- package/dist/src/server/runtime-message-builders.d.ts +0 -1
- package/dist/src/server/runtime-message-builders.js +0 -8
- package/dist/src/server/runtime-store-contract.d.ts +15 -0
- package/dist/src/server/runtime-store-dream.d.ts +14 -1
- package/dist/src/server/runtime-store-dream.js +49 -1
- package/dist/src/server/runtime-store-helpers.d.ts +7 -0
- package/dist/src/server/runtime-store-helpers.js +85 -22
- package/dist/src/server/runtime-store-worker-mutations.d.ts +11 -0
- package/dist/src/server/runtime-store-worker-mutations.js +46 -0
- package/dist/src/server/runtime-store-workflows.js +10 -6
- package/dist/src/server/runtime-store.js +34 -42
- package/dist/src/server/scenario-presets.d.ts +25 -0
- package/dist/src/server/scenario-presets.js +35 -0
- package/dist/src/server/sentinel-heartbeat.d.ts +30 -0
- package/dist/src/server/sentinel-heartbeat.js +145 -0
- package/dist/src/server/spawn-cli-resolver.d.ts +37 -0
- package/dist/src/server/spawn-cli-resolver.js +70 -0
- package/dist/src/server/spawn-worker-defaults.d.ts +13 -0
- package/dist/src/server/spawn-worker-defaults.js +45 -0
- package/dist/src/server/sqlite-schema-v32.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v32.js +17 -0
- package/dist/src/server/sqlite-schema-v33.d.ts +3 -0
- package/dist/src/server/sqlite-schema-v33.js +18 -0
- package/dist/src/server/sqlite-schema-v34.d.ts +11 -0
- package/dist/src/server/sqlite-schema-v34.js +19 -0
- package/dist/src/server/sqlite-schema-v35.d.ts +3 -0
- package/dist/src/server/sqlite-schema-v35.js +23 -0
- package/dist/src/server/sqlite-schema.d.ts +1 -1
- package/dist/src/server/sqlite-schema.js +35 -1
- package/dist/src/server/system-message.d.ts +5 -2
- package/dist/src/server/system-message.js +5 -2
- package/dist/src/server/tasks-file-watcher.d.ts +8 -0
- package/dist/src/server/tasks-file-watcher.js +31 -2
- package/dist/src/server/team-authz.d.ts +9 -1
- package/dist/src/server/team-authz.js +24 -0
- package/dist/src/server/team-list-serializer.d.ts +2 -2
- package/dist/src/server/team-list-serializer.js +2 -1
- package/dist/src/server/team-memory-digest.js +4 -4
- package/dist/src/server/team-memory-dream-applier.js +24 -3
- package/dist/src/server/team-memory-dream-prompt.d.ts +13 -0
- package/dist/src/server/team-memory-dream-prompt.js +91 -0
- package/dist/src/server/team-memory-dream-run-store.d.ts +2 -0
- package/dist/src/server/team-memory-dream-run-store.js +14 -4
- package/dist/src/server/team-memory-dream-runner.d.ts +2 -21
- package/dist/src/server/team-memory-dream-runner.js +3 -148
- package/dist/src/server/team-memory-dream-store.d.ts +1 -1
- package/dist/src/server/team-memory-dream-store.js +1 -1
- package/dist/src/server/team-operations.d.ts +18 -2
- package/dist/src/server/team-operations.js +222 -33
- package/dist/src/server/team-recap.d.ts +10 -0
- package/dist/src/server/team-recap.js +73 -0
- package/dist/src/server/terminal-input-profile.js +95 -6
- package/dist/src/server/upload-limits.d.ts +2 -0
- package/dist/src/server/upload-limits.js +2 -0
- package/dist/src/server/workflow-cli-policy.d.ts +7 -2
- package/dist/src/server/workflow-cli-policy.js +15 -3
- package/dist/src/server/workflow-run-store.d.ts +1 -0
- package/dist/src/server/workflow-run-store.js +11 -1
- package/dist/src/server/workflow-runner.d.ts +4 -1
- package/dist/src/server/workflow-runner.js +418 -118
- package/dist/src/server/workflow-script-loader.d.ts +3 -2
- package/dist/src/server/workflow-script-loader.js +161 -0
- package/dist/src/server/workspace-store-contract.d.ts +2 -0
- package/dist/src/server/workspace-store.d.ts +1 -1
- package/dist/src/server/workspace-store.js +40 -30
- package/dist/src/server/workspace-upload-store.d.ts +40 -0
- package/dist/src/server/workspace-upload-store.js +295 -0
- package/dist/src/shared/scenario-presets.d.ts +32 -0
- package/dist/src/shared/scenario-presets.js +69 -0
- package/dist/src/shared/types.d.ts +12 -1
- package/package.json +1 -1
- package/web/dist/assets/AddWorkerDialog-DBLhwb91.js +2 -0
- package/web/dist/assets/AddWorkspaceFlow-cxvhVAsT.js +1 -0
- package/web/dist/assets/FirstRunWizard-DlEPnWWw.js +1 -0
- package/web/dist/assets/{MarketplaceDrawer-BFfGT8hH.js → MarketplaceDrawer-CfSiRi8e.js} +11 -11
- package/web/dist/assets/TaskGraphDrawer-C2JufcPs.js +1 -0
- package/web/dist/assets/WhatsNewDialog-vP7buLos.js +1 -0
- package/web/dist/assets/WorkerModal-CSorwcdP.js +1 -0
- package/web/dist/assets/{WorkflowsDrawer-CiIdHS6_.js → WorkflowsDrawer-BXS3w9Uq.js} +1 -1
- package/web/dist/assets/WorkspaceMemoryDrawer-D71ivohr.js +1 -0
- package/web/dist/assets/{WorkspaceTaskDrawer-CyhhEB1Z.js → WorkspaceTaskDrawer-CGCTSHKa.js} +1 -1
- package/web/dist/assets/index-BcwN8cCw.js +79 -0
- package/web/dist/assets/index-StXTPHls.css +1 -0
- package/web/dist/assets/{search-BtRkkEmS.js → search-BZw4T67h.js} +1 -1
- package/web/dist/assets/{square-terminal-lEeQUWb3.js → square-terminal-B7E57In1.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +1 -1
- package/dist/src/server/env-sync-message.d.ts +0 -9
- package/dist/src/server/env-sync-message.js +0 -29
- package/web/dist/assets/AddWorkerDialog-C86CwNgQ.js +0 -2
- package/web/dist/assets/AddWorkspaceFlow-Bm2Jz34D.js +0 -1
- package/web/dist/assets/FirstRunWizard-XzBoEpA5.js +0 -1
- package/web/dist/assets/TaskGraphDrawer-_uVH_0C1.js +0 -1
- package/web/dist/assets/WhatsNewDialog-DkJHmkMs.js +0 -1
- package/web/dist/assets/WorkerModal-BtMJEOG9.js +0 -1
- package/web/dist/assets/WorkspaceMemoryDrawer-C6sNocl_.js +0 -1
- package/web/dist/assets/index-BAiLYajK.css +0 -1
- package/web/dist/assets/index-K-GG8UwR.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
|
-
|
|
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:
|
|
129
|
+
writeToRun: (targetRunId, text) => {
|
|
130
|
+
restartWrite = postStartWriter(targetRunId, text);
|
|
131
|
+
return restartWrite;
|
|
132
|
+
},
|
|
115
133
|
});
|
|
116
|
-
if (
|
|
117
|
-
|
|
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
|
-
}))
|
|
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
|
|
18
|
-
//
|
|
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.', '
|
|
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>]
|
|
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 = [
|
|
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 = [
|
|
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');
|