blun-king-cli 9.1.601 → 9.1.603

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 (124) hide show
  1. package/CHANGELOG.md +105 -94
  2. package/LIESMICH.txt +57 -57
  3. package/README.md +90 -90
  4. package/bin/agentspine-king-goal-inbox.mjs +127 -127
  5. package/bin/agentspine-king-goal-intake.mjs +106 -106
  6. package/bin/agentspine-king-host-runner.mjs +109 -109
  7. package/bin/agentspine-king-snapshot-policy.cjs +80 -80
  8. package/bin/agentspine-king-status-policy.mjs +226 -226
  9. package/bin/agentspine-king-worker-host.mjs +160 -160
  10. package/bin/core-bootstrap.js +77 -77
  11. package/bin/launcher-mode.js +93 -93
  12. package/bin/launcher-restart-policy.cjs +131 -131
  13. package/bin/launcher-runtime.js +491 -491
  14. package/bin/managed-node.js +186 -186
  15. package/bin/managed-plugin-selection.cjs +115 -115
  16. package/bin/native-module-repair.js +201 -201
  17. package/bin/node-runtime.js +34 -34
  18. package/bin/node-version.js +49 -49
  19. package/bin/plugin-bootstrap.js +162 -162
  20. package/bin/private-paths.js +1606 -1606
  21. package/bin/runtime-exit-ledger.cjs +144 -144
  22. package/bin/runtime-exit-ledger.d.cts +23 -23
  23. package/bin/standard-tools-bootstrap.js +272 -272
  24. package/bin/turn-thinking-policy.cjs +82 -82
  25. package/bin/update-copy.js +200 -200
  26. package/bin/update-lease.js +597 -597
  27. package/bin/update-notice.js +1568 -1568
  28. package/bin/verify-agent-behavior.cjs +122 -122
  29. package/bin/verify-agent-components.cjs +104 -104
  30. package/bin/verify-bundled-agent-sources.cjs +83 -83
  31. package/bin/windows-node-crash-dump.cjs +289 -289
  32. package/blun.mjs +1709 -1601
  33. package/codebase-index/codebase_index.py +470 -470
  34. package/dist-web/assets/{CodeBlockNode-CXnTLMNr.js → CodeBlockNode-TKKkJX8L.js} +1 -1
  35. package/dist-web/assets/{CodeBlockShell.vue_vue_type_style_index_0_lang-CCF8Ntnq.js → CodeBlockShell.vue_vue_type_style_index_0_lang-CUj2Ds5x.js} +1 -1
  36. package/dist-web/assets/DesignSystemView-DwK1aQMo.js +12 -0
  37. package/dist-web/assets/DesignSystemView-szjTj7ns.css +1 -0
  38. package/dist-web/assets/{Tooltip-CFQEzqC-.js → Tooltip-CTWv7WCU.js} +1 -1
  39. package/dist-web/assets/{abnfDiagram-N423BO3Z-Z1APZG5m.js → abnfDiagram-N423BO3Z-DA8V8Zjh.js} +1 -1
  40. package/dist-web/assets/{arc-C4VyWloT.js → arc-YtddKbdY.js} +1 -1
  41. package/dist-web/assets/{architectureDiagram-T3A2C74G-DpddbUcM.js → architectureDiagram-T3A2C74G-CGEiObmo.js} +1 -1
  42. package/dist-web/assets/{blockDiagram-VBNYF7ZC-BqccOSMr.js → blockDiagram-VBNYF7ZC-Bd5Vt50e.js} +1 -1
  43. package/dist-web/assets/{c4Diagram-5PPSVZJV-OHAcJrTy.js → c4Diagram-5PPSVZJV-D2ApPM54.js} +1 -1
  44. package/dist-web/assets/channel-BnluexuN.js +1 -0
  45. package/dist-web/assets/{chunk-2GRJ4B5K-DqJHs8Fw.js → chunk-2GRJ4B5K-BDcIEZSl.js} +1 -1
  46. package/dist-web/assets/{chunk-2Q5K7J3B-B1nCd91o.js → chunk-2Q5K7J3B-6aSabCX7.js} +1 -1
  47. package/dist-web/assets/{chunk-5RXB4S5H-neG70kZx.js → chunk-5RXB4S5H-BFRQvIJS.js} +1 -1
  48. package/dist-web/assets/{chunk-5VM5RSS4-l7biGqeH.js → chunk-5VM5RSS4-ChAa-DNY.js} +1 -1
  49. package/dist-web/assets/{chunk-6Q2QTUOP-BO-F11Gs.js → chunk-6Q2QTUOP-BIfrb2Jc.js} +1 -1
  50. package/dist-web/assets/{chunk-GF5L2VYU-BtC69fw0.js → chunk-GF5L2VYU-BR0gvtWI.js} +1 -1
  51. package/dist-web/assets/{chunk-JWPE2WC7-DIOubEqr.js → chunk-JWPE2WC7-BgXKsx5E.js} +1 -1
  52. package/dist-web/assets/{chunk-KBJHAD2P-DDUeH_Ba.js → chunk-KBJHAD2P-I8mrZhpz.js} +1 -1
  53. package/dist-web/assets/{chunk-RYQCIY6F-B9gFMVbS.js → chunk-RYQCIY6F-BMPaem92.js} +1 -1
  54. package/dist-web/assets/{chunk-XXDRQBXY-Bn1Xz6TQ.js → chunk-XXDRQBXY-COy0J-0j.js} +1 -1
  55. package/dist-web/assets/classDiagram-JCYQIIEL-BliFdSD9.js +1 -0
  56. package/dist-web/assets/classDiagram-v2-OCEON4UE-BliFdSD9.js +1 -0
  57. package/dist-web/assets/{cose-bilkent-JH36ORCC-DqigO3T4.js → cose-bilkent-JH36ORCC-MosTTRdo.js} +1 -1
  58. package/dist-web/assets/{cynefin-VYW2F7L2-rpD93tQ-.js → cynefin-VYW2F7L2-5tjPgrkP.js} +1 -1
  59. package/dist-web/assets/{cynefinDiagram-MW4NZA55-DbTqnkVJ.js → cynefinDiagram-MW4NZA55-CbDcdInR.js} +1 -1
  60. package/dist-web/assets/{dagre-VZM6K2ZE-DcM8aEVY.js → dagre-VZM6K2ZE-YaBom1ZH.js} +1 -1
  61. package/dist-web/assets/{diagram-7IWD3JNH-UymgsBNa.js → diagram-7IWD3JNH-Da4m-Hgz.js} +1 -1
  62. package/dist-web/assets/{diagram-B4RE2ZJO-BiYu5WPF.js → diagram-B4RE2ZJO-CAUUegxg.js} +1 -1
  63. package/dist-web/assets/{diagram-LBJQPF4R-BD3kkgOO.js → diagram-LBJQPF4R-o21mWKG4.js} +1 -1
  64. package/dist-web/assets/{diagram-Q27KOJAE-D1mYQ_cI.js → diagram-Q27KOJAE-BA3z3sXf.js} +1 -1
  65. package/dist-web/assets/{diagram-UB23O5K3-BtX8u6HG.js → diagram-UB23O5K3-CGRdSSDK.js} +1 -1
  66. package/dist-web/assets/{ebnfDiagram-BXEA7PRR-CBAnFkRF.js → ebnfDiagram-BXEA7PRR-Bw-iU9FP.js} +1 -1
  67. package/dist-web/assets/{erDiagram-JOGREHBK-3vKcWsxm.js → erDiagram-JOGREHBK-BwM1yiR0.js} +1 -1
  68. package/dist-web/assets/{flowDiagram-UKHOOZJN-D9wBbHm2.js → flowDiagram-UKHOOZJN-z05eOR1Q.js} +1 -1
  69. package/dist-web/assets/{ganttDiagram-PKOTCBZU-Cd_dViF-.js → ganttDiagram-PKOTCBZU-BMUubNTl.js} +1 -1
  70. package/dist-web/assets/{gitGraphDiagram-DS77QQ5N-BiEEtTo7.js → gitGraphDiagram-DS77QQ5N-Dccbi1wU.js} +1 -1
  71. package/dist-web/assets/{index-DdEakFJM.js → index-BBbC5LRF.js} +1 -1
  72. package/dist-web/assets/{index-BmKj7CB1.css → index-C9eDH3u-.css} +1 -1
  73. package/dist-web/assets/{index-Ba1OvLjf.js → index-D48p0M2S.js} +2 -2
  74. package/dist-web/assets/{index-CiPA8Kex.js → index-h0KEOmkU.js} +14 -14
  75. package/dist-web/assets/{index10-CNr4U-Nw.js → index10-Qbuw1N6u.js} +1 -1
  76. package/dist-web/assets/{index11-Dto_yZNz.js → index11-g8v025wN.js} +1 -1
  77. package/dist-web/assets/{index5-B2PldaO2.js → index5-BYNcV8BY.js} +1 -1
  78. package/dist-web/assets/{index6-CBdZmmyx.js → index6-BetANtnV.js} +1 -1
  79. package/dist-web/assets/{index7-Dz9oCsUF.js → index7-BcPbgCKN.js} +1 -1
  80. package/dist-web/assets/{index8-B6k25jtU.js → index8-B1lp-RvF.js} +2 -2
  81. package/dist-web/assets/{infoDiagram-6WML65LV-D4ph2sIH.js → infoDiagram-6WML65LV-BF_pbUnN.js} +1 -1
  82. package/dist-web/assets/{ishikawaDiagram-WSZJBQD7-DjKjbMm9.js → ishikawaDiagram-WSZJBQD7-Dedv4urS.js} +1 -1
  83. package/dist-web/assets/{journeyDiagram-NVQOT4AX-DttHDfPQ.js → journeyDiagram-NVQOT4AX-B1Ex3b6o.js} +1 -1
  84. package/dist-web/assets/{kanban-definition-27J2QSJJ-C5gB-W0d.js → kanban-definition-27J2QSJJ-BtCGliBH.js} +1 -1
  85. package/dist-web/assets/{linear-BRGe4-d6.js → linear-ByItc8Qr.js} +1 -1
  86. package/dist-web/assets/{mermaid.core-D-9Sx1No.js → mermaid.core-B1M2O7nN.js} +4 -4
  87. package/dist-web/assets/{mindmap-definition-FAOFIHXS-Cg_hoVIq.js → mindmap-definition-FAOFIHXS-D0UHKUyj.js} +1 -1
  88. package/dist-web/assets/{pegDiagram-VL7TDLO6-CSX9hitD.js → pegDiagram-VL7TDLO6-D7zYCVmG.js} +1 -1
  89. package/dist-web/assets/{pieDiagram-7S7Q4E2Y-0L0mxahZ.js → pieDiagram-7S7Q4E2Y-Bd0i9CHa.js} +1 -1
  90. package/dist-web/assets/{quadrantDiagram-CIZ2JOQS-U483z0er.js → quadrantDiagram-CIZ2JOQS-MG-Ug47d.js} +1 -1
  91. package/dist-web/assets/{railroadDiagram-AXF67PYL-D80a2_DJ.js → railroadDiagram-AXF67PYL-AZdl90Gj.js} +1 -1
  92. package/dist-web/assets/{requirementDiagram-LRYGKXZP-CeyHfq8t.js → requirementDiagram-LRYGKXZP-DjplF9kt.js} +1 -1
  93. package/dist-web/assets/{sankeyDiagram-W5VNT64P-CCISkkrR.js → sankeyDiagram-W5VNT64P-Bi9Zgfjs.js} +1 -1
  94. package/dist-web/assets/{sequenceDiagram-SI44F4Z6-jQfm2XS7.js → sequenceDiagram-SI44F4Z6-ChEpdCvb.js} +1 -1
  95. package/dist-web/assets/{sizeCapture-X5ZJPWSS-lRZsmCoS.js → sizeCapture-X5ZJPWSS-B5RFUdaa.js} +1 -1
  96. package/dist-web/assets/{stateDiagram-OKZ733FA-ClCgTdUm.js → stateDiagram-OKZ733FA-BkRblSKp.js} +1 -1
  97. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fVkQcACW.js +1 -0
  98. package/dist-web/assets/{swimlanes-SLNWSIFB-BCdOUN05.js → swimlanes-SLNWSIFB-DLXH6Zhc.js} +2 -2
  99. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-Bg-BiZJQ.js +8 -0
  100. package/dist-web/assets/{timeline-definition-Z64GVDOM-DpWutSKa.js → timeline-definition-Z64GVDOM-C_N88siO.js} +1 -1
  101. package/dist-web/assets/{vennDiagram-T6HMQDX7-DVC5JVsc.js → vennDiagram-T6HMQDX7-DMsnvDt0.js} +1 -1
  102. package/dist-web/assets/{vue.runtime.esm-bundler-BwwRtQwQ.js → vue.runtime.esm-bundler-B-3uIy85.js} +1 -1
  103. package/dist-web/assets/{wardleyDiagram-T6FBY63Y-BK2mrq7L.js → wardleyDiagram-T6FBY63Y-C9SJIPzq.js} +1 -1
  104. package/dist-web/assets/{xychartDiagram-ELKLHX3M-DUJu9F7-.js → xychartDiagram-ELKLHX3M-DZLWA6Go.js} +1 -1
  105. package/dist-web/index.html +32 -32
  106. package/dist-web/vis/index.html.gz +0 -0
  107. package/package.json +1 -1
  108. package/scripts/fix-node-pty-perms.js +53 -53
  109. package/standard-skills/blun-app-design-system/SKILL.md +30 -30
  110. package/standard-skills/blun-app-design-system/provenance.json +17 -17
  111. package/standard-skills/blun-app-design-system/references/tokens.md +48 -48
  112. package/standard-skills/blun-app-design-system/scripts/check-blun-design.mjs +49 -49
  113. package/telegram-plugin/blun.plugin.json +20 -20
  114. package/telegram-plugin/commands/access.md +37 -37
  115. package/telegram-plugin/dist/bridge.mjs +81 -81
  116. package/telegram-plugin/dist/mcp-server.mjs +158 -158
  117. package/worker-host.mjs +932 -932
  118. package/dist-web/assets/DesignSystemView-6HgAUEmC.css +0 -1
  119. package/dist-web/assets/DesignSystemView-BbMOu-XO.js +0 -12
  120. package/dist-web/assets/channel-B2VOQOI-.js +0 -1
  121. package/dist-web/assets/classDiagram-JCYQIIEL-BA1TjAxj.js +0 -1
  122. package/dist-web/assets/classDiagram-v2-OCEON4UE-BA1TjAxj.js +0 -1
  123. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fi_lrTAx.js +0 -1
  124. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-BUd_uYK3.js +0 -8
