@tt-a1i/hive 1.4.4 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.en.md +21 -0
- package/README.md +16 -0
- package/assets/qq-group.jpg +0 -0
- package/dist/bin/team.cmd +1 -0
- package/dist/src/cli/hive-update.d.ts +45 -17
- package/dist/src/cli/hive-update.js +63 -25
- package/dist/src/cli/hive.d.ts +25 -0
- package/dist/src/cli/hive.js +41 -3
- package/dist/src/cli/team.d.ts +1 -0
- package/dist/src/cli/team.js +216 -3
- package/dist/src/server/agent-command-resolver.js +3 -19
- package/dist/src/server/agent-manager-support.d.ts +2 -2
- package/dist/src/server/agent-manager-support.js +98 -24
- package/dist/src/server/agent-run-starter.d.ts +6 -1
- package/dist/src/server/agent-run-starter.js +9 -2
- package/dist/src/server/agent-run-store.d.ts +1 -1
- package/dist/src/server/agent-runtime-close.d.ts +1 -0
- package/dist/src/server/agent-runtime-close.js +25 -1
- package/dist/src/server/agent-runtime-contract.d.ts +12 -1
- package/dist/src/server/agent-runtime-stop-run.d.ts +1 -1
- package/dist/src/server/agent-runtime-stop-run.js +4 -1
- package/dist/src/server/agent-runtime.d.ts +2 -1
- package/dist/src/server/agent-runtime.js +14 -3
- package/dist/src/server/agent-startup-instructions.d.ts +7 -1
- package/dist/src/server/agent-startup-instructions.js +17 -9
- package/dist/src/server/agent-stdin-dispatcher.d.ts +25 -5
- package/dist/src/server/agent-stdin-dispatcher.js +141 -40
- package/dist/src/server/cron-util.d.ts +7 -0
- package/dist/src/server/cron-util.js +19 -0
- package/dist/src/server/dispatch-ledger-store.d.ts +22 -0
- package/dist/src/server/dispatch-ledger-store.js +51 -3
- package/dist/src/server/env-sync-message.js +9 -9
- package/dist/src/server/feature-flags.d.ts +42 -0
- package/dist/src/server/feature-flags.js +24 -0
- package/dist/src/server/fs-pick-folder.js +4 -0
- package/dist/src/server/fs-sandbox.js +36 -7
- package/dist/src/server/hive-team-guidance.d.ts +12 -6
- package/dist/src/server/hive-team-guidance.js +253 -71
- package/dist/src/server/live-run-registry.d.ts +1 -0
- package/dist/src/server/live-run-registry.js +1 -1
- package/dist/src/server/open-target-commands.js +5 -6
- package/dist/src/server/orchestrator-autostart.d.ts +12 -0
- package/dist/src/server/orchestrator-autostart.js +15 -13
- package/dist/src/server/path-canonicalization.d.ts +3 -0
- package/dist/src/server/path-canonicalization.js +29 -0
- package/dist/src/server/platform-path.d.ts +3 -0
- package/dist/src/server/platform-path.js +13 -0
- package/dist/src/server/post-start-input-writer.d.ts +1 -1
- package/dist/src/server/post-start-input-writer.js +110 -13
- package/dist/src/server/preset-launch-support.d.ts +1 -1
- package/dist/src/server/preset-launch-support.js +33 -2
- package/dist/src/server/recovery-summary.d.ts +5 -1
- package/dist/src/server/recovery-summary.js +18 -17
- package/dist/src/server/report-outbox-store.d.ts +36 -0
- package/dist/src/server/report-outbox-store.js +33 -0
- package/dist/src/server/restart-policy-support.d.ts +5 -1
- package/dist/src/server/restart-policy-support.js +9 -1
- package/dist/src/server/restart-policy.d.ts +6 -2
- package/dist/src/server/restart-policy.js +51 -31
- package/dist/src/server/role-template-store.d.ts +1 -0
- package/dist/src/server/role-template-store.js +11 -1
- package/dist/src/server/route-types.d.ts +43 -0
- package/dist/src/server/routes-runtime.js +2 -1
- package/dist/src/server/routes-settings.js +76 -0
- package/dist/src/server/routes-tasks.js +23 -0
- package/dist/src/server/routes-team.js +211 -1
- package/dist/src/server/routes-workflow-schedules.d.ts +2 -0
- package/dist/src/server/routes-workflow-schedules.js +58 -0
- package/dist/src/server/routes-workflows.d.ts +2 -0
- package/dist/src/server/routes-workflows.js +83 -0
- package/dist/src/server/routes-workspaces.js +5 -0
- package/dist/src/server/routes.js +4 -0
- package/dist/src/server/runtime-restart-policy.d.ts +3 -1
- package/dist/src/server/runtime-restart-policy.js +2 -1
- package/dist/src/server/runtime-store-contract.d.ts +125 -0
- package/dist/src/server/runtime-store-contract.js +1 -0
- package/dist/src/server/runtime-store-helpers.d.ts +11 -0
- package/dist/src/server/runtime-store-helpers.js +106 -2
- package/dist/src/server/runtime-store-workflows.d.ts +6 -0
- package/dist/src/server/runtime-store-workflows.js +108 -0
- package/dist/src/server/runtime-store.d.ts +3 -72
- package/dist/src/server/runtime-store.js +71 -4
- package/dist/src/server/session-capture-codex.d.ts +3 -3
- package/dist/src/server/session-capture-codex.js +9 -7
- package/dist/src/server/session-capture-gemini.d.ts +1 -1
- package/dist/src/server/session-capture-gemini.js +6 -3
- package/dist/src/server/settings-store.d.ts +3 -0
- package/dist/src/server/settings-store.js +1 -0
- package/dist/src/server/sqlite-schema-v19.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v19.js +17 -0
- package/dist/src/server/sqlite-schema-v20.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v20.js +20 -0
- package/dist/src/server/sqlite-schema-v21.d.ts +2 -0
- package/dist/src/server/sqlite-schema-v21.js +20 -0
- package/dist/src/server/sqlite-schema.d.ts +1 -1
- package/dist/src/server/sqlite-schema.js +110 -1
- package/dist/src/server/system-message.d.ts +7 -0
- package/dist/src/server/system-message.js +8 -1
- package/dist/src/server/task-deps.d.ts +32 -0
- package/dist/src/server/task-deps.js +40 -0
- package/dist/src/server/tasks-file-watcher.d.ts +12 -1
- package/dist/src/server/tasks-file-watcher.js +128 -23
- package/dist/src/server/tasks-file.d.ts +3 -1
- package/dist/src/server/tasks-file.js +33 -9
- package/dist/src/server/tasks-websocket-server.js +13 -14
- package/dist/src/server/team-authz.d.ts +1 -1
- package/dist/src/server/team-authz.js +10 -1
- package/dist/src/server/team-autostaff.d.ts +16 -0
- package/dist/src/server/team-autostaff.js +16 -0
- package/dist/src/server/team-list-serializer.d.ts +1 -1
- package/dist/src/server/team-list-serializer.js +3 -1
- package/dist/src/server/team-operations.d.ts +21 -1
- package/dist/src/server/team-operations.js +183 -16
- package/dist/src/server/terminal-protocol.js +9 -3
- package/dist/src/server/terminal-stream-hub.js +16 -10
- package/dist/src/server/terminal-ws-server.js +10 -8
- package/dist/src/server/webhook-notifier.d.ts +34 -0
- package/dist/src/server/webhook-notifier.js +47 -0
- package/dist/src/server/websocket-upgrade-safety.d.ts +10 -0
- package/dist/src/server/websocket-upgrade-safety.js +35 -0
- package/dist/src/server/windows-command-line.d.ts +3 -0
- package/dist/src/server/windows-command-line.js +9 -0
- package/dist/src/server/windows-filename.d.ts +2 -0
- package/dist/src/server/windows-filename.js +33 -0
- package/dist/src/server/workflow-cli-policy.d.ts +60 -0
- package/dist/src/server/workflow-cli-policy.js +110 -0
- package/dist/src/server/workflow-dispatch-awaiter.d.ts +12 -0
- package/dist/src/server/workflow-dispatch-awaiter.js +80 -0
- package/dist/src/server/workflow-feature.d.ts +15 -0
- package/dist/src/server/workflow-feature.js +15 -0
- package/dist/src/server/workflow-http-serializers.d.ts +64 -0
- package/dist/src/server/workflow-http-serializers.js +58 -0
- package/dist/src/server/workflow-output-schema.d.ts +18 -0
- package/dist/src/server/workflow-output-schema.js +41 -0
- package/dist/src/server/workflow-run-log-store.d.ts +19 -0
- package/dist/src/server/workflow-run-log-store.js +45 -0
- package/dist/src/server/workflow-run-store.d.ts +50 -0
- package/dist/src/server/workflow-run-store.js +103 -0
- package/dist/src/server/workflow-runner.d.ts +147 -0
- package/dist/src/server/workflow-runner.js +411 -0
- package/dist/src/server/workflow-schedule-create.d.ts +14 -0
- package/dist/src/server/workflow-schedule-create.js +41 -0
- package/dist/src/server/workflow-schedule-store.d.ts +43 -0
- package/dist/src/server/workflow-schedule-store.js +112 -0
- package/dist/src/server/workflow-scheduler.d.ts +36 -0
- package/dist/src/server/workflow-scheduler.js +97 -0
- package/dist/src/server/workflow-script-loader.d.ts +34 -0
- package/dist/src/server/workflow-script-loader.js +106 -0
- package/dist/src/server/workspace-path-validation.js +16 -4
- package/dist/src/server/workspace-shell-runtime.d.ts +5 -0
- package/dist/src/server/workspace-shell-runtime.js +24 -2
- package/dist/src/server/workspace-store-contract.d.ts +4 -1
- package/dist/src/server/workspace-store-hydration.js +23 -7
- package/dist/src/server/workspace-store-mutations.js +2 -5
- package/dist/src/server/workspace-store-support.d.ts +4 -0
- package/dist/src/server/workspace-store-support.js +13 -1
- package/dist/src/server/workspace-store.js +38 -4
- package/dist/src/shared/types.d.ts +16 -1
- package/package.json +4 -2
- package/web/dist/assets/{AddWorkerDialog-DeZhTQLi.js → AddWorkerDialog-CGbaxu0T.js} +2 -2
- package/web/dist/assets/AddWorkspaceDialog-CNgExu6b.js +1 -0
- package/web/dist/assets/{FirstRunWizard-B5wLcat5.js → FirstRunWizard-DxGApUNc.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-BC0eBOEW.js → MarketplaceDrawer-Bk6cpukn.js} +1 -1
- package/web/dist/assets/WhatsNewDialog-CSGzk-2U.js +1 -0
- package/web/dist/assets/WorkerModal-i2F3n3nZ.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-C_Ta_K13.js +1 -0
- package/web/dist/assets/WorkspaceTerminalPanels-VdDxtrQF.js +1 -0
- package/web/dist/assets/index-5zh61jMg.css +1 -0
- package/web/dist/assets/index-CAgGM6nb.js +75 -0
- package/web/dist/assets/path-join-7MR1s7b1.js +1 -0
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +1 -1
- package/web/dist/assets/AddWorkspaceDialog-DDpXNEKf.js +0 -1
- package/web/dist/assets/WorkerModal-BwMHq-Bi.js +0 -1
- package/web/dist/assets/WorkspaceTaskDrawer-CxvT4nqs.js +0 -1
- package/web/dist/assets/WorkspaceTerminalPanels-CvibsPSd.js +0 -1
- package/web/dist/assets/index-BEsTmfrO.css +0 -1
- package/web/dist/assets/index-Ddb7bDN5.js +0 -75
- package/web/dist/assets/path-join-S7qkXQtP.js +0 -1
|
@@ -7,9 +7,26 @@ const MAX_SUBMIT_AFTER_PASTE_DELAY_MS = 1500;
|
|
|
7
7
|
const PASTE_CHARS_PER_DELAY_MS = 4;
|
|
8
8
|
const PASTE_ACK_CHECK_INTERVAL_MS = 50;
|
|
9
9
|
const PASTE_ACK_SETTLE_DELAY_MS = 100;
|
|
10
|
+
const CODEX_PASTE_ACK_SETTLE_DELAY_MS = 250;
|
|
10
11
|
const PASTE_ACK_TIMEOUT_MS = 3000;
|
|
12
|
+
// Codex can take several seconds to render "[Pasted Content ...]" on Windows
|
|
13
|
+
// conpty for large bracketed-paste payloads. Keep Claude's shorter fallback,
|
|
14
|
+
// but give Codex enough time to use the ack-gated path in normal operation.
|
|
15
|
+
const CODEX_PASTE_ACK_TIMEOUT_MS = 10000;
|
|
16
|
+
// Codex only emits "[Pasted Content ...]" for sufficiently large pastes. Short
|
|
17
|
+
// Hive report/status/user-input payloads are usually rendered literally in the
|
|
18
|
+
// input box, so waiting for an acknowledgement there degrades into the 10s
|
|
19
|
+
// timeout before Enter is sent.
|
|
20
|
+
const CODEX_PASTE_ACK_MIN_CHARS = 2000;
|
|
21
|
+
const CODEX_SUBMIT_RETRY_DELAY_MS = 500;
|
|
11
22
|
const COMMANDS_WITH_BRACKETED_PASTE = new Set(['claude', 'codex', 'opencode']);
|
|
23
|
+
const COMMANDS_WAITING_FOR_PASTE_ACK = new Set(['claude', 'codex']);
|
|
24
|
+
const BRACKETED_PASTE_END = '\u001b[201~';
|
|
25
|
+
const PASTE_ACK_PATTERN = /(?:^|[\r\n])\s*(?:[❯›]\s*)?\[(?:Pasted text #\d+[^\]]*|Pasted Content [\d,]+ chars)\]/u;
|
|
26
|
+
const PASTE_ACK_ONLY_DELTA_PATTERN = /^[\s\r\n]*(?:[❯›]\s*)?\[(?:Pasted text #\d+[^\]]*|Pasted Content [\d,]+ chars)\]\s*(?:[❯›]\s*)?$/u;
|
|
12
27
|
export const toBracketedPasteSubmission = (text) => `\u001b[200~${text}\u001b[201~`;
|
|
28
|
+
const toError = (error) => (error instanceof Error ? error : new Error(String(error)));
|
|
29
|
+
const createRunInactiveError = (runId) => new Error(`Run became inactive before input was submitted: ${runId}`);
|
|
13
30
|
const getSubmitAfterPasteDelayMs = (text) => Math.min(MAX_SUBMIT_AFTER_PASTE_DELAY_MS, Math.max(MIN_SUBMIT_AFTER_PASTE_DELAY_MS, Math.ceil(text.length / PASTE_CHARS_PER_DELAY_MS)));
|
|
14
31
|
export const isInteractiveAgentCommand = (command) => {
|
|
15
32
|
const brand = normalizeExecutableToken(command);
|
|
@@ -22,8 +39,24 @@ export const hasInteractivePromptReady = (output, command = '') => {
|
|
|
22
39
|
return (/(?:^|[\r\n])\s*[❯›]\s*/u.test(output) ||
|
|
23
40
|
(commandName === 'gemini' && hasGeminiPromptReady(output)));
|
|
24
41
|
};
|
|
25
|
-
export const hasBracketedPasteAcknowledgement = (output, baselineLength) =>
|
|
26
|
-
const
|
|
42
|
+
export const hasBracketedPasteAcknowledgement = (output, baselineLength) => {
|
|
43
|
+
const outputAfterPaste = output.slice(baselineLength);
|
|
44
|
+
const pasteEndIndex = outputAfterPaste.lastIndexOf(BRACKETED_PASTE_END);
|
|
45
|
+
const acknowledgementOutput = pasteEndIndex === -1
|
|
46
|
+
? outputAfterPaste
|
|
47
|
+
: outputAfterPaste.slice(pasteEndIndex + BRACKETED_PASTE_END.length);
|
|
48
|
+
return PASTE_ACK_PATTERN.test(acknowledgementOutput);
|
|
49
|
+
};
|
|
50
|
+
const onlyPasteAcknowledgementWasAppended = (before, after) => after.startsWith(before) && PASTE_ACK_ONLY_DELTA_PATTERN.test(after.slice(before.length));
|
|
51
|
+
const shouldWaitForPasteAck = (command, text) => {
|
|
52
|
+
const commandName = getCommandName(command);
|
|
53
|
+
if (commandName === 'codex')
|
|
54
|
+
return text.length >= CODEX_PASTE_ACK_MIN_CHARS;
|
|
55
|
+
return COMMANDS_WAITING_FOR_PASTE_ACK.has(commandName);
|
|
56
|
+
};
|
|
57
|
+
const retriesSubmit = (command) => getCommandName(command) === 'codex';
|
|
58
|
+
const getPasteAckSettleDelayMs = (command) => getCommandName(command) === 'codex' ? CODEX_PASTE_ACK_SETTLE_DELAY_MS : PASTE_ACK_SETTLE_DELAY_MS;
|
|
59
|
+
const getPasteAckTimeoutMs = (command) => getCommandName(command) === 'codex' ? CODEX_PASTE_ACK_TIMEOUT_MS : PASTE_ACK_TIMEOUT_MS;
|
|
27
60
|
const usesBracketedPaste = (command) => COMMANDS_WITH_BRACKETED_PASTE.has(getCommandName(command));
|
|
28
61
|
const canTimeoutBeforePromptReady = (command) => getCommandName(command) !== 'gemini';
|
|
29
62
|
const isWritableRunStatus = (status) => status === undefined || status === 'starting' || status === 'running';
|
|
@@ -40,7 +73,7 @@ const writeIfRunWritable = (agentManager, runId, text) => {
|
|
|
40
73
|
agentManager.writeInput(runId, text);
|
|
41
74
|
return true;
|
|
42
75
|
};
|
|
43
|
-
const submitPastedInteractiveInput = (agentManager, runId, text, baselineLength, waitForPasteAck) => {
|
|
76
|
+
const submitPastedInteractiveInput = (agentManager, runId, text, baselineLength, waitForPasteAck, pasteAckSettleDelayMs, pasteAckTimeoutMs, retrySubmit, onDone, onError) => {
|
|
44
77
|
const pastedAt = Date.now();
|
|
45
78
|
const minDelay = getSubmitAfterPasteDelayMs(text);
|
|
46
79
|
let acknowledgedAt = null;
|
|
@@ -53,30 +86,73 @@ const submitPastedInteractiveInput = (agentManager, runId, text, baselineLength,
|
|
|
53
86
|
return null;
|
|
54
87
|
}
|
|
55
88
|
};
|
|
56
|
-
const
|
|
89
|
+
const getRunOutputState = () => {
|
|
57
90
|
try {
|
|
58
|
-
|
|
91
|
+
const run = agentManager.getRun(runId);
|
|
92
|
+
return { output: run.output, writable: isWritableRunStatus(run.status) };
|
|
59
93
|
}
|
|
60
94
|
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const submit = (retryAfterSubmit) => {
|
|
99
|
+
try {
|
|
100
|
+
const outputBeforeSubmit = retryAfterSubmit ? getWritableOutput() : null;
|
|
101
|
+
if (!writeIfRunWritable(agentManager, runId, '\r')) {
|
|
102
|
+
onError(createRunInactiveError(runId));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!retryAfterSubmit) {
|
|
106
|
+
onDone();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
setTimeout(() => {
|
|
110
|
+
try {
|
|
111
|
+
const runAfterSubmit = getRunOutputState();
|
|
112
|
+
if (runAfterSubmit === null) {
|
|
113
|
+
onDone();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (outputBeforeSubmit !== null &&
|
|
117
|
+
hasBracketedPasteAcknowledgement(runAfterSubmit.output, baselineLength) &&
|
|
118
|
+
onlyPasteAcknowledgementWasAppended(outputBeforeSubmit, runAfterSubmit.output)) {
|
|
119
|
+
if (!runAfterSubmit.writable || !writeIfRunWritable(agentManager, runId, '\r')) {
|
|
120
|
+
onError(createRunInactiveError(runId));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
onError(toError(error));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
onDone();
|
|
130
|
+
}, CODEX_SUBMIT_RETRY_DELAY_MS);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
61
133
|
// The PTY may have exited between paste and submit.
|
|
134
|
+
onError(toError(error));
|
|
62
135
|
}
|
|
63
136
|
};
|
|
64
137
|
const trySubmit = () => {
|
|
65
138
|
if (!waitForPasteAck) {
|
|
66
|
-
submit();
|
|
139
|
+
submit(false);
|
|
67
140
|
return;
|
|
68
141
|
}
|
|
69
142
|
const output = getWritableOutput();
|
|
70
143
|
if (output === null) {
|
|
144
|
+
onError(createRunInactiveError(runId));
|
|
71
145
|
return;
|
|
72
146
|
}
|
|
73
147
|
if (acknowledgedAt === null && hasBracketedPasteAcknowledgement(output, baselineLength)) {
|
|
74
148
|
acknowledgedAt = Date.now();
|
|
75
149
|
}
|
|
76
150
|
const elapsed = Date.now() - pastedAt;
|
|
77
|
-
const ackSettled = acknowledgedAt !== null && Date.now() - acknowledgedAt >=
|
|
78
|
-
|
|
79
|
-
|
|
151
|
+
const ackSettled = acknowledgedAt !== null && Date.now() - acknowledgedAt >= pasteAckSettleDelayMs;
|
|
152
|
+
const submitAfterAck = ackSettled && elapsed >= minDelay;
|
|
153
|
+
const submitAfterTimeout = elapsed >= pasteAckTimeoutMs;
|
|
154
|
+
if (submitAfterAck || submitAfterTimeout) {
|
|
155
|
+
submit(retrySubmit && submitAfterTimeout);
|
|
80
156
|
return;
|
|
81
157
|
}
|
|
82
158
|
setTimeout(trySubmit, PASTE_ACK_CHECK_INTERVAL_MS);
|
|
@@ -86,10 +162,21 @@ const submitPastedInteractiveInput = (agentManager, runId, text, baselineLength,
|
|
|
86
162
|
export const createPostStartInputWriter = (agentManager, command) => {
|
|
87
163
|
if (!isInteractiveAgentCommand(command)) {
|
|
88
164
|
return (runId, text) => {
|
|
89
|
-
|
|
165
|
+
// Synchronous write; an EPIPE/inactive failure still throws synchronously
|
|
166
|
+
// (before the promise is returned), preserving the dispatcher's contract.
|
|
167
|
+
if (!writeIfRunWritable(agentManager, runId, `${text}\n`)) {
|
|
168
|
+
throw createRunInactiveError(runId);
|
|
169
|
+
}
|
|
170
|
+
return Promise.resolve();
|
|
90
171
|
};
|
|
91
172
|
}
|
|
92
173
|
return (runId, text) => {
|
|
174
|
+
let resolveDone;
|
|
175
|
+
let rejectDone;
|
|
176
|
+
const rejectableDone = new Promise((resolve, reject) => {
|
|
177
|
+
resolveDone = resolve;
|
|
178
|
+
rejectDone = reject;
|
|
179
|
+
});
|
|
93
180
|
const startedAt = Date.now();
|
|
94
181
|
let isInitialAttempt = true;
|
|
95
182
|
const tryWrite = () => {
|
|
@@ -99,33 +186,43 @@ export const createPostStartInputWriter = (agentManager, command) => {
|
|
|
99
186
|
output = isWritableRunStatus(run.status) ? run.output : null;
|
|
100
187
|
}
|
|
101
188
|
catch {
|
|
189
|
+
rejectDone(createRunInactiveError(runId));
|
|
102
190
|
return;
|
|
103
191
|
}
|
|
104
|
-
if (output === null)
|
|
192
|
+
if (output === null) {
|
|
193
|
+
rejectDone(createRunInactiveError(runId));
|
|
105
194
|
return;
|
|
195
|
+
}
|
|
106
196
|
if (hasInteractivePromptReady(output, command) ||
|
|
107
197
|
(canTimeoutBeforePromptReady(command) && Date.now() - startedAt >= READY_TIMEOUT_MS)) {
|
|
108
198
|
const baselineLength = output.length;
|
|
109
199
|
const input = usesBracketedPaste(command) ? toBracketedPasteSubmission(text) : text;
|
|
110
200
|
try {
|
|
111
|
-
if (!writeIfRunWritable(agentManager, runId, input))
|
|
201
|
+
if (!writeIfRunWritable(agentManager, runId, input)) {
|
|
202
|
+
rejectDone(createRunInactiveError(runId));
|
|
112
203
|
return;
|
|
204
|
+
}
|
|
113
205
|
}
|
|
114
206
|
catch (error) {
|
|
115
207
|
if (isInitialAttempt)
|
|
116
208
|
throw error;
|
|
209
|
+
rejectDone(toError(error));
|
|
117
210
|
return;
|
|
118
211
|
}
|
|
119
|
-
submitPastedInteractiveInput(agentManager, runId, text, baselineLength,
|
|
212
|
+
submitPastedInteractiveInput(agentManager, runId, text, baselineLength, shouldWaitForPasteAck(command, text), getPasteAckSettleDelayMs(command), getPasteAckTimeoutMs(command), retriesSubmit(command), resolveDone, rejectDone);
|
|
120
213
|
return;
|
|
121
214
|
}
|
|
122
215
|
setTimeout(tryWrite, READY_CHECK_INTERVAL_MS);
|
|
123
216
|
};
|
|
124
217
|
try {
|
|
218
|
+
// Called outside the Promise executor so an initial-attempt throw
|
|
219
|
+
// propagates synchronously to the caller (preserving the dispatcher's
|
|
220
|
+
// synchronous-throw contract) instead of becoming a rejection.
|
|
125
221
|
tryWrite();
|
|
126
222
|
}
|
|
127
223
|
finally {
|
|
128
224
|
isInitialAttempt = false;
|
|
129
225
|
}
|
|
226
|
+
return rejectableDone;
|
|
130
227
|
};
|
|
131
228
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentLaunchConfigInput } from './agent-run-store.js';
|
|
2
2
|
import type { CommandPresetRecord } from './command-preset-store.js';
|
|
3
3
|
import type { SessionCaptureSnapshot } from './session-capture.js';
|
|
4
|
-
type BoundPreset = Pick<CommandPresetRecord, 'resumeArgsTemplate' | 'sessionIdCapture' | 'yoloArgsTemplate'
|
|
4
|
+
type BoundPreset = Pick<CommandPresetRecord, 'resumeArgsTemplate' | 'sessionIdCapture' | 'yoloArgsTemplate'> & Partial<Pick<CommandPresetRecord, 'command' | 'id'>>;
|
|
5
5
|
export declare const withPresetResumeArgs: (config: AgentLaunchConfigInput, preset: BoundPreset | null | undefined, lastSessionId: string | undefined, cwd?: string, discriminator?: SessionCaptureSnapshot["discriminator"], onInvalidSessionId?: (sessionId: string) => void) => AgentLaunchConfigInput;
|
|
6
6
|
export {};
|
|
@@ -3,6 +3,36 @@ const appendUniqueArgs = (prefix, args) => {
|
|
|
3
3
|
const seen = new Set(prefix);
|
|
4
4
|
return prefix.concat(args.filter((arg) => !seen.has(arg)));
|
|
5
5
|
};
|
|
6
|
+
const unquoteExecutable = (command) => command.trim().replace(/^"(.+)"$/, '$1');
|
|
7
|
+
const isNodeExecutable = (command) => {
|
|
8
|
+
if (!command)
|
|
9
|
+
return false;
|
|
10
|
+
return /(?:^|[\\/])node(?:\.exe)?$/iu.test(unquoteExecutable(command));
|
|
11
|
+
};
|
|
12
|
+
const isCodexNpmEntrypoint = (arg) => {
|
|
13
|
+
if (!arg)
|
|
14
|
+
return false;
|
|
15
|
+
const normalized = arg.replace(/\\/gu, '/');
|
|
16
|
+
return /(?:^|\/)@openai\/codex\/bin\/codex\.js$/iu.test(normalized);
|
|
17
|
+
};
|
|
18
|
+
const isCodexPreset = (preset) => preset?.id === 'codex' ||
|
|
19
|
+
preset?.command === 'codex' ||
|
|
20
|
+
preset?.sessionIdCapture?.source === 'codex_session_jsonl_dir';
|
|
21
|
+
const normalizeCodexNodeEntrypoint = (config, preset) => {
|
|
22
|
+
if (!isCodexPreset(preset) || !isNodeExecutable(config.command))
|
|
23
|
+
return config;
|
|
24
|
+
const args = config.args ?? [];
|
|
25
|
+
if (!isCodexNpmEntrypoint(args[0]))
|
|
26
|
+
return config;
|
|
27
|
+
return {
|
|
28
|
+
...config,
|
|
29
|
+
args: args.slice(1),
|
|
30
|
+
command: preset?.command ?? 'codex',
|
|
31
|
+
interactiveCommand: isNodeExecutable(config.interactiveCommand)
|
|
32
|
+
? 'codex'
|
|
33
|
+
: (config.interactiveCommand ?? 'codex'),
|
|
34
|
+
};
|
|
35
|
+
};
|
|
6
36
|
const getEffectiveCapture = (config, preset) => config.sessionIdCapture ?? preset?.sessionIdCapture ?? null;
|
|
7
37
|
const getEffectiveResumeTemplate = (config, preset) => config.resumeArgsTemplate ?? preset?.resumeArgsTemplate ?? null;
|
|
8
38
|
const withPresetYoloArgs = (config, preset) => {
|
|
@@ -35,7 +65,8 @@ const supportsPresetResume = (capture) => capture?.source === 'claude_project_js
|
|
|
35
65
|
capture?.source === 'gemini_session_json_dir' ||
|
|
36
66
|
capture?.source === 'opencode_session_db';
|
|
37
67
|
export const withPresetResumeArgs = (config, preset, lastSessionId, cwd, discriminator, onInvalidSessionId) => {
|
|
38
|
-
|
|
68
|
+
const launchConfig = normalizeCodexNodeEntrypoint(config, preset);
|
|
69
|
+
let nextConfig = withPresetYoloArgs(launchConfig, preset);
|
|
39
70
|
const sessionIdCapture = getEffectiveCapture(nextConfig, preset);
|
|
40
71
|
if (sessionIdCapture && sessionIdCapture !== nextConfig.sessionIdCapture) {
|
|
41
72
|
nextConfig = { ...nextConfig, sessionIdCapture };
|
|
@@ -52,7 +83,7 @@ export const withPresetResumeArgs = (config, preset, lastSessionId, cwd, discrim
|
|
|
52
83
|
onInvalidSessionId?.(lastSessionId);
|
|
53
84
|
return nextConfig;
|
|
54
85
|
}
|
|
55
|
-
const args =
|
|
86
|
+
const args = launchConfig.args ?? [];
|
|
56
87
|
if (hasResumeArgs(args))
|
|
57
88
|
return nextConfig;
|
|
58
89
|
const yoloArgs = getPresetYoloArgs(preset);
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { AgentSummary, WorkspaceSummary } from '../shared/types.js';
|
|
2
|
+
import { type FeatureFlags } from './feature-flags.js';
|
|
2
3
|
import type { RecoveryMessage } from './message-log-store.js';
|
|
3
|
-
export declare const buildRecoverySummary: ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, }: {
|
|
4
|
+
export declare const buildRecoverySummary: ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, flags, }: {
|
|
4
5
|
agent: AgentSummary;
|
|
5
6
|
allTaskMessages?: RecoveryMessage[];
|
|
6
7
|
messages: RecoveryMessage[];
|
|
7
8
|
tasksContent: string;
|
|
8
9
|
workers: AgentSummary[];
|
|
9
10
|
workspace: WorkspaceSummary;
|
|
11
|
+
/** Live experimental flags — keep the recovered handover prompt consistent
|
|
12
|
+
* with what a fresh startup would inject (workflow + team-sizing rules). */
|
|
13
|
+
flags?: FeatureFlags;
|
|
10
14
|
}) => string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
|
|
1
2
|
import { getHiveTeamRules } from './hive-team-guidance.js';
|
|
2
3
|
import { wrapSystemMessage } from './system-message.js';
|
|
3
4
|
import { TASKS_RELATIVE_PATH } from './tasks-file.js';
|
|
@@ -6,7 +7,7 @@ const formatUserInputs = (messages) => {
|
|
|
6
7
|
const userInputs = messages.filter((message) => message.type === 'user_input');
|
|
7
8
|
return userInputs.length > 0
|
|
8
9
|
? userInputs.slice(-5).map((message) => `- user: ${message.text}`)
|
|
9
|
-
: ['-
|
|
10
|
+
: ['- (no new user_input in the last hour)'];
|
|
10
11
|
};
|
|
11
12
|
const formatTaskEvents = (messages, agent) => {
|
|
12
13
|
const taskEvents = messages.filter((message) => {
|
|
@@ -28,7 +29,7 @@ const formatTaskEvents = (messages, agent) => {
|
|
|
28
29
|
const status = message.status ? ` [${message.status}]` : '';
|
|
29
30
|
return `- report <- ${message.from}${status}: ${message.text}`;
|
|
30
31
|
})
|
|
31
|
-
: ['-
|
|
32
|
+
: ['- (no recent task events)'];
|
|
32
33
|
};
|
|
33
34
|
const getOpenTaskTargets = (agent, workers) => agent.role === 'orchestrator' ? workers : [agent];
|
|
34
35
|
const formatOpenTasks = (messages, agent, workers) => {
|
|
@@ -53,38 +54,38 @@ const formatOpenTasks = (messages, agent, workers) => {
|
|
|
53
54
|
lines.push(`- ${target.name}: ${task.text}`);
|
|
54
55
|
}
|
|
55
56
|
if (target.pendingTaskCount > queue.length) {
|
|
56
|
-
lines.push(`- ${target.name}: ${target.pendingTaskCount - queue.length}
|
|
57
|
+
lines.push(`- ${target.name}: ${target.pendingTaskCount - queue.length} pending with no recoverable detail`);
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
|
-
return lines.length > 0 ? lines : ['-
|
|
60
|
+
return lines.length > 0 ? lines : ['- (no open tasks right now)'];
|
|
60
61
|
};
|
|
61
62
|
const formatWorkers = (workers) => {
|
|
62
63
|
if (workers.length === 0)
|
|
63
|
-
return ['-
|
|
64
|
+
return ['- (no other workers)'];
|
|
64
65
|
return workers.map((worker) => `- ${worker.name} (${worker.role}, ${worker.status}, pending_task_count: ${worker.pendingTaskCount})`);
|
|
65
66
|
};
|
|
66
|
-
const getTaskSectionTitle = (agent) => agent.role === 'orchestrator' ? '##
|
|
67
|
-
export const buildRecoverySummary = ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, }) => wrapSystemMessage([
|
|
68
|
-
|
|
69
|
-
'
|
|
67
|
+
const getTaskSectionTitle = (agent) => agent.role === 'orchestrator' ? '## Tasks you dispatched' : '## Tasks recently sent to you';
|
|
68
|
+
export const buildRecoverySummary = ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, flags = FEATURE_FLAGS_ALL_OFF, }) => wrapSystemMessage([
|
|
69
|
+
`You are ${agent.name} (${agent.role}) in workspace ${workspace.name}.`,
|
|
70
|
+
'Hive just restarted you and could not recover via native session resume. Here is the handover context.',
|
|
70
71
|
'',
|
|
71
|
-
'##
|
|
72
|
+
'## Conversation with the user in the last hour',
|
|
72
73
|
...formatUserInputs(messages),
|
|
73
74
|
'',
|
|
74
75
|
getTaskSectionTitle(agent),
|
|
75
76
|
...formatTaskEvents(messages, agent),
|
|
76
77
|
'',
|
|
77
|
-
'##
|
|
78
|
+
'## Open tasks',
|
|
78
79
|
...formatOpenTasks(allTaskMessages ?? messages, agent, workers),
|
|
79
80
|
'',
|
|
80
|
-
`##
|
|
81
|
-
tasksContent.slice(0, TASKS_HEAD_LIMIT) || '(
|
|
81
|
+
`## Current ${TASKS_RELATIVE_PATH}`,
|
|
82
|
+
tasksContent.slice(0, TASKS_HEAD_LIMIT) || '(empty)',
|
|
82
83
|
'',
|
|
83
|
-
'##
|
|
84
|
+
'## Active workers',
|
|
84
85
|
...formatWorkers(workers),
|
|
85
86
|
'',
|
|
86
|
-
agent.role === 'orchestrator' ? '## Hive worker
|
|
87
|
-
...getHiveTeamRules(agent),
|
|
87
|
+
agent.role === 'orchestrator' ? '## Hive worker dispatch rules' : '## Hive worker boundaries',
|
|
88
|
+
...getHiveTeamRules(agent, flags),
|
|
88
89
|
'',
|
|
89
|
-
'
|
|
90
|
+
'Continue from here. If unsure, ask the user.',
|
|
90
91
|
].join('\n'));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Database } from 'better-sqlite3';
|
|
2
|
+
/**
|
|
3
|
+
* Durable redelivery queue for worker reports the orchestrator could not
|
|
4
|
+
* receive. `team report` persists the dispatch as reported and forwards the
|
|
5
|
+
* report into the orchestrator's stdin best-effort; if that write fails (the
|
|
6
|
+
* PTY exited / is mid-restart, or the orchestrator was down at report time)
|
|
7
|
+
* the report would otherwise be lost — the ledger says "reported" yet the
|
|
8
|
+
* orchestrator waits forever, indistinguishable from a hang.
|
|
9
|
+
*
|
|
10
|
+
* Entries are keyed by dispatch id (UNIQUE) so a report enqueues at most once,
|
|
11
|
+
* and drain marks them delivered only after the orchestrator PTY write
|
|
12
|
+
* actually resolves, so a failed redelivery stays pending for the next drain.
|
|
13
|
+
*/
|
|
14
|
+
export interface ReportOutboxEntry {
|
|
15
|
+
id: number;
|
|
16
|
+
workspaceId: string;
|
|
17
|
+
targetAgentId: string;
|
|
18
|
+
dispatchId: string;
|
|
19
|
+
payload: string;
|
|
20
|
+
createdAt: number;
|
|
21
|
+
deliveredAt: number | null;
|
|
22
|
+
}
|
|
23
|
+
interface EnqueueInput {
|
|
24
|
+
workspaceId: string;
|
|
25
|
+
targetAgentId: string;
|
|
26
|
+
dispatchId: string;
|
|
27
|
+
payload: string;
|
|
28
|
+
}
|
|
29
|
+
export declare const createReportOutboxStore: (db: Database) => {
|
|
30
|
+
enqueue: (input: EnqueueInput) => void;
|
|
31
|
+
listPending: (workspaceId: string, targetAgentId: string) => ReportOutboxEntry[];
|
|
32
|
+
markDelivered: (id: number) => void;
|
|
33
|
+
pendingCount: (workspaceId: string, targetAgentId: string) => number;
|
|
34
|
+
};
|
|
35
|
+
export type ReportOutboxStore = ReturnType<typeof createReportOutboxStore>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const rowToEntry = (row) => ({
|
|
2
|
+
id: row.id,
|
|
3
|
+
workspaceId: row.workspace_id,
|
|
4
|
+
targetAgentId: row.target_agent_id,
|
|
5
|
+
dispatchId: row.dispatch_id,
|
|
6
|
+
payload: row.payload,
|
|
7
|
+
createdAt: row.created_at,
|
|
8
|
+
deliveredAt: row.delivered_at,
|
|
9
|
+
});
|
|
10
|
+
export const createReportOutboxStore = (db) => {
|
|
11
|
+
// INSERT OR IGNORE on the UNIQUE dispatch_id: a dispatch reports once, so a
|
|
12
|
+
// second enqueue for the same dispatch (e.g. a retry path) is a no-op rather
|
|
13
|
+
// than a duplicate the orchestrator would see twice.
|
|
14
|
+
const enqueue = (input) => {
|
|
15
|
+
db.prepare(`INSERT OR IGNORE INTO report_outbox
|
|
16
|
+
(workspace_id, target_agent_id, dispatch_id, payload, created_at)
|
|
17
|
+
VALUES (?, ?, ?, ?, ?)`).run(input.workspaceId, input.targetAgentId, input.dispatchId, input.payload, Date.now());
|
|
18
|
+
};
|
|
19
|
+
const listPending = (workspaceId, targetAgentId) => db
|
|
20
|
+
.prepare(`SELECT id, workspace_id, target_agent_id, dispatch_id, payload, created_at, delivered_at
|
|
21
|
+
FROM report_outbox
|
|
22
|
+
WHERE workspace_id = ? AND target_agent_id = ? AND delivered_at IS NULL
|
|
23
|
+
ORDER BY created_at ASC, id ASC`)
|
|
24
|
+
.all(workspaceId, targetAgentId).map(rowToEntry);
|
|
25
|
+
const markDelivered = (id) => {
|
|
26
|
+
db.prepare(`UPDATE report_outbox SET delivered_at = ? WHERE id = ? AND delivered_at IS NULL`).run(Date.now(), id);
|
|
27
|
+
};
|
|
28
|
+
const pendingCount = (workspaceId, targetAgentId) => db
|
|
29
|
+
.prepare(`SELECT COUNT(*) AS n FROM report_outbox
|
|
30
|
+
WHERE workspace_id = ? AND target_agent_id = ? AND delivered_at IS NULL`)
|
|
31
|
+
.get(workspaceId, targetAgentId).n;
|
|
32
|
+
return { enqueue, listPending, markDelivered, pendingCount };
|
|
33
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentSummary, WorkspaceSummary } from '../shared/types.js';
|
|
2
2
|
import type { PersistedAgentRun } from './agent-run-store.js';
|
|
3
|
+
import type { FeatureFlags } from './feature-flags.js';
|
|
3
4
|
import type { MessageLogHandle, MessageLogRecord, RecoveryMessage } from './message-log-store.js';
|
|
4
5
|
export interface RestartPolicyInput {
|
|
5
6
|
deleteMessage: (handle: MessageLogHandle) => void;
|
|
@@ -11,6 +12,9 @@ export interface RestartPolicyInput {
|
|
|
11
12
|
listAgentRuns: (agentId: string) => PersistedAgentRun[];
|
|
12
13
|
listMessagesForRecovery: (workspaceId: string, sinceMs: number) => RecoveryMessage[];
|
|
13
14
|
readTasks: (workspacePath: string) => string;
|
|
15
|
+
/** Resolves the live experimental flags, threaded into the recovery handover
|
|
16
|
+
* prompt so it matches a fresh startup. Optional; omitted → all off. */
|
|
17
|
+
getFlags?: () => FeatureFlags;
|
|
14
18
|
}
|
|
15
19
|
export declare const findPreviousRun: (runs: PersistedAgentRun[], currentRunId: string) => PersistedAgentRun | undefined;
|
|
16
20
|
export declare const writeSystemMessage: ({ deleteMessage, insertMessage, record, runId, text, writeToRun, }: {
|
|
@@ -19,5 +23,5 @@ export declare const writeSystemMessage: ({ deleteMessage, insertMessage, record
|
|
|
19
23
|
record: MessageLogRecord;
|
|
20
24
|
runId: string;
|
|
21
25
|
text: string;
|
|
22
|
-
writeToRun: (runId: string, text: string) => void
|
|
26
|
+
writeToRun: (runId: string, text: string) => Promise<void>;
|
|
23
27
|
}) => void;
|
|
@@ -2,7 +2,15 @@ export const findPreviousRun = (runs, currentRunId) => runs.find((run) => run.ru
|
|
|
2
2
|
export const writeSystemMessage = ({ deleteMessage, insertMessage, record, runId, text, writeToRun, }) => {
|
|
3
3
|
const handle = insertMessage(record);
|
|
4
4
|
try {
|
|
5
|
-
writeToRun(runId, text)
|
|
5
|
+
void writeToRun(runId, text).catch(() => {
|
|
6
|
+
try {
|
|
7
|
+
deleteMessage(handle);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// The runtime may already be closing; the failed post-start write is
|
|
11
|
+
// non-critical once the run is gone.
|
|
12
|
+
}
|
|
13
|
+
});
|
|
6
14
|
}
|
|
7
15
|
catch (error) {
|
|
8
16
|
deleteMessage(handle);
|
|
@@ -7,8 +7,12 @@ export interface RestartPolicy {
|
|
|
7
7
|
runId: string;
|
|
8
8
|
startConfig: AgentLaunchConfigInput;
|
|
9
9
|
workspace: WorkspaceSummary;
|
|
10
|
-
writeToRun: (runId: string, text: string) => void
|
|
10
|
+
writeToRun: (runId: string, text: string) => Promise<void>;
|
|
11
11
|
}) => boolean;
|
|
12
|
+
/** Record that `runId` was killed at the user's request (Stop button), so a
|
|
13
|
+
* subsequent restart does not inject the crash-recovery handover for a run
|
|
14
|
+
* the user deliberately ended. */
|
|
15
|
+
markUserStopped: (runId: string) => void;
|
|
12
16
|
}
|
|
13
17
|
export declare const createNoopRestartPolicy: () => RestartPolicy;
|
|
14
|
-
export declare const createRestartPolicy: ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, }: RestartPolicyInput) => RestartPolicy;
|
|
18
|
+
export declare const createRestartPolicy: ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, getFlags, }: RestartPolicyInput) => RestartPolicy;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FEATURE_FLAGS_ALL_OFF } from './feature-flags.js';
|
|
1
2
|
import { buildRecoverySummary } from './recovery-summary.js';
|
|
2
3
|
import { findPreviousRun, writeSystemMessage, } from './restart-policy-support.js';
|
|
3
4
|
import { createSystemRecoverySummaryMessage } from './runtime-message-builders.js';
|
|
@@ -6,36 +7,55 @@ export const createNoopRestartPolicy = () => ({
|
|
|
6
7
|
injectPostStartMessage() {
|
|
7
8
|
return false;
|
|
8
9
|
},
|
|
10
|
+
markUserStopped() { },
|
|
9
11
|
});
|
|
10
|
-
export const createRestartPolicy = ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, }) =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
export const createRestartPolicy = ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, getFlags, }) => {
|
|
13
|
+
// Runs the user killed via the Stop button. A deliberate stop is otherwise
|
|
14
|
+
// byte-identical to a crash (both end status 'error'), so without this a
|
|
15
|
+
// stop+Restart would inject the "could not recover" handover with stale open
|
|
16
|
+
// tasks the user may have meant to abandon. In-process only: after a runtime
|
|
17
|
+
// restart this is empty, which is correct — that case IS a recovery.
|
|
18
|
+
const userStoppedRuns = new Set();
|
|
19
|
+
return {
|
|
20
|
+
markUserStopped(runId) {
|
|
21
|
+
userStoppedRuns.add(runId);
|
|
22
|
+
},
|
|
23
|
+
injectPostStartMessage({ agentId, runId, startConfig, workspace, writeToRun }) {
|
|
24
|
+
const previousRun = findPreviousRun(listAgentRuns(agentId), runId);
|
|
25
|
+
if (!previousRun)
|
|
26
|
+
return false;
|
|
27
|
+
// Consume the marker whether or not we end up skipping, so it never
|
|
28
|
+
// lingers to suppress a later genuine crash on the same run id.
|
|
29
|
+
const wasUserStopped = userStoppedRuns.delete(previousRun.runId);
|
|
30
|
+
const snapshot = getWorkspaceSnapshot(workspace.id);
|
|
31
|
+
const agent = snapshot.agents.find((item) => item.id === agentId);
|
|
32
|
+
if (!agent)
|
|
33
|
+
return false;
|
|
34
|
+
const workers = snapshot.agents.filter((item) => item.role !== 'orchestrator' && item.id !== agentId);
|
|
35
|
+
const tasksContent = readTasks(snapshot.summary.path);
|
|
36
|
+
if (startConfig.resumedSessionId)
|
|
37
|
+
return true;
|
|
38
|
+
// Deliberate stop + restart: start fresh, no crash handover.
|
|
39
|
+
if (wasUserStopped)
|
|
40
|
+
return false;
|
|
41
|
+
const text = buildRecoverySummary({
|
|
42
|
+
agent,
|
|
43
|
+
allTaskMessages: listMessagesForRecovery(workspace.id, 0),
|
|
44
|
+
messages: listMessagesForRecovery(workspace.id, Date.now() - RECOVERY_WINDOW_MS),
|
|
45
|
+
tasksContent,
|
|
46
|
+
workers,
|
|
47
|
+
workspace,
|
|
48
|
+
flags: getFlags?.() ?? FEATURE_FLAGS_ALL_OFF,
|
|
49
|
+
});
|
|
50
|
+
writeSystemMessage({
|
|
51
|
+
deleteMessage,
|
|
52
|
+
insertMessage,
|
|
53
|
+
record: createSystemRecoverySummaryMessage(workspace.id, agentId, text),
|
|
54
|
+
runId,
|
|
55
|
+
text,
|
|
56
|
+
writeToRun,
|
|
57
|
+
});
|
|
22
58
|
return true;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
messages: listMessagesForRecovery(workspace.id, Date.now() - RECOVERY_WINDOW_MS),
|
|
27
|
-
tasksContent,
|
|
28
|
-
workers,
|
|
29
|
-
workspace,
|
|
30
|
-
});
|
|
31
|
-
writeSystemMessage({
|
|
32
|
-
deleteMessage,
|
|
33
|
-
insertMessage,
|
|
34
|
-
record: createSystemRecoverySummaryMessage(workspace.id, agentId, text),
|
|
35
|
-
runId,
|
|
36
|
-
text,
|
|
37
|
-
writeToRun,
|
|
38
|
-
});
|
|
39
|
-
return true;
|
|
40
|
-
},
|
|
41
|
-
});
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
};
|
|
@@ -34,6 +34,16 @@ export const createRoleTemplateStore = (db) => {
|
|
|
34
34
|
.all()
|
|
35
35
|
.map((row) => toRecord(row));
|
|
36
36
|
};
|
|
37
|
+
/* TIER 2 #4 — case-insensitive lookup by template name. The workflow
|
|
38
|
+
runner uses this to resolve `agent('...', { agentType: 'security-reviewer' })`
|
|
39
|
+
against the workspace's custom role library so workflows can target
|
|
40
|
+
curated roles instead of always re-rolling fresh claude defaults. */
|
|
41
|
+
const findByName = (name) => {
|
|
42
|
+
const needle = name.trim().toLowerCase();
|
|
43
|
+
if (!needle)
|
|
44
|
+
return undefined;
|
|
45
|
+
return list().find((template) => template.name.trim().toLowerCase() === needle);
|
|
46
|
+
};
|
|
37
47
|
const create = (input) => {
|
|
38
48
|
const record = { id: randomUUID(), ...input, isBuiltin: false };
|
|
39
49
|
const now = Date.now();
|
|
@@ -62,5 +72,5 @@ export const createRoleTemplateStore = (db) => {
|
|
|
62
72
|
throw new ConflictError(`Builtin role template is read-only: ${id}`);
|
|
63
73
|
db.prepare('DELETE FROM role_templates WHERE id = ?').run(id);
|
|
64
74
|
};
|
|
65
|
-
return { create, list, remove, update };
|
|
75
|
+
return { create, list, remove, update, findByName };
|
|
66
76
|
};
|