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.
Files changed (117) hide show
  1. package/LIESMICH.txt +13 -869
  2. package/README.md +41 -833
  3. package/bin/assistant-message-offload-policy.cjs +3 -1
  4. package/bin/context-performance-policy.cjs +2 -5
  5. package/bin/context-pressure-policy.cjs +20 -0
  6. package/bin/cron-run-output.cjs +45 -0
  7. package/bin/cron-run-store.cjs +145 -0
  8. package/bin/durable-task-resume-policy.cjs +130 -0
  9. package/bin/durable-task-resume-runtime.cjs +117 -0
  10. package/bin/durable-task-resume-store.cjs +88 -0
  11. package/bin/editable-tool-approval-policy.cjs +540 -0
  12. package/bin/editable-tool-approval-runtime.cjs +99 -0
  13. package/bin/html-to-research-markdown.cjs +146 -0
  14. package/bin/programmatic-tool-runtime.mjs +330 -4
  15. package/bin/read-continuation-policy.cjs +36 -5
  16. package/bin/scoped-cron-run-policy.cjs +358 -0
  17. package/bin/startup-preferences.cjs +3 -3
  18. package/bin/structured-agent-swarm-output.cjs +325 -0
  19. package/bin/subagent-context-fork-policy.cjs +155 -0
  20. package/bin/subagent-skill-policy.cjs +204 -0
  21. package/bin/telegram-approval-relay.cjs +2 -1
  22. package/bin/tool-result-offload-policy.cjs +14 -1
  23. package/bin/update-notice.js +14 -18
  24. package/bin/user-message-offload-policy.cjs +3 -1
  25. package/blun.mjs +1089 -477
  26. package/codebase-index/README.md +12 -0
  27. package/codebase-index/codebase_index.py +129 -18
  28. package/package.json +23 -58
  29. package/telegram-plugin/bin/telegram-typing-keepalive.cjs +89 -0
  30. package/telegram-plugin/dist/bridge.mjs +8 -1
  31. package/CHANGELOG.md +0 -321
  32. package/agent-spine-plugin/CHANGELOG.md +0 -406
  33. package/agent-spine-plugin/CONTRIBUTING.md +0 -52
  34. package/agent-spine-plugin/README.md +0 -344
  35. package/agent-spine-plugin/SECURITY.md +0 -47
  36. package/agent-spine-plugin/docs/acceptance.md +0 -61
  37. package/agent-spine-plugin/docs/architecture.md +0 -183
  38. package/agent-spine-plugin/docs/attention.md +0 -121
  39. package/agent-spine-plugin/docs/automatic-continuity.md +0 -79
  40. package/agent-spine-plugin/docs/channel-runtime.md +0 -92
  41. package/agent-spine-plugin/docs/coordination.md +0 -138
  42. package/agent-spine-plugin/docs/feed-transport.md +0 -99
  43. package/agent-spine-plugin/docs/gateway-runtime.md +0 -116
  44. package/agent-spine-plugin/docs/harness-reference.md +0 -45
  45. package/agent-spine-plugin/docs/host-integration.md +0 -129
  46. package/agent-spine-plugin/docs/https-transport.md +0 -116
  47. package/agent-spine-plugin/docs/learning.md +0 -133
  48. package/agent-spine-plugin/docs/object-transport.md +0 -93
  49. package/agent-spine-plugin/docs/peer-transport.md +0 -88
  50. package/agent-spine-plugin/docs/preflight-recall.md +0 -69
  51. package/agent-spine-plugin/docs/preservation-contract.md +0 -53
  52. package/agent-spine-plugin/docs/quality-gates.md +0 -50
  53. package/agent-spine-plugin/docs/relationships.md +0 -73
  54. package/agent-spine-plugin/docs/releasing.md +0 -83
  55. package/agent-spine-plugin/docs/roadmap.md +0 -307
  56. package/agent-spine-plugin/docs/selfstarter.md +0 -88
  57. package/agent-spine-plugin/docs/session-briefing.md +0 -74
  58. package/agent-spine-plugin/docs/shared-memory.md +0 -259
  59. package/agent-spine-plugin/docs/source-roots.md +0 -86
  60. package/agent-spine-plugin/docs/sqlite-transport.md +0 -76
  61. package/agent-spine-plugin/scripts/check-hosts.js +0 -195
  62. package/agent-spine-plugin/scripts/check-install.js +0 -569
  63. package/agent-spine-plugin/scripts/check-syntax.js +0 -29
  64. package/agent-spine-plugin/scripts/github-actions.js +0 -11
  65. package/agent-spine-plugin/scripts/release-check.js +0 -128
  66. package/agent-spine-plugin/scripts/run-acceptance.js +0 -19
  67. package/agent-spine-plugin/scripts/run-checks.js +0 -46
  68. package/agent-spine-plugin/scripts/run-tests-hermetic.js +0 -73
  69. package/agent-spine-plugin/spine-example/1-identity.md +0 -12
  70. package/agent-spine-plugin/spine-example/2-voice.md +0 -6
  71. package/agent-spine-plugin/spine-example/3-conduct.md +0 -8
  72. package/agent-spine-plugin/spine-example/4-history.md +0 -4
  73. package/bin/fredrik-glm-provider.cjs +0 -256
  74. package/bin/package-regression-policy.cjs +0 -77
  75. package/fredrik-glm-profile.toml.example +0 -26
  76. package/release-planned-removals.json +0 -15
  77. package/scripts/check-active-profile-plugin-startup.js +0 -36
  78. package/scripts/check-active-work-steer-regression.js +0 -46
  79. package/scripts/check-approval-observability-regression.js +0 -111
  80. package/scripts/check-approval-queue-shortcuts-regression.js +0 -65
  81. package/scripts/check-bundled-agent-spine-regression.js +0 -48
  82. package/scripts/check-codebase-search-packaging-regression.js +0 -92
  83. package/scripts/check-copy-command-regression.js +0 -74
  84. package/scripts/check-current-turn-read-pin-mutation-regression.js +0 -72
  85. package/scripts/check-current-turn-read-pin-regression.js +0 -94
  86. package/scripts/check-deepseek-native-max-regression.js +0 -49
  87. package/scripts/check-empty-response-effort-downgrade-regression.js +0 -48
  88. package/scripts/check-fredrik-glm-mutation-regression.js +0 -18
  89. package/scripts/check-fredrik-glm-regression.js +0 -169
  90. package/scripts/check-historical-tool-result-preview-regression.js +0 -77
  91. package/scripts/check-history-pressure-offload-regression.js +0 -77
  92. package/scripts/check-mcp-startup-wait-budget.js +0 -48
  93. package/scripts/check-package-regression.js +0 -38
  94. package/scripts/check-plugin-startup-regression.js +0 -53
  95. package/scripts/check-programmatic-context-isolation-regression.js +0 -193
  96. package/scripts/check-programmatic-tool-regression.js +0 -294
  97. package/scripts/check-queue-controls-regression.js +0 -189
  98. package/scripts/check-release-metadata.js +0 -103
  99. package/scripts/check-reload-agent-spine-regression.js +0 -76
  100. package/scripts/check-resume-replay-regression.js +0 -102
  101. package/scripts/check-session-cancel-regression.js +0 -43
  102. package/scripts/check-session-picker-resume-metrics-regression.js +0 -97
  103. package/scripts/check-session-start-hook-context-regression.js +0 -228
  104. package/scripts/check-shell-terminal-isolation-regression.js +0 -81
  105. package/scripts/check-slash-escape-regression.js +0 -89
  106. package/scripts/check-startup-swarm-command-regression.js +0 -24
  107. package/scripts/check-structured-subagent-output-regression.js +0 -331
  108. package/scripts/check-telegram-bridge-watchdog.js +0 -60
  109. package/scripts/check-telegram-direct-work-resume-regression.js +0 -53
  110. package/scripts/check-telegram-loop-exactly-once-regression.js +0 -71
  111. package/scripts/check-todo-loop-regression.js +0 -78
  112. package/scripts/check-todo-progress-regression.js +0 -416
  113. package/scripts/check-todo-recovery-catalog-regression.js +0 -50
  114. package/scripts/check-tool-schema-capacity-regression.js +0 -40
  115. package/scripts/programmatic-tool-runtime.test.mjs +0 -365
  116. package/scripts/structured-subagent-output.test.cjs +0 -170
  117. /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;