blun-king-cli 9.1.307 → 9.1.309
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/bin/cognitive-attention-policy.cjs +143 -0
- package/bin/cognitive-state-store.cjs +55 -0
- package/bin/launcher-runtime.js +63 -13
- package/blun.mjs +24 -6
- package/package.json +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
|
|
5
|
+
const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
|
|
6
|
+
const SUBJECT_KINDS = new Set(['human', 'agent', 'task']);
|
|
7
|
+
const SIGNAL_KINDS = new Set(['heartbeat', 'commitment', 'task_blocked']);
|
|
8
|
+
const HEARTBEAT_STALE_MS = 5 * 60 * 1000;
|
|
9
|
+
const BLOCKED_TASK_STALE_MS = 10 * 60 * 1000;
|
|
10
|
+
const MAX_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000;
|
|
11
|
+
|
|
12
|
+
function fail() {
|
|
13
|
+
const error = new Error('ATTENTION_INVALID_INPUT');
|
|
14
|
+
error.code = 'ATTENTION_INVALID_INPUT';
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function exactKeys(value, keys) {
|
|
19
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
20
|
+
&& Object.keys(value).every((key) => keys.has(key));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function safeId(value) {
|
|
24
|
+
const text = String(value ?? '').trim();
|
|
25
|
+
return SAFE_ID_RE.test(text) ? text : '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function instant(value) {
|
|
29
|
+
if (value === null || value === undefined) return null;
|
|
30
|
+
const milliseconds = Date.parse(String(value));
|
|
31
|
+
return Number.isFinite(milliseconds) ? milliseconds : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeQuietHours(value) {
|
|
35
|
+
if (!exactKeys(value, new Set(['startHour', 'endHour']))) fail();
|
|
36
|
+
const startHour = Number(value.startHour);
|
|
37
|
+
const endHour = Number(value.endHour);
|
|
38
|
+
if (!Number.isInteger(startHour) || startHour < 0 || startHour > 23
|
|
39
|
+
|| !Number.isInteger(endHour) || endHour < 0 || endHour > 23
|
|
40
|
+
|| startHour === endHour) fail();
|
|
41
|
+
return { startHour, endHour };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isQuietTime(nowMs, offsetMinutes, quietHours) {
|
|
45
|
+
const localHour = new Date(nowMs + offsetMinutes * 60 * 1000).getUTCHours();
|
|
46
|
+
return quietHours.startHour < quietHours.endHour
|
|
47
|
+
? localHour >= quietHours.startHour && localHour < quietHours.endHour
|
|
48
|
+
: localHour >= quietHours.startHour || localHour < quietHours.endHour;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function signalReason(subjectKind, signal, nowMs) {
|
|
52
|
+
if (!exactKeys(signal, new Set([
|
|
53
|
+
'kind', 'heartbeatAt', 'activeTask', 'expectedBy', 'blockedSince',
|
|
54
|
+
'evidenceId', 'evidenceScope', 'confidence',
|
|
55
|
+
]))) fail();
|
|
56
|
+
if (!SIGNAL_KINDS.has(signal.kind)) fail();
|
|
57
|
+
const evidenceId = safeId(signal.evidenceId);
|
|
58
|
+
const evidenceScope = safeId(signal.evidenceScope);
|
|
59
|
+
const confidence = Number(signal.confidence);
|
|
60
|
+
if (!evidenceId || !evidenceScope || !Number.isFinite(confidence)
|
|
61
|
+
|| confidence < 0 || confidence > 1) fail();
|
|
62
|
+
if (confidence < 0.5) return null;
|
|
63
|
+
|
|
64
|
+
if (signal.kind === 'heartbeat') {
|
|
65
|
+
if (!exactKeys(signal, new Set(['kind', 'heartbeatAt', 'activeTask', 'evidenceId', 'evidenceScope', 'confidence']))
|
|
66
|
+
|| subjectKind !== 'agent' || signal.activeTask !== true) return null;
|
|
67
|
+
const heartbeatAt = instant(signal.heartbeatAt);
|
|
68
|
+
if (heartbeatAt === null || heartbeatAt > nowMs) fail();
|
|
69
|
+
return nowMs - heartbeatAt >= HEARTBEAT_STALE_MS
|
|
70
|
+
? { reason: 'missing_heartbeat', anchor: new Date(heartbeatAt).toISOString(), evidenceId, evidenceScope, confidence }
|
|
71
|
+
: null;
|
|
72
|
+
}
|
|
73
|
+
if (signal.kind === 'commitment') {
|
|
74
|
+
if (!exactKeys(signal, new Set(['kind', 'expectedBy', 'evidenceId', 'evidenceScope', 'confidence']))) fail();
|
|
75
|
+
const expectedBy = instant(signal.expectedBy);
|
|
76
|
+
if (expectedBy === null) fail();
|
|
77
|
+
return nowMs > expectedBy
|
|
78
|
+
? { reason: 'overdue_commitment', anchor: new Date(expectedBy).toISOString(), evidenceId, evidenceScope, confidence }
|
|
79
|
+
: null;
|
|
80
|
+
}
|
|
81
|
+
if (!exactKeys(signal, new Set(['kind', 'blockedSince', 'evidenceId', 'evidenceScope', 'confidence'])) || subjectKind !== 'task') return null;
|
|
82
|
+
const blockedSince = instant(signal.blockedSince);
|
|
83
|
+
if (blockedSince === null || blockedSince > nowMs) fail();
|
|
84
|
+
return nowMs - blockedSince >= BLOCKED_TASK_STALE_MS
|
|
85
|
+
? { reason: 'blocked_task', anchor: new Date(blockedSince).toISOString(), evidenceId, evidenceScope, confidence }
|
|
86
|
+
: null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildAttentionCandidate(input) {
|
|
90
|
+
const allowed = new Set([
|
|
91
|
+
'tenantId', 'subjectKind', 'subjectId', 'responsibleAgent', 'currentAgent', 'now', 'signal',
|
|
92
|
+
'absenceUntil', 'timezoneOffsetMinutes', 'quietHours', 'cooldownMs', 'allowedChannel',
|
|
93
|
+
]);
|
|
94
|
+
if (!exactKeys(input, allowed)) fail();
|
|
95
|
+
const tenantId = safeId(input.tenantId);
|
|
96
|
+
const subjectKind = String(input.subjectKind ?? '');
|
|
97
|
+
const subjectId = safeId(input.subjectId);
|
|
98
|
+
const responsibleAgent = safeId(input.responsibleAgent);
|
|
99
|
+
const currentAgent = safeId(input.currentAgent);
|
|
100
|
+
const allowedChannel = safeId(input.allowedChannel);
|
|
101
|
+
const nowMs = instant(input.now);
|
|
102
|
+
const offsetMinutes = Number(input.timezoneOffsetMinutes);
|
|
103
|
+
const cooldownMs = Number(input.cooldownMs);
|
|
104
|
+
if (!tenantId || !SUBJECT_KINDS.has(subjectKind) || !subjectId || !responsibleAgent
|
|
105
|
+
|| !currentAgent || !allowedChannel || nowMs === null
|
|
106
|
+
|| !Number.isInteger(offsetMinutes) || offsetMinutes < -840 || offsetMinutes > 840
|
|
107
|
+
|| !Number.isSafeInteger(cooldownMs) || cooldownMs < 1000 || cooldownMs > MAX_COOLDOWN_MS) fail();
|
|
108
|
+
if (currentAgent !== responsibleAgent) return null;
|
|
109
|
+
|
|
110
|
+
const absenceUntil = instant(input.absenceUntil);
|
|
111
|
+
if (input.absenceUntil !== null && absenceUntil === null) fail();
|
|
112
|
+
if (absenceUntil !== null && absenceUntil > nowMs) return null;
|
|
113
|
+
const quietHours = normalizeQuietHours(input.quietHours);
|
|
114
|
+
if (isQuietTime(nowMs, offsetMinutes, quietHours)) return null;
|
|
115
|
+
|
|
116
|
+
const reason = signalReason(subjectKind, input.signal, nowMs);
|
|
117
|
+
if (reason === null) return null;
|
|
118
|
+
const digest = crypto.createHash('sha256')
|
|
119
|
+
.update([tenantId, subjectKind, subjectId, reason.reason, reason.evidenceId].join('\0'))
|
|
120
|
+
.digest('hex')
|
|
121
|
+
.slice(0, 40);
|
|
122
|
+
return {
|
|
123
|
+
candidate_id: `attention-${digest}`,
|
|
124
|
+
subject_kind: subjectKind,
|
|
125
|
+
subject_id: subjectId,
|
|
126
|
+
reason: reason.reason,
|
|
127
|
+
evidence_id: reason.evidenceId,
|
|
128
|
+
evidence_scope: reason.evidenceScope,
|
|
129
|
+
evidence_at: reason.anchor,
|
|
130
|
+
confidence: reason.confidence,
|
|
131
|
+
responsible_agent: responsibleAgent,
|
|
132
|
+
allowed_channel: allowedChannel,
|
|
133
|
+
detected_at: new Date(nowMs).toISOString(),
|
|
134
|
+
cooldown_ms: cooldownMs,
|
|
135
|
+
requires_runtime_authorization: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
BLOCKED_TASK_STALE_MS,
|
|
141
|
+
HEARTBEAT_STALE_MS,
|
|
142
|
+
buildAttentionCandidate,
|
|
143
|
+
};
|
|
@@ -147,6 +147,14 @@ function initialize(db) {
|
|
|
147
147
|
);
|
|
148
148
|
CREATE INDEX IF NOT EXISTS cognitive_observations_stream
|
|
149
149
|
ON cognitive_observations (tenant_id, agent_id, occurred_at, observation_id);
|
|
150
|
+
CREATE TABLE IF NOT EXISTS cognitive_attention_claims (
|
|
151
|
+
tenant_id TEXT NOT NULL,
|
|
152
|
+
candidate_id TEXT NOT NULL,
|
|
153
|
+
claimed_by TEXT NOT NULL,
|
|
154
|
+
claimed_at TEXT NOT NULL,
|
|
155
|
+
next_allowed_at TEXT NOT NULL,
|
|
156
|
+
PRIMARY KEY (tenant_id, candidate_id)
|
|
157
|
+
);
|
|
150
158
|
`);
|
|
151
159
|
}
|
|
152
160
|
|
|
@@ -235,10 +243,57 @@ function openCognitiveStateStore({ home } = {}) {
|
|
|
235
243
|
return { valid: true, events: rows.length, head_hash: previousHash };
|
|
236
244
|
}
|
|
237
245
|
|
|
246
|
+
function claimAttention(input) {
|
|
247
|
+
const allowedInput = new Set(['tenantId', 'candidate', 'claimedBy', 'claimedAt']);
|
|
248
|
+
const allowedCandidate = new Set([
|
|
249
|
+
'candidate_id', 'subject_kind', 'subject_id', 'reason', 'responsible_agent',
|
|
250
|
+
'evidence_id', 'evidence_scope', 'evidence_at', 'confidence', 'allowed_channel',
|
|
251
|
+
'detected_at', 'cooldown_ms', 'requires_runtime_authorization',
|
|
252
|
+
]);
|
|
253
|
+
if (!exactKeys(input, allowedInput) || !exactKeys(input.candidate, allowedCandidate)
|
|
254
|
+
|| hasForbiddenKey(input)) fail('COGNITIVE_FORBIDDEN_FIELD');
|
|
255
|
+
const tenant = safeId(input.tenantId);
|
|
256
|
+
const candidateId = safeId(input.candidate.candidate_id);
|
|
257
|
+
const claimedBy = safeId(input.claimedBy);
|
|
258
|
+
const claimedAtMs = Date.parse(String(input.claimedAt ?? ''));
|
|
259
|
+
const cooldownMs = Number(input.candidate.cooldown_ms);
|
|
260
|
+
if (!tenant || !candidateId || !claimedBy || !Number.isFinite(claimedAtMs)
|
|
261
|
+
|| !Number.isSafeInteger(cooldownMs) || cooldownMs < 1000
|
|
262
|
+
|| input.candidate.requires_runtime_authorization !== true) fail('COGNITIVE_INVALID_EVENT');
|
|
263
|
+
const claimedAt = new Date(claimedAtMs).toISOString();
|
|
264
|
+
const nextAllowedAt = new Date(claimedAtMs + cooldownMs).toISOString();
|
|
265
|
+
db.exec('BEGIN IMMEDIATE');
|
|
266
|
+
try {
|
|
267
|
+
const existing = db.prepare(`SELECT claimed_by, next_allowed_at FROM cognitive_attention_claims
|
|
268
|
+
WHERE tenant_id = ? AND candidate_id = ?`).get(tenant, candidateId);
|
|
269
|
+
if (existing && Date.parse(existing.next_allowed_at) > claimedAtMs) {
|
|
270
|
+
db.exec('COMMIT');
|
|
271
|
+
return {
|
|
272
|
+
claimed: false,
|
|
273
|
+
claimed_by: existing.claimed_by,
|
|
274
|
+
next_allowed_at: existing.next_allowed_at,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
db.prepare(`INSERT INTO cognitive_attention_claims
|
|
278
|
+
(tenant_id, candidate_id, claimed_by, claimed_at, next_allowed_at) VALUES (?, ?, ?, ?, ?)
|
|
279
|
+
ON CONFLICT(tenant_id, candidate_id) DO UPDATE SET
|
|
280
|
+
claimed_by = excluded.claimed_by,
|
|
281
|
+
claimed_at = excluded.claimed_at,
|
|
282
|
+
next_allowed_at = excluded.next_allowed_at`)
|
|
283
|
+
.run(tenant, candidateId, claimedBy, claimedAt, nextAllowedAt);
|
|
284
|
+
db.exec('COMMIT');
|
|
285
|
+
return { claimed: true, claimed_by: claimedBy, next_allowed_at: nextAllowedAt };
|
|
286
|
+
} catch (error) {
|
|
287
|
+
try { db.exec('ROLLBACK'); } catch {}
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
238
292
|
return {
|
|
239
293
|
commit,
|
|
240
294
|
read,
|
|
241
295
|
verify,
|
|
296
|
+
claimAttention,
|
|
242
297
|
close: () => {
|
|
243
298
|
try { db.exec('PRAGMA wal_checkpoint(TRUNCATE)'); } catch {}
|
|
244
299
|
db.close();
|
package/bin/launcher-runtime.js
CHANGED
|
@@ -68,6 +68,7 @@ const RAW_ARGS = process.argv.slice(2);
|
|
|
68
68
|
const PROFILE = parseProfileLaunchArgs(RAW_ARGS, launcherModeFromArgv(process.argv));
|
|
69
69
|
const ARGS = PROFILE.args;
|
|
70
70
|
const CORE_LOAD_TIMEOUT_MS = 30_000;
|
|
71
|
+
const RUNNING_UPDATE_RESUME_SESSION_ENV = 'BLUN_RUNNING_UPDATE_RESUME_SESSION_ID';
|
|
71
72
|
const RUNTIME_READY_TIMEOUT_MS = 60_000;
|
|
72
73
|
const RUNNING_UPDATE_RECHECK_MS = 5 * 60_000;
|
|
73
74
|
const RUNNING_UPDATE_RECHECK_JITTER_MS = 2 * 60_000;
|
|
@@ -312,6 +313,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
|
|
|
312
313
|
let pendingHandoff;
|
|
313
314
|
let updateStarted = false;
|
|
314
315
|
let runtimeReady = false;
|
|
316
|
+
let activeSession;
|
|
315
317
|
let runtimeExitIntent = false;
|
|
316
318
|
let supervisionCompleted = false;
|
|
317
319
|
let runningUpdatePollTimer;
|
|
@@ -329,6 +331,23 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
|
|
|
329
331
|
(options.clearTimeoutImpl || clearTimeout)(runningUpdatePollTimer);
|
|
330
332
|
runningUpdatePollTimer = undefined;
|
|
331
333
|
};
|
|
334
|
+
const validSessionMessage = (message) => typeof message?.sessionId === 'string'
|
|
335
|
+
&& message.sessionId.length > 0
|
|
336
|
+
&& message.sessionId.length <= 256
|
|
337
|
+
&& !/[\u0000-\u001f\u007f]/u.test(message.sessionId)
|
|
338
|
+
&& resolveHandoffCwd(message.cwd, '') !== '';
|
|
339
|
+
const stagedRuntimeOptions = () => ({
|
|
340
|
+
mode: runningUpdateMode,
|
|
341
|
+
...(runningUpdateMode === RUNNING_UPDATE_MODES.RESUME && activeSession !== undefined
|
|
342
|
+
? { cwd: activeSession.cwd, sessionId: activeSession.sessionId }
|
|
343
|
+
: {}),
|
|
344
|
+
});
|
|
345
|
+
const persistPreparedRuntime = () => {
|
|
346
|
+
if (preparedTarget === undefined) return;
|
|
347
|
+
const sharedHome = env.BLUN_SHARED_HOME;
|
|
348
|
+
if (typeof sharedHome !== 'string' || sharedHome.length === 0) return;
|
|
349
|
+
(options.stageRuntime || stageRuntime)(sharedHome, preparedTarget, stagedRuntimeOptions());
|
|
350
|
+
};
|
|
332
351
|
const scheduleNextPreparation = (child) => {
|
|
333
352
|
clearRunningUpdatePoll();
|
|
334
353
|
if (supervisionCompleted || pendingHandoff !== undefined || updateStarted) return;
|
|
@@ -367,7 +386,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
|
|
|
367
386
|
return;
|
|
368
387
|
}
|
|
369
388
|
preparedTarget = target;
|
|
370
|
-
(
|
|
389
|
+
persistPreparedRuntime();
|
|
371
390
|
const runningVersion = readPackageVersionAt(packageRoot);
|
|
372
391
|
(options.recordRunningUpdateEvent || recordRunningUpdateEvent)(sharedHome, previousTarget === undefined ? {
|
|
373
392
|
event: 'prepared-for-next-start',
|
|
@@ -396,13 +415,20 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
|
|
|
396
415
|
&& preparedTarget !== undefined
|
|
397
416
|
&& message.version === preparedTarget.version
|
|
398
417
|
&& message.mode === runningUpdateMode
|
|
399
|
-
&&
|
|
400
|
-
|
|
401
|
-
&& message.sessionId.length <= 256
|
|
402
|
-
&& !/[\u0000-\u001f\u007f]/u.test(message.sessionId)
|
|
403
|
-
&& resolveHandoffCwd(message.cwd, '') !== '') {
|
|
404
|
-
pendingHandoff = Object.freeze({
|
|
418
|
+
&& validSessionMessage(message)) {
|
|
419
|
+
activeSession = Object.freeze({
|
|
405
420
|
cwd: resolveHandoffCwd(message.cwd, cwd),
|
|
421
|
+
sessionId: message.sessionId,
|
|
422
|
+
});
|
|
423
|
+
try {
|
|
424
|
+
persistPreparedRuntime();
|
|
425
|
+
} catch (error) {
|
|
426
|
+
options.onRunningUpdateError?.(error);
|
|
427
|
+
scheduleNextPreparation(child);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
pendingHandoff = Object.freeze({
|
|
431
|
+
cwd: activeSession.cwd,
|
|
406
432
|
mode: message.mode,
|
|
407
433
|
sessionId: message.sessionId,
|
|
408
434
|
target: preparedTarget,
|
|
@@ -423,6 +449,17 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
|
|
|
423
449
|
}
|
|
424
450
|
}
|
|
425
451
|
if (message?.type === RUNTIME_READY_MESSAGE) {
|
|
452
|
+
if (validSessionMessage(message)) {
|
|
453
|
+
activeSession = Object.freeze({
|
|
454
|
+
cwd: resolveHandoffCwd(message.cwd, cwd),
|
|
455
|
+
sessionId: message.sessionId,
|
|
456
|
+
});
|
|
457
|
+
try {
|
|
458
|
+
persistPreparedRuntime();
|
|
459
|
+
} catch (error) {
|
|
460
|
+
options.onRunningUpdateError?.(error);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
426
463
|
runtimeReady = true;
|
|
427
464
|
refreshRunningUpdateMode();
|
|
428
465
|
if (automaticMode()) startPreparation(child);
|
|
@@ -482,7 +519,11 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
|
|
|
482
519
|
const handoffArgs = handoffArgsForMode(args, pendingHandoff.mode, pendingHandoff.sessionId);
|
|
483
520
|
const handoffCwd = pendingHandoff.cwd;
|
|
484
521
|
const previousActive = (options.readActiveRuntime || readActiveRuntime)(sharedHome);
|
|
485
|
-
const
|
|
522
|
+
const resumeEnv = {
|
|
523
|
+
...env,
|
|
524
|
+
[RUNNING_UPDATE_RESUME_SESSION_ENV]: pendingHandoff.sessionId,
|
|
525
|
+
};
|
|
526
|
+
const nextCore = spawnCore(handoffArgs, resumeEnv, handoffCwd, { packageRoot: target.packageRoot });
|
|
486
527
|
const nextLoaded = await nextCore.loaded;
|
|
487
528
|
const nextReady = nextLoaded && await (options.waitForRuntimeReady || waitForRuntimeReady)(
|
|
488
529
|
nextCore,
|
|
@@ -498,7 +539,7 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
|
|
|
498
539
|
toVersion: target.version,
|
|
499
540
|
mode: pendingHandoff.mode,
|
|
500
541
|
}, { now: options.nowImpl });
|
|
501
|
-
return superviseProtectedCore(handoffArgs,
|
|
542
|
+
return superviseProtectedCore(handoffArgs, resumeEnv, handoffCwd, async () => {}, {
|
|
502
543
|
...options,
|
|
503
544
|
existingCore: nextCore,
|
|
504
545
|
packageRoot: target.packageRoot,
|
|
@@ -519,14 +560,14 @@ async function superviseProtectedCore(args, env, cwd, releaseNotice, options = {
|
|
|
519
560
|
fromVersion: readPackageVersionAt(packageRoot),
|
|
520
561
|
toVersion: target.version,
|
|
521
562
|
}, { now: options.nowImpl });
|
|
522
|
-
const fallback = spawnCore(handoffArgs,
|
|
563
|
+
const fallback = spawnCore(handoffArgs, resumeEnv, handoffCwd, { packageRoot });
|
|
523
564
|
const fallbackLoaded = await fallback.loaded;
|
|
524
565
|
const fallbackReady = fallbackLoaded && await (options.waitForRuntimeReady || waitForRuntimeReady)(
|
|
525
566
|
fallback,
|
|
526
567
|
options.readyTimeoutMs,
|
|
527
568
|
);
|
|
528
569
|
if (!fallbackReady) return 1;
|
|
529
|
-
return superviseProtectedCore(handoffArgs,
|
|
570
|
+
return superviseProtectedCore(handoffArgs, resumeEnv, handoffCwd, async () => {}, {
|
|
530
571
|
...options,
|
|
531
572
|
existingCore: fallback,
|
|
532
573
|
packageRoot,
|
|
@@ -641,14 +682,20 @@ function spawnManagedLauncher(binary, cwd) {
|
|
|
641
682
|
});
|
|
642
683
|
}
|
|
643
684
|
|
|
644
|
-
function spawnActiveLauncher(packageRoot, cwd, args = RAW_ARGS) {
|
|
685
|
+
function spawnActiveLauncher(packageRoot, cwd, args = RAW_ARGS, options = {}) {
|
|
645
686
|
const entryName = path.basename(process.argv[1] || 'king.js').toLowerCase() === 'blun.js'
|
|
646
687
|
? 'blun.js'
|
|
647
688
|
: 'king.js';
|
|
648
689
|
return new Promise((resolve, reject) => {
|
|
649
690
|
const child = spawn(process.execPath, [path.join(packageRoot, 'bin', entryName), ...args], {
|
|
650
691
|
cwd,
|
|
651
|
-
env: {
|
|
692
|
+
env: {
|
|
693
|
+
...process.env,
|
|
694
|
+
BLUN_ACTIVE_RUNTIME_ROOT: packageRoot,
|
|
695
|
+
...(typeof options.resumeSessionId === 'string' && options.resumeSessionId.length > 0
|
|
696
|
+
? { [RUNNING_UPDATE_RESUME_SESSION_ENV]: options.resumeSessionId }
|
|
697
|
+
: {}),
|
|
698
|
+
},
|
|
652
699
|
stdio: 'inherit',
|
|
653
700
|
windowsHide: true,
|
|
654
701
|
});
|
|
@@ -873,6 +920,9 @@ async function runLauncher(options = {}) {
|
|
|
873
920
|
deferredPendingRuntime.packageRoot,
|
|
874
921
|
deferredPendingRuntime.cwd || callerCwd,
|
|
875
922
|
pendingArgs,
|
|
923
|
+
deferredPendingRuntime.mode === RUNNING_UPDATE_MODES.RESUME
|
|
924
|
+
? { resumeSessionId: deferredPendingRuntime.sessionId }
|
|
925
|
+
: {},
|
|
876
926
|
);
|
|
877
927
|
return;
|
|
878
928
|
}
|
package/blun.mjs
CHANGED
|
@@ -515728,20 +515728,28 @@ const {
|
|
|
515728
515728
|
readRunningUpdateMode,
|
|
515729
515729
|
writeRunningUpdateMode
|
|
515730
515730
|
} = __require("./bin/running-update-preference.cjs");
|
|
515731
|
-
|
|
515732
|
-
|
|
515733
|
-
|
|
515734
|
-
|
|
515731
|
+
const RUNNING_UPDATE_RESUME_SESSION_ENV = "BLUN_RUNNING_UPDATE_RESUME_SESSION_ID";
|
|
515732
|
+
function shouldContinueActiveGoalAfterRunningUpdate(expectedSessionId, currentSessionId, goalStatus) {
|
|
515733
|
+
return typeof expectedSessionId === "string" && expectedSessionId.length > 0 && currentSessionId === expectedSessionId && goalStatus === "active";
|
|
515734
|
+
}
|
|
515735
|
+
function sendRunningRuntimeSession(tui) {
|
|
515735
515736
|
if (!process.connected) return;
|
|
515736
515737
|
const sessionId = tui.getCurrentSessionId();
|
|
515737
515738
|
if (sessionId.length === 0) return;
|
|
515738
515739
|
try {
|
|
515739
515740
|
process.send({
|
|
515740
515741
|
type: RUNTIME_READY_MESSAGE,
|
|
515741
|
-
sessionId
|
|
515742
|
+
sessionId,
|
|
515743
|
+
cwd: tui.state.appState.workDir
|
|
515742
515744
|
});
|
|
515743
515745
|
} catch {}
|
|
515744
515746
|
}
|
|
515747
|
+
function notifyRunningRuntimeReady(tui) {
|
|
515748
|
+
if (typeof process.env["BLUN_SHARED_HOME"] === "string" && process.env["BLUN_SHARED_HOME"].length > 0) void pruneRunningUpdateReleases(process.env["BLUN_SHARED_HOME"], {
|
|
515749
|
+
activePackageRoot: __dirname
|
|
515750
|
+
}).catch(() => {});
|
|
515751
|
+
sendRunningRuntimeSession(tui);
|
|
515752
|
+
}
|
|
515745
515753
|
function requestRunningUpdateAtSafeBoundary(tui) {
|
|
515746
515754
|
if (tui.runningUpdateHandoffStarted || tui.isShuttingDown || !process.connected) return false;
|
|
515747
515755
|
const version = tui.runningUpdatePreparedVersion;
|
|
@@ -516268,7 +516276,8 @@ var BlunTUI = class {
|
|
|
516268
516276
|
if (this.session !== void 0) {
|
|
516269
516277
|
this.sessionEventHandler.startSubscription();
|
|
516270
516278
|
if (shouldReplayHistory) {
|
|
516271
|
-
|
|
516279
|
+
const continuedAfterUpdate = this.continueActiveGoalAfterRunningUpdateIfNeeded();
|
|
516280
|
+
if (!continuedAfterUpdate) await this.promptStartupResumeGoalIfNeeded();
|
|
516272
516281
|
if (this.aborted) return;
|
|
516273
516282
|
}
|
|
516274
516283
|
this.showSessionWarnings(this.session);
|
|
@@ -516418,6 +516427,13 @@ var BlunTUI = class {
|
|
|
516418
516427
|
}
|
|
516419
516428
|
if (choice === "resume") await handleGoalCommand(this, "resume");
|
|
516420
516429
|
}
|
|
516430
|
+
continueActiveGoalAfterRunningUpdateIfNeeded() {
|
|
516431
|
+
const expectedSessionId = process.env[RUNNING_UPDATE_RESUME_SESSION_ENV];
|
|
516432
|
+
delete process.env[RUNNING_UPDATE_RESUME_SESSION_ENV];
|
|
516433
|
+
if (!shouldContinueActiveGoalAfterRunningUpdate(expectedSessionId, this.session?.id, this.state.appState.goal?.status)) return false;
|
|
516434
|
+
this.sendNormalUserInput(RESUME_GOAL_INPUT);
|
|
516435
|
+
return true;
|
|
516436
|
+
}
|
|
516421
516437
|
async stop(exitCode) {
|
|
516422
516438
|
if (this.isShuttingDown) return;
|
|
516423
516439
|
this.isShuttingDown = true;
|
|
@@ -517913,6 +517929,7 @@ var BlunTUI = class {
|
|
|
517913
517929
|
if (resumeState?.warning !== void 0) this.showStatus(uiText("blunTui.warning", { warning: resumeState.warning }), "warning");
|
|
517914
517930
|
this.showStatus(statusMessage);
|
|
517915
517931
|
this.showSessionWarnings(session);
|
|
517932
|
+
sendRunningRuntimeSession(this);
|
|
517916
517933
|
}
|
|
517917
517934
|
async reloadCurrentSessionView(session, statusMessage) {
|
|
517918
517935
|
await this.personalMemoryController.clear(session);
|
|
@@ -517976,6 +517993,7 @@ var BlunTUI = class {
|
|
|
517976
517993
|
this.showStatus(uiText("blunTui.session.started", { sessionId: session.id }));
|
|
517977
517994
|
this.showSessionWarnings(session);
|
|
517978
517995
|
this.showConfigWarningsIfAny();
|
|
517996
|
+
sendRunningRuntimeSession(this);
|
|
517979
517997
|
}
|
|
517980
517998
|
/** Surface config.toml load warnings (degraded or kept-previous config) in the status bar. */
|
|
517981
517999
|
async showConfigWarningsIfAny() {
|