blun-king-cli 9.1.536 → 9.1.561

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 (129) 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/compaction-transaction-policy.cjs +122 -0
  5. package/bin/context-performance-policy.cjs +2 -5
  6. package/bin/context-pressure-policy.cjs +20 -0
  7. package/bin/cron-run-output.cjs +45 -0
  8. package/bin/cron-run-store.cjs +145 -0
  9. package/bin/default-model-output-budget-policy.cjs +28 -0
  10. package/bin/durable-task-resume-policy.cjs +130 -0
  11. package/bin/durable-task-resume-runtime.cjs +117 -0
  12. package/bin/durable-task-resume-store.cjs +88 -0
  13. package/bin/editable-tool-approval-policy.cjs +540 -0
  14. package/bin/editable-tool-approval-runtime.cjs +99 -0
  15. package/bin/file-observation-policy.cjs +133 -0
  16. package/bin/html-to-research-markdown.cjs +146 -0
  17. package/bin/launcher-runtime.js +0 -1
  18. package/bin/micro-compaction-policy.cjs +64 -0
  19. package/bin/mnemo-connect-heartbeat.cjs +1 -3
  20. package/bin/programmatic-tool-runtime.mjs +330 -4
  21. package/bin/read-continuation-policy.cjs +36 -5
  22. package/bin/retry-checkpoint-policy.cjs +13 -0
  23. package/bin/scoped-cron-run-policy.cjs +358 -0
  24. package/bin/session-checkpoint-policy.cjs +25 -0
  25. package/bin/startup-preferences.cjs +4 -3
  26. package/bin/structured-agent-swarm-output.cjs +325 -0
  27. package/bin/subagent-context-fork-policy.cjs +155 -0
  28. package/bin/subagent-skill-policy.cjs +204 -0
  29. package/bin/telegram-approval-relay.cjs +2 -1
  30. package/bin/tool-file-persistence.cjs +141 -0
  31. package/bin/tool-result-offload-policy.cjs +25 -33
  32. package/bin/turn-thinking-policy.cjs +2 -26
  33. package/bin/update-notice.js +30 -18
  34. package/bin/user-message-offload-policy.cjs +3 -1
  35. package/blun.mjs +1564 -640
  36. package/codebase-index/README.md +12 -0
  37. package/codebase-index/codebase_index.py +129 -18
  38. package/package.json +23 -58
  39. package/telegram-plugin/bin/telegram-mnemo-capture.cjs +1 -3
  40. package/telegram-plugin/bin/telegram-typing-keepalive.cjs +89 -0
  41. package/telegram-plugin/dist/bridge.mjs +8 -1
  42. package/CHANGELOG.md +0 -321
  43. package/agent-spine-plugin/CHANGELOG.md +0 -406
  44. package/agent-spine-plugin/CONTRIBUTING.md +0 -52
  45. package/agent-spine-plugin/README.md +0 -344
  46. package/agent-spine-plugin/SECURITY.md +0 -47
  47. package/agent-spine-plugin/docs/acceptance.md +0 -61
  48. package/agent-spine-plugin/docs/architecture.md +0 -183
  49. package/agent-spine-plugin/docs/attention.md +0 -121
  50. package/agent-spine-plugin/docs/automatic-continuity.md +0 -79
  51. package/agent-spine-plugin/docs/channel-runtime.md +0 -92
  52. package/agent-spine-plugin/docs/coordination.md +0 -138
  53. package/agent-spine-plugin/docs/feed-transport.md +0 -99
  54. package/agent-spine-plugin/docs/gateway-runtime.md +0 -116
  55. package/agent-spine-plugin/docs/harness-reference.md +0 -45
  56. package/agent-spine-plugin/docs/host-integration.md +0 -129
  57. package/agent-spine-plugin/docs/https-transport.md +0 -116
  58. package/agent-spine-plugin/docs/learning.md +0 -133
  59. package/agent-spine-plugin/docs/object-transport.md +0 -93
  60. package/agent-spine-plugin/docs/peer-transport.md +0 -88
  61. package/agent-spine-plugin/docs/preflight-recall.md +0 -69
  62. package/agent-spine-plugin/docs/preservation-contract.md +0 -53
  63. package/agent-spine-plugin/docs/quality-gates.md +0 -50
  64. package/agent-spine-plugin/docs/relationships.md +0 -73
  65. package/agent-spine-plugin/docs/releasing.md +0 -83
  66. package/agent-spine-plugin/docs/roadmap.md +0 -307
  67. package/agent-spine-plugin/docs/selfstarter.md +0 -88
  68. package/agent-spine-plugin/docs/session-briefing.md +0 -74
  69. package/agent-spine-plugin/docs/shared-memory.md +0 -259
  70. package/agent-spine-plugin/docs/source-roots.md +0 -86
  71. package/agent-spine-plugin/docs/sqlite-transport.md +0 -76
  72. package/agent-spine-plugin/scripts/check-hosts.js +0 -195
  73. package/agent-spine-plugin/scripts/check-install.js +0 -569
  74. package/agent-spine-plugin/scripts/check-syntax.js +0 -29
  75. package/agent-spine-plugin/scripts/github-actions.js +0 -11
  76. package/agent-spine-plugin/scripts/release-check.js +0 -128
  77. package/agent-spine-plugin/scripts/run-acceptance.js +0 -19
  78. package/agent-spine-plugin/scripts/run-checks.js +0 -46
  79. package/agent-spine-plugin/scripts/run-tests-hermetic.js +0 -73
  80. package/agent-spine-plugin/spine-example/1-identity.md +0 -12
  81. package/agent-spine-plugin/spine-example/2-voice.md +0 -6
  82. package/agent-spine-plugin/spine-example/3-conduct.md +0 -8
  83. package/agent-spine-plugin/spine-example/4-history.md +0 -4
  84. package/bin/empty-response-retry-policy.cjs +0 -29
  85. package/bin/fredrik-glm-provider.cjs +0 -256
  86. package/bin/package-regression-policy.cjs +0 -77
  87. package/fredrik-glm-profile.toml.example +0 -26
  88. package/release-planned-removals.json +0 -15
  89. package/scripts/check-active-profile-plugin-startup.js +0 -36
  90. package/scripts/check-active-work-steer-regression.js +0 -46
  91. package/scripts/check-approval-observability-regression.js +0 -111
  92. package/scripts/check-approval-queue-shortcuts-regression.js +0 -65
  93. package/scripts/check-bundled-agent-spine-regression.js +0 -48
  94. package/scripts/check-codebase-search-packaging-regression.js +0 -92
  95. package/scripts/check-copy-command-regression.js +0 -74
  96. package/scripts/check-current-turn-read-pin-mutation-regression.js +0 -72
  97. package/scripts/check-current-turn-read-pin-regression.js +0 -94
  98. package/scripts/check-deepseek-native-max-regression.js +0 -49
  99. package/scripts/check-empty-response-effort-downgrade-regression.js +0 -48
  100. package/scripts/check-fredrik-glm-mutation-regression.js +0 -18
  101. package/scripts/check-fredrik-glm-regression.js +0 -169
  102. package/scripts/check-historical-tool-result-preview-regression.js +0 -77
  103. package/scripts/check-history-pressure-offload-regression.js +0 -77
  104. package/scripts/check-mcp-startup-wait-budget.js +0 -48
  105. package/scripts/check-package-regression.js +0 -38
  106. package/scripts/check-plugin-startup-regression.js +0 -53
  107. package/scripts/check-programmatic-context-isolation-regression.js +0 -193
  108. package/scripts/check-programmatic-tool-regression.js +0 -294
  109. package/scripts/check-queue-controls-regression.js +0 -189
  110. package/scripts/check-release-metadata.js +0 -103
  111. package/scripts/check-reload-agent-spine-regression.js +0 -76
  112. package/scripts/check-resume-replay-regression.js +0 -102
  113. package/scripts/check-session-cancel-regression.js +0 -43
  114. package/scripts/check-session-picker-resume-metrics-regression.js +0 -97
  115. package/scripts/check-session-start-hook-context-regression.js +0 -228
  116. package/scripts/check-shell-terminal-isolation-regression.js +0 -81
  117. package/scripts/check-slash-escape-regression.js +0 -89
  118. package/scripts/check-startup-swarm-command-regression.js +0 -24
  119. package/scripts/check-structured-subagent-output-regression.js +0 -331
  120. package/scripts/check-telegram-bridge-watchdog.js +0 -60
  121. package/scripts/check-telegram-direct-work-resume-regression.js +0 -53
  122. package/scripts/check-telegram-loop-exactly-once-regression.js +0 -71
  123. package/scripts/check-todo-loop-regression.js +0 -78
  124. package/scripts/check-todo-progress-regression.js +0 -416
  125. package/scripts/check-todo-recovery-catalog-regression.js +0 -50
  126. package/scripts/check-tool-schema-capacity-regression.js +0 -40
  127. package/scripts/programmatic-tool-runtime.test.mjs +0 -365
  128. package/scripts/structured-subagent-output.test.cjs +0 -170
  129. /package/{scripts → bin}/fix-node-pty-perms.js +0 -0
