blun-king-cli 9.1.514 → 9.1.516

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.
@@ -0,0 +1,189 @@
1
+ const assert = require('node:assert/strict');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const { enqueueTelegramAddressed } = require('../bin/telegram-addressed-priority.cjs');
5
+ const { enqueueTelegramBotPriority } = require('../bin/telegram-bot-priority.cjs');
6
+ const { enqueueTelegramDirect } = require('../bin/telegram-direct-focus-policy.cjs');
7
+ const { enqueueTelegramUrgent } = require('../bin/telegram-urgent-policy.cjs');
8
+
9
+ const bundle = fs.readFileSync(path.join(__dirname, '..', 'blun.mjs'), 'utf8');
10
+
11
+ function between(start, end) {
12
+ const startIndex = bundle.indexOf(start);
13
+ assert.notEqual(startIndex, -1, `missing bundle marker: ${start}`);
14
+ const endIndex = bundle.indexOf(end, startIndex + start.length);
15
+ assert.notEqual(endIndex, -1, `missing bundle marker: ${end}`);
16
+ return bundle.slice(startIndex, endIndex);
17
+ }
18
+
19
+ function method(name, nextName) {
20
+ const source = between(`\n\t${name}(`, `\n\t${nextName}(`).trim();
21
+ return Function(`"use strict"; return ({${source}}).${name};`)();
22
+ }
23
+
24
+ const ctrlS = between('\n\t\teditor.onCtrlS = () => {', '\n\t\teditor.onCtrlB =');
25
+ assert.match(ctrlS, /host\.flushQueuedMessages\(/);
26
+ assert.doesNotMatch(ctrlS, /toggleTodoPanelExpansion|shortcut_todo_expand/);
27
+
28
+ const escape = between('\n\t\teditor.onEscape =', '\n\t\teditor.onShiftTab =');
29
+ const releaseIndex = escape.indexOf('host.releaseNextQueuedMessage()');
30
+ const cancelIndex = escape.indexOf('host.markCurrentRunCancelledByUser()');
31
+ assert.ok(releaseIndex >= 0, 'Escape must release queued work');
32
+ assert.ok(cancelIndex > releaseIndex, 'Escape must release queued work before cancelling the run');
33
+
34
+ const releaseNextQueuedMessage = method('releaseNextQueuedMessage', 'markCurrentRunCancelledByUser');
35
+ const releaseHost = {
36
+ queueSteerInFlight: undefined,
37
+ queueFlushBatchRemaining: 0,
38
+ state: {
39
+ queuedMessages: [{ text: 'one' }, { text: 'two' }, { text: 'three' }],
40
+ ui: { requestRender() {} },
41
+ },
42
+ steerQueueFlushPrefixCalls: 0,
43
+ updateQueueDisplayCalls: 0,
44
+ steerQueueFlushPrefix() { this.steerQueueFlushPrefixCalls += 1; },
45
+ updateQueueDisplay() { this.updateQueueDisplayCalls += 1; },
46
+ };
47
+
48
+ assert.equal(releaseNextQueuedMessage.call(releaseHost), true);
49
+ assert.equal(releaseHost.queueFlushBatchRemaining, 1);
50
+ assert.equal(releaseNextQueuedMessage.call(releaseHost), true);
51
+ assert.equal(releaseHost.queueFlushBatchRemaining, 2);
52
+ assert.equal(releaseNextQueuedMessage.call(releaseHost), true);
53
+ assert.equal(releaseHost.queueFlushBatchRemaining, 3);
54
+ assert.equal(releaseNextQueuedMessage.call(releaseHost), false);
55
+ assert.equal(releaseHost.queueFlushBatchRemaining, 3);
56
+ assert.equal(releaseHost.steerQueueFlushPrefixCalls, 3);
57
+
58
+ const flushQueuedMessages = method('flushQueuedMessages', 'steerQueueFlushPrefix');
59
+ const flushHost = {
60
+ queueSteerInFlight: { items: [{ text: 'already accepted' }] },
61
+ queueFlushBatchRemaining: 0,
62
+ state: {
63
+ queuedMessages: [{ text: 'one' }, { text: 'two' }, { text: 'three' }],
64
+ editor: { setText() {}, inputMode: 'prompt' },
65
+ },
66
+ steerQueueFlushPrefixCalls: 0,
67
+ scheduleQueueDrainCalls: 0,
68
+ steerQueueFlushPrefix() { this.steerQueueFlushPrefixCalls += 1; },
69
+ scheduleQueueDrain() { this.scheduleQueueDrainCalls += 1; },
70
+ };
71
+
72
+ flushQueuedMessages.call(flushHost, '', 'prompt');
73
+ assert.equal(flushHost.queueFlushBatchRemaining, 4);
74
+ assert.equal(flushHost.steerQueueFlushPrefixCalls, 1);
75
+ assert.equal(flushHost.scheduleQueueDrainCalls, 1);
76
+
77
+ const commitQueuedSteer = method('commitQueuedSteer', 'recoverRejectedActiveSteer');
78
+ let commits = 0;
79
+ const inFlight = {
80
+ items: [{ text: 'telegram message' }],
81
+ confirmationTimer: undefined,
82
+ onCommit() { commits += 1; },
83
+ };
84
+ const commitHost = {
85
+ queueSteerInFlight: inFlight,
86
+ updateQueueDisplay() {},
87
+ state: { ui: { requestRender() {} } },
88
+ steerQueueFlushPrefix() {},
89
+ scheduleQueueDrain() {},
90
+ };
91
+
92
+ commitQueuedSteer.call(commitHost, inFlight);
93
+ commitQueuedSteer.call(commitHost, inFlight);
94
+ assert.equal(commits, 1);
95
+ assert.equal(commitHost.queueSteerInFlight, undefined);
96
+
97
+ const acceptQueuedSteer = method('acceptQueuedSteer', 'commitQueuedSteerIfReady');
98
+ let acceptedCommits = 0;
99
+ const acceptedInFlight = {
100
+ items: [{ channelDeliveryTrace: { messageId: '63508' } }],
101
+ accepted: false,
102
+ stepStarted: false,
103
+ turnEnded: false,
104
+ onCommit() { acceptedCommits += 1; },
105
+ };
106
+ const acceptedHost = {
107
+ queueSteerInFlight: acceptedInFlight,
108
+ traced: [],
109
+ traceTelegramDelivery(event) { this.traced.push(event); },
110
+ commitQueuedSteer(item) {
111
+ assert.equal(item, acceptedInFlight);
112
+ if (this.queueSteerInFlight !== item) return;
113
+ this.queueSteerInFlight = undefined;
114
+ item.onCommit();
115
+ },
116
+ };
117
+
118
+ assert.equal(acceptQueuedSteer.call(acceptedHost, acceptedInFlight, {
119
+ accepted: true,
120
+ duplicate: false,
121
+ turnId: 77,
122
+ }, true), true);
123
+ assert.equal(acceptedCommits, 1, 'durably accepted steer must commit before a delayed step event');
124
+ assert.equal(acceptedHost.queueSteerInFlight, undefined);
125
+ assert.equal(acceptedHost.traced.length, 1);
126
+ assert.equal(acceptedHost.traced[0].stage, 'accepted');
127
+
128
+ const recoverRejectedActiveSteer = method('recoverRejectedActiveSteer', 'quarantineQueuedSteer');
129
+ let restored = 0;
130
+ const rejectedInFlight = {
131
+ items: [{ text: 'keep me queued', channelDeliveryFailures: 2 }],
132
+ onRestore() { restored += 1; },
133
+ };
134
+ const rejectedHost = {
135
+ queueSteerInFlight: rejectedInFlight,
136
+ state: { queuedMessages: [], ui: { requestRender() {} } },
137
+ traced: [],
138
+ traceTelegramDelivery(event) { this.traced.push(event); },
139
+ updateQueueDisplay() {},
140
+ streamingUI: { hasActiveTurn() { return true; } },
141
+ };
142
+
143
+ assert.equal(recoverRejectedActiveSteer.call(rejectedHost, rejectedInFlight), false);
144
+ assert.equal(restored, 1);
145
+ assert.equal(rejectedHost.state.queuedMessages.length, 1, 'rejected steer must remain queued');
146
+ assert.equal(rejectedHost.state.queuedMessages[0].text, 'keep me queued');
147
+ assert.equal('channelDeliveryFailures' in rejectedHost.state.queuedMessages[0], false);
148
+
149
+ const channelDelivery = between('\n\tasync deliverQueuedChannelHead() {', '\n\tinstallChannelReplyGuard(');
150
+ assert.match(
151
+ channelDelivery,
152
+ /if \(!result\.accepted\) return this\.recoverRejectedActiveSteer\(inFlight\);[\s\S]*return this\.acceptQueuedSteer\(inFlight, result, true\);/,
153
+ 'channel delivery must commit a durably accepted active steer',
154
+ );
155
+ assert.doesNotMatch(
156
+ channelDelivery,
157
+ /channel steer confirmation timed out|CHANNEL_STEER_CONFIRM_TIMEOUT_MS/,
158
+ 'durably accepted channel messages must not be restored by a wall-clock timeout',
159
+ );
160
+
161
+ const ctrlSDelivery = between('\n\tsteerQueueFlushPrefix() {', '\n\tdrainOneQueuedMessage() {');
162
+ assert.match(
163
+ ctrlSDelivery,
164
+ /if \(!result\.accepted\) this\.recoverRejectedActiveSteer\(inFlight\);[\s\S]*else this\.acceptQueuedSteer\(inFlight, result\);/,
165
+ 'Ctrl+S delivery must use the same durable acceptance rule',
166
+ );
167
+
168
+ const priorityQueue = [{ id: 'normal-a' }, { id: 'normal-b' }];
169
+ enqueueTelegramBotPriority(priorityQueue, { id: 'bot-a', channelBotPriority: true });
170
+ enqueueTelegramBotPriority(priorityQueue, { id: 'bot-b', channelBotPriority: true });
171
+ enqueueTelegramAddressed(priorityQueue, { id: 'addressed-a', channelAddressed: true });
172
+ enqueueTelegramAddressed(priorityQueue, { id: 'addressed-b', channelAddressed: true });
173
+ enqueueTelegramDirect(priorityQueue, { id: 'direct-a', channelDirect: true });
174
+ enqueueTelegramDirect(priorityQueue, { id: 'direct-b', channelDirect: true });
175
+ enqueueTelegramUrgent(priorityQueue, { id: 'urgent-a', channelUrgent: true });
176
+ enqueueTelegramUrgent(priorityQueue, { id: 'urgent-b', channelUrgent: true });
177
+ assert.deepEqual(
178
+ priorityQueue.map((item) => item.id),
179
+ [
180
+ 'urgent-a', 'urgent-b',
181
+ 'direct-a', 'direct-b',
182
+ 'addressed-a', 'addressed-b',
183
+ 'bot-a', 'bot-b',
184
+ 'normal-a', 'normal-b',
185
+ ],
186
+ 'Telegram queue priority must be urgent, direct, addressed, bot, normal with stable FIFO inside each class',
187
+ );
188
+
189
+ console.log('queue controls regression: PASS');
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ const bundlePath = process.env.BLUN_BUNDLE_UNDER_TEST
9
+ ? path.resolve(process.env.BLUN_BUNDLE_UNDER_TEST)
10
+ : path.join(__dirname, '..', 'blun.mjs');
11
+ const bundle = fs.readFileSync(bundlePath, 'utf8');
12
+
13
+ function between(start, end) {
14
+ const startIndex = bundle.indexOf(start);
15
+ assert.notEqual(startIndex, -1, `missing bundle marker: ${start}`);
16
+ const endIndex = bundle.indexOf(end, startIndex + start.length);
17
+ assert.notEqual(endIndex, -1, `missing bundle marker: ${end}`);
18
+ return bundle.slice(startIndex, endIndex);
19
+ }
20
+
21
+ function compileMethod(name, nextName, dependencies = {}) {
22
+ const source = between(`\n\t\t${name}(`, `\n\t\t${nextName}(`).trim();
23
+ const names = Object.keys(dependencies);
24
+ const values = Object.values(dependencies);
25
+ return Function(...names, `"use strict"; return ({${source}}).${name};`)(...values);
26
+ }
27
+
28
+ function resolveStepEventUuid(openSteps, event) {
29
+ if (event.stepUuid !== undefined) return event.stepUuid;
30
+ let latest;
31
+ for (const uuid of openSteps.keys()) latest = uuid;
32
+ return latest;
33
+ }
34
+
35
+ function resolveCompletedStepUuid(openSteps, event) {
36
+ if (event.uuid !== undefined) return event.uuid;
37
+ let latest;
38
+ for (const uuid of openSteps.keys()) latest = uuid;
39
+ return latest;
40
+ }
41
+
42
+ const recoverMissingOpenStep = compileMethod(
43
+ 'recoverMissingOpenStep',
44
+ 'appendLoopEvent',
45
+ { resolveStepEventUuid },
46
+ );
47
+ const appendLoopEvent = compileMethod(
48
+ 'appendLoopEvent',
49
+ 'appendMessage',
50
+ {
51
+ projectLoopEventForRecord: (event) => event,
52
+ resolveCompletedStepUuid,
53
+ createToolMessage: (toolCallId, content) => ({ toolCallId, content, toolCalls: [] }),
54
+ toolResultOutputForModel: (result) => result.output,
55
+ estimateTokensForMessages: () => 0,
56
+ },
57
+ );
58
+
59
+ const records = [];
60
+ const warnings = [];
61
+ const host = {
62
+ _history: [],
63
+ _tokenCount: 0,
64
+ tokenCountCoveredMessageCount: 0,
65
+ openSteps: new Map(),
66
+ pendingToolResultIds: new Set(),
67
+ agent: {
68
+ records: {
69
+ restoring: { time: Date.now() },
70
+ logRecord(record) { records.push(record); },
71
+ },
72
+ log: { warn(message, fields) { warnings.push({ message, fields }); } },
73
+ },
74
+ recoverMissingOpenStep,
75
+ pushHistory(...messages) { this._history.push(...messages); },
76
+ markPendingTokenEstimateDirty() {},
77
+ flushDeferredMessagesIfToolExchangeClosed() {},
78
+ closePendingToolResults() { return []; },
79
+ };
80
+
81
+ // Exact shape measured after context.apply_compaction: no step.begin survives,
82
+ // then text, a tool call/result pair, and step.end continue the interrupted turn.
83
+ appendLoopEvent.call(host, { type: 'content.part', part: { type: 'text', text: 'continued after compaction' } });
84
+ appendLoopEvent.call(host, { type: 'tool.call', toolCallId: 'tool-1', name: 'Read', args: { path: 'SPEC.md' } });
85
+ appendLoopEvent.call(host, { type: 'tool.result', toolCallId: 'tool-1', result: { output: 'ok', isError: false } });
86
+ appendLoopEvent.call(host, { type: 'step.end', contextTokens: 100 });
87
+
88
+ assert.equal(warnings.length, 1, 'one missing step must produce one bounded repair warning');
89
+ assert.equal(warnings[0].fields.eventType, 'content.part');
90
+ assert.equal(warnings[0].fields.restoring, true);
91
+ assert.equal(host._history.length, 2, 'repair must retain the assistant continuation and its tool result');
92
+ assert.deepEqual(host._history[0].content, [{ type: 'text', text: 'continued after compaction' }]);
93
+ assert.equal(host._history[0].toolCalls.length, 1);
94
+ assert.equal(host._history[0].toolCalls[0].id, 'tool-1');
95
+ assert.equal(host._history[1].toolCallId, 'tool-1');
96
+ assert.equal(host.pendingToolResultIds.size, 0);
97
+ assert.equal(host.openSteps.size, 0);
98
+ assert.equal(records.filter((record) => record.type === 'context.append_loop_event').length, 4);
99
+
100
+ process.stdout.write('resume-replay-regression PASS\n');
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ const bundle = fs.readFileSync(path.join(__dirname, '..', 'blun.mjs'), 'utf8');
9
+
10
+ function between(start, end) {
11
+ const startIndex = bundle.indexOf(start);
12
+ assert.notEqual(startIndex, -1, `missing bundle marker: ${start}`);
13
+ const endIndex = bundle.indexOf(end, startIndex + start.length);
14
+ assert.notEqual(endIndex, -1, `missing bundle marker: ${end}`);
15
+ return bundle.slice(startIndex, endIndex);
16
+ }
17
+
18
+ function method(name, nextName) {
19
+ const source = between(`\n\t${name}(`, `\n\t${nextName}(`).trim();
20
+ return Function(`"use strict"; return ({${source}}).${name};`)();
21
+ }
22
+
23
+ async function main() {
24
+ const scheduleBridgeRestart = method('scheduleBridgeRestart', 'ensureBridgeRunning');
25
+ const host = {
26
+ stopped: false,
27
+ activeOwner: true,
28
+ bridgeRestartTimer: undefined,
29
+ bridgeRestartDelayMs: 5,
30
+ ownsChannel() { return true; },
31
+ starts: 0,
32
+ ensureBridgeRunning() { this.starts += 1; },
33
+ };
34
+
35
+ scheduleBridgeRestart.call(host);
36
+ const firstTimer = host.bridgeRestartTimer;
37
+ assert.notEqual(firstTimer, undefined);
38
+ scheduleBridgeRestart.call(host);
39
+ assert.equal(host.bridgeRestartTimer, firstTimer, 'restart scheduling must be deduplicated');
40
+ await new Promise((resolve) => setTimeout(resolve, 25));
41
+ assert.equal(host.starts, 1, 'a dead owned bridge must be restarted once');
42
+ assert.equal(host.bridgeRestartTimer, undefined);
43
+
44
+ host.stopped = true;
45
+ scheduleBridgeRestart.call(host);
46
+ assert.equal(host.bridgeRestartTimer, undefined, 'stopped controllers must not restart bridges');
47
+
48
+ const ensureBridgeRunning = between('\n\tensureBridgeRunning() {', '\n\tresolveBridgeEntry() {');
49
+ assert.match(ensureBridgeRunning, /child\.once\("exit",[\s\S]*this\.scheduleBridgeRestart\(\)/);
50
+ assert.match(ensureBridgeRunning, /child\.once\("error",[\s\S]*this\.scheduleBridgeRestart\(\)/);
51
+ const beginStop = between('\n\tbeginStop() {', '\n\tasync stopOwnedBridge(');
52
+ assert.match(beginStop, /clearTimeout\(this\.bridgeRestartTimer\)/);
53
+
54
+ process.stdout.write('telegram-bridge-watchdog PASS\n');
55
+ }
56
+
57
+ main().catch((error) => {
58
+ console.error(error);
59
+ process.exitCode = 1;
60
+ });
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { enforceSingleTodoWritePerStep } = require('../bin/todo-list-turn-policy.cjs');
7
+
8
+ const bundlePath = process.env.BLUN_BUNDLE_UNDER_TEST
9
+ ? path.resolve(process.env.BLUN_BUNDLE_UNDER_TEST)
10
+ : path.resolve(__dirname, '..', 'blun.mjs');
11
+ const bundle = fs.readFileSync(bundlePath, 'utf8');
12
+
13
+ function assert(condition, message) {
14
+ if (!condition) throw new Error(message);
15
+ }
16
+
17
+ assert(
18
+ !bundle.includes('TodoList is unchanged after extended work'),
19
+ 'TODO_LOOP_REGRESSION: unchanged truthful TodoList refreshes must remain accepted',
20
+ );
21
+ assert(
22
+ !/isIdle:\s*\(task\)\s*=>[\s\S]{0,240}owner\s*!==\s*["']session-loop["']/.test(bundle),
23
+ 'TODO_LOOP_REGRESSION: unfinished TodoList work must not suppress an idle session loop',
24
+ );
25
+ assert(
26
+ /isIdle:\s*\(\)\s*=>\s*!agent\.turn\.hasActiveTurn/.test(bundle),
27
+ 'TODO_LOOP_REGRESSION: expected idle-only session loop scheduler wiring is missing',
28
+ );
29
+ assert(
30
+ /todos\.filter\(\(todo\)\s*=>\s*todo\.status\s*!==\s*["']done["']\)/.test(bundle),
31
+ 'TODO_LIFECYCLE_REGRESSION: completed items must be pruned from the stored visible list',
32
+ );
33
+ assert(
34
+ /const PENDING_TEXT\s*=\s*["']#aeb8c6["']/.test(bundle)
35
+ && !/const PENDING_TEXT\s*=\s*["']#2a3550["']/.test(bundle),
36
+ 'TODO_UI_REGRESSION: pending markers must remain visible on ANSI16 terminals',
37
+ );
38
+ assert(
39
+ /activeGoal\s*===\s*null\s*&&\s*!startsTrackedList/.test(bundle)
40
+ && /The first TodoList must contain at least one item, exactly one in_progress item/.test(bundle),
41
+ 'TODO_INITIAL_STATE_REGRESSION: a new non-goal TodoList must not accept an all-pending list',
42
+ );
43
+ assert(
44
+ /enforceSingleTodoWritePerStep\(ctx\)/.test(bundle),
45
+ 'TODO_PARALLEL_REGRESSION: per-step TodoList write guard is not wired',
46
+ );
47
+
48
+ const calls = [
49
+ { id: 'todo-first', name: 'TodoList' },
50
+ { id: 'todo-second', name: 'TodoList' },
51
+ ];
52
+ assert(
53
+ enforceSingleTodoWritePerStep({
54
+ toolCall: calls[0],
55
+ toolCalls: calls,
56
+ args: { todos: [{ title: 'first', status: 'in_progress' }] },
57
+ }) === undefined,
58
+ 'TODO_PARALLEL_REGRESSION: first TodoList write must remain executable',
59
+ );
60
+ const duplicateWrite = enforceSingleTodoWritePerStep({
61
+ toolCall: calls[1],
62
+ toolCalls: calls,
63
+ args: { todos: [{ title: 'second', status: 'in_progress' }] },
64
+ });
65
+ assert(
66
+ duplicateWrite?.block === true,
67
+ 'TODO_PARALLEL_REGRESSION: later TodoList writes in the same step must be blocked',
68
+ );
69
+ assert(
70
+ enforceSingleTodoWritePerStep({
71
+ toolCall: { id: 'todo-read', name: 'TodoList' },
72
+ toolCalls: calls,
73
+ args: {},
74
+ }) === undefined,
75
+ 'TODO_PARALLEL_REGRESSION: read-only TodoList queries must not be blocked',
76
+ );
77
+
78
+ process.stdout.write('todo-loop-regression PASS\n');