@tt-a1i/hive 1.4.4 → 1.5.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 +22 -0
- package/README.md +8 -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 +199 -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 +7 -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 +2 -1
- package/dist/src/server/agent-runtime.d.ts +1 -1
- package/dist/src/server/agent-runtime.js +8 -2
- package/dist/src/server/agent-startup-instructions.d.ts +8 -1
- package/dist/src/server/agent-startup-instructions.js +15 -9
- package/dist/src/server/agent-stdin-dispatcher.d.ts +12 -5
- package/dist/src/server/agent-stdin-dispatcher.js +129 -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/fs-pick-folder.js +4 -0
- package/dist/src/server/fs-sandbox.js +36 -7
- package/dist/src/server/hive-team-guidance.d.ts +11 -6
- package/dist/src/server/hive-team-guidance.js +252 -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 +6 -1
- package/dist/src/server/recovery-summary.js +17 -17
- package/dist/src/server/restart-policy-support.d.ts +6 -1
- package/dist/src/server/restart-policy-support.js +9 -1
- package/dist/src/server/restart-policy.d.ts +2 -2
- package/dist/src/server/restart-policy.js +3 -1
- 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-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.js +4 -0
- package/dist/src/server/runtime-restart-policy.d.ts +3 -1
- package/dist/src/server/runtime-restart-policy.js +3 -1
- package/dist/src/server/runtime-store-contract.d.ts +122 -0
- package/dist/src/server/runtime-store-contract.js +1 -0
- package/dist/src/server/runtime-store-helpers.d.ts +9 -0
- package/dist/src/server/runtime-store-helpers.js +101 -2
- package/dist/src/server/runtime-store-workflows.d.ts +6 -0
- package/dist/src/server/runtime-store-workflows.js +100 -0
- package/dist/src/server/runtime-store.d.ts +3 -72
- package/dist/src/server/runtime-store.js +70 -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 +97 -1
- package/dist/src/server/system-message.d.ts +7 -0
- package/dist/src/server/system-message.js +8 -1
- package/dist/src/server/tasks-file-watcher.d.ts +13 -1
- package/dist/src/server/tasks-file-watcher.js +127 -23
- package/dist/src/server/tasks-file.d.ts +2 -1
- package/dist/src/server/tasks-file.js +32 -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 +9 -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 +15 -1
- package/dist/src/server/team-operations.js +116 -11
- 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/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-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 +401 -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-CcC-7kgG.js} +2 -2
- package/web/dist/assets/AddWorkspaceDialog-BDpOTfmt.js +1 -0
- package/web/dist/assets/{FirstRunWizard-B5wLcat5.js → FirstRunWizard-BYX_ocQn.js} +1 -1
- package/web/dist/assets/{MarketplaceDrawer-BC0eBOEW.js → MarketplaceDrawer-DUxSk7db.js} +1 -1
- package/web/dist/assets/WhatsNewDialog-B_RlCXcV.js +1 -0
- package/web/dist/assets/WorkerModal-D9-7YfZZ.js +1 -0
- package/web/dist/assets/WorkspaceTaskDrawer-BCKoF7qc.js +1 -0
- package/web/dist/assets/{WorkspaceTerminalPanels-CvibsPSd.js → WorkspaceTerminalPanels-Dq8y91t2.js} +1 -1
- package/web/dist/assets/index-BiOvKIVw.css +1 -0
- package/web/dist/assets/index-DMRUklT3.js +73 -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/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,15 @@
|
|
|
1
1
|
import type { AgentSummary, WorkspaceSummary } from '../shared/types.js';
|
|
2
2
|
import type { RecoveryMessage } from './message-log-store.js';
|
|
3
|
-
export declare const buildRecoverySummary: ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, }: {
|
|
3
|
+
export declare const buildRecoverySummary: ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, workflowsEnabled, autostaffEnabled, }: {
|
|
4
4
|
agent: AgentSummary;
|
|
5
5
|
allTaskMessages?: RecoveryMessage[];
|
|
6
6
|
messages: RecoveryMessage[];
|
|
7
7
|
tasksContent: string;
|
|
8
8
|
workers: AgentSummary[];
|
|
9
9
|
workspace: WorkspaceSummary;
|
|
10
|
+
/** Experimental workflow gate — keeps the recovered handover prompt
|
|
11
|
+
* consistent with the live flag (omits the workflow rule when off). */
|
|
12
|
+
workflowsEnabled?: boolean;
|
|
13
|
+
/** Experimental auto-staff gate (default on) — same, for the team-sizing rule. */
|
|
14
|
+
autostaffEnabled?: boolean;
|
|
10
15
|
}) => string;
|
|
@@ -6,7 +6,7 @@ const formatUserInputs = (messages) => {
|
|
|
6
6
|
const userInputs = messages.filter((message) => message.type === 'user_input');
|
|
7
7
|
return userInputs.length > 0
|
|
8
8
|
? userInputs.slice(-5).map((message) => `- user: ${message.text}`)
|
|
9
|
-
: ['-
|
|
9
|
+
: ['- (no new user_input in the last hour)'];
|
|
10
10
|
};
|
|
11
11
|
const formatTaskEvents = (messages, agent) => {
|
|
12
12
|
const taskEvents = messages.filter((message) => {
|
|
@@ -28,7 +28,7 @@ const formatTaskEvents = (messages, agent) => {
|
|
|
28
28
|
const status = message.status ? ` [${message.status}]` : '';
|
|
29
29
|
return `- report <- ${message.from}${status}: ${message.text}`;
|
|
30
30
|
})
|
|
31
|
-
: ['-
|
|
31
|
+
: ['- (no recent task events)'];
|
|
32
32
|
};
|
|
33
33
|
const getOpenTaskTargets = (agent, workers) => agent.role === 'orchestrator' ? workers : [agent];
|
|
34
34
|
const formatOpenTasks = (messages, agent, workers) => {
|
|
@@ -53,38 +53,38 @@ const formatOpenTasks = (messages, agent, workers) => {
|
|
|
53
53
|
lines.push(`- ${target.name}: ${task.text}`);
|
|
54
54
|
}
|
|
55
55
|
if (target.pendingTaskCount > queue.length) {
|
|
56
|
-
lines.push(`- ${target.name}: ${target.pendingTaskCount - queue.length}
|
|
56
|
+
lines.push(`- ${target.name}: ${target.pendingTaskCount - queue.length} pending with no recoverable detail`);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
return lines.length > 0 ? lines : ['-
|
|
59
|
+
return lines.length > 0 ? lines : ['- (no open tasks right now)'];
|
|
60
60
|
};
|
|
61
61
|
const formatWorkers = (workers) => {
|
|
62
62
|
if (workers.length === 0)
|
|
63
|
-
return ['-
|
|
63
|
+
return ['- (no other workers)'];
|
|
64
64
|
return workers.map((worker) => `- ${worker.name} (${worker.role}, ${worker.status}, pending_task_count: ${worker.pendingTaskCount})`);
|
|
65
65
|
};
|
|
66
|
-
const getTaskSectionTitle = (agent) => agent.role === 'orchestrator' ? '##
|
|
67
|
-
export const buildRecoverySummary = ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, }) => wrapSystemMessage([
|
|
68
|
-
|
|
69
|
-
'
|
|
66
|
+
const getTaskSectionTitle = (agent) => agent.role === 'orchestrator' ? '## Tasks you dispatched' : '## Tasks recently sent to you';
|
|
67
|
+
export const buildRecoverySummary = ({ agent, allTaskMessages, messages, tasksContent, workers, workspace, workflowsEnabled = false, autostaffEnabled = false, }) => wrapSystemMessage([
|
|
68
|
+
`You are ${agent.name} (${agent.role}) in workspace ${workspace.name}.`,
|
|
69
|
+
'Hive just restarted you and could not recover via native session resume. Here is the handover context.',
|
|
70
70
|
'',
|
|
71
|
-
'##
|
|
71
|
+
'## Conversation with the user in the last hour',
|
|
72
72
|
...formatUserInputs(messages),
|
|
73
73
|
'',
|
|
74
74
|
getTaskSectionTitle(agent),
|
|
75
75
|
...formatTaskEvents(messages, agent),
|
|
76
76
|
'',
|
|
77
|
-
'##
|
|
77
|
+
'## Open tasks',
|
|
78
78
|
...formatOpenTasks(allTaskMessages ?? messages, agent, workers),
|
|
79
79
|
'',
|
|
80
|
-
`##
|
|
81
|
-
tasksContent.slice(0, TASKS_HEAD_LIMIT) || '(
|
|
80
|
+
`## Current ${TASKS_RELATIVE_PATH}`,
|
|
81
|
+
tasksContent.slice(0, TASKS_HEAD_LIMIT) || '(empty)',
|
|
82
82
|
'',
|
|
83
|
-
'##
|
|
83
|
+
'## Active workers',
|
|
84
84
|
...formatWorkers(workers),
|
|
85
85
|
'',
|
|
86
|
-
agent.role === 'orchestrator' ? '## Hive worker
|
|
87
|
-
...getHiveTeamRules(agent),
|
|
86
|
+
agent.role === 'orchestrator' ? '## Hive worker dispatch rules' : '## Hive worker boundaries',
|
|
87
|
+
...getHiveTeamRules(agent, workflowsEnabled, autostaffEnabled),
|
|
88
88
|
'',
|
|
89
|
-
'
|
|
89
|
+
'Continue from here. If unsure, ask the user.',
|
|
90
90
|
].join('\n'));
|
|
@@ -11,6 +11,11 @@ export interface RestartPolicyInput {
|
|
|
11
11
|
listAgentRuns: (agentId: string) => PersistedAgentRun[];
|
|
12
12
|
listMessagesForRecovery: (workspaceId: string, sinceMs: number) => RecoveryMessage[];
|
|
13
13
|
readTasks: (workspacePath: string) => string;
|
|
14
|
+
/** Experimental workflow gate — threaded into the recovery handover prompt
|
|
15
|
+
* so it matches the live flag. Optional; omitted → off. */
|
|
16
|
+
getWorkflowsEnabled?: () => boolean;
|
|
17
|
+
/** Experimental auto-staff gate (default on) — same. */
|
|
18
|
+
getAutostaffEnabled?: () => boolean;
|
|
14
19
|
}
|
|
15
20
|
export declare const findPreviousRun: (runs: PersistedAgentRun[], currentRunId: string) => PersistedAgentRun | undefined;
|
|
16
21
|
export declare const writeSystemMessage: ({ deleteMessage, insertMessage, record, runId, text, writeToRun, }: {
|
|
@@ -19,5 +24,5 @@ export declare const writeSystemMessage: ({ deleteMessage, insertMessage, record
|
|
|
19
24
|
record: MessageLogRecord;
|
|
20
25
|
runId: string;
|
|
21
26
|
text: string;
|
|
22
|
-
writeToRun: (runId: string, text: string) => void
|
|
27
|
+
writeToRun: (runId: string, text: string) => Promise<void>;
|
|
23
28
|
}) => 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,8 @@ 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
12
|
}
|
|
13
13
|
export declare const createNoopRestartPolicy: () => RestartPolicy;
|
|
14
|
-
export declare const createRestartPolicy: ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, }: RestartPolicyInput) => RestartPolicy;
|
|
14
|
+
export declare const createRestartPolicy: ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, getWorkflowsEnabled, getAutostaffEnabled, }: RestartPolicyInput) => RestartPolicy;
|
|
@@ -7,7 +7,7 @@ export const createNoopRestartPolicy = () => ({
|
|
|
7
7
|
return false;
|
|
8
8
|
},
|
|
9
9
|
});
|
|
10
|
-
export const createRestartPolicy = ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, }) => ({
|
|
10
|
+
export const createRestartPolicy = ({ deleteMessage, getWorkspaceSnapshot, insertMessage, listAgentRuns, listMessagesForRecovery, readTasks, getWorkflowsEnabled, getAutostaffEnabled, }) => ({
|
|
11
11
|
injectPostStartMessage({ agentId, runId, startConfig, workspace, writeToRun }) {
|
|
12
12
|
const previousRun = findPreviousRun(listAgentRuns(agentId), runId);
|
|
13
13
|
if (!previousRun)
|
|
@@ -27,6 +27,8 @@ export const createRestartPolicy = ({ deleteMessage, getWorkspaceSnapshot, inser
|
|
|
27
27
|
tasksContent,
|
|
28
28
|
workers,
|
|
29
29
|
workspace,
|
|
30
|
+
workflowsEnabled: getWorkflowsEnabled?.() ?? false,
|
|
31
|
+
autostaffEnabled: getAutostaffEnabled?.() ?? false,
|
|
30
32
|
});
|
|
31
33
|
writeSystemMessage({
|
|
32
34
|
deleteMessage,
|
|
@@ -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
|
};
|
|
@@ -13,6 +13,49 @@ export interface SendTaskBody {
|
|
|
13
13
|
to: string;
|
|
14
14
|
text: string;
|
|
15
15
|
}
|
|
16
|
+
export interface SpawnAgentBody {
|
|
17
|
+
hive_port?: string;
|
|
18
|
+
project_id: string;
|
|
19
|
+
from_agent_id: string;
|
|
20
|
+
token?: string;
|
|
21
|
+
role?: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
cli?: string;
|
|
24
|
+
/** Opt-in: auto-dismiss the worker after its next dispatch report. Default
|
|
25
|
+
* false → persistent member (lives until explicit `team dismiss`). */
|
|
26
|
+
ephemeral?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface DismissAgentBody {
|
|
29
|
+
hive_port?: string;
|
|
30
|
+
project_id: string;
|
|
31
|
+
from_agent_id: string;
|
|
32
|
+
token?: string;
|
|
33
|
+
name: string;
|
|
34
|
+
}
|
|
35
|
+
export interface WorkflowRunBody {
|
|
36
|
+
hive_port?: string;
|
|
37
|
+
project_id: string;
|
|
38
|
+
from_agent_id: string;
|
|
39
|
+
token?: string;
|
|
40
|
+
source: string;
|
|
41
|
+
name?: string;
|
|
42
|
+
args?: unknown;
|
|
43
|
+
}
|
|
44
|
+
export interface WorkflowControlBody {
|
|
45
|
+
project_id: string;
|
|
46
|
+
from_agent_id: string;
|
|
47
|
+
token?: string;
|
|
48
|
+
run_id: string;
|
|
49
|
+
}
|
|
50
|
+
export interface WorkflowScheduleBody {
|
|
51
|
+
project_id: string;
|
|
52
|
+
from_agent_id: string;
|
|
53
|
+
token?: string;
|
|
54
|
+
source: string;
|
|
55
|
+
name: string;
|
|
56
|
+
cron: string;
|
|
57
|
+
args?: unknown;
|
|
58
|
+
}
|
|
16
59
|
export interface ReportTaskBody {
|
|
17
60
|
dispatch_id?: string;
|
|
18
61
|
project_id: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
|
|
2
2
|
import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
|
|
3
|
+
import { serializeLiveAgentRun } from './workflow-http-serializers.js';
|
|
3
4
|
import { getWorkspaceShellAgentId } from './workspace-shell-runtime.js';
|
|
4
5
|
export const runtimeRoutes = [
|
|
5
6
|
route('GET', '/api/ui/workspaces/:workspaceId/runs', ({ params, request, response, store }) => {
|
|
@@ -73,6 +74,6 @@ export const runtimeRoutes = [
|
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
75
76
|
requireUiTokenFromRequest(request, store.validateUiToken);
|
|
76
|
-
sendJson(response, 200, store.getLiveRun(runId));
|
|
77
|
+
sendJson(response, 200, serializeLiveAgentRun(store.getLiveRun(runId)));
|
|
77
78
|
}),
|
|
78
79
|
];
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { resolveCommandPath } from './agent-command-resolver.js';
|
|
2
|
+
import { BadRequestError } from './http-errors.js';
|
|
2
3
|
import { getRequiredParam, readJsonBody, route, sendJson } from './route-helpers.js';
|
|
4
|
+
import { ensureProtocolFile } from './tasks-file.js';
|
|
5
|
+
import { AUTOSTAFF_ENABLED_KEY, readAutostaffEnabled, serializeAutostaffEnabled, } from './team-autostaff.js';
|
|
3
6
|
import { requireUiTokenFromRequest } from './ui-auth-helpers.js';
|
|
7
|
+
import { assertValidWorkflowCliPolicy, CANONICAL_WORKFLOW_CLIS, readWorkflowCliPolicy, WORKFLOW_CLI_POLICY_KEY, } from './workflow-cli-policy.js';
|
|
8
|
+
import { readWorkflowEnabled, serializeWorkflowEnabled, WORKFLOW_ENABLED_KEY, } from './workflow-feature.js';
|
|
4
9
|
const serializeCommandPreset = (preset) => {
|
|
5
10
|
let available = false;
|
|
6
11
|
try {
|
|
@@ -35,6 +40,26 @@ const serializeRoleTemplate = (template) => ({
|
|
|
35
40
|
default_env: template.defaultEnv,
|
|
36
41
|
is_builtin: template.isBuiltin,
|
|
37
42
|
});
|
|
43
|
+
/**
|
|
44
|
+
* Rewrite every open workspace's `.hive/PROTOCOL.md` to match the just-saved
|
|
45
|
+
* workflow feature flag + CLI policy. Without this the doc only refreshes on
|
|
46
|
+
* the next workspace open / watcher start, so toggling the feature would leave
|
|
47
|
+
* stale guidance (workflow DSL still present after disabling, or absent right
|
|
48
|
+
* after enabling). Idempotent: ensureProtocolFile only rewrites on change.
|
|
49
|
+
*/
|
|
50
|
+
const refreshWorkflowProtocolDocs = (store) => {
|
|
51
|
+
const policy = readWorkflowCliPolicy(store.settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null);
|
|
52
|
+
const enabled = readWorkflowEnabled(store.settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null);
|
|
53
|
+
const autostaff = readAutostaffEnabled(store.settings.getAppState(AUTOSTAFF_ENABLED_KEY)?.value ?? null);
|
|
54
|
+
for (const workspace of store.listWorkspaces()) {
|
|
55
|
+
try {
|
|
56
|
+
ensureProtocolFile(workspace.path, policy, enabled, autostaff);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error('[hive] swallowed:settings.refreshProtocol', error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
38
63
|
const readCommandPresetBody = async (request) => {
|
|
39
64
|
const body = await readJsonBody(request);
|
|
40
65
|
return {
|
|
@@ -134,4 +159,55 @@ export const settingsRoutes = [
|
|
|
134
159
|
response.statusCode = 204;
|
|
135
160
|
response.end();
|
|
136
161
|
}),
|
|
162
|
+
route('GET', '/api/settings/workflow-cli-policy', ({ request, response, store }) => {
|
|
163
|
+
requireUiTokenFromRequest(request, store.validateUiToken);
|
|
164
|
+
const policy = readWorkflowCliPolicy(store.settings.getAppState(WORKFLOW_CLI_POLICY_KEY)?.value ?? null);
|
|
165
|
+
sendJson(response, 200, { ...policy, supported: [...CANONICAL_WORKFLOW_CLIS] });
|
|
166
|
+
}),
|
|
167
|
+
route('PUT', '/api/settings/workflow-cli-policy', async ({ request, response, store }) => {
|
|
168
|
+
requireUiTokenFromRequest(request, store.validateUiToken);
|
|
169
|
+
const body = await readJsonBody(request);
|
|
170
|
+
// Strict validation: a bad payload is rejected (400) rather than persisted.
|
|
171
|
+
const clean = (() => {
|
|
172
|
+
try {
|
|
173
|
+
return assertValidWorkflowCliPolicy(body);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
throw new BadRequestError(error instanceof Error ? error.message : String(error));
|
|
177
|
+
}
|
|
178
|
+
})();
|
|
179
|
+
store.settings.setAppState(WORKFLOW_CLI_POLICY_KEY, JSON.stringify(clean));
|
|
180
|
+
refreshWorkflowProtocolDocs(store);
|
|
181
|
+
sendJson(response, 200, { ...clean, supported: [...CANONICAL_WORKFLOW_CLIS] });
|
|
182
|
+
}),
|
|
183
|
+
route('GET', '/api/settings/workflow-feature', ({ request, response, store }) => {
|
|
184
|
+
requireUiTokenFromRequest(request, store.validateUiToken);
|
|
185
|
+
const enabled = readWorkflowEnabled(store.settings.getAppState(WORKFLOW_ENABLED_KEY)?.value ?? null);
|
|
186
|
+
sendJson(response, 200, { enabled });
|
|
187
|
+
}),
|
|
188
|
+
route('PUT', '/api/settings/workflow-feature', async ({ request, response, store }) => {
|
|
189
|
+
requireUiTokenFromRequest(request, store.validateUiToken);
|
|
190
|
+
const body = await readJsonBody(request);
|
|
191
|
+
if (typeof body.enabled !== 'boolean') {
|
|
192
|
+
throw new BadRequestError('workflow-feature requires { enabled: boolean }');
|
|
193
|
+
}
|
|
194
|
+
store.settings.setAppState(WORKFLOW_ENABLED_KEY, serializeWorkflowEnabled(body.enabled));
|
|
195
|
+
refreshWorkflowProtocolDocs(store);
|
|
196
|
+
sendJson(response, 200, { enabled: body.enabled });
|
|
197
|
+
}),
|
|
198
|
+
route('GET', '/api/settings/team-autostaff', ({ request, response, store }) => {
|
|
199
|
+
requireUiTokenFromRequest(request, store.validateUiToken);
|
|
200
|
+
const enabled = readAutostaffEnabled(store.settings.getAppState(AUTOSTAFF_ENABLED_KEY)?.value ?? null);
|
|
201
|
+
sendJson(response, 200, { enabled });
|
|
202
|
+
}),
|
|
203
|
+
route('PUT', '/api/settings/team-autostaff', async ({ request, response, store }) => {
|
|
204
|
+
requireUiTokenFromRequest(request, store.validateUiToken);
|
|
205
|
+
const body = await readJsonBody(request);
|
|
206
|
+
if (typeof body.enabled !== 'boolean') {
|
|
207
|
+
throw new BadRequestError('team-autostaff requires { enabled: boolean }');
|
|
208
|
+
}
|
|
209
|
+
store.settings.setAppState(AUTOSTAFF_ENABLED_KEY, serializeAutostaffEnabled(body.enabled));
|
|
210
|
+
refreshWorkflowProtocolDocs(store);
|
|
211
|
+
sendJson(response, 200, { enabled: body.enabled });
|
|
212
|
+
}),
|
|
137
213
|
];
|