@@ -1,160 +1,160 @@
1
- #!/usr/bin/env node
2
- import { appendFile, mkdir, readFile, realpath } from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { fileURLToPath, pathToFileURL } from 'node:url';
5
- import { setTimeout as delay } from 'node:timers/promises';
6
- import { createGoalHostFactory } from './agentspine-king-host-runner.mjs';
7
- import { ingestGoalInbox } from './agentspine-king-goal-inbox.mjs';
8
- import { createGoalStatusReporter, goalResultRejectionCode } from './agentspine-king-status-policy.mjs';
9
- import snapshotPolicy from './agentspine-king-snapshot-policy.cjs';
10
-
11
- const packageRoot = path.resolve(import.meta.dirname, '..');
12
- const required = ['blunHome', 'profile', 'projectRoot', 'projectId', 'standFile', 'expectedStandSha256',
13
- 'expectedSnapshotSha256', 'exchangeDirectory', 'telegramQueueFile', 'instructionSenderId', 'instructionChatId',
14
- 'logicalPersonaId', 'runtimePersonaId', 'groupId', 'ownerSubjectId', 'permission', 'gatewayStateDirectory'];
15
-
16
- export async function loadWorkerConfig(file) {
17
- const config = JSON.parse(await readFile(path.resolve(file), 'utf8'));
18
- if (config?.schema !== 'blun.king-goal-worker-config/v1'
19
- || required.some(key => typeof config[key] !== 'string' || !config[key])
20
- || (config.instructionThreadId !== null && config.instructionThreadId !== undefined && (typeof config.instructionThreadId !== 'string'
21
- || !/^[1-9][0-9]*$/.test(config.instructionThreadId) || !Number.isSafeInteger(Number(config.instructionThreadId))))
22
- || !['manual', 'auto', 'yolo'].includes(config.permission)) throw new Error('KING_WORKER_CONFIG_INVALID');
23
- for (const key of ['blunHome', 'projectRoot', 'exchangeDirectory', 'telegramQueueFile', 'gatewayStateDirectory']) {
24
- if (!path.isAbsolute(config[key])) throw new Error('KING_WORKER_ABSOLUTE_PATH_REQUIRED');
25
- }
26
- const blunHome = await realpath(config.blunHome);
27
- const relative = path.relative(blunHome, path.resolve(config.gatewayStateDirectory));
28
- if (path.isAbsolute(relative) || relative === '..' || relative.startsWith('..' + path.sep)) throw new Error('KING_WORKER_STATE_OUTSIDE_APP_HOME');
29
- const state = await realpath(config.gatewayStateDirectory);
30
- const canonicalRelative = path.relative(blunHome, state);
31
- if (path.isAbsolute(canonicalRelative) || canonicalRelative === '..' || canonicalRelative.startsWith('..' + path.sep)) {
32
- throw new Error('KING_WORKER_STATE_OUTSIDE_APP_HOME');
33
- }
34
- return Object.freeze({ ...config, blunHome, gatewayStateDirectory: state, projectRoot: await realpath(config.projectRoot) });
35
- }
36
-
37
- export async function runGuardedWorkerTick(context) {
38
- const { config, gateway, hostFactory, env, adapter } = context;
39
- const snapshot = snapshotPolicy.verifyWorkspaceSnapshot({ root: config.projectRoot, projectId: config.projectId,
40
- standFile: config.standFile, expectedStandSha256: config.expectedStandSha256,
41
- expectedSnapshotSha256: config.expectedSnapshotSha256 });
42
- const personas = await gateway.loadPersonaRuntime(snapshot.root);
43
- const persona = personas.runtime.personas.find(item => item.personaId === config.runtimePersonaId && item.status === 'active');
44
- const binding = personas.policy.bindings.find(item => item.id === persona?.bindingId && item.active);
45
- if (!binding || binding.profileId !== config.profile || binding.groupId !== config.groupId) {
46
- throw new Error('KING_WORKER_IDENTITY_MISMATCH');
47
- }
48
- const { withOwnedFileLock } = await import(pathToFileURL(path.join(packageRoot,
49
- 'agent-spine-plugin/src/lib/owned-file-lock.js')).href);
50
- const statusReporter = createGoalStatusReporter({
51
- stateFile: path.join(config.blunHome, 'worker', 'goal-status-v2.json'),
52
- scope: { logicalPersonaId: config.logicalPersonaId, runtimePersonaId: config.runtimePersonaId,
53
- projectId: config.projectId, groupId: config.groupId, ownerSubjectId: config.ownerSubjectId,
54
- chatId: config.instructionChatId, senderId: config.instructionSenderId,
55
- threadId: config.instructionThreadId ?? null },
56
- withOwnedFileLock, loadPolicy: () => gateway.loadChannelPolicy(snapshot.root), adapter,
57
- });
58
- const statusReports = [];
59
- const reportCommittedStatus = async () => {
60
- const context = await gateway.gatewayContext({ root: snapshot.root, agentId: config.runtimePersonaId });
61
- if (!context.enabled || context.killSwitch) return;
62
- await statusReporter.reconcile(context);
63
- statusReports.push(...await statusReporter.flush());
64
- };
65
- // Reconcile the saved goal state first: a prior process may have stopped after committing its tick.
66
- await reportCommittedStatus();
67
- const intake = await ingestGoalInbox({ exchangeDirectory: config.exchangeDirectory,
68
- queueFile: config.telegramQueueFile, checkpointFile: path.join(config.blunHome, 'worker', 'goal-queue-checkpoint.json'),
69
- instructionSenderId: config.instructionSenderId, instructionChatId: config.instructionChatId,
70
- instructionThreadId: config.instructionThreadId,
71
- logicalPersonaId: config.logicalPersonaId, runtimePersonaId: config.runtimePersonaId,
72
- projectId: config.projectId, groupId: config.groupId, ownerSubjectId: config.ownerSubjectId,
73
- snapshot, assignGoal: gateway.assignGoal,
74
- recordResult: result => result.status === 'rejected'
75
- ? statusReporter.record({ kind: 'intake-rejected', eventId: result.eventId,
76
- messageId: result.messageId, error: result.error }) : undefined });
77
- const result = await gateway.runWorkerTick({ root: snapshot.root, workerId: 'gateway-worker:blun:' + process.pid,
78
- agentId: config.runtimePersonaId, projectId: config.projectId, groupId: config.groupId,
79
- goalOnly: true, env,
80
- hostFactory: async item => {
81
- const host = await hostFactory(item);
82
- return { ...host, async run() {
83
- try { return await host.run(); }
84
- catch (error) {
85
- const rejection = goalResultRejectionCode(error);
86
- if (rejection) {
87
- // Record the concrete parser failure before the gateway replaces it with an execution hold.
88
- await statusReporter.record({ kind: 'result-rejected', eventId: `${item.queueId}:${item.attempts}`,
89
- goalId: item.goal.goalId, goalStepId: item.goalStep?.stepId ?? null,
90
- attempt: item.attempts, error: rejection });
91
- }
92
- throw error;
93
- }
94
- } };
95
- },
96
- adapter: { send: item => adapter.send(item) } });
97
- await reportCommittedStatus();
98
- return { ...result, intake, statusReports };
99
- }
100
-
101
- export async function runWorkerHost(config, { once = false, signal, onResult = () => {} } = {}) {
102
- const gateway = await import(pathToFileURL(path.join(packageRoot, 'agent-spine-plugin/src/index.js')).href);
103
- const { createKingWorkerHarness, LocalKaos } = await import(pathToFileURL(path.join(packageRoot, 'worker-host.mjs')).href);
104
- const env = { ...process.env, BLUN_HOME: config.blunHome, BLUN_PROFILE: config.profile };
105
- const harness = createKingWorkerHarness({ homeDir: config.blunHome, configPath: config.settingsPath });
106
- const hostFactory = createGoalHostFactory({ harness, workDir: config.projectRoot, model: config.model,
107
- permission: config.permission, tickTimeoutMs: config.tickTimeoutMs, cleanupTimeoutMs: config.cleanupTimeoutMs,
108
- sessionOptions: { kaos: (await LocalKaos.create()).withCwd(config.projectRoot).withEnv({
109
- BLUN_HOME: config.blunHome, BLUN_PROFILE: config.profile, AGENTSPINE_STATE_DIR: config.gatewayStateDirectory,
110
- }) },
111
- signal });
112
- const adapter = gateway.createTelegramAdapter({ root: config.projectRoot, env });
113
- try {
114
- do {
115
- if (signal?.aborted) return { status: 'stopped', processed: false };
116
- const result = await runGuardedWorkerTick({ config, gateway, hostFactory, env, adapter });
117
- await mkdir(path.join(config.blunHome, 'worker'), { recursive: true });
118
- await appendFile(path.join(config.blunHome, 'worker', 'worker.log'), JSON.stringify({
119
- schema: 'blun.king-goal-worker-log/v1', at: new Date().toISOString(), status: result.status,
120
- queueId: result.queueId ?? null, hostSettlement: result.hostSettlement ?? null,
121
- statusReports: result.statusReports,
122
- intake: result.intake.map(item => ({ status: item.status, messageId: item.messageId, error: item.error }))
123
- }) + '\n', { mode: 0o600 });
124
- await onResult(result);
125
- if (once || result.status === 'stopped' || result.status === 'blocked') return result;
126
- await delay(result.processed ? 1000 : 15000, undefined, { signal }).catch(error => {
127
- if (error.name !== 'AbortError') throw error;
128
- });
129
- } while (!signal?.aborted);
130
- return { status: 'stopped', processed: false };
131
- } finally {
132
- let quiet = true;
133
- for (const session of harness.sessions.values()) {
134
- const proof = await session.quiesce({ deadlineAt: Date.now() + (config.cleanupTimeoutMs ?? 10000) }).catch(() => null);
135
- if (!proof || proof.status !== 'quiescent' || proof.remaining.length) quiet = false;
136
- }
137
- // Never turn a cleanup deadline into a successful host termination acknowledgement.
138
- if (quiet) await harness.close();
139
- }
140
- }
141
-
142
- async function main() {
143
- const args = process.argv.slice(2);
144
- const index = args.indexOf('--config');
145
- if (index < 0 || !args[index + 1] || args[index + 1].startsWith('--')) throw new Error('--config requires a path');
146
- const config = await loadWorkerConfig(args[index + 1]);
147
- process.env.BLUN_HOME = config.blunHome;
148
- process.env.BLUN_PROFILE = config.profile;
149
- process.env.AGENTSPINE_STATE_DIR = config.gatewayStateDirectory;
150
- const controller = new AbortController();
151
- const stop = () => controller.abort();
152
- process.once('SIGINT', stop); process.once('SIGTERM', stop);
153
- try { await runWorkerHost(config, { once: args.includes('--once'), signal: controller.signal,
154
- onResult: result => process.stdout.write(JSON.stringify(result) + '\n') }); }
155
- finally { process.removeListener('SIGINT', stop); process.removeListener('SIGTERM', stop); }
156
- }
157
-
158
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
159
- main().catch(error => { process.stderr.write(String(error.message).slice(0, 500) + '\n'); process.exitCode = 1; });
160
- }
1
+ #!/usr/bin/env node
2
+ import { appendFile, mkdir, readFile, realpath } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath, pathToFileURL } from 'node:url';
5
+ import { setTimeout as delay } from 'node:timers/promises';
6
+ import { createGoalHostFactory } from './agentspine-king-host-runner.mjs';
7
+ import { ingestGoalInbox } from './agentspine-king-goal-inbox.mjs';
8
+ import { createGoalStatusReporter, goalResultRejectionCode } from './agentspine-king-status-policy.mjs';
9
+ import snapshotPolicy from './agentspine-king-snapshot-policy.cjs';
10
+
11
+ const packageRoot = path.resolve(import.meta.dirname, '..');
12
+ const required = ['blunHome', 'profile', 'projectRoot', 'projectId', 'standFile', 'expectedStandSha256',
13
+ 'expectedSnapshotSha256', 'exchangeDirectory', 'telegramQueueFile', 'instructionSenderId', 'instructionChatId',
14
+ 'logicalPersonaId', 'runtimePersonaId', 'groupId', 'ownerSubjectId', 'permission', 'gatewayStateDirectory'];
15
+
16
+ export async function loadWorkerConfig(file) {
17
+ const config = JSON.parse(await readFile(path.resolve(file), 'utf8'));
18
+ if (config?.schema !== 'blun.king-goal-worker-config/v1'
19
+ || required.some(key => typeof config[key] !== 'string' || !config[key])
20
+ || (config.instructionThreadId !== null && config.instructionThreadId !== undefined && (typeof config.instructionThreadId !== 'string'
21
+ || !/^[1-9][0-9]*$/.test(config.instructionThreadId) || !Number.isSafeInteger(Number(config.instructionThreadId))))
22
+ || !['manual', 'auto', 'yolo'].includes(config.permission)) throw new Error('KING_WORKER_CONFIG_INVALID');
23
+ for (const key of ['blunHome', 'projectRoot', 'exchangeDirectory', 'telegramQueueFile', 'gatewayStateDirectory']) {
24
+ if (!path.isAbsolute(config[key])) throw new Error('KING_WORKER_ABSOLUTE_PATH_REQUIRED');
25
+ }
26
+ const blunHome = await realpath(config.blunHome);
27
+ const relative = path.relative(blunHome, path.resolve(config.gatewayStateDirectory));
28
+ if (path.isAbsolute(relative) || relative === '..' || relative.startsWith('..' + path.sep)) throw new Error('KING_WORKER_STATE_OUTSIDE_APP_HOME');
29
+ const state = await realpath(config.gatewayStateDirectory);
30
+ const canonicalRelative = path.relative(blunHome, state);
31
+ if (path.isAbsolute(canonicalRelative) || canonicalRelative === '..' || canonicalRelative.startsWith('..' + path.sep)) {
32
+ throw new Error('KING_WORKER_STATE_OUTSIDE_APP_HOME');
33
+ }
34
+ return Object.freeze({ ...config, blunHome, gatewayStateDirectory: state, projectRoot: await realpath(config.projectRoot) });
35
+ }
36
+
37
+ export async function runGuardedWorkerTick(context) {
38
+ const { config, gateway, hostFactory, env, adapter } = context;
39
+ const snapshot = snapshotPolicy.verifyWorkspaceSnapshot({ root: config.projectRoot, projectId: config.projectId,
40
+ standFile: config.standFile, expectedStandSha256: config.expectedStandSha256,
41
+ expectedSnapshotSha256: config.expectedSnapshotSha256 });
42
+ const personas = await gateway.loadPersonaRuntime(snapshot.root);
43
+ const persona = personas.runtime.personas.find(item => item.personaId === config.runtimePersonaId && item.status === 'active');
44
+ const binding = personas.policy.bindings.find(item => item.id === persona?.bindingId && item.active);
45
+ if (!binding || binding.profileId !== config.profile || binding.groupId !== config.groupId) {
46
+ throw new Error('KING_WORKER_IDENTITY_MISMATCH');
47
+ }
48
+ const { withOwnedFileLock } = await import(pathToFileURL(path.join(packageRoot,
49
+ 'agent-spine-plugin/src/lib/owned-file-lock.js')).href);
50
+ const statusReporter = createGoalStatusReporter({
51
+ stateFile: path.join(config.blunHome, 'worker', 'goal-status-v2.json'),
52
+ scope: { logicalPersonaId: config.logicalPersonaId, runtimePersonaId: config.runtimePersonaId,
53
+ projectId: config.projectId, groupId: config.groupId, ownerSubjectId: config.ownerSubjectId,
54
+ chatId: config.instructionChatId, senderId: config.instructionSenderId,
55
+ threadId: config.instructionThreadId ?? null },
56
+ withOwnedFileLock, loadPolicy: () => gateway.loadChannelPolicy(snapshot.root), adapter,
57
+ });
58
+ const statusReports = [];
59
+ const reportCommittedStatus = async () => {
60
+ const context = await gateway.gatewayContext({ root: snapshot.root, agentId: config.runtimePersonaId });
61
+ if (!context.enabled || context.killSwitch) return;
62
+ await statusReporter.reconcile(context);
63
+ statusReports.push(...await statusReporter.flush());
64
+ };
65
+ // Reconcile the saved goal state first: a prior process may have stopped after committing its tick.
66
+ await reportCommittedStatus();
67
+ const intake = await ingestGoalInbox({ exchangeDirectory: config.exchangeDirectory,
68
+ queueFile: config.telegramQueueFile, checkpointFile: path.join(config.blunHome, 'worker', 'goal-queue-checkpoint.json'),
69
+ instructionSenderId: config.instructionSenderId, instructionChatId: config.instructionChatId,
70
+ instructionThreadId: config.instructionThreadId,
71
+ logicalPersonaId: config.logicalPersonaId, runtimePersonaId: config.runtimePersonaId,
72
+ projectId: config.projectId, groupId: config.groupId, ownerSubjectId: config.ownerSubjectId,
73
+ snapshot, assignGoal: gateway.assignGoal,
74
+ recordResult: result => result.status === 'rejected'
75
+ ? statusReporter.record({ kind: 'intake-rejected', eventId: result.eventId,
76
+ messageId: result.messageId, error: result.error }) : undefined });
77
+ const result = await gateway.runWorkerTick({ root: snapshot.root, workerId: 'gateway-worker:blun:' + process.pid,
78
+ agentId: config.runtimePersonaId, projectId: config.projectId, groupId: config.groupId,
79
+ goalOnly: true, env,
80
+ hostFactory: async item => {
81
+ const host = await hostFactory(item);
82
+ return { ...host, async run() {
83
+ try { return await host.run(); }
84
+ catch (error) {
85
+ const rejection = goalResultRejectionCode(error);
86
+ if (rejection) {
87
+ // Record the concrete parser failure before the gateway replaces it with an execution hold.
88
+ await statusReporter.record({ kind: 'result-rejected', eventId: `${item.queueId}:${item.attempts}`,
89
+ goalId: item.goal.goalId, goalStepId: item.goalStep?.stepId ?? null,
90
+ attempt: item.attempts, error: rejection });
91
+ }
92
+ throw error;
93
+ }
94
+ } };
95
+ },
96
+ adapter: { send: item => adapter.send(item) } });
97
+ await reportCommittedStatus();
98
+ return { ...result, intake, statusReports };
99
+ }
100
+
101
+ export async function runWorkerHost(config, { once = false, signal, onResult = () => {} } = {}) {
102
+ const gateway = await import(pathToFileURL(path.join(packageRoot, 'agent-spine-plugin/src/index.js')).href);
103
+ const { createKingWorkerHarness, LocalKaos } = await import(pathToFileURL(path.join(packageRoot, 'worker-host.mjs')).href);
104
+ const env = { ...process.env, BLUN_HOME: config.blunHome, BLUN_PROFILE: config.profile };
105
+ const harness = createKingWorkerHarness({ homeDir: config.blunHome, configPath: config.settingsPath });
106
+ const hostFactory = createGoalHostFactory({ harness, workDir: config.projectRoot, model: config.model,
107
+ permission: config.permission, tickTimeoutMs: config.tickTimeoutMs, cleanupTimeoutMs: config.cleanupTimeoutMs,
108
+ sessionOptions: { kaos: (await LocalKaos.create()).withCwd(config.projectRoot).withEnv({
109
+ BLUN_HOME: config.blunHome, BLUN_PROFILE: config.profile, AGENTSPINE_STATE_DIR: config.gatewayStateDirectory,
110
+ }) },
111
+ signal });
112
+ const adapter = gateway.createTelegramAdapter({ root: config.projectRoot, env });
113
+ try {
114
+ do {
115
+ if (signal?.aborted) return { status: 'stopped', processed: false };
116
+ const result = await runGuardedWorkerTick({ config, gateway, hostFactory, env, adapter });
117
+ await mkdir(path.join(config.blunHome, 'worker'), { recursive: true });
118
+ await appendFile(path.join(config.blunHome, 'worker', 'worker.log'), JSON.stringify({
119
+ schema: 'blun.king-goal-worker-log/v1', at: new Date().toISOString(), status: result.status,
120
+ queueId: result.queueId ?? null, hostSettlement: result.hostSettlement ?? null,
121
+ statusReports: result.statusReports,
122
+ intake: result.intake.map(item => ({ status: item.status, messageId: item.messageId, error: item.error }))
123
+ }) + '\n', { mode: 0o600 });
124
+ await onResult(result);
125
+ if (once || result.status === 'stopped' || result.status === 'blocked') return result;
126
+ await delay(result.processed ? 1000 : 15000, undefined, { signal }).catch(error => {
127
+ if (error.name !== 'AbortError') throw error;
128
+ });
129
+ } while (!signal?.aborted);
130
+ return { status: 'stopped', processed: false };
131
+ } finally {
132
+ let quiet = true;
133
+ for (const session of harness.sessions.values()) {
134
+ const proof = await session.quiesce({ deadlineAt: Date.now() + (config.cleanupTimeoutMs ?? 10000) }).catch(() => null);
135
+ if (!proof || proof.status !== 'quiescent' || proof.remaining.length) quiet = false;
136
+ }
137
+ // Never turn a cleanup deadline into a successful host termination acknowledgement.
138
+ if (quiet) await harness.close();
139
+ }
140
+ }
141
+
142
+ async function main() {
143
+ const args = process.argv.slice(2);
144
+ const index = args.indexOf('--config');
145
+ if (index < 0 || !args[index + 1] || args[index + 1].startsWith('--')) throw new Error('--config requires a path');
146
+ const config = await loadWorkerConfig(args[index + 1]);
147
+ process.env.BLUN_HOME = config.blunHome;
148
+ process.env.BLUN_PROFILE = config.profile;
149
+ process.env.AGENTSPINE_STATE_DIR = config.gatewayStateDirectory;
150
+ const controller = new AbortController();
151
+ const stop = () => controller.abort();
152
+ process.once('SIGINT', stop); process.once('SIGTERM', stop);
153
+ try { await runWorkerHost(config, { once: args.includes('--once'), signal: controller.signal,
154
+ onResult: result => process.stdout.write(JSON.stringify(result) + '\n') }); }
155
+ finally { process.removeListener('SIGINT', stop); process.removeListener('SIGTERM', stop); }
156
+ }
157
+
158
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
159
+ main().catch(error => { process.stderr.write(String(error.message).slice(0, 500) + '\n'); process.exitCode = 1; });
160
+ }
@@ -1,77 +1,77 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
- const { pathToFileURL } = require('node:url');
6
-
7
- const { acquireSharedRuntimeLease } = require('./update-lease');
8
- const { installWindowsNodeCrashDump } = require('./windows-node-crash-dump.cjs');
9
- const { recordRuntimeExit } = require('./runtime-exit-ledger.cjs');
10
- const { isStandaloneHelpRequest } = require('./launcher-mode');
11
-
12
- const CORE_LOADED_MESSAGE = 'blun-core-bootstrap-loaded';
13
-
14
- async function runCoreBootstrap() {
15
- const informational = isStandaloneHelpRequest(process.argv.slice(2));
16
- let diagnostics;
17
- if (!informational) {
18
- try { diagnostics = installWindowsNodeCrashDump({ homeDir: process.env.BLUN_HOME }); } catch {}
19
- }
20
- const packageRoot = path.resolve(__dirname, '..');
21
- const mainPath = path.join(packageRoot, 'blun.mjs');
22
- let leaseResult;
23
-
24
- try {
25
- leaseResult = await acquireSharedRuntimeLease({ packageRoot });
26
- if (!leaseResult.acquired) throw new Error('RUNTIME_PROTECTION_UNAVAILABLE');
27
- leaseResult.lease.assertOwned();
28
- process.argv = [process.execPath, mainPath, ...process.argv.slice(2)];
29
- await import(pathToFileURL(mainPath).href);
30
- leaseResult.lease.assertOwned();
31
- } catch (error) {
32
- if (!informational) {
33
- try {
34
- if (diagnostics?.enabled) diagnostics.write(error, 'bootstrap-failure');
35
- else recordRuntimeExit({ homeDir: process.env.BLUN_HOME, source: 'bootstrap',
36
- kind: 'bootstrap-failure', phase: 'startup', error });
37
- } catch {}
38
- }
39
- try { diagnostics?.dispose(); } catch {}
40
- try { await leaseResult?.lease?.release(); }
41
- catch (leaseError) {
42
- if (!informational) {
43
- try {
44
- recordRuntimeExit({ homeDir: process.env.BLUN_HOME, source: 'bootstrap',
45
- kind: 'lease-release-failure', phase: 'cleanup', error: leaseError });
46
- } catch {}
47
- }
48
- }
49
- throw error;
50
- }
51
-
52
- // The bundled CLI loads package assets lazily after module evaluation. Keep
53
- // the install lease for this process's lifetime, without keeping print/help
54
- // commands alive solely because of the owner server.
55
- leaseResult.lease.unref();
56
-
57
- if (process.connected) {
58
- try {
59
- process.send({ type: CORE_LOADED_MESSAGE }, () => {
60
- try {
61
- process.disconnect();
62
- } catch {}
63
- });
64
- } catch {}
65
- }
66
- }
67
-
68
- if (require.main === module) {
69
- // oxlint-disable-next-line unicorn/prefer-top-level-await -- Worker entry is CommonJS.
70
- void runCoreBootstrap().catch((error) => {
71
- const message = error && error.message ? error.message : String(error);
72
- fs.writeSync(process.stderr.fd, `BLUN Start fehlgeschlagen: ${message}\n`);
73
- process.exit(1);
74
- });
75
- }
76
-
77
- module.exports = { CORE_LOADED_MESSAGE, runCoreBootstrap };
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { pathToFileURL } = require('node:url');
6
+
7
+ const { acquireSharedRuntimeLease } = require('./update-lease');
8
+ const { installWindowsNodeCrashDump } = require('./windows-node-crash-dump.cjs');
9
+ const { recordRuntimeExit } = require('./runtime-exit-ledger.cjs');
10
+ const { isStandaloneHelpRequest } = require('./launcher-mode');
11
+
12
+ const CORE_LOADED_MESSAGE = 'blun-core-bootstrap-loaded';
13
+
14
+ async function runCoreBootstrap() {
15
+ const informational = isStandaloneHelpRequest(process.argv.slice(2));
16
+ let diagnostics;
17
+ if (!informational) {
18
+ try { diagnostics = installWindowsNodeCrashDump({ homeDir: process.env.BLUN_HOME }); } catch {}
19
+ }
20
+ const packageRoot = path.resolve(__dirname, '..');
21
+ const mainPath = path.join(packageRoot, 'blun.mjs');
22
+ let leaseResult;
23
+
24
+ try {
25
+ leaseResult = await acquireSharedRuntimeLease({ packageRoot });
26
+ if (!leaseResult.acquired) throw new Error('RUNTIME_PROTECTION_UNAVAILABLE');
27
+ leaseResult.lease.assertOwned();
28
+ process.argv = [process.execPath, mainPath, ...process.argv.slice(2)];
29
+ await import(pathToFileURL(mainPath).href);
30
+ leaseResult.lease.assertOwned();
31
+ } catch (error) {
32
+ if (!informational) {
33
+ try {
34
+ if (diagnostics?.enabled) diagnostics.write(error, 'bootstrap-failure');
35
+ else recordRuntimeExit({ homeDir: process.env.BLUN_HOME, source: 'bootstrap',
36
+ kind: 'bootstrap-failure', phase: 'startup', error });
37
+ } catch {}
38
+ }
39
+ try { diagnostics?.dispose(); } catch {}
40
+ try { await leaseResult?.lease?.release(); }
41
+ catch (leaseError) {
42
+ if (!informational) {
43
+ try {
44
+ recordRuntimeExit({ homeDir: process.env.BLUN_HOME, source: 'bootstrap',
45
+ kind: 'lease-release-failure', phase: 'cleanup', error: leaseError });
46
+ } catch {}
47
+ }
48
+ }
49
+ throw error;
50
+ }
51
+
52
+ // The bundled CLI loads package assets lazily after module evaluation. Keep
53
+ // the install lease for this process's lifetime, without keeping print/help
54
+ // commands alive solely because of the owner server.
55
+ leaseResult.lease.unref();
56
+
57
+ if (process.connected) {
58
+ try {
59
+ process.send({ type: CORE_LOADED_MESSAGE }, () => {
60
+ try {
61
+ process.disconnect();
62
+ } catch {}
63
+ });
64
+ } catch {}
65
+ }
66
+ }
67
+
68
+ if (require.main === module) {
69
+ // oxlint-disable-next-line unicorn/prefer-top-level-await -- Worker entry is CommonJS.
70
+ void runCoreBootstrap().catch((error) => {
71
+ const message = error && error.message ? error.message : String(error);
72
+ fs.writeSync(process.stderr.fd, `BLUN Start fehlgeschlagen: ${message}\n`);
73
+ process.exit(1);
74
+ });
75
+ }
76
+
77
+ module.exports = { CORE_LOADED_MESSAGE, runCoreBootstrap };