@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.
Files changed (124) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +165 -2
  3. package/dist/activity-BbC4sgKL.d.cts +258 -0
  4. package/dist/activity-Czlan5xW.d.ts +258 -0
  5. package/dist/activity-module-DaO1Rry0.d.cts +22 -0
  6. package/dist/activity-module-T22wliZu.d.ts +22 -0
  7. package/dist/activity-ports-Dqxa86zT.d.cts +12 -0
  8. package/dist/activity-ports-cxfrelaL.d.ts +12 -0
  9. package/dist/activity.cjs +16 -0
  10. package/dist/activity.cjs.map +1 -0
  11. package/dist/activity.d.cts +12 -0
  12. package/dist/activity.d.ts +12 -0
  13. package/dist/activity.js +16 -0
  14. package/dist/activity.js.map +1 -0
  15. package/dist/chunk-3JYLJPE5.js +95 -0
  16. package/dist/chunk-3JYLJPE5.js.map +1 -0
  17. package/dist/chunk-3LEIHB7Y.js +221 -0
  18. package/dist/chunk-3LEIHB7Y.js.map +1 -0
  19. package/dist/chunk-4XF6LQSU.cjs +9 -0
  20. package/dist/chunk-4XF6LQSU.cjs.map +1 -0
  21. package/dist/chunk-5Q7WB63A.cjs +282 -0
  22. package/dist/chunk-5Q7WB63A.cjs.map +1 -0
  23. package/dist/chunk-7KDOTBRX.cjs +58 -0
  24. package/dist/chunk-7KDOTBRX.cjs.map +1 -0
  25. package/dist/chunk-AHHEGTUA.js +148 -0
  26. package/dist/chunk-AHHEGTUA.js.map +1 -0
  27. package/dist/chunk-AOHQCX7K.cjs +95 -0
  28. package/dist/chunk-AOHQCX7K.cjs.map +1 -0
  29. package/dist/chunk-DINLR6E6.cjs +443 -0
  30. package/dist/chunk-DINLR6E6.cjs.map +1 -0
  31. package/dist/chunk-EYTVC2H5.cjs +1612 -0
  32. package/dist/chunk-EYTVC2H5.cjs.map +1 -0
  33. package/dist/chunk-FEE2YBZ7.js +1972 -0
  34. package/dist/chunk-FEE2YBZ7.js.map +1 -0
  35. package/dist/chunk-FNO5K3A7.js +282 -0
  36. package/dist/chunk-FNO5K3A7.js.map +1 -0
  37. package/dist/chunk-IHHDXZPZ.js +382 -0
  38. package/dist/chunk-IHHDXZPZ.js.map +1 -0
  39. package/dist/chunk-J3EAIVA3.js +280 -0
  40. package/dist/chunk-J3EAIVA3.js.map +1 -0
  41. package/dist/chunk-KH3WF3K4.js +443 -0
  42. package/dist/chunk-KH3WF3K4.js.map +1 -0
  43. package/dist/chunk-M35X5PHO.js +196 -0
  44. package/dist/chunk-M35X5PHO.js.map +1 -0
  45. package/dist/chunk-QRGZD2JU.cjs +1972 -0
  46. package/dist/chunk-QRGZD2JU.cjs.map +1 -0
  47. package/dist/chunk-RBOER6EW.cjs +221 -0
  48. package/dist/chunk-RBOER6EW.cjs.map +1 -0
  49. package/dist/chunk-RQP77ZTY.js +9 -0
  50. package/dist/chunk-RQP77ZTY.js.map +1 -0
  51. package/dist/chunk-RSH6QSEC.cjs +196 -0
  52. package/dist/chunk-RSH6QSEC.cjs.map +1 -0
  53. package/dist/chunk-VMRDXOLX.cjs +148 -0
  54. package/dist/chunk-VMRDXOLX.cjs.map +1 -0
  55. package/dist/chunk-X4UHZX6F.js +58 -0
  56. package/dist/chunk-X4UHZX6F.js.map +1 -0
  57. package/dist/chunk-XBSGUVCY.cjs +382 -0
  58. package/dist/chunk-XBSGUVCY.cjs.map +1 -0
  59. package/dist/chunk-XKDZHAUE.js +1612 -0
  60. package/dist/chunk-XKDZHAUE.js.map +1 -0
  61. package/dist/chunk-ZUZGAO6G.cjs +280 -0
  62. package/dist/chunk-ZUZGAO6G.cjs.map +1 -0
  63. package/dist/content-C5aJZFlw.d.cts +35 -0
  64. package/dist/content-hLH5BNud.d.ts +35 -0
  65. package/dist/contracts/index.cjs +31 -0
  66. package/dist/contracts/index.cjs.map +1 -0
  67. package/dist/contracts/index.d.cts +5 -0
  68. package/dist/contracts/index.d.ts +5 -0
  69. package/dist/contracts/index.js +31 -0
  70. package/dist/contracts/index.js.map +1 -0
  71. package/dist/discovery-9E-LfwIw.d.cts +61 -0
  72. package/dist/discovery-D60yrjej.d.ts +61 -0
  73. package/dist/discovery.cjs +377 -0
  74. package/dist/discovery.cjs.map +1 -0
  75. package/dist/discovery.d.cts +22 -0
  76. package/dist/discovery.d.ts +22 -0
  77. package/dist/discovery.js +377 -0
  78. package/dist/discovery.js.map +1 -0
  79. package/dist/execution-bridge-fpCnH6z5.d.cts +52 -0
  80. package/dist/execution-bridge-rk0uVy_n.d.ts +52 -0
  81. package/dist/execution.cjs +18 -0
  82. package/dist/execution.cjs.map +1 -0
  83. package/dist/execution.d.cts +42 -0
  84. package/dist/execution.d.ts +42 -0
  85. package/dist/execution.js +18 -0
  86. package/dist/execution.js.map +1 -0
  87. package/dist/index.cjs +74 -0
  88. package/dist/index.cjs.map +1 -0
  89. package/dist/index.d.cts +54 -0
  90. package/dist/index.d.ts +54 -0
  91. package/dist/index.js +74 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/module-CYfE7YhM.d.ts +11 -0
  94. package/dist/module-Ca9L3qCn.d.cts +11 -0
  95. package/dist/planning-CHrOqQ6b.d.cts +879 -0
  96. package/dist/planning-CHrOqQ6b.d.ts +879 -0
  97. package/dist/ports-B0McVg7B.d.ts +143 -0
  98. package/dist/ports-BeGO1VRI.d.cts +49 -0
  99. package/dist/ports-GQy0x-RI.d.cts +143 -0
  100. package/dist/ports-ykGiNUSx.d.ts +49 -0
  101. package/dist/runtime/index.cjs +205 -0
  102. package/dist/runtime/index.cjs.map +1 -0
  103. package/dist/runtime/index.d.cts +75 -0
  104. package/dist/runtime/index.d.ts +75 -0
  105. package/dist/runtime/index.js +205 -0
  106. package/dist/runtime/index.js.map +1 -0
  107. package/dist/runtime/integration.cjs +423 -0
  108. package/dist/runtime/integration.cjs.map +1 -0
  109. package/dist/runtime/integration.d.cts +658 -0
  110. package/dist/runtime/integration.d.ts +658 -0
  111. package/dist/runtime/integration.js +423 -0
  112. package/dist/runtime/integration.js.map +1 -0
  113. package/dist/storage.cjs +21 -0
  114. package/dist/storage.cjs.map +1 -0
  115. package/dist/storage.d.cts +71 -0
  116. package/dist/storage.d.ts +71 -0
  117. package/dist/storage.js +21 -0
  118. package/dist/storage.js.map +1 -0
  119. package/dist/store-DPwHzGiz.d.cts +28 -0
  120. package/dist/store-DgdrPCV9.d.ts +28 -0
  121. package/dist/task-projection-CSorlMws.d.ts +11 -0
  122. package/dist/task-projection-DKKB_ueb.d.cts +11 -0
  123. package/examples/standalone.ts +22 -0
  124. package/package.json +143 -4
