blun-king-cli 9.1.623 → 9.1.625

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 (142) hide show
  1. package/CHANGELOG.md +62 -209
  2. package/LIESMICH.txt +57 -74
  3. package/README.md +76 -119
  4. package/agent-spine-plugin/scripts/check-install.js +4 -4
  5. package/agent-spine-plugin/src/hook.js +0 -4
  6. package/agent-spine-plugin/src/lib/documents.js +1 -1
  7. package/agent-spine-plugin/src/lib/hook-context.js +0 -1
  8. package/agent-spine-plugin/src/lib/host-instruction-budget.js +2 -1
  9. package/agent-spine-plugin/src/lib/preflight.js +9 -38
  10. package/agent-spine-plugin/src/lib/source-roots.js +1 -1
  11. package/bin/agentspine-king-goal-inbox.mjs +127 -127
  12. package/bin/agentspine-king-goal-intake.mjs +106 -106
  13. package/bin/agentspine-king-host-runner.mjs +109 -109
  14. package/bin/agentspine-king-snapshot-policy.cjs +80 -80
  15. package/bin/agentspine-king-status-policy.mjs +226 -226
  16. package/bin/agentspine-king-worker-host.mjs +160 -160
  17. package/bin/core-bootstrap.js +71 -100
  18. package/bin/launcher-mode.js +93 -93
  19. package/bin/launcher-restart-policy.cjs +131 -131
  20. package/bin/launcher-runtime.js +89 -254
  21. package/bin/managed-node.js +186 -186
  22. package/bin/managed-plugin-selection.cjs +109 -116
  23. package/bin/native-module-repair.js +201 -201
  24. package/bin/node-runtime.js +34 -34
  25. package/bin/node-version.js +49 -49
  26. package/bin/plugin-bootstrap.js +149 -172
  27. package/bin/private-paths.js +1578 -1624
  28. package/bin/runtime-exit-ledger.cjs +144 -144
  29. package/bin/runtime-exit-ledger.d.cts +23 -23
  30. package/bin/standard-tools-bootstrap.js +277 -272
  31. package/bin/turn-thinking-policy.cjs +82 -82
  32. package/bin/update-copy.js +200 -200
  33. package/bin/update-lease.js +597 -599
  34. package/bin/update-notice.js +1568 -1568
  35. package/bin/verify-agent-behavior.cjs +122 -122
  36. package/bin/verify-agent-components.cjs +104 -104
  37. package/bin/verify-bundled-agent-sources.cjs +91 -96
  38. package/bin/windows-node-crash-dump.cjs +289 -289
  39. package/blun.mjs +194198 -189561
  40. package/bundled-agent-sources.json +26 -30
  41. package/codebase-index/codebase_index.py +462 -470
  42. package/dist-web/assets/{CodeBlockNode-TKKkJX8L.js → CodeBlockNode-BMbuG85x.js} +1 -1
  43. package/dist-web/assets/{CodeBlockShell.vue_vue_type_style_index_0_lang-CUj2Ds5x.js → CodeBlockShell.vue_vue_type_style_index_0_lang-C1fv7l_Z.js} +1 -1
  44. package/dist-web/assets/DesignSystemView-6HgAUEmC.css +1 -0
  45. package/dist-web/assets/DesignSystemView-CEEBqXSX.js +12 -0
  46. package/dist-web/assets/{Tooltip-CTWv7WCU.js → Tooltip--YoauJ24.js} +1 -1
  47. package/dist-web/assets/{abnfDiagram-N423BO3Z-DA8V8Zjh.js → abnfDiagram-N423BO3Z-DZvBWkRg.js} +1 -1
  48. package/dist-web/assets/{arc-YtddKbdY.js → arc-B04JATeX.js} +1 -1
  49. package/dist-web/assets/{architectureDiagram-T3A2C74G-CGEiObmo.js → architectureDiagram-T3A2C74G-DmYnMep2.js} +1 -1
  50. package/dist-web/assets/{blockDiagram-VBNYF7ZC-Bd5Vt50e.js → blockDiagram-VBNYF7ZC-DJ1gPS1C.js} +1 -1
  51. package/dist-web/assets/{c4Diagram-5PPSVZJV-D2ApPM54.js → c4Diagram-5PPSVZJV-DmjxvUbd.js} +1 -1
  52. package/dist-web/assets/channel-472fcEvw.js +1 -0
  53. package/dist-web/assets/{chunk-2GRJ4B5K-BDcIEZSl.js → chunk-2GRJ4B5K-AB0raj3I.js} +1 -1
  54. package/dist-web/assets/{chunk-2Q5K7J3B-6aSabCX7.js → chunk-2Q5K7J3B-Drf039vM.js} +1 -1
  55. package/dist-web/assets/{chunk-5RXB4S5H-BFRQvIJS.js → chunk-5RXB4S5H-D5ILttmz.js} +1 -1
  56. package/dist-web/assets/{chunk-5VM5RSS4-ChAa-DNY.js → chunk-5VM5RSS4-DrLSdi8y.js} +1 -1
  57. package/dist-web/assets/{chunk-6Q2QTUOP-BIfrb2Jc.js → chunk-6Q2QTUOP-LiaqdEOQ.js} +1 -1
  58. package/dist-web/assets/{chunk-GF5L2VYU-BR0gvtWI.js → chunk-GF5L2VYU-AIYkb--o.js} +1 -1
  59. package/dist-web/assets/{chunk-JWPE2WC7-BgXKsx5E.js → chunk-JWPE2WC7-CogdNl0i.js} +1 -1
  60. package/dist-web/assets/{chunk-KBJHAD2P-I8mrZhpz.js → chunk-KBJHAD2P-DHjpNtZv.js} +1 -1
  61. package/dist-web/assets/{chunk-RYQCIY6F-BMPaem92.js → chunk-RYQCIY6F-BUSOqH2X.js} +1 -1
  62. package/dist-web/assets/{chunk-XXDRQBXY-COy0J-0j.js → chunk-XXDRQBXY-BpoMV0bh.js} +1 -1
  63. package/dist-web/assets/classDiagram-JCYQIIEL-BTZeLngj.js +1 -0
  64. package/dist-web/assets/classDiagram-v2-OCEON4UE-BTZeLngj.js +1 -0
  65. package/dist-web/assets/{cose-bilkent-JH36ORCC-MosTTRdo.js → cose-bilkent-JH36ORCC-BJr2I61o.js} +1 -1
  66. package/dist-web/assets/{cynefin-VYW2F7L2-5tjPgrkP.js → cynefin-VYW2F7L2-HtysQZ4A.js} +1 -1
  67. package/dist-web/assets/{cynefinDiagram-MW4NZA55-CbDcdInR.js → cynefinDiagram-MW4NZA55-CcpC_2uv.js} +1 -1
  68. package/dist-web/assets/{dagre-VZM6K2ZE-YaBom1ZH.js → dagre-VZM6K2ZE-D8Ao0LUg.js} +1 -1
  69. package/dist-web/assets/{diagram-7IWD3JNH-Da4m-Hgz.js → diagram-7IWD3JNH-BITiLhjX.js} +1 -1
  70. package/dist-web/assets/{diagram-B4RE2ZJO-CAUUegxg.js → diagram-B4RE2ZJO-CJtwfy9f.js} +1 -1
  71. package/dist-web/assets/{diagram-LBJQPF4R-o21mWKG4.js → diagram-LBJQPF4R-BsVTh6w8.js} +1 -1
  72. package/dist-web/assets/{diagram-Q27KOJAE-BA3z3sXf.js → diagram-Q27KOJAE-pA_MUCUf.js} +1 -1
  73. package/dist-web/assets/{diagram-UB23O5K3-CGRdSSDK.js → diagram-UB23O5K3-CIY8z7vu.js} +1 -1
  74. package/dist-web/assets/{ebnfDiagram-BXEA7PRR-Bw-iU9FP.js → ebnfDiagram-BXEA7PRR-C3FMioqp.js} +1 -1
  75. package/dist-web/assets/{erDiagram-JOGREHBK-BwM1yiR0.js → erDiagram-JOGREHBK-CzoyKmKc.js} +1 -1
  76. package/dist-web/assets/{flowDiagram-UKHOOZJN-z05eOR1Q.js → flowDiagram-UKHOOZJN-DOqfushy.js} +1 -1
  77. package/dist-web/assets/{ganttDiagram-PKOTCBZU-BMUubNTl.js → ganttDiagram-PKOTCBZU-Cf9XQSWl.js} +1 -1
  78. package/dist-web/assets/{gitGraphDiagram-DS77QQ5N-Dccbi1wU.js → gitGraphDiagram-DS77QQ5N-DHUDofE5.js} +1 -1
  79. package/dist-web/assets/{index-h0KEOmkU.js → index-B6qEMQMx.js} +14 -14
  80. package/dist-web/assets/{index-C9eDH3u-.css → index-BmKj7CB1.css} +1 -1
  81. package/dist-web/assets/{index-BBbC5LRF.js → index-DokxDX1w.js} +1 -1
  82. package/dist-web/assets/{index-D48p0M2S.js → index-auojtiAA.js} +2 -2
  83. package/dist-web/assets/{index10-Qbuw1N6u.js → index10-Drb2n5e5.js} +1 -1
  84. package/dist-web/assets/{index11-g8v025wN.js → index11-Cx-aV4G2.js} +1 -1
  85. package/dist-web/assets/{index5-BYNcV8BY.js → index5-Dg3qGTyH.js} +1 -1
  86. package/dist-web/assets/{index6-BetANtnV.js → index6-BgvtIICP.js} +1 -1
  87. package/dist-web/assets/{index7-BcPbgCKN.js → index7-uEnniPQG.js} +1 -1
  88. package/dist-web/assets/{index8-B1lp-RvF.js → index8-h4IKc0ek.js} +2 -2
  89. package/dist-web/assets/{infoDiagram-6WML65LV-BF_pbUnN.js → infoDiagram-6WML65LV-D-3tvEiJ.js} +1 -1
  90. package/dist-web/assets/{ishikawaDiagram-WSZJBQD7-Dedv4urS.js → ishikawaDiagram-WSZJBQD7-DIIBNv7a.js} +1 -1
  91. package/dist-web/assets/{journeyDiagram-NVQOT4AX-B1Ex3b6o.js → journeyDiagram-NVQOT4AX-DP4r1Vlx.js} +1 -1
  92. package/dist-web/assets/{kanban-definition-27J2QSJJ-BtCGliBH.js → kanban-definition-27J2QSJJ-C3_Ztgxv.js} +1 -1
  93. package/dist-web/assets/{linear-ByItc8Qr.js → linear-DnqhIw-y.js} +1 -1
  94. package/dist-web/assets/{mermaid.core-B1M2O7nN.js → mermaid.core-xYUgBa3T.js} +4 -4
  95. package/dist-web/assets/{mindmap-definition-FAOFIHXS-D0UHKUyj.js → mindmap-definition-FAOFIHXS-l-dq-zfr.js} +1 -1
  96. package/dist-web/assets/{pegDiagram-VL7TDLO6-D7zYCVmG.js → pegDiagram-VL7TDLO6-C8vXQiaJ.js} +1 -1
  97. package/dist-web/assets/{pieDiagram-7S7Q4E2Y-Bd0i9CHa.js → pieDiagram-7S7Q4E2Y-CyNRSsI6.js} +1 -1
  98. package/dist-web/assets/{quadrantDiagram-CIZ2JOQS-MG-Ug47d.js → quadrantDiagram-CIZ2JOQS-D-w8AD_g.js} +1 -1
  99. package/dist-web/assets/{railroadDiagram-AXF67PYL-AZdl90Gj.js → railroadDiagram-AXF67PYL-BLkyQCWy.js} +1 -1
  100. package/dist-web/assets/{requirementDiagram-LRYGKXZP-DjplF9kt.js → requirementDiagram-LRYGKXZP-CEWO7XfT.js} +1 -1
  101. package/dist-web/assets/{sankeyDiagram-W5VNT64P-Bi9Zgfjs.js → sankeyDiagram-W5VNT64P-ByGoAaGj.js} +1 -1
  102. package/dist-web/assets/{sequenceDiagram-SI44F4Z6-ChEpdCvb.js → sequenceDiagram-SI44F4Z6-Bhn9GQtd.js} +1 -1
  103. package/dist-web/assets/{sizeCapture-X5ZJPWSS-B5RFUdaa.js → sizeCapture-X5ZJPWSS-jVKR9CTu.js} +1 -1
  104. package/dist-web/assets/{stateDiagram-OKZ733FA-BkRblSKp.js → stateDiagram-OKZ733FA-wW3XbegA.js} +1 -1
  105. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-B8aids-Z.js +1 -0
  106. package/dist-web/assets/{swimlanes-SLNWSIFB-DLXH6Zhc.js → swimlanes-SLNWSIFB-B8QyFS7G.js} +2 -2
  107. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-B8XAkdSm.js +8 -0
  108. package/dist-web/assets/{timeline-definition-Z64GVDOM-C_N88siO.js → timeline-definition-Z64GVDOM-DpvxS-1A.js} +1 -1
  109. package/dist-web/assets/{vennDiagram-T6HMQDX7-DMsnvDt0.js → vennDiagram-T6HMQDX7-BzGM0ihY.js} +1 -1
  110. package/dist-web/assets/{vue.runtime.esm-bundler-B-3uIy85.js → vue.runtime.esm-bundler-BOcpwIX1.js} +1 -1
  111. package/dist-web/assets/{wardleyDiagram-T6FBY63Y-C9SJIPzq.js → wardleyDiagram-T6FBY63Y-2DKAOs5e.js} +1 -1
  112. package/dist-web/assets/{xychartDiagram-ELKLHX3M-DZLWA6Go.js → xychartDiagram-ELKLHX3M-B0SP0TXd.js} +1 -1
  113. package/dist-web/index.html +32 -32
  114. package/dist-web/vis/index.html.gz +0 -0
  115. package/package.json +1 -1
  116. package/scripts/fix-node-pty-perms.js +53 -53
  117. package/standard-skills/blun-app-design-system/SKILL.md +30 -30
  118. package/standard-skills/blun-app-design-system/provenance.json +17 -17
  119. package/standard-skills/blun-app-design-system/references/tokens.md +48 -48
  120. package/standard-skills/blun-app-design-system/scripts/check-blun-design.mjs +49 -49
  121. package/standard-skills/translate-native/README.md +1 -1
  122. package/standard-skills/translate-native/scripts/blun_language_guard.py +2 -0
  123. package/standard-skills/translate-native/scripts/language_gateway.py +2 -0
  124. package/standard-skills/translate-native/scripts/pre_output_guard.py +2 -0
  125. package/standard-tools/language-guard/blun_language_guard.py +2 -0
  126. package/standard-tools/language-guard/language_gateway.py +2 -0
  127. package/standard-tools/language-guard/pre_output_guard.py +2 -0
  128. package/telegram-plugin/blun.plugin.json +20 -20
  129. package/telegram-plugin/commands/access.md +37 -37
  130. package/telegram-plugin/dist/bridge.mjs +310 -187
  131. package/telegram-plugin/dist/mcp-server.mjs +174 -261
  132. package/telegram-plugin/dist/noise.mjs +1 -19
  133. package/worker-host.mjs +13879 -17458
  134. package/bin/profile-handoff.cjs +0 -152
  135. package/bin/profile-launch.cjs +0 -120
  136. package/dist-web/assets/DesignSystemView-DwK1aQMo.js +0 -12
  137. package/dist-web/assets/DesignSystemView-szjTj7ns.css +0 -1
  138. package/dist-web/assets/channel-BnluexuN.js +0 -1
  139. package/dist-web/assets/classDiagram-JCYQIIEL-BliFdSD9.js +0 -1
  140. package/dist-web/assets/classDiagram-v2-OCEON4UE-BliFdSD9.js +0 -1
  141. package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fVkQcACW.js +0 -1
  142. package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-Bg-BiZJQ.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,106 +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, tryAcquireUpdateLease } = 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
