blun-king-cli 9.1.624 → 9.1.626

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 -213
  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 +83 -97
  18. package/bin/launcher-mode.js +93 -93
  19. package/bin/launcher-restart-policy.cjs +131 -131
  20. package/bin/launcher-runtime.js +91 -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 +187640 -180475
  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 +13747 -16824
  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,109 +1,109 @@
1
- import { randomUUID } from 'node:crypto';
2
-
3
- export const MAX_RAW_BYTES = 64 * 1024;
4
- export const MAX_CHECKPOINT_BYTES = 16 * 1024;
5
- export const MAX_TICK_MS = 1_800_000;
6
- export const RESULT_SCHEMA = 'agentspine.blun-step-result/v1';
7
- const HOST_ENVIRONMENT_KEYS = [
8
- 'AGENTSPINE_GATEWAY_CONTEXT', 'AGENTSPINE_ENTITY_ID', 'AGENTSPINE_PROJECT_ID',
9
- 'AGENTSPINE_HOST', 'AGENTSPINE_GATEWAY_QUEUE_ID', 'AGENTSPINE_GATEWAY_ATTEMPT',
10
- 'AGENTSPINE_GROUP_ID', 'AGENTSPINE_GOAL_ID', 'AGENTSPINE_GOAL_STEP_ID',
11
- 'AGENTSPINE_PLAN_DEFINITIONS_DIGEST', 'AGENTSPINE_CHANNEL_EVENT_ID',
12
- 'AGENTSPINE_CHANNEL_PROVIDER', 'AGENTSPINE_PORTAL_REF', 'AGENTSPINE_THREAD_REF',
13
- ];
14
-
15
- function goalSessionKaos(kaos, environment) {
16
- if (environment === undefined) return kaos;
17
- if (!environment || typeof environment !== 'object' || Array.isArray(environment)
18
- || Object.entries(environment).some(([key, value]) => !HOST_ENVIRONMENT_KEYS.includes(key)
19
- || typeof value !== 'string' || value.includes('\0'))) throw new Error('GOAL_WORKER_ENVIRONMENT_INVALID');
20
- if (typeof kaos?.withEnv !== 'function') throw new Error('GOAL_WORKER_ENVIRONMENT_REQUIRED');
21
- // Replace the complete per-tick scope without changing process.env or the base Kaos.
22
- return kaos.withEnv(Object.fromEntries(HOST_ENVIRONMENT_KEYS.map(key => [key, environment[key] ?? ''])));
23
- }
24
-
25
- export function buildGoalPrompt(item) {
26
- return 'Execute exactly one bounded goal-plan tick. Return exactly one visible final JSON object.\n'
27
- + JSON.stringify({ schema: 'agentspine.blun-step-request/v1', goal: item.goal, goalStep: item.goalStep,
28
- selfHelpPolicy: item.selfHelpPolicy, requiredResult: { schema: RESULT_SCHEMA,
29
- fields: ['text', 'checkpoint', 'completed', 'blocked', 'blocker', 'knowledgeGap', 'selfHelp', 'execution'],
30
- rule: 'Every tick requires a non-null checkpoint; only report completed work backed by verification.' } });
31
- }
32
-
33
- export function parseGoalResult(text) {
34
- if (Buffer.byteLength(text, 'utf8') > MAX_RAW_BYTES) throw new Error('GOAL_STEP_RESULT_EXCEEDS_64_KIB');
35
- let value;
36
- try { value = JSON.parse(text.trim()); } catch { throw new Error('GOAL_STEP_RESULT_INVALID_JSON'); }
37
- if (!value || value.schema !== RESULT_SCHEMA || typeof value.text !== 'string'
38
- || !value.text.trim() || value.text.length > 16000
39
- || (value.completed !== undefined && typeof value.completed !== 'boolean')
40
- || (value.blocked !== undefined && typeof value.blocked !== 'boolean')
41
- || (value.completed === true && value.blocked === true)) throw new Error('GOAL_STEP_RESULT_INVALID');
42
- if (value.checkpoint === undefined || value.checkpoint === null) throw new Error('GOAL_STEP_CHECKPOINT_REQUIRED');
43
- if (Buffer.byteLength(JSON.stringify(value.checkpoint), 'utf8') > MAX_CHECKPOINT_BYTES) {
44
- throw new Error('GOAL_STEP_CHECKPOINT_TOO_LARGE');
45
- }
46
- return value;
47
- }
48
-
49
- export function runSessionStep(session, item, timeoutMs, signal) {
50
- return new Promise((resolve, reject) => {
51
- let ended = false;
52
- let text = '';
53
- let turnId;
54
- let unsubscribe = () => {};
55
- const finish = (error) => {
56
- if (ended) return;
57
- ended = true; clearTimeout(timer); unsubscribe(); signal?.removeEventListener('abort', aborted);
58
- if (error) { reject(error); return; }
59
- try { resolve(parseGoalResult(text)); } catch (error) { reject(error); }
60
- };
61
- const aborted = () => finish(new Error('GOAL_WORKER_STOP_REQUESTED'));
62
- const timer = setTimeout(() => finish(new Error('GOAL_STEP_DEADLINE')), timeoutMs);
63
- unsubscribe = session.onEvent((event) => {
64
- if (event.sessionId !== session.id || event.agentId !== 'main') return;
65
- if (event.type === 'turn.started' && turnId === undefined) { turnId = event.turnId; return; }
66
- if (event.type === 'error') { finish(new Error('GOAL_STEP_SESSION_ERROR')); return; }
67
- if (turnId === undefined || event.turnId !== turnId) return;
68
- if (event.type === 'turn.step.started' || event.type === 'turn.step.retrying') text = '';
69
- if (event.type === 'assistant.delta') {
70
- text += event.delta;
71
- if (Buffer.byteLength(text, 'utf8') > MAX_RAW_BYTES) finish(new Error('GOAL_STEP_RESULT_EXCEEDS_64_KIB'));
72
- }
73
- if (event.type === 'turn.ended') finish(event.reason === 'completed' ? null : new Error('GOAL_STEP_TURN_NOT_COMPLETED'));
74
- });
75
- signal?.addEventListener('abort', aborted, { once: true });
76
- if (signal?.aborted) aborted();
77
- if (!ended) Promise.resolve().then(() => { if (!ended) return session.prompt(buildGoalPrompt(item)); }).catch(finish);
78
- });
79
- }
80
-
81
- export function createGoalHostFactory({ harness, workDir, model, permission, tickTimeoutMs = MAX_TICK_MS,
82
- cleanupTimeoutMs = 10000, sessionOptions, onSession, signal }) {
83
- if (!Number.isInteger(tickTimeoutMs) || tickTimeoutMs < 1000 || tickTimeoutMs > MAX_TICK_MS
84
- || !Number.isInteger(cleanupTimeoutMs) || cleanupTimeoutMs < 1 || cleanupTimeoutMs > 60000) {
85
- throw new Error('GOAL_WORKER_TIMEOUT_OUT_OF_RANGE');
86
- }
87
- if (!['manual', 'auto', 'yolo'].includes(permission)) throw new Error('GOAL_WORKER_PERMISSION_REQUIRED');
88
- return async (item) => {
89
- if (item.projectRoot !== workDir || !item.goal) throw new Error('GOAL_WORKER_SCOPE_MISMATCH');
90
- const session = await harness.createSession({ ...sessionOptions, id: 'worker-' + randomUUID(),
91
- workDir, model, permission, kaos: goalSessionKaos(sessionOptions?.kaos, item.hostEnvironment),
92
- metadata: { gatewayQueueId: item.queueId, gatewayAttempt: item.attempts } });
93
- onSession?.(session);
94
- let started = false;
95
- return { sessionId: session.id,
96
- async run() {
97
- if (started) throw new Error('GOAL_WORKER_SESSION_ALREADY_RUN');
98
- started = true;
99
- return runSessionStep(session, item, tickTimeoutMs, signal);
100
- },
101
- async quiesce() {
102
- const proof = await session.quiesce({ deadlineAt: Date.now() + cleanupTimeoutMs });
103
- if (proof.status === 'quiescent' && proof.sessionId === session.id && proof.remaining.length === 0) {
104
- await session.close();
105
- }
106
- return proof;
107
- } };
108
- };
109
- }
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ export const MAX_RAW_BYTES = 64 * 1024;
4
+ export const MAX_CHECKPOINT_BYTES = 16 * 1024;
5
+ export const MAX_TICK_MS = 1_800_000;
6
+ export const RESULT_SCHEMA = 'agentspine.blun-step-result/v1';
7
+ const HOST_ENVIRONMENT_KEYS = [
8
+ 'AGENTSPINE_GATEWAY_CONTEXT', 'AGENTSPINE_ENTITY_ID', 'AGENTSPINE_PROJECT_ID',
9
+ 'AGENTSPINE_HOST', 'AGENTSPINE_GATEWAY_QUEUE_ID', 'AGENTSPINE_GATEWAY_ATTEMPT',
10
+ 'AGENTSPINE_GROUP_ID', 'AGENTSPINE_GOAL_ID', 'AGENTSPINE_GOAL_STEP_ID',
11
+ 'AGENTSPINE_PLAN_DEFINITIONS_DIGEST', 'AGENTSPINE_CHANNEL_EVENT_ID',
12
+ 'AGENTSPINE_CHANNEL_PROVIDER', 'AGENTSPINE_PORTAL_REF', 'AGENTSPINE_THREAD_REF',
13
+ ];
14
+
15
+ function goalSessionKaos(kaos, environment) {
16
+ if (environment === undefined) return kaos;
17
+ if (!environment || typeof environment !== 'object' || Array.isArray(environment)
18
+ || Object.entries(environment).some(([key, value]) => !HOST_ENVIRONMENT_KEYS.includes(key)
19
+ || typeof value !== 'string' || value.includes('\0'))) throw new Error('GOAL_WORKER_ENVIRONMENT_INVALID');
20
+ if (typeof kaos?.withEnv !== 'function') throw new Error('GOAL_WORKER_ENVIRONMENT_REQUIRED');
21
+ // Replace the complete per-tick scope without changing process.env or the base Kaos.
22
+ return kaos.withEnv(Object.fromEntries(HOST_ENVIRONMENT_KEYS.map(key => [key, environment[key] ?? ''])));
23
+ }
24
+
25
+ export function buildGoalPrompt(item) {
26
+ return 'Execute exactly one bounded goal-plan tick. Return exactly one visible final JSON object.\n'
27
+ + JSON.stringify({ schema: 'agentspine.blun-step-request/v1', goal: item.goal, goalStep: item.goalStep,
28
+ selfHelpPolicy: item.selfHelpPolicy, requiredResult: { schema: RESULT_SCHEMA,
29
+ fields: ['text', 'checkpoint', 'completed', 'blocked', 'blocker', 'knowledgeGap', 'selfHelp', 'execution'],
30
+ rule: 'Every tick requires a non-null checkpoint; only report completed work backed by verification.' } });
31
+ }
32
+
33
+ export function parseGoalResult(text) {
34
+ if (Buffer.byteLength(text, 'utf8') > MAX_RAW_BYTES) throw new Error('GOAL_STEP_RESULT_EXCEEDS_64_KIB');
35
+ let value;
36
+ try { value = JSON.parse(text.trim()); } catch { throw new Error('GOAL_STEP_RESULT_INVALID_JSON'); }
37
+ if (!value || value.schema !== RESULT_SCHEMA || typeof value.text !== 'string'
38
+ || !value.text.trim() || value.text.length > 16000
39
+ || (value.completed !== undefined && typeof value.completed !== 'boolean')
40
+ || (value.blocked !== undefined && typeof value.blocked !== 'boolean')
41
+ || (value.completed === true && value.blocked === true)) throw new Error('GOAL_STEP_RESULT_INVALID');
42
+ if (value.checkpoint === undefined || value.checkpoint === null) throw new Error('GOAL_STEP_CHECKPOINT_REQUIRED');
43
+ if (Buffer.byteLength(JSON.stringify(value.checkpoint), 'utf8') > MAX_CHECKPOINT_BYTES) {
44
+ throw new Error('GOAL_STEP_CHECKPOINT_TOO_LARGE');
45
+ }
46
+ return value;
47
+ }
48
+
49
+ export function runSessionStep(session, item, timeoutMs, signal) {
50
+ return new Promise((resolve, reject) => {
51
+ let ended = false;
52
+ let text = '';
53
+ let turnId;
54
+ let unsubscribe = () => {};
55
+ const finish = (error) => {
56
+ if (ended) return;
57
+ ended = true; clearTimeout(timer); unsubscribe(); signal?.removeEventListener('abort', aborted);
58
+ if (error) { reject(error); return; }
59
+ try { resolve(parseGoalResult(text)); } catch (error) { reject(error); }
60
+ };
61
+ const aborted = () => finish(new Error('GOAL_WORKER_STOP_REQUESTED'));
62
+ const timer = setTimeout(() => finish(new Error('GOAL_STEP_DEADLINE')), timeoutMs);
63
+ unsubscribe = session.onEvent((event) => {
64
+ if (event.sessionId !== session.id || event.agentId !== 'main') return;
65
+ if (event.type === 'turn.started' && turnId === undefined) { turnId = event.turnId; return; }
66
+ if (event.type === 'error') { finish(new Error('GOAL_STEP_SESSION_ERROR')); return; }
67
+ if (turnId === undefined || event.turnId !== turnId) return;
68
+ if (event.type === 'turn.step.started' || event.type === 'turn.step.retrying') text = '';
69
+ if (event.type === 'assistant.delta') {
70
+ text += event.delta;
71
+ if (Buffer.byteLength(text, 'utf8') > MAX_RAW_BYTES) finish(new Error('GOAL_STEP_RESULT_EXCEEDS_64_KIB'));
72
+ }
73
+ if (event.type === 'turn.ended') finish(event.reason === 'completed' ? null : new Error('GOAL_STEP_TURN_NOT_COMPLETED'));
74
+ });
75
+ signal?.addEventListener('abort', aborted, { once: true });
76
+ if (signal?.aborted) aborted();
77
+ if (!ended) Promise.resolve().then(() => { if (!ended) return session.prompt(buildGoalPrompt(item)); }).catch(finish);
78
+ });
79
+ }
80
+
81
+ export function createGoalHostFactory({ harness, workDir, model, permission, tickTimeoutMs = MAX_TICK_MS,
82
+ cleanupTimeoutMs = 10000, sessionOptions, onSession, signal }) {
83
+ if (!Number.isInteger(tickTimeoutMs) || tickTimeoutMs < 1000 || tickTimeoutMs > MAX_TICK_MS
84
+ || !Number.isInteger(cleanupTimeoutMs) || cleanupTimeoutMs < 1 || cleanupTimeoutMs > 60000) {
85
+ throw new Error('GOAL_WORKER_TIMEOUT_OUT_OF_RANGE');
86
+ }
87
+ if (!['manual', 'auto', 'yolo'].includes(permission)) throw new Error('GOAL_WORKER_PERMISSION_REQUIRED');
88
+ return async (item) => {
89
+ if (item.projectRoot !== workDir || !item.goal) throw new Error('GOAL_WORKER_SCOPE_MISMATCH');
90
+ const session = await harness.createSession({ ...sessionOptions, id: 'worker-' + randomUUID(),
91
+ workDir, model, permission, kaos: goalSessionKaos(sessionOptions?.kaos, item.hostEnvironment),
92
+ metadata: { gatewayQueueId: item.queueId, gatewayAttempt: item.attempts } });
93
+ onSession?.(session);
94
+ let started = false;
95
+ return { sessionId: session.id,
96
+ async run() {
97
+ if (started) throw new Error('GOAL_WORKER_SESSION_ALREADY_RUN');
98
+ started = true;
99
+ return runSessionStep(session, item, tickTimeoutMs, signal);
100
+ },
101
+ async quiesce() {
102
+ const proof = await session.quiesce({ deadlineAt: Date.now() + cleanupTimeoutMs });
103
+ if (proof.status === 'quiescent' && proof.sessionId === session.id && proof.remaining.length === 0) {
104
+ await session.close();
105
+ }
106
+ return proof;
107
+ } };
108
+ };
109
+ }
@@ -1,80 +1,80 @@
1
- 'use strict';
2
-
3
- const { createHash } = require('node:crypto');
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
-
7
- const STAND_SCHEMA = 'blun.snapshot-stand/v1';
8
- const SHA256_RE = /^[a-f0-9]{64}$/iu;
9
-
10
- function sha256(value) {
11
- return createHash('sha256').update(value).digest('hex');
12
- }
13
-
14
- function exactSha(value, label) {
15
- const digest = String(value || '').trim().toLowerCase();
16
- if (!SHA256_RE.test(digest)) throw new Error(`${label} must be a SHA256 digest`);
17
- return digest;
18
- }
19
-
20
- function isInside(root, candidate) {
21
- const relative = path.relative(root, candidate);
22
- return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
23
- }
24
-
25
- function standMetadata(fsImpl, root, standPath) {
26
- if (!isInside(root, standPath)) throw new Error('SNAPSHOT_STAND_OUTSIDE_WORKSPACE');
27
- const parts = path.relative(root, standPath).split(path.sep);
28
- let cursor = root;
29
- let metadata;
30
- for (let index = 0; index < parts.length; index++) {
31
- cursor = path.join(cursor, parts[index]);
32
- metadata = fsImpl.lstatSync(cursor, { bigint: true });
33
- if (metadata.isSymbolicLink() || (index < parts.length - 1 ? !metadata.isDirectory() : !metadata.isFile())) {
34
- throw new Error('SNAPSHOT_STAND_NOT_REGULAR');
35
- }
36
- }
37
- if (!isInside(root, fsImpl.realpathSync(standPath))) throw new Error('SNAPSHOT_STAND_OUTSIDE_WORKSPACE');
38
- return metadata;
39
- }
40
-
41
- function sameFile(left, right) {
42
- return left.isFile() && right.isFile() && ['dev', 'ino', 'size', 'mtimeNs', 'ctimeNs']
43
- .every(key => left[key] === right[key]);
44
- }
45
-
46
- function verifyWorkspaceSnapshot(input, options = {}) {
47
- const fsImpl = options.fsImpl || fs;
48
- const root = fsImpl.realpathSync(path.resolve(input.root));
49
- const standPath = path.resolve(root, input.standFile || '.blun-snapshot-stand.json');
50
- const metadata = standMetadata(fsImpl, root, standPath);
51
- const descriptor = fsImpl.openSync(standPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
52
- let payload;
53
- try {
54
- const before = fsImpl.fstatSync(descriptor, { bigint: true });
55
- if (!sameFile(metadata, before)) throw new Error('SNAPSHOT_STAND_CHANGED');
56
- payload = fsImpl.readFileSync(descriptor);
57
- const after = fsImpl.fstatSync(descriptor, { bigint: true });
58
- if (!sameFile(before, after) || !sameFile(after, standMetadata(fsImpl, root, standPath))) {
59
- throw new Error('SNAPSHOT_STAND_CHANGED');
60
- }
61
- } finally { fsImpl.closeSync(descriptor); }
62
- const standSha256 = sha256(payload);
63
- if (standSha256 !== exactSha(input.expectedStandSha256, 'expectedStandSha256')) {
64
- throw new Error('SNAPSHOT_STAND_SHA256_MISMATCH');
65
- }
66
- let stand;
67
- try { stand = JSON.parse(payload.toString('utf8')); }
68
- catch { throw new Error('SNAPSHOT_STAND_INVALID_JSON'); }
69
- if (stand?.schema !== STAND_SCHEMA || typeof stand.projectId !== 'string') {
70
- throw new Error('SNAPSHOT_STAND_INVALID');
71
- }
72
- const snapshotSha256 = exactSha(stand.snapshotSha256, 'snapshotSha256');
73
- if (snapshotSha256 !== exactSha(input.expectedSnapshotSha256, 'expectedSnapshotSha256')) {
74
- throw new Error('SNAPSHOT_SHA256_MISMATCH');
75
- }
76
- if (stand.projectId !== input.projectId) throw new Error('SNAPSHOT_PROJECT_MISMATCH');
77
- return Object.freeze({ root, standPath, standSha256, snapshotSha256, projectId: stand.projectId });
78
- }
79
-
80
- module.exports = { STAND_SCHEMA, verifyWorkspaceSnapshot };
1
+ 'use strict';
2
+
3
+ const { createHash } = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const STAND_SCHEMA = 'blun.snapshot-stand/v1';
8
+ const SHA256_RE = /^[a-f0-9]{64}$/iu;
9
+
10
+ function sha256(value) {
11
+ return createHash('sha256').update(value).digest('hex');
12
+ }
13
+
14
+ function exactSha(value, label) {
15
+ const digest = String(value || '').trim().toLowerCase();
16
+ if (!SHA256_RE.test(digest)) throw new Error(`${label} must be a SHA256 digest`);
17
+ return digest;
18
+ }
19
+
20
+ function isInside(root, candidate) {
21
+ const relative = path.relative(root, candidate);
22
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
23
+ }
24
+
25
+ function standMetadata(fsImpl, root, standPath) {
26
+ if (!isInside(root, standPath)) throw new Error('SNAPSHOT_STAND_OUTSIDE_WORKSPACE');
27
+ const parts = path.relative(root, standPath).split(path.sep);
28
+ let cursor = root;
29
+ let metadata;
30
+ for (let index = 0; index < parts.length; index++) {
31
+ cursor = path.join(cursor, parts[index]);
32
+ metadata = fsImpl.lstatSync(cursor, { bigint: true });
33
+ if (metadata.isSymbolicLink() || (index < parts.length - 1 ? !metadata.isDirectory() : !metadata.isFile())) {
34
+ throw new Error('SNAPSHOT_STAND_NOT_REGULAR');
35
+ }
36
+ }
37
+ if (!isInside(root, fsImpl.realpathSync(standPath))) throw new Error('SNAPSHOT_STAND_OUTSIDE_WORKSPACE');
38
+ return metadata;
39
+ }
40
+
41
+ function sameFile(left, right) {
42
+ return left.isFile() && right.isFile() && ['dev', 'ino', 'size', 'mtimeNs', 'ctimeNs']
43
+ .every(key => left[key] === right[key]);
44
+ }
45
+
46
+ function verifyWorkspaceSnapshot(input, options = {}) {
47
+ const fsImpl = options.fsImpl || fs;
48
+ const root = fsImpl.realpathSync(path.resolve(input.root));
49
+ const standPath = path.resolve(root, input.standFile || '.blun-snapshot-stand.json');
50
+ const metadata = standMetadata(fsImpl, root, standPath);
51
+ const descriptor = fsImpl.openSync(standPath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
52
+ let payload;
53
+ try {
54
+ const before = fsImpl.fstatSync(descriptor, { bigint: true });
55
+ if (!sameFile(metadata, before)) throw new Error('SNAPSHOT_STAND_CHANGED');
56
+ payload = fsImpl.readFileSync(descriptor);
57
+ const after = fsImpl.fstatSync(descriptor, { bigint: true });
58
+ if (!sameFile(before, after) || !sameFile(after, standMetadata(fsImpl, root, standPath))) {
59
+ throw new Error('SNAPSHOT_STAND_CHANGED');
60
+ }
61
+ } finally { fsImpl.closeSync(descriptor); }
62
+ const standSha256 = sha256(payload);
63
+ if (standSha256 !== exactSha(input.expectedStandSha256, 'expectedStandSha256')) {
64
+ throw new Error('SNAPSHOT_STAND_SHA256_MISMATCH');
65
+ }
66
+ let stand;
67
+ try { stand = JSON.parse(payload.toString('utf8')); }
68
+ catch { throw new Error('SNAPSHOT_STAND_INVALID_JSON'); }
69
+ if (stand?.schema !== STAND_SCHEMA || typeof stand.projectId !== 'string') {
70
+ throw new Error('SNAPSHOT_STAND_INVALID');
71
+ }
72
+ const snapshotSha256 = exactSha(stand.snapshotSha256, 'snapshotSha256');
73
+ if (snapshotSha256 !== exactSha(input.expectedSnapshotSha256, 'expectedSnapshotSha256')) {
74
+ throw new Error('SNAPSHOT_SHA256_MISMATCH');
75
+ }
76
+ if (stand.projectId !== input.projectId) throw new Error('SNAPSHOT_PROJECT_MISMATCH');
77
+ return Object.freeze({ root, standPath, standSha256, snapshotSha256, projectId: stand.projectId });
78
+ }
79
+
80
+ module.exports = { STAND_SCHEMA, verifyWorkspaceSnapshot };