@@ -0,0 +1,377 @@
1
+ import {
2
+ discoveryState,
3
+ identity,
4
+ positive,
5
+ requireValue,
6
+ text
7
+ } from "./chunk-3LEIHB7Y.js";
8
+ import "./chunk-X4UHZX6F.js";
9
+ import {
10
+ KanbanError,
11
+ KanbanScanNotStartedError,
12
+ randomUUID
13
+ } from "./chunk-M35X5PHO.js";
14
+ import "./chunk-3JYLJPE5.js";
15
+
16
+ // src/scan-context.ts
17
+ function scanContext(board, state, sessions, options, runId) {
18
+ requireValue(state.board.revision === board.revision, "SCAN_BOARD_CHANGED");
19
+ const archived = state.archiveIndex ?? [];
20
+ const input = {
21
+ runId,
22
+ executorId: options.executor.identity,
23
+ board: { ...board, items: [] },
24
+ sessions,
25
+ connection: structuredClone(options.policy.connection),
26
+ projectContext: { totalRecords: state.records?.filter((record) => record.status === "confirmed").length ?? 0, omittedRecords: state.records?.filter((record) => record.status === "confirmed").length ?? 0, excludedRecords: state.records?.filter((record) => record.status !== "confirmed").length ?? 0, records: [] },
27
+ context: {
28
+ totalItems: board.items.length,
29
+ omittedItems: board.items.length,
30
+ archivedTotal: archived.length,
31
+ archivedItems: []
32
+ }
33
+ };
34
+ const bytes = () => new TextEncoder().encode(JSON.stringify(input)).length;
35
+ requireValue(bytes() <= options.policy.maxInputBytes, "SCAN_INPUT_LIMIT");
36
+ const sessionIds = new Set(sessions.map((s) => s.session.id));
37
+ addRecords(input, state, sessionIds, bytes, options.policy.maxInputBytes);
38
+ const content = sessions.flatMap((s) => s.messages.map((m) => m.text.toLowerCase())).join("\n");
39
+ const ordered = [...board.items].sort((a, b) => score(a, sessionIds, content) - score(b, sessionIds, content) || (a.planning?.priority ?? 2) - (b.planning?.priority ?? 2) || a.id.localeCompare(b.id));
40
+ for (const item of ordered) {
41
+ if (input.board.items.length >= 50) break;
42
+ input.board.items.push(item);
43
+ input.context.omittedItems--;
44
+ if (bytes() > options.policy.maxInputBytes) {
45
+ input.board.items.pop();
46
+ input.context.omittedItems++;
47
+ }
48
+ }
49
+ const history = [...archived].sort((a, b) => Number(b.sessionIds.some((id) => sessionIds.has(id))) - Number(a.sessionIds.some((id) => sessionIds.has(id))) || (b.closedAt ?? b.updatedAt) - (a.closedAt ?? a.updatedAt));
50
+ for (const item of history.slice(0, 20)) {
51
+ input.context.archivedItems.push(item);
52
+ if (bytes() > options.policy.maxInputBytes) input.context.archivedItems.pop();
53
+ }
54
+ return structuredClone(input);
55
+ }
56
+ function addRecords(input, state, sessionIds, bytes, maxBytes) {
57
+ const records = (state.records ?? []).filter((record) => record.status === "confirmed").sort((a, b) => Number(b.kind === "decision") - Number(a.kind === "decision") || Number(b.sources.some((source) => sessionIds.has(source.sessionId))) - Number(a.sources.some((source) => sessionIds.has(source.sessionId))) || b.createdAt - a.createdAt);
58
+ for (const record of records.slice(0, 10)) {
59
+ input.projectContext.records.push(record);
60
+ input.projectContext.omittedRecords--;
61
+ if (bytes() > maxBytes) {
62
+ input.projectContext.records.pop();
63
+ input.projectContext.omittedRecords++;
64
+ }
65
+ }
66
+ }
67
+ function score(item, sessions, content) {
68
+ if (item.sources?.some((source) => sessions.has(source.sessionId)) || content.includes(item.title.toLowerCase())) return 0;
69
+ if (item.status === "claimed" || item.status === "review") return 1;
70
+ if (item.status === "open") return 2;
71
+ return item.status === "completed" || item.status === "cancelled" ? 4 : 3;
72
+ }
73
+
74
+ // src/discovery.ts
75
+ function createKanbanDiscovery(options) {
76
+ return new Discovery(options);
77
+ }
78
+ var Discovery = class {
79
+ constructor(options) {
80
+ this.options = options;
81
+ this.now = options.now ?? Date.now;
82
+ this.id = options.id ?? randomUUID;
83
+ this.token = this.id();
84
+ for (const value of [options.policy.idleMs, options.policy.maxSessions, options.policy.maxInputBytes, options.policy.leaseMs]) positive(value);
85
+ identity(options.policy.connection.connectionId);
86
+ }
87
+ now;
88
+ id;
89
+ token;
90
+ active;
91
+ stopping = false;
92
+ resets = /* @__PURE__ */ new Set();
93
+ stage = "prepare";
94
+ scan = () => {
95
+ if (this.stopping) return Promise.reject(new KanbanError("SERVICE_STOPPING"));
96
+ if (this.active) return this.active;
97
+ const operationId = this.id(), started = this.now();
98
+ this.active = this.run().catch(async (error) => {
99
+ if (error instanceof KanbanError && ["SOURCE_CHANGED", "SCAN_BOARD_CHANGED", "REVISION_CONFLICT"].includes(error.code)) {
100
+ await this.settleInvalidatedRun();
101
+ await this.update("invalidate", (state) => {
102
+ if (state.pending?.token === this.token) {
103
+ state.lastRun = { runId: state.pending.runId, outcome: "invalidated", at: this.now(), code: error.code };
104
+ delete state.pending;
105
+ }
106
+ });
107
+ }
108
+ throw error;
109
+ }).then(() => this.log(operationId, started, "success"), (error) => {
110
+ this.log(operationId, started, "failure", errorCode(error));
111
+ throw error;
112
+ }).finally(() => {
113
+ this.active = void 0;
114
+ });
115
+ return this.active;
116
+ };
117
+ tick = () => this.scan();
118
+ async quiesce() {
119
+ this.stopping = true;
120
+ await Promise.allSettled([this.active, ...this.resets]);
121
+ }
122
+ dispose = () => this.quiesce();
123
+ reset = (runId) => {
124
+ if (this.stopping) return Promise.reject(new KanbanError("SERVICE_STOPPING"));
125
+ const work = this.resetRun(runId).finally(() => {
126
+ this.resets.delete(work);
127
+ });
128
+ this.resets.add(work);
129
+ return work;
130
+ };
131
+ async resetRun(runId) {
132
+ const pending = discoveryState(await this.options.store.read(this.options.boardId))?.pending;
133
+ requireValue(pending?.runId === runId, "SCAN_CHANGED");
134
+ await this.options.authorize("commit", pending.input);
135
+ requireValue(pending.token === this.token || pending.leaseUntil <= this.now(), "SCAN_BUSY");
136
+ if (pending.executionStarted && !pending.executionSettled) requireValue(await this.options.executor.settled?.(pending.input), "SCAN_EXECUTION_UNSETTLED");
137
+ await this.update("reset", (state) => {
138
+ requireValue(state.pending?.runId === runId && state.pending.token === pending.token, "SCAN_CHANGED");
139
+ state.lastRun = { runId, outcome: "invalidated", at: this.now(), code: "SCAN_MANUAL_RESET" };
140
+ delete state.pending;
141
+ });
142
+ }
143
+ async run() {
144
+ await this.options.authorize("read");
145
+ const board = await this.options.service.read(this.options.boardId);
146
+ requireValue(matchesScope(board.scope, this.options.source), "SOURCE_PROJECT_MISMATCH");
147
+ requireValue(this.options.source.kind === this.options.policy.connection.kind || this.options.policy.connection.allowContentTransfer, "CONTENT_TRANSFER_NOT_ALLOWED");
148
+ const stored = await this.options.store.read(board.id);
149
+ let pending = discoveryState(stored)?.pending;
150
+ if (pending && pending.token !== this.token && pending.leaseUntil > this.now()) return;
151
+ if (!pending) {
152
+ const sessions = await this.collect();
153
+ if (!sessions.length) return;
154
+ const current = await this.options.store.read(board.id);
155
+ requireValue(current, "BOARD_NOT_FOUND");
156
+ const input = scanContext(board, current, sessions, this.options, this.id());
157
+ requireValue(new TextEncoder().encode(JSON.stringify(input)).length <= this.options.policy.maxInputBytes, "SCAN_INPUT_LIMIT");
158
+ pending = { runId: input.runId, autoCreate: this.options.policy.autoCreate && (input.projectContext?.omittedRecords ?? 0) === 0 && input.context.omittedItems === 0 && input.context.archivedItems.length === input.context.archivedTotal, token: this.token, leaseUntil: this.now() + this.options.policy.leaseMs, input };
159
+ }
160
+ const selected = pending;
161
+ await this.update("claim", (state) => {
162
+ requireValue(!state.pending || state.pending.token === this.token || state.pending.leaseUntil <= this.now(), "SCAN_BUSY");
163
+ if (state.pending) requireValue(state.pending.runId === selected.runId, "SCAN_CHANGED");
164
+ const restoring = selected.token !== this.token;
165
+ state.pending = { ...selected, ...restoring ? { request: void 0 } : {}, token: this.token, leaseUntil: this.now() + this.options.policy.leaseMs };
166
+ });
167
+ await this.process();
168
+ }
169
+ async collect() {
170
+ this.stage = "source.list";
171
+ const sessions = await this.options.source.list(this.options.policy.includeArchived);
172
+ for (const session of sessions) {
173
+ identity(session.id);
174
+ requireValue(typeof session.revision === "string" && session.revision.length > 0 && session.revision.length <= 16e3, "INVALID_SOURCE_REVISION");
175
+ }
176
+ requireValue(new Set(sessions.map((s) => s.id)).size === sessions.length, "DUPLICATE_SOURCE_SESSION");
177
+ await this.observe(sessions);
178
+ const state = discoveryState(await this.options.store.read(this.options.boardId));
179
+ const result = [];
180
+ let bytes = 0;
181
+ for (const session of sessions.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0) || a.id.localeCompare(b.id))) {
182
+ if (!this.eligible(session, state)) continue;
183
+ const current = await this.options.source.inspect(session.id);
184
+ if (!this.eligible(current, state) || current.revision !== session.revision) continue;
185
+ this.stage = "source.read";
186
+ const text2 = await this.readCandidate(current, Math.floor(this.options.policy.maxInputBytes * 0.7) - bytes);
187
+ if (!text2) continue;
188
+ requireValue(text2.session.id === current.id && text2.session.revision === current.revision, "SOURCE_CHANGED");
189
+ if (!text2.complete) continue;
190
+ const size = new TextEncoder().encode(JSON.stringify(text2)).length;
191
+ if (bytes + size > this.options.policy.maxInputBytes * 0.7) break;
192
+ bytes += size;
193
+ result.push(text2);
194
+ if (result.length >= this.options.policy.maxSessions) break;
195
+ }
196
+ return result;
197
+ }
198
+ async readCandidate(session, maxBytes) {
199
+ const started = this.now();
200
+ try {
201
+ const result = await this.options.source.read(session, maxBytes);
202
+ if (!result.complete) this.log(this.id(), started, "failure", "SOURCE_PROVENANCE_UNKNOWN", session.id);
203
+ return result;
204
+ } catch (error) {
205
+ const code = errorCode(error);
206
+ if (!["SCAN_INPUT_LIMIT", "SOURCE_CHANGED", "HARNESS_DIGEST_CHANGED"].includes(code)) throw error;
207
+ this.log(this.id(), started, "failure", code, session.id);
208
+ return null;
209
+ }
210
+ }
211
+ eligible(session, state) {
212
+ const quiet = state.observations?.[session.id];
213
+ return !session.scannerOwned && (this.options.policy.includeArchived || !session.archived) && session.activity === "idle" && session.updatedAt !== null && session.updatedAt <= this.now() && quiet?.revision === session.revision && this.now() - quiet.idleSince >= this.options.policy.idleMs && state.checkpoints[session.id] !== session.revision;
214
+ }
215
+ async observe(sessions) {
216
+ await this.update("observe", (state) => {
217
+ state.observations ??= {};
218
+ for (const session of sessions) {
219
+ if (session.activity !== "idle" || session.scannerOwned) {
220
+ delete state.observations[session.id];
221
+ continue;
222
+ }
223
+ const old = state.observations[session.id];
224
+ if (old?.revision !== session.revision) state.observations[session.id] = { revision: session.revision, idleSince: this.now() };
225
+ }
226
+ });
227
+ }
228
+ async process() {
229
+ let pending = discoveryState(await this.options.store.read(this.options.boardId)).pending;
230
+ requireValue(pending.input.executorId === this.options.executor.identity, "SCANNER_EXECUTOR_CHANGED");
231
+ await this.checkSources(pending.input);
232
+ if (!pending.changes) {
233
+ await this.options.authorize("execute", pending.input);
234
+ this.stage = "analyzer.execute";
235
+ await this.updateOwned("execute", (state) => {
236
+ state.pending.executionStarted = true;
237
+ });
238
+ const output = await this.execute(pending.input), result = Array.isArray(output) ? { changes: output } : output;
239
+ await this.updateOwned("settled", (state) => {
240
+ state.pending.executionSettled = true;
241
+ });
242
+ validateChanges(result.changes, pending.input);
243
+ validateRecords(result.records ?? [], pending.input, result.changes);
244
+ await this.updateOwned("result", (state) => {
245
+ state.pending.changes = result.changes;
246
+ state.pending.records = result.records;
247
+ });
248
+ }
249
+ pending = discoveryState(await this.options.store.read(this.options.boardId)).pending;
250
+ await this.options.authorize("commit", pending.input);
251
+ await this.checkSources(pending.input);
252
+ if (!pending.request) {
253
+ const board = await this.options.service.read(this.options.boardId);
254
+ requireValue(board.revision === pending.input.board.revision, "SCAN_BOARD_CHANGED");
255
+ const request = {
256
+ operationId: `discovery:${pending.runId}`,
257
+ expectedRevision: board.revision,
258
+ command: { type: "apply-discovery", batch: {
259
+ runId: pending.runId,
260
+ leaseToken: this.token,
261
+ changes: pending.changes,
262
+ ...pending.records ? { records: pending.records } : {},
263
+ autoCreate: pending.autoCreate,
264
+ checkpoints: pending.input.sessions.map((s) => ({ sessionId: s.session.id, revision: s.session.revision }))
265
+ } }
266
+ };
267
+ await this.updateOwned("prepare", (state) => {
268
+ state.pending.request = request;
269
+ });
270
+ pending.request = request;
271
+ }
272
+ this.stage = "board.commit";
273
+ await this.options.service.execute(this.options.boardId, pending.request);
274
+ }
275
+ async execute(input) {
276
+ try {
277
+ return await this.options.executor.run(structuredClone(input));
278
+ } catch (error) {
279
+ if (error instanceof KanbanScanNotStartedError) await this.updateOwned("not-started", (state) => {
280
+ state.pending.executionSettled = true;
281
+ });
282
+ throw error;
283
+ }
284
+ }
285
+ async settleInvalidatedRun() {
286
+ const pending = discoveryState(await this.options.store.read(this.options.boardId))?.pending;
287
+ if (!pending || pending.token !== this.token || !pending.executionStarted || pending.executionSettled) return;
288
+ requireValue(await this.options.executor.settled?.(pending.input), "SCAN_EXECUTION_UNSETTLED");
289
+ }
290
+ async checkSources(input) {
291
+ for (const source of input.sessions) {
292
+ const current = await this.options.source.inspect(source.session.id);
293
+ requireValue(current.revision === source.session.revision && current.activity === "idle" && !current.scannerOwned, "SOURCE_CHANGED");
294
+ }
295
+ }
296
+ async updateOwned(stage, mutate) {
297
+ await this.update(stage, (state) => {
298
+ requireValue(state.pending?.token === this.token && state.pending.leaseUntil > this.now(), "SCAN_LEASE_EXPIRED");
299
+ mutate(state);
300
+ });
301
+ }
302
+ async update(stage, mutate) {
303
+ const operation = this.id();
304
+ await this.options.store.update(this.options.boardId, operation, `discovery:${stage}:${operation}`, (state) => {
305
+ requireValue(state, "BOARD_NOT_FOUND");
306
+ state.discovery ??= { checkpoints: {} };
307
+ mutate(state.discovery);
308
+ return state;
309
+ });
310
+ }
311
+ log(operationId, started, result, code, sessionId) {
312
+ try {
313
+ this.options.log({ operationId, boardId: this.options.boardId, sessionId, stage: `discovery.${this.stage}`, result, code, durationMs: this.now() - started });
314
+ } catch {
315
+ }
316
+ }
317
+ };
318
+ function validateChanges(changes, input) {
319
+ requireValue(Array.isArray(changes) && changes.length <= 50, "SCAN_CHANGE_LIMIT");
320
+ const ids = /* @__PURE__ */ new Set();
321
+ for (const change of changes) {
322
+ requireValue(change && ["create", "update", "link"].includes(change.action) && !ids.has(change.itemId), "INVALID_CHANGE");
323
+ ids.add(change.itemId);
324
+ identity(change.itemId);
325
+ if (change.action !== "create") requireValue(input.board.items.some((i) => i.id === change.itemId), "UNKNOWN_CONTEXT_ITEM");
326
+ text(change.title);
327
+ text(change.responsibility);
328
+ text(change.acceptance);
329
+ requireValue(change.eligibleAgentIds === void 0 || Array.isArray(change.eligibleAgentIds), "INVALID_ELIGIBILITY");
330
+ change.eligibleAgentIds?.forEach(identity);
331
+ requireValue(change.eligiblePrincipals === void 0 || Array.isArray(change.eligiblePrincipals), "INVALID_ELIGIBILITY");
332
+ requireValue(Array.isArray(change.sources) && change.sources.length > 0 && change.sources.length <= 100 && change.sources.some((s) => s.role === "user"), "USER_EVIDENCE_REQUIRED");
333
+ for (const ref of change.sources) {
334
+ const source = input.sessions.find((s) => s.session.id === ref.sessionId && s.session.revision === ref.revision);
335
+ requireValue(source?.messages.some((m) => m.id === ref.messageId && m.role === ref.role), "INVALID_SOURCE_REFERENCE");
336
+ }
337
+ }
338
+ }
339
+ function validateRecords(records, input, changes) {
340
+ requireValue(Array.isArray(records) && records.length <= 20, "SCAN_RECORD_LIMIT");
341
+ const ids = /* @__PURE__ */ new Set();
342
+ for (const record of records) {
343
+ identity(record.id);
344
+ requireValue(!ids.has(record.id), "INVALID_RECORD_ID");
345
+ ids.add(record.id);
346
+ text(record.title);
347
+ text(record.content);
348
+ requireValue(["log", "decision"].includes(record.kind), "INVALID_RECORD_KIND");
349
+ if (record.kind === "decision") text(record.rationale ?? "");
350
+ requireValue(Array.isArray(record.itemIds) && record.itemIds.every((id) => input.board.items.some((item) => item.id === id) || changes.some((change) => change.action === "create" && change.itemId === id)), "UNKNOWN_CONTEXT_ITEM");
351
+ if (record.supersedes) requireValue(input.projectContext?.records.some((old) => old.id === record.supersedes && old.kind === "decision"), "UNKNOWN_CONTEXT_RECORD");
352
+ requireValue(Array.isArray(record.sources) && record.sources.length > 0 && record.sources.length <= 100 && record.sources.some((source) => source.role === "user"), "USER_EVIDENCE_REQUIRED");
353
+ for (const ref of record.sources) {
354
+ const source = input.sessions.find((s) => s.session.id === ref.sessionId && s.session.revision === ref.revision);
355
+ requireValue(source?.messages.some((message) => message.id === ref.messageId && message.role === ref.role), "INVALID_SOURCE_REFERENCE");
356
+ }
357
+ }
358
+ }
359
+ function errorCode(error) {
360
+ const value = error;
361
+ const code = value?.remoteCode ?? value?.code;
362
+ return typeof code === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? code : "SCAN_PORT_FAILURE";
363
+ }
364
+ function matchesScope(scope, source) {
365
+ const expected = source.scope ?? (source.projectId ? { kind: "project", projectId: source.projectId } : void 0);
366
+ if (!expected || scope.kind !== expected.kind) return false;
367
+ if (scope.kind === "system") return true;
368
+ if (scope.kind === "project" && expected.kind === "project") return scope.projectId === expected.projectId;
369
+ return scope.kind === "collection" && expected.kind === "collection" && scope.namespace === expected.namespace && scope.id === expected.id;
370
+ }
371
+ export {
372
+ KanbanScanNotStartedError,
373
+ scanContext as buildDiscoveryContext,
374
+ createKanbanDiscovery,
375
+ discoveryState
376
+ };
377
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scan-context.ts","../src/discovery.ts"],"sourcesContent":["import type {KanbanBoard, KanbanItem, KanbanScanInput, KanbanScanText} from './contracts';\nimport type {KanbanDiscoveryOptions} from './scan-ports';\nimport type {KanbanState} from './ports';\nimport {requireValue} from './rules';\n\n/** A bounded working set, with explicit omitted counts instead of pretending full coverage. */\nexport function scanContext(board: KanbanBoard, state: KanbanState, sessions: KanbanScanText[], options: KanbanDiscoveryOptions, runId: string): KanbanScanInput {\n requireValue(state.board.revision === board.revision, 'SCAN_BOARD_CHANGED');\n const archived = state.archiveIndex ?? [];\n const input: KanbanScanInput = {runId, executorId: options.executor.identity, board: {...board, items: []}, sessions,\n connection: structuredClone(options.policy.connection), projectContext: {totalRecords: state.records?.filter(record => record.status === 'confirmed').length ?? 0, omittedRecords: state.records?.filter(record => record.status === 'confirmed').length ?? 0, excludedRecords: state.records?.filter(record => record.status !== 'confirmed').length ?? 0, records: []}, context: {totalItems: board.items.length, omittedItems: board.items.length,\n archivedTotal: archived.length, archivedItems: []}};\n const bytes = () => new TextEncoder().encode(JSON.stringify(input)).length;\n requireValue(bytes() <= options.policy.maxInputBytes, 'SCAN_INPUT_LIMIT');\n const sessionIds = new Set(sessions.map(s => s.session.id));\n addRecords(input, state, sessionIds, bytes, options.policy.maxInputBytes);\n const content = sessions.flatMap(s => s.messages.map(m => m.text.toLowerCase())).join('\\n');\n const ordered = [...board.items].sort((a, b) => score(a, sessionIds, content) - score(b, sessionIds, content)\n || (a.planning?.priority ?? 2) - (b.planning?.priority ?? 2) || a.id.localeCompare(b.id));\n for (const item of ordered) {\n if (input.board.items.length >= 50) break;\n input.board.items.push(item); input.context!.omittedItems--;\n if (bytes() > options.policy.maxInputBytes) {input.board.items.pop(); input.context!.omittedItems++;}\n }\n const history = [...archived].sort((a, b) => Number(b.sessionIds.some(id => sessionIds.has(id))) - Number(a.sessionIds.some(id => sessionIds.has(id)))\n || (b.closedAt ?? b.updatedAt) - (a.closedAt ?? a.updatedAt));\n for (const item of history.slice(0, 20)) {\n input.context!.archivedItems.push(item);\n if (bytes() > options.policy.maxInputBytes) input.context!.archivedItems.pop();\n }\n return structuredClone(input);\n}\nfunction addRecords(input: KanbanScanInput, state: KanbanState, sessionIds: Set<string>, bytes: () => number, maxBytes: number): void {\n const records = (state.records ?? []).filter(record => record.status === 'confirmed').sort((a, b) => Number(b.kind === 'decision') - Number(a.kind === 'decision')\n || Number(b.sources.some(source => sessionIds.has(source.sessionId))) - Number(a.sources.some(source => sessionIds.has(source.sessionId))) || b.createdAt - a.createdAt);\n for (const record of records.slice(0, 10)) {\n input.projectContext!.records.push(record); input.projectContext!.omittedRecords--;\n if (bytes() > maxBytes) {input.projectContext!.records.pop(); input.projectContext!.omittedRecords++;}\n }\n}\n\nfunction score(item: KanbanItem, sessions: Set<string>, content: string): number {\n if (item.sources?.some(source => sessions.has(source.sessionId)) || content.includes(item.title.toLowerCase())) return 0;\n if (item.status === 'claimed' || item.status === 'review') return 1;\n if (item.status === 'open') return 2;\n return item.status === 'completed' || item.status === 'cancelled' ? 4 : 3;\n}\n","import {discoveryState} from './discovery-state';\nimport {randomUUID} from './utils';\nimport type {KanbanDiscoveryChange, KanbanRequest, KanbanScanInput, KanbanScanSession, KanbanScanText} from './contracts';\nimport {KanbanError, KanbanScanNotStartedError} from './ports';\nimport type {KanbanDiscoveryOptions, KanbanDiscoveryState} from './scan-ports';\nimport {scanContext} from './scan-context';\nimport {identity, positive, requireValue, text as validateText} from './rules';\n\nexport interface KanbanDiscoveryHandle {scan(): Promise<void>; tick(): Promise<void>; reset(runId: string): Promise<void>; quiesce(): Promise<void>; dispose(): Promise<void>}\nexport function createKanbanDiscovery(options: KanbanDiscoveryOptions): KanbanDiscoveryHandle {return new Discovery(options);}\nclass Discovery {\n private readonly now;\n private readonly id;\n private readonly token;\n private active?: Promise<void>;\n private stopping = false;\n private readonly resets = new Set<Promise<void>>();\n private stage = 'prepare';\n constructor(private readonly options: KanbanDiscoveryOptions) {\n this.now = options.now ?? Date.now; this.id = options.id ?? randomUUID; this.token = this.id();\n for (const value of [options.policy.idleMs, options.policy.maxSessions, options.policy.maxInputBytes, options.policy.leaseMs]) positive(value);\n identity(options.policy.connection.connectionId);\n }\n scan = (): Promise<void> => {\n if (this.stopping) return Promise.reject(new KanbanError('SERVICE_STOPPING'));\n if (this.active) return this.active;\n const operationId = this.id(), started = this.now();\n this.active = this.run().catch(async error => {\n if (error instanceof KanbanError && ['SOURCE_CHANGED', 'SCAN_BOARD_CHANGED', 'REVISION_CONFLICT'].includes(error.code)) {\n await this.settleInvalidatedRun();\n await this.update('invalidate', state => {if (state.pending?.token === this.token) {\n state.lastRun = {runId: state.pending.runId, outcome: 'invalidated', at: this.now(), code: error.code}; delete state.pending;\n }});\n }\n throw error;\n }).then(() => this.log(operationId, started, 'success'), error => {\n this.log(operationId, started, 'failure', errorCode(error)); throw error;\n }).finally(() => {this.active = undefined;});\n return this.active;\n };\n tick = (): Promise<void> => this.scan();\n async quiesce(): Promise<void> {\n this.stopping = true;\n await Promise.allSettled([this.active, ...this.resets]);\n }\n dispose = (): Promise<void> => this.quiesce();\n reset = (runId: string): Promise<void> => {\n if (this.stopping) return Promise.reject(new KanbanError('SERVICE_STOPPING'));\n const work = this.resetRun(runId).finally(() => {this.resets.delete(work);});\n this.resets.add(work); return work;\n };\n private async resetRun(runId: string): Promise<void> {\n const pending = discoveryState(await this.options.store.read(this.options.boardId))?.pending;\n requireValue(pending?.runId === runId, 'SCAN_CHANGED');\n await this.options.authorize('commit', pending.input);\n requireValue(pending.token === this.token || pending.leaseUntil <= this.now(), 'SCAN_BUSY');\n if (pending.executionStarted && !pending.executionSettled) requireValue(await this.options.executor.settled?.(pending.input), 'SCAN_EXECUTION_UNSETTLED');\n await this.update('reset', state => {\n requireValue(state.pending?.runId === runId && state.pending.token === pending.token, 'SCAN_CHANGED');\n state.lastRun = {runId, outcome: 'invalidated', at: this.now(), code: 'SCAN_MANUAL_RESET'}; delete state.pending;\n });\n }\n private async run(): Promise<void> {\n await this.options.authorize('read');\n const board = await this.options.service.read(this.options.boardId);\n requireValue(matchesScope(board.scope, this.options.source), 'SOURCE_PROJECT_MISMATCH');\n requireValue(this.options.source.kind === this.options.policy.connection.kind || this.options.policy.connection.allowContentTransfer, 'CONTENT_TRANSFER_NOT_ALLOWED');\n const stored = await this.options.store.read(board.id);\n let pending = discoveryState(stored)?.pending;\n if (pending && pending.token !== this.token && pending.leaseUntil > this.now()) return;\n if (!pending) {\n const sessions = await this.collect(); if (!sessions.length) return;\n const current = await this.options.store.read(board.id); requireValue(current, 'BOARD_NOT_FOUND');\n const input = scanContext(board, current, sessions, this.options, this.id());\n requireValue(new TextEncoder().encode(JSON.stringify(input)).length <= this.options.policy.maxInputBytes, 'SCAN_INPUT_LIMIT');\n pending = {runId: input.runId, autoCreate: this.options.policy.autoCreate && (input.projectContext?.omittedRecords ?? 0) === 0 && input.context!.omittedItems === 0 && input.context!.archivedItems.length === input.context!.archivedTotal, token: this.token, leaseUntil: this.now() + this.options.policy.leaseMs, input};\n }\n const selected = pending;\n await this.update('claim', state => {\n requireValue(!state.pending || state.pending.token === this.token || state.pending.leaseUntil <= this.now(), 'SCAN_BUSY');\n if (state.pending) requireValue(state.pending.runId === selected.runId, 'SCAN_CHANGED');\n const restoring = selected.token !== this.token;\n state.pending = {...selected, ...(restoring ? {request: undefined} : {}), token: this.token, leaseUntil: this.now() + this.options.policy.leaseMs};\n });\n await this.process();\n }\n private async collect(): Promise<KanbanScanText[]> {\n this.stage = 'source.list';\n const sessions = await this.options.source.list(this.options.policy.includeArchived);\n for (const session of sessions) {\n identity(session.id); requireValue(typeof session.revision === 'string' && session.revision.length > 0 && session.revision.length <= 16000, 'INVALID_SOURCE_REVISION');\n }\n requireValue(new Set(sessions.map(s => s.id)).size === sessions.length, 'DUPLICATE_SOURCE_SESSION');\n await this.observe(sessions);\n const state = discoveryState(await this.options.store.read(this.options.boardId))!;\n const result: KanbanScanText[] = []; let bytes = 0;\n for (const session of sessions.sort((a, b) => (a.updatedAt ?? 0) - (b.updatedAt ?? 0) || a.id.localeCompare(b.id))) {\n if (!this.eligible(session, state)) continue;\n const current = await this.options.source.inspect(session.id);\n if (!this.eligible(current, state) || current.revision !== session.revision) continue;\n this.stage = 'source.read';\n const text = await this.readCandidate(current, Math.floor(this.options.policy.maxInputBytes * 0.7) - bytes);\n if (!text) continue;\n requireValue(text.session.id === current.id && text.session.revision === current.revision, 'SOURCE_CHANGED');\n if (!text.complete) continue;\n const size = new TextEncoder().encode(JSON.stringify(text)).length;\n if (bytes + size > this.options.policy.maxInputBytes * 0.7) break;\n bytes += size; result.push(text); if (result.length >= this.options.policy.maxSessions) break;\n }\n return result;\n }\n private async readCandidate(session: KanbanScanSession, maxBytes: number): Promise<KanbanScanText | null> {\n const started = this.now();\n try {\n const result = await this.options.source.read(session, maxBytes);\n if (!result.complete) this.log(this.id(), started, 'failure', 'SOURCE_PROVENANCE_UNKNOWN', session.id);\n return result;\n } catch (error) {\n const code = errorCode(error);\n if (!['SCAN_INPUT_LIMIT', 'SOURCE_CHANGED', 'HARNESS_DIGEST_CHANGED'].includes(code)) throw error;\n this.log(this.id(), started, 'failure', code, session.id); return null;\n }\n }\n private eligible(session: KanbanScanSession, state: KanbanDiscoveryState): boolean {\n const quiet = state.observations?.[session.id];\n return !session.scannerOwned && (this.options.policy.includeArchived || !session.archived)\n && session.activity === 'idle' && session.updatedAt !== null && session.updatedAt <= this.now()\n && quiet?.revision === session.revision && this.now() - quiet.idleSince >= this.options.policy.idleMs\n && state.checkpoints[session.id] !== session.revision;\n }\n private async observe(sessions: KanbanScanSession[]): Promise<void> {\n await this.update('observe', state => {\n state.observations ??= {};\n for (const session of sessions) {\n if (session.activity !== 'idle' || session.scannerOwned) {delete state.observations[session.id]; continue;}\n const old = state.observations[session.id];\n if (old?.revision !== session.revision) state.observations[session.id] = {revision: session.revision, idleSince: this.now()};\n }\n });\n }\n private async process(): Promise<void> {\n let pending = discoveryState(await this.options.store.read(this.options.boardId))!.pending!;\n requireValue(pending.input.executorId === this.options.executor.identity, 'SCANNER_EXECUTOR_CHANGED');\n await this.checkSources(pending.input);\n if (!pending.changes) {\n await this.options.authorize('execute', pending.input);\n this.stage = 'analyzer.execute';\n await this.updateOwned('execute', state => {state.pending!.executionStarted = true;});\n const output = await this.execute(pending.input), result = Array.isArray(output) ? {changes: output} : output;\n await this.updateOwned('settled', state => {state.pending!.executionSettled = true;});\n validateChanges(result.changes, pending.input); validateRecords(result.records ?? [], pending.input, result.changes);\n await this.updateOwned('result', state => {state.pending!.changes = result.changes; state.pending!.records = result.records;});\n }\n pending = discoveryState(await this.options.store.read(this.options.boardId))!.pending!;\n await this.options.authorize('commit', pending.input); await this.checkSources(pending.input);\n if (!pending.request) {\n const board = await this.options.service.read(this.options.boardId);\n requireValue(board.revision === pending.input.board.revision, 'SCAN_BOARD_CHANGED');\n const request: KanbanRequest = {operationId: `discovery:${pending.runId}`, expectedRevision: board.revision,\n command: {type: 'apply-discovery', batch: {runId: pending.runId, leaseToken: this.token, changes: pending.changes!, ...(pending.records ? {records: pending.records} : {}), autoCreate: pending.autoCreate,\n checkpoints: pending.input.sessions.map(s => ({sessionId: s.session.id, revision: s.session.revision}))}}};\n await this.updateOwned('prepare', state => {state.pending!.request = request;});\n pending.request = request;\n }\n this.stage = 'board.commit';\n await this.options.service.execute(this.options.boardId, pending.request);\n }\n private async execute(input: KanbanScanInput): Promise<KanbanDiscoveryChange[] | import('./contracts').KanbanDiscoveryResult> {\n try {return await this.options.executor.run(structuredClone(input));}\n catch (error) {\n if (error instanceof KanbanScanNotStartedError) await this.updateOwned('not-started', state => {state.pending!.executionSettled = true;});\n throw error;\n }\n }\n private async settleInvalidatedRun(): Promise<void> {\n const pending = discoveryState(await this.options.store.read(this.options.boardId))?.pending;\n if (!pending || pending.token !== this.token || !pending.executionStarted || pending.executionSettled) return;\n requireValue(await this.options.executor.settled?.(pending.input), 'SCAN_EXECUTION_UNSETTLED');\n }\n private async checkSources(input: KanbanScanInput): Promise<void> {\n for (const source of input.sessions) {\n const current = await this.options.source.inspect(source.session.id);\n requireValue(current.revision === source.session.revision && current.activity === 'idle' && !current.scannerOwned, 'SOURCE_CHANGED');\n }\n }\n private async updateOwned(stage: string, mutate: (state: KanbanDiscoveryState) => void): Promise<void> {\n await this.update(stage, state => {\n requireValue(state.pending?.token === this.token && state.pending.leaseUntil > this.now(), 'SCAN_LEASE_EXPIRED'); mutate(state);\n });\n }\n private async update(stage: string, mutate: (state: KanbanDiscoveryState) => void): Promise<void> {\n const operation = this.id();\n await this.options.store.update(this.options.boardId, operation, `discovery:${stage}:${operation}`, state => {\n requireValue(state, 'BOARD_NOT_FOUND'); state.discovery ??= {checkpoints: {}}; mutate(state.discovery as KanbanDiscoveryState); return state;\n });\n }\n private log(operationId: string, started: number, result: 'success' | 'failure', code?: string, sessionId?: string): void {\n try {this.options.log({operationId, boardId: this.options.boardId, sessionId, stage: `discovery.${this.stage}`, result, code, durationMs: this.now() - started});}\n catch { /* Diagnostics cannot affect durable execution. */ }\n }\n}\nfunction validateChanges(changes: KanbanDiscoveryChange[], input: KanbanScanInput): void {\n requireValue(Array.isArray(changes) && changes.length <= 50, 'SCAN_CHANGE_LIMIT');\n const ids = new Set<string>();\n for (const change of changes) {\n requireValue(change && ['create', 'update', 'link'].includes(change.action) && !ids.has(change.itemId), 'INVALID_CHANGE'); ids.add(change.itemId); identity(change.itemId);\n if (change.action !== 'create') requireValue(input.board.items.some(i => i.id === change.itemId), 'UNKNOWN_CONTEXT_ITEM');\n validateText(change.title); validateText(change.responsibility); validateText(change.acceptance);\n requireValue(change.eligibleAgentIds === undefined || Array.isArray(change.eligibleAgentIds), 'INVALID_ELIGIBILITY');\n change.eligibleAgentIds?.forEach(identity);\n requireValue(change.eligiblePrincipals === undefined || Array.isArray(change.eligiblePrincipals), 'INVALID_ELIGIBILITY');\n requireValue(Array.isArray(change.sources) && change.sources.length > 0 && change.sources.length <= 100 && change.sources.some(s => s.role === 'user'), 'USER_EVIDENCE_REQUIRED');\n for (const ref of change.sources) {\n const source = input.sessions.find(s => s.session.id === ref.sessionId && s.session.revision === ref.revision);\n requireValue(source?.messages.some(m => m.id === ref.messageId && m.role === ref.role), 'INVALID_SOURCE_REFERENCE');\n }\n }\n}\n\nfunction validateRecords(records: import('./contracts').KanbanRecordInput[], input: KanbanScanInput, changes: KanbanDiscoveryChange[]): void {\n requireValue(Array.isArray(records) && records.length <= 20, 'SCAN_RECORD_LIMIT');\n const ids = new Set<string>();\n for (const record of records) {\n identity(record.id); requireValue(!ids.has(record.id), 'INVALID_RECORD_ID'); ids.add(record.id);\n validateText(record.title); validateText(record.content);\n requireValue(['log', 'decision'].includes(record.kind), 'INVALID_RECORD_KIND');\n if (record.kind === 'decision') validateText(record.rationale ?? '');\n requireValue(Array.isArray(record.itemIds) && record.itemIds.every(id => input.board.items.some(item => item.id === id) || changes.some(change => change.action === 'create' && change.itemId === id)), 'UNKNOWN_CONTEXT_ITEM');\n if (record.supersedes) requireValue(input.projectContext?.records.some(old => old.id === record.supersedes && old.kind === 'decision'), 'UNKNOWN_CONTEXT_RECORD');\n requireValue(Array.isArray(record.sources) && record.sources.length > 0 && record.sources.length <= 100\n && record.sources.some(source => source.role === 'user'), 'USER_EVIDENCE_REQUIRED');\n for (const ref of record.sources) {\n const source = input.sessions.find(s => s.session.id === ref.sessionId && s.session.revision === ref.revision);\n requireValue(source?.messages.some(message => message.id === ref.messageId && message.role === ref.role), 'INVALID_SOURCE_REFERENCE');\n }\n }\n}\n\nfunction errorCode(error: unknown): string {\n const value = error as {code?: unknown; remoteCode?: unknown};\n const code = value?.remoteCode ?? value?.code;\n return typeof code === 'string' && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? code : 'SCAN_PORT_FAILURE';\n}\n\nfunction matchesScope(scope: import('./contracts').KanbanBoard['scope'], source: import('./scan-ports').KanbanScanSource): boolean {\n const expected = source.scope ?? (source.projectId ? {kind: 'project' as const, projectId: source.projectId} : undefined);\n if (!expected || scope.kind !== expected.kind) return false;\n if (scope.kind === 'system') return true;\n if (scope.kind === 'project' && expected.kind === 'project') return scope.projectId === expected.projectId;\n return scope.kind === 'collection' && expected.kind === 'collection' && scope.namespace === expected.namespace && scope.id === expected.id;\n}\n"],"mappings":"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","names":["text"]}
@@ -0,0 +1,52 @@
1
+ import { T as KanbanService, I as KanbanPrincipal, r as KanbanDispatch, w as KanbanItem, d as KanbanArtifactRef, u as KanbanExternalReference, s as KanbanEstimate } from './planning-CHrOqQ6b.cjs';
2
+ import { IFileSystem } from '@nestos/vfs-core';
3
+
4
+ interface KanbanExecutionObservation {
5
+ controlCapabilities?: {
6
+ pause: 'boundary' | 'none';
7
+ resume: boolean;
8
+ };
9
+ executionSessionId?: string;
10
+ outcome: 'running' | 'unknown' | 'succeeded' | 'failed' | 'cancelled';
11
+ outputs?: KanbanArtifactRef[];
12
+ code?: string;
13
+ }
14
+ interface KanbanExecutionPort {
15
+ prepareContinuation?(dispatch: KanbanDispatch, item: KanbanItem, previousRequest: unknown): Promise<unknown>;
16
+ /** Persistable request; preparing cannot submit agent work. */
17
+ prepare(dispatch: KanbanDispatch, item: KanbanItem): Promise<unknown>;
18
+ start(request: unknown): Promise<KanbanExecutionObservation>;
19
+ /** Observe the original admission only. Missing receipts mean unknown. */
20
+ inspect(request: unknown): Promise<KanbanExecutionObservation>;
21
+ }
22
+ interface KanbanExecutionBridgeOptions {
23
+ fs: IFileSystem;
24
+ directory: string;
25
+ boardId: string;
26
+ service: KanbanService;
27
+ worker(principal: KanbanPrincipal): Promise<KanbanService>;
28
+ executor: KanbanExecutionPort;
29
+ authorize(stage: 'prepare' | 'start' | 'inspect', dispatch: KanbanDispatch): Promise<void>;
30
+ log(input: {
31
+ operationId: string;
32
+ stage: string;
33
+ result: 'success' | 'failure';
34
+ code?: string;
35
+ retryAfterMs?: number;
36
+ durationMs: number;
37
+ }): void;
38
+ executionReference?(sessionId: string, dispatchId: string): KanbanExternalReference;
39
+ subscribe?(wake: () => void): () => void;
40
+ now?: () => number;
41
+ autoDispatch?: boolean | (() => Promise<boolean>);
42
+ shutdownTimeoutMs?: number;
43
+ estimate?(item: KanbanItem, principal: KanbanPrincipal): Promise<KanbanEstimate | undefined>;
44
+ }
45
+ /** The host owns execution. Prepared/submitting identities survive response loss and host shutdown. */
46
+ declare function createKanbanExecutionBridge(options: KanbanExecutionBridgeOptions): Promise<{
47
+ scan: () => Promise<void>;
48
+ tick: () => Promise<void>;
49
+ quiesce(): Promise<void>;
50
+ }>;
51
+
52
+ export { type KanbanExecutionBridgeOptions as K, type KanbanExecutionObservation as a, type KanbanExecutionPort as b, createKanbanExecutionBridge as c };
@@ -0,0 +1,52 @@
1
+ import { T as KanbanService, I as KanbanPrincipal, r as KanbanDispatch, w as KanbanItem, d as KanbanArtifactRef, u as KanbanExternalReference, s as KanbanEstimate } from './planning-CHrOqQ6b.js';
2
+ import { IFileSystem } from '@nestos/vfs-core';
3
+
4
+ interface KanbanExecutionObservation {
5
+ controlCapabilities?: {
6
+ pause: 'boundary' | 'none';
7
+ resume: boolean;
8
+ };
9
+ executionSessionId?: string;
10
+ outcome: 'running' | 'unknown' | 'succeeded' | 'failed' | 'cancelled';
11
+ outputs?: KanbanArtifactRef[];
12
+ code?: string;
13
+ }
14
+ interface KanbanExecutionPort {
15
+ prepareContinuation?(dispatch: KanbanDispatch, item: KanbanItem, previousRequest: unknown): Promise<unknown>;
16
+ /** Persistable request; preparing cannot submit agent work. */
17
+ prepare(dispatch: KanbanDispatch, item: KanbanItem): Promise<unknown>;
18
+ start(request: unknown): Promise<KanbanExecutionObservation>;
19
+ /** Observe the original admission only. Missing receipts mean unknown. */
20
+ inspect(request: unknown): Promise<KanbanExecutionObservation>;
21
+ }
22
+ interface KanbanExecutionBridgeOptions {
23
+ fs: IFileSystem;
24
+ directory: string;
25
+ boardId: string;
26
+ service: KanbanService;
27
+ worker(principal: KanbanPrincipal): Promise<KanbanService>;
28
+ executor: KanbanExecutionPort;
29
+ authorize(stage: 'prepare' | 'start' | 'inspect', dispatch: KanbanDispatch): Promise<void>;
30
+ log(input: {
31
+ operationId: string;
32
+ stage: string;
33
+ result: 'success' | 'failure';
34
+ code?: string;
35
+ retryAfterMs?: number;
36
+ durationMs: number;
37
+ }): void;
38
+ executionReference?(sessionId: string, dispatchId: string): KanbanExternalReference;
39
+ subscribe?(wake: () => void): () => void;
40
+ now?: () => number;
41
+ autoDispatch?: boolean | (() => Promise<boolean>);
42
+ shutdownTimeoutMs?: number;
43
+ estimate?(item: KanbanItem, principal: KanbanPrincipal): Promise<KanbanEstimate | undefined>;
44
+ }
45
+ /** The host owns execution. Prepared/submitting identities survive response loss and host shutdown. */
46
+ declare function createKanbanExecutionBridge(options: KanbanExecutionBridgeOptions): Promise<{
47
+ scan: () => Promise<void>;
48
+ tick: () => Promise<void>;
49
+ quiesce(): Promise<void>;
50
+ }>;
51
+
52
+ export { type KanbanExecutionBridgeOptions as K, type KanbanExecutionObservation as a, type KanbanExecutionPort as b, createKanbanExecutionBridge as c };
@@ -0,0 +1,18 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+ var _chunkXBSGUVCYcjs = require('./chunk-XBSGUVCY.cjs');
8
+ require('./chunk-4XF6LQSU.cjs');
9
+ require('./chunk-RSH6QSEC.cjs');
10
+ require('./chunk-AOHQCX7K.cjs');
11
+
12
+
13
+
14
+
15
+
16
+
17
+ exports.consumeWorkMessages = _chunkXBSGUVCYcjs.consumeWorkMessages; exports.createKanbanExecutionBridge = _chunkXBSGUVCYcjs.createKanbanExecutionBridge; exports.parseWorkMessage = _chunkXBSGUVCYcjs.parseWorkMessage; exports.startKanbanMonitor = _chunkXBSGUVCYcjs.startKanbanMonitor; exports.unresolvedWorkIssues = _chunkXBSGUVCYcjs.unresolvedWorkIssues;
18
+ //# sourceMappingURL=execution.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/li/share/prj/x1/libraries/kanban/dist/execution.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B,gCAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACF,kWAAC","file":"/home/li/share/prj/x1/libraries/kanban/dist/execution.cjs"}
@@ -0,0 +1,42 @@
1
+ export { K as KanbanExecutionBridgeOptions, a as KanbanExecutionObservation, b as KanbanExecutionPort, c as createKanbanExecutionBridge } from './execution-bridge-fpCnH6z5.cjs';
2
+ import { K as KanbanClient } from './activity-BbC4sgKL.cjs';
3
+ import { IFileSystem } from '@nestos/vfs-core';
4
+ import './planning-CHrOqQ6b.cjs';
5
+
6
+ /** Host supplies its scheduler; disposal prevents new scans and awaits in-flight work. */
7
+ declare function startKanbanMonitor(options: {
8
+ service: Pick<KanbanClient, 'scan'>;
9
+ subscribeTick: (tick: () => void) => () => void;
10
+ onError: (error: unknown) => void | Promise<void>;
11
+ onSuccess?: () => void | Promise<void>;
12
+ subscribeChanges?: (tick: () => void) => () => void;
13
+ initialScan?: boolean;
14
+ }): {
15
+ dispose(): Promise<void>;
16
+ };
17
+
18
+ interface WorkMessageScope {
19
+ runId: string;
20
+ itemId: string;
21
+ attemptId: string;
22
+ }
23
+ interface WorkMessage extends WorkMessageScope {
24
+ schema: 'work-message/1' | 'work-message/2';
25
+ id: string;
26
+ type: 'progress' | 'clarification' | 'error' | 'result' | 'resolved';
27
+ content: string;
28
+ replyTo?: string;
29
+ }
30
+ interface Journal {
31
+ get<T>(key: string): Promise<T | undefined>;
32
+ update<T>(key: string, change: (old: T | undefined) => T): Promise<T>;
33
+ }
34
+ /** Directory events are wakeups; immutable files plus durable receipts are authoritative. */
35
+ declare function consumeWorkMessages(fs: IFileSystem, directory: string, scope: WorkMessageScope, journal: Journal, deliver: (message: WorkMessage) => Promise<void | {
36
+ pending: true;
37
+ }>, decode: (text: string) => unknown, publishReply: (directory: string, name: string, content: string) => Promise<void>): Promise<WorkMessage[]>;
38
+ declare function parseWorkMessage(text: string, scope: WorkMessageScope, decode: (text: string) => unknown): WorkMessage;
39
+ /** Only explicit linked resolutions close issues; unrelated progress cannot erase them. */
40
+ declare function unresolvedWorkIssues(messages: WorkMessage[]): WorkMessage[];
41
+
42
+ export { type WorkMessage, type WorkMessageScope, consumeWorkMessages, parseWorkMessage, startKanbanMonitor, unresolvedWorkIssues };