@@ -0,0 +1,358 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const path = require('node:path');
5
+
6
+ const MAX_ALLOWED_TOOLS = 64;
7
+ const MAX_TOOL_NAME_BYTES = 160;
8
+ const MAX_RECEIPT_TOOL_NAMES = 64;
9
+ const MAX_RECEIPT_OUTPUTS = 32;
10
+ const MAX_LIST_LIMIT = 100;
11
+ const TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled', 'lost']);
12
+ const ALL_STATUSES = new Set(['buffered', 'running', ...TERMINAL_STATUSES]);
13
+ const TRANSITIONS = new Map([
14
+ ['buffered', new Set(['running', 'failed', 'cancelled', 'lost'])],
15
+ ['running', new Set(['completed', 'failed', 'cancelled', 'lost'])],
16
+ ]);
17
+ const ERROR_CATEGORIES = new Set(['executor', 'provider', 'policy', 'unknown']);
18
+
19
+ class ScopedCronRunError extends TypeError {
20
+ constructor(code, message) {
21
+ super(message);
22
+ this.name = 'ScopedCronRunError';
23
+ this.code = code;
24
+ }
25
+ }
26
+
27
+ function fail(code, message) {
28
+ throw new ScopedCronRunError(code, message);
29
+ }
30
+
31
+ function clone(value) {
32
+ return JSON.parse(JSON.stringify(value));
33
+ }
34
+
35
+ function deepFreeze(value) {
36
+ if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
37
+ for (const child of Object.values(value)) deepFreeze(child);
38
+ return Object.freeze(value);
39
+ }
40
+
41
+ function assertOpaqueId(value, field) {
42
+ if (typeof value !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value)) {
43
+ fail('CRON_RUN_INVALID_ID', `${field} must be a bounded opaque identifier`);
44
+ }
45
+ return value;
46
+ }
47
+
48
+ function canonicalToolName(value, field) {
49
+ if (
50
+ typeof value !== 'string'
51
+ || value.length === 0
52
+ || Buffer.byteLength(value, 'utf8') > MAX_TOOL_NAME_BYTES
53
+ || /[*?[\]{}]/u.test(value)
54
+ || !/^[A-Za-z0-9][A-Za-z0-9._:/-]*$/u.test(value)
55
+ ) {
56
+ fail('CRON_SCOPE_INVALID_TOOL', `${field} must be an exact canonical tool name`);
57
+ }
58
+ return value;
59
+ }
60
+
61
+ function canonicalToolNames(tools, field = 'tools') {
62
+ if (!Array.isArray(tools)) fail('CRON_SCOPE_INVALID_CATALOG', `${field} must be an array`);
63
+ const names = tools.map((entry) => canonicalToolName(
64
+ typeof entry === 'string' ? entry : entry && entry.name,
65
+ field,
66
+ ));
67
+ return [...new Set(names)];
68
+ }
69
+
70
+ function cronToolScopeDigest(names) {
71
+ return `sha256:${crypto.createHash('sha256').update(JSON.stringify(names)).digest('hex')}`;
72
+ }
73
+
74
+ function normalizeCronAllowedTools(input, effectiveTools) {
75
+ if (input === undefined) return Object.freeze({mode: 'legacy', names: null, digest: null});
76
+ if (!Array.isArray(input)) fail('CRON_SCOPE_NOT_ARRAY', 'allowed_tools must be an array');
77
+ if (input.length > MAX_ALLOWED_TOOLS) {
78
+ fail('CRON_SCOPE_TOO_MANY_TOOLS', `allowed_tools supports at most ${MAX_ALLOWED_TOOLS} entries`);
79
+ }
80
+ const catalog = new Set(canonicalToolNames(effectiveTools, 'effective tools'));
81
+ const names = [];
82
+ const seen = new Set();
83
+ for (const rawName of input) {
84
+ const name = canonicalToolName(rawName, 'allowed_tools entry');
85
+ if (!catalog.has(name)) fail('CRON_SCOPE_UNKNOWN_TOOL', `Unknown current tool: ${name}`);
86
+ if (seen.has(name)) continue;
87
+ seen.add(name);
88
+ names.push(name);
89
+ }
90
+ return deepFreeze({mode: 'scoped', names, digest: cronToolScopeDigest(names)});
91
+ }
92
+
93
+ function cronToolScopeForTask(task) {
94
+ if (!task || task.allowedTools === undefined) {
95
+ return Object.freeze({mode: 'legacy', names: null, digest: null});
96
+ }
97
+ if (!Array.isArray(task.allowedTools)) fail('CRON_SCOPE_INVALID_TASK', 'Persisted allowedTools must be an array');
98
+ const names = canonicalToolNames(task.allowedTools, 'persisted allowedTools');
99
+ const digest = cronToolScopeDigest(names);
100
+ if (task.toolScopeDigest !== digest) fail('CRON_SCOPE_DIGEST_MISMATCH', 'Persisted tool scope digest does not match');
101
+ return deepFreeze({mode: 'scoped', names, digest});
102
+ }
103
+
104
+ function isCronToolScope(value) {
105
+ if (!value || value.mode !== 'scoped' || !Array.isArray(value.names)) return false;
106
+ try {
107
+ const names = canonicalToolNames(value.names, 'tool scope');
108
+ return names.length === value.names.length && value.digest === cronToolScopeDigest(names);
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+
114
+ function filterToolsForCronScope(tools, scope) {
115
+ if (!Array.isArray(tools)) fail('CRON_SCOPE_INVALID_CATALOG', 'tools must be an array');
116
+ if (!scope || scope.mode === 'legacy') return tools;
117
+ if (!isCronToolScope(scope)) fail('CRON_SCOPE_INVALID_ORIGIN', 'Invalid cron tool scope');
118
+ const allowed = new Set(scope.names);
119
+ return tools.filter((tool) => tool && allowed.has(tool.name));
120
+ }
121
+
122
+ function inheritedCronToolScope(parentScope) {
123
+ if (!parentScope || parentScope.mode === 'legacy') return undefined;
124
+ if (!isCronToolScope(parentScope)) fail('CRON_SCOPE_INVALID_ORIGIN', 'Invalid inherited cron tool scope');
125
+ return deepFreeze({mode: 'scoped', names: [...parentScope.names], digest: parentScope.digest});
126
+ }
127
+
128
+ function isoTimestamp(value, field) {
129
+ if (typeof value !== 'string' || !Number.isFinite(Date.parse(value))) {
130
+ fail('CRON_RUN_INVALID_TIME', `${field} must be an ISO timestamp`);
131
+ }
132
+ return value;
133
+ }
134
+
135
+ function createCronRunReceipt(input) {
136
+ const digest = input.toolScopeDigest;
137
+ if (digest !== null && (typeof digest !== 'string' || !/^sha256:[a-f0-9]{64}$/u.test(digest))) {
138
+ fail('CRON_RUN_INVALID_DIGEST', 'toolScopeDigest is invalid');
139
+ }
140
+ if (!Number.isSafeInteger(input.coalescedCount) || input.coalescedCount < 1) {
141
+ fail('CRON_RUN_INVALID_COALESCE', 'coalescedCount must be a positive integer');
142
+ }
143
+ return deepFreeze({
144
+ runId: assertOpaqueId(input.runId, 'runId'),
145
+ jobId: assertOpaqueId(input.jobId, 'jobId'),
146
+ jobDeleted: false,
147
+ cron: String(input.cron),
148
+ recurring: input.recurring === true,
149
+ stale: input.stale === true,
150
+ coalescedCount: input.coalescedCount,
151
+ toolScopeDigest: digest,
152
+ status: 'buffered',
153
+ firedAt: isoTimestamp(input.firedAt, 'firedAt'),
154
+ startedAt: null,
155
+ endedAt: null,
156
+ turnId: null,
157
+ toolSummary: {calls: 0, errors: 0, names: [], excessNames: 0},
158
+ outputs: [],
159
+ finalMessageRef: null,
160
+ });
161
+ }
162
+
163
+ function summarizeCronToolCalls(toolCalls) {
164
+ if (!Array.isArray(toolCalls)) fail('CRON_RUN_INVALID_TOOLS', 'toolCalls must be an array');
165
+ const names = new Set();
166
+ let errors = 0;
167
+ for (const call of toolCalls) {
168
+ canonicalToolName(call && call.name, 'tool call name');
169
+ names.add(call.name);
170
+ if (call.isError === true) errors += 1;
171
+ }
172
+ const sorted = [...names].sort((left, right) => left.localeCompare(right));
173
+ return deepFreeze({
174
+ calls: toolCalls.length,
175
+ errors,
176
+ names: sorted.slice(0, MAX_RECEIPT_TOOL_NAMES),
177
+ excessNames: Math.max(0, sorted.length - MAX_RECEIPT_TOOL_NAMES),
178
+ });
179
+ }
180
+
181
+ function normalizeRelativeFilePath(value) {
182
+ if (typeof value !== 'string' || value.length === 0 || value.includes('\0')) return null;
183
+ const normalized = value.replaceAll('\\', '/');
184
+ if (normalized.startsWith('/') || /^[A-Za-z]:\//u.test(normalized) || normalized.startsWith('//')) return null;
185
+ const posix = path.posix.normalize(normalized);
186
+ if (posix === '.' || posix === '..' || posix.startsWith('../')) return null;
187
+ return posix;
188
+ }
189
+
190
+ function rootRelativeFile(absolutePath, root, rootRef, pathApi) {
191
+ const relative = pathApi.relative(pathApi.resolve(root), pathApi.resolve(absolutePath));
192
+ if (relative === '' || relative === '..' || relative.startsWith(`..${pathApi.sep}`) || pathApi.isAbsolute(relative)) return null;
193
+ const relativePath = normalizeRelativeFilePath(relative);
194
+ return relativePath === null ? null : {rootRef, relativePath};
195
+ }
196
+
197
+ function buildCronFileCandidate({toolName, toolCallId, display, workspaceRoot, additionalRoots = [], pathExists}) {
198
+ if (!['Write', 'Edit'].includes(toolName)) return null;
199
+ if (!display || display.kind !== 'file_io' || typeof display.path !== 'string') return null;
200
+ assertOpaqueId(toolCallId, 'toolCallId');
201
+ const pathApi = process.platform === 'win32' || /^[A-Za-z]:[\\/]/u.test(display.path) ? path.win32 : path;
202
+ let located = rootRelativeFile(display.path, workspaceRoot, 'root:workspace', pathApi);
203
+ if (located === null) {
204
+ for (let index = 0; index < additionalRoots.length; index += 1) {
205
+ located = rootRelativeFile(display.path, additionalRoots[index], `root:additional:${String(index + 1)}`, pathApi);
206
+ if (located !== null) break;
207
+ }
208
+ }
209
+ if (located === null) return null;
210
+ return deepFreeze({
211
+ ...located,
212
+ change: toolName === 'Edit' || pathExists === true ? 'modified' : 'created',
213
+ toolCallId,
214
+ kind: 'file',
215
+ });
216
+ }
217
+
218
+ function normalizeCronOutputs(outputs) {
219
+ if (!Array.isArray(outputs)) fail('CRON_RUN_INVALID_OUTPUTS', 'outputs must be an array');
220
+ const safe = [];
221
+ for (const output of outputs) {
222
+ if (safe.length >= MAX_RECEIPT_OUTPUTS) break;
223
+ if (!output || output.kind !== 'file') continue;
224
+ if (!/^root:(workspace|additional:[1-9][0-9]*)$/u.test(output.rootRef)) continue;
225
+ const relativePath = normalizeRelativeFilePath(output.relativePath);
226
+ if (relativePath === null || !['created', 'modified'].includes(output.change)) continue;
227
+ safe.push({
228
+ rootRef: output.rootRef,
229
+ relativePath,
230
+ change: output.change,
231
+ toolCallId: assertOpaqueId(output.toolCallId, 'toolCallId'),
232
+ kind: 'file',
233
+ });
234
+ }
235
+ return deepFreeze(safe);
236
+ }
237
+
238
+ function safeCronRunError(status, error) {
239
+ if (status !== 'failed') return undefined;
240
+ const category = error && ERROR_CATEGORIES.has(error.category) ? error.category : 'unknown';
241
+ const rawCode = error && typeof error.code === 'string' ? error.code : 'unspecified';
242
+ const code = /^[A-Za-z0-9._-]{1,64}$/u.test(rawCode) ? rawCode : 'redacted';
243
+ return {category, code};
244
+ }
245
+
246
+ function transitionCronRunReceipt(receipt, status, details = {}) {
247
+ const allowed = receipt && TRANSITIONS.get(receipt.status);
248
+ if (!allowed || !allowed.has(status)) {
249
+ fail('CRON_RUN_INVALID_TRANSITION', `Invalid cron run transition: ${String(receipt && receipt.status)} -> ${status}`);
250
+ }
251
+ if (status === 'running') {
252
+ if (!Number.isSafeInteger(details.turnId) || details.turnId < 0) {
253
+ fail('CRON_RUN_INVALID_TURN', 'turnId must be a non-negative integer');
254
+ }
255
+ return deepFreeze({
256
+ ...clone(receipt),
257
+ status,
258
+ startedAt: isoTimestamp(details.startedAt, 'startedAt'),
259
+ turnId: details.turnId,
260
+ });
261
+ }
262
+ const terminal = {
263
+ ...clone(receipt),
264
+ status,
265
+ endedAt: isoTimestamp(details.endedAt, 'endedAt'),
266
+ toolSummary: summarizeCronToolCalls(details.toolCalls || []),
267
+ outputs: normalizeCronOutputs(details.outputs || []),
268
+ finalMessageRef: typeof details.finalStepId === 'string' && details.finalStepId.length > 0
269
+ ? {stepId: assertOpaqueId(details.finalStepId, 'finalStepId')}
270
+ : null,
271
+ };
272
+ const error = safeCronRunError(status, details.error);
273
+ if (error !== undefined) terminal.error = error;
274
+ return deepFreeze(terminal);
275
+ }
276
+
277
+ function markCronJobDeleted(receipt) {
278
+ return deepFreeze({...clone(receipt), jobDeleted: true});
279
+ }
280
+
281
+ function isValidCronRunReceipt(value) {
282
+ if (!value || typeof value !== 'object') return false;
283
+ try {
284
+ assertOpaqueId(value.runId, 'runId');
285
+ assertOpaqueId(value.jobId, 'jobId');
286
+ isoTimestamp(value.firedAt, 'firedAt');
287
+ if (!ALL_STATUSES.has(value.status)) return false;
288
+ if (!Array.isArray(value.outputs) || !value.toolSummary || typeof value.toolSummary.calls !== 'number') return false;
289
+ if (value.status === 'running' && (!Number.isSafeInteger(value.turnId) || value.startedAt === null)) return false;
290
+ if (TERMINAL_STATUSES.has(value.status) && value.endedAt === null) return false;
291
+ return assertCronRunReceiptPrivacy(value);
292
+ } catch {
293
+ return false;
294
+ }
295
+ }
296
+
297
+ function publicCronRunList(receipts, {jobId, status, limit = 20} = {}) {
298
+ if (!Array.isArray(receipts)) fail('CRON_RUN_INVALID_LIST', 'receipts must be an array');
299
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_LIST_LIMIT) {
300
+ fail('CRON_RUN_INVALID_LIMIT', `limit must be between 1 and ${MAX_LIST_LIMIT}`);
301
+ }
302
+ if (status !== undefined && !ALL_STATUSES.has(status)) fail('CRON_RUN_INVALID_STATUS', 'Unknown cron run status');
303
+ return receipts
304
+ .filter((receipt) => jobId === undefined || receipt.jobId === jobId)
305
+ .filter((receipt) => status === undefined || receipt.status === status)
306
+ .sort((left, right) => Date.parse(right.firedAt) - Date.parse(left.firedAt))
307
+ .slice(0, limit)
308
+ .map((receipt) => ({
309
+ runId: receipt.runId,
310
+ jobId: receipt.jobId,
311
+ status: receipt.status,
312
+ firedAt: receipt.firedAt,
313
+ startedAt: receipt.startedAt,
314
+ endedAt: receipt.endedAt,
315
+ coalescedCount: receipt.coalescedCount,
316
+ toolCalls: receipt.toolSummary.calls,
317
+ toolErrors: receipt.toolSummary.errors,
318
+ outputCount: receipt.outputs.length,
319
+ finalMessageAvailable: receipt.finalMessageRef !== null,
320
+ }));
321
+ }
322
+
323
+ function assertCronRunReceiptPrivacy(receipt, canaries = []) {
324
+ const serialized = JSON.stringify(receipt);
325
+ const forbidden = ['thoughts', 'reasoning', 'prompt', 'arguments', 'results', 'headers', 'approvalFeedback', 'rawError'];
326
+ for (const key of forbidden) {
327
+ if (new RegExp(`"${key}"\\s*:`, 'u').test(serialized)) {
328
+ fail('CRON_RUN_PRIVATE_FIELD', `Forbidden cron receipt field: ${key}`);
329
+ }
330
+ }
331
+ for (const canary of canaries) {
332
+ if (typeof canary === 'string' && canary.length > 0 && serialized.includes(canary)) {
333
+ fail('CRON_RUN_SECRET_LEAK', 'Cron receipt contains a secret canary');
334
+ }
335
+ }
336
+ return true;
337
+ }
338
+
339
+ module.exports = {
340
+ MAX_ALLOWED_TOOLS,
341
+ MAX_LIST_LIMIT,
342
+ MAX_RECEIPT_OUTPUTS,
343
+ ScopedCronRunError,
344
+ assertCronRunReceiptPrivacy,
345
+ buildCronFileCandidate,
346
+ createCronRunReceipt,
347
+ cronToolScopeDigest,
348
+ cronToolScopeForTask,
349
+ filterToolsForCronScope,
350
+ inheritedCronToolScope,
351
+ isCronToolScope,
352
+ isValidCronRunReceipt,
353
+ markCronJobDeleted,
354
+ normalizeCronAllowedTools,
355
+ publicCronRunList,
356
+ summarizeCronToolCalls,
357
+ transitionCronRunReceipt,
358
+ };
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const TOOL_ABORTED_BEFORE_DISPATCH = 'TOOL_ABORTED_BEFORE_DISPATCH';
4
+
5
+ async function checkpointBeforeExternalSideEffect(options = {}) {
6
+ const flush = options.flush;
7
+ const signal = options.signal;
8
+ if (typeof flush !== 'function') {
9
+ throw new TypeError('checkpointBeforeExternalSideEffect requires flush');
10
+ }
11
+ await flush();
12
+ if (signal?.aborted === true) {
13
+ return {
14
+ allowed: false,
15
+ code: TOOL_ABORTED_BEFORE_DISPATCH,
16
+ error: 'Tool call aborted before dispatch while its durable checkpoint was being written.',
17
+ };
18
+ }
19
+ return { allowed: true };
20
+ }
21
+
22
+ module.exports = {
23
+ TOOL_ABORTED_BEFORE_DISPATCH,
24
+ checkpointBeforeExternalSideEffect,
25
+ };
@@ -20,7 +20,7 @@ function safeConfigPath(profileHome, fsImpl = fs) {
20
20
 
21
21
  function parseStartupPreferences(text) {
22
22
  let section = '';
23
- let autoInstall = true;
23
+ let autoInstall = false;
24
24
  let uiLocale = 'en';
25
25
 
26
26
  for (const line of String(text).replace(/^\uFEFF/u, '').split(/\r?\n/u)) {
@@ -47,11 +47,11 @@ function parseStartupPreferences(text) {
47
47
 
48
48
  function readStartupPreferences(profileHome, fsImpl = fs) {
49
49
  const configPath = safeConfigPath(profileHome, fsImpl);
50
- if (!configPath) return { autoInstall: true, uiLocale: 'en' };
50
+ if (!configPath) return { autoInstall: false, uiLocale: 'en' };
51
51
  try {
52
52
  return parseStartupPreferences(fsImpl.readFileSync(configPath, 'utf8'));
53
53
  } catch {
54
- return { autoInstall: true, uiLocale: 'en' };
54
+ return { autoInstall: false, uiLocale: 'en' };
55
55
  }
56
56
  }
57
57
 
@@ -125,6 +125,7 @@ function migrateLegacyStartupPreferences(profileHome, fsImpl = fs) {
125
125
 
126
126
  module.exports = {
127
127
  migrateLegacyStartupPreferences,
128
+ parseStartupPreferences,
128
129
  readStartupPreferences,
129
130
  resolveStartupUiLocale,
130
131
  };