- function bindInteractiveTerminalOwner() {
15
- if (process.platform !== 'win32' || process.env.BLUN_TERMINAL_OWNER !== '1' || !process.channel) return;
16
- const stop = () => {
17
- // IPC belongs to this launcher, not to another profile or a PID file.
18
- // Use the TUI's normal shutdown first; bound only this process's cleanup.
19
- setTimeout(() => process.exit(143), 5000);
20
- if (!process.emit('SIGTERM')) process.exit(143);
21
- };
22
- process.once('disconnect', stop);
23
- if (!process.connected) stop();
24
- }
25
-
26
- async function runCoreBootstrap() {
27
- bindInteractiveTerminalOwner();
28
- const informational = isStandaloneHelpRequest(process.argv.slice(2));
29
- let diagnostics;
30
- if (!informational) {
31
- try { diagnostics = installWindowsNodeCrashDump({ homeDir: process.env.BLUN_HOME }); } catch {}
32
- }
33
- const packageRoot = path.resolve(__dirname, '..');
34
- const mainPath = path.join(packageRoot, 'blun.mjs');
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');
35
22
  let leaseResult;
36
- let profileLease;
37
-
38
- try {
39
- leaseResult = await acquireSharedRuntimeLease({ packageRoot });
40
- if (!leaseResult.acquired) throw new Error('RUNTIME_PROTECTION_UNAVAILABLE');
23
+
24
+ try {
25
+ leaseResult = await acquireSharedRuntimeLease({ packageRoot });
26
+ if (!leaseResult.acquired) throw new Error('RUNTIME_PROTECTION_UNAVAILABLE');
41
27
  leaseResult.lease.assertOwned();
42
- if (process.env.BLUN_PROFILE_HANDOFF === '1') {
43
- if (!path.isAbsolute(process.env.BLUN_HOME || '')) throw new Error('PROFILE_RUNTIME_UNAVAILABLE');
44
- const result = await tryAcquireUpdateLease({ packageRoot: process.env.BLUN_HOME, role: 'profile' });
45
- if (!result.acquired) throw Object.assign(new Error(`PROFILE_RUNTIME_${result.reason.toUpperCase()}`),
46
- { code: result.reason === 'busy' ? 'PROFILE_RUNTIME_BUSY' : 'PROFILE_RUNTIME_UNAVAILABLE' });
47
- profileLease = result.lease;
48
- profileLease.assertOwned();
49
- }
50
- process.argv = [process.execPath, mainPath, ...process.argv.slice(2)];
51
- await import(pathToFileURL(mainPath).href);
28
+ process.argv = [process.execPath, mainPath, ...process.argv.slice(2)];
29
+ await import(pathToFileURL(mainPath).href);
52
30
  leaseResult.lease.assertOwned();
53
- profileLease?.assertOwned();
54
- } catch (error) {
55
- if (!informational) {
56
- try {
57
- if (diagnostics?.enabled) diagnostics.write(error, 'bootstrap-failure');
58
- else recordRuntimeExit({ homeDir: process.env.BLUN_HOME, source: 'bootstrap',
59
- kind: 'bootstrap-failure', phase: 'startup', error });
60
- } catch {}
61
- }
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
+ }
62
39
  try { diagnostics?.dispose(); } catch {}
63
- try { await profileLease?.release(); } catch {}
64
- try { await leaseResult?.lease?.release(); }
65
- catch (leaseError) {
66
- if (!informational) {
67
- try {
68
- recordRuntimeExit({ homeDir: process.env.BLUN_HOME, source: 'bootstrap',
69
- kind: 'lease-release-failure', phase: 'cleanup', error: leaseError });
70
- } catch {}
71
- }
72
- }
73
- throw error;
74
- }
75
-
76
- // The bundled CLI loads package assets lazily after module evaluation. Keep
77
- // the install lease for this process's lifetime, without keeping print/help
78
- // commands alive solely because of the owner server.
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.
79
55
  leaseResult.lease.unref();
80
- profileLease?.unref();
81
-
82
- if (process.connected) {
83
- try {
56
+
57
+ if (process.connected) {
58
+ try {
84
59
  process.send({ type: CORE_LOADED_MESSAGE }, () => {
85
- if (process.env.BLUN_PROFILE_HANDOFF === '1') {
86
- process.channel?.unref();
87
- return;
88
- }
89
- try {
90
- process.disconnect();
91
- } catch {}
92
- });
93
- } catch {}
94
- }
95
- }
96
-
97
- if (require.main === module) {
98
- // oxlint-disable-next-line unicorn/prefer-top-level-await -- Worker entry is CommonJS.
99
- void runCoreBootstrap().catch((error) => {
100
- const message = error && error.message ? error.message : String(error);
101
- fs.writeSync(process.stderr.fd, `BLUN Start fehlgeschlagen: ${message}\n`);
102
- process.exit(error?.code === 'PROFILE_RUNTIME_BUSY' ? 75 : 1);
103
- });
104
- }
105
-
106
- module.exports = { CORE_LOADED_MESSAGE, runCoreBootstrap };
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 };