@volter/supercode-orchestrator 0.4.95 → 0.4.97
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/board/cli.mjs +3 -2
- package/board/rows.mjs +16 -0
- package/board/workflow.mjs +1 -1
- package/board/ztrack.mjs +24 -9
- package/package.json +1 -1
package/board/cli.mjs
CHANGED
|
@@ -236,7 +236,8 @@ export async function runWorkflow(argv) {
|
|
|
236
236
|
const ids = (first = rest) => [...first, ...(args.ids ?? [])];
|
|
237
237
|
|
|
238
238
|
// a caller that is a card's own session (its harness names it; the board recorded which session it launched)
|
|
239
|
-
const
|
|
239
|
+
const listing = ['list','ls'].includes(verb);
|
|
240
|
+
const sid = listing && !args.mine ? null : callerSession();
|
|
240
241
|
// what this command records is the calling session's act (its address, as its subscriptions name it)
|
|
241
242
|
if (sid) setActor(`sc:${localMachine()}:${sid === process.env.CODEX_THREAD_ID ? 'codex' : 'claude-code'}:${sid}`);
|
|
242
243
|
if (sid && !READS.has(verb) && boardExists(root, slug)) {
|
|
@@ -244,7 +245,7 @@ export async function runWorkflow(argv) {
|
|
|
244
245
|
}
|
|
245
246
|
// the board's workflow (the IR's): what each verb does, and who may send it, is its
|
|
246
247
|
// every verb reads the board through its workflow (a list settles cards too), so every verb loads it
|
|
247
|
-
if (verb !== 'help') await loadWorkflow(root).catch((error) => { if (verb !== 'init' && verb !== 'boards') throw error; });
|
|
248
|
+
if (verb !== 'help' && !listing) await loadWorkflow(root).catch((error) => { if (verb !== 'init' && verb !== 'boards') throw error; });
|
|
248
249
|
engine.setCaller((db, cardId) => rolesFor(db, cardId, sid, workflowFor(root).params));
|
|
249
250
|
// a verb with no session behind it (a person at the CLI, the board file's sync) is the manager's, as its roles are
|
|
250
251
|
if (!sid && verb !== 'help') { try { setActor([].concat(workflowFor(root).params?.managers ?? [])[0] ?? null); } catch { /* no workflow */ } }
|
package/board/rows.mjs
CHANGED
|
@@ -57,6 +57,21 @@ export class RuntimeRows {
|
|
|
57
57
|
return {name,key,value:row,source:{name,key,row}};
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
+
mirror(changes, revision) {
|
|
61
|
+
const previous=Number(this.db.prepare("SELECT value FROM state WHERE key='read_revision'").get()?.value??-1);
|
|
62
|
+
for(const name of ['tasks','supercode_cards']) {
|
|
63
|
+
const table=this.tables.get(name); if(!table)return;
|
|
64
|
+
this.db.exec(table.sql.replace(/^CREATE TABLE (?:IF NOT EXISTS )?(?:"[^"]+"|\w+)/,`CREATE TABLE IF NOT EXISTS ${quote('read_'+name)}`));
|
|
65
|
+
}
|
|
66
|
+
const rows=previous===revision-1?changes.filter(c=>['tasks','supercode_cards'].includes(c.source.name)).map(c=>c.source):this.db.prepare("SELECT name,key,value FROM runtime_rows WHERE name IN ('@task_cache','@card_cache')").all().map(r=>({name:r.name==='@task_cache'?'tasks':'supercode_cards',key:JSON.stringify([r.key]),row:json(r.value,null)}));
|
|
67
|
+
for(const {name,key,row} of rows) {
|
|
68
|
+
const table=this.tables.get(name), target=quote('read_'+name);
|
|
69
|
+
if(!row) {this.db.prepare(`DELETE FROM ${target} WHERE ${table.keys.map(k=>`${quote(k)} IS ?`).join(' AND ')}`).run(...JSON.parse(key));continue;}
|
|
70
|
+
const keys=Object.keys(row);
|
|
71
|
+
this.db.prepare(`INSERT OR REPLACE INTO ${target} (${keys.map(quote)}) VALUES (${keys.map(()=>'?')})`).run(...keys.map(k=>row[k]??null));
|
|
72
|
+
}
|
|
73
|
+
this.db.prepare("INSERT INTO state VALUES('read_revision',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(String(revision));
|
|
74
|
+
}
|
|
60
75
|
write(changes) {
|
|
61
76
|
this.db.exec(`CREATE TABLE IF NOT EXISTS runtime_schema(name TEXT PRIMARY KEY,sql TEXT NOT NULL); CREATE TABLE IF NOT EXISTS runtime_rows(name TEXT,key TEXT,value TEXT,revision INTEGER NOT NULL,PRIMARY KEY(name,key)); CREATE INDEX IF NOT EXISTS runtime_rows_revision ON runtime_rows(revision); CREATE INDEX IF NOT EXISTS runtime_rows_task ON runtime_rows(name,json_extract(value,'$.task_id'),json_extract(value,'$.id')); CREATE INDEX IF NOT EXISTS runtime_rows_id ON runtime_rows(name,json_extract(value,'$.id'))`);
|
|
62
77
|
const revision=Number(this.db.prepare("SELECT value FROM state WHERE key='rows_revision'").get()?.value??0)+1;
|
|
@@ -77,6 +92,7 @@ export class RuntimeRows {
|
|
|
77
92
|
}
|
|
78
93
|
for(const {name,sql} of this.tables.values())if(!OMIT.has(name))this.db.prepare('INSERT OR IGNORE INTO runtime_schema VALUES(?,?)').run(name,sql);
|
|
79
94
|
this.db.prepare("INSERT INTO state VALUES('rows_revision',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(String(revision));
|
|
95
|
+
this.mirror(changes,revision);
|
|
80
96
|
this.revision=revision;
|
|
81
97
|
}
|
|
82
98
|
}
|
package/board/workflow.mjs
CHANGED
|
@@ -113,7 +113,7 @@ function lastCommitAt(path, id) { return path ? commitTimes.get(`${path}:${id}`)
|
|
|
113
113
|
export function refreshCardInputs(board, id, { commits = false } = {}) {
|
|
114
114
|
const task=board.db.prepare('SELECT t.*,c.subtask_of FROM tasks t LEFT JOIN supercode_cards c ON c.task_id=t.id WHERE t.id=?').get(id);
|
|
115
115
|
if (!task)return;
|
|
116
|
-
if (commits) {
|
|
116
|
+
if (commits && !task.subtask_of) {
|
|
117
117
|
const own=task.workspace_kind!=='scratch'?task.workspace_path:null;
|
|
118
118
|
const parent=!own && task.subtask_of?board.db.prepare('SELECT workspace_path FROM tasks WHERE id=?').get(task.subtask_of)?.workspace_path:null;
|
|
119
119
|
const path=own??parent??null;
|
package/board/ztrack.mjs
CHANGED
|
@@ -109,6 +109,7 @@ class ZtrackBoard {
|
|
|
109
109
|
|
|
110
110
|
transaction(fn) {
|
|
111
111
|
if (this.cachedReader) this.invalidate();
|
|
112
|
+
this.readOnly(()=>{ if(this.runtime.prepare('PRAGMA journal_mode').get().journal_mode!=='wal')this.runtime.exec('PRAGMA journal_mode=WAL'); });
|
|
112
113
|
for (;;) {
|
|
113
114
|
try { this.runtime.exec('BEGIN'); }
|
|
114
115
|
catch (error) { if (!/busy|locked/i.test(error.message)) throw error; continue; }
|
|
@@ -126,12 +127,12 @@ class ZtrackBoard {
|
|
|
126
127
|
const result = fn(this.db);
|
|
127
128
|
const changes = this.rows.changes(this.mem, { all: !this.rows.normalized() });
|
|
128
129
|
const canonical = (row, omitted) => row && Object.fromEntries(Object.entries(row).filter(([key])=>!omitted.has(key)));
|
|
129
|
-
const
|
|
130
|
+
const documentChanges = changes.filter(({source})=>{
|
|
130
131
|
if(source.name==='tasks')return JSON.stringify(canonical(source.row,new Set([...RUNTIME,'status'])))!==JSON.stringify(canonical(this.rows.cachedTasks[JSON.parse(source.key)[0]],new Set([...RUNTIME,'status'])));
|
|
131
132
|
if(source.name==='supercode_cards')return JSON.stringify(canonical(source.row,new Set(['since','after_fired','next_run_at'])))!==JSON.stringify(canonical(this.rows.cachedCards[JSON.parse(source.key)[0]],new Set(['since','after_fired','next_run_at'])));
|
|
132
|
-
return
|
|
133
|
+
return false;
|
|
133
134
|
});
|
|
134
|
-
const documents =
|
|
135
|
+
const documents = this.seed || documentChanges.length ? this.documents(this.seed ? null : documentChanges) : {open:this.text, archived:this.archiveText};
|
|
135
136
|
const files = [{ path: this.path, before: hash(this.text), base: this.text, text: documents.open }, { path: this.archive, before: hash(this.archiveText), base: this.archiveText, text: documents.archived }];
|
|
136
137
|
const snapshot = this.snapshot(documents);
|
|
137
138
|
this.computing = false;
|
|
@@ -219,12 +220,21 @@ class ZtrackBoard {
|
|
|
219
220
|
this.mem.prepare(`INSERT OR REPLACE INTO ${quote(table)} (${keys.map(quote).join(',')}) VALUES (${keys.map(() => '?').join(',')})`).run(...keys.map((k) => row[k] ?? null));
|
|
220
221
|
}
|
|
221
222
|
openCachedReader(saved) {
|
|
223
|
+
const native = this.get('read_revision',-1)===this.get('rows_revision',0);
|
|
224
|
+
if(this.cachedReader && native!==this.nativeReader)this.invalidate();
|
|
222
225
|
if (!this.cachedReader) {
|
|
223
226
|
this.mem?.close(); this.mem=new DatabaseSync(':memory:'); this.init(this.mem,{fresh:true});
|
|
224
227
|
for(const row of this.runtime.prepare('SELECT name,sql FROM runtime_schema').all())this.mem.exec(row.sql.replace('CREATE TABLE ','CREATE TABLE IF NOT EXISTS '));
|
|
225
228
|
this.mem.prepare('ATTACH DATABASE ? AS source').run(this.runtimePath);
|
|
226
229
|
const tables=this.mem.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all();
|
|
227
230
|
for(const {name} of tables) {
|
|
231
|
+
if (native && name==='tasks') {
|
|
232
|
+
const fields=this.mem.prepare('PRAGMA main.table_info(tasks)').all().map(({name:column})=>column==='status'?'COALESCE(c.stage,t.status) AS status':`t.${quote(column)}`);
|
|
233
|
+
this.mem.exec(`CREATE TEMP VIEW tasks AS SELECT ${fields.join(',')} FROM source.read_tasks t LEFT JOIN source.read_supercode_cards c ON c.task_id=t.id`);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if(native && name==='supercode_cards') {this.mem.exec('CREATE TEMP VIEW supercode_cards AS SELECT * FROM source.read_supercode_cards');continue;}
|
|
237
|
+
if(native && name==='task_links') {this.mem.exec("CREATE TEMP VIEW task_links AS SELECT json_extract(b.value,'$.id') parent_id,r.task_id child_id FROM source.read_supercode_cards r,json_each(r.blocked_by) b WHERE json_extract(b.value,'$.kind')='card'");continue;}
|
|
228
238
|
if(name==='task_links') {
|
|
229
239
|
this.mem.exec(`CREATE TEMP VIEW task_links AS SELECT json_extract(b.value,'$.id') parent_id,r.key child_id FROM source.runtime_rows r,json_each(json_extract(r.value,'$.blocked_by')) b WHERE r.name='@card_cache' AND json_extract(b.value,'$.kind')='card'`);
|
|
230
240
|
continue;
|
|
@@ -236,7 +246,7 @@ class ZtrackBoard {
|
|
|
236
246
|
this.mem.exec(`CREATE TEMP VIEW ${quote(name)} AS SELECT ${fields.join(',')} FROM source.runtime_rows r${join} WHERE r.name='${recordName}' AND r.value IS NOT NULL`);
|
|
237
247
|
}
|
|
238
248
|
this.mem.exec('PRAGMA query_only=ON');
|
|
239
|
-
this.cachedReader=true;
|
|
249
|
+
this.cachedReader=true;this.nativeReader=native;
|
|
240
250
|
}
|
|
241
251
|
this.saved=saved; this.errors.clear();
|
|
242
252
|
for(const [id,reason] of saved.errors??[])this.errors.set(id,reason);
|
|
@@ -275,12 +285,13 @@ class ZtrackBoard {
|
|
|
275
285
|
this.rows.watch(this.mem);
|
|
276
286
|
this.mem.exec('DELETE FROM tasks; DELETE FROM supercode_cards; DELETE FROM task_links');
|
|
277
287
|
this.errors.clear(); this.preserve.clear();
|
|
278
|
-
const records = [];
|
|
288
|
+
const records = []; this.documentRecords=new Map();
|
|
279
289
|
for (const [text,path] of [[this.text,this.path],[this.archiveText,this.archive]]) {
|
|
280
290
|
const result = this.codec.inspectBoardDocument(text,path);
|
|
281
291
|
const kept = new Set(result.errors.map(e=>e.id));
|
|
282
292
|
for(const error of result.errors)this.errors.set(nativeId(error.id),error.reason);
|
|
283
293
|
for(const record of result.records) {
|
|
294
|
+
this.documentRecords.set(nativeId(record.id),{record,path});
|
|
284
295
|
try { records.push(this.preset.KanbanRootSchema.parse(this.preset.parseKanban([record])).issues[0]); }
|
|
285
296
|
catch(error) { this.errors.set(nativeId(record.id),error.message); kept.add(record.id); }
|
|
286
297
|
}
|
|
@@ -380,10 +391,11 @@ class ZtrackBoard {
|
|
|
380
391
|
committer() {
|
|
381
392
|
return 'document-editor';
|
|
382
393
|
}
|
|
383
|
-
documents() {
|
|
384
|
-
const
|
|
394
|
+
documents(changes=null) {
|
|
395
|
+
const allRows = this.mem.prepare('SELECT * FROM tasks ORDER BY created_at, id').all();
|
|
385
396
|
const states = new Map(this.mem.prepare('SELECT * FROM supercode_cards').all().map((c) => [c.task_id, c]));
|
|
386
|
-
const
|
|
397
|
+
const affected=changes?new Set(changes.map(({source})=>{const id=JSON.parse(source.key)[0];return states.get(id)?.subtask_of??this.rows.cachedCards[id]?.subtask_of??id;})):null;
|
|
398
|
+
const rows=affected?allRows.filter(row=>affected.has(states.get(row.id)?.subtask_of??row.id)):allRows;
|
|
387
399
|
const cards = new Map();
|
|
388
400
|
for (const row of rows.filter((r) => !states.get(r.id)?.subtask_of && !this.errors.has(r.id))) {
|
|
389
401
|
const c = states.get(row.id) ?? {};
|
|
@@ -420,12 +432,15 @@ class ZtrackBoard {
|
|
|
420
432
|
blockedBy: json(c.blocked_by, []).map((b) => ({ issue: b.id })) });
|
|
421
433
|
}
|
|
422
434
|
const open = [], archived = [];
|
|
435
|
+
if(affected)for(const [id,{record,path}] of this.documentRecords??[])if(!affected.has(id)&&!this.errors.has(id))(path===this.path?open:archived).push(record);
|
|
423
436
|
for (const [id, card] of cards) {
|
|
424
437
|
const serialized = this.preset.serializeKanbanCard(card);
|
|
425
438
|
const record = { id: card.id, title: card.title, status: card.status, ...(card.assignee ? { assignee: card.assignee } : {}), body: serialized.body };
|
|
426
439
|
(states.get(id)?.archived || ['done', 'cancelled'].includes(card.status) ? archived : open).push(record);
|
|
427
440
|
}
|
|
428
|
-
|
|
441
|
+
const original=this.documentRecords;
|
|
442
|
+
const changedPath=path=>!affected||[...affected].some(id=>original?.get(id)?.path===path||cards.has(id)&&((states.get(id)?.archived||['done','cancelled'].includes(cards.get(id).status))?this.archive:this.path)===path);
|
|
443
|
+
return { open: !changedPath(this.path)?this.text:this.codec.renderBoardDocument(this.text, open, this.path, [...(this.preserve.get(this.path)??[])]), archived: !changedPath(this.archive)?this.archiveText:this.codec.renderBoardDocument(this.archiveText, archived, this.archive, [...(this.preserve.get(this.archive)??[])]) };
|
|
429
444
|
}
|
|
430
445
|
snapshot(documents) {
|
|
431
446
|
return { documentHashes:{open:hash(documents.open),archived:hash(documents.archived)}, errors:[...this.errors], fingerprints: this.fingerprints(), taskIds: Object.fromEntries([...this.taskKeys].map(([id, key]) => [key, id])) };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-orchestrator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.97",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The orchestrator runtime over the Volter Harness ontology: one typed operational model whose folder is its serialization, read and written through the harness orchestration doors (docs/ORCHESTRATOR-IR.md)",
|
|
6
6
|
"exports": {
|