blun-king-cli 9.1.536 → 9.1.550
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/LIESMICH.txt +13 -869
- package/README.md +41 -833
- package/bin/assistant-message-offload-policy.cjs +3 -1
- package/bin/context-performance-policy.cjs +2 -5
- package/bin/context-pressure-policy.cjs +20 -0
- package/bin/cron-run-output.cjs +45 -0
- package/bin/cron-run-store.cjs +145 -0
- package/bin/durable-task-resume-policy.cjs +130 -0
- package/bin/durable-task-resume-runtime.cjs +117 -0
- package/bin/durable-task-resume-store.cjs +88 -0
- package/bin/editable-tool-approval-policy.cjs +540 -0
- package/bin/editable-tool-approval-runtime.cjs +99 -0
- package/bin/html-to-research-markdown.cjs +146 -0
- package/bin/programmatic-tool-runtime.mjs +330 -4
- package/bin/read-continuation-policy.cjs +36 -5
- package/bin/scoped-cron-run-policy.cjs +358 -0
- package/bin/startup-preferences.cjs +3 -3
- package/bin/structured-agent-swarm-output.cjs +325 -0
- package/bin/subagent-context-fork-policy.cjs +155 -0
- package/bin/subagent-skill-policy.cjs +204 -0
- package/bin/telegram-approval-relay.cjs +2 -1
- package/bin/tool-result-offload-policy.cjs +14 -1
- package/bin/update-notice.js +14 -18
- package/bin/user-message-offload-policy.cjs +3 -1
- package/blun.mjs +1089 -477
- package/codebase-index/README.md +12 -0
- package/codebase-index/codebase_index.py +129 -18
- package/package.json +23 -58
- package/telegram-plugin/bin/telegram-typing-keepalive.cjs +89 -0
- package/telegram-plugin/dist/bridge.mjs +8 -1
- package/CHANGELOG.md +0 -321
- package/agent-spine-plugin/CHANGELOG.md +0 -406
- package/agent-spine-plugin/CONTRIBUTING.md +0 -52
- package/agent-spine-plugin/README.md +0 -344
- package/agent-spine-plugin/SECURITY.md +0 -47
- package/agent-spine-plugin/docs/acceptance.md +0 -61
- package/agent-spine-plugin/docs/architecture.md +0 -183
- package/agent-spine-plugin/docs/attention.md +0 -121
- package/agent-spine-plugin/docs/automatic-continuity.md +0 -79
- package/agent-spine-plugin/docs/channel-runtime.md +0 -92
- package/agent-spine-plugin/docs/coordination.md +0 -138
- package/agent-spine-plugin/docs/feed-transport.md +0 -99
- package/agent-spine-plugin/docs/gateway-runtime.md +0 -116
- package/agent-spine-plugin/docs/harness-reference.md +0 -45
- package/agent-spine-plugin/docs/host-integration.md +0 -129
- package/agent-spine-plugin/docs/https-transport.md +0 -116
- package/agent-spine-plugin/docs/learning.md +0 -133
- package/agent-spine-plugin/docs/object-transport.md +0 -93
- package/agent-spine-plugin/docs/peer-transport.md +0 -88
- package/agent-spine-plugin/docs/preflight-recall.md +0 -69
- package/agent-spine-plugin/docs/preservation-contract.md +0 -53
- package/agent-spine-plugin/docs/quality-gates.md +0 -50
- package/agent-spine-plugin/docs/relationships.md +0 -73
- package/agent-spine-plugin/docs/releasing.md +0 -83
- package/agent-spine-plugin/docs/roadmap.md +0 -307
- package/agent-spine-plugin/docs/selfstarter.md +0 -88
- package/agent-spine-plugin/docs/session-briefing.md +0 -74
- package/agent-spine-plugin/docs/shared-memory.md +0 -259
- package/agent-spine-plugin/docs/source-roots.md +0 -86
- package/agent-spine-plugin/docs/sqlite-transport.md +0 -76
- package/agent-spine-plugin/scripts/check-hosts.js +0 -195
- package/agent-spine-plugin/scripts/check-install.js +0 -569
- package/agent-spine-plugin/scripts/check-syntax.js +0 -29
- package/agent-spine-plugin/scripts/github-actions.js +0 -11
- package/agent-spine-plugin/scripts/release-check.js +0 -128
- package/agent-spine-plugin/scripts/run-acceptance.js +0 -19
- package/agent-spine-plugin/scripts/run-checks.js +0 -46
- package/agent-spine-plugin/scripts/run-tests-hermetic.js +0 -73
- package/agent-spine-plugin/spine-example/1-identity.md +0 -12
- package/agent-spine-plugin/spine-example/2-voice.md +0 -6
- package/agent-spine-plugin/spine-example/3-conduct.md +0 -8
- package/agent-spine-plugin/spine-example/4-history.md +0 -4
- package/bin/fredrik-glm-provider.cjs +0 -256
- package/bin/package-regression-policy.cjs +0 -77
- package/fredrik-glm-profile.toml.example +0 -26
- package/release-planned-removals.json +0 -15
- package/scripts/check-active-profile-plugin-startup.js +0 -36
- package/scripts/check-active-work-steer-regression.js +0 -46
- package/scripts/check-approval-observability-regression.js +0 -111
- package/scripts/check-approval-queue-shortcuts-regression.js +0 -65
- package/scripts/check-bundled-agent-spine-regression.js +0 -48
- package/scripts/check-codebase-search-packaging-regression.js +0 -92
- package/scripts/check-copy-command-regression.js +0 -74
- package/scripts/check-current-turn-read-pin-mutation-regression.js +0 -72
- package/scripts/check-current-turn-read-pin-regression.js +0 -94
- package/scripts/check-deepseek-native-max-regression.js +0 -49
- package/scripts/check-empty-response-effort-downgrade-regression.js +0 -48
- package/scripts/check-fredrik-glm-mutation-regression.js +0 -18
- package/scripts/check-fredrik-glm-regression.js +0 -169
- package/scripts/check-historical-tool-result-preview-regression.js +0 -77
- package/scripts/check-history-pressure-offload-regression.js +0 -77
- package/scripts/check-mcp-startup-wait-budget.js +0 -48
- package/scripts/check-package-regression.js +0 -38
- package/scripts/check-plugin-startup-regression.js +0 -53
- package/scripts/check-programmatic-context-isolation-regression.js +0 -193
- package/scripts/check-programmatic-tool-regression.js +0 -294
- package/scripts/check-queue-controls-regression.js +0 -189
- package/scripts/check-release-metadata.js +0 -103
- package/scripts/check-reload-agent-spine-regression.js +0 -76
- package/scripts/check-resume-replay-regression.js +0 -102
- package/scripts/check-session-cancel-regression.js +0 -43
- package/scripts/check-session-picker-resume-metrics-regression.js +0 -97
- package/scripts/check-session-start-hook-context-regression.js +0 -228
- package/scripts/check-shell-terminal-isolation-regression.js +0 -81
- package/scripts/check-slash-escape-regression.js +0 -89
- package/scripts/check-startup-swarm-command-regression.js +0 -24
- package/scripts/check-structured-subagent-output-regression.js +0 -331
- package/scripts/check-telegram-bridge-watchdog.js +0 -60
- package/scripts/check-telegram-direct-work-resume-regression.js +0 -53
- package/scripts/check-telegram-loop-exactly-once-regression.js +0 -71
- package/scripts/check-todo-loop-regression.js +0 -78
- package/scripts/check-todo-progress-regression.js +0 -416
- package/scripts/check-todo-recovery-catalog-regression.js +0 -50
- package/scripts/check-tool-schema-capacity-regression.js +0 -40
- package/scripts/programmatic-tool-runtime.test.mjs +0 -365
- package/scripts/structured-subagent-output.test.cjs +0 -170
- /package/{scripts → bin}/fix-node-pty-perms.js +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MAX_STRUCTURED_SWARM_OUTPUT_BYTES = 256 * 1024;
|
|
4
|
+
const MAX_STRUCTURED_SWARM_FALLBACK_BYTES = 64 * 1024;
|
|
5
|
+
const MAX_STRUCTURED_SWARM_RESULTS = 128;
|
|
6
|
+
const MAX_PROFILE_BYTES = 64;
|
|
7
|
+
const MAX_ERROR_CHARS = 512;
|
|
8
|
+
const RESUME_HINT = 'Resume this agent through AgentSwarm.resume_agent_ids.';
|
|
9
|
+
|
|
10
|
+
function isPlainObject(value) {
|
|
11
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
12
|
+
const prototype = Object.getPrototypeOf(value);
|
|
13
|
+
return prototype === Object.prototype || prototype === null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function byteLength(value) {
|
|
17
|
+
return Buffer.byteLength(value, 'utf8');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function validBoundedString(value, maxBytes) {
|
|
21
|
+
return typeof value === 'string' && value.length > 0 && byteLength(value) <= maxBytes;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function safeUsage(value) {
|
|
25
|
+
if (!isPlainObject(value)) return null;
|
|
26
|
+
const keys = ['input', 'output', 'cache_read', 'cache_write'];
|
|
27
|
+
if (Object.keys(value).length !== keys.length || !keys.every((key) => Object.hasOwn(value, key))) return null;
|
|
28
|
+
if (!keys.every((key) => Number.isSafeInteger(value[key]) && value[key] >= 0)) return null;
|
|
29
|
+
return {
|
|
30
|
+
input: value.input,
|
|
31
|
+
output: value.output,
|
|
32
|
+
cache_read: value.cache_read,
|
|
33
|
+
cache_write: value.cache_write,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function boundedError(value) {
|
|
38
|
+
const text = String(value ?? 'unknown error').replace(/[\r\n\t]+/gu, ' ').trim();
|
|
39
|
+
return text.length > MAX_ERROR_CHARS ? `${text.slice(0, MAX_ERROR_CHARS - 3)}...` : text;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseStructuredChildEnvelope(raw, expected) {
|
|
43
|
+
if (typeof raw !== 'string') return { ok: false, error: 'child envelope is not a JSON string' };
|
|
44
|
+
let value;
|
|
45
|
+
try {
|
|
46
|
+
value = JSON.parse(raw);
|
|
47
|
+
} catch {
|
|
48
|
+
return { ok: false, error: 'child envelope is not exactly one JSON object' };
|
|
49
|
+
}
|
|
50
|
+
if (!isPlainObject(value)) return { ok: false, error: 'child envelope is not an object' };
|
|
51
|
+
const keys = ['agent_id', 'actual_subagent_type', 'status', 'response_format', 'result', 'usage'];
|
|
52
|
+
if (Object.keys(value).length !== keys.length || !keys.every((key) => Object.hasOwn(value, key))) {
|
|
53
|
+
return { ok: false, error: 'child envelope shape mismatch' };
|
|
54
|
+
}
|
|
55
|
+
if (value.agent_id !== expected.agentId) return { ok: false, error: 'agent_id mismatch' };
|
|
56
|
+
if (value.actual_subagent_type !== expected.profileName) return { ok: false, error: 'actual_subagent_type mismatch' };
|
|
57
|
+
if (!validBoundedString(value.actual_subagent_type, MAX_PROFILE_BYTES)) {
|
|
58
|
+
return { ok: false, error: 'actual_subagent_type exceeds 64 UTF-8 bytes' };
|
|
59
|
+
}
|
|
60
|
+
if (value.status !== 'completed') return { ok: false, error: 'child status mismatch' };
|
|
61
|
+
if (value.response_format !== expected.responseFormat.name) return { ok: false, error: 'response_format mismatch' };
|
|
62
|
+
const usage = safeUsage(value.usage);
|
|
63
|
+
if (usage === null) return { ok: false, error: 'child usage shape mismatch' };
|
|
64
|
+
try {
|
|
65
|
+
if (expected.responseFormat.validator(value.result) !== true) {
|
|
66
|
+
return { ok: false, error: 'child result failed response_format validation' };
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
return { ok: false, error: 'child result validator failed closed' };
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
ok: true,
|
|
73
|
+
value: {
|
|
74
|
+
agentId: value.agent_id,
|
|
75
|
+
profileName: value.actual_subagent_type,
|
|
76
|
+
result: value.result,
|
|
77
|
+
usage,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function baseEntry(input, outcome, profileName) {
|
|
83
|
+
const entry = {
|
|
84
|
+
index: input.spec.index,
|
|
85
|
+
mode: input.spec.kind,
|
|
86
|
+
};
|
|
87
|
+
if (input.spec.item !== undefined) entry.item = input.spec.item;
|
|
88
|
+
if (input.agentId !== undefined) entry.agent_id = input.agentId;
|
|
89
|
+
if (profileName !== undefined) entry.actual_subagent_type = profileName;
|
|
90
|
+
entry.outcome = outcome;
|
|
91
|
+
return entry;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function failureEntry(input, error, profileName) {
|
|
95
|
+
const entry = baseEntry(input, 'failed', profileName);
|
|
96
|
+
entry.error = `Structured swarm item failed: ${boundedError(error)}`;
|
|
97
|
+
if (input.agentId !== undefined) entry.resume_hint = RESUME_HINT;
|
|
98
|
+
return entry;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeResult(input, responseFormat) {
|
|
102
|
+
const profileName = validBoundedString(input.profileName, MAX_PROFILE_BYTES) ? input.profileName : undefined;
|
|
103
|
+
if (input.status !== 'completed') {
|
|
104
|
+
const outcome = input.status === 'aborted' ? 'aborted' : 'failed';
|
|
105
|
+
const entry = baseEntry(input, outcome, profileName);
|
|
106
|
+
entry.error = boundedError(input.error);
|
|
107
|
+
if (input.agentId !== undefined) entry.resume_hint = RESUME_HINT;
|
|
108
|
+
return entry;
|
|
109
|
+
}
|
|
110
|
+
if (profileName === undefined) return failureEntry(input, 'authoritative profile is unavailable or invalid');
|
|
111
|
+
const parsed = parseStructuredChildEnvelope(input.result, {
|
|
112
|
+
agentId: input.agentId,
|
|
113
|
+
profileName,
|
|
114
|
+
responseFormat,
|
|
115
|
+
});
|
|
116
|
+
if (!parsed.ok) return failureEntry(input, parsed.error, profileName);
|
|
117
|
+
const entry = baseEntry(input, 'completed', parsed.value.profileName);
|
|
118
|
+
entry.result = parsed.value.result;
|
|
119
|
+
entry.usage = parsed.value.usage;
|
|
120
|
+
return entry;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function summaryFor(results) {
|
|
124
|
+
return {
|
|
125
|
+
completed: results.filter((entry) => entry.outcome === 'completed').length,
|
|
126
|
+
failed: results.filter((entry) => entry.outcome === 'failed').length,
|
|
127
|
+
aborted: results.filter((entry) => entry.outcome === 'aborted').length,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function aggregateStatus(summary) {
|
|
132
|
+
if (summary.completed === summary.completed + summary.failed + summary.aborted) return 'completed';
|
|
133
|
+
if (summary.completed === 0) return 'failed';
|
|
134
|
+
return 'partial';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function metadataOnlyEntry(entry) {
|
|
138
|
+
const result = {
|
|
139
|
+
index: entry.index,
|
|
140
|
+
mode: entry.mode,
|
|
141
|
+
};
|
|
142
|
+
if (entry.agent_id !== undefined) result.agent_id = entry.agent_id;
|
|
143
|
+
if (entry.actual_subagent_type !== undefined) result.actual_subagent_type = entry.actual_subagent_type;
|
|
144
|
+
result.outcome = entry.outcome;
|
|
145
|
+
result.result_omitted = true;
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function minimalOverflowEnvelope(responseFormat, summary) {
|
|
150
|
+
return {
|
|
151
|
+
status: 'failed',
|
|
152
|
+
error_code: 'structured_swarm_output_limit_exceeded',
|
|
153
|
+
response_format: responseFormat.name,
|
|
154
|
+
summary,
|
|
155
|
+
results_omitted: true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function validateInputs(inputs) {
|
|
160
|
+
if (inputs.length === 0) throw new Error('structured swarm requires at least one result');
|
|
161
|
+
if (inputs.length > MAX_STRUCTURED_SWARM_RESULTS) {
|
|
162
|
+
throw new Error(`structured swarm supports at most ${String(MAX_STRUCTURED_SWARM_RESULTS)} results`);
|
|
163
|
+
}
|
|
164
|
+
const indexes = inputs.map((input) => input?.spec?.index);
|
|
165
|
+
const expected = Array.from({ length: inputs.length }, (_, index) => index + 1);
|
|
166
|
+
const ordered = [...indexes].sort((left, right) => left - right);
|
|
167
|
+
if (!ordered.every((value, index) => Number.isInteger(value) && value === expected[index])) {
|
|
168
|
+
throw new Error('structured swarm indexes must be unique contiguous integers starting at 1');
|
|
169
|
+
}
|
|
170
|
+
if (!inputs.every((input) => input.spec.kind === 'spawn' || input.spec.kind === 'resume')) {
|
|
171
|
+
throw new Error('structured swarm mode must be spawn or resume');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function renderStructuredSwarmResultsWithLimits(inputs, responseFormat, limits) {
|
|
176
|
+
if (!Array.isArray(inputs)) throw new TypeError('structured swarm results must be an array');
|
|
177
|
+
if (!responseFormat || typeof responseFormat.name !== 'string' || typeof responseFormat.validator !== 'function') {
|
|
178
|
+
throw new TypeError('prepared response format is required');
|
|
179
|
+
}
|
|
180
|
+
validateInputs(inputs);
|
|
181
|
+
const maxOutputBytes = limits?.maxOutputBytes ?? MAX_STRUCTURED_SWARM_OUTPUT_BYTES;
|
|
182
|
+
const maxFallbackBytes = limits?.maxFallbackBytes ?? MAX_STRUCTURED_SWARM_FALLBACK_BYTES;
|
|
183
|
+
const results = inputs.map((input) => normalizeResult(input, responseFormat)).sort((left, right) => left.index - right.index);
|
|
184
|
+
const summary = summaryFor(results);
|
|
185
|
+
const full = JSON.stringify({
|
|
186
|
+
status: aggregateStatus(summary),
|
|
187
|
+
response_format: responseFormat.name,
|
|
188
|
+
summary,
|
|
189
|
+
results,
|
|
190
|
+
});
|
|
191
|
+
if (byteLength(full) <= maxOutputBytes) return full;
|
|
192
|
+
|
|
193
|
+
const fallback = JSON.stringify({
|
|
194
|
+
status: 'failed',
|
|
195
|
+
error_code: 'structured_swarm_output_limit_exceeded',
|
|
196
|
+
response_format: responseFormat.name,
|
|
197
|
+
summary,
|
|
198
|
+
results: results.map(metadataOnlyEntry),
|
|
199
|
+
});
|
|
200
|
+
if (byteLength(fallback) <= maxFallbackBytes) return fallback;
|
|
201
|
+
return JSON.stringify(minimalOverflowEnvelope(responseFormat, summary));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function renderStructuredSwarmResults(inputs, responseFormat) {
|
|
205
|
+
return renderStructuredSwarmResultsWithLimits(inputs, responseFormat, undefined);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function safeSummary(value) {
|
|
209
|
+
if (!isPlainObject(value)) return null;
|
|
210
|
+
const keys = ['completed', 'failed', 'aborted'];
|
|
211
|
+
if (Object.keys(value).length !== keys.length || !keys.every((key) => Object.hasOwn(value, key))) return null;
|
|
212
|
+
if (!keys.every((key) => Number.isSafeInteger(value[key]) && value[key] >= 0)) return null;
|
|
213
|
+
return {
|
|
214
|
+
completed: value.completed,
|
|
215
|
+
failed: value.failed,
|
|
216
|
+
aborted: value.aborted,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function parseStructuredDisplayEntry(entry) {
|
|
221
|
+
if (!isPlainObject(entry)) return null;
|
|
222
|
+
if (!Number.isSafeInteger(entry.index) || entry.index < 1) return null;
|
|
223
|
+
if (entry.mode !== 'spawn' && entry.mode !== 'resume') return null;
|
|
224
|
+
if (entry.outcome !== 'completed' && entry.outcome !== 'failed' && entry.outcome !== 'aborted') return null;
|
|
225
|
+
|
|
226
|
+
const resultOmitted = entry.result_omitted === true;
|
|
227
|
+
if (Object.hasOwn(entry, 'result_omitted') && !resultOmitted) return null;
|
|
228
|
+
if (resultOmitted && (Object.hasOwn(entry, 'result') || Object.hasOwn(entry, 'error'))) return null;
|
|
229
|
+
if (!resultOmitted && entry.outcome === 'completed' && !Object.hasOwn(entry, 'result')) return null;
|
|
230
|
+
if (!resultOmitted && entry.outcome !== 'completed' && typeof entry.error !== 'string') return null;
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
index: entry.index,
|
|
234
|
+
status: entry.outcome,
|
|
235
|
+
completedText: entry.outcome === 'completed' && !resultOmitted
|
|
236
|
+
? JSON.stringify(entry.result)
|
|
237
|
+
: undefined,
|
|
238
|
+
failureText: entry.outcome !== 'completed' && !resultOmitted
|
|
239
|
+
? boundedError(entry.error)
|
|
240
|
+
: undefined,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function parseStructuredSwarmDisplay(output) {
|
|
245
|
+
if (typeof output !== 'string') return { recognized: false };
|
|
246
|
+
let value;
|
|
247
|
+
try {
|
|
248
|
+
value = JSON.parse(output);
|
|
249
|
+
} catch {
|
|
250
|
+
return { recognized: false };
|
|
251
|
+
}
|
|
252
|
+
if (!isPlainObject(value)) return { recognized: false };
|
|
253
|
+
if (!validBoundedString(value.response_format, MAX_STRUCTURED_SWARM_FALLBACK_BYTES)) {
|
|
254
|
+
return { recognized: false };
|
|
255
|
+
}
|
|
256
|
+
if (value.status !== 'completed' && value.status !== 'partial' && value.status !== 'failed') {
|
|
257
|
+
return { recognized: false };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const summary = safeSummary(value.summary);
|
|
261
|
+
if (summary === null) return { recognized: false };
|
|
262
|
+
const summaryTotal = summary.completed + summary.failed + summary.aborted;
|
|
263
|
+
if (summaryTotal < 1 || summaryTotal > MAX_STRUCTURED_SWARM_RESULTS) return { recognized: false };
|
|
264
|
+
const overflow = value.error_code === 'structured_swarm_output_limit_exceeded';
|
|
265
|
+
if (Object.hasOwn(value, 'error_code') && !overflow) return { recognized: false };
|
|
266
|
+
if (overflow && value.status !== 'failed') return { recognized: false };
|
|
267
|
+
|
|
268
|
+
if (value.results_omitted === true) {
|
|
269
|
+
const allowedKeys = ['status', 'error_code', 'response_format', 'summary', 'results_omitted'];
|
|
270
|
+
if (!overflow || Object.hasOwn(value, 'results') || Object.keys(value).some((key) => !allowedKeys.includes(key))) {
|
|
271
|
+
return { recognized: false };
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
recognized: true,
|
|
275
|
+
aggregateFailed: true,
|
|
276
|
+
summary,
|
|
277
|
+
statuses: [],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (Object.hasOwn(value, 'results_omitted') || !Array.isArray(value.results)) return { recognized: false };
|
|
282
|
+
const allowedKeys = overflow
|
|
283
|
+
? ['status', 'error_code', 'response_format', 'summary', 'results']
|
|
284
|
+
: ['status', 'response_format', 'summary', 'results'];
|
|
285
|
+
if (Object.keys(value).some((key) => !allowedKeys.includes(key))) return { recognized: false };
|
|
286
|
+
if (value.results.length === 0 || value.results.length > MAX_STRUCTURED_SWARM_RESULTS) return { recognized: false };
|
|
287
|
+
|
|
288
|
+
const statuses = value.results.map(parseStructuredDisplayEntry);
|
|
289
|
+
if (statuses.some((entry) => entry === null)) return { recognized: false };
|
|
290
|
+
statuses.sort((left, right) => left.index - right.index);
|
|
291
|
+
if (!statuses.every((entry, index) => entry.index === index + 1)) return { recognized: false };
|
|
292
|
+
|
|
293
|
+
const actualSummary = {
|
|
294
|
+
completed: statuses.filter((entry) => entry.status === 'completed').length,
|
|
295
|
+
failed: statuses.filter((entry) => entry.status === 'failed').length,
|
|
296
|
+
aborted: statuses.filter((entry) => entry.status === 'aborted').length,
|
|
297
|
+
};
|
|
298
|
+
if (!Object.keys(actualSummary).every((key) => actualSummary[key] === summary[key])) return { recognized: false };
|
|
299
|
+
if (!overflow && aggregateStatus(summary) !== value.status) return { recognized: false };
|
|
300
|
+
if (!overflow && value.results.some((entry) => entry.result_omitted === true)) return { recognized: false };
|
|
301
|
+
if (overflow && !value.results.every((entry) => entry.result_omitted === true)) return { recognized: false };
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
recognized: true,
|
|
305
|
+
aggregateFailed: overflow,
|
|
306
|
+
summary,
|
|
307
|
+
statuses,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function structuredSwarmToolResult(output) {
|
|
312
|
+
const parsed = parseStructuredSwarmDisplay(output);
|
|
313
|
+
return parsed.recognized && parsed.aggregateFailed ? { output, isError: true } : { output };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
module.exports = {
|
|
317
|
+
MAX_STRUCTURED_SWARM_OUTPUT_BYTES,
|
|
318
|
+
MAX_STRUCTURED_SWARM_FALLBACK_BYTES,
|
|
319
|
+
MAX_STRUCTURED_SWARM_RESULTS,
|
|
320
|
+
parseStructuredChildEnvelope,
|
|
321
|
+
parseStructuredSwarmDisplay,
|
|
322
|
+
renderStructuredSwarmResults,
|
|
323
|
+
renderStructuredSwarmResultsWithLimits,
|
|
324
|
+
structuredSwarmToolResult,
|
|
325
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_MAX_MESSAGES = 16;
|
|
4
|
+
const DEFAULT_MAX_CHARS = 64 * 1024;
|
|
5
|
+
const MAX_CONFIGURED_MESSAGES = 64;
|
|
6
|
+
const MAX_CONFIGURED_CHARS = 256 * 1024;
|
|
7
|
+
|
|
8
|
+
const SUBAGENT_FORK_REMINDER = [
|
|
9
|
+
'You received a bounded, redacted projection of the parent conversation.',
|
|
10
|
+
'It contains only direct local user text and completed text-only assistant replies.',
|
|
11
|
+
'It excludes system injections, private channel messages, attachments, tool calls, tool arguments, and tool results.',
|
|
12
|
+
'Treat that history as already happened. Your actual delegated task below is authoritative.',
|
|
13
|
+
'Do not launch another context fork. Return the requested result to the parent agent.',
|
|
14
|
+
].join(' ');
|
|
15
|
+
|
|
16
|
+
function boundedPositiveInteger(value, fallback, maximum, name) {
|
|
17
|
+
if (value === undefined) return fallback;
|
|
18
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > maximum) {
|
|
19
|
+
throw new TypeError(`${name} must be an integer between 1 and ${String(maximum)}`);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveSubagentContextMode(options = {}) {
|
|
25
|
+
const requested = options.requested ?? 'isolated';
|
|
26
|
+
if (requested !== 'isolated' && requested !== 'fork') {
|
|
27
|
+
throw new TypeError('context_mode must be either "isolated" or "fork"');
|
|
28
|
+
}
|
|
29
|
+
if (requested === 'isolated') return requested;
|
|
30
|
+
if (options.resume === true) {
|
|
31
|
+
throw new Error('context_mode="fork" is available only when creating a new subagent, not while resuming one');
|
|
32
|
+
}
|
|
33
|
+
if (options.isRoot !== true) {
|
|
34
|
+
throw new Error('context_mode="fork" is available only to the root agent');
|
|
35
|
+
}
|
|
36
|
+
return requested;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function redactSecrets(value) {
|
|
40
|
+
return String(value)
|
|
41
|
+
.replaceAll(/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/giu, '[redacted]')
|
|
42
|
+
.replaceAll(/\b(authorization)\s*:\s*(?:basic|bearer)\s+\S+/giu, '$1: [redacted]')
|
|
43
|
+
.replaceAll(/\b(api[_ -]?(?:key|token)|access[_ -]?token|refresh[_ -]?token|id[_ -]?token|token|secret|password|passwd|pwd|credential)\b\s*([:=])\s*(?:"[^"]*"|'[^']*'|\S+)/giu, '$1$2[redacted]')
|
|
44
|
+
.replaceAll(/\b(?:sk|ghp|xoxb|xoxp|token)[-_][A-Za-z0-9_-]{16,}\b/gu, '[redacted]')
|
|
45
|
+
.replaceAll(/\b[A-Za-z0-9][A-Za-z0-9+/=_-]{39,}\b/gu, '[redacted]');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function textContent(message) {
|
|
49
|
+
if (!Array.isArray(message?.content)) return '';
|
|
50
|
+
return message.content
|
|
51
|
+
.filter((part) => part?.type === 'text' && typeof part.text === 'string')
|
|
52
|
+
.map((part) => part.text)
|
|
53
|
+
.join('\n')
|
|
54
|
+
.trim();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function eligibleMessage(message) {
|
|
58
|
+
if (message?.role === 'user') {
|
|
59
|
+
if (message.origin?.kind !== 'user') return undefined;
|
|
60
|
+
} else if (message?.role === 'assistant') {
|
|
61
|
+
if (message.origin !== undefined) return undefined;
|
|
62
|
+
if (!Array.isArray(message.toolCalls) || message.toolCalls.length > 0) return undefined;
|
|
63
|
+
} else {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const text = textContent(message);
|
|
67
|
+
if (text.length === 0) return undefined;
|
|
68
|
+
return { role: message.role, text: redactSecrets(text) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function modelMessage(role, text) {
|
|
72
|
+
return {
|
|
73
|
+
role,
|
|
74
|
+
content: [{ type: 'text', text }],
|
|
75
|
+
toolCalls: [],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function mergeAdjacent(messages) {
|
|
80
|
+
const merged = [];
|
|
81
|
+
for (const message of messages) {
|
|
82
|
+
const previous = merged.at(-1);
|
|
83
|
+
if (previous?.role === message.role) {
|
|
84
|
+
previous.content[0].text += `\n\n${message.content[0].text}`;
|
|
85
|
+
} else {
|
|
86
|
+
merged.push(modelMessage(message.role, message.content[0].text));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
while (merged[0]?.role === 'assistant') merged.shift();
|
|
90
|
+
return merged;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function emptyProjection() {
|
|
94
|
+
return {
|
|
95
|
+
messages: [],
|
|
96
|
+
sourceMessageCount: 0,
|
|
97
|
+
sourceChars: 0,
|
|
98
|
+
truncatedOlderContext: false,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function projectSubagentForkContext(history, options = {}) {
|
|
103
|
+
if (!Array.isArray(history)) throw new TypeError('history must be an array');
|
|
104
|
+
const maxMessages = boundedPositiveInteger(
|
|
105
|
+
options.maxMessages,
|
|
106
|
+
DEFAULT_MAX_MESSAGES,
|
|
107
|
+
MAX_CONFIGURED_MESSAGES,
|
|
108
|
+
'maxMessages',
|
|
109
|
+
);
|
|
110
|
+
const maxChars = boundedPositiveInteger(
|
|
111
|
+
options.maxChars,
|
|
112
|
+
DEFAULT_MAX_CHARS,
|
|
113
|
+
MAX_CONFIGURED_CHARS,
|
|
114
|
+
'maxChars',
|
|
115
|
+
);
|
|
116
|
+
const eligible = history.map(eligibleMessage).filter((message) => message !== undefined);
|
|
117
|
+
const newestUserIndex = eligible.findLastIndex((message) => message.role === 'user');
|
|
118
|
+
if (newestUserIndex < 0) return emptyProjection();
|
|
119
|
+
const throughCurrentUser = eligible.slice(0, newestUserIndex + 1);
|
|
120
|
+
const newestUser = throughCurrentUser.at(-1);
|
|
121
|
+
if (newestUser.text.length > maxChars) {
|
|
122
|
+
throw new Error(`newest direct user message exceeds the ${String(maxChars)}-character fork context limit`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const selected = [];
|
|
126
|
+
let sourceChars = 0;
|
|
127
|
+
let truncatedOlderContext = false;
|
|
128
|
+
for (let index = throughCurrentUser.length - 1; index >= 0; index -= 1) {
|
|
129
|
+
const message = throughCurrentUser[index];
|
|
130
|
+
if (selected.length >= maxMessages || sourceChars + message.text.length > maxChars) {
|
|
131
|
+
truncatedOlderContext = true;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
selected.push(modelMessage(message.role, message.text));
|
|
135
|
+
sourceChars += message.text.length;
|
|
136
|
+
}
|
|
137
|
+
selected.reverse();
|
|
138
|
+
const messages = mergeAdjacent(selected);
|
|
139
|
+
const retainedMessageCount = selected.length - selected.findIndex((message) => message.role === 'user');
|
|
140
|
+
return {
|
|
141
|
+
messages,
|
|
142
|
+
sourceMessageCount: retainedMessageCount,
|
|
143
|
+
sourceChars: messages.reduce((total, message) => total + message.content[0].text.replaceAll('\n\n', '').length, 0),
|
|
144
|
+
truncatedOlderContext,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
DEFAULT_MAX_CHARS,
|
|
150
|
+
DEFAULT_MAX_MESSAGES,
|
|
151
|
+
SUBAGENT_FORK_REMINDER,
|
|
152
|
+
projectSubagentForkContext,
|
|
153
|
+
redactSecrets,
|
|
154
|
+
resolveSubagentContextMode,
|
|
155
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createHash } = require('node:crypto');
|
|
4
|
+
|
|
5
|
+
const POLICY_VERSION = 1;
|
|
6
|
+
const MAX_SKILLS = 32;
|
|
7
|
+
const MAX_NAME_BYTES = 160;
|
|
8
|
+
const INVALID_CODE = 'SUBAGENT_SKILL_POLICY_INVALID';
|
|
9
|
+
const CHANGED_CODE = 'SUBAGENT_SKILL_POLICY_CHANGED';
|
|
10
|
+
|
|
11
|
+
class SubagentSkillPolicyError extends Error {
|
|
12
|
+
constructor(code, message) {
|
|
13
|
+
super(`${code}: ${message}`);
|
|
14
|
+
this.name = 'SubagentSkillPolicyError';
|
|
15
|
+
this.code = code;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeName(name) {
|
|
20
|
+
return name.toLowerCase();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function sha256(value) {
|
|
24
|
+
return `sha256:${createHash('sha256').update(value).digest('hex')}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function digestJson(value) {
|
|
28
|
+
return sha256(JSON.stringify(value));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isInlineSkill(skill) {
|
|
32
|
+
const type = skill?.metadata?.type;
|
|
33
|
+
return type === undefined || type === 'prompt' || type === 'inline';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function frozenSkill(skill) {
|
|
37
|
+
const metadata = Object.freeze({ ...(skill.metadata || {}) });
|
|
38
|
+
const plugin = skill.plugin === undefined
|
|
39
|
+
? undefined
|
|
40
|
+
: Object.freeze({ ...skill.plugin });
|
|
41
|
+
return Object.freeze({ ...skill, metadata, ...(plugin === undefined ? {} : { plugin }) });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function catalogEntry(skill) {
|
|
45
|
+
return {
|
|
46
|
+
name: normalizeName(skill.name),
|
|
47
|
+
source: skill.source ?? null,
|
|
48
|
+
path: skill.path ?? null,
|
|
49
|
+
metadata: {
|
|
50
|
+
type: skill.metadata.type ?? null,
|
|
51
|
+
disableModelInvocation: skill.metadata.disableModelInvocation === true,
|
|
52
|
+
isSubSkill: skill.metadata.isSubSkill === true,
|
|
53
|
+
},
|
|
54
|
+
plugin: skill.plugin === undefined
|
|
55
|
+
? null
|
|
56
|
+
: {
|
|
57
|
+
id: skill.plugin.id ?? null,
|
|
58
|
+
instructionsSha256: sha256(skill.plugin.instructions ?? ''),
|
|
59
|
+
},
|
|
60
|
+
contentSha256: sha256(skill.content ?? ''),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function defaultModelListing(skills) {
|
|
65
|
+
if (skills.length === 0) return '';
|
|
66
|
+
return [
|
|
67
|
+
'Only the profile-allowed skills are available to this subagent.',
|
|
68
|
+
...skills.map((skill) => `- ${skill.name}: ${skill.description ?? ''}`.trimEnd()),
|
|
69
|
+
].join('\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createRegistryView(selected, sourceRegistry, options) {
|
|
73
|
+
const ordered = Object.freeze([...selected].sort((a, b) => a.name.localeCompare(b.name)));
|
|
74
|
+
const byName = new Map(ordered.map((skill) => [normalizeName(skill.name), skill]));
|
|
75
|
+
const renderModelListing = options.renderModelListing ?? defaultModelListing;
|
|
76
|
+
|
|
77
|
+
return Object.freeze({
|
|
78
|
+
getSkill(name) {
|
|
79
|
+
return typeof name === 'string' ? byName.get(normalizeName(name)) : undefined;
|
|
80
|
+
},
|
|
81
|
+
listSkills() {
|
|
82
|
+
return [...ordered];
|
|
83
|
+
},
|
|
84
|
+
listInvocableSkills() {
|
|
85
|
+
return [...ordered];
|
|
86
|
+
},
|
|
87
|
+
getModelSkillListing() {
|
|
88
|
+
return renderModelListing([...ordered]);
|
|
89
|
+
},
|
|
90
|
+
renderSkillPrompt(skill, args) {
|
|
91
|
+
const expected = skill && typeof skill.name === 'string'
|
|
92
|
+
? byName.get(normalizeName(skill.name))
|
|
93
|
+
: undefined;
|
|
94
|
+
if (expected === undefined || expected !== skill) {
|
|
95
|
+
throw new SubagentSkillPolicyError(INVALID_CODE, 'skill object is outside the profile allowlist');
|
|
96
|
+
}
|
|
97
|
+
return sourceRegistry.renderSkillPrompt(skill, args);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function validateNames(rawNames) {
|
|
103
|
+
if (rawNames === undefined) return [];
|
|
104
|
+
if (!Array.isArray(rawNames)) {
|
|
105
|
+
throw new SubagentSkillPolicyError(INVALID_CODE, 'skills must be an array');
|
|
106
|
+
}
|
|
107
|
+
if (rawNames.length > MAX_SKILLS) {
|
|
108
|
+
throw new SubagentSkillPolicyError(INVALID_CODE, `skills exceeds the ${MAX_SKILLS}-entry limit`);
|
|
109
|
+
}
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
return rawNames.map((name) => {
|
|
112
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
113
|
+
throw new SubagentSkillPolicyError(INVALID_CODE, 'skill names must be non-empty strings');
|
|
114
|
+
}
|
|
115
|
+
if (Buffer.byteLength(name, 'utf8') > MAX_NAME_BYTES) {
|
|
116
|
+
throw new SubagentSkillPolicyError(INVALID_CODE, `skill name exceeds ${MAX_NAME_BYTES} UTF-8 bytes`);
|
|
117
|
+
}
|
|
118
|
+
const normalized = normalizeName(name);
|
|
119
|
+
if (seen.has(normalized)) {
|
|
120
|
+
throw new SubagentSkillPolicyError(INVALID_CODE, `duplicate skill name "${name}"`);
|
|
121
|
+
}
|
|
122
|
+
seen.add(normalized);
|
|
123
|
+
return { requested: name, normalized };
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function prepareSubagentSkillPolicy(rawNames, sourceRegistry, options = {}) {
|
|
128
|
+
if (sourceRegistry === undefined || sourceRegistry === null) {
|
|
129
|
+
throw new SubagentSkillPolicyError(INVALID_CODE, 'session skill registry is unavailable');
|
|
130
|
+
}
|
|
131
|
+
const requested = validateNames(rawNames);
|
|
132
|
+
const selected = requested.map(({ requested: name, normalized }) => {
|
|
133
|
+
const skill = sourceRegistry.getSkill(name);
|
|
134
|
+
if (skill === undefined || normalizeName(skill.name) !== normalized) {
|
|
135
|
+
throw new SubagentSkillPolicyError(INVALID_CODE, `skill "${name}" is not registered`);
|
|
136
|
+
}
|
|
137
|
+
if (skill.metadata?.disableModelInvocation === true) {
|
|
138
|
+
throw new SubagentSkillPolicyError(INVALID_CODE, `skill "${name}" disables model invocation`);
|
|
139
|
+
}
|
|
140
|
+
if (!isInlineSkill(skill)) {
|
|
141
|
+
throw new SubagentSkillPolicyError(INVALID_CODE, `skill "${name}" is not inline`);
|
|
142
|
+
}
|
|
143
|
+
return frozenSkill(skill);
|
|
144
|
+
});
|
|
145
|
+
const normalizedNames = Object.freeze(requested.map((entry) => entry.normalized));
|
|
146
|
+
const metadata = Object.freeze({
|
|
147
|
+
version: POLICY_VERSION,
|
|
148
|
+
names: normalizedNames,
|
|
149
|
+
profileDigest: digestJson(normalizedNames),
|
|
150
|
+
catalogDigest: digestJson(selected.map(catalogEntry)),
|
|
151
|
+
});
|
|
152
|
+
const enabled = selected.length > 0;
|
|
153
|
+
return Object.freeze({
|
|
154
|
+
enabled,
|
|
155
|
+
registry: enabled ? createRegistryView(selected, sourceRegistry, options) : null,
|
|
156
|
+
metadata,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function validPersistedPolicy(policy) {
|
|
161
|
+
return policy !== null
|
|
162
|
+
&& typeof policy === 'object'
|
|
163
|
+
&& policy.version === POLICY_VERSION
|
|
164
|
+
&& Array.isArray(policy.names)
|
|
165
|
+
&& typeof policy.profileDigest === 'string'
|
|
166
|
+
&& typeof policy.catalogDigest === 'string';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function restoreSubagentSkillPolicy(rawNames, persistedPolicy, sourceRegistry, options = {}) {
|
|
170
|
+
if (!validPersistedPolicy(persistedPolicy)) {
|
|
171
|
+
throw new SubagentSkillPolicyError(CHANGED_CODE, 'persisted skill policy is missing or invalid');
|
|
172
|
+
}
|
|
173
|
+
let prepared;
|
|
174
|
+
try {
|
|
175
|
+
prepared = prepareSubagentSkillPolicy(rawNames, sourceRegistry, options);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
throw new SubagentSkillPolicyError(CHANGED_CODE, error instanceof Error ? error.message : String(error));
|
|
178
|
+
}
|
|
179
|
+
if (JSON.stringify(prepared.metadata.names) !== JSON.stringify(persistedPolicy.names)
|
|
180
|
+
|| prepared.metadata.profileDigest !== persistedPolicy.profileDigest
|
|
181
|
+
|| prepared.metadata.catalogDigest !== persistedPolicy.catalogDigest) {
|
|
182
|
+
throw new SubagentSkillPolicyError(CHANGED_CODE, 'profile or selected skill catalog changed; spawn a new subagent');
|
|
183
|
+
}
|
|
184
|
+
return prepared;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function profileWithSubagentSkillTools(profile, policy) {
|
|
188
|
+
const withoutSkill = profile.tools.filter((name) => name !== 'Skill');
|
|
189
|
+
const tools = policy.enabled ? [...withoutSkill, 'Skill'] : withoutSkill;
|
|
190
|
+
return Object.freeze({ ...profile, tools: Object.freeze(tools) });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
CHANGED_CODE,
|
|
195
|
+
INVALID_CODE,
|
|
196
|
+
MAX_NAME_BYTES,
|
|
197
|
+
MAX_SKILLS,
|
|
198
|
+
POLICY_VERSION,
|
|
199
|
+
SubagentSkillPolicyError,
|
|
200
|
+
prepareSubagentSkillPolicy,
|
|
201
|
+
profileWithSubagentSkillTools,
|
|
202
|
+
restoreSubagentSkillPolicy,
|
|
203
|
+
};
|
|
204
|
+
|
|
@@ -168,11 +168,12 @@ function beginTelegramApprovalRelay(payload, options = {}) {
|
|
|
168
168
|
if (settled) return false;
|
|
169
169
|
settled = true;
|
|
170
170
|
if (interval !== undefined) clearInterval(interval);
|
|
171
|
+
const persistedResponse = response?.decision === 'edited' ? { decision: 'edited' } : response;
|
|
171
172
|
atomicWriteJson(resolvedFile, {
|
|
172
173
|
v: RELAY_VERSION,
|
|
173
174
|
requestId: request.requestId,
|
|
174
175
|
resolvedAt: Date.now(),
|
|
175
|
-
response,
|
|
176
|
+
response: persistedResponse,
|
|
176
177
|
});
|
|
177
178
|
resolvePromise(response);
|
|
178
179
|
return true;
|