blun-king-cli 9.1.602 → 9.1.604

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 +118 -99
  2. package/LIESMICH.txt +74 -57
  3. package/README.md +119 -76
  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 +98 -76
  31. package/bin/windows-node-crash-dump.cjs +289 -289
  32. package/blun.mjs +8041 -6407
  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 +117 -100
  116. package/telegram-plugin/dist/mcp-server.mjs +185 -161
  117. package/worker-host.mjs +1458 -1022
  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,127 +1,127 @@
1
- import { open, lstat, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { createHash } from 'node:crypto';
4
-
5
- import { ingestGoalInstruction, parseGoalInstruction } from './agentspine-king-goal-intake.mjs';
6
-
7
- const CHECKPOINT_SCHEMA = 'blun.king-goal-queue-checkpoint/v1';
8
- const MAX_READ_BYTES = 4 * 1024 * 1024;
9
-
10
- function queueIdentity(metadata) {
11
- return `${metadata.dev}:${metadata.ino}:${metadata.birthtimeMs}`;
12
- }
13
-
14
- async function readCheckpoint(filePath) {
15
- try {
16
- const value = JSON.parse(await readFile(filePath, 'utf8'));
17
- if (value?.schema === CHECKPOINT_SCHEMA && typeof value.fileId === 'string'
18
- && Number.isSafeInteger(value.offset) && value.offset >= 0 && Array.isArray(value.accepted)
19
- && value.accepted.length <= 512 && value.accepted.every(item => item && /^[a-f0-9]{64}$/.test(item.sha256)
20
- && typeof item.goalId === 'string' && item.goalId.length > 0)) return value;
21
- throw new Error('GOAL_QUEUE_CHECKPOINT_INVALID');
22
- } catch (error) {
23
- if (error?.code !== 'ENOENT') throw error;
24
- }
25
- return { schema: CHECKPOINT_SCHEMA, fileId: '', offset: 0, lastMessageId: null, accepted: [] };
26
- }
27
-
28
- async function writeCheckpoint(filePath, value) {
29
- await mkdir(path.dirname(filePath), { recursive: true });
30
- const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
31
- await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, {
32
- encoding: 'utf8', flag: 'wx', mode: 0o600,
33
- });
34
- await rename(temporary, filePath);
35
- }
36
-
37
- function parseQueueLine(line) {
38
- let envelope;
39
- try { envelope = JSON.parse(line); } catch { return null; }
40
- if (!envelope || typeof envelope.text !== 'string' || !envelope.meta || Array.isArray(envelope.meta)
41
- || typeof envelope.meta !== 'object') return null;
42
- return envelope;
43
- }
44
-
45
- function isAuthorizedInstruction(envelope, input) {
46
- const addressed = envelope.meta.addressed === true || envelope.meta.addressed === 'true';
47
- const threads = ['thread_id', 'telegram_thread_id', 'message_thread_id']
48
- .map(key => envelope.meta[key]).filter(value => value !== null && value !== undefined && value !== '').map(String);
49
- const thread = input.instructionThreadId === null || input.instructionThreadId === undefined ? null : String(input.instructionThreadId);
50
- return addressed && String(envelope.meta.user_id) === String(input.instructionSenderId)
51
- && String(envelope.meta.chat_id) === String(input.instructionChatId)
52
- && (threads.length > 0 ? threads.every(value => value === thread) : thread === null);
53
- }
54
-
55
- async function queueChunk(filePath, checkpoint) {
56
- const metadata = await lstat(filePath);
57
- if (!metadata.isFile() || metadata.isSymbolicLink()) throw new Error('GOAL_QUEUE_NOT_REGULAR');
58
- const fileId = queueIdentity(metadata);
59
- const offset = checkpoint.fileId === fileId && metadata.size >= checkpoint.offset ? checkpoint.offset : 0;
60
- const bytes = Math.min(MAX_READ_BYTES, metadata.size - offset);
61
- if (bytes <= 0) return { fileId, offset, lines: [] };
62
- const handle = await open(filePath, 'r');
63
- try {
64
- const buffer = Buffer.alloc(bytes);
65
- const { bytesRead } = await handle.read(buffer, 0, bytes, offset);
66
- const content = buffer.subarray(0, bytesRead);
67
- const lastNewline = content.lastIndexOf(10);
68
- if (lastNewline < 0) {
69
- if (bytesRead >= MAX_READ_BYTES) throw new Error('GOAL_QUEUE_LINE_TOO_LARGE');
70
- return { fileId, offset, lines: [] };
71
- }
72
- const complete = content.subarray(0, lastNewline + 1);
73
- return { fileId, offset, lines: complete.toString('utf8').split('\n').slice(0, -1) };
74
- } finally { await handle.close(); }
75
- }
76
-
77
- async function ingestGoalInbox(input) {
78
- const checkpoint = await readCheckpoint(input.checkpointFile);
79
- let cursor = checkpoint.offset;
80
- const results = [];
81
- const chunk = await queueChunk(input.queueFile, checkpoint);
82
- if (chunk.offset !== checkpoint.offset || chunk.fileId !== checkpoint.fileId) cursor = chunk.offset;
83
- for (const line of chunk.lines) {
84
- cursor += Buffer.byteLength(line, 'utf8') + 1;
85
- const envelope = parseQueueLine(line);
86
- if (!envelope || !isAuthorizedInstruction(envelope, input)
87
- || !/\bZIEL-[A-Za-z0-9._-]+\.json\b/u.test(envelope.text)) continue;
88
- try {
89
- const instruction = parseGoalInstruction(envelope.text);
90
- const accepted = checkpoint.accepted.find((item) => item.sha256 === instruction.sha256);
91
- if (accepted) {
92
- results.push({ status: 'duplicate', messageId: String(envelope.meta.message_id),
93
- sha256: instruction.sha256, goal: { goalId: accepted.goalId } });
94
- continue;
95
- }
96
- const ingested = await ingestGoalInstruction({ ...input, instructionText: envelope.text });
97
- results.push({ status: ingested.duplicate ? 'duplicate' : 'ingested',
98
- messageId: String(envelope.meta.message_id), ...ingested });
99
- checkpoint.accepted.push({ sha256: ingested.sha256, goalId: ingested.goal.goalId });
100
- checkpoint.accepted = checkpoint.accepted.slice(-512);
101
- } catch (error) {
102
- results.push({ status: 'rejected', messageId: String(envelope.meta.message_id),
103
- error: String(error?.message || error).slice(0, 200) });
104
- }
105
- const result = results.at(-1);
106
- result.eventId = createHash('sha256').update(line).digest('hex');
107
- // Keep status persistence outside the ingestion catch and before advancing the durable cursor.
108
- await input.recordResult?.(result);
109
- }
110
- await writeCheckpoint(input.checkpointFile, {
111
- schema: CHECKPOINT_SCHEMA,
112
- fileId: chunk.fileId,
113
- offset: cursor,
114
- lastMessageId: results.at(-1)?.messageId || checkpoint.lastMessageId || null,
115
- accepted: checkpoint.accepted,
116
- updatedAt: new Date().toISOString(),
117
- });
118
- return results;
119
- }
120
-
121
- export {
122
- CHECKPOINT_SCHEMA,
123
- ingestGoalInbox,
124
- isAuthorizedInstruction,
125
- parseQueueLine,
126
- queueIdentity,
127
- };
1
+ import { open, lstat, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+
5
+ import { ingestGoalInstruction, parseGoalInstruction } from './agentspine-king-goal-intake.mjs';
6
+
7
+ const CHECKPOINT_SCHEMA = 'blun.king-goal-queue-checkpoint/v1';
8
+ const MAX_READ_BYTES = 4 * 1024 * 1024;
9
+
10
+ function queueIdentity(metadata) {
11
+ return `${metadata.dev}:${metadata.ino}:${metadata.birthtimeMs}`;
12
+ }
13
+
14
+ async function readCheckpoint(filePath) {
15
+ try {
16
+ const value = JSON.parse(await readFile(filePath, 'utf8'));
17
+ if (value?.schema === CHECKPOINT_SCHEMA && typeof value.fileId === 'string'
18
+ && Number.isSafeInteger(value.offset) && value.offset >= 0 && Array.isArray(value.accepted)
19
+ && value.accepted.length <= 512 && value.accepted.every(item => item && /^[a-f0-9]{64}$/.test(item.sha256)
20
+ && typeof item.goalId === 'string' && item.goalId.length > 0)) return value;
21
+ throw new Error('GOAL_QUEUE_CHECKPOINT_INVALID');
22
+ } catch (error) {
23
+ if (error?.code !== 'ENOENT') throw error;
24
+ }
25
+ return { schema: CHECKPOINT_SCHEMA, fileId: '', offset: 0, lastMessageId: null, accepted: [] };
26
+ }
27
+
28
+ async function writeCheckpoint(filePath, value) {
29
+ await mkdir(path.dirname(filePath), { recursive: true });
30
+ const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
31
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, {
32
+ encoding: 'utf8', flag: 'wx', mode: 0o600,
33
+ });
34
+ await rename(temporary, filePath);
35
+ }
36
+
37
+ function parseQueueLine(line) {
38
+ let envelope;
39
+ try { envelope = JSON.parse(line); } catch { return null; }
40
+ if (!envelope || typeof envelope.text !== 'string' || !envelope.meta || Array.isArray(envelope.meta)
41
+ || typeof envelope.meta !== 'object') return null;
42
+ return envelope;
43
+ }
44
+
45
+ function isAuthorizedInstruction(envelope, input) {
46
+ const addressed = envelope.meta.addressed === true || envelope.meta.addressed === 'true';
47
+ const threads = ['thread_id', 'telegram_thread_id', 'message_thread_id']
48
+ .map(key => envelope.meta[key]).filter(value => value !== null && value !== undefined && value !== '').map(String);
49
+ const thread = input.instructionThreadId === null || input.instructionThreadId === undefined ? null : String(input.instructionThreadId);
50
+ return addressed && String(envelope.meta.user_id) === String(input.instructionSenderId)
51
+ && String(envelope.meta.chat_id) === String(input.instructionChatId)
52
+ && (threads.length > 0 ? threads.every(value => value === thread) : thread === null);
53
+ }
54
+
55
+ async function queueChunk(filePath, checkpoint) {
56
+ const metadata = await lstat(filePath);
57
+ if (!metadata.isFile() || metadata.isSymbolicLink()) throw new Error('GOAL_QUEUE_NOT_REGULAR');
58
+ const fileId = queueIdentity(metadata);
59
+ const offset = checkpoint.fileId === fileId && metadata.size >= checkpoint.offset ? checkpoint.offset : 0;
60
+ const bytes = Math.min(MAX_READ_BYTES, metadata.size - offset);
61
+ if (bytes <= 0) return { fileId, offset, lines: [] };
62
+ const handle = await open(filePath, 'r');
63
+ try {
64
+ const buffer = Buffer.alloc(bytes);
65
+ const { bytesRead } = await handle.read(buffer, 0, bytes, offset);
66
+ const content = buffer.subarray(0, bytesRead);
67
+ const lastNewline = content.lastIndexOf(10);
68
+ if (lastNewline < 0) {
69
+ if (bytesRead >= MAX_READ_BYTES) throw new Error('GOAL_QUEUE_LINE_TOO_LARGE');
70
+ return { fileId, offset, lines: [] };
71
+ }
72
+ const complete = content.subarray(0, lastNewline + 1);
73
+ return { fileId, offset, lines: complete.toString('utf8').split('\n').slice(0, -1) };
74
+ } finally { await handle.close(); }
75
+ }
76
+
77
+ async function ingestGoalInbox(input) {
78
+ const checkpoint = await readCheckpoint(input.checkpointFile);
79
+ let cursor = checkpoint.offset;
80
+ const results = [];
81
+ const chunk = await queueChunk(input.queueFile, checkpoint);
82
+ if (chunk.offset !== checkpoint.offset || chunk.fileId !== checkpoint.fileId) cursor = chunk.offset;
83
+ for (const line of chunk.lines) {
84
+ cursor += Buffer.byteLength(line, 'utf8') + 1;
85
+ const envelope = parseQueueLine(line);
86
+ if (!envelope || !isAuthorizedInstruction(envelope, input)
87
+ || !/\bZIEL-[A-Za-z0-9._-]+\.json\b/u.test(envelope.text)) continue;
88
+ try {
89
+ const instruction = parseGoalInstruction(envelope.text);
90
+ const accepted = checkpoint.accepted.find((item) => item.sha256 === instruction.sha256);
91
+ if (accepted) {
92
+ results.push({ status: 'duplicate', messageId: String(envelope.meta.message_id),
93
+ sha256: instruction.sha256, goal: { goalId: accepted.goalId } });
94
+ continue;
95
+ }
96
+ const ingested = await ingestGoalInstruction({ ...input, instructionText: envelope.text });
97
+ results.push({ status: ingested.duplicate ? 'duplicate' : 'ingested',
98
+ messageId: String(envelope.meta.message_id), ...ingested });
99
+ checkpoint.accepted.push({ sha256: ingested.sha256, goalId: ingested.goal.goalId });
100
+ checkpoint.accepted = checkpoint.accepted.slice(-512);
101
+ } catch (error) {
102
+ results.push({ status: 'rejected', messageId: String(envelope.meta.message_id),
103
+ error: String(error?.message || error).slice(0, 200) });
104
+ }
105
+ const result = results.at(-1);
106
+ result.eventId = createHash('sha256').update(line).digest('hex');
107
+ // Keep status persistence outside the ingestion catch and before advancing the durable cursor.
108
+ await input.recordResult?.(result);
109
+ }
110
+ await writeCheckpoint(input.checkpointFile, {
111
+ schema: CHECKPOINT_SCHEMA,
112
+ fileId: chunk.fileId,
113
+ offset: cursor,
114
+ lastMessageId: results.at(-1)?.messageId || checkpoint.lastMessageId || null,
115
+ accepted: checkpoint.accepted,
116
+ updatedAt: new Date().toISOString(),
117
+ });
118
+ return results;
119
+ }
120
+
121
+ export {
122
+ CHECKPOINT_SCHEMA,
123
+ ingestGoalInbox,
124
+ isAuthorizedInstruction,
125
+ parseQueueLine,
126
+ queueIdentity,
127
+ };
@@ -1,106 +1,106 @@
1
- import { createHash } from 'node:crypto';
2
- import { lstat, readFile, realpath } from 'node:fs/promises';
3
- import path from 'node:path';
4
-
5
- const ASSIGNMENT_SCHEMA = 'blun.king-goal-assignment/v1';
6
- const INSTRUCTION_RE = /(ZIEL-[A-Za-z0-9._-]+\.json)\b[^\r\n]{0,200}?\bsha256\b\s*[=:]?\s*([a-f0-9]{64})(?:\s|[),.;]|$)/iu;
7
-
8
- function digest(value) {
9
- return createHash('sha256').update(value).digest('hex');
10
- }
11
-
12
- function parseGoalInstruction(text) {
13
- const match = String(text || '').match(INSTRUCTION_RE);
14
- if (!match) throw new Error('GOAL_INSTRUCTION_REQUIRES_FILE_AND_SHA256');
15
- return Object.freeze({ fileName: match[1], sha256: match[2].toLowerCase() });
16
- }
17
-
18
- function assignmentInput(value) {
19
- if (!value || value.schema !== ASSIGNMENT_SCHEMA || typeof value.goalId !== 'string'
20
- || typeof value.logicalPersonaId !== 'string' || typeof value.successCriterion !== 'string'
21
- || !Array.isArray(value.steps) || value.steps.length === 0) {
22
- throw new Error('GOAL_ASSIGNMENT_INVALID');
23
- }
24
- assertTerminalGoalGate(value.steps);
25
- return value;
26
- }
27
-
28
- function assertTerminalGoalGate(steps) {
29
- const ids = new Set(steps.map((step) => step?.stepId));
30
- if (ids.size !== steps.length || ids.has(undefined)) throw new Error('GOAL_PLAN_STEP_IDS_INVALID');
31
- const dependedOn = new Set(steps.flatMap((step) => Array.isArray(step.dependsOn) ? step.dependsOn : []));
32
- const terminalSteps = steps.filter((step) => !dependedOn.has(step.stepId));
33
- if (terminalSteps.length !== 1 || !terminalSteps[0].execution?.verification) {
34
- throw new Error('GOAL_PLAN_TERMINAL_GATE_REQUIRED');
35
- }
36
- const terminalId = terminalSteps[0].stepId;
37
- const byId = new Map(steps.map((step) => [step.stepId, step]));
38
- const reachesGate = (stepId, seen = new Set()) => {
39
- if (stepId === terminalId) return true;
40
- if (seen.has(stepId)) return false;
41
- seen.add(stepId);
42
- return steps.some((candidate) => Array.isArray(candidate.dependsOn)
43
- && candidate.dependsOn.includes(stepId) && reachesGate(candidate.stepId, new Set(seen)));
44
- };
45
- if ([...byId.keys()].some((stepId) => !reachesGate(stepId))) {
46
- throw new Error('GOAL_PLAN_TERMINAL_GATE_MUST_DEPEND_ON_ALL_STEPS');
47
- }
48
- }
49
-
50
- function inside(directory, candidate) {
51
- const relative = path.relative(directory, candidate);
52
- return relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
53
- }
54
-
55
- async function readVerifiedGoalFile(exchangeDirectory, instruction) {
56
- const exchange = await realpath(path.resolve(exchangeDirectory));
57
- const candidate = path.resolve(exchange, instruction.fileName);
58
- if (!inside(exchange, candidate)) throw new Error('GOAL_FILE_OUTSIDE_EXCHANGE');
59
- const metadata = await lstat(candidate);
60
- if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > 1024 * 1024) {
61
- throw new Error('GOAL_FILE_NOT_REGULAR');
62
- }
63
- const canonical = await realpath(candidate);
64
- if (!inside(exchange, canonical)) throw new Error('GOAL_FILE_OUTSIDE_EXCHANGE');
65
- const payload = await readFile(canonical);
66
- if (digest(payload) !== instruction.sha256) throw new Error('GOAL_FILE_SHA256_MISMATCH');
67
- let value;
68
- try { value = JSON.parse(payload.toString('utf8')); }
69
- catch { throw new Error('GOAL_FILE_INVALID_JSON'); }
70
- return { assignment: assignmentInput(value), filePath: canonical, sha256: instruction.sha256 };
71
- }
72
-
73
- async function ingestGoalInstruction(input) {
74
- const instruction = parseGoalInstruction(input.instructionText);
75
- const verified = await readVerifiedGoalFile(input.exchangeDirectory, instruction);
76
- const value = verified.assignment;
77
- if (value.logicalPersonaId !== input.logicalPersonaId) throw new Error('GOAL_PERSONA_MISMATCH');
78
- if (value.projectId !== input.projectId) throw new Error('GOAL_PROJECT_MISMATCH');
79
- if (value.groupId !== input.groupId || value.ownerSubjectId !== input.ownerSubjectId) {
80
- throw new Error('GOAL_AUTHORITY_MISMATCH');
81
- }
82
- if (value.snapshotSha256 !== input.snapshot.snapshotSha256) throw new Error('GOAL_SNAPSHOT_MISMATCH');
83
- const result = await input.assignGoal({
84
- root: input.snapshot.root,
85
- goalId: value.goalId,
86
- agentId: input.runtimePersonaId,
87
- ownerSubjectId: value.ownerSubjectId,
88
- projectId: value.projectId,
89
- groupId: value.groupId,
90
- priority: value.priority ?? 70,
91
- successCriterion: value.successCriterion,
92
- nextSafeStep: value.nextSafeStep ?? null,
93
- steps: value.steps,
94
- deadline: value.deadline ?? null,
95
- confirmation: 'local-owner-confirmed',
96
- });
97
- return Object.freeze({ ...verified, duplicate: result.duplicate === true, goal: result.goal });
98
- }
99
-
100
- export {
101
- ASSIGNMENT_SCHEMA,
102
- assertTerminalGoalGate,
103
- ingestGoalInstruction,
104
- parseGoalInstruction,
105
- readVerifiedGoalFile,
106
- };
1
+ import { createHash } from 'node:crypto';
2
+ import { lstat, readFile, realpath } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ const ASSIGNMENT_SCHEMA = 'blun.king-goal-assignment/v1';
6
+ const INSTRUCTION_RE = /(ZIEL-[A-Za-z0-9._-]+\.json)\b[^\r\n]{0,200}?\bsha256\b\s*[=:]?\s*([a-f0-9]{64})(?:\s|[),.;]|$)/iu;
7
+
8
+ function digest(value) {
9
+ return createHash('sha256').update(value).digest('hex');
10
+ }
11
+
12
+ function parseGoalInstruction(text) {
13
+ const match = String(text || '').match(INSTRUCTION_RE);
14
+ if (!match) throw new Error('GOAL_INSTRUCTION_REQUIRES_FILE_AND_SHA256');
15
+ return Object.freeze({ fileName: match[1], sha256: match[2].toLowerCase() });
16
+ }
17
+
18
+ function assignmentInput(value) {
19
+ if (!value || value.schema !== ASSIGNMENT_SCHEMA || typeof value.goalId !== 'string'
20
+ || typeof value.logicalPersonaId !== 'string' || typeof value.successCriterion !== 'string'
21
+ || !Array.isArray(value.steps) || value.steps.length === 0) {
22
+ throw new Error('GOAL_ASSIGNMENT_INVALID');
23
+ }
24
+ assertTerminalGoalGate(value.steps);
25
+ return value;
26
+ }
27
+
28
+ function assertTerminalGoalGate(steps) {
29
+ const ids = new Set(steps.map((step) => step?.stepId));
30
+ if (ids.size !== steps.length || ids.has(undefined)) throw new Error('GOAL_PLAN_STEP_IDS_INVALID');
31
+ const dependedOn = new Set(steps.flatMap((step) => Array.isArray(step.dependsOn) ? step.dependsOn : []));
32
+ const terminalSteps = steps.filter((step) => !dependedOn.has(step.stepId));
33
+ if (terminalSteps.length !== 1 || !terminalSteps[0].execution?.verification) {
34
+ throw new Error('GOAL_PLAN_TERMINAL_GATE_REQUIRED');
35
+ }
36
+ const terminalId = terminalSteps[0].stepId;
37
+ const byId = new Map(steps.map((step) => [step.stepId, step]));
38
+ const reachesGate = (stepId, seen = new Set()) => {
39
+ if (stepId === terminalId) return true;
40
+ if (seen.has(stepId)) return false;
41
+ seen.add(stepId);
42
+ return steps.some((candidate) => Array.isArray(candidate.dependsOn)
43
+ && candidate.dependsOn.includes(stepId) && reachesGate(candidate.stepId, new Set(seen)));
44
+ };
45
+ if ([...byId.keys()].some((stepId) => !reachesGate(stepId))) {
46
+ throw new Error('GOAL_PLAN_TERMINAL_GATE_MUST_DEPEND_ON_ALL_STEPS');
47
+ }
48
+ }
49
+
50
+ function inside(directory, candidate) {
51
+ const relative = path.relative(directory, candidate);
52
+ return relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
53
+ }
54
+
55
+ async function readVerifiedGoalFile(exchangeDirectory, instruction) {
56
+ const exchange = await realpath(path.resolve(exchangeDirectory));
57
+ const candidate = path.resolve(exchange, instruction.fileName);
58
+ if (!inside(exchange, candidate)) throw new Error('GOAL_FILE_OUTSIDE_EXCHANGE');
59
+ const metadata = await lstat(candidate);
60
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > 1024 * 1024) {
61
+ throw new Error('GOAL_FILE_NOT_REGULAR');
62
+ }
63
+ const canonical = await realpath(candidate);
64
+ if (!inside(exchange, canonical)) throw new Error('GOAL_FILE_OUTSIDE_EXCHANGE');
65
+ const payload = await readFile(canonical);
66
+ if (digest(payload) !== instruction.sha256) throw new Error('GOAL_FILE_SHA256_MISMATCH');
67
+ let value;
68
+ try { value = JSON.parse(payload.toString('utf8')); }
69
+ catch { throw new Error('GOAL_FILE_INVALID_JSON'); }
70
+ return { assignment: assignmentInput(value), filePath: canonical, sha256: instruction.sha256 };
71
+ }
72
+
73
+ async function ingestGoalInstruction(input) {
74
+ const instruction = parseGoalInstruction(input.instructionText);
75
+ const verified = await readVerifiedGoalFile(input.exchangeDirectory, instruction);
76
+ const value = verified.assignment;
77
+ if (value.logicalPersonaId !== input.logicalPersonaId) throw new Error('GOAL_PERSONA_MISMATCH');
78
+ if (value.projectId !== input.projectId) throw new Error('GOAL_PROJECT_MISMATCH');
79
+ if (value.groupId !== input.groupId || value.ownerSubjectId !== input.ownerSubjectId) {
80
+ throw new Error('GOAL_AUTHORITY_MISMATCH');
81
+ }
82
+ if (value.snapshotSha256 !== input.snapshot.snapshotSha256) throw new Error('GOAL_SNAPSHOT_MISMATCH');
83
+ const result = await input.assignGoal({
84
+ root: input.snapshot.root,
85
+ goalId: value.goalId,
86
+ agentId: input.runtimePersonaId,
87
+ ownerSubjectId: value.ownerSubjectId,
88
+ projectId: value.projectId,
89
+ groupId: value.groupId,
90
+ priority: value.priority ?? 70,
91
+ successCriterion: value.successCriterion,
92
+ nextSafeStep: value.nextSafeStep ?? null,
93
+ steps: value.steps,
94
+ deadline: value.deadline ?? null,
95
+ confirmation: 'local-owner-confirmed',
96
+ });
97
+ return Object.freeze({ ...verified, duplicate: result.duplicate === true, goal: result.goal });
98
+ }
99
+
100
+ export {
101
+ ASSIGNMENT_SCHEMA,
102
+ assertTerminalGoalGate,
103
+ ingestGoalInstruction,
104
+ parseGoalInstruction,
105
+ readVerifiedGoalFile,
106
+ };