wyrm-mcp 7.2.0 → 7.2.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 +26 -667
- package/NOTICE +14 -33
- package/dist/activation.d.ts.map +1 -1
- package/dist/activation.js +1 -44
- package/dist/activation.js.map +1 -1
- package/dist/agent-daemon.js +4 -281
- package/dist/agent-loop.js +7 -332
- package/dist/analytics.js +13 -236
- package/dist/attribution.js +1 -49
- package/dist/audit.js +2 -457
- package/dist/auto-capture.js +3 -138
- package/dist/auto-orchestrator.js +1 -325
- package/dist/autoconfig.js +39 -840
- package/dist/buddy-runner.js +1 -109
- package/dist/buddy.js +14 -564
- package/dist/build-flags.js +1 -17
- package/dist/capabilities.js +3 -183
- package/dist/capture.js +1 -56
- package/dist/causality.js +6 -107
- package/dist/cli.js +20 -281
- package/dist/cloud/cli.js +5 -541
- package/dist/cloud/client.js +1 -221
- package/dist/cloud/crypto.js +1 -85
- package/dist/cloud/machine-id.js +2 -113
- package/dist/cloud/recovery.js +1 -60
- package/dist/cloud/sync-engine.js +7 -543
- package/dist/cloud-backup.js +5 -579
- package/dist/cloud-profile.js +1 -138
- package/dist/cloud-sync-entrypoint.js +1 -47
- package/dist/cloud-sync.js +2 -309
- package/dist/constellation.js +12 -168
- package/dist/context-build-budgeted.js +4 -144
- package/dist/context-ranking.js +1 -69
- package/dist/crypto.js +1 -179
- package/dist/daemon-write-endpoint.js +1 -290
- package/dist/daemon-writer.js +2 -406
- package/dist/database.js +43 -1110
- package/dist/deprecations.js +2 -162
- package/dist/design.js +13 -141
- package/dist/event-replication.js +1 -112
- package/dist/events-sse.js +7 -43
- package/dist/events.js +6 -238
- package/dist/failure-patterns.js +42 -659
- package/dist/federation.js +12 -236
- package/dist/goals.js +13 -101
- package/dist/golden.js +3 -355
- package/dist/handlers/agent.js +4 -165
- package/dist/handlers/alias-adapters.js +1 -129
- package/dist/handlers/aliases.js +1 -171
- package/dist/handlers/audit.js +1 -87
- package/dist/handlers/boundary.js +1 -221
- package/dist/handlers/capture.js +73 -1109
- package/dist/handlers/causality.js +7 -114
- package/dist/handlers/cloud.js +85 -382
- package/dist/handlers/companion.js +28 -459
- package/dist/handlers/datalake.js +7 -187
- package/dist/handlers/dispatch-context.js +0 -22
- package/dist/handlers/entity.js +25 -256
- package/dist/handlers/events.js +16 -335
- package/dist/handlers/failure.js +13 -340
- package/dist/handlers/goals.js +4 -296
- package/dist/handlers/intelligence.js +126 -674
- package/dist/handlers/invoicing.js +1 -70
- package/dist/handlers/mcpclient.js +6 -137
- package/dist/handlers/orchestration.js +40 -125
- package/dist/handlers/output-schemas.js +1 -24
- package/dist/handlers/presence.js +3 -99
- package/dist/handlers/project.js +28 -182
- package/dist/handlers/prompts.js +6 -157
- package/dist/handlers/quest.js +4 -224
- package/dist/handlers/recall.js +11 -218
- package/dist/handlers/registry.js +1 -167
- package/dist/handlers/resources.js +1 -288
- package/dist/handlers/review.js +11 -74
- package/dist/handlers/run.js +17 -487
- package/dist/handlers/search.js +15 -326
- package/dist/handlers/session.js +28 -615
- package/dist/handlers/share.js +8 -184
- package/dist/handlers/shims.js +1 -464
- package/dist/handlers/skill.js +67 -449
- package/dist/handlers/survivors.js +1 -120
- package/dist/handlers/symbols.js +8 -109
- package/dist/handlers/syncops.js +4 -302
- package/dist/handlers/types.js +1 -27
- package/dist/harvest.js +5 -191
- package/dist/hours.js +7 -156
- package/dist/http-auth.js +3 -321
- package/dist/http-fast.js +21 -1137
- package/dist/icons.js +1 -47
- package/dist/index.js +2 -924
- package/dist/indexer.js +4 -145
- package/dist/intelligence.js +31 -261
- package/dist/internal-dispatch.js +3 -212
- package/dist/keyset.js +1 -110
- package/dist/knowledge-graph.js +12 -176
- package/dist/license.d.ts +11 -0
- package/dist/license.d.ts.map +1 -1
- package/dist/license.js +2 -414
- package/dist/license.js.map +1 -1
- package/dist/logger.js +2 -199
- package/dist/maintenance.js +2 -148
- package/dist/mcp-client.js +6 -262
- package/dist/memory-artifacts.js +30 -449
- package/dist/migrate-prompt.js +2 -124
- package/dist/migrations.js +40 -655
- package/dist/performance.js +1 -228
- package/dist/presence.js +11 -140
- package/dist/priority-embed.js +5 -164
- package/dist/providers/embedding-provider.js +1 -196
- package/dist/readonly-gate.js +1 -29
- package/dist/rehydration.js +9 -157
- package/dist/reindex.js +1 -88
- package/dist/render-target.js +21 -514
- package/dist/render.js +4 -280
- package/dist/repl-guard.js +1 -173
- package/dist/replication-daemon-entrypoint.js +1 -31
- package/dist/replication-daemon.js +2 -262
- package/dist/resilience.js +1 -591
- package/dist/reverse-bridge.js +5 -360
- package/dist/security.js +1 -244
- package/dist/session-seen.js +3 -51
- package/dist/setup.js +1 -260
- package/dist/skill-author.js +5 -168
- package/dist/spec-kit.js +1 -191
- package/dist/sqlite-busy.js +1 -154
- package/dist/statusline.js +11 -315
- package/dist/sub-agent.js +13 -262
- package/dist/summarizer.js +13 -139
- package/dist/symbols.js +7 -283
- package/dist/sync.js +5 -359
- package/dist/tasks-dispatch.js +1 -84
- package/dist/tasks.js +1 -282
- package/dist/token-budget.js +1 -143
- package/dist/tool-analytics.js +7 -129
- package/dist/tool-annotations.js +1 -365
- package/dist/tool-manifest-v2.json +1 -1
- package/dist/tool-manifest.json +1 -1
- package/dist/tool-profiles.js +1 -75
- package/dist/trace-harvest.js +6 -244
- package/dist/types.js +1 -30
- package/dist/ui-dashboard.js +41 -50
- package/dist/ulid.js +1 -81
- package/dist/validate.js +1 -129
- package/dist/vault.js +1 -534
- package/dist/vectors.js +3 -184
- package/dist/version-check.js +4 -136
- package/dist/visibility.js +19 -155
- package/dist/wyrm-cli.js +98 -2451
- package/dist/wyrm-cli.js.map +1 -1
- package/dist/wyrm-guard.js +14 -424
- package/dist/wyrm-loop.js +3 -150
- package/dist/wyrm-manifest.json +1 -1
- package/dist/wyrm-statusline-daemon.js +1 -11
- package/dist/wyrm-statusline.js +4 -56
- package/dist/wyrm-ui.js +9 -77
- package/package.json +4 -2
package/dist/federation.js
CHANGED
|
@@ -1,257 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* shared (per-row `is_shared=1` flag — set when the operator marks a
|
|
7
|
-
* truth/quest/artifact as team-visible).
|
|
8
|
-
*
|
|
9
|
-
* Sync surface:
|
|
10
|
-
* POST /sync/push pushes is_shared=1 local rows changed since cursor
|
|
11
|
-
* GET /sync/pull pulls remote rows changed since cursor
|
|
12
|
-
*
|
|
13
|
-
* Conflict policy: if a remote push lands on top of a local edit whose
|
|
14
|
-
* `updated_at` is newer, we DO NOT silently overwrite. Instead we record
|
|
15
|
-
* the conflict in `sync_conflicts` and mint a quest for the operator to
|
|
16
|
-
* resolve. Last-write-wins is the failure mode of every team sync tool;
|
|
17
|
-
* Wyrm refuses to silently lose work.
|
|
18
|
-
*
|
|
19
|
-
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
20
|
-
* @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
|
|
21
|
-
*/
|
|
22
|
-
const KIND_TO_TABLE = {
|
|
23
|
-
session: { table: 'sessions', ts: 'created_at' },
|
|
24
|
-
quest: { table: 'quests', ts: 'created_at' },
|
|
25
|
-
truth: { table: 'ground_truths', ts: 'updated_at' },
|
|
26
|
-
artifact: { table: 'memory_artifacts', ts: 'last_accessed_at' },
|
|
27
|
-
edge: { table: 'decision_edges', ts: 'created_at' },
|
|
28
|
-
};
|
|
29
|
-
export class Federation {
|
|
30
|
-
db;
|
|
31
|
-
constructor(db) {
|
|
32
|
-
this.db = db;
|
|
33
|
-
}
|
|
34
|
-
/** Mark a row as shared (will be picked up on next push). */
|
|
35
|
-
share(kind, id) {
|
|
36
|
-
const t = KIND_TO_TABLE[kind];
|
|
37
|
-
if (!t)
|
|
38
|
-
return false;
|
|
39
|
-
// [grove isolation] Refuse to share a row that belongs to a PRIVATE grove.
|
|
40
|
-
// A private grove (projects.sync_policy='private') must never federate to a
|
|
41
|
-
// team Wyrm, so we block the is_shared flag at the source (defense in depth
|
|
42
|
-
// alongside the collectForPush grove gate).
|
|
43
|
-
if (this.isPrivateGroveRow(t.table, id))
|
|
44
|
-
return false;
|
|
45
|
-
const info = this.db.prepare(`UPDATE ${t.table} SET is_shared = 1 WHERE id = ?`).run(id);
|
|
46
|
-
return info.changes > 0;
|
|
47
|
-
}
|
|
48
|
-
/** True if the row's project grove has sync_policy='private' (never federates). */
|
|
49
|
-
isPrivateGroveRow(table, id) {
|
|
50
|
-
try {
|
|
51
|
-
const row = this.db.prepare(`SELECT (SELECT sync_policy FROM projects WHERE id = t.project_id) AS policy
|
|
52
|
-
FROM ${table} t WHERE t.id = ?`).get(id);
|
|
53
|
-
// No row, or a project-less / unknown-project row: not grove-private here
|
|
54
|
-
// (governed by is_shared alone). An explicit 'private' policy blocks it.
|
|
55
|
-
if (!row || row.policy == null)
|
|
56
|
-
return false;
|
|
57
|
-
return row.policy === 'private';
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// projects.sync_policy not present yet (pre-migration) → grove feature off.
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
/** Whether the grove-policy column exists (feature gate, pre-migration safe). */
|
|
65
|
-
projectsHasSyncPolicy() {
|
|
66
|
-
try {
|
|
67
|
-
return this.db.prepare(`PRAGMA table_info(projects)`).all()
|
|
68
|
-
.some((c) => c.name === 'sync_policy');
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
/** Unshare (revoke from team visibility). Local row stays. */
|
|
75
|
-
unshare(kind, id) {
|
|
76
|
-
const t = KIND_TO_TABLE[kind];
|
|
77
|
-
if (!t)
|
|
78
|
-
return false;
|
|
79
|
-
const info = this.db.prepare(`UPDATE ${t.table} SET is_shared = 0 WHERE id = ?`).run(id);
|
|
80
|
-
return info.changes > 0;
|
|
81
|
-
}
|
|
82
|
-
/** Collect rows to push: is_shared=1 AND ts > since_cursor. */
|
|
83
|
-
collectForPush(since_cursor, limit = 500) {
|
|
84
|
-
const cursor = since_cursor ?? '1970-01-01';
|
|
85
|
-
const out = [];
|
|
86
|
-
// [grove isolation] Only federate rows whose grove opts into team sharing
|
|
87
|
-
// (projects.sync_policy='team'). Project-less rows fall back to is_shared.
|
|
88
|
-
const groveGate = this.projectsHasSyncPolicy()
|
|
89
|
-
? `AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy = 'team'))`
|
|
90
|
-
: ``;
|
|
91
|
-
for (const [kindStr, t] of Object.entries(KIND_TO_TABLE)) {
|
|
92
|
-
if (out.length >= limit)
|
|
93
|
-
break;
|
|
94
|
-
const kind = kindStr;
|
|
95
|
-
const rows = this.db.prepare(`SELECT * FROM ${t.table}
|
|
96
|
-
WHERE is_shared = 1 ${groveGate} AND ${t.ts} > ?
|
|
97
|
-
ORDER BY ${t.ts} ASC
|
|
98
|
-
LIMIT ?`).all(cursor, Math.max(1, limit - out.length));
|
|
99
|
-
for (const r of rows) {
|
|
100
|
-
out.push({
|
|
101
|
-
kind,
|
|
102
|
-
id: r.id,
|
|
103
|
-
project_id: (r.project_id ?? null),
|
|
104
|
-
payload: r,
|
|
105
|
-
updated_at: r[t.ts] || new Date().toISOString(),
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return out;
|
|
110
|
-
}
|
|
111
|
-
/** Apply a remote row locally. Returns:
|
|
112
|
-
* { kind: 'inserted' } — fresh row, no conflict
|
|
113
|
-
* { kind: 'overwritten' } — local row was older, remote wins
|
|
114
|
-
* { kind: 'conflict', conflict_id } — local row newer, conflict recorded
|
|
115
|
-
* Idempotent — applying the same payload twice is a no-op.
|
|
116
|
-
*
|
|
117
|
-
* Conflict policy: if local updated_at > remote updated_at, the remote
|
|
118
|
-
* is REJECTED into sync_conflicts; a quest is minted. The local row stays.
|
|
119
|
-
*/
|
|
120
|
-
applyRemote(row, remoteOrigin) {
|
|
121
|
-
const t = KIND_TO_TABLE[row.kind];
|
|
122
|
-
if (!t)
|
|
123
|
-
return { kind: 'skipped' };
|
|
124
|
-
const localTs = this.db.prepare(`SELECT ${t.ts} as ts FROM ${t.table} WHERE id = ?`).get(row.id);
|
|
125
|
-
if (!localTs) {
|
|
126
|
-
// Fresh insert. Build columns from payload, skipping id (auto-incremented)
|
|
127
|
-
// — but we want to preserve the same id so future syncs are stable.
|
|
128
|
-
// SQLite allows explicit id insert.
|
|
129
|
-
const cols = Object.keys(row.payload).filter(c => c !== 'rowid');
|
|
130
|
-
const placeholders = cols.map(() => '?').join(',');
|
|
131
|
-
const values = cols.map(c => row.payload[c]);
|
|
132
|
-
try {
|
|
133
|
-
this.db.prepare(`INSERT OR IGNORE INTO ${t.table} (${cols.join(',')}) VALUES (${placeholders})`).run(...values);
|
|
134
|
-
return { kind: 'inserted' };
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return { kind: 'skipped' };
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Existing row — compare timestamps.
|
|
141
|
-
const localNewer = localTs.ts > row.updated_at;
|
|
142
|
-
if (localNewer) {
|
|
143
|
-
// Conflict — record it and mint a quest if this row has a project_id.
|
|
144
|
-
const conflictInfo = this.db.prepare(`
|
|
1
|
+
const d={session:{table:"sessions",ts:"created_at"},quest:{table:"quests",ts:"created_at"},truth:{table:"ground_truths",ts:"updated_at"},artifact:{table:"memory_artifacts",ts:"last_accessed_at"},edge:{table:"decision_edges",ts:"created_at"}};class E{db;constructor(t){this.db=t}share(t,r){const e=d[t];return!e||this.isPrivateGroveRow(e.table,r)?!1:this.db.prepare(`UPDATE ${e.table} SET is_shared = 1 WHERE id = ?`).run(r).changes>0}isPrivateGroveRow(t,r){try{const e=this.db.prepare(`SELECT (SELECT sync_policy FROM projects WHERE id = t.project_id) AS policy
|
|
2
|
+
FROM ${t} t WHERE t.id = ?`).get(r);return!e||e.policy==null?!1:e.policy==="private"}catch{return!1}}projectsHasSyncPolicy(){try{return this.db.prepare("PRAGMA table_info(projects)").all().some(t=>t.name==="sync_policy")}catch{return!1}}unshare(t,r){const e=d[t];return e?this.db.prepare(`UPDATE ${e.table} SET is_shared = 0 WHERE id = ?`).run(r).changes>0:!1}collectForPush(t,r=500){const e=t??"1970-01-01",s=[],o=this.projectsHasSyncPolicy()?"AND (project_id IS NULL OR project_id IN (SELECT id FROM projects WHERE sync_policy = 'team'))":"";for(const[a,c]of Object.entries(d)){if(s.length>=r)break;const l=a,n=this.db.prepare(`SELECT * FROM ${c.table}
|
|
3
|
+
WHERE is_shared = 1 ${o} AND ${c.ts} > ?
|
|
4
|
+
ORDER BY ${c.ts} ASC
|
|
5
|
+
LIMIT ?`).all(e,Math.max(1,r-s.length));for(const i of n)s.push({kind:l,id:i.id,project_id:i.project_id??null,payload:i,updated_at:i[c.ts]||new Date().toISOString()})}return s}applyRemote(t,r){const e=d[t.kind];if(!e)return{kind:"skipped"};const s=this.db.prepare(`SELECT ${e.ts} as ts FROM ${e.table} WHERE id = ?`).get(t.id);if(!s){const n=Object.keys(t.payload).filter(p=>p!=="rowid"),i=n.map(()=>"?").join(","),u=n.map(p=>t.payload[p]);try{return this.db.prepare(`INSERT OR IGNORE INTO ${e.table} (${n.join(",")}) VALUES (${i})`).run(...u),{kind:"inserted"}}catch{return{kind:"skipped"}}}if(s.ts>t.updated_at){const i=this.db.prepare(`
|
|
145
6
|
INSERT INTO sync_conflicts
|
|
146
7
|
(local_kind, local_id, remote_payload, remote_origin)
|
|
147
8
|
VALUES (?, ?, ?, ?)
|
|
148
|
-
`).run(
|
|
149
|
-
const conflict_id = conflictInfo.lastInsertRowid;
|
|
150
|
-
// Best-effort: mint a quest so the operator sees it on next session
|
|
151
|
-
if (row.project_id != null) {
|
|
152
|
-
try {
|
|
153
|
-
this.db.prepare(`
|
|
9
|
+
`).run(t.kind,t.id,JSON.stringify(t.payload),r).lastInsertRowid;if(t.project_id!=null)try{this.db.prepare(`
|
|
154
10
|
INSERT INTO quests (project_id, title, description, priority, status, tags)
|
|
155
11
|
VALUES (?, ?, ?, 'high', 'pending', 'sync-conflict')
|
|
156
|
-
`).run(
|
|
157
|
-
`See sync_conflicts row #${conflict_id} for the remote payload. ` +
|
|
158
|
-
`Resolve with wyrm_sync_resolve (kept_local | kept_remote | merged).`);
|
|
159
|
-
}
|
|
160
|
-
catch { /* best-effort */ }
|
|
161
|
-
}
|
|
162
|
-
return { kind: 'conflict', conflict_id };
|
|
163
|
-
}
|
|
164
|
-
// Local is older — accept remote overwrite.
|
|
165
|
-
const cols = Object.keys(row.payload).filter(c => c !== 'id');
|
|
166
|
-
const setClause = cols.map(c => `${c} = ?`).join(', ');
|
|
167
|
-
const values = cols.map(c => row.payload[c]);
|
|
168
|
-
try {
|
|
169
|
-
this.db.prepare(`UPDATE ${t.table} SET ${setClause} WHERE id = ?`).run(...values, row.id);
|
|
170
|
-
return { kind: 'overwritten' };
|
|
171
|
-
}
|
|
172
|
-
catch {
|
|
173
|
-
return { kind: 'skipped' };
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
/** Bulk push — record one sync_log row, return summary. Caller does HTTP. */
|
|
177
|
-
recordPush(rows, remoteOrigin, duration_ms, since_cursor) {
|
|
178
|
-
const newCursor = new Date().toISOString();
|
|
179
|
-
this.db.prepare(`
|
|
12
|
+
`).run(t.project_id,`[sync] Resolve conflict on ${t.kind} #${t.id}`,`A remote ${r} pushed a ${t.kind} that conflicts with a newer local edit. See sync_conflicts row #${i} for the remote payload. Resolve with wyrm_sync_resolve (kept_local | kept_remote | merged).`)}catch{}return{kind:"conflict",conflict_id:i}}const a=Object.keys(t.payload).filter(n=>n!=="id"),c=a.map(n=>`${n} = ?`).join(", "),l=a.map(n=>t.payload[n]);try{return this.db.prepare(`UPDATE ${e.table} SET ${c} WHERE id = ?`).run(...l,t.id),{kind:"overwritten"}}catch{return{kind:"skipped"}}}recordPush(t,r,e,s){const o=new Date().toISOString();return this.db.prepare(`
|
|
180
13
|
INSERT INTO sync_log
|
|
181
14
|
(direction, remote_origin, kind, count, since_cursor, new_cursor, duration_ms)
|
|
182
15
|
VALUES ('push', ?, 'mixed', ?, ?, ?, ?)
|
|
183
|
-
`).run(
|
|
184
|
-
return {
|
|
185
|
-
pushed: rows.length,
|
|
186
|
-
conflicts: 0,
|
|
187
|
-
new_cursor: newCursor,
|
|
188
|
-
conflict_ids: [],
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
/** Bulk pull — apply each remote row, record sync_log, return summary. */
|
|
192
|
-
applyPull(rows, remoteOrigin, duration_ms, since_cursor) {
|
|
193
|
-
const byKind = {};
|
|
194
|
-
let pulled = 0;
|
|
195
|
-
for (const row of rows) {
|
|
196
|
-
const result = this.applyRemote(row, remoteOrigin);
|
|
197
|
-
if (result.kind === 'inserted' || result.kind === 'overwritten') {
|
|
198
|
-
pulled++;
|
|
199
|
-
byKind[row.kind] = (byKind[row.kind] ?? 0) + 1;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
const newCursor = new Date().toISOString();
|
|
203
|
-
this.db.prepare(`
|
|
16
|
+
`).run(r,t.length,s??null,o,e),{pushed:t.length,conflicts:0,new_cursor:o,conflict_ids:[]}}applyPull(t,r,e,s){const o={};let a=0;for(const l of t){const n=this.applyRemote(l,r);(n.kind==="inserted"||n.kind==="overwritten")&&(a++,o[l.kind]=(o[l.kind]??0)+1)}const c=new Date().toISOString();return this.db.prepare(`
|
|
204
17
|
INSERT INTO sync_log
|
|
205
18
|
(direction, remote_origin, kind, count, since_cursor, new_cursor, duration_ms)
|
|
206
19
|
VALUES ('pull', ?, 'mixed', ?, ?, ?, ?)
|
|
207
|
-
`).run(
|
|
208
|
-
return { pulled, new_cursor: newCursor, by_kind: byKind };
|
|
209
|
-
}
|
|
210
|
-
/** Get the latest cursor from a previous sync. */
|
|
211
|
-
latestCursor(direction, remoteOrigin) {
|
|
212
|
-
const row = this.db.prepare(`
|
|
20
|
+
`).run(r,a,s??null,c,e),{pulled:a,new_cursor:c,by_kind:o}}latestCursor(t,r){return this.db.prepare(`
|
|
213
21
|
SELECT new_cursor FROM sync_log
|
|
214
22
|
WHERE direction = ? AND remote_origin = ?
|
|
215
23
|
ORDER BY id DESC LIMIT 1
|
|
216
|
-
`).get(
|
|
217
|
-
return row?.new_cursor ?? null;
|
|
218
|
-
}
|
|
219
|
-
/** List unresolved sync conflicts. */
|
|
220
|
-
unresolvedConflicts(limit = 50) {
|
|
221
|
-
return this.db.prepare(`
|
|
24
|
+
`).get(t,r)?.new_cursor??null}unresolvedConflicts(t=50){return this.db.prepare(`
|
|
222
25
|
SELECT id, local_kind, local_id, remote_origin, detected_at, quest_id
|
|
223
26
|
FROM sync_conflicts
|
|
224
27
|
WHERE resolved_at IS NULL
|
|
225
28
|
ORDER BY detected_at DESC LIMIT ?
|
|
226
|
-
`).all(
|
|
227
|
-
}
|
|
228
|
-
/** Resolve a conflict. mode: 'kept_local' | 'kept_remote' | 'merged'. */
|
|
229
|
-
resolveConflict(conflict_id, mode, merged_payload) {
|
|
230
|
-
const conflict = this.db.prepare('SELECT * FROM sync_conflicts WHERE id = ? AND resolved_at IS NULL').get(conflict_id);
|
|
231
|
-
if (!conflict)
|
|
232
|
-
return false;
|
|
233
|
-
if (mode === 'kept_remote' || mode === 'merged') {
|
|
234
|
-
const t = KIND_TO_TABLE[conflict.local_kind];
|
|
235
|
-
if (t) {
|
|
236
|
-
const payload = mode === 'merged' && merged_payload
|
|
237
|
-
? merged_payload
|
|
238
|
-
: JSON.parse(conflict.remote_payload);
|
|
239
|
-
const cols = Object.keys(payload).filter(c => c !== 'id');
|
|
240
|
-
const setClause = cols.map(c => `${c} = ?`).join(', ');
|
|
241
|
-
const values = cols.map(c => payload[c]);
|
|
242
|
-
try {
|
|
243
|
-
this.db.prepare(`UPDATE ${t.table} SET ${setClause} WHERE id = ?`).run(...values, conflict.local_id);
|
|
244
|
-
}
|
|
245
|
-
catch { /* best-effort */ }
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
// 'kept_local' = no row mutation needed.
|
|
249
|
-
this.db.prepare(`
|
|
29
|
+
`).all(t)}resolveConflict(t,r,e){const s=this.db.prepare("SELECT * FROM sync_conflicts WHERE id = ? AND resolved_at IS NULL").get(t);if(!s)return!1;if(r==="kept_remote"||r==="merged"){const o=d[s.local_kind];if(o){const a=r==="merged"&&e?e:JSON.parse(s.remote_payload),c=Object.keys(a).filter(i=>i!=="id"),l=c.map(i=>`${i} = ?`).join(", "),n=c.map(i=>a[i]);try{this.db.prepare(`UPDATE ${o.table} SET ${l} WHERE id = ?`).run(...n,s.local_id)}catch{}}}return this.db.prepare(`
|
|
250
30
|
UPDATE sync_conflicts
|
|
251
31
|
SET resolved_at = datetime('now'), resolution = ?
|
|
252
32
|
WHERE id = ?
|
|
253
|
-
`).run(
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
//# sourceMappingURL=federation.js.map
|
|
33
|
+
`).run(r,t),!0}}export{E as Federation};
|
package/dist/goals.js
CHANGED
|
@@ -1,97 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
* Goals — persistent objectives Wyrm pursues across sessions.
|
|
3
|
-
*
|
|
4
|
-
* A goal is a long-lived row. Each `pursue` invocation runs one OODA
|
|
5
|
-
* iteration (Observe → Orient → Decide → Act), appends a row to
|
|
6
|
-
* `goal_iterations`, and updates the goal's `last_pursued_at` +
|
|
7
|
-
* `iterations_count`. The agent loop (`wyrm-loop` daemon) picks the
|
|
8
|
-
* highest-priority active goal each tick.
|
|
9
|
-
*
|
|
10
|
-
* Goals carry `max_iterations` to prevent infinite spinning on
|
|
11
|
-
* unresolvable objectives. `success_criteria` is the operator-defined
|
|
12
|
-
* "done when X" — the LLM checks against it each iteration.
|
|
13
|
-
*
|
|
14
|
-
* @copyright 2026 Ghost Protocol (Pvt) Ltd.
|
|
15
|
-
* @license AGPL-3.0-or-later — dual-licensed; commercial terms: ghosts.lk@proton.me. See LICENSE.
|
|
16
|
-
*/
|
|
17
|
-
export class Goals {
|
|
18
|
-
db;
|
|
19
|
-
constructor(db) {
|
|
20
|
-
this.db = db;
|
|
21
|
-
}
|
|
22
|
-
set(input) {
|
|
23
|
-
const info = this.db.prepare(`
|
|
1
|
+
class n{db;constructor(t){this.db=t}set(t){const e=this.db.prepare(`
|
|
24
2
|
INSERT INTO goals
|
|
25
3
|
(project_id, title, description, success_criteria, deadline,
|
|
26
4
|
priority, max_iterations)
|
|
27
5
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
28
|
-
`).run(
|
|
29
|
-
return this.get(info.lastInsertRowid);
|
|
30
|
-
}
|
|
31
|
-
get(id) {
|
|
32
|
-
return this.db.prepare('SELECT * FROM goals WHERE id = ?').get(id) ?? null;
|
|
33
|
-
}
|
|
34
|
-
list(opts) {
|
|
35
|
-
const where = [];
|
|
36
|
-
const params = [];
|
|
37
|
-
if (opts?.status) {
|
|
38
|
-
where.push('status = ?');
|
|
39
|
-
params.push(opts.status);
|
|
40
|
-
}
|
|
41
|
-
if (opts?.project_id != null) {
|
|
42
|
-
where.push('project_id = ?');
|
|
43
|
-
params.push(opts.project_id);
|
|
44
|
-
}
|
|
45
|
-
const sql = `
|
|
6
|
+
`).run(t.project_id??null,t.title,t.description??null,t.success_criteria??null,t.deadline??null,t.priority??"medium",Math.max(1,Math.min(t.max_iterations??20,200)));return this.get(e.lastInsertRowid)}get(t){return this.db.prepare("SELECT * FROM goals WHERE id = ?").get(t)??null}list(t){const e=[],a=[];t?.status&&(e.push("status = ?"),a.push(t.status)),t?.project_id!=null&&(e.push("project_id = ?"),a.push(t.project_id));const s=`
|
|
46
7
|
SELECT * FROM goals
|
|
47
|
-
${
|
|
8
|
+
${e.length?"WHERE "+e.join(" AND "):""}
|
|
48
9
|
ORDER BY
|
|
49
10
|
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1
|
|
50
11
|
WHEN 'medium' THEN 2 ELSE 3 END,
|
|
51
12
|
COALESCE(last_pursued_at, '1970-01-01') ASC,
|
|
52
13
|
created_at ASC
|
|
53
14
|
LIMIT ?
|
|
54
|
-
`;
|
|
55
|
-
params.push(opts?.limit ?? 50);
|
|
56
|
-
return this.db.prepare(sql).all(...params);
|
|
57
|
-
}
|
|
58
|
-
/** Pick the next goal to pursue: highest priority, least-recently
|
|
59
|
-
* pursued, only active goals that haven't hit iteration cap. Returns
|
|
60
|
-
* null when there's nothing to do. */
|
|
61
|
-
nextToPursue(projectId) {
|
|
62
|
-
const params = [];
|
|
63
|
-
let where = `status = 'active' AND iterations_count < max_iterations`;
|
|
64
|
-
if (projectId != null) {
|
|
65
|
-
where += ` AND project_id = ?`;
|
|
66
|
-
params.push(projectId);
|
|
67
|
-
}
|
|
68
|
-
const sql = `
|
|
15
|
+
`;return a.push(t?.limit??50),this.db.prepare(s).all(...a)}nextToPursue(t){const e=[];let a="status = 'active' AND iterations_count < max_iterations";t!=null&&(a+=" AND project_id = ?",e.push(t));const s=`
|
|
69
16
|
SELECT * FROM goals
|
|
70
|
-
WHERE ${
|
|
17
|
+
WHERE ${a}
|
|
71
18
|
ORDER BY
|
|
72
19
|
CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1
|
|
73
20
|
WHEN 'medium' THEN 2 ELSE 3 END,
|
|
74
21
|
COALESCE(last_pursued_at, '1970-01-01') ASC
|
|
75
22
|
LIMIT 1
|
|
76
|
-
`;
|
|
77
|
-
return this.db.prepare(sql).get(...params) ?? null;
|
|
78
|
-
}
|
|
79
|
-
/** Record one OODA iteration result and bump the goal's counters. */
|
|
80
|
-
recordIteration(goal_id, input) {
|
|
81
|
-
const goal = this.get(goal_id);
|
|
82
|
-
if (!goal)
|
|
83
|
-
throw new Error(`Goal ${goal_id} not found`);
|
|
84
|
-
const iterationNum = goal.iterations_count + 1;
|
|
85
|
-
const info = this.db.prepare(`
|
|
23
|
+
`;return this.db.prepare(s).get(...e)??null}recordIteration(t,e){const a=this.get(t);if(!a)throw new Error(`Goal ${t} not found`);const s=a.iterations_count+1,r=this.db.prepare(`
|
|
86
24
|
INSERT INTO goal_iterations
|
|
87
25
|
(goal_id, iteration_num, observe_summary, orient_summary,
|
|
88
26
|
decided_action, action_result, outcome, notes,
|
|
89
27
|
latency_ms, tokens_in, tokens_out, model)
|
|
90
28
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
91
|
-
`).run(
|
|
92
|
-
// Update goal counters + last_outcome. If outcome=='done', mark completed.
|
|
93
|
-
const shouldComplete = input.outcome === 'done';
|
|
94
|
-
this.db.prepare(`
|
|
29
|
+
`).run(t,s,e.observe_summary??null,e.orient_summary??null,e.decided_action??null,e.action_result??null,e.outcome,e.notes??null,Math.max(0,Math.round(e.latency_ms)),e.tokens_in??null,e.tokens_out??null,e.model??null),o=e.outcome==="done";return this.db.prepare(`
|
|
95
30
|
UPDATE goals
|
|
96
31
|
SET iterations_count = iterations_count + 1,
|
|
97
32
|
last_pursued_at = datetime('now'),
|
|
@@ -100,47 +35,24 @@ export class Goals {
|
|
|
100
35
|
status = CASE WHEN ? = 1 THEN 'completed' ELSE status END,
|
|
101
36
|
completed_at = CASE WHEN ? = 1 THEN datetime('now') ELSE completed_at END
|
|
102
37
|
WHERE id = ?
|
|
103
|
-
`).run(
|
|
104
|
-
return (this.db.prepare('SELECT * FROM goal_iterations WHERE id = ?').get(info.lastInsertRowid));
|
|
105
|
-
}
|
|
106
|
-
iterationsFor(goal_id, limit = 50) {
|
|
107
|
-
return this.db.prepare(`
|
|
38
|
+
`).run(e.outcome,o?1:0,o?1:0,t),this.db.prepare("SELECT * FROM goal_iterations WHERE id = ?").get(r.lastInsertRowid)}iterationsFor(t,e=50){return this.db.prepare(`
|
|
108
39
|
SELECT * FROM goal_iterations
|
|
109
40
|
WHERE goal_id = ? ORDER BY iteration_num DESC LIMIT ?
|
|
110
|
-
`).all(
|
|
111
|
-
}
|
|
112
|
-
complete(id, note) {
|
|
113
|
-
this.db.prepare(`
|
|
41
|
+
`).all(t,e)}complete(t,e){return this.db.prepare(`
|
|
114
42
|
UPDATE goals
|
|
115
43
|
SET status = 'completed',
|
|
116
44
|
completed_at = datetime('now'),
|
|
117
45
|
updated_at = datetime('now'),
|
|
118
46
|
last_outcome = COALESCE(?, last_outcome)
|
|
119
47
|
WHERE id = ?
|
|
120
|
-
`).run(
|
|
121
|
-
return this.get(id);
|
|
122
|
-
}
|
|
123
|
-
pause(id) {
|
|
124
|
-
this.db.prepare(`
|
|
48
|
+
`).run(e??null,t),this.get(t)}pause(t){return this.db.prepare(`
|
|
125
49
|
UPDATE goals SET status = 'paused', updated_at = datetime('now') WHERE id = ?
|
|
126
|
-
`).run(
|
|
127
|
-
return this.get(id);
|
|
128
|
-
}
|
|
129
|
-
resume(id) {
|
|
130
|
-
this.db.prepare(`
|
|
50
|
+
`).run(t),this.get(t)}resume(t){return this.db.prepare(`
|
|
131
51
|
UPDATE goals SET status = 'active', updated_at = datetime('now') WHERE id = ?
|
|
132
|
-
`).run(
|
|
133
|
-
return this.get(id);
|
|
134
|
-
}
|
|
135
|
-
abandon(id, reason) {
|
|
136
|
-
this.db.prepare(`
|
|
52
|
+
`).run(t),this.get(t)}abandon(t,e){return this.db.prepare(`
|
|
137
53
|
UPDATE goals
|
|
138
54
|
SET status = 'abandoned',
|
|
139
55
|
updated_at = datetime('now'),
|
|
140
56
|
last_outcome = COALESCE(?, last_outcome)
|
|
141
57
|
WHERE id = ?
|
|
142
|
-
`).run(
|
|
143
|
-
return this.get(id);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
//# sourceMappingURL=goals.js.map
|
|
58
|
+
`).run(e??null,t),this.get(t)}}export{n as Goals};
|