blun-king-cli 9.1.602 → 9.1.603
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -97
- package/LIESMICH.txt +57 -57
- package/README.md +90 -90
- package/bin/agentspine-king-goal-inbox.mjs +127 -127
- package/bin/agentspine-king-goal-intake.mjs +106 -106
- package/bin/agentspine-king-host-runner.mjs +109 -109
- package/bin/agentspine-king-snapshot-policy.cjs +80 -80
- package/bin/agentspine-king-status-policy.mjs +226 -226
- package/bin/agentspine-king-worker-host.mjs +160 -160
- package/bin/core-bootstrap.js +77 -77
- package/bin/launcher-mode.js +93 -93
- package/bin/launcher-restart-policy.cjs +131 -131
- package/bin/launcher-runtime.js +491 -491
- package/bin/managed-node.js +186 -186
- package/bin/managed-plugin-selection.cjs +115 -115
- package/bin/native-module-repair.js +201 -201
- package/bin/node-runtime.js +34 -34
- package/bin/node-version.js +49 -49
- package/bin/plugin-bootstrap.js +162 -162
- package/bin/private-paths.js +1606 -1606
- package/bin/runtime-exit-ledger.cjs +144 -144
- package/bin/runtime-exit-ledger.d.cts +23 -23
- package/bin/standard-tools-bootstrap.js +272 -272
- package/bin/turn-thinking-policy.cjs +82 -82
- package/bin/update-copy.js +200 -200
- package/bin/update-lease.js +597 -597
- package/bin/update-notice.js +1568 -1568
- package/bin/verify-agent-behavior.cjs +122 -122
- package/bin/verify-agent-components.cjs +104 -104
- package/bin/verify-bundled-agent-sources.cjs +83 -83
- package/bin/windows-node-crash-dump.cjs +289 -289
- package/blun.mjs +115 -7
- package/codebase-index/codebase_index.py +470 -470
- package/dist-web/assets/{CodeBlockNode-CXnTLMNr.js → CodeBlockNode-TKKkJX8L.js} +1 -1
- 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
- package/dist-web/assets/DesignSystemView-DwK1aQMo.js +12 -0
- package/dist-web/assets/DesignSystemView-szjTj7ns.css +1 -0
- package/dist-web/assets/{Tooltip-CFQEzqC-.js → Tooltip-CTWv7WCU.js} +1 -1
- package/dist-web/assets/{abnfDiagram-N423BO3Z-Z1APZG5m.js → abnfDiagram-N423BO3Z-DA8V8Zjh.js} +1 -1
- package/dist-web/assets/{arc-C4VyWloT.js → arc-YtddKbdY.js} +1 -1
- package/dist-web/assets/{architectureDiagram-T3A2C74G-DpddbUcM.js → architectureDiagram-T3A2C74G-CGEiObmo.js} +1 -1
- package/dist-web/assets/{blockDiagram-VBNYF7ZC-BqccOSMr.js → blockDiagram-VBNYF7ZC-Bd5Vt50e.js} +1 -1
- package/dist-web/assets/{c4Diagram-5PPSVZJV-OHAcJrTy.js → c4Diagram-5PPSVZJV-D2ApPM54.js} +1 -1
- package/dist-web/assets/channel-BnluexuN.js +1 -0
- package/dist-web/assets/{chunk-2GRJ4B5K-DqJHs8Fw.js → chunk-2GRJ4B5K-BDcIEZSl.js} +1 -1
- package/dist-web/assets/{chunk-2Q5K7J3B-B1nCd91o.js → chunk-2Q5K7J3B-6aSabCX7.js} +1 -1
- package/dist-web/assets/{chunk-5RXB4S5H-neG70kZx.js → chunk-5RXB4S5H-BFRQvIJS.js} +1 -1
- package/dist-web/assets/{chunk-5VM5RSS4-l7biGqeH.js → chunk-5VM5RSS4-ChAa-DNY.js} +1 -1
- package/dist-web/assets/{chunk-6Q2QTUOP-BO-F11Gs.js → chunk-6Q2QTUOP-BIfrb2Jc.js} +1 -1
- package/dist-web/assets/{chunk-GF5L2VYU-BtC69fw0.js → chunk-GF5L2VYU-BR0gvtWI.js} +1 -1
- package/dist-web/assets/{chunk-JWPE2WC7-DIOubEqr.js → chunk-JWPE2WC7-BgXKsx5E.js} +1 -1
- package/dist-web/assets/{chunk-KBJHAD2P-DDUeH_Ba.js → chunk-KBJHAD2P-I8mrZhpz.js} +1 -1
- package/dist-web/assets/{chunk-RYQCIY6F-B9gFMVbS.js → chunk-RYQCIY6F-BMPaem92.js} +1 -1
- package/dist-web/assets/{chunk-XXDRQBXY-Bn1Xz6TQ.js → chunk-XXDRQBXY-COy0J-0j.js} +1 -1
- package/dist-web/assets/classDiagram-JCYQIIEL-BliFdSD9.js +1 -0
- package/dist-web/assets/classDiagram-v2-OCEON4UE-BliFdSD9.js +1 -0
- package/dist-web/assets/{cose-bilkent-JH36ORCC-DqigO3T4.js → cose-bilkent-JH36ORCC-MosTTRdo.js} +1 -1
- package/dist-web/assets/{cynefin-VYW2F7L2-rpD93tQ-.js → cynefin-VYW2F7L2-5tjPgrkP.js} +1 -1
- package/dist-web/assets/{cynefinDiagram-MW4NZA55-DbTqnkVJ.js → cynefinDiagram-MW4NZA55-CbDcdInR.js} +1 -1
- package/dist-web/assets/{dagre-VZM6K2ZE-DcM8aEVY.js → dagre-VZM6K2ZE-YaBom1ZH.js} +1 -1
- package/dist-web/assets/{diagram-7IWD3JNH-UymgsBNa.js → diagram-7IWD3JNH-Da4m-Hgz.js} +1 -1
- package/dist-web/assets/{diagram-B4RE2ZJO-BiYu5WPF.js → diagram-B4RE2ZJO-CAUUegxg.js} +1 -1
- package/dist-web/assets/{diagram-LBJQPF4R-BD3kkgOO.js → diagram-LBJQPF4R-o21mWKG4.js} +1 -1
- package/dist-web/assets/{diagram-Q27KOJAE-D1mYQ_cI.js → diagram-Q27KOJAE-BA3z3sXf.js} +1 -1
- package/dist-web/assets/{diagram-UB23O5K3-BtX8u6HG.js → diagram-UB23O5K3-CGRdSSDK.js} +1 -1
- package/dist-web/assets/{ebnfDiagram-BXEA7PRR-CBAnFkRF.js → ebnfDiagram-BXEA7PRR-Bw-iU9FP.js} +1 -1
- package/dist-web/assets/{erDiagram-JOGREHBK-3vKcWsxm.js → erDiagram-JOGREHBK-BwM1yiR0.js} +1 -1
- package/dist-web/assets/{flowDiagram-UKHOOZJN-D9wBbHm2.js → flowDiagram-UKHOOZJN-z05eOR1Q.js} +1 -1
- package/dist-web/assets/{ganttDiagram-PKOTCBZU-Cd_dViF-.js → ganttDiagram-PKOTCBZU-BMUubNTl.js} +1 -1
- package/dist-web/assets/{gitGraphDiagram-DS77QQ5N-BiEEtTo7.js → gitGraphDiagram-DS77QQ5N-Dccbi1wU.js} +1 -1
- package/dist-web/assets/{index-DdEakFJM.js → index-BBbC5LRF.js} +1 -1
- package/dist-web/assets/{index-BmKj7CB1.css → index-C9eDH3u-.css} +1 -1
- package/dist-web/assets/{index-Ba1OvLjf.js → index-D48p0M2S.js} +2 -2
- package/dist-web/assets/{index-CiPA8Kex.js → index-h0KEOmkU.js} +14 -14
- package/dist-web/assets/{index10-CNr4U-Nw.js → index10-Qbuw1N6u.js} +1 -1
- package/dist-web/assets/{index11-Dto_yZNz.js → index11-g8v025wN.js} +1 -1
- package/dist-web/assets/{index5-B2PldaO2.js → index5-BYNcV8BY.js} +1 -1
- package/dist-web/assets/{index6-CBdZmmyx.js → index6-BetANtnV.js} +1 -1
- package/dist-web/assets/{index7-Dz9oCsUF.js → index7-BcPbgCKN.js} +1 -1
- package/dist-web/assets/{index8-B6k25jtU.js → index8-B1lp-RvF.js} +2 -2
- package/dist-web/assets/{infoDiagram-6WML65LV-D4ph2sIH.js → infoDiagram-6WML65LV-BF_pbUnN.js} +1 -1
- package/dist-web/assets/{ishikawaDiagram-WSZJBQD7-DjKjbMm9.js → ishikawaDiagram-WSZJBQD7-Dedv4urS.js} +1 -1
- package/dist-web/assets/{journeyDiagram-NVQOT4AX-DttHDfPQ.js → journeyDiagram-NVQOT4AX-B1Ex3b6o.js} +1 -1
- package/dist-web/assets/{kanban-definition-27J2QSJJ-C5gB-W0d.js → kanban-definition-27J2QSJJ-BtCGliBH.js} +1 -1
- package/dist-web/assets/{linear-BRGe4-d6.js → linear-ByItc8Qr.js} +1 -1
- package/dist-web/assets/{mermaid.core-D-9Sx1No.js → mermaid.core-B1M2O7nN.js} +4 -4
- package/dist-web/assets/{mindmap-definition-FAOFIHXS-Cg_hoVIq.js → mindmap-definition-FAOFIHXS-D0UHKUyj.js} +1 -1
- package/dist-web/assets/{pegDiagram-VL7TDLO6-CSX9hitD.js → pegDiagram-VL7TDLO6-D7zYCVmG.js} +1 -1
- package/dist-web/assets/{pieDiagram-7S7Q4E2Y-0L0mxahZ.js → pieDiagram-7S7Q4E2Y-Bd0i9CHa.js} +1 -1
- package/dist-web/assets/{quadrantDiagram-CIZ2JOQS-U483z0er.js → quadrantDiagram-CIZ2JOQS-MG-Ug47d.js} +1 -1
- package/dist-web/assets/{railroadDiagram-AXF67PYL-D80a2_DJ.js → railroadDiagram-AXF67PYL-AZdl90Gj.js} +1 -1
- package/dist-web/assets/{requirementDiagram-LRYGKXZP-CeyHfq8t.js → requirementDiagram-LRYGKXZP-DjplF9kt.js} +1 -1
- package/dist-web/assets/{sankeyDiagram-W5VNT64P-CCISkkrR.js → sankeyDiagram-W5VNT64P-Bi9Zgfjs.js} +1 -1
- package/dist-web/assets/{sequenceDiagram-SI44F4Z6-jQfm2XS7.js → sequenceDiagram-SI44F4Z6-ChEpdCvb.js} +1 -1
- package/dist-web/assets/{sizeCapture-X5ZJPWSS-lRZsmCoS.js → sizeCapture-X5ZJPWSS-B5RFUdaa.js} +1 -1
- package/dist-web/assets/{stateDiagram-OKZ733FA-ClCgTdUm.js → stateDiagram-OKZ733FA-BkRblSKp.js} +1 -1
- package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fVkQcACW.js +1 -0
- package/dist-web/assets/{swimlanes-SLNWSIFB-BCdOUN05.js → swimlanes-SLNWSIFB-DLXH6Zhc.js} +2 -2
- package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-Bg-BiZJQ.js +8 -0
- package/dist-web/assets/{timeline-definition-Z64GVDOM-DpWutSKa.js → timeline-definition-Z64GVDOM-C_N88siO.js} +1 -1
- package/dist-web/assets/{vennDiagram-T6HMQDX7-DVC5JVsc.js → vennDiagram-T6HMQDX7-DMsnvDt0.js} +1 -1
- package/dist-web/assets/{vue.runtime.esm-bundler-BwwRtQwQ.js → vue.runtime.esm-bundler-B-3uIy85.js} +1 -1
- package/dist-web/assets/{wardleyDiagram-T6FBY63Y-BK2mrq7L.js → wardleyDiagram-T6FBY63Y-C9SJIPzq.js} +1 -1
- package/dist-web/assets/{xychartDiagram-ELKLHX3M-DUJu9F7-.js → xychartDiagram-ELKLHX3M-DZLWA6Go.js} +1 -1
- package/dist-web/index.html +32 -32
- package/dist-web/vis/index.html.gz +0 -0
- package/package.json +1 -1
- package/scripts/fix-node-pty-perms.js +53 -53
- package/standard-skills/blun-app-design-system/SKILL.md +30 -30
- package/standard-skills/blun-app-design-system/provenance.json +17 -17
- package/standard-skills/blun-app-design-system/references/tokens.md +48 -48
- package/standard-skills/blun-app-design-system/scripts/check-blun-design.mjs +49 -49
- package/telegram-plugin/blun.plugin.json +20 -20
- package/telegram-plugin/commands/access.md +37 -37
- package/worker-host.mjs +5 -5
- package/dist-web/assets/DesignSystemView-6HgAUEmC.css +0 -1
- package/dist-web/assets/DesignSystemView-BbMOu-XO.js +0 -12
- package/dist-web/assets/channel-B2VOQOI-.js +0 -1
- package/dist-web/assets/classDiagram-JCYQIIEL-BA1TjAxj.js +0 -1
- package/dist-web/assets/classDiagram-v2-OCEON4UE-BA1TjAxj.js +0 -1
- package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fi_lrTAx.js +0 -1
- 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
|
+
};
|