@nestos/kanban 0.0.0-stage → 0.5.2
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/LICENSE +674 -0
- package/README.md +165 -2
- package/dist/activity-BbC4sgKL.d.cts +258 -0
- package/dist/activity-Czlan5xW.d.ts +258 -0
- package/dist/activity-module-DaO1Rry0.d.cts +22 -0
- package/dist/activity-module-T22wliZu.d.ts +22 -0
- package/dist/activity-ports-Dqxa86zT.d.cts +12 -0
- package/dist/activity-ports-cxfrelaL.d.ts +12 -0
- package/dist/activity.cjs +16 -0
- package/dist/activity.cjs.map +1 -0
- package/dist/activity.d.cts +12 -0
- package/dist/activity.d.ts +12 -0
- package/dist/activity.js +16 -0
- package/dist/activity.js.map +1 -0
- package/dist/chunk-3JYLJPE5.js +95 -0
- package/dist/chunk-3JYLJPE5.js.map +1 -0
- package/dist/chunk-3LEIHB7Y.js +221 -0
- package/dist/chunk-3LEIHB7Y.js.map +1 -0
- package/dist/chunk-4XF6LQSU.cjs +9 -0
- package/dist/chunk-4XF6LQSU.cjs.map +1 -0
- package/dist/chunk-5Q7WB63A.cjs +282 -0
- package/dist/chunk-5Q7WB63A.cjs.map +1 -0
- package/dist/chunk-7KDOTBRX.cjs +58 -0
- package/dist/chunk-7KDOTBRX.cjs.map +1 -0
- package/dist/chunk-AHHEGTUA.js +148 -0
- package/dist/chunk-AHHEGTUA.js.map +1 -0
- package/dist/chunk-AOHQCX7K.cjs +95 -0
- package/dist/chunk-AOHQCX7K.cjs.map +1 -0
- package/dist/chunk-DINLR6E6.cjs +443 -0
- package/dist/chunk-DINLR6E6.cjs.map +1 -0
- package/dist/chunk-EYTVC2H5.cjs +1612 -0
- package/dist/chunk-EYTVC2H5.cjs.map +1 -0
- package/dist/chunk-FEE2YBZ7.js +1972 -0
- package/dist/chunk-FEE2YBZ7.js.map +1 -0
- package/dist/chunk-FNO5K3A7.js +282 -0
- package/dist/chunk-FNO5K3A7.js.map +1 -0
- package/dist/chunk-IHHDXZPZ.js +382 -0
- package/dist/chunk-IHHDXZPZ.js.map +1 -0
- package/dist/chunk-J3EAIVA3.js +280 -0
- package/dist/chunk-J3EAIVA3.js.map +1 -0
- package/dist/chunk-KH3WF3K4.js +443 -0
- package/dist/chunk-KH3WF3K4.js.map +1 -0
- package/dist/chunk-M35X5PHO.js +196 -0
- package/dist/chunk-M35X5PHO.js.map +1 -0
- package/dist/chunk-QRGZD2JU.cjs +1972 -0
- package/dist/chunk-QRGZD2JU.cjs.map +1 -0
- package/dist/chunk-RBOER6EW.cjs +221 -0
- package/dist/chunk-RBOER6EW.cjs.map +1 -0
- package/dist/chunk-RQP77ZTY.js +9 -0
- package/dist/chunk-RQP77ZTY.js.map +1 -0
- package/dist/chunk-RSH6QSEC.cjs +196 -0
- package/dist/chunk-RSH6QSEC.cjs.map +1 -0
- package/dist/chunk-VMRDXOLX.cjs +148 -0
- package/dist/chunk-VMRDXOLX.cjs.map +1 -0
- package/dist/chunk-X4UHZX6F.js +58 -0
- package/dist/chunk-X4UHZX6F.js.map +1 -0
- package/dist/chunk-XBSGUVCY.cjs +382 -0
- package/dist/chunk-XBSGUVCY.cjs.map +1 -0
- package/dist/chunk-XKDZHAUE.js +1612 -0
- package/dist/chunk-XKDZHAUE.js.map +1 -0
- package/dist/chunk-ZUZGAO6G.cjs +280 -0
- package/dist/chunk-ZUZGAO6G.cjs.map +1 -0
- package/dist/content-C5aJZFlw.d.cts +35 -0
- package/dist/content-hLH5BNud.d.ts +35 -0
- package/dist/contracts/index.cjs +31 -0
- package/dist/contracts/index.cjs.map +1 -0
- package/dist/contracts/index.d.cts +5 -0
- package/dist/contracts/index.d.ts +5 -0
- package/dist/contracts/index.js +31 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/discovery-9E-LfwIw.d.cts +61 -0
- package/dist/discovery-D60yrjej.d.ts +61 -0
- package/dist/discovery.cjs +377 -0
- package/dist/discovery.cjs.map +1 -0
- package/dist/discovery.d.cts +22 -0
- package/dist/discovery.d.ts +22 -0
- package/dist/discovery.js +377 -0
- package/dist/discovery.js.map +1 -0
- package/dist/execution-bridge-fpCnH6z5.d.cts +52 -0
- package/dist/execution-bridge-rk0uVy_n.d.ts +52 -0
- package/dist/execution.cjs +18 -0
- package/dist/execution.cjs.map +1 -0
- package/dist/execution.d.cts +42 -0
- package/dist/execution.d.ts +42 -0
- package/dist/execution.js +18 -0
- package/dist/execution.js.map +1 -0
- package/dist/index.cjs +74 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +54 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +74 -0
- package/dist/index.js.map +1 -0
- package/dist/module-CYfE7YhM.d.ts +11 -0
- package/dist/module-Ca9L3qCn.d.cts +11 -0
- package/dist/planning-CHrOqQ6b.d.cts +879 -0
- package/dist/planning-CHrOqQ6b.d.ts +879 -0
- package/dist/ports-B0McVg7B.d.ts +143 -0
- package/dist/ports-BeGO1VRI.d.cts +49 -0
- package/dist/ports-GQy0x-RI.d.cts +143 -0
- package/dist/ports-ykGiNUSx.d.ts +49 -0
- package/dist/runtime/index.cjs +205 -0
- package/dist/runtime/index.cjs.map +1 -0
- package/dist/runtime/index.d.cts +75 -0
- package/dist/runtime/index.d.ts +75 -0
- package/dist/runtime/index.js +205 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/integration.cjs +423 -0
- package/dist/runtime/integration.cjs.map +1 -0
- package/dist/runtime/integration.d.cts +658 -0
- package/dist/runtime/integration.d.ts +658 -0
- package/dist/runtime/integration.js +423 -0
- package/dist/runtime/integration.js.map +1 -0
- package/dist/storage.cjs +21 -0
- package/dist/storage.cjs.map +1 -0
- package/dist/storage.d.cts +71 -0
- package/dist/storage.d.ts +71 -0
- package/dist/storage.js +21 -0
- package/dist/storage.js.map +1 -0
- package/dist/store-DPwHzGiz.d.cts +28 -0
- package/dist/store-DgdrPCV9.d.ts +28 -0
- package/dist/task-projection-CSorlMws.d.ts +11 -0
- package/dist/task-projection-DKKB_ueb.d.cts +11 -0
- package/examples/standalone.ts +22 -0
- package/package.json +143 -4
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } async function _asyncOptionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = await fn(value); } else if (op === 'call' || op === 'optionalCall') { value = await fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
var _chunkEYTVC2H5cjs = require('./chunk-EYTVC2H5.cjs');
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
var _chunkRSH6QSECcjs = require('./chunk-RSH6QSEC.cjs');
|
|
24
|
+
|
|
25
|
+
// src/store.ts
|
|
26
|
+
var _vfscore = require('@nestos/vfs-core');
|
|
27
|
+
var SeqKanbanStore = class _SeqKanbanStore {
|
|
28
|
+
constructor(fs, path, bodies) {
|
|
29
|
+
this.fs = fs;
|
|
30
|
+
this.path = path;
|
|
31
|
+
this.bodies = _nullishCoalesce(bodies, () => ( { read: async (hash) => {
|
|
32
|
+
const body = await fs.meta.assets.getAsset(path, `${hash}.json`);
|
|
33
|
+
if (body === null || body instanceof ArrayBuffer) return body;
|
|
34
|
+
throw new (0, _chunkRSH6QSECcjs.KanbanError)("BODY_INTEGRITY");
|
|
35
|
+
}, write: async (hash, bytes) => {
|
|
36
|
+
await fs.meta.assets.putAsset(path, `${hash}.json`, bytes);
|
|
37
|
+
} }));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static async open(fs, directory = "/kanban", bodies) {
|
|
41
|
+
if (!_optionalChain([fs, 'access', _2 => _2.meta, 'access', _3 => _3.seq, 'optionalAccess', _4 => _4.transaction])) throw new (0, _chunkRSH6QSECcjs.KanbanError)("TRANSACTION_REQUIRED");
|
|
42
|
+
try {
|
|
43
|
+
await fs.driver.createFile({ parentPath: directory, name: "boards.seq", type: "seqfile", recursive: true });
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (!(error instanceof _vfscore.FSError) || error.code !== "EEXIST") throw error;
|
|
46
|
+
}
|
|
47
|
+
return new _SeqKanbanStore(fs, `${directory}/boards.seq`, bodies);
|
|
48
|
+
}
|
|
49
|
+
async boardId(number) {
|
|
50
|
+
return this.fs.meta.seq.getEntry(this.path, _chunkEYTVC2H5cjs.boardNumberKey.call(void 0, number));
|
|
51
|
+
}
|
|
52
|
+
async ids() {
|
|
53
|
+
return JSON.parse(await _asyncNullishCoalesce(await this.fs.meta.seq.getEntry(this.path, "index"), async () => ( "[]")));
|
|
54
|
+
}
|
|
55
|
+
async events(id, after, limit, through) {
|
|
56
|
+
const end = Math.min(through, after + limit);
|
|
57
|
+
const keys = Array.from({ length: Math.max(0, end - after) }, (_, index) => `event/${encodeURIComponent(id)}/${String(after + index + 1).padStart(16, "0")}`);
|
|
58
|
+
if (!keys.length) return [];
|
|
59
|
+
const rows = await this.fs.meta.seq.getEntries(this.path, keys);
|
|
60
|
+
return keys.filter((key) => rows[key] !== void 0).map((key) => JSON.parse(rows[key]));
|
|
61
|
+
}
|
|
62
|
+
async archived(boardId, itemId) {
|
|
63
|
+
const raw = await this.fs.meta.seq.getEntry(this.path, _chunkEYTVC2H5cjs.archiveKey.call(void 0, boardId, itemId));
|
|
64
|
+
if (!raw) return null;
|
|
65
|
+
const record = await _chunkEYTVC2H5cjs.decodeRow.call(void 0, raw, this.bodies);
|
|
66
|
+
record.item.number ??= await _asyncOptionalChain([(await this.view(boardId)), 'optionalAccess', async _5 => _5.state, 'access', async _6 => _6.archiveIndex, 'optionalAccess', async _7 => _7.find, 'call', async _8 => _8((item) => item.id === itemId), 'optionalAccess', async _9 => _9.number]);
|
|
67
|
+
return record;
|
|
68
|
+
}
|
|
69
|
+
async load(id, receiptKey) {
|
|
70
|
+
return this.fs.meta.seq.transaction(async (tx) => {
|
|
71
|
+
const raw = await tx.getEntry(this.path, _chunkEYTVC2H5cjs.boardKey.call(void 0, id));
|
|
72
|
+
return { raw, rows: await tx.getEntries(this.path, _chunkEYTVC2H5cjs.rowKeys.call(void 0, raw)), receipt: receiptKey ? await tx.getEntry(this.path, receiptKey) : void 0 };
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async view(id) {
|
|
76
|
+
const raw = await this.fs.meta.seq.getEntry(this.path, _chunkEYTVC2H5cjs.boardKey.call(void 0, id));
|
|
77
|
+
const indexed = _chunkEYTVC2H5cjs.indexedView.call(void 0, raw);
|
|
78
|
+
if (indexed) return indexed;
|
|
79
|
+
const state = await this.read(id);
|
|
80
|
+
return state ? _chunkEYTVC2H5cjs.viewFromState.call(void 0, state) : null;
|
|
81
|
+
}
|
|
82
|
+
async read(id) {
|
|
83
|
+
return _chunkEYTVC2H5cjs.hydrate.call(void 0, await this.load(id), this.bodies);
|
|
84
|
+
}
|
|
85
|
+
async update(id, operationId, fingerprint, change) {
|
|
86
|
+
const receiptKey = `receipt/${encodeURIComponent(id)}/${encodeURIComponent(operationId)}`;
|
|
87
|
+
const before = await this.load(id, receiptKey), current = await _chunkEYTVC2H5cjs.hydrate.call(void 0, before, this.bodies), receipt = before.receipt;
|
|
88
|
+
if (receipt) {
|
|
89
|
+
_chunkEYTVC2H5cjs.verifyReceipt.call(void 0, receipt, fingerprint);
|
|
90
|
+
if (!current) throw new (0, _chunkRSH6QSECcjs.KanbanError)("CORRUPT_RECEIPT");
|
|
91
|
+
return current;
|
|
92
|
+
}
|
|
93
|
+
const original = JSON.stringify(current), next = change(current);
|
|
94
|
+
_chunkEYTVC2H5cjs.numberEntities.call(void 0, next);
|
|
95
|
+
if (JSON.stringify(next) === original) return next;
|
|
96
|
+
return this.persist(id, receiptKey, fingerprint, before, next);
|
|
97
|
+
}
|
|
98
|
+
async persist(id, receiptKey, fingerprint, before, next) {
|
|
99
|
+
const events = [...next.events], archives = await Promise.all((_nullishCoalesce(next.archiveWrites, () => ( []))).map(async (write) => ({ key: _chunkEYTVC2H5cjs.archiveKey.call(void 0, id, write.id), value: write.value ? await _chunkEYTVC2H5cjs.encodeRow.call(void 0, write.value, this.bodies) : null })));
|
|
100
|
+
delete next.archiveWrites;
|
|
101
|
+
next.events = [];
|
|
102
|
+
const encoded = await _chunkEYTVC2H5cjs.encodeBoard.call(void 0, next, this.bodies);
|
|
103
|
+
const committed = await this.fs.meta.seq.transaction(async (tx) => {
|
|
104
|
+
const latestReceipt = await tx.getEntry(this.path, receiptKey);
|
|
105
|
+
if (latestReceipt) {
|
|
106
|
+
_chunkEYTVC2H5cjs.verifyReceipt.call(void 0, latestReceipt, fingerprint);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (await tx.getEntry(this.path, _chunkEYTVC2H5cjs.boardKey.call(void 0, id)) !== before.raw) throw new (0, _chunkRSH6QSECcjs.KanbanError)("REVISION_CONFLICT");
|
|
110
|
+
await commitBoardNumber(tx, this.path, id, before, next, encoded);
|
|
111
|
+
const eventRows = events.map((event) => ({ key: `event/${encodeURIComponent(id)}/${String(event.sequence).padStart(16, "0")}`, value: JSON.stringify(event) }));
|
|
112
|
+
await writeRows(tx, this.path, [
|
|
113
|
+
...eventRows,
|
|
114
|
+
..._chunkEYTVC2H5cjs.rowChanges.call(void 0, before, encoded),
|
|
115
|
+
...archives,
|
|
116
|
+
{ key: _chunkEYTVC2H5cjs.boardKey.call(void 0, id), value: encoded.raw },
|
|
117
|
+
{ key: receiptKey, value: _chunkEYTVC2H5cjs.receiptDigest.call(void 0, fingerprint) }
|
|
118
|
+
]);
|
|
119
|
+
const ids = JSON.parse(await _asyncNullishCoalesce(await tx.getEntry(this.path, "index"), async () => ( "[]")));
|
|
120
|
+
if (!ids.includes(id)) await tx.setEntry(this.path, "index", JSON.stringify([...ids, id]));
|
|
121
|
+
return next;
|
|
122
|
+
});
|
|
123
|
+
if (committed) return committed;
|
|
124
|
+
const replay = await this.read(id);
|
|
125
|
+
if (!replay) throw new (0, _chunkRSH6QSECcjs.KanbanError)("CORRUPT_RECEIPT");
|
|
126
|
+
return replay;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
async function writeRows(tx, path, rows) {
|
|
130
|
+
for (const row of rows) {
|
|
131
|
+
if (row.value === null) await tx.deleteEntry(path, row.key);
|
|
132
|
+
else await tx.setEntry(path, row.key, row.value);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function commitBoardNumber(tx, path, id, before, next, encoded) {
|
|
136
|
+
const previous = _chunkEYTVC2H5cjs.counter.call(void 0, await tx.getEntry(path, _chunkEYTVC2H5cjs.boardCounterKey));
|
|
137
|
+
next.board.number = _chunkEYTVC2H5cjs.allocateBoardNumber.call(void 0, before.raw, previous);
|
|
138
|
+
const owner = await tx.getEntry(path, _chunkEYTVC2H5cjs.boardNumberKey.call(void 0, next.board.number));
|
|
139
|
+
if (owner !== null && owner !== id) throw new (0, _chunkRSH6QSECcjs.KanbanError)("INVALID_NUMBERING");
|
|
140
|
+
encoded.raw = _chunkEYTVC2H5cjs.patchBoardNumber.call(void 0, encoded.raw, next.board.number);
|
|
141
|
+
await tx.setEntry(path, _chunkEYTVC2H5cjs.boardCounterKey, String(Math.max(previous, next.board.number)));
|
|
142
|
+
await tx.setEntry(path, _chunkEYTVC2H5cjs.boardNumberKey.call(void 0, next.board.number), id);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
exports.SeqKanbanStore = SeqKanbanStore;
|
|
148
|
+
//# sourceMappingURL=chunk-VMRDXOLX.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/li/share/prj/x1/libraries/kanban/dist/chunk-VMRDXOLX.cjs","../src/store.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;ACtBA,2CAA0C;AAKnC,IAAM,eAAA,EAAN,MAAM,gBAAsC;AAAA,EAEvC,WAAA,CAA6B,EAAA,EAAkC,IAAA,EAAc,MAAA,EAA0B;AAA1E,IAAA,IAAA,CAAA,GAAA,EAAA,EAAA;AAAkC,IAAA,IAAA,CAAA,KAAA,EAAA,IAAA;AACnE,IAAA,IAAA,CAAK,OAAA,mBAAS,MAAA,UAAU,EAAC,IAAA,EAAM,MAAA,CAAM,IAAA,EAAA,GAAQ;AACzC,MAAA,MAAM,KAAA,EAAO,MAAM,EAAA,CAAG,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,IAAA,EAAM,CAAA,EAAA;AACC,MAAA;AACZ,MAAA;AACT,IAAA;AAA4C,MAAA;AAAiB,IAAA;AAClG,EAAA;AAPiB,EAAA;AAQ4F,EAAA;AAChE,IAAA;AACrC,IAAA;AAA2C,MAAA;AACjC,IAAA;AAAoD,MAAA;AAAuB,IAAA;AAC/C,IAAA;AAC9C,EAAA;AACsD,EAAA;AAA8C,IAAA;AAAwB,EAAA;AAC7F,EAAA;AACwB,IAAA;AACvD,EAAA;AACgG,EAAA;AACjD,IAAA;AACQ,IAAA;AAEzB,IAAA;AAE2B,IAAA;AACF,IAAA;AACvD,EAAA;AAEoF,EAAA;AAC9B,IAAA;AACjC,IAAA;AAAyE,IAAA;AACvC,IAAA;AAAqE,IAAA;AAC5H,EAAA;AACsG,EAAA;AAChD,IAAA;AACD,MAAA;AACD,MAAA;AAC/C,IAAA;AACL,EAAA;AACuB,EAAA;AAC+B,IAAA;AACnB,IAAA;AAAuB,IAAA;AACtB,IAAA;AAAyC,IAAA;AAC7E,EAAA;AACoD,EAAA;AAAgD,IAAA;AAAE,EAAA;AAExB,EAAA;AACtB,IAAA;AACJ,IAAA;AACnC,IAAA;AAAmC,MAAA;AAAuC,MAAA;AAA2B,MAAA;AAAQ,IAAA;AACzE,IAAA;AAC9B,IAAA;AAC2B,IAAA;AACG,IAAA;AACrD,EAAA;AACgG,EAAA;AAC1C,IAAA;AAEtC,IAAA;AAA8B,IAAA;AAES,IAAA;AACT,IAAA;AACO,MAAA;AAC1B,MAAA;AAAyC,QAAA;AAAU,QAAA;AAAK,MAAA;AACnC,MAAA;AACG,MAAA;AACE,MAAA;AACd,MAAA;AAAI,QAAA;AAAwC,QAAA;AAAM,QAAA;AAC3C,QAAA;AAA0C,QAAA;AAAc,MAAA;AACtC,MAAA;AACV,MAAA;AACvC,MAAA;AACV,IAAA;AACqB,IAAA;AACW,IAAA;AAAuD,IAAA;AAAU,IAAA;AACtG,EAAA;AACJ;AAC4J;AAChI,EAAA;AAC+B,IAAA;AACJ,IAAA;AACnD,EAAA;AACJ;AAEoI;AAC/E,EAAA;AACG,EAAA;AACC,EAAA;AACP,EAAA;AACI,EAAA;AACG,EAAA;AACH,EAAA;AACtD;AD2CwD;AACA;AACA;AACA","file":"/home/li/share/prj/x1/libraries/kanban/dist/chunk-VMRDXOLX.cjs","sourcesContent":[null,"import {numberEntities, boardCounterKey, boardNumberKey, counter, allocateBoardNumber, patchBoardNumber} from './numbering';\nimport type { KanbanEvent, KanbanArchivedItem } from './contracts';\nimport { FSError, type IFileSystem } from '@nestos/vfs-core';\nimport { KanbanError, type KanbanState, type KanbanStore } from './ports';\nimport {indexedView, viewFromState, archiveKey, boardKey as key, rowKeys, hydrate, encodeBoard, rowChanges, encodeRow, decodeRow, receiptDigest, verifyReceipt, type StoredBoard, type KanbanBodyStore} from './storage-codec';\n\n/** One user-scoped journal, with atomic board/index/receipt commits. */\nexport class SeqKanbanStore implements KanbanStore {\n private readonly bodies: KanbanBodyStore;\n private constructor(private readonly fs: IFileSystem, private readonly path: string, bodies?: KanbanBodyStore) {\n this.bodies = bodies ?? {read: async hash => {\n const body = await fs.meta.assets.getAsset(path, `${hash}.json`);\n if (body === null || body instanceof ArrayBuffer) return body;\n throw new KanbanError('BODY_INTEGRITY');\n }, write: async (hash, bytes) => {await fs.meta.assets.putAsset(path, `${hash}.json`, bytes);}};\n }\n static async open(fs: IFileSystem, directory = '/kanban', bodies?: KanbanBodyStore): Promise<SeqKanbanStore> {\n if (!fs.meta.seq?.transaction) throw new KanbanError('TRANSACTION_REQUIRED');\n try { await fs.driver.createFile({ parentPath: directory, name: 'boards.seq', type: 'seqfile', recursive: true }); }\n catch (error) { if (!(error instanceof FSError) || error.code !== 'EEXIST') throw error; }\n return new SeqKanbanStore(fs, `${directory}/boards.seq`, bodies);\n }\n async boardId(number: number): Promise<string | null> {return this.fs.meta.seq!.getEntry(this.path, boardNumberKey(number));}\n async ids(): Promise<string[]> {\n return JSON.parse(await this.fs.meta.seq!.getEntry(this.path, 'index') ?? '[]');\n }\n async events(id: string, after: number, limit: number, through: number): Promise<KanbanEvent[]> {\n const end = Math.min(through, after + limit);\n const keys = Array.from({length: Math.max(0, end - after)}, (_, index) =>\n `event/${encodeURIComponent(id)}/${String(after + index + 1).padStart(16, '0')}`);\n if (!keys.length) return [];\n // The committed sequence freezes this immutable prefix; bulk key reads avoid history scans.\n const rows = await this.fs.meta.seq!.getEntries(this.path, keys);\n return keys.filter(key => rows[key] !== undefined).map(key => JSON.parse(rows[key]) as KanbanEvent);\n }\n\n async archived(boardId: string, itemId: string): Promise<KanbanArchivedItem | null> {\n const raw = await this.fs.meta.seq!.getEntry(this.path, archiveKey(boardId, itemId));\n if (!raw) return null; const record = await decodeRow<KanbanArchivedItem>(raw, this.bodies);\n record.item.number ??= (await this.view(boardId))?.state.archiveIndex?.find(item => item.id === itemId)?.number; return record;\n }\n private async load(id: string, receiptKey?: string): Promise<StoredBoard & {receipt?: string | null}> {\n return this.fs.meta.seq!.transaction!(async tx => {\n const raw = await tx.getEntry(this.path, key(id));\n return {raw, rows: await tx.getEntries(this.path, rowKeys(raw)), receipt: receiptKey ? await tx.getEntry(this.path, receiptKey) : undefined};\n });\n }\n async view(id: string) {\n const raw = await this.fs.meta.seq!.getEntry(this.path, key(id));\n const indexed = indexedView(raw); if (indexed) return indexed;\n const state = await this.read(id); return state ? viewFromState(state) : null;\n }\n async read(id: string): Promise<KanbanState | null> {return hydrate(await this.load(id), this.bodies);}\n async update(id: string, operationId: string, fingerprint: string,\n change: (state: KanbanState | null) => KanbanState): Promise<KanbanState> {\n const receiptKey = `receipt/${encodeURIComponent(id)}/${encodeURIComponent(operationId)}`;\n const before = await this.load(id, receiptKey), current = await hydrate(before, this.bodies), receipt = before.receipt;\n if (receipt) {verifyReceipt(receipt, fingerprint); if (!current) throw new KanbanError('CORRUPT_RECEIPT'); return current;}\n const original = JSON.stringify(current), next = change(current);\n numberEntities(next);\n if (JSON.stringify(next) === original) return next;\n return this.persist(id, receiptKey, fingerprint, before, next);\n }\n private async persist(id: string, receiptKey: string, fingerprint: string, before: StoredBoard, next: KanbanState): Promise<KanbanState> {\n const events = [...next.events], archives = await Promise.all((next.archiveWrites ?? []).map(async write =>\n ({key: archiveKey(id, write.id), value: write.value ? await encodeRow(write.value, this.bodies) : null})));\n delete next.archiveWrites; next.events = [];\n // No asset I/O runs under the SeqFile transaction: it may use the same backend.\n const encoded = await encodeBoard(next, this.bodies);\n const committed = await this.fs.meta.seq!.transaction!(async tx => {\n const latestReceipt = await tx.getEntry(this.path, receiptKey);\n if (latestReceipt) {verifyReceipt(latestReceipt, fingerprint); return null;}\n if (await tx.getEntry(this.path, key(id)) !== before.raw) throw new KanbanError('REVISION_CONFLICT');\n await commitBoardNumber(tx, this.path, id, before, next, encoded);\n const eventRows = events.map(event => ({key: `event/${encodeURIComponent(id)}/${String(event.sequence).padStart(16, '0')}`, value: JSON.stringify(event)}));\n await writeRows(tx, this.path, [...eventRows, ...rowChanges(before, encoded), ...archives,\n {key: key(id), value: encoded.raw!}, {key: receiptKey, value: receiptDigest(fingerprint)}]);\n const ids: string[] = JSON.parse(await tx.getEntry(this.path, 'index') ?? '[]');\n if (!ids.includes(id)) await tx.setEntry(this.path, 'index', JSON.stringify([...ids, id]));\n return next;\n });\n if (committed) return committed;\n const replay = await this.read(id); if (!replay) throw new KanbanError('CORRUPT_RECEIPT'); return replay;\n }\n}\nasync function writeRows(tx: import('@nestos/vfs-core').ISeqFileTransaction, path: string, rows: Array<{key: string; value: string | null}>): Promise<void> {\n for (const row of rows) {\n if (row.value === null) await tx.deleteEntry(path, row.key);\n else await tx.setEntry(path, row.key, row.value);\n }\n}\n\nasync function commitBoardNumber(tx: import('@nestos/vfs-core').ISeqFileTransaction, path: string, id: string, before: StoredBoard, next: KanbanState, encoded: StoredBoard): Promise<void> {\n const previous = counter(await tx.getEntry(path, boardCounterKey));\n next.board.number = allocateBoardNumber(before.raw, previous);\n const owner = await tx.getEntry(path, boardNumberKey(next.board.number));\n if (owner !== null && owner !== id) throw new KanbanError('INVALID_NUMBERING');\n encoded.raw = patchBoardNumber(encoded.raw!, next.board.number);\n await tx.setEntry(path, boardCounterKey, String(Math.max(previous, next.board.number)));\n await tx.setEntry(path, boardNumberKey(next.board.number), id);\n}\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/contracts/references.ts
|
|
2
|
+
function kanbanReference(board, entity) {
|
|
3
|
+
const prefix = board.number ? `KB-${board.number}` : board.id;
|
|
4
|
+
if (!entity) return prefix;
|
|
5
|
+
if (!entity.number) return `${prefix}/${entity.id}`;
|
|
6
|
+
return entity.kind ? `${prefix}/${entity.kind === "log" ? "L" : "D"}-${entity.number}` : `${prefix}#${entity.number}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// src/contracts/configuration.ts
|
|
10
|
+
var DEFAULT_KANBAN_WORK_TYPES = [
|
|
11
|
+
{ id: "initiative", label: "Goal", rank: 0, executable: false },
|
|
12
|
+
{ id: "feature", label: "Group", rank: 1, executable: false },
|
|
13
|
+
{ id: "task", label: "Action", rank: 2, executable: true },
|
|
14
|
+
{ id: "fix", label: "Fix", rank: 2, executable: true },
|
|
15
|
+
{ id: "subtask", label: "Subtask", rank: 3, executable: true }
|
|
16
|
+
];
|
|
17
|
+
function executableWorkItem(board, item) {
|
|
18
|
+
return item.planning?.execution !== "children" && (board.workTypes ?? DEFAULT_KANBAN_WORK_TYPES).some((type) => type.id === (item.planning?.kind ?? board.defaultWorkType ?? "task") && type.executable);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/contracts/discovery.ts
|
|
22
|
+
var DEFAULT_KANBAN_SCAN_POLICY = {
|
|
23
|
+
idleMs: 3e5,
|
|
24
|
+
includeArchived: true,
|
|
25
|
+
maxSessions: 20,
|
|
26
|
+
maxInputBytes: 128 * 1024,
|
|
27
|
+
leaseMs: 6e5,
|
|
28
|
+
autoCreate: false
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/contracts/automation.ts
|
|
32
|
+
function sameAutomationBinding(left, right) {
|
|
33
|
+
const signature = (value) => JSON.stringify([
|
|
34
|
+
value?.planner?.system,
|
|
35
|
+
value?.planner?.id,
|
|
36
|
+
value?.planner?.revision,
|
|
37
|
+
value?.executor?.system,
|
|
38
|
+
value?.executor?.id,
|
|
39
|
+
value?.executor?.revision,
|
|
40
|
+
value?.coordinator?.kind,
|
|
41
|
+
value?.coordinator?.id,
|
|
42
|
+
value?.worker?.kind,
|
|
43
|
+
value?.worker?.id,
|
|
44
|
+
value?.autoAccept,
|
|
45
|
+
value?.preparation,
|
|
46
|
+
Object.entries(value?.preparationOptions ?? {}).sort(([a], [b]) => a.localeCompare(b))
|
|
47
|
+
]);
|
|
48
|
+
return signature(left) === signature(right);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
kanbanReference,
|
|
53
|
+
DEFAULT_KANBAN_WORK_TYPES,
|
|
54
|
+
executableWorkItem,
|
|
55
|
+
DEFAULT_KANBAN_SCAN_POLICY,
|
|
56
|
+
sameAutomationBinding
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=chunk-X4UHZX6F.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/contracts/references.ts","../src/contracts/configuration.ts","../src/contracts/discovery.ts","../src/contracts/automation.ts"],"sourcesContent":["import type {KanbanBoard} from './contracts';\n\nexport interface KanbanReferenceTarget {boardId: string; itemId?: string; recordId?: string; reference: string; scope: KanbanBoard['scope']}\nexport function kanbanReference(board: {id: string; number?: number}, entity?: {id: string; number?: number; kind?: 'log' | 'decision'}): string {\n const prefix = board.number ? `KB-${board.number}` : board.id;\n if (!entity) return prefix;\n if (!entity.number) return `${prefix}/${entity.id}`;\n return entity.kind ? `${prefix}/${entity.kind === 'log' ? 'L' : 'D'}-${entity.number}` : `${prefix}#${entity.number}`;\n}\n","import type {KanbanItem, KanbanBoard} from './contracts';\n\nexport interface KanbanPrincipal {kind: 'agent' | 'human' | 'service'; id: string}\nexport interface KanbanMember extends KanbanPrincipal {\n responsibility: string; roles?: Array<'worker' | 'reviewer' | 'coordinator'>;\n}\nexport interface KanbanWorkType {id: string; label: string; rank: number; executable: boolean}\nexport interface KanbanColumn {id: string; label: string; states: KanbanItem['status'][]}\nexport interface KanbanNotificationPolicy {rules?: Record<string, 'info' | 'error' | 'none'>}\nexport const DEFAULT_KANBAN_WORK_TYPES: readonly KanbanWorkType[] = [\n {id: 'initiative', label: 'Goal', rank: 0, executable: false},\n {id: 'feature', label: 'Group', rank: 1, executable: false},\n {id: 'task', label: 'Action', rank: 2, executable: true},\n {id: 'fix', label: 'Fix', rank: 2, executable: true},\n {id: 'subtask', label: 'Subtask', rank: 3, executable: true},\n];\n\nexport interface KanbanExternalReference {system: string; id: string; revision?: string}\n\n/** A composite work item delegates execution to its children. */\nexport function executableWorkItem(board:Pick<KanbanBoard,'workTypes'|'defaultWorkType'>, item:{planning?:{kind:string;execution?:'direct'|'children'}}):boolean {\n return item.planning?.execution!=='children'&&(board.workTypes??DEFAULT_KANBAN_WORK_TYPES)\n .some(type=>type.id===(item.planning?.kind??board.defaultWorkType??'task')&&type.executable);\n}\n","import type {KanbanItemSummary} from './planning';\nimport type {KanbanBoard} from './contracts';\n\nexport type {KanbanSourceRef, KanbanDiscoveryChange, KanbanDiscoveryBatch} from './source';\nimport type {KanbanDiscoveryChange} from './source';\nexport interface KanbanScanSession {\n id: string; revision: string; updatedAt: number | null; idleSince: number | null;\n activity: 'idle' | 'active' | 'waiting' | 'unknown'; archived: boolean; scannerOwned?: boolean;\n}\nexport interface KanbanScanText {\n session: KanbanScanSession; complete: boolean;\n messages: Array<{id: string; role: 'user' | 'assistant'; text: string}>;\n}\nexport interface KanbanScanConnection {\n kind: 'local' | 'remote'; connectionId: string;\n /** Explicit consent for moving source text to a different execution boundary. */\n allowContentTransfer: boolean;\n}\nexport interface KanbanScanPolicy {\n idleMs: number; includeArchived: boolean; maxSessions: number; maxInputBytes: number; leaseMs: number;\n autoCreate: boolean; connection: KanbanScanConnection;\n}\nexport interface KanbanScanInput {\n projectContext?: {totalRecords: number; excludedRecords?: number; omittedRecords: number; records: import('./context').KanbanProjectRecord[]};\n context?: {totalItems: number; omittedItems: number; archivedTotal: number; archivedItems: KanbanItemSummary[]};\n runId: string; executorId: string; board: KanbanBoard; sessions: KanbanScanText[]; connection: KanbanScanConnection;\n}\nexport const DEFAULT_KANBAN_SCAN_POLICY: Omit<KanbanScanPolicy, 'connection'> = {\n idleMs: 300000, includeArchived: true, maxSessions: 20, maxInputBytes: 128 * 1024, leaseMs: 600000, autoCreate: false,\n};\n\nexport interface KanbanDiscoveryResult {changes: KanbanDiscoveryChange[]; records?: import('./context').KanbanRecordInput[]}\n","import type {KanbanExternalReference, KanbanPrincipal} from './configuration';\n\nexport interface KanbanAutomationSettings {\n preparation?: KanbanExternalReference;\n preparationOptions?: Record<string, string>;\n enabled: boolean; planner: KanbanExternalReference; executor: KanbanExternalReference;\n coordinator: KanbanPrincipal; worker: KanbanPrincipal; autoAccept: boolean;\n}\nexport interface KanbanAutomationOption {\n name: string; label: string; defaultValue?: string;\n choices?: Array<{value: string; label: string}>;\n}\nexport interface KanbanAutomationTarget {reference: KanbanExternalReference; label: string; options?: KanbanAutomationOption[]}\nexport interface KanbanAutomationCatalog {\n preparations?: KanbanAutomationTarget[];\n targets: KanbanAutomationTarget[]; coordinator: KanbanPrincipal; worker: KanbanPrincipal;\n}\nexport interface KanbanAnalysisStatus {\n itemId: string; state: 'analyzing' | 'ready' | 'clarification' | 'failed' | 'unknown';\n reason?: string; code?: string; dependencies?: string[];\n}\nexport function sameAutomationBinding(left?: KanbanAutomationSettings, right?: KanbanAutomationSettings): boolean {\n const signature = (value?: KanbanAutomationSettings) => JSON.stringify([value?.planner?.system, value?.planner?.id, value?.planner?.revision,\n value?.executor?.system, value?.executor?.id, value?.executor?.revision, value?.coordinator?.kind, value?.coordinator?.id,\n value?.worker?.kind, value?.worker?.id, value?.autoAccept, value?.preparation, Object.entries(value?.preparationOptions ?? {}).sort(([a], [b]) => a.localeCompare(b))]);\n return signature(left) === signature(right);\n}\n"],"mappings":";AAGO,SAAS,gBAAgB,OAAsC,QAA2E;AAC7I,QAAM,SAAS,MAAM,SAAS,MAAM,MAAM,MAAM,KAAK,MAAM;AAC3D,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,CAAC,OAAO,OAAQ,QAAO,GAAG,MAAM,IAAI,OAAO,EAAE;AACjD,SAAO,OAAO,OAAO,GAAG,MAAM,IAAI,OAAO,SAAS,QAAQ,MAAM,GAAG,IAAI,OAAO,MAAM,KAAK,GAAG,MAAM,IAAI,OAAO,MAAM;AACvH;;;ACCO,IAAM,4BAAuD;AAAA,EAChE,EAAC,IAAI,cAAc,OAAO,QAAQ,MAAM,GAAG,YAAY,MAAK;AAAA,EAC5D,EAAC,IAAI,WAAW,OAAO,SAAS,MAAM,GAAG,YAAY,MAAK;AAAA,EAC1D,EAAC,IAAI,QAAQ,OAAO,UAAU,MAAM,GAAG,YAAY,KAAI;AAAA,EACvD,EAAC,IAAI,OAAO,OAAO,OAAO,MAAM,GAAG,YAAY,KAAI;AAAA,EACnD,EAAC,IAAI,WAAW,OAAO,WAAW,MAAM,GAAG,YAAY,KAAI;AAC/D;AAKO,SAAS,mBAAmB,OAAuD,MAAuE;AAC7J,SAAO,KAAK,UAAU,cAAY,eAAa,MAAM,aAAW,2BAC3D,KAAK,UAAM,KAAK,QAAM,KAAK,UAAU,QAAM,MAAM,mBAAiB,WAAS,KAAK,UAAU;AACnG;;;ACIO,IAAM,6BAAmE;AAAA,EAC5E,QAAQ;AAAA,EAAQ,iBAAiB;AAAA,EAAM,aAAa;AAAA,EAAI,eAAe,MAAM;AAAA,EAAM,SAAS;AAAA,EAAQ,YAAY;AACpH;;;ACRO,SAAS,sBAAsB,MAAiC,OAA2C;AAC9G,QAAM,YAAY,CAAC,UAAqC,KAAK,UAAU;AAAA,IAAC,OAAO,SAAS;AAAA,IAAQ,OAAO,SAAS;AAAA,IAAI,OAAO,SAAS;AAAA,IAChI,OAAO,UAAU;AAAA,IAAQ,OAAO,UAAU;AAAA,IAAI,OAAO,UAAU;AAAA,IAAU,OAAO,aAAa;AAAA,IAAM,OAAO,aAAa;AAAA,IACvH,OAAO,QAAQ;AAAA,IAAM,OAAO,QAAQ;AAAA,IAAI,OAAO;AAAA,IAAY,OAAO;AAAA,IAAa,OAAO,QAAQ,OAAO,sBAAsB,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAAA,EAAC,CAAC;AAC1K,SAAO,UAAU,IAAI,MAAM,UAAU,KAAK;AAC9C;","names":[]}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } async function _asyncOptionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = await fn(value); } else if (op === 'call' || op === 'optionalCall') { value = await fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
var _chunk4XF6LQSUcjs = require('./chunk-4XF6LQSU.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
var _chunkRSH6QSECcjs = require('./chunk-RSH6QSEC.cjs');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
var _chunkAOHQCX7Kcjs = require('./chunk-AOHQCX7K.cjs');
|
|
13
|
+
|
|
14
|
+
// src/execution-bridge.ts
|
|
15
|
+
async function createKanbanExecutionBridge(options) {
|
|
16
|
+
const path = `${options.directory}/executions.seq`;
|
|
17
|
+
try {
|
|
18
|
+
await options.fs.driver.createFile({ parentPath: options.directory, name: "executions.seq", type: "seqfile", recursive: true });
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (_optionalChain([error, 'optionalAccess', _ => _.code]) !== "EEXIST") throw error;
|
|
21
|
+
}
|
|
22
|
+
if (!_optionalChain([options, 'access', _2 => _2.fs, 'access', _3 => _3.meta, 'access', _4 => _4.seq, 'optionalAccess', _5 => _5.transaction])) throw new (0, _chunkRSH6QSECcjs.KanbanError)("TRANSACTION_REQUIRED");
|
|
23
|
+
let stopped = false, again = false, pending;
|
|
24
|
+
const writes = /* @__PURE__ */ new Set();
|
|
25
|
+
const failures = /* @__PURE__ */ new Map();
|
|
26
|
+
const track = (operation) => {
|
|
27
|
+
const tracked = operation.finally(() => {
|
|
28
|
+
writes.delete(tracked);
|
|
29
|
+
});
|
|
30
|
+
writes.add(tracked);
|
|
31
|
+
return tracked;
|
|
32
|
+
};
|
|
33
|
+
const active = () => !stopped;
|
|
34
|
+
const scan = async () => {
|
|
35
|
+
if (stopped) return;
|
|
36
|
+
if (pending) {
|
|
37
|
+
again = true;
|
|
38
|
+
return pending;
|
|
39
|
+
}
|
|
40
|
+
const started = Date.now();
|
|
41
|
+
pending = runBridge(options, path, active, track, failures).catch((error) => {
|
|
42
|
+
try {
|
|
43
|
+
options.log({ operationId: _chunkRSH6QSECcjs.randomUUID.call(void 0, ), stage: "bridge.scan", result: "failure", code: safeCode(error), durationMs: Date.now() - started });
|
|
44
|
+
} catch (e) {
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}).finally(() => {
|
|
48
|
+
pending = void 0;
|
|
49
|
+
if (again && !stopped) {
|
|
50
|
+
again = false;
|
|
51
|
+
void scan().catch(() => {
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return pending;
|
|
56
|
+
};
|
|
57
|
+
const unsubscribe = _optionalChain([options, 'access', _6 => _6.subscribe, 'optionalCall', _7 => _7(() => {
|
|
58
|
+
void scan().catch(() => {
|
|
59
|
+
});
|
|
60
|
+
})]);
|
|
61
|
+
return { scan, tick: scan, async quiesce() {
|
|
62
|
+
stopped = true;
|
|
63
|
+
_optionalChain([unsubscribe, 'optionalCall', _8 => _8()]);
|
|
64
|
+
if (!pending) return;
|
|
65
|
+
let timer;
|
|
66
|
+
try {
|
|
67
|
+
await Promise.race([pending, new Promise((resolve) => {
|
|
68
|
+
timer = setTimeout(resolve, _nullishCoalesce(options.shutdownTimeoutMs, () => ( 5e3)));
|
|
69
|
+
})]);
|
|
70
|
+
} finally {
|
|
71
|
+
if (timer) clearTimeout(timer);
|
|
72
|
+
await Promise.allSettled([...writes]);
|
|
73
|
+
}
|
|
74
|
+
} };
|
|
75
|
+
}
|
|
76
|
+
async function runBridge(options, path, active, track, failures) {
|
|
77
|
+
const workflow = await options.service.workflow(options.boardId);
|
|
78
|
+
const live = new Set(Object.values(workflow.dispatches).filter((dispatch) => ["prepared", "running", "unknown"].includes(dispatch.outcome)).map((dispatch) => dispatch.id));
|
|
79
|
+
for (const id of failures.keys()) if (!live.has(id)) failures.delete(id);
|
|
80
|
+
for (const dispatch of Object.values(workflow.dispatches)) {
|
|
81
|
+
if (!active()) return;
|
|
82
|
+
if (live.has(dispatch.id) && (_nullishCoalesce(_optionalChain([failures, 'access', _9 => _9.get, 'call', _10 => _10(dispatch.id), 'optionalAccess', _11 => _11.retryAt]), () => ( 0))) <= Date.now()) await runDispatch(options, path, dispatch, active, track, failures);
|
|
83
|
+
}
|
|
84
|
+
const automatic = typeof options.autoDispatch === "function" ? await options.autoDispatch() : options.autoDispatch;
|
|
85
|
+
if (!automatic || !active()) return;
|
|
86
|
+
for (const id of (await options.service.workflow(options.boardId)).ready) {
|
|
87
|
+
if (!active()) return;
|
|
88
|
+
const { item } = await options.service.item(options.boardId, id), owner = _optionalChain([item, 'access', _12 => _12.eligiblePrincipals, 'optionalAccess', _13 => _13[0]]);
|
|
89
|
+
if (!owner || !_chunkAOHQCX7Kcjs.taskDue.call(void 0, item, (_nullishCoalesce(options.now, () => ( Date.now)))())) continue;
|
|
90
|
+
const worker = await options.worker(owner), board = await worker.read(options.boardId);
|
|
91
|
+
if (!active()) return;
|
|
92
|
+
if (!_chunkAOHQCX7Kcjs.boardAllowsStart.call(void 0, board, item)) continue;
|
|
93
|
+
try {
|
|
94
|
+
const dispatchId = _chunkRSH6QSECcjs.randomUUID.call(void 0, );
|
|
95
|
+
const estimate = await _optionalChain([options, 'access', _14 => _14.estimate, 'optionalCall', _15 => _15(item, owner)]);
|
|
96
|
+
if (!active()) return;
|
|
97
|
+
await worker.execute(board.id, { operationId: "dispatch:" + dispatchId, expectedRevision: board.revision, command: { type: "prepare-dispatch", itemId: id, dispatchId, estimate, trigger: "automatic" } });
|
|
98
|
+
const dispatch = (await options.service.workflow(board.id)).dispatches[dispatchId];
|
|
99
|
+
if (active()) await runDispatch(options, path, dispatch, active, track, failures);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (!["WIP_LIMIT", "ASSIGNEE_WIP_LIMIT", "READY_LIMIT", "REVISION_CONFLICT"].includes(safeCode(error))) throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function runDispatch(options, path, dispatch, active, track, failures) {
|
|
106
|
+
const start = Date.now();
|
|
107
|
+
let stage = "inspect", result;
|
|
108
|
+
const previousFailure = failures.has(dispatch.id);
|
|
109
|
+
try {
|
|
110
|
+
let record = await loadRecord(options, path, dispatch);
|
|
111
|
+
if (!active()) return;
|
|
112
|
+
const current = await options.service.item(options.boardId, dispatch.itemId);
|
|
113
|
+
if ((_optionalChain([current, 'access', _16 => _16.item, 'access', _17 => _17.control, 'optionalAccess', _18 => _18.action]) === "cancel" || (await options.service.read(options.boardId)).lifecycle) && await track(preventSubmission(options, path, dispatch))) {
|
|
114
|
+
await settle(options, dispatch, { outcome: "cancelled", code: "CANCELLED_BEFORE_START" });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if ((!record || record.phase === "prepared") && (await _asyncOptionalChain([(await options.service.read(options.boardId)), 'access', async _19 => _19.executionControl, 'optionalAccess', async _20 => _20.paused]) || _optionalChain([current, 'access', _21 => _21.item, 'access', _22 => _22.control, 'optionalAccess', _23 => _23.action]) === "pause")) return;
|
|
118
|
+
if (!record) {
|
|
119
|
+
stage = "prepare";
|
|
120
|
+
await options.authorize(stage, dispatch);
|
|
121
|
+
if (!active()) return;
|
|
122
|
+
const item = (await options.service.item(options.boardId, dispatch.itemId)).item;
|
|
123
|
+
if (!active()) return;
|
|
124
|
+
const request = await prepareRequest(options, path, dispatch, item);
|
|
125
|
+
if (!active()) return;
|
|
126
|
+
record = await track(savePrepared(options, path, dispatch, request));
|
|
127
|
+
}
|
|
128
|
+
if (!active()) return;
|
|
129
|
+
const submit = record.phase === "prepared" && await track(claimSubmission(options, path, dispatch));
|
|
130
|
+
if (!submit && await options.fs.meta.seq.getEntry(path, "cancelled:" + dispatch.id)) {
|
|
131
|
+
await settle(options, dispatch, { outcome: "cancelled", code: "CANCELLED_BEFORE_START" });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!active()) return;
|
|
135
|
+
stage = submit ? "start" : "inspect";
|
|
136
|
+
await options.authorize(stage, dispatch);
|
|
137
|
+
if (!active()) return;
|
|
138
|
+
result = await (submit ? options.executor.start(record.request) : options.executor.inspect(record.request));
|
|
139
|
+
failures.delete(dispatch.id);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const notStarted = _optionalChain([error, 'optionalAccess', _24 => _24.executionOutcome]) === "not-started" && error.retryable === true;
|
|
142
|
+
if (notStarted && stage === "start") await track(releaseUnstartedSubmission(options, path, dispatch));
|
|
143
|
+
result = { outcome: stage === "prepare" && !notStarted ? "failed" : "unknown", code: safeCode(error) };
|
|
144
|
+
if (stage === "inspect" || notStarted) {
|
|
145
|
+
const previous = failures.get(dispatch.id), count = previous && previous.code === result.code ? previous.count + 1 : 1;
|
|
146
|
+
failures.set(dispatch.id, { code: result.code, count, retryAt: Date.now() + Math.min(6e4, 1e3 * 2 ** Math.min(count - 1, 6)) });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (stage !== "inspect" || _chunk4XF6LQSUcjs.executionObservationChanged.call(void 0, dispatch, result) || previousFailure && !failures.has(dispatch.id))
|
|
150
|
+
try {
|
|
151
|
+
options.log({ operationId: dispatch.id, stage: `bridge.${stage}`, result: ["unknown", "failed", "cancelled"].includes(result.outcome) ? "failure" : "success", code: result.code, ...failures.has(dispatch.id) ? { retryAfterMs: Math.max(0, failures.get(dispatch.id).retryAt - Date.now()) } : {}, durationMs: Date.now() - start });
|
|
152
|
+
} catch (e2) {
|
|
153
|
+
}
|
|
154
|
+
if (active()) try {
|
|
155
|
+
await linkSession(options, dispatch, result.executionSessionId);
|
|
156
|
+
if (active()) await settle(options, dispatch, result);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (safeCode(error) !== "REVISION_CONFLICT") throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function releaseUnstartedSubmission(options, path, dispatch) {
|
|
162
|
+
await options.fs.meta.seq.transaction(async (tx) => {
|
|
163
|
+
const raw = await tx.getEntry(path, dispatch.id);
|
|
164
|
+
if (!raw) throw new (0, _chunkRSH6QSECcjs.KanbanError)("EXECUTION_JOURNAL_MISSING");
|
|
165
|
+
const record = decodeRecord(raw, dispatch);
|
|
166
|
+
if (record.phase === "submitting") await tx.setEntry(path, dispatch.id, JSON.stringify({ ...record, phase: "prepared" }));
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async function prepareRequest(options, path, dispatch, item) {
|
|
170
|
+
if (!dispatch.continuation) return options.executor.prepare(dispatch, item);
|
|
171
|
+
if (!options.executor.prepareContinuation) throw new (0, _chunkRSH6QSECcjs.KanbanError)("CONTINUATION_UNSUPPORTED");
|
|
172
|
+
const previous = (await options.service.workflow(options.boardId)).dispatches[dispatch.continuation.previousDispatchId];
|
|
173
|
+
if (!previous || previous.itemId !== dispatch.itemId) throw new (0, _chunkRSH6QSECcjs.KanbanError)("CONTINUATION_SCOPE_MISMATCH");
|
|
174
|
+
const record = await loadRecord(options, path, previous);
|
|
175
|
+
if (!record) throw new (0, _chunkRSH6QSECcjs.KanbanError)("EXECUTION_JOURNAL_MISSING");
|
|
176
|
+
return options.executor.prepareContinuation(dispatch, item, record.request);
|
|
177
|
+
}
|
|
178
|
+
async function loadRecord(options, path, dispatch) {
|
|
179
|
+
const raw = await options.fs.meta.seq.getEntry(path, dispatch.id);
|
|
180
|
+
if (!raw) return;
|
|
181
|
+
return decodeRecord(raw, dispatch);
|
|
182
|
+
}
|
|
183
|
+
async function savePrepared(options, path, dispatch, request) {
|
|
184
|
+
const rawRequest = JSON.stringify(request);
|
|
185
|
+
if (typeof rawRequest !== "string" || new TextEncoder().encode(rawRequest).byteLength > 1024 * 1024) throw new (0, _chunkRSH6QSECcjs.KanbanError)("INVALID_EXECUTION_REQUEST");
|
|
186
|
+
return options.fs.meta.seq.transaction(async (tx) => {
|
|
187
|
+
const raw = await tx.getEntry(path, dispatch.id);
|
|
188
|
+
if (raw) return decodeRecord(raw, dispatch);
|
|
189
|
+
const record = { schema: 1, fence: dispatch.fence, request: JSON.parse(rawRequest), digest: _chunkRSH6QSECcjs.sha256HexSync.call(void 0, rawRequest), phase: "prepared" };
|
|
190
|
+
await tx.setEntry(path, dispatch.id, JSON.stringify(record));
|
|
191
|
+
return record;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async function claimSubmission(options, path, dispatch) {
|
|
195
|
+
return options.fs.meta.seq.transaction(async (tx) => {
|
|
196
|
+
const raw = await tx.getEntry(path, dispatch.id);
|
|
197
|
+
if (!raw) throw new (0, _chunkRSH6QSECcjs.KanbanError)("EXECUTION_JOURNAL_MISSING");
|
|
198
|
+
const record = decodeRecord(raw, dispatch);
|
|
199
|
+
if (record.phase !== "prepared" || await tx.getEntry(path, "cancelled:" + dispatch.id)) return false;
|
|
200
|
+
await tx.setEntry(path, dispatch.id, JSON.stringify({ ...record, phase: "submitting" }));
|
|
201
|
+
return true;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function decodeRecord(raw, dispatch) {
|
|
205
|
+
const record = JSON.parse(raw);
|
|
206
|
+
if (record.schema !== 1 || record.fence !== dispatch.fence || !["prepared", "submitting"].includes(record.phase) || record.digest !== _chunkRSH6QSECcjs.sha256HexSync.call(void 0, JSON.stringify(record.request))) throw new (0, _chunkRSH6QSECcjs.KanbanError)("INVALID_EXECUTION_JOURNAL");
|
|
207
|
+
return record;
|
|
208
|
+
}
|
|
209
|
+
async function settle(options, dispatch, result) {
|
|
210
|
+
const current = (await options.service.workflow(options.boardId)).dispatches[dispatch.id];
|
|
211
|
+
if (!current || !["prepared", "running", "unknown"].includes(current.outcome)) return;
|
|
212
|
+
const board = await options.service.read(options.boardId), item = board.items.find((item2) => item2.id === dispatch.itemId);
|
|
213
|
+
const renew = ["running", "unknown"].includes(result.outcome) && (_nullishCoalesce(_optionalChain([item, 'optionalAccess', _25 => _25.owner, 'optionalAccess', _26 => _26.leaseUntil]), () => ( 0))) <= Date.now() + board.policy.leaseMs / 2;
|
|
214
|
+
if (!renew && !_chunk4XF6LQSUcjs.executionObservationChanged.call(void 0, current, result)) return;
|
|
215
|
+
const { outcome, outputs, code, executionSessionId, controlCapabilities } = result, observation = { outcome, ...controlCapabilities ? { controlCapabilities } : {}, ...executionSessionId ? { executionSessionId } : {}, ...outputs ? { outputs } : {}, ...code ? { code } : {} };
|
|
216
|
+
const digest = _chunkRSH6QSECcjs.sha256HexSync.call(void 0, JSON.stringify(observation));
|
|
217
|
+
await options.service.execute(board.id, {
|
|
218
|
+
operationId: `settle:${dispatch.id}:${digest}:${board.revision}`,
|
|
219
|
+
expectedRevision: board.revision,
|
|
220
|
+
command: { type: "observe-execution", dispatchId: dispatch.id, fence: dispatch.fence, ...observation }
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async function linkSession(options, dispatch, sessionId) {
|
|
224
|
+
if (!sessionId || !options.executionReference) return;
|
|
225
|
+
const reference = options.executionReference(sessionId, dispatch.id);
|
|
226
|
+
const board = await options.service.read(options.boardId), item = board.items.find((item2) => item2.id === dispatch.itemId);
|
|
227
|
+
if (_optionalChain([item, 'optionalAccess', _27 => _27.owner, 'optionalAccess', _28 => _28.token]) !== dispatch.fence) throw new (0, _chunkRSH6QSECcjs.KanbanError)("STALE_EXECUTION_RESULT");
|
|
228
|
+
const current = _optionalChain([item, 'access', _29 => _29.externalRefs, 'optionalAccess', _30 => _30.filter, 'call', _31 => _31((ref) => ref.system === reference.system), 'access', _32 => _32.at, 'call', _33 => _33(-1)]);
|
|
229
|
+
if (_optionalChain([current, 'optionalAccess', _34 => _34.id]) === reference.id && current.revision === reference.revision) return;
|
|
230
|
+
await options.service.execute(board.id, {
|
|
231
|
+
operationId: `execution-session:${dispatch.id}:${_chunkRSH6QSECcjs.sha256HexSync.call(void 0, sessionId)}:${board.revision}`,
|
|
232
|
+
expectedRevision: board.revision,
|
|
233
|
+
command: { type: "link-reference", itemId: dispatch.itemId, reference }
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function safeCode(error) {
|
|
237
|
+
const code = _nullishCoalesce(_optionalChain([error, 'optionalAccess', _35 => _35.reasonCode]), () => ( _optionalChain([error, 'optionalAccess', _36 => _36.code])));
|
|
238
|
+
return typeof code === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? code : "EXECUTION_OBSERVATION_FAILED";
|
|
239
|
+
}
|
|
240
|
+
async function preventSubmission(options, path, dispatch) {
|
|
241
|
+
return options.fs.meta.seq.transaction(async (tx) => {
|
|
242
|
+
const raw = await tx.getEntry(path, dispatch.id);
|
|
243
|
+
if (raw && decodeRecord(raw, dispatch).phase === "submitting") return false;
|
|
244
|
+
await tx.setEntry(path, "cancelled:" + dispatch.id, dispatch.fence);
|
|
245
|
+
return true;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/monitor.ts
|
|
250
|
+
function startKanbanMonitor(options) {
|
|
251
|
+
let stopped = false, pending;
|
|
252
|
+
const tick = () => {
|
|
253
|
+
if (stopped || pending) return;
|
|
254
|
+
pending = Promise.resolve().then(() => stopped ? void 0 : options.service.scan()).then(() => stopped ? void 0 : _optionalChain([options, 'access', _37 => _37.onSuccess, 'optionalCall', _38 => _38()])).catch(async (error) => {
|
|
255
|
+
if (stopped) return;
|
|
256
|
+
try {
|
|
257
|
+
await options.onError(error);
|
|
258
|
+
} catch (e3) {
|
|
259
|
+
}
|
|
260
|
+
}).finally(() => {
|
|
261
|
+
pending = void 0;
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
const unsubscribe = options.subscribeTick(tick), unwatch = _optionalChain([options, 'access', _39 => _39.subscribeChanges, 'optionalCall', _40 => _40(tick)]);
|
|
265
|
+
if (options.initialScan !== false) tick();
|
|
266
|
+
return { async dispose() {
|
|
267
|
+
if (stopped) {
|
|
268
|
+
await pending;
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
stopped = true;
|
|
272
|
+
try {
|
|
273
|
+
unsubscribe();
|
|
274
|
+
_optionalChain([unwatch, 'optionalCall', _41 => _41()]);
|
|
275
|
+
} finally {
|
|
276
|
+
await pending;
|
|
277
|
+
}
|
|
278
|
+
} };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/work-messages.ts
|
|
282
|
+
var _vfscore = require('@nestos/vfs-core');
|
|
283
|
+
async function consumeWorkMessages(fs, directory, scope, journal, deliver, decode, publishReply) {
|
|
284
|
+
const publications = await readPublications(fs, directory, scope, decode), indexKey = "workspace-message-index:" + scope.attemptId;
|
|
285
|
+
const known = await _asyncNullishCoalesce(await journal.get(indexKey), async () => ( []));
|
|
286
|
+
if (known.some((id) => !publications.some((p) => p.message.id === id))) throw protocolError("WORK_MESSAGE_MISSING", "Published workspace message is missing", "ECONFLICT");
|
|
287
|
+
unresolvedWorkIssues(publications.map((p) => p.message));
|
|
288
|
+
publications.sort((a, b) => Number(a.message.type === "resolved") - Number(b.message.type === "resolved"));
|
|
289
|
+
for (const { message, bytes } of publications) {
|
|
290
|
+
const digest = _chunkRSH6QSECcjs.sha256HexSync.call(void 0, bytes);
|
|
291
|
+
const delivered = await deliverPublication(journal, indexKey, message, digest, deliver);
|
|
292
|
+
if (delivered) await publishReply(directory + "/outbox", "ack-" + message.id + ".yaml", JSON.stringify({ schema: "work-reply/1", ...scope, id: "ack-" + message.id, type: "accepted", replyTo: message.id, content: "Accepted", digest }));
|
|
293
|
+
}
|
|
294
|
+
return publications.map((p) => p.message);
|
|
295
|
+
}
|
|
296
|
+
async function readPublications(fs, directory, scope, decode) {
|
|
297
|
+
const files = await fs.driver.getChildren(directory + "/inbox", { fields: "entry" }), publications = [];
|
|
298
|
+
if (files.length > 200) throw protocolError("WORK_MESSAGE_COUNT_LIMIT", "Workspace inbox exceeds 200 entries");
|
|
299
|
+
for (const file of files.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
300
|
+
if (file.type !== "file" || !/^[a-zA-Z0-9_-]{1,128}\.(yaml|yml|json)$/.test(file.name)) continue;
|
|
301
|
+
const bytes = await readMessageBytes(fs, directory + "/inbox/" + file.name);
|
|
302
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes), envelope = decode(text);
|
|
303
|
+
if (_optionalChain([envelope, 'optionalAccess', _42 => _42.schema]) === "work-message/2" && Object.hasOwn(envelope, "scopeId")) continue;
|
|
304
|
+
const message = parseWorkMessage(text, scope, decode);
|
|
305
|
+
if (file.name.slice(0, file.name.lastIndexOf(".")) !== message.id) throw protocolError("WORK_MESSAGE_FILENAME_MISMATCH", "Workspace message filename does not match its identity");
|
|
306
|
+
publications.push({ message, bytes });
|
|
307
|
+
}
|
|
308
|
+
return publications;
|
|
309
|
+
}
|
|
310
|
+
async function deliverPublication(journal, indexKey, message, digest, deliver) {
|
|
311
|
+
const key = "workspace-message:" + message.attemptId + ":" + message.id;
|
|
312
|
+
const receipt = await journal.update(key, (old) => {
|
|
313
|
+
if (old && old.digest !== digest) throw protocolError("WORK_MESSAGE_CHANGED", "Published workspace message changed", "ECONFLICT");
|
|
314
|
+
return _nullishCoalesce(old, () => ( { digest, message, delivered: false }));
|
|
315
|
+
});
|
|
316
|
+
await journal.update(indexKey, (old) => _optionalChain([old, 'optionalAccess', _43 => _43.includes, 'call', _44 => _44(message.id)]) ? old : [..._nullishCoalesce(old, () => ( [])), message.id]);
|
|
317
|
+
if (receipt.delivered) return true;
|
|
318
|
+
const outcome = await deliver(receipt.message);
|
|
319
|
+
if (_optionalChain([outcome, 'optionalAccess', _45 => _45.pending])) return false;
|
|
320
|
+
await journal.update(key, (old) => ({ ...old, delivered: true }));
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
function parseWorkMessage(text, scope, decode) {
|
|
324
|
+
let value;
|
|
325
|
+
try {
|
|
326
|
+
value = decode(text);
|
|
327
|
+
} catch (e4) {
|
|
328
|
+
throw protocolError("WORK_MESSAGE_YAML_INVALID", "Workspace message is not valid bounded YAML");
|
|
329
|
+
}
|
|
330
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw protocolError("WORK_MESSAGE_SCHEMA_INVALID", "Workspace message schema is invalid");
|
|
331
|
+
if ("body" in value) {
|
|
332
|
+
if (Object.hasOwn(value, "content")) throw protocolError("WORK_MESSAGE_FIELDS_INVALID", "Workspace message must not contain both body and content");
|
|
333
|
+
value = { ...value, content: value.body };
|
|
334
|
+
}
|
|
335
|
+
if (Object.keys(value).some((key) => !["schema", "runId", "itemId", "attemptId", "id", "type", "content", "body", "replyTo"].includes(key)))
|
|
336
|
+
throw protocolError("WORK_MESSAGE_FIELDS_INVALID", "Workspace message contains undeclared fields");
|
|
337
|
+
validateEnvelope(value, scope);
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
function validateEnvelope(value, scope) {
|
|
341
|
+
if (!["work-message/1", "work-message/2"].includes(value.schema)) throw protocolError("WORK_MESSAGE_SCHEMA_INVALID", "Workspace message schema is unsupported");
|
|
342
|
+
if (!["progress", "clarification", "error", "result", ...value.schema === "work-message/2" ? ["resolved"] : []].includes(value.type))
|
|
343
|
+
throw protocolError("WORK_MESSAGE_TYPE_INVALID", "Workspace message schema does not permit this type");
|
|
344
|
+
if (!validId(value.id)) throw protocolError("WORK_MESSAGE_ID_INVALID", "Workspace message identity is invalid");
|
|
345
|
+
if (["runId", "itemId", "attemptId"].some((key) => value[key] !== scope[key]))
|
|
346
|
+
throw protocolError("WORK_MESSAGE_SCOPE_MISMATCH", "Workspace message execution scope is invalid");
|
|
347
|
+
if (typeof value.content !== "string" || !value.content.trim() || value.content.length > 8e3) throw protocolError("WORK_MESSAGE_CONTENT_INVALID", "Workspace message content is invalid");
|
|
348
|
+
if (value.replyTo !== void 0 && !validId(value.replyTo) || value.type === "resolved" && !value.replyTo)
|
|
349
|
+
throw protocolError("WORK_MESSAGE_REPLY_INVALID", "Workspace message reply target is invalid");
|
|
350
|
+
}
|
|
351
|
+
function validId(value) {
|
|
352
|
+
return typeof value === "string" && /^[a-zA-Z0-9_-]{1,128}$/.test(value);
|
|
353
|
+
}
|
|
354
|
+
function protocolError(reasonCode, message, code = "EINVAL") {
|
|
355
|
+
return Object.assign(new (0, _vfscore.FSError)(code, message), { reasonCode });
|
|
356
|
+
}
|
|
357
|
+
async function readMessageBytes(fs, path) {
|
|
358
|
+
const before = await fs.driver.getNode(path);
|
|
359
|
+
if (_optionalChain([before, 'optionalAccess', _46 => _46.type]) !== "file" || !Number.isSafeInteger(before.size) || before.size < 1) throw protocolError("WORK_MESSAGE_FILE_INVALID", "Workspace message is not a nonempty regular file");
|
|
360
|
+
if (before.size > 65536) throw protocolError("WORK_MESSAGE_SIZE_LIMIT", "Workspace message exceeds 64 KiB");
|
|
361
|
+
const bytes = await fs.driver.readContent(path, { encoding: "binary", length: before.size });
|
|
362
|
+
const after = await fs.driver.getNode(path);
|
|
363
|
+
if (bytes.byteLength !== before.size || _optionalChain([after, 'optionalAccess', _47 => _47.type]) !== "file" || after.size !== before.size || after.revision !== before.revision || after.modifiedAt !== before.modifiedAt)
|
|
364
|
+
throw protocolError("WORK_MESSAGE_READ_CONFLICT", "Workspace message changed while reading", "ECONFLICT");
|
|
365
|
+
return bytes;
|
|
366
|
+
}
|
|
367
|
+
function unresolvedWorkIssues(messages) {
|
|
368
|
+
const issues = messages.filter((m) => m.type === "clarification" || m.type === "error");
|
|
369
|
+
for (const resolution of messages.filter((m) => m.type === "resolved")) {
|
|
370
|
+
if (!issues.some((issue) => issue.id === resolution.replyTo)) throw Object.assign(new Error("Resolution target is not an issue in this attempt"), { code: "WORKSPACE_RESOLUTION_INVALID" });
|
|
371
|
+
}
|
|
372
|
+
return issues.filter((issue) => !messages.some((m) => m.type === "resolved" && m.replyTo === issue.id));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
exports.createKanbanExecutionBridge = createKanbanExecutionBridge; exports.startKanbanMonitor = startKanbanMonitor; exports.consumeWorkMessages = consumeWorkMessages; exports.parseWorkMessage = parseWorkMessage; exports.unresolvedWorkIssues = unresolvedWorkIssues;
|
|
382
|
+
//# sourceMappingURL=chunk-XBSGUVCY.cjs.map
|