@xingular/harness 0.1.0 → 0.1.1
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/dist/cli.js +799 -335
- package/package.json +1 -1
- package/src/accounts/env.test.ts +95 -0
- package/src/accounts/env.ts +176 -42
- package/src/capabilities/api.ts +3 -3
- package/src/harness.ts +12 -2
- package/src/index.ts +2 -1
- package/src/mcp/connections.ts +176 -3
- package/src/mcp/scopeMigration.ts +204 -0
- package/src/mcp/service.ts +18 -36
- package/src/mcp/types.ts +3 -0
- package/src/memory/api.ts +5 -3
- package/src/memory/dispatch.ts +2 -2
- package/src/memory/maintenance.test.ts +1 -1
- package/src/plugins/attachments.ts +13 -0
- package/src/plugins/catalogue.ts +3 -2
- package/src/plugins/manifest.ts +34 -2
- package/src/plugins/plugins.ts +8 -3
- package/src/plugins/types.ts +19 -0
- package/src/prompt/compile.ts +4 -4
- package/src/server/methods.ts +6 -0
- package/src/sessions/sessions.ts +10 -10
- package/src/skills/registry.ts +16 -3
- package/src/tools/memoryTools.ts +2 -2
package/dist/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ function log(...parts) {
|
|
|
24
24
|
|
|
25
25
|
// src/harness.ts
|
|
26
26
|
import { mkdirSync as mkdirSync14 } from "node:fs";
|
|
27
|
-
import { join as
|
|
27
|
+
import { join as join39 } from "node:path";
|
|
28
28
|
|
|
29
29
|
// src/accounts/accounts.ts
|
|
30
30
|
import { chmodSync as chmodSync3, rmSync as rmSync2 } from "node:fs";
|
|
@@ -133,9 +133,9 @@ function newId() {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
// src/accounts/env.ts
|
|
136
|
-
import { chmodSync, lstatSync, mkdirSync as mkdirSync2, readlinkSync, renameSync as renameSync2, symlinkSync, unlinkSync } from "node:fs";
|
|
136
|
+
import { chmodSync, copyFileSync, linkSync, lstatSync, mkdirSync as mkdirSync2, readlinkSync, renameSync as renameSync2, rmdirSync, statSync, symlinkSync, unlinkSync, utimesSync } from "node:fs";
|
|
137
137
|
import { homedir } from "node:os";
|
|
138
|
-
import { basename as basename2, dirname as dirname2, join as join2, sep as sep2 } from "node:path";
|
|
138
|
+
import { basename as basename2, dirname as dirname2, join as join2, resolve, sep as sep2 } from "node:path";
|
|
139
139
|
var DIR_VARS = ["CODEX_HOME", "CLAUDE_CONFIG_DIR", "PI_CODING_AGENT_DIR"];
|
|
140
140
|
function isCredentialVar(name) {
|
|
141
141
|
const upper = name.toUpperCase();
|
|
@@ -197,43 +197,124 @@ var modified = (path3) => {
|
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
199
|
function backUp(path3) {
|
|
200
|
-
|
|
200
|
+
const backup2 = join2(dirname2(path3), `${basename2(path3)}.agentx-backup-${Math.floor(Date.now() / 1e3)}`);
|
|
201
|
+
renameSync2(path3, backup2);
|
|
202
|
+
return backup2;
|
|
201
203
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
204
|
+
var nodeLinker = {
|
|
205
|
+
symlink: (target, path3, isDir2) => symlinkSync(resolve(target), path3, isDir2 ? "junction" : "file"),
|
|
206
|
+
hardLink: (target, path3) => linkSync(target, path3)
|
|
207
|
+
};
|
|
208
|
+
var codeOf = (error) => error.code ?? String(error);
|
|
209
|
+
var PRIVILEGE_CODES = ["EPERM", "EACCES"];
|
|
210
|
+
function createLink(target, path3, isDir2, linker) {
|
|
211
|
+
try {
|
|
212
|
+
linker.symlink(target, path3, isDir2);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (isDir2 || !PRIVILEGE_CODES.includes(codeOf(error))) throw error;
|
|
215
|
+
linker.hardLink(target, path3);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
var isLink = (path3) => {
|
|
219
|
+
try {
|
|
220
|
+
return lstatSync(path3).isSymbolicLink();
|
|
221
|
+
} catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
var LONG_PREFIX = /^\\\\\?\\/;
|
|
226
|
+
function samePath(a, b) {
|
|
227
|
+
const normal = (path3) => {
|
|
228
|
+
const full = resolve(path3.replace(LONG_PREFIX, ""));
|
|
229
|
+
return process.platform === "win32" ? full.toLowerCase() : full;
|
|
230
|
+
};
|
|
231
|
+
return normal(a) === normal(b);
|
|
232
|
+
}
|
|
233
|
+
function sameFile(a, b) {
|
|
234
|
+
try {
|
|
235
|
+
const left = lstatSync(a);
|
|
236
|
+
const right = lstatSync(b);
|
|
237
|
+
return left.ino !== 0 && left.dev === right.dev && left.ino === right.ino;
|
|
238
|
+
} catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function isLinkTo(path3, target, isDir2) {
|
|
243
|
+
try {
|
|
244
|
+
const meta = lstatSync(path3);
|
|
245
|
+
if (meta.isSymbolicLink()) return samePath(readlinkSync(path3), target);
|
|
246
|
+
return !isDir2 && meta.isFile() && sameFile(path3, target);
|
|
247
|
+
} catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function removeLink(path3, isDir2) {
|
|
252
|
+
try {
|
|
253
|
+
unlinkSync(path3);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (!isDir2) throw error;
|
|
256
|
+
rmdirSync(path3);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function keepCopy(target, path3, isDir2) {
|
|
260
|
+
if (isDir2 || present(path3) || !present(target)) return;
|
|
261
|
+
try {
|
|
262
|
+
copyFileSync(target, path3);
|
|
263
|
+
const { atime, mtime } = statSync(target);
|
|
264
|
+
utimesSync(path3, atime, mtime);
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function linkOne(name, target, path3, isDir2, linker) {
|
|
269
|
+
if (isLinkTo(path3, target, isDir2)) {
|
|
270
|
+
if (isDir2) mkdirSync2(target, { recursive: true });
|
|
271
|
+
return { name, state: "linked" };
|
|
272
|
+
}
|
|
273
|
+
const undo2 = [];
|
|
274
|
+
try {
|
|
275
|
+
if (isLink(path3)) {
|
|
276
|
+
const previous = readlinkSync(path3);
|
|
277
|
+
removeLink(path3, isDir2);
|
|
278
|
+
undo2.push(() => nodeLinker.symlink(previous, path3, isDir2));
|
|
279
|
+
} else if (present(path3)) {
|
|
280
|
+
if (!present(target)) {
|
|
281
|
+
renameSync2(path3, target);
|
|
282
|
+
undo2.push(() => renameSync2(target, path3));
|
|
283
|
+
} else if (!isDir2 && modified(path3) > modified(target)) {
|
|
284
|
+
const backup2 = backUp(target);
|
|
285
|
+
undo2.push(() => renameSync2(backup2, target));
|
|
286
|
+
renameSync2(path3, target);
|
|
287
|
+
undo2.push(() => renameSync2(target, path3));
|
|
288
|
+
} else {
|
|
289
|
+
const backup2 = backUp(path3);
|
|
290
|
+
undo2.push(() => renameSync2(backup2, path3));
|
|
217
291
|
}
|
|
218
|
-
unlinkSync(local);
|
|
219
|
-
} else if (meta) {
|
|
220
|
-
if (!present(target)) renameSync2(local, target);
|
|
221
|
-
else if (!isDir2 && modified(local) > modified(target)) {
|
|
222
|
-
backUp(target);
|
|
223
|
-
renameSync2(local, target);
|
|
224
|
-
} else backUp(local);
|
|
225
292
|
}
|
|
226
293
|
if (isDir2) mkdirSync2(target, { recursive: true });
|
|
227
|
-
|
|
294
|
+
createLink(target, path3, isDir2, linker);
|
|
295
|
+
return { name, state: "linked" };
|
|
296
|
+
} catch (error) {
|
|
297
|
+
for (const step of undo2.reverse()) {
|
|
298
|
+
try {
|
|
299
|
+
step();
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
keepCopy(target, path3, isDir2);
|
|
304
|
+
return { name, state: "unshared", reason: codeOf(error) };
|
|
228
305
|
}
|
|
229
306
|
}
|
|
230
|
-
function
|
|
307
|
+
function linkSharedEntries(accountsDir, account, linker = nodeLinker) {
|
|
231
308
|
makePrivate(join2(accountsDir, account.id));
|
|
232
309
|
makePrivate(homeDir(accountsDir, account.id));
|
|
233
310
|
const dir = configDir(accountsDir, account);
|
|
234
311
|
makePrivate(dir);
|
|
235
|
-
|
|
236
|
-
|
|
312
|
+
const shared = ensureSharedDir(accountsDir);
|
|
313
|
+
const entries2 = SHARED_ENTRIES.map(([name, isDir2]) => linkOne(name, join2(shared, name), join2(dir, name), isDir2, linker));
|
|
314
|
+
return { dir, entries: entries2 };
|
|
315
|
+
}
|
|
316
|
+
function ensureHome(accountsDir, account) {
|
|
317
|
+
return linkSharedEntries(accountsDir, account).dir;
|
|
237
318
|
}
|
|
238
319
|
var inside = (path3, parent) => path3 === parent || path3.startsWith(parent.endsWith(sep2) ? parent : `${parent}${sep2}`);
|
|
239
320
|
function assertIsolated(accountsDir, dir) {
|
|
@@ -242,16 +323,21 @@ function assertIsolated(accountsDir, dir) {
|
|
|
242
323
|
throw new HarnessError("account_required", "A session cannot start outside its isolated account folder.");
|
|
243
324
|
}
|
|
244
325
|
}
|
|
245
|
-
function
|
|
246
|
-
const dir = ensureHome(accountsDir, account);
|
|
247
|
-
assertIsolated(accountsDir, dir);
|
|
326
|
+
function sessionEnv(dir, inherited) {
|
|
248
327
|
const env = {};
|
|
249
328
|
for (const name of inheritedCredentialVars(inherited)) env[name] = null;
|
|
250
329
|
env.PI_CODING_AGENT_DIR = dir;
|
|
251
330
|
return env;
|
|
252
331
|
}
|
|
332
|
+
function launchEnv(accountsDir, account, inherited = process.env) {
|
|
333
|
+
const dir = ensureHome(accountsDir, account);
|
|
334
|
+
assertIsolated(accountsDir, dir);
|
|
335
|
+
return sessionEnv(dir, inherited);
|
|
336
|
+
}
|
|
253
337
|
function accountContext(accountsDir, account) {
|
|
254
|
-
|
|
338
|
+
const { dir, entries: entries2 } = linkSharedEntries(accountsDir, account);
|
|
339
|
+
assertIsolated(accountsDir, dir);
|
|
340
|
+
return { id: account.id, provider: "pi", label: account.label, env: sessionEnv(dir, process.env), configDir: dir, sharing: entries2 };
|
|
255
341
|
}
|
|
256
342
|
|
|
257
343
|
// src/accounts/secrets.ts
|
|
@@ -406,7 +492,7 @@ function find(dir, accountId) {
|
|
|
406
492
|
return account;
|
|
407
493
|
}
|
|
408
494
|
var usable = (state2) => state2.health === "ok" || state2.health === "unknown";
|
|
409
|
-
function
|
|
495
|
+
function resolve2(dir, accountId, poolId) {
|
|
410
496
|
const registry = loadRegistry(dir);
|
|
411
497
|
if (accountId) {
|
|
412
498
|
const account2 = find(dir, accountId);
|
|
@@ -432,7 +518,7 @@ function resolve(dir, accountId, poolId) {
|
|
|
432
518
|
}
|
|
433
519
|
function defaultAccount(dir) {
|
|
434
520
|
try {
|
|
435
|
-
return
|
|
521
|
+
return resolve2(dir);
|
|
436
522
|
} catch {
|
|
437
523
|
return null;
|
|
438
524
|
}
|
|
@@ -545,7 +631,7 @@ var Accounts = class {
|
|
|
545
631
|
}
|
|
546
632
|
/** The account a new session uses; a session that already has one passes it and keeps it. */
|
|
547
633
|
resolve(accountId, poolId) {
|
|
548
|
-
return
|
|
634
|
+
return resolve2(this.dir, accountId, poolId);
|
|
549
635
|
}
|
|
550
636
|
context(account) {
|
|
551
637
|
return accountContext(this.dir, account);
|
|
@@ -801,9 +887,9 @@ var Accounts = class {
|
|
|
801
887
|
const interaction = {
|
|
802
888
|
signal: controller.signal,
|
|
803
889
|
notify: (notification) => event(notification),
|
|
804
|
-
prompt: (prompt2) => new Promise((
|
|
890
|
+
prompt: (prompt2) => new Promise((resolve9, reject) => {
|
|
805
891
|
const promptId = randomUUID2();
|
|
806
|
-
login.prompts.set(promptId, { resolve:
|
|
892
|
+
login.prompts.set(promptId, { resolve: resolve9, reject });
|
|
807
893
|
prompt2.signal?.addEventListener("abort", () => {
|
|
808
894
|
login.prompts.delete(promptId);
|
|
809
895
|
event({ type: "promptClosed", promptId });
|
|
@@ -922,7 +1008,7 @@ var Accounts = class {
|
|
|
922
1008
|
};
|
|
923
1009
|
|
|
924
1010
|
// src/plugins/api.ts
|
|
925
|
-
import { join as
|
|
1011
|
+
import { join as join28 } from "node:path";
|
|
926
1012
|
|
|
927
1013
|
// src/core/projects.ts
|
|
928
1014
|
import { existsSync } from "node:fs";
|
|
@@ -1048,13 +1134,24 @@ function forget(attachments, scope) {
|
|
|
1048
1134
|
if (scope.agentId) delete attachments.agents[scope.agentId];
|
|
1049
1135
|
if (scope.projectId) delete attachments.projects[scope.projectId];
|
|
1050
1136
|
}
|
|
1137
|
+
function forgetId(attachments, id) {
|
|
1138
|
+
const forgetFrom = (item) => {
|
|
1139
|
+
item.on = item.on.filter((entry) => entry !== id);
|
|
1140
|
+
item.off = item.off.filter((entry) => entry !== id);
|
|
1141
|
+
};
|
|
1142
|
+
forgetFrom(attachments.global);
|
|
1143
|
+
for (const item of Object.values(attachments.agents)) forgetFrom(item);
|
|
1144
|
+
for (const item of Object.values(attachments.projects)) forgetFrom(item);
|
|
1145
|
+
attachments.agents = Object.fromEntries(Object.entries(attachments.agents).filter(([, item]) => item.on.length || item.off.length));
|
|
1146
|
+
attachments.projects = Object.fromEntries(Object.entries(attachments.projects).filter(([, item]) => item.on.length || item.off.length));
|
|
1147
|
+
}
|
|
1051
1148
|
|
|
1052
1149
|
// src/prompt/compile.ts
|
|
1053
|
-
import { readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as
|
|
1150
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync11, statSync as statSync3 } from "node:fs";
|
|
1054
1151
|
import { join as join18 } from "node:path";
|
|
1055
1152
|
|
|
1056
1153
|
// src/core/index.ts
|
|
1057
|
-
import { copyFileSync, existsSync as existsSync2, readdirSync, rmSync as rmSync3 } from "node:fs";
|
|
1154
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync2, readdirSync, rmSync as rmSync3 } from "node:fs";
|
|
1058
1155
|
import { join as join7 } from "node:path";
|
|
1059
1156
|
import { DatabaseSync } from "node:sqlite";
|
|
1060
1157
|
var RECALL_SCHEMA = `CREATE TABLE recall_runs (run_id TEXT PRIMARY KEY, passages INTEGER NOT NULL, status TEXT NOT NULL);
|
|
@@ -1141,14 +1238,14 @@ function migrate(path3, migrations) {
|
|
|
1141
1238
|
throw sqlError(error);
|
|
1142
1239
|
}
|
|
1143
1240
|
if (current >= migrations.length) return database;
|
|
1144
|
-
const
|
|
1241
|
+
const backup2 = `${path3.replace(/\.sqlite$/, "")}.sqlite.bak-v${current}`;
|
|
1145
1242
|
const hadFile = existsSync2(path3) && current > 0;
|
|
1146
1243
|
if (hadFile) {
|
|
1147
1244
|
try {
|
|
1148
1245
|
database.exec("PRAGMA wal_checkpoint(TRUNCATE);");
|
|
1149
1246
|
} catch {
|
|
1150
1247
|
}
|
|
1151
|
-
|
|
1248
|
+
copyFileSync2(path3, backup2);
|
|
1152
1249
|
}
|
|
1153
1250
|
for (let index = current; index < migrations.length; index += 1) {
|
|
1154
1251
|
try {
|
|
@@ -1158,7 +1255,7 @@ function migrate(path3, migrations) {
|
|
|
1158
1255
|
});
|
|
1159
1256
|
} catch (error) {
|
|
1160
1257
|
database.close();
|
|
1161
|
-
if (hadFile)
|
|
1258
|
+
if (hadFile) copyFileSync2(backup2, path3);
|
|
1162
1259
|
else rmSync3(path3, { force: true });
|
|
1163
1260
|
throw new HarnessError("migration_failed", `Index migration ${index + 1} failed and the previous index was restored: ${error instanceof Error ? error.message : String(error)}`);
|
|
1164
1261
|
}
|
|
@@ -1280,7 +1377,7 @@ import { join as join9 } from "node:path";
|
|
|
1280
1377
|
|
|
1281
1378
|
// src/memory/store.ts
|
|
1282
1379
|
import { createHash } from "node:crypto";
|
|
1283
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
1380
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
1284
1381
|
import { join as join8, relative } from "node:path";
|
|
1285
1382
|
import { stringify as stringify2 } from "yaml";
|
|
1286
1383
|
|
|
@@ -1453,7 +1550,7 @@ function markdownFiles(directory, into) {
|
|
|
1453
1550
|
const path3 = join8(directory, name);
|
|
1454
1551
|
let directoryEntry = false;
|
|
1455
1552
|
try {
|
|
1456
|
-
directoryEntry =
|
|
1553
|
+
directoryEntry = statSync2(path3).isDirectory();
|
|
1457
1554
|
} catch {
|
|
1458
1555
|
continue;
|
|
1459
1556
|
}
|
|
@@ -1463,14 +1560,14 @@ function markdownFiles(directory, into) {
|
|
|
1463
1560
|
}
|
|
1464
1561
|
function isFile(path3) {
|
|
1465
1562
|
try {
|
|
1466
|
-
return
|
|
1563
|
+
return statSync2(path3).isFile();
|
|
1467
1564
|
} catch {
|
|
1468
1565
|
return false;
|
|
1469
1566
|
}
|
|
1470
1567
|
}
|
|
1471
1568
|
function isDir(path3) {
|
|
1472
1569
|
try {
|
|
1473
|
-
return
|
|
1570
|
+
return statSync2(path3).isDirectory();
|
|
1474
1571
|
} catch {
|
|
1475
1572
|
return false;
|
|
1476
1573
|
}
|
|
@@ -1802,7 +1899,7 @@ function editRefusal(caller, block2) {
|
|
|
1802
1899
|
if (block2.scope === "project") return block2.owner === caller.projectSlug ? null : "A session may edit only its own project's brief.";
|
|
1803
1900
|
return `A Harness session can read the ${label} block but cannot edit it. Report what you learned in your result instead.`;
|
|
1804
1901
|
}
|
|
1805
|
-
function
|
|
1902
|
+
function resolve3(root, caller, rawLabel) {
|
|
1806
1903
|
const wiki = wikiDir(root);
|
|
1807
1904
|
const label = rawLabel.trim().replace(/^\/+/, "");
|
|
1808
1905
|
const unknown = () => invalid(`There is no memory block called ${label}. Use one of the labels shown in your memory blocks.`);
|
|
@@ -1887,7 +1984,7 @@ function write(wiki, block2, front, body2, actor, reason) {
|
|
|
1887
1984
|
}
|
|
1888
1985
|
function edit(root, caller, label, change) {
|
|
1889
1986
|
const wiki = wikiDir(root);
|
|
1890
|
-
const block2 =
|
|
1987
|
+
const block2 = resolve3(root, caller, label);
|
|
1891
1988
|
const refusal = editRefusal(caller, block2);
|
|
1892
1989
|
if (refusal) throw invalid(refusal);
|
|
1893
1990
|
const body2 = applyEdit(block2.body, change);
|
|
@@ -2181,6 +2278,14 @@ import { join as join14 } from "node:path";
|
|
|
2181
2278
|
import { lstatSync as lstatSync2, mkdirSync as mkdirSync7, mkdtempSync, readdirSync as readdirSync4, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync4, unlinkSync as unlinkSync2, writeFileSync } from "node:fs";
|
|
2182
2279
|
import { basename as basename3, dirname as dirname4, join as join12 } from "node:path";
|
|
2183
2280
|
var KINDS = ["skills", "prompts", "rules"];
|
|
2281
|
+
function contributionId(item) {
|
|
2282
|
+
return `${item.builtin ? "included" : "yours"}/${item.id}`;
|
|
2283
|
+
}
|
|
2284
|
+
function limitToContributions(items, contributions2) {
|
|
2285
|
+
if (contributions2 === void 0) return [...items];
|
|
2286
|
+
const allowed2 = new Set(contributions2);
|
|
2287
|
+
return items.filter((item) => allowed2.has(contributionId(item)));
|
|
2288
|
+
}
|
|
2184
2289
|
var MAX_SKILL_BUNDLES = 20;
|
|
2185
2290
|
var MAX_SKILL_FILES = 100;
|
|
2186
2291
|
var MAX_SKILL_IMPORT_BYTES = 10 * 1024 * 1024;
|
|
@@ -2254,7 +2359,8 @@ function enablementIds(map, key) {
|
|
|
2254
2359
|
}
|
|
2255
2360
|
var BUILTIN_OFF_KEY = "builtinOff";
|
|
2256
2361
|
var isItemId = (id) => KINDS.some((kind) => id.startsWith(`${kind}/`));
|
|
2257
|
-
function enabledFor(root, projectId, agentId = null) {
|
|
2362
|
+
function enabledFor(root, projectId, agentId = null, contributions2) {
|
|
2363
|
+
if (contributions2 !== void 0) return limitToContributions(list(root), contributions2);
|
|
2258
2364
|
const choices = load2(root);
|
|
2259
2365
|
return list(root).filter((item) => attached(choices, [item.id], { agentId, projectId }, !!item.builtin));
|
|
2260
2366
|
}
|
|
@@ -2266,8 +2372,8 @@ function itemBody(item) {
|
|
|
2266
2372
|
return "";
|
|
2267
2373
|
}
|
|
2268
2374
|
}
|
|
2269
|
-
function resources(root, projectId, agentId = null) {
|
|
2270
|
-
const items = enabledFor(root, projectId, agentId);
|
|
2375
|
+
function resources(root, projectId, agentId = null, contributions2) {
|
|
2376
|
+
const items = enabledFor(root, projectId, agentId, contributions2);
|
|
2271
2377
|
return {
|
|
2272
2378
|
ids: items.map((item) => item.id),
|
|
2273
2379
|
skillPaths: items.filter((item) => item.kind === "skills").map((item) => item.path),
|
|
@@ -3238,7 +3344,7 @@ function cite(wiki, id) {
|
|
|
3238
3344
|
}
|
|
3239
3345
|
|
|
3240
3346
|
// src/sessions/runStore.ts
|
|
3241
|
-
import { appendFileSync, copyFileSync as
|
|
3347
|
+
import { appendFileSync, copyFileSync as copyFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync8, readFileSync as readFileSync10 } from "node:fs";
|
|
3242
3348
|
import { join as join17 } from "node:path";
|
|
3243
3349
|
function runDir(root, runId) {
|
|
3244
3350
|
return join17(root, "runs", safeId(runId));
|
|
@@ -3384,8 +3490,8 @@ function markInterruptedRuns(root, reason = STOPPED) {
|
|
|
3384
3490
|
const status = terminalStatus(settled2);
|
|
3385
3491
|
const settledReason = reasonOf(settled2);
|
|
3386
3492
|
if (record3.status !== status || record3.endedAt !== settled2.timestamp || record3.terminalReason !== settledReason) {
|
|
3387
|
-
const
|
|
3388
|
-
if (!existsSync7(
|
|
3493
|
+
const backup2 = `${path3}.before-outcome-repair`;
|
|
3494
|
+
if (!existsSync7(backup2)) copyFileSync3(path3, backup2);
|
|
3389
3495
|
Object.assign(record3, { status, endedAt: settled2.timestamp, terminalReason: settledReason });
|
|
3390
3496
|
write2(root, record3);
|
|
3391
3497
|
}
|
|
@@ -3716,10 +3822,10 @@ function hostSection(host, kept) {
|
|
|
3716
3822
|
return section(host.id, host.title, own ? `${host.pinned.text}
|
|
3717
3823
|
${own.body}` : host.pinned.text, host.budget, host.pinned.pointer ?? host.pointer ?? "", []);
|
|
3718
3824
|
}
|
|
3719
|
-
function compile(env, context, launch = {}) {
|
|
3825
|
+
function compile(env, context, launch = {}, contributions2) {
|
|
3720
3826
|
const { root } = env;
|
|
3721
3827
|
const wiki = wikiDir(root);
|
|
3722
|
-
const active = enabledFor(root, context.projectId ?? null, context.agentId ?? null);
|
|
3828
|
+
const active = enabledFor(root, context.projectId ?? null, context.agentId ?? null, contributions2);
|
|
3723
3829
|
const groups = toolGroups(context, launch);
|
|
3724
3830
|
const contextProfile = profileFor(root, context);
|
|
3725
3831
|
const keep = (id) => id === "references" || keeps(contextProfile, id);
|
|
@@ -3832,7 +3938,7 @@ function sessionFile(sessionDir, resume) {
|
|
|
3832
3938
|
const files = names.filter((name) => name.endsWith(".jsonl") && name.length > ".jsonl".length).flatMap((name) => {
|
|
3833
3939
|
const path3 = join18(sessionDir, name);
|
|
3834
3940
|
try {
|
|
3835
|
-
return [{ path: path3, modified:
|
|
3941
|
+
return [{ path: path3, modified: statSync3(path3).mtimeMs }];
|
|
3836
3942
|
} catch {
|
|
3837
3943
|
return [];
|
|
3838
3944
|
}
|
|
@@ -3844,8 +3950,8 @@ function sameSection(left, right) {
|
|
|
3844
3950
|
if (!left || !right) return !left && !right;
|
|
3845
3951
|
return left.id === right.id && left.title === right.title && left.body === right.body && left.budget === right.budget && left.size === right.size && left.cut === right.cut && left.sources.length === right.sources.length && left.sources.every((source2, index) => source2.id === right.sources[index].id && source2.kind === right.sources[index].kind && (source2.revision ?? null) === (right.sources[index].revision ?? null));
|
|
3846
3952
|
}
|
|
3847
|
-
function sessionLaunch(env, context, launch, historyFile) {
|
|
3848
|
-
const pack = compile(env, context, launch);
|
|
3953
|
+
function sessionLaunch(env, context, launch, historyFile, contributions2) {
|
|
3954
|
+
const pack = compile(env, context, launch, contributions2);
|
|
3849
3955
|
const dynamic = [...BUILT_IN_DYNAMIC, ...(launch.sections ?? []).filter((host) => host.dynamic).map((host) => ({ id: host.id, order: host.order }))].map((slot, index) => ({ ...slot, index })).sort((left, right) => left.order - right.order || left.index - right.index).map((slot) => slot.id);
|
|
3850
3956
|
const previous = historyFile ? deliveredSnapshot(historyFile) : null;
|
|
3851
3957
|
const snapshot3 = pack.sections.filter((entry) => dynamic.includes(entry.id));
|
|
@@ -4443,6 +4549,30 @@ import { join as join21, sep as sep3 } from "node:path";
|
|
|
4443
4549
|
|
|
4444
4550
|
// src/plugins/manifest.ts
|
|
4445
4551
|
import { basename as basename4, join as join20 } from "node:path";
|
|
4552
|
+
import { z as z3 } from "zod";
|
|
4553
|
+
var connectorTemplateSchema = z3.object({
|
|
4554
|
+
id: z3.string().regex(/^[a-z0-9-]+$/),
|
|
4555
|
+
name: z3.string().min(1).max(120),
|
|
4556
|
+
description: z3.string().max(500),
|
|
4557
|
+
url: z3.string().url().startsWith("https://"),
|
|
4558
|
+
auth: z3.enum(["none", "headers", "oauth"]),
|
|
4559
|
+
oauthClient: z3.enum(["dynamic", "user"]).nullable(),
|
|
4560
|
+
oauthScopes: z3.array(z3.string().max(500)).max(30),
|
|
4561
|
+
docsUrl: z3.string().url().startsWith("https://").nullable(),
|
|
4562
|
+
fields: z3.array(z3.object({
|
|
4563
|
+
header: z3.string().regex(/^[A-Za-z0-9_-]+$/),
|
|
4564
|
+
label: z3.string().min(1).max(120),
|
|
4565
|
+
prefix: z3.string().max(100),
|
|
4566
|
+
help: z3.string().max(500)
|
|
4567
|
+
}).strict()).max(12),
|
|
4568
|
+
setup: z3.array(z3.string().max(1e3)).max(20),
|
|
4569
|
+
notice: z3.string().max(1e3).nullable()
|
|
4570
|
+
}).strict().superRefine((template, context) => {
|
|
4571
|
+
const oauth = template.auth === "oauth";
|
|
4572
|
+
const headers = template.auth === "headers";
|
|
4573
|
+
if (oauth !== (template.oauthClient !== null)) context.addIssue({ code: "custom", message: "OAuth templates must state a client type and other templates must not.", path: ["oauthClient"] });
|
|
4574
|
+
if (headers !== template.fields.length > 0) context.addIssue({ code: "custom", message: "Header templates must declare input fields and other templates must not.", path: ["fields"] });
|
|
4575
|
+
});
|
|
4446
4576
|
function inside2(path3) {
|
|
4447
4577
|
if (typeof path3 !== "string" || !path3.trim()) return null;
|
|
4448
4578
|
try {
|
|
@@ -4459,7 +4589,11 @@ function packageManifest(packageRoot) {
|
|
|
4459
4589
|
description: typeof manifest.description === "string" ? manifest.description : "",
|
|
4460
4590
|
version: typeof manifest.version === "string" ? manifest.version : null,
|
|
4461
4591
|
extends: (Array.isArray(own.extends) ? own.extends : typeof own.extends === "string" ? [own.extends] : []).filter((id) => typeof id === "string" && !!id),
|
|
4462
|
-
hooks: inside2(own.hooks)
|
|
4592
|
+
hooks: inside2(own.hooks),
|
|
4593
|
+
connectorTemplates: (Array.isArray(own.connectors) ? own.connectors : []).flatMap((value) => {
|
|
4594
|
+
const parsed2 = connectorTemplateSchema.safeParse(value);
|
|
4595
|
+
return parsed2.success ? [parsed2.data] : [];
|
|
4596
|
+
})
|
|
4463
4597
|
};
|
|
4464
4598
|
}
|
|
4465
4599
|
|
|
@@ -4488,14 +4622,14 @@ var piResolver = async ({ cwd, agentDir, install: install3, packages }) => {
|
|
|
4488
4622
|
const settings = pi.SettingsManager.create(cwd, agentDir);
|
|
4489
4623
|
const shared = new Set((settings.getGlobalSettings().packages ?? []).map((entry) => typeof entry === "string" ? entry : entry.source));
|
|
4490
4624
|
const manager2 = () => new pi.DefaultPackageManager({ cwd, agentDir, settingsManager: settings, builtinExtensions: [...PI_BUILTINS] });
|
|
4491
|
-
const
|
|
4625
|
+
const resolve9 = (installing) => manager2().resolve(async (source2) => installing && shared.has(source2) ? "install" : "skip");
|
|
4492
4626
|
let resolved2;
|
|
4493
4627
|
try {
|
|
4494
|
-
resolved2 = install3 ? await withoutInstallScripts(() =>
|
|
4628
|
+
resolved2 = install3 ? await withoutInstallScripts(() => resolve9(true)) : await resolve9(false);
|
|
4495
4629
|
} catch (error) {
|
|
4496
4630
|
if (!install3) throw error;
|
|
4497
4631
|
log("a plugin package could not be installed", error instanceof Error ? error.message : String(error));
|
|
4498
|
-
resolved2 = await
|
|
4632
|
+
resolved2 = await resolve9(false);
|
|
4499
4633
|
}
|
|
4500
4634
|
const found = [];
|
|
4501
4635
|
const add = (paths) => {
|
|
@@ -4547,8 +4681,8 @@ var piResolver = async ({ cwd, agentDir, install: install3, packages }) => {
|
|
|
4547
4681
|
|
|
4548
4682
|
// src/plugins/runtime.ts
|
|
4549
4683
|
import { createHash as createHash2 } from "node:crypto";
|
|
4550
|
-
import { readFileSync as readFileSync12, readdirSync as readdirSync7, statSync as
|
|
4551
|
-
import { dirname as dirname7, resolve as
|
|
4684
|
+
import { readFileSync as readFileSync12, readdirSync as readdirSync7, statSync as statSync4 } from "node:fs";
|
|
4685
|
+
import { dirname as dirname7, resolve as resolve4 } from "node:path";
|
|
4552
4686
|
|
|
4553
4687
|
// src/plugins/identity.ts
|
|
4554
4688
|
import { basename as basename5, dirname as dirname6 } from "node:path";
|
|
@@ -4566,10 +4700,10 @@ function describeFound(item) {
|
|
|
4566
4700
|
const described = describeFile(item.path);
|
|
4567
4701
|
return { name: described?.name ?? file, description: described?.description ?? "" };
|
|
4568
4702
|
}
|
|
4569
|
-
var
|
|
4703
|
+
var contributionId2 = (pluginId, kind, name) => `${pluginId}/${kind}/${name}`;
|
|
4570
4704
|
function foundIds(item) {
|
|
4571
4705
|
const pluginId = pluginIdOf(item);
|
|
4572
|
-
return [
|
|
4706
|
+
return [contributionId2(pluginId, FOUND_KINDS[item.kind], describeFound(item).name), pluginId];
|
|
4573
4707
|
}
|
|
4574
4708
|
|
|
4575
4709
|
// src/plugins/runtime.ts
|
|
@@ -4620,21 +4754,21 @@ function observeResources(pi, loader, loading, records, publish) {
|
|
|
4620
4754
|
collected.push(resource);
|
|
4621
4755
|
return loading.resourceAllowed?.({ ...owner, kind, path: resourcePath }) !== false;
|
|
4622
4756
|
};
|
|
4623
|
-
const absolute = (entry) =>
|
|
4757
|
+
const absolute = (entry) => resolve4(dirname7(path3), entry);
|
|
4624
4758
|
const skills2 = pi.loadSkills({ cwd: dirname7(path3), agentDir: dirname7(path3), includeDefaults: false, skillPaths: (result.skillPaths ?? []).map(absolute) }).skills;
|
|
4625
4759
|
const scanner = new pi.DefaultResourceLoader({ cwd: dirname7(path3), agentDir: dirname7(path3), settingsManager: pi.SettingsManager.inMemory({}), noExtensions: true, noSkills: true, noThemes: true, noContextFiles: true, noPromptTemplates: true, additionalPromptTemplatePaths: (result.promptPaths ?? []).map(absolute) });
|
|
4626
4760
|
await scanner.reload();
|
|
4627
4761
|
const prompts = scanner.getPrompts().prompts;
|
|
4628
4762
|
const themes = (result.themePaths ?? []).map(absolute).flatMap((theme) => {
|
|
4629
4763
|
try {
|
|
4630
|
-
if (
|
|
4764
|
+
if (statSync4(theme).isDirectory()) return readdirSync7(theme).filter((name) => {
|
|
4631
4765
|
if (!name.endsWith(".json")) return false;
|
|
4632
4766
|
try {
|
|
4633
|
-
return
|
|
4767
|
+
return statSync4(resolve4(theme, name)).isFile();
|
|
4634
4768
|
} catch {
|
|
4635
4769
|
return false;
|
|
4636
4770
|
}
|
|
4637
|
-
}).map((name) =>
|
|
4771
|
+
}).map((name) => resolve4(theme, name));
|
|
4638
4772
|
} catch {
|
|
4639
4773
|
}
|
|
4640
4774
|
return [theme];
|
|
@@ -4800,8 +4934,9 @@ var Plugins = class {
|
|
|
4800
4934
|
if (item.kind === "hook") return !attached(choices, [pluginIdOf(item)], {}, true);
|
|
4801
4935
|
return !attached(choices, foundIds(item), input.caller ?? {}, true);
|
|
4802
4936
|
};
|
|
4803
|
-
const
|
|
4804
|
-
const
|
|
4937
|
+
const allowed2 = (item) => input.contributions === void 0 ? !off(item) : input.contributions.includes(foundIds(item)[0]);
|
|
4938
|
+
const detached = found.filter((item) => !allowed2(item)).map((item) => item.path);
|
|
4939
|
+
const chosen = found.filter((item) => loads(item, trusted) && allowed2(item));
|
|
4805
4940
|
const paths = (kind, builtin) => chosen.filter((item) => item.kind === kind && item.origin === "builtin" === builtin).map((item) => item.path);
|
|
4806
4941
|
return {
|
|
4807
4942
|
extensionPaths: paths("extension", false),
|
|
@@ -4812,7 +4947,7 @@ var Plugins = class {
|
|
|
4812
4947
|
hooks: chosen.filter((item) => item.kind === "hook" && item.packageRoot).map((item) => ({ path: item.path, packageRoot: item.packageRoot, hash: packageCodeState[item.packageRoot]?.hash ?? "" })),
|
|
4813
4948
|
found,
|
|
4814
4949
|
detached,
|
|
4815
|
-
resourceAllowed:
|
|
4950
|
+
resourceAllowed: allowed2,
|
|
4816
4951
|
projectCode: projectCodeState,
|
|
4817
4952
|
packageCode: packageCodeState
|
|
4818
4953
|
};
|
|
@@ -4842,7 +4977,7 @@ var Plugins = class {
|
|
|
4842
4977
|
// src/plugins/catalogue.ts
|
|
4843
4978
|
var REGISTRY_KINDS = { rules: "instruction", skills: "skill", prompts: "command" };
|
|
4844
4979
|
var contribution = (plugin, kind, name, rest = {}) => ({
|
|
4845
|
-
id:
|
|
4980
|
+
id: contributionId2(plugin, kind, name),
|
|
4846
4981
|
kind,
|
|
4847
4982
|
name,
|
|
4848
4983
|
description: "",
|
|
@@ -4873,6 +5008,7 @@ function fromDescriptor(descriptor, source2, attachments = null, caller = {}) {
|
|
|
4873
5008
|
required: descriptor.required ?? null,
|
|
4874
5009
|
manage: descriptor.manage ?? (descriptor.tools?.length ? "tools" : null),
|
|
4875
5010
|
settings: (descriptor.settings ?? []).map((setting) => ({ ...setting, value: settingValue(setting, attachments?.settings[descriptor.id]) })),
|
|
5011
|
+
connectorTemplates: descriptor.connectorTemplates ?? [],
|
|
4876
5012
|
attachment: switchable2 ? describe(attachments, descriptor.id, switchable2.on) : null,
|
|
4877
5013
|
contributions: [
|
|
4878
5014
|
...(descriptor.code ?? []).map((code) => contribution(descriptor.id, "code", code.name, { description: code.description, state: on ? "on" : "off" })),
|
|
@@ -4934,7 +5070,7 @@ function registeredLine(entry) {
|
|
|
4934
5070
|
function foundContribution(plugin, item, trusted, input, projects) {
|
|
4935
5071
|
const kind = FOUND_KINDS[item.kind];
|
|
4936
5072
|
const { name, description } = describeFound(item);
|
|
4937
|
-
const id =
|
|
5073
|
+
const id = contributionId2(plugin, kind, name);
|
|
4938
5074
|
const attachment = switchable(item) && item.kind !== "hook" ? describe(input.attachments, id, attached(input.attachments, [plugin], {}, true)) : null;
|
|
4939
5075
|
const on = item.kind === "hook" ? attached(input.attachments, [plugin], {}, true) : !attachment || attached(input.attachments, [id, plugin], input.caller, true);
|
|
4940
5076
|
const state2 = !item.enabled || !on ? "off" : loads(item, trusted) ? "on" : "untrusted";
|
|
@@ -4955,7 +5091,7 @@ function foundContribution(plugin, item, trusted, input, projects) {
|
|
|
4955
5091
|
if (item.kind === "context") return contribution(plugin, kind, name, { ...rest, description: CONTEXT_NOTES[basename7(item.path)] ?? "Read by Pi at the start of every session in this folder.", path: item.path });
|
|
4956
5092
|
return contribution(plugin, kind, name, { ...rest, description: description ?? "", path: item.path });
|
|
4957
5093
|
}
|
|
4958
|
-
var empty = (id, title, description, source2, path3) => ({ id, title, description, version: null, source: source2, path: path3, runsCode: false, trust: "none", code: null, install: null, extends: [], required: null, manage: "files", settings: [], attachment: null, contributions: [] });
|
|
5094
|
+
var empty = (id, title, description, source2, path3) => ({ id, title, description, version: null, source: source2, path: path3, runsCode: false, trust: "none", code: null, install: null, extends: [], required: null, manage: "files", settings: [], connectorTemplates: [], attachment: null, contributions: [] });
|
|
4959
5095
|
function catalogue(input) {
|
|
4960
5096
|
const plugins = /* @__PURE__ */ new Map();
|
|
4961
5097
|
for (const descriptor of input.descriptors) plugins.set(descriptor.id, fromDescriptor(descriptor, descriptor.id === "pi" ? "pi" : descriptor.id === YOURS_PLUGIN.id ? "yours" : input.application.has(descriptor.id) ? "application" : "builtin", input.attachments, input.caller));
|
|
@@ -4986,7 +5122,7 @@ function catalogue(input) {
|
|
|
4986
5122
|
const manifest = packageManifest(item.packageRoot);
|
|
4987
5123
|
const install3 = item.project ? null : item.packageRoot.startsWith(`${input.packagesDir}/`) ? { kind: "folder", source: item.packageRoot } : item.source ? { kind: "settings", source: item.source } : null;
|
|
4988
5124
|
const extended = manifest.extends.filter((part) => input.descriptors.some((descriptor) => descriptor.id === part));
|
|
4989
|
-
plugin = { ...empty(id, manifest.name, manifest.description, item.project ? "project" : "package", item.packageRoot), version: manifest.version, install: install3, extends: extended };
|
|
5125
|
+
plugin = { ...empty(id, manifest.name, manifest.description, item.project ? "project" : "package", item.packageRoot), version: manifest.version, install: install3, extends: extended, connectorTemplates: manifest.connectorTemplates };
|
|
4990
5126
|
} else if (item.origin === "home") plugin = empty(id, "Home folder skills", "Skills in the .agents folder of your home folder. Pi reads them for every account.", "home", dirname9(dirname9(item.path)));
|
|
4991
5127
|
else if (item.origin === "project") plugin = empty(id, "Project folder", "Found in the working folder: its .pi and .agents folders, and the instruction files Pi reads on the way up from it.", "project", input.cwd);
|
|
4992
5128
|
else plugin = empty(id, "Shared Pi folder", "Extensions, skills, prompts and instruction files in the folder every account shares.", "shared", input.sharedDir);
|
|
@@ -4999,13 +5135,13 @@ function catalogue(input) {
|
|
|
4999
5135
|
if (item.kind === "extension" && added.registered) {
|
|
5000
5136
|
const registered = added.registered;
|
|
5001
5137
|
const append = (kind, name, rest = {}) => {
|
|
5002
|
-
const id2 =
|
|
5138
|
+
const id2 = contributionId2(plugin.id, kind, name);
|
|
5003
5139
|
if (!plugin.contributions.some((existing) => existing.id === id2)) plugin.contributions.push(contribution(plugin.id, kind, name, { state: added.state, description: `Registered by ${added.name}. Observed ${registered.seenAt ?? "in a session"}.`, ...rest }));
|
|
5004
5140
|
};
|
|
5005
5141
|
for (const name of registered.tools) append("tool", name);
|
|
5006
5142
|
for (const name of registered.commands) append("command", name);
|
|
5007
5143
|
for (const resource of registered.resources ?? []) {
|
|
5008
|
-
const id2 =
|
|
5144
|
+
const id2 = contributionId2(plugin.id, resource.kind, resource.name);
|
|
5009
5145
|
const on = attached(input.attachments, [id2, plugin.id], input.caller, true);
|
|
5010
5146
|
append(resource.kind, resource.name, { path: resource.path, state: on ? added.state : "off", attachment: describe(input.attachments, id2, attached(input.attachments, [plugin.id], {}, true)) });
|
|
5011
5147
|
}
|
|
@@ -5024,15 +5160,15 @@ function catalogue(input) {
|
|
|
5024
5160
|
}
|
|
5025
5161
|
|
|
5026
5162
|
// src/plugins/inventory.ts
|
|
5027
|
-
import { readFileSync as
|
|
5163
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
5028
5164
|
|
|
5029
5165
|
// src/mcp/connections.ts
|
|
5030
|
-
import { chmodSync as
|
|
5031
|
-
import { isAbsolute as isAbsolute3, join as
|
|
5166
|
+
import { chmodSync as chmodSync7 } from "node:fs";
|
|
5167
|
+
import { isAbsolute as isAbsolute3, join as join27 } from "node:path";
|
|
5032
5168
|
|
|
5033
5169
|
// src/core/path.ts
|
|
5034
5170
|
import { execFileSync } from "node:child_process";
|
|
5035
|
-
import { accessSync, constants, statSync as
|
|
5171
|
+
import { accessSync, constants, statSync as statSync5 } from "node:fs";
|
|
5036
5172
|
import { homedir as homedir3 } from "node:os";
|
|
5037
5173
|
import { delimiter, isAbsolute as isAbsolute2, join as join25 } from "node:path";
|
|
5038
5174
|
var resolved = null;
|
|
@@ -5054,7 +5190,7 @@ function searchPath() {
|
|
|
5054
5190
|
function isExecutable(path3) {
|
|
5055
5191
|
try {
|
|
5056
5192
|
accessSync(path3, constants.X_OK);
|
|
5057
|
-
return
|
|
5193
|
+
return statSync5(path3).isFile();
|
|
5058
5194
|
} catch {
|
|
5059
5195
|
return false;
|
|
5060
5196
|
}
|
|
@@ -5068,51 +5204,212 @@ function findBinary(name) {
|
|
|
5068
5204
|
return null;
|
|
5069
5205
|
}
|
|
5070
5206
|
|
|
5207
|
+
// src/mcp/scopeMigration.ts
|
|
5208
|
+
import { chmodSync as chmodSync6, existsSync as existsSync10, readFileSync as readFileSync14, unlinkSync as unlinkSync3 } from "node:fs";
|
|
5209
|
+
import { join as join26 } from "node:path";
|
|
5210
|
+
|
|
5071
5211
|
// src/mcp/types.ts
|
|
5072
|
-
import { z as
|
|
5212
|
+
import { z as z4 } from "zod";
|
|
5073
5213
|
var MCP_OAUTH_PORT = 47823;
|
|
5074
5214
|
var MCP_OAUTH_REDIRECT_URL = `http://127.0.0.1:${MCP_OAUTH_PORT}/oauth/callback`;
|
|
5075
|
-
var
|
|
5076
|
-
|
|
5077
|
-
|
|
5215
|
+
var connectionCapabilityId = (id) => `connected-apps/connection/${id}`;
|
|
5216
|
+
var connectionSchema = z4.object({
|
|
5217
|
+
id: z4.string().default(""),
|
|
5218
|
+
name: z4.string().default(""),
|
|
5078
5219
|
/** `workspace` (the first-party Xingular Workspace server), `local` (a command), or `remote` (Streamable HTTP). */
|
|
5079
|
-
kind:
|
|
5080
|
-
command:
|
|
5081
|
-
args:
|
|
5082
|
-
cwd:
|
|
5083
|
-
envKeys:
|
|
5084
|
-
enabled:
|
|
5220
|
+
kind: z4.string().default(""),
|
|
5221
|
+
command: z4.string().default(""),
|
|
5222
|
+
args: z4.array(z4.string()).default([]),
|
|
5223
|
+
cwd: z4.string().nullable().default(null),
|
|
5224
|
+
envKeys: z4.array(z4.string()).default([]),
|
|
5225
|
+
enabled: z4.boolean().default(false),
|
|
5085
5226
|
/** Available to every agent and project, except those excluded. */
|
|
5086
|
-
global:
|
|
5087
|
-
agentIds:
|
|
5088
|
-
projectIds:
|
|
5089
|
-
excludedAgentIds:
|
|
5090
|
-
excludedProjectIds:
|
|
5227
|
+
global: z4.boolean().default(false),
|
|
5228
|
+
agentIds: z4.array(z4.string()).default([]),
|
|
5229
|
+
projectIds: z4.array(z4.string()).default([]),
|
|
5230
|
+
excludedAgentIds: z4.array(z4.string()).default([]),
|
|
5231
|
+
excludedProjectIds: z4.array(z4.string()).default([]),
|
|
5091
5232
|
/** Tools that run without asking. */
|
|
5092
|
-
readTools:
|
|
5093
|
-
expectedVaultId:
|
|
5094
|
-
checkedAt:
|
|
5095
|
-
serverName:
|
|
5096
|
-
tools:
|
|
5097
|
-
error:
|
|
5098
|
-
url:
|
|
5233
|
+
readTools: z4.array(z4.string()).default([]),
|
|
5234
|
+
expectedVaultId: z4.string().nullable().default(null),
|
|
5235
|
+
checkedAt: z4.string().nullable().default(null),
|
|
5236
|
+
serverName: z4.string().nullable().default(null),
|
|
5237
|
+
tools: z4.array(z4.unknown()).default([]),
|
|
5238
|
+
error: z4.string().nullable().default(null),
|
|
5239
|
+
url: z4.string().nullable().default(null),
|
|
5099
5240
|
/** `none`, `headers`, or `oauth`. */
|
|
5100
|
-
auth:
|
|
5101
|
-
headerKeys:
|
|
5102
|
-
oauthClient:
|
|
5103
|
-
oauthScopes:
|
|
5104
|
-
authState:
|
|
5105
|
-
catalogId:
|
|
5241
|
+
auth: z4.string().default(""),
|
|
5242
|
+
headerKeys: z4.array(z4.string()).default([]),
|
|
5243
|
+
oauthClient: z4.string().nullable().default(null),
|
|
5244
|
+
oauthScopes: z4.array(z4.string()).default([]),
|
|
5245
|
+
authState: z4.string().nullable().default(null),
|
|
5246
|
+
catalogId: z4.string().nullable().default(null)
|
|
5106
5247
|
});
|
|
5107
5248
|
var connectionInputSchema = connectionSchema.extend({
|
|
5108
|
-
env:
|
|
5109
|
-
removeEnv:
|
|
5110
|
-
headers:
|
|
5111
|
-
removeHeaders:
|
|
5249
|
+
env: z4.record(z4.string(), z4.string()).nullish(),
|
|
5250
|
+
removeEnv: z4.array(z4.string()).nullish(),
|
|
5251
|
+
headers: z4.record(z4.string(), z4.string()).nullish(),
|
|
5252
|
+
removeHeaders: z4.array(z4.string()).nullish(),
|
|
5112
5253
|
/** A client registered by the user with the provider; replaces any saved sign-in. */
|
|
5113
|
-
oauthCredentials:
|
|
5254
|
+
oauthCredentials: z4.object({ clientId: z4.string(), clientSecret: z4.string() }).nullish()
|
|
5114
5255
|
});
|
|
5115
5256
|
|
|
5257
|
+
// src/mcp/scopeMigration.ts
|
|
5258
|
+
var recordPath2 = (root) => join26(root, "config/capability-scope-migration.json");
|
|
5259
|
+
var attachmentsPath = (root) => join26(root, "config/plugin-attachments.json");
|
|
5260
|
+
var connectionPath = (stateDir) => join26(stateDir, "connections.json");
|
|
5261
|
+
var connectionsBackupPath = (stateDir) => join26(stateDir, "connections.pre-capability-scope.json");
|
|
5262
|
+
var attachmentsBackupPath = (root) => join26(root, "config/plugin-attachments.pre-capability-scope.json");
|
|
5263
|
+
function connectionScopeMigrationComplete(root) {
|
|
5264
|
+
try {
|
|
5265
|
+
const marker = readJson(recordPath2(root));
|
|
5266
|
+
return marker?.version === 1 && marker.status === "complete";
|
|
5267
|
+
} catch {
|
|
5268
|
+
return false;
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
5271
|
+
function unique(values2) {
|
|
5272
|
+
return [...new Set(values2)];
|
|
5273
|
+
}
|
|
5274
|
+
function scopeRows(connection) {
|
|
5275
|
+
const agents = /* @__PURE__ */ new Map();
|
|
5276
|
+
const projects = /* @__PURE__ */ new Map();
|
|
5277
|
+
for (const id of unique([...connection.agentIds, ...connection.excludedAgentIds])) {
|
|
5278
|
+
agents.set(id, !connection.excludedAgentIds.includes(id));
|
|
5279
|
+
}
|
|
5280
|
+
for (const id of unique([...connection.projectIds, ...connection.excludedProjectIds])) {
|
|
5281
|
+
projects.set(id, !connection.excludedProjectIds.includes(id));
|
|
5282
|
+
}
|
|
5283
|
+
return { global: connection.global, agents, projects };
|
|
5284
|
+
}
|
|
5285
|
+
function assignmentCount(rows) {
|
|
5286
|
+
return 1 + rows.agents.size + rows.projects.size;
|
|
5287
|
+
}
|
|
5288
|
+
function removeId(choices, id) {
|
|
5289
|
+
forgetId(choices, id);
|
|
5290
|
+
}
|
|
5291
|
+
function put(map, id, value) {
|
|
5292
|
+
map.on = map.on.filter((item) => item !== id);
|
|
5293
|
+
map.off = map.off.filter((item) => item !== id);
|
|
5294
|
+
map[value ? "on" : "off"].push(id);
|
|
5295
|
+
}
|
|
5296
|
+
function writeRows(choices, id, rows) {
|
|
5297
|
+
removeId(choices, id);
|
|
5298
|
+
put(choices.global, id, rows.global);
|
|
5299
|
+
for (const [scopeId, on] of rows.agents) {
|
|
5300
|
+
const target = choices.agents[scopeId] ??= { on: [], off: [] };
|
|
5301
|
+
put(target, id, on);
|
|
5302
|
+
}
|
|
5303
|
+
for (const [scopeId, on] of rows.projects) {
|
|
5304
|
+
const target = choices.projects[scopeId] ??= { on: [], off: [] };
|
|
5305
|
+
put(target, id, on);
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
5308
|
+
function readValue(item, id) {
|
|
5309
|
+
if (item?.off.includes(id)) return false;
|
|
5310
|
+
if (item?.on.includes(id)) return true;
|
|
5311
|
+
return null;
|
|
5312
|
+
}
|
|
5313
|
+
function verifyRows(choices, id, expected) {
|
|
5314
|
+
if (readValue(choices.global, id) !== expected.global) return false;
|
|
5315
|
+
for (const [scopeId, on] of expected.agents) {
|
|
5316
|
+
if (readValue(choices.agents[scopeId], id) !== on) return false;
|
|
5317
|
+
}
|
|
5318
|
+
for (const [scopeId, on] of expected.projects) {
|
|
5319
|
+
if (readValue(choices.projects[scopeId], id) !== on) return false;
|
|
5320
|
+
}
|
|
5321
|
+
return true;
|
|
5322
|
+
}
|
|
5323
|
+
function backup(source2, target) {
|
|
5324
|
+
if (!existsSync10(source2)) return false;
|
|
5325
|
+
if (!existsSync10(target)) atomicWrite(target, readFileSync14(source2));
|
|
5326
|
+
chmodSync6(target, 384);
|
|
5327
|
+
return true;
|
|
5328
|
+
}
|
|
5329
|
+
function setPrivate(path3) {
|
|
5330
|
+
try {
|
|
5331
|
+
chmodSync6(path3, 384);
|
|
5332
|
+
} catch {
|
|
5333
|
+
}
|
|
5334
|
+
}
|
|
5335
|
+
function stripScopes(stateDir) {
|
|
5336
|
+
const path3 = connectionPath(stateDir);
|
|
5337
|
+
if (!existsSync10(path3)) return;
|
|
5338
|
+
const stored = readJson(path3);
|
|
5339
|
+
if (!Array.isArray(stored)) throw new Error("Stored connector data must be a list before its scopes can be migrated.");
|
|
5340
|
+
const records = stored.map((item) => {
|
|
5341
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error("A stored connector record is invalid; no scope data was removed.");
|
|
5342
|
+
const { global: _global, agentIds: _agentIds, projectIds: _projectIds, excludedAgentIds: _excludedAgentIds, excludedProjectIds: _excludedProjectIds, ...connection } = item;
|
|
5343
|
+
return connection;
|
|
5344
|
+
});
|
|
5345
|
+
writeJson(path3, records);
|
|
5346
|
+
setPrivate(path3);
|
|
5347
|
+
}
|
|
5348
|
+
function migrateConnectionScopes(root, stateDir) {
|
|
5349
|
+
const marker = readJson(recordPath2(root));
|
|
5350
|
+
if (marker?.version === 1 && marker.status === "complete") return marker;
|
|
5351
|
+
if (marker?.version === 1 && marker.status === "attachments-written") {
|
|
5352
|
+
stripScopes(stateDir);
|
|
5353
|
+
const complete = { ...marker, status: "complete" };
|
|
5354
|
+
writeJson(recordPath2(root), complete);
|
|
5355
|
+
return complete;
|
|
5356
|
+
}
|
|
5357
|
+
const connectionsFile = connectionPath(stateDir);
|
|
5358
|
+
const hadConnections = existsSync10(connectionsFile);
|
|
5359
|
+
const raw = hadConnections ? readJson(connectionsFile) : [];
|
|
5360
|
+
if (!Array.isArray(raw)) throw new Error("Stored connector data must be a list before its scopes can be migrated.");
|
|
5361
|
+
const parsed2 = raw.map((item) => {
|
|
5362
|
+
const result = connectionSchema.safeParse(item);
|
|
5363
|
+
if (!result.success) throw new Error("A stored connector record is invalid; no scope data was removed.");
|
|
5364
|
+
return result.data;
|
|
5365
|
+
});
|
|
5366
|
+
const connectionBackup = hadConnections ? connectionsBackupPath(stateDir) : null;
|
|
5367
|
+
const hadAttachments = existsSync10(attachmentsPath(root));
|
|
5368
|
+
const attachmentBackup = hadAttachments ? attachmentsBackupPath(root) : null;
|
|
5369
|
+
const migration = {
|
|
5370
|
+
version: 1,
|
|
5371
|
+
status: "attachments-written",
|
|
5372
|
+
connectionCount: parsed2.length,
|
|
5373
|
+
scopeAssignmentCount: parsed2.reduce((count, connection) => count + assignmentCount(scopeRows(connection)), 0),
|
|
5374
|
+
connectionsBackup: connectionBackup,
|
|
5375
|
+
attachmentsBackup: attachmentBackup,
|
|
5376
|
+
hadAttachments
|
|
5377
|
+
};
|
|
5378
|
+
backup(connectionsFile, connectionBackup ?? "");
|
|
5379
|
+
backup(attachmentsPath(root), attachmentBackup ?? "");
|
|
5380
|
+
try {
|
|
5381
|
+
const choices = load2(root);
|
|
5382
|
+
const expected = /* @__PURE__ */ new Map();
|
|
5383
|
+
for (const connection of parsed2) {
|
|
5384
|
+
const id = connectionCapabilityId(connection.id);
|
|
5385
|
+
const rows = scopeRows(connection);
|
|
5386
|
+
expected.set(id, rows);
|
|
5387
|
+
writeRows(choices, id, rows);
|
|
5388
|
+
}
|
|
5389
|
+
const migratedAssignments = [...expected.values()].reduce((count, rows) => count + assignmentCount(rows), 0);
|
|
5390
|
+
if (migratedAssignments !== migration.scopeAssignmentCount || [...expected].some(([id, rows]) => !verifyRows(choices, id, rows))) {
|
|
5391
|
+
throw new Error("Connector scope count check failed; the original files were restored.");
|
|
5392
|
+
}
|
|
5393
|
+
save(root, choices);
|
|
5394
|
+
const saved = load2(root);
|
|
5395
|
+
if ([...expected].some(([id, rows]) => !verifyRows(saved, id, rows))) throw new Error("Connector scope count check failed after saving; the original files were restored.");
|
|
5396
|
+
writeJson(recordPath2(root), migration);
|
|
5397
|
+
stripScopes(stateDir);
|
|
5398
|
+
const complete = { ...migration, status: "complete" };
|
|
5399
|
+
writeJson(recordPath2(root), complete);
|
|
5400
|
+
return complete;
|
|
5401
|
+
} catch (error) {
|
|
5402
|
+
if (attachmentBackup && existsSync10(attachmentBackup)) atomicWrite(attachmentsPath(root), readFileSync14(attachmentBackup));
|
|
5403
|
+
else if (!hadAttachments && existsSync10(attachmentsPath(root))) unlinkSync3(attachmentsPath(root));
|
|
5404
|
+
if (connectionBackup && existsSync10(connectionBackup)) atomicWrite(connectionsFile, readFileSync14(connectionBackup));
|
|
5405
|
+
try {
|
|
5406
|
+
unlinkSync3(recordPath2(root));
|
|
5407
|
+
} catch {
|
|
5408
|
+
}
|
|
5409
|
+
throw error;
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
|
|
5116
5413
|
// src/mcp/connections.ts
|
|
5117
5414
|
var OAUTH_STATE = "oauth";
|
|
5118
5415
|
var OAUTH_CLIENT_ID = "oauth_client_id";
|
|
@@ -5133,6 +5430,17 @@ function allowed(connection, scope) {
|
|
|
5133
5430
|
if (scope.projectId && connection.excludedProjectIds.includes(scope.projectId)) return false;
|
|
5134
5431
|
return connection.global || Boolean(scope.agentId && connection.agentIds.includes(scope.agentId)) || Boolean(scope.projectId && connection.projectIds.includes(scope.projectId));
|
|
5135
5432
|
}
|
|
5433
|
+
function setAgentScope(all2, agentId, ids) {
|
|
5434
|
+
for (const connection of all2) {
|
|
5435
|
+
connection.agentIds = connection.agentIds.filter((id) => id !== agentId);
|
|
5436
|
+
connection.excludedAgentIds = connection.excludedAgentIds.filter((id) => id !== agentId);
|
|
5437
|
+
if (ids.includes(connection.id)) {
|
|
5438
|
+
if (!connection.global) connection.agentIds.push(agentId);
|
|
5439
|
+
} else if (connection.global) {
|
|
5440
|
+
connection.excludedAgentIds.push(agentId);
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
5136
5444
|
function setScope(connection, scope, on) {
|
|
5137
5445
|
const without = (ids, id) => ids.filter((item) => item !== id);
|
|
5138
5446
|
if (scope.agentId) {
|
|
@@ -5203,23 +5511,48 @@ function scrubWith(value, secrets) {
|
|
|
5203
5511
|
function revisionOf(all2) {
|
|
5204
5512
|
return all2.map((item) => ({ id: item.id, enabled: item.enabled, global: item.global, agents: item.agentIds, projects: item.projectIds, excludedAgents: item.excludedAgentIds, excludedProjects: item.excludedProjectIds, checkedAt: item.checkedAt, error: item.error }));
|
|
5205
5513
|
}
|
|
5514
|
+
function sameScope(left, right) {
|
|
5515
|
+
const ids = (values2) => [...values2].sort();
|
|
5516
|
+
return left.global === right.global && JSON.stringify(ids(left.agentIds)) === JSON.stringify(ids(right.agentIds)) && JSON.stringify(ids(left.projectIds)) === JSON.stringify(ids(right.projectIds)) && JSON.stringify(ids(left.excludedAgentIds)) === JSON.stringify(ids(right.excludedAgentIds)) && JSON.stringify(ids(left.excludedProjectIds)) === JSON.stringify(ids(right.excludedProjectIds));
|
|
5517
|
+
}
|
|
5518
|
+
function hasScope(connection) {
|
|
5519
|
+
return connection.global || connection.agentIds.length > 0 || connection.projectIds.length > 0 || connection.excludedAgentIds.length > 0 || connection.excludedProjectIds.length > 0;
|
|
5520
|
+
}
|
|
5521
|
+
function scopeMigrationError() {
|
|
5522
|
+
return new HarnessError("connection_scope_migration_pending", "Connector access settings cannot be changed until their scope migration succeeds. Reopen the data root to retry it.");
|
|
5523
|
+
}
|
|
5206
5524
|
var Connections = class {
|
|
5207
|
-
constructor(stateDir, secrets, bus) {
|
|
5525
|
+
constructor(stateDir, secrets, bus, attachmentRoot = () => null) {
|
|
5208
5526
|
this.secrets = secrets;
|
|
5209
5527
|
this.bus = bus;
|
|
5210
|
-
this.
|
|
5528
|
+
this.attachmentRoot = attachmentRoot;
|
|
5529
|
+
this.path = join27(stateDir, "connections.json");
|
|
5211
5530
|
}
|
|
5212
5531
|
path;
|
|
5213
5532
|
/** Every secret value a connection holds, so results can be redacted without waiting on the store. */
|
|
5214
5533
|
known = /* @__PURE__ */ new Map();
|
|
5215
5534
|
list() {
|
|
5216
5535
|
const stored = readJson(this.path) ?? [];
|
|
5536
|
+
const root = this.attachmentRoot();
|
|
5537
|
+
const choices = root ? load2(root) : null;
|
|
5217
5538
|
return stored.flatMap((item) => {
|
|
5218
5539
|
const parsed2 = connectionSchema.safeParse(item);
|
|
5219
5540
|
if (!parsed2.success) return [];
|
|
5220
5541
|
const connection = parsed2.data;
|
|
5221
5542
|
if (!connection.auth) connection.auth = "none";
|
|
5222
5543
|
if (connection.kind === "workspace" && connection.name === "X Workspace") connection.name = "Xingular Workspace";
|
|
5544
|
+
if (choices) {
|
|
5545
|
+
const id = connectionCapabilityId(connection.id);
|
|
5546
|
+
const hasChoice = choices.global.on.includes(id) || choices.global.off.includes(id) || Object.values(choices.agents).some((entry) => entry.on.includes(id) || entry.off.includes(id)) || Object.values(choices.projects).some((entry) => entry.on.includes(id) || entry.off.includes(id));
|
|
5547
|
+
if (hasChoice || !(connection.global || connection.agentIds.length || connection.projectIds.length || connection.excludedAgentIds.length || connection.excludedProjectIds.length)) {
|
|
5548
|
+
const attachment = describe(choices, id, connection.global);
|
|
5549
|
+
connection.global = attachment.global;
|
|
5550
|
+
connection.agentIds = attachment.on.agentIds;
|
|
5551
|
+
connection.projectIds = attachment.on.projectIds;
|
|
5552
|
+
connection.excludedAgentIds = attachment.off.agentIds;
|
|
5553
|
+
connection.excludedProjectIds = attachment.off.projectIds;
|
|
5554
|
+
}
|
|
5555
|
+
}
|
|
5223
5556
|
return [connection];
|
|
5224
5557
|
});
|
|
5225
5558
|
}
|
|
@@ -5235,12 +5568,49 @@ var Connections = class {
|
|
|
5235
5568
|
return revisionOf(this.list());
|
|
5236
5569
|
}
|
|
5237
5570
|
write(all2) {
|
|
5238
|
-
|
|
5571
|
+
const root = this.attachmentRoot();
|
|
5572
|
+
let stored = all2;
|
|
5573
|
+
if (root && connectionScopeMigrationComplete(root)) {
|
|
5574
|
+
stored = all2.map(({ global: _global, agentIds: _agentIds, projectIds: _projectIds, excludedAgentIds: _excludedAgentIds, excludedProjectIds: _excludedProjectIds, ...connection }) => connection);
|
|
5575
|
+
} else if (root) {
|
|
5576
|
+
const previous = /* @__PURE__ */ new Map();
|
|
5577
|
+
for (const item of readJson(this.path) ?? []) {
|
|
5578
|
+
const parsed2 = connectionSchema.safeParse(item);
|
|
5579
|
+
if (parsed2.success) previous.set(parsed2.data.id, parsed2.data);
|
|
5580
|
+
}
|
|
5581
|
+
stored = all2.map((connection) => {
|
|
5582
|
+
const legacy = previous.get(connection.id);
|
|
5583
|
+
return legacy ? {
|
|
5584
|
+
...connection,
|
|
5585
|
+
global: legacy.global,
|
|
5586
|
+
agentIds: legacy.agentIds,
|
|
5587
|
+
projectIds: legacy.projectIds,
|
|
5588
|
+
excludedAgentIds: legacy.excludedAgentIds,
|
|
5589
|
+
excludedProjectIds: legacy.excludedProjectIds
|
|
5590
|
+
} : connection;
|
|
5591
|
+
});
|
|
5592
|
+
}
|
|
5593
|
+
writeJson(this.path, stored);
|
|
5239
5594
|
try {
|
|
5240
|
-
|
|
5595
|
+
chmodSync7(this.path, 384);
|
|
5241
5596
|
} catch {
|
|
5242
5597
|
}
|
|
5243
5598
|
}
|
|
5599
|
+
writeScope(root, connection) {
|
|
5600
|
+
const choices = load2(root);
|
|
5601
|
+
const id = connectionCapabilityId(connection.id);
|
|
5602
|
+
forgetId(choices, id);
|
|
5603
|
+
set(choices, [id], {}, connection.global, false);
|
|
5604
|
+
for (const agentId of /* @__PURE__ */ new Set([...connection.agentIds, ...connection.excludedAgentIds])) {
|
|
5605
|
+
const on = connection.agentIds.includes(agentId) && !connection.excludedAgentIds.includes(agentId);
|
|
5606
|
+
set(choices, [id], { agentId }, on, connection.global);
|
|
5607
|
+
}
|
|
5608
|
+
for (const projectId of /* @__PURE__ */ new Set([...connection.projectIds, ...connection.excludedProjectIds])) {
|
|
5609
|
+
const on = connection.projectIds.includes(projectId) && !connection.excludedProjectIds.includes(projectId);
|
|
5610
|
+
set(choices, [id], { projectId }, on, connection.global);
|
|
5611
|
+
}
|
|
5612
|
+
save(root, choices);
|
|
5613
|
+
}
|
|
5244
5614
|
changed() {
|
|
5245
5615
|
this.bus.emit({ type: "connections.changed" });
|
|
5246
5616
|
}
|
|
@@ -5249,17 +5619,95 @@ var Connections = class {
|
|
|
5249
5619
|
const all2 = this.list();
|
|
5250
5620
|
const connection = all2.find((item) => item.id === id);
|
|
5251
5621
|
if (!connection) throw notFound("Connection");
|
|
5622
|
+
const before = { ...connection, agentIds: [...connection.agentIds], projectIds: [...connection.projectIds], excludedAgentIds: [...connection.excludedAgentIds], excludedProjectIds: [...connection.excludedProjectIds] };
|
|
5252
5623
|
change(connection);
|
|
5624
|
+
const root = this.attachmentRoot();
|
|
5625
|
+
if (root && !connectionScopeMigrationComplete(root) && !sameScope(before, connection)) throw scopeMigrationError();
|
|
5253
5626
|
this.write(all2);
|
|
5254
5627
|
return connection;
|
|
5255
5628
|
}
|
|
5256
5629
|
/** Read-modify-write of every connection. */
|
|
5257
5630
|
updateAll(change) {
|
|
5258
5631
|
const all2 = this.list();
|
|
5632
|
+
const before = new Map(all2.map((connection) => [connection.id, {
|
|
5633
|
+
...connection,
|
|
5634
|
+
agentIds: [...connection.agentIds],
|
|
5635
|
+
projectIds: [...connection.projectIds],
|
|
5636
|
+
excludedAgentIds: [...connection.excludedAgentIds],
|
|
5637
|
+
excludedProjectIds: [...connection.excludedProjectIds]
|
|
5638
|
+
}]));
|
|
5259
5639
|
change(all2);
|
|
5640
|
+
const root = this.attachmentRoot();
|
|
5641
|
+
if (root && !connectionScopeMigrationComplete(root)) {
|
|
5642
|
+
const remainingIds = new Set(all2.map((connection) => connection.id));
|
|
5643
|
+
const scopeChanged = all2.some((connection) => {
|
|
5644
|
+
const previous = before.get(connection.id);
|
|
5645
|
+
return previous ? !sameScope(previous, connection) : hasScope(connection);
|
|
5646
|
+
}) || [...before].some(([id, connection]) => !remainingIds.has(id) && hasScope(connection));
|
|
5647
|
+
if (scopeChanged) throw scopeMigrationError();
|
|
5648
|
+
}
|
|
5260
5649
|
this.write(all2);
|
|
5261
5650
|
this.changed();
|
|
5262
5651
|
}
|
|
5652
|
+
/** Switches one connector in the shared attachment store, preserving all other scopes. */
|
|
5653
|
+
setScope(connectionId, scope, on) {
|
|
5654
|
+
const connection = this.find(connectionId);
|
|
5655
|
+
const root = this.attachmentRoot();
|
|
5656
|
+
if (root && !connectionScopeMigrationComplete(root)) throw scopeMigrationError();
|
|
5657
|
+
if (!root) {
|
|
5658
|
+
this.update(connectionId, (item) => setScope(item, scope, on));
|
|
5659
|
+
this.changed();
|
|
5660
|
+
return this.forScope(scope);
|
|
5661
|
+
}
|
|
5662
|
+
const choices = load2(root);
|
|
5663
|
+
const fallback = connection.global;
|
|
5664
|
+
set(choices, [connectionCapabilityId(connectionId)], scope, on, fallback);
|
|
5665
|
+
save(root, choices);
|
|
5666
|
+
this.changed();
|
|
5667
|
+
return this.forScope(scope);
|
|
5668
|
+
}
|
|
5669
|
+
/** Gives one agent an exact connector set without changing project or global choices. */
|
|
5670
|
+
setAgentScope(agentId, ids) {
|
|
5671
|
+
const all2 = this.list();
|
|
5672
|
+
const root = this.attachmentRoot();
|
|
5673
|
+
if (root && !connectionScopeMigrationComplete(root)) throw scopeMigrationError();
|
|
5674
|
+
if (!root) {
|
|
5675
|
+
setAgentScope(all2, agentId, ids);
|
|
5676
|
+
this.write(all2);
|
|
5677
|
+
this.changed();
|
|
5678
|
+
return this.forScope({ agentId });
|
|
5679
|
+
}
|
|
5680
|
+
const choices = load2(root);
|
|
5681
|
+
for (const connection of all2) {
|
|
5682
|
+
set(choices, [connectionCapabilityId(connection.id)], { agentId }, ids.includes(connection.id), connection.global);
|
|
5683
|
+
}
|
|
5684
|
+
save(root, choices);
|
|
5685
|
+
this.changed();
|
|
5686
|
+
return this.forScope({ agentId });
|
|
5687
|
+
}
|
|
5688
|
+
/** Removes only connector choices when an agent is deleted; other capability choices are separate. */
|
|
5689
|
+
removeAgentScope(agentId) {
|
|
5690
|
+
const root = this.attachmentRoot();
|
|
5691
|
+
if (root && !connectionScopeMigrationComplete(root)) return;
|
|
5692
|
+
if (!root) {
|
|
5693
|
+
this.updateAll((all2) => {
|
|
5694
|
+
for (const connection of all2) {
|
|
5695
|
+
connection.agentIds = connection.agentIds.filter((id) => id !== agentId);
|
|
5696
|
+
connection.excludedAgentIds = connection.excludedAgentIds.filter((id) => id !== agentId);
|
|
5697
|
+
}
|
|
5698
|
+
});
|
|
5699
|
+
return;
|
|
5700
|
+
}
|
|
5701
|
+
const choices = load2(root);
|
|
5702
|
+
const entry = choices.agents[agentId];
|
|
5703
|
+
if (!entry) return;
|
|
5704
|
+
const isConnector = (id) => id.startsWith("connected-apps/connection/");
|
|
5705
|
+
entry.on = entry.on.filter((id) => !isConnector(id));
|
|
5706
|
+
entry.off = entry.off.filter((id) => !isConnector(id));
|
|
5707
|
+
if (!entry.on.length && !entry.off.length) delete choices.agents[agentId];
|
|
5708
|
+
save(root, choices);
|
|
5709
|
+
this.changed();
|
|
5710
|
+
}
|
|
5263
5711
|
secret(connectionId, name) {
|
|
5264
5712
|
return this.secrets.get(secretKey(`mcp--${connectionId}`, name));
|
|
5265
5713
|
}
|
|
@@ -5387,6 +5835,10 @@ var Connections = class {
|
|
|
5387
5835
|
if (connection.cwd && (!isAbsolute3(connection.cwd) || !isDir(connection.cwd))) throw invalid("Working directory must be an existing absolute directory.");
|
|
5388
5836
|
const all2 = this.list();
|
|
5389
5837
|
const existing = all2.find((item) => item.id === connection.id);
|
|
5838
|
+
const root = this.attachmentRoot();
|
|
5839
|
+
if (root && !connectionScopeMigrationComplete(root) && (existing ? !sameScope(existing, connection) : hasScope(connection))) {
|
|
5840
|
+
throw scopeMigrationError();
|
|
5841
|
+
}
|
|
5390
5842
|
connection.envKeys = [...existing?.envKeys ?? []];
|
|
5391
5843
|
connection.headerKeys = [...existing?.headerKeys ?? []];
|
|
5392
5844
|
const sameServer = (existing?.url ?? null) === connection.url && (existing?.auth ?? "") === connection.auth && !oauthCredentials;
|
|
@@ -5417,6 +5869,7 @@ var Connections = class {
|
|
|
5417
5869
|
connection.envKeys = connection.envKeys.filter((value) => value !== key);
|
|
5418
5870
|
}
|
|
5419
5871
|
Object.assign(connection, { checkedAt: null, tools: [], serverName: null, error: null });
|
|
5872
|
+
if (root && connectionScopeMigrationComplete(root)) this.writeScope(root, connection);
|
|
5420
5873
|
this.write([...all2.filter((item) => item.id !== connection.id), connection]);
|
|
5421
5874
|
await this.refreshSecrets();
|
|
5422
5875
|
this.changed();
|
|
@@ -5444,6 +5897,12 @@ var Connections = class {
|
|
|
5444
5897
|
for (const name of [OAUTH_STATE, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET]) await this.deleteSecret(connection.id, name);
|
|
5445
5898
|
}
|
|
5446
5899
|
this.write(all2.filter((item) => item.id !== connectionId));
|
|
5900
|
+
const root = this.attachmentRoot();
|
|
5901
|
+
if (root) {
|
|
5902
|
+
const choices = load2(root);
|
|
5903
|
+
forgetId(choices, connectionCapabilityId(connectionId));
|
|
5904
|
+
save(root, choices);
|
|
5905
|
+
}
|
|
5447
5906
|
this.known.delete(connectionId);
|
|
5448
5907
|
this.changed();
|
|
5449
5908
|
}
|
|
@@ -5463,7 +5922,7 @@ var Connections = class {
|
|
|
5463
5922
|
var size = (path3) => {
|
|
5464
5923
|
if (!path3) return null;
|
|
5465
5924
|
try {
|
|
5466
|
-
return charCount(
|
|
5925
|
+
return charCount(readFileSync15(path3, "utf8"));
|
|
5467
5926
|
} catch {
|
|
5468
5927
|
return null;
|
|
5469
5928
|
}
|
|
@@ -5563,7 +6022,7 @@ async function list2(harness, scope = {}) {
|
|
|
5563
6022
|
loading: await loadingFor(harness, scope),
|
|
5564
6023
|
cwd: scope.cwd || null,
|
|
5565
6024
|
sharedDir: sharedDir(harness.accounts.dir),
|
|
5566
|
-
packagesDir:
|
|
6025
|
+
packagesDir: join28(harness.env.root, "plugins"),
|
|
5567
6026
|
registered: harness.plugins.registered(),
|
|
5568
6027
|
hooks: harness.hooks.status(),
|
|
5569
6028
|
attachments: load2(harness.env.root),
|
|
@@ -5650,7 +6109,7 @@ function forgetAgent(harness, agentId) {
|
|
|
5650
6109
|
}
|
|
5651
6110
|
|
|
5652
6111
|
// src/capabilities/api.ts
|
|
5653
|
-
var
|
|
6112
|
+
var unique2 = (values2) => [...new Set(values2.filter((value) => value.length > 0))];
|
|
5654
6113
|
function contributions(plugins) {
|
|
5655
6114
|
return plugins.flatMap((plugin) => plugin.contributions.map((item) => ({
|
|
5656
6115
|
id: item.id,
|
|
@@ -5667,17 +6126,26 @@ async function list3(harness, params = {}) {
|
|
|
5667
6126
|
const plugins = await list2(harness, params);
|
|
5668
6127
|
return { plugins, capabilities: contributions(plugins) };
|
|
5669
6128
|
}
|
|
5670
|
-
async function
|
|
6129
|
+
async function resolve5(harness, params) {
|
|
5671
6130
|
const caller = { agentId: params.agentId ?? null, projectId: params.projectId ?? null };
|
|
5672
6131
|
const catalogue2 = await list3(harness, params);
|
|
5673
6132
|
const connectors = harness.apps.connections.forScope(caller).map((connection) => connection.id);
|
|
5674
6133
|
return {
|
|
5675
6134
|
version: 1,
|
|
5676
|
-
tools:
|
|
5677
|
-
connectors:
|
|
6135
|
+
tools: unique2(params.tools ?? []),
|
|
6136
|
+
connectors: unique2(connectors),
|
|
5678
6137
|
contributions: catalogue2.capabilities.filter((item) => item.state === "on").map((item) => item.id)
|
|
5679
6138
|
};
|
|
5680
6139
|
}
|
|
6140
|
+
function toolIntersection(parent, assignee, taskTools) {
|
|
6141
|
+
const permitted = taskTools === void 0 ? null : new Set(taskTools);
|
|
6142
|
+
return {
|
|
6143
|
+
version: 1,
|
|
6144
|
+
tools: parent.tools.filter((tool) => assignee.tools.includes(tool) && (!permitted || permitted.has(tool))),
|
|
6145
|
+
connectors: parent.connectors.filter((id) => assignee.connectors.includes(id)),
|
|
6146
|
+
contributions: parent.contributions.filter((id) => assignee.contributions.includes(id))
|
|
6147
|
+
};
|
|
6148
|
+
}
|
|
5681
6149
|
function inventory2(harness, params = {}) {
|
|
5682
6150
|
return inventoryFor(harness, params);
|
|
5683
6151
|
}
|
|
@@ -5692,7 +6160,7 @@ async function switchCapability(harness, params, on) {
|
|
|
5692
6160
|
|
|
5693
6161
|
// src/mcp/service.ts
|
|
5694
6162
|
import { createHash as createHash4, randomUUID as randomUUID5 } from "node:crypto";
|
|
5695
|
-
import { join as
|
|
6163
|
+
import { join as join29 } from "node:path";
|
|
5696
6164
|
|
|
5697
6165
|
// src/mcp/manager.ts
|
|
5698
6166
|
import { Client, SSEClientTransport, StreamableHTTPClientTransport, UnauthorizedError as UnauthorizedError2 } from "@modelcontextprotocol/client";
|
|
@@ -5781,8 +6249,8 @@ var cancelActive = null;
|
|
|
5781
6249
|
function listenForCallback(expectedState, clientName) {
|
|
5782
6250
|
return new Promise((ready, failed) => {
|
|
5783
6251
|
let settle;
|
|
5784
|
-
const result = new Promise((
|
|
5785
|
-
settle = { resolve:
|
|
6252
|
+
const result = new Promise((resolve9, reject) => {
|
|
6253
|
+
settle = { resolve: resolve9, reject };
|
|
5786
6254
|
});
|
|
5787
6255
|
result.catch(() => void 0);
|
|
5788
6256
|
const server = createServer((request2, response3) => {
|
|
@@ -5806,7 +6274,7 @@ function listenForCallback(expectedState, clientName) {
|
|
|
5806
6274
|
clearTimeout(timer);
|
|
5807
6275
|
failed(new HarnessError("invalid_request", error.code === "EADDRINUSE" ? `Port ${MCP_OAUTH_PORT} is in use. Finish or cancel the other sign-in, or quit the program using that port, then try again.` : "The sign-in listener could not start."));
|
|
5808
6276
|
});
|
|
5809
|
-
const closed = new Promise((
|
|
6277
|
+
const closed = new Promise((resolve9) => server.once("close", resolve9));
|
|
5810
6278
|
const close = () => {
|
|
5811
6279
|
clearTimeout(timer);
|
|
5812
6280
|
if (cancelActive === cancel) cancelActive = null;
|
|
@@ -5991,7 +6459,7 @@ var UNATTENDED_SCOPE = "plugin-hooks";
|
|
|
5991
6459
|
var ConnectedApps = class {
|
|
5992
6460
|
constructor(options2) {
|
|
5993
6461
|
this.options = options2;
|
|
5994
|
-
this.connections = new Connections(options2.stateDir, options2.secrets, options2.bus);
|
|
6462
|
+
this.connections = new Connections(options2.stateDir, options2.secrets, options2.bus, () => options2.env.dataRoot);
|
|
5995
6463
|
this.oauth = {
|
|
5996
6464
|
clientName: options2.env.productName,
|
|
5997
6465
|
load: (id) => this.connections.oauthLoad(id),
|
|
@@ -6086,7 +6554,7 @@ var ConnectedApps = class {
|
|
|
6086
6554
|
return { tools: tools3, errors: [] };
|
|
6087
6555
|
} catch (error) {
|
|
6088
6556
|
const unauthorized = isSignInRequired(error);
|
|
6089
|
-
const message = unauthorized ? signInRequired(connection).message : `${connection.name} could not load its tools. Check
|
|
6557
|
+
const message = unauthorized ? signInRequired(connection).message : `${connection.name} could not load its tools. Check its scope in Capability \u2192 Connectors.`;
|
|
6090
6558
|
if (unauthorized) await this.manager.close(connection.id, session.sessionId);
|
|
6091
6559
|
this.connectionFailed(connection.id, unauthorized);
|
|
6092
6560
|
return { tools: [], errors: [{ connection: connection.name, message }] };
|
|
@@ -6206,7 +6674,7 @@ ${action}`, standing: "switch", timeoutMs: 3e5 });
|
|
|
6206
6674
|
}).slice(0, 100);
|
|
6207
6675
|
const trace = { id: toolCallId, connectionId: connection.id, connectionName: connection.name, tool, at: now(), ok, summary: tool, result: takeChars(raw, 24e3), sources, truncated: charCount(raw) > 24e3 || structured?.truncated === true };
|
|
6208
6676
|
try {
|
|
6209
|
-
const file =
|
|
6677
|
+
const file = join29(runDir(this.options.env.root, runId), "mcp-context.json");
|
|
6210
6678
|
writeJson(file, [...readJson(file) ?? [], trace]);
|
|
6211
6679
|
} catch {
|
|
6212
6680
|
}
|
|
@@ -6214,7 +6682,7 @@ ${action}`, standing: "switch", timeoutMs: 3e5 });
|
|
|
6214
6682
|
}
|
|
6215
6683
|
/** What a run retrieved from connected apps, in order. */
|
|
6216
6684
|
runContext(runId) {
|
|
6217
|
-
return readJson(
|
|
6685
|
+
return readJson(join29(runDir(this.options.env.root, runId), "mcp-context.json")) ?? [];
|
|
6218
6686
|
}
|
|
6219
6687
|
/** Changes whenever what a session can discover may have changed. */
|
|
6220
6688
|
async revision(session) {
|
|
@@ -6230,8 +6698,8 @@ ${action}`, standing: "switch", timeoutMs: 3e5 });
|
|
|
6230
6698
|
return [
|
|
6231
6699
|
{
|
|
6232
6700
|
name: "mcp_list_tools",
|
|
6233
|
-
label: "App and
|
|
6234
|
-
description: `Discover enabled ${this.options.env.productName} app operations and
|
|
6701
|
+
label: "App and connector tools",
|
|
6702
|
+
description: `Discover enabled ${this.options.env.productName} app operations and connector tools by name. Pass tool to get one tool with its input schema.${this.brokered?.discoveryNote ? ` ${this.brokered.discoveryNote}` : ""}`,
|
|
6235
6703
|
parameters: (Type) => Type.Object({
|
|
6236
6704
|
tool: Type.Optional(Type.String({ description: "One tool name, to get its input schema." })),
|
|
6237
6705
|
query: Type.Optional(Type.String({ description: "Only tools whose name or description contains this text." })),
|
|
@@ -6247,8 +6715,8 @@ ${action}`, standing: "switch", timeoutMs: 3e5 });
|
|
|
6247
6715
|
},
|
|
6248
6716
|
{
|
|
6249
6717
|
name: "mcp_call_tool",
|
|
6250
|
-
label: "Use
|
|
6251
|
-
description: "Invoke an enabled app or
|
|
6718
|
+
label: "Use connector",
|
|
6719
|
+
description: "Invoke an enabled app or connector tool returned by mcp_list_tools. Current settings are checked on every call. Changes require approval.",
|
|
6252
6720
|
parameters: (Type) => Type.Object({ name: Type.String(), arguments: Type.Record(Type.String(), Type.Unknown()) }),
|
|
6253
6721
|
execute: async (input) => JSON.stringify(await this.invoke(session, String(input.name), input.arguments ?? {}))
|
|
6254
6722
|
}
|
|
@@ -6325,23 +6793,13 @@ ${action}`, standing: "switch", timeoutMs: 3e5 });
|
|
|
6325
6793
|
}
|
|
6326
6794
|
/** Gives one agent exactly these connections, if its creator may reach each of them. */
|
|
6327
6795
|
setAgentScope(creator, agentId, ids) {
|
|
6328
|
-
this.connections.
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
connection.agentIds = connection.agentIds.filter((id) => id !== agentId);
|
|
6336
|
-
connection.excludedAgentIds = connection.excludedAgentIds.filter((id) => id !== agentId);
|
|
6337
|
-
if (ids.includes(connection.id)) {
|
|
6338
|
-
if (!connection.global) connection.agentIds.push(agentId);
|
|
6339
|
-
} else if (connection.global) {
|
|
6340
|
-
connection.excludedAgentIds.push(agentId);
|
|
6341
|
-
}
|
|
6342
|
-
}
|
|
6343
|
-
});
|
|
6344
|
-
return this.connections.forScope({ agentId });
|
|
6796
|
+
const all2 = this.connections.list();
|
|
6797
|
+
for (const id of ids) {
|
|
6798
|
+
const connection = all2.find((item) => item.id === id);
|
|
6799
|
+
if (!connection) throw notFound("Connection");
|
|
6800
|
+
if (!allowed(connection, creator)) throw new HarnessError("required_connections_unavailable", `${connection.name} is disabled or outside your connection scope.`);
|
|
6801
|
+
}
|
|
6802
|
+
return this.connections.setAgentScope(agentId, ids);
|
|
6345
6803
|
}
|
|
6346
6804
|
/**
|
|
6347
6805
|
* A call with no session and nobody to ask, made by a plugin's hook. It reaches a connection
|
|
@@ -6358,21 +6816,13 @@ ${action}`, standing: "switch", timeoutMs: 3e5 });
|
|
|
6358
6816
|
}
|
|
6359
6817
|
/** Switches one saved connection on or off for everyone, one agent, or one project. */
|
|
6360
6818
|
attach(connectionId, scope, on) {
|
|
6361
|
-
this.connections.
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
if (!connection.enabled) throw invalid(`${connection.name} is switched off. Switch it on in its own settings first.`);
|
|
6365
|
-
setScope(connection, scope, on);
|
|
6366
|
-
});
|
|
6819
|
+
const connection = this.connections.find(connectionId);
|
|
6820
|
+
if (!connection.enabled) throw invalid(`${connection.name} is switched off. Switch it on in its own settings first.`);
|
|
6821
|
+
this.connections.setScope(connectionId, scope, on);
|
|
6367
6822
|
}
|
|
6368
6823
|
/** Forgets an agent that no longer exists. */
|
|
6369
6824
|
removeAgentScope(agentId) {
|
|
6370
|
-
this.connections.
|
|
6371
|
-
for (const connection of all2) {
|
|
6372
|
-
connection.agentIds = connection.agentIds.filter((id) => id !== agentId);
|
|
6373
|
-
connection.excludedAgentIds = connection.excludedAgentIds.filter((id) => id !== agentId);
|
|
6374
|
-
}
|
|
6375
|
-
});
|
|
6825
|
+
this.connections.removeAgentScope(agentId);
|
|
6376
6826
|
}
|
|
6377
6827
|
async close() {
|
|
6378
6828
|
await this.manager.close(void 0, UNATTENDED_SCOPE).catch(() => void 0);
|
|
@@ -6381,56 +6831,56 @@ ${action}`, standing: "switch", timeoutMs: 3e5 });
|
|
|
6381
6831
|
};
|
|
6382
6832
|
|
|
6383
6833
|
// src/memory/maintenance.ts
|
|
6384
|
-
import { readFileSync as
|
|
6385
|
-
import { join as
|
|
6834
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
6835
|
+
import { join as join31 } from "node:path";
|
|
6386
6836
|
|
|
6387
6837
|
// src/memory/api.ts
|
|
6388
6838
|
import { dirname as dirname11 } from "node:path";
|
|
6389
6839
|
|
|
6390
6840
|
// src/memory/proposals.ts
|
|
6391
6841
|
import { createHash as createHash5 } from "node:crypto";
|
|
6392
|
-
import { mkdirSync as mkdirSync10, readFileSync as
|
|
6393
|
-
import { dirname as dirname10, join as
|
|
6394
|
-
import { z as
|
|
6395
|
-
var optionalText2 =
|
|
6396
|
-
var sourceRefSchema =
|
|
6397
|
-
kind:
|
|
6398
|
-
ref:
|
|
6842
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync16, renameSync as renameSync6, rmSync as rmSync6 } from "node:fs";
|
|
6843
|
+
import { dirname as dirname10, join as join30, relative as relative4 } from "node:path";
|
|
6844
|
+
import { z as z5 } from "zod";
|
|
6845
|
+
var optionalText2 = z5.string().nullish().transform((value) => value ?? null);
|
|
6846
|
+
var sourceRefSchema = z5.object({
|
|
6847
|
+
kind: z5.string(),
|
|
6848
|
+
ref: z5.string(),
|
|
6399
6849
|
/** A passage quoted from the evidence. A run or source reference counts only when this passage is found in it. */
|
|
6400
|
-
excerpt:
|
|
6850
|
+
excerpt: z5.string().nullish().transform((value) => value ?? void 0)
|
|
6401
6851
|
});
|
|
6402
|
-
var linkProposalSchema =
|
|
6403
|
-
var pageProposalSchema =
|
|
6852
|
+
var linkProposalSchema = z5.object({ toId: z5.string(), type: z5.string() });
|
|
6853
|
+
var pageProposalSchema = z5.object({
|
|
6404
6854
|
id: optionalText2,
|
|
6405
|
-
title:
|
|
6406
|
-
type:
|
|
6407
|
-
body:
|
|
6855
|
+
title: z5.string(),
|
|
6856
|
+
type: z5.string(),
|
|
6857
|
+
body: z5.string(),
|
|
6408
6858
|
/** Where a new page is filed (`/library`, `/projects/<slug>`); the job's namespace by default. */
|
|
6409
6859
|
namespace: optionalText2,
|
|
6410
6860
|
confidence: optionalText2,
|
|
6411
|
-
sourceRefs:
|
|
6412
|
-
links:
|
|
6861
|
+
sourceRefs: z5.array(sourceRefSchema).default([]),
|
|
6862
|
+
links: z5.array(linkProposalSchema).default([])
|
|
6413
6863
|
});
|
|
6414
|
-
var memoryProposalSchema =
|
|
6864
|
+
var memoryProposalSchema = z5.object({
|
|
6415
6865
|
id: optionalText2,
|
|
6416
|
-
title:
|
|
6417
|
-
kind:
|
|
6418
|
-
body:
|
|
6866
|
+
title: z5.string(),
|
|
6867
|
+
kind: z5.string(),
|
|
6868
|
+
body: z5.string(),
|
|
6419
6869
|
/** `/` for everything, or a project namespace. The job's project namespace by default. */
|
|
6420
6870
|
namespace: optionalText2,
|
|
6421
6871
|
confidence: optionalText2,
|
|
6422
6872
|
supersedes: optionalText2,
|
|
6423
|
-
volatile:
|
|
6424
|
-
sourceRefs:
|
|
6425
|
-
links:
|
|
6873
|
+
volatile: z5.boolean().nullish().transform((value) => value ?? null),
|
|
6874
|
+
sourceRefs: z5.array(sourceRefSchema).default([]),
|
|
6875
|
+
links: z5.array(linkProposalSchema).default([])
|
|
6426
6876
|
});
|
|
6427
|
-
var blockProposalSchema =
|
|
6428
|
-
var claimFlagSchema =
|
|
6429
|
-
var proposalSetSchema =
|
|
6430
|
-
pages:
|
|
6431
|
-
memories:
|
|
6432
|
-
blocks:
|
|
6433
|
-
flags:
|
|
6877
|
+
var blockProposalSchema = z5.object({ block: z5.string(), body: z5.string(), sourceRefs: z5.array(sourceRefSchema).default([]) });
|
|
6878
|
+
var claimFlagSchema = z5.object({ claim: z5.string(), kind: z5.string(), reason: z5.string() });
|
|
6879
|
+
var proposalSetSchema = z5.object({
|
|
6880
|
+
pages: z5.array(pageProposalSchema).default([]),
|
|
6881
|
+
memories: z5.array(memoryProposalSchema).default([]),
|
|
6882
|
+
blocks: z5.array(blockProposalSchema).default([]),
|
|
6883
|
+
flags: z5.array(claimFlagSchema).default([])
|
|
6434
6884
|
});
|
|
6435
6885
|
function parseProposalSet(value) {
|
|
6436
6886
|
const parsed2 = proposalSetSchema.safeParse(value ?? {});
|
|
@@ -6468,14 +6918,14 @@ function triggerRunId(trigger) {
|
|
|
6468
6918
|
}
|
|
6469
6919
|
function fileHash(path3) {
|
|
6470
6920
|
try {
|
|
6471
|
-
return createHash5("sha256").update(
|
|
6921
|
+
return createHash5("sha256").update(readFileSync16(path3)).digest("hex");
|
|
6472
6922
|
} catch {
|
|
6473
6923
|
return null;
|
|
6474
6924
|
}
|
|
6475
6925
|
}
|
|
6476
6926
|
function readOrNull(path3) {
|
|
6477
6927
|
try {
|
|
6478
|
-
return
|
|
6928
|
+
return readFileSync16(path3, "utf8");
|
|
6479
6929
|
} catch {
|
|
6480
6930
|
return null;
|
|
6481
6931
|
}
|
|
@@ -6506,11 +6956,11 @@ function writeIndex(wiki) {
|
|
|
6506
6956
|
|
|
6507
6957
|
`;
|
|
6508
6958
|
for (const page2 of own) {
|
|
6509
|
-
const inside4 = relative4(
|
|
6959
|
+
const inside4 = relative4(join30(wiki, folder), page2.path);
|
|
6510
6960
|
text6 += `- [${page2.frontmatter.title}](${inside4.startsWith("..") ? page2.path : inside4}) (${page2.frontmatter.type}, \`${page2.frontmatter.id}\`) \u2014 ${firstParagraph(page2.body)}
|
|
6511
6961
|
`;
|
|
6512
6962
|
}
|
|
6513
|
-
atomicWrite(
|
|
6963
|
+
atomicWrite(join30(wiki, folder, "index.md"), text6);
|
|
6514
6964
|
}
|
|
6515
6965
|
const active = memories2.filter((memory) => memory.front.status === "active");
|
|
6516
6966
|
if (active.length) {
|
|
@@ -6518,7 +6968,7 @@ function writeIndex(wiki) {
|
|
|
6518
6968
|
for (const memory of active) root += `- [${memory.front.title}](facts/${memory.front.id}.md) (${memory.front.kind}, ${memory.front.namespace}${memory.front.volatile ? ", volatile" : ""})
|
|
6519
6969
|
`;
|
|
6520
6970
|
}
|
|
6521
|
-
atomicWrite(
|
|
6971
|
+
atomicWrite(join30(wiki, "index.md"), root);
|
|
6522
6972
|
}
|
|
6523
6973
|
function apply(wiki, defaultNamespace, trigger, proposals, captured, evidence) {
|
|
6524
6974
|
const existing = loadPages(wiki);
|
|
@@ -6618,7 +7068,7 @@ ${takeChars(body2, 600)}`, [entryId2], null, payload);
|
|
|
6618
7068
|
const sourceList = current ? [...current.frontmatter.sourceIds] : [];
|
|
6619
7069
|
for (const id of found) if (!sourceList.includes(id)) sourceList.push(id);
|
|
6620
7070
|
const front = { id: pageId, title: proposal.title.trim(), type: proposal.type, updatedAt: now(), sourceIds: sourceList, links: links2 };
|
|
6621
|
-
const path3 = current?.path ??
|
|
7071
|
+
const path3 = current?.path ?? join30(targetDir, `${pageId}.md`);
|
|
6622
7072
|
outcome.notes.push(`Page "${proposal.title}" (${pageId}) ${current ? "updated" : "created"} in ${namespace}.`);
|
|
6623
7073
|
writes.push({ entryId: pageId, oldText: current ? readOrNull(current.path) : null, newText: serializePage(front, proposal.body), path: path3 });
|
|
6624
7074
|
}
|
|
@@ -6747,7 +7197,7 @@ ${takeChars(proposal.body, 600)}`, [proposal.block]);
|
|
|
6747
7197
|
afterRevisionId: snapshot(wiki, write3.entryId, write3.newText, actor, `Update ${short} (${described})`).id
|
|
6748
7198
|
}));
|
|
6749
7199
|
const operation = { id: operationId, createdAt: now(), trigger: described, pages, undone: false };
|
|
6750
|
-
writeJson(
|
|
7200
|
+
writeJson(join30(operationsDir(wiki), `${operationId}.json`), operation);
|
|
6751
7201
|
for (const write3 of writes) noteChange({ wiki, id: write3.entryId, path: write3.path, text: write3.newText, actor, reason: described });
|
|
6752
7202
|
writeIndex(wiki);
|
|
6753
7203
|
log2(
|
|
@@ -6765,13 +7215,13 @@ ${takeChars(proposal.body, 600)}`, [proposal.block]);
|
|
|
6765
7215
|
outcome.operation = operation;
|
|
6766
7216
|
return outcome;
|
|
6767
7217
|
}
|
|
6768
|
-
var registryProposalSchema =
|
|
7218
|
+
var registryProposalSchema = z5.object({
|
|
6769
7219
|
/** `skill`, `prompt`, or `rule`. */
|
|
6770
|
-
kind:
|
|
6771
|
-
name:
|
|
6772
|
-
description:
|
|
6773
|
-
body:
|
|
6774
|
-
reason:
|
|
7220
|
+
kind: z5.string(),
|
|
7221
|
+
name: z5.string(),
|
|
7222
|
+
description: z5.string().default(""),
|
|
7223
|
+
body: z5.string(),
|
|
7224
|
+
reason: z5.string().default("")
|
|
6775
7225
|
});
|
|
6776
7226
|
function parkRegistryProposal(wiki, trigger, proposal) {
|
|
6777
7227
|
if (!["skill", "prompt", "rule"].includes(proposal.kind) || !proposal.name.trim() || !proposal.body.trim()) {
|
|
@@ -6800,7 +7250,7 @@ function parsed(schema, value) {
|
|
|
6800
7250
|
}
|
|
6801
7251
|
function accept(root, itemId) {
|
|
6802
7252
|
const wiki = wikiDir(root);
|
|
6803
|
-
const path3 =
|
|
7253
|
+
const path3 = join30(wiki, "inbox", `${safeId(itemId)}.json`);
|
|
6804
7254
|
const item = readJson(path3);
|
|
6805
7255
|
if (!item) throw notFound("Inbox item");
|
|
6806
7256
|
const payload = item.payload;
|
|
@@ -6830,9 +7280,9 @@ function accept(root, itemId) {
|
|
|
6830
7280
|
} else {
|
|
6831
7281
|
throw invalid("Unknown proposal kind.");
|
|
6832
7282
|
}
|
|
6833
|
-
const target =
|
|
7283
|
+
const target = join30(wiki, "inbox", "dismissed");
|
|
6834
7284
|
mkdirSync10(target, { recursive: true });
|
|
6835
|
-
renameSync6(path3,
|
|
7285
|
+
renameSync6(path3, join30(target, `${item.id}.json`));
|
|
6836
7286
|
return message;
|
|
6837
7287
|
}
|
|
6838
7288
|
|
|
@@ -6991,13 +7441,13 @@ function list4(env, params) {
|
|
|
6991
7441
|
const pages = loadPages(wiki).filter((page2) => !namespace || page2.location.namespace === namespace || page2.location.path.startsWith(prefix)).slice(0, 200).map((page2) => `- ${page2.location.path} \u2014 ${page2.frontmatter.title} (${page2.frontmatter.type}) [${page2.frontmatter.id}]`);
|
|
6992
7442
|
return text6 + (pages.length ? pages.join("\n") : "No pages here.");
|
|
6993
7443
|
}
|
|
6994
|
-
function skill(env, context, params) {
|
|
7444
|
+
function skill(env, context, params, contributions2) {
|
|
6995
7445
|
const { root } = env;
|
|
6996
7446
|
const name = textParam(params, "name").toLowerCase();
|
|
6997
|
-
const items = list(root).filter((item) => item.kind === "skills");
|
|
7447
|
+
const items = limitToContributions(list(root).filter((item) => item.kind === "skills"), contributions2);
|
|
6998
7448
|
if (!name) {
|
|
6999
7449
|
if (!items.length) return "No skills yet.";
|
|
7000
|
-
const active = enabledFor(root, context.projectId ?? null, context.agentId ?? null).map((item) => item.id);
|
|
7450
|
+
const active = (contributions2 === void 0 ? enabledFor(root, context.projectId ?? null, context.agentId ?? null) : items).map((item) => item.id);
|
|
7001
7451
|
return items.map((item) => `- ${item.name} (${item.id})${active.includes(item.id) ? " \xB7 active" : ""} \u2014 ${item.description}`).join("\n");
|
|
7002
7452
|
}
|
|
7003
7453
|
const found = items.find((item) => item.id.toLowerCase() === name || item.id.replace(/^skills\//, "").toLowerCase() === name || item.name.toLowerCase() === name);
|
|
@@ -7299,7 +7749,7 @@ function chatUpdatesPaused(settings) {
|
|
|
7299
7749
|
function runEvidence(root, runId) {
|
|
7300
7750
|
const run3 = findRun(root, runId);
|
|
7301
7751
|
if (!run3) throw new Error("Run was not found.");
|
|
7302
|
-
const settings = readJsonOr(
|
|
7752
|
+
const settings = readJsonOr(join31(root, "config/settings.json"), null);
|
|
7303
7753
|
if (Array.isArray(settings?.excludedSourceIds) && settings.excludedSourceIds.includes(runId)) throw new Error("run excluded in settings");
|
|
7304
7754
|
const projectId = run3.mode === "chat" || run3.mode === "code" ? run3.projectId : null;
|
|
7305
7755
|
let namespace;
|
|
@@ -7322,7 +7772,7 @@ function runBlocks(env, runId) {
|
|
|
7322
7772
|
const run3 = findRun(env.root, runId);
|
|
7323
7773
|
if (!run3) return [];
|
|
7324
7774
|
const chat = run3.mode === "chat";
|
|
7325
|
-
const recorded = readJsonOr(
|
|
7775
|
+
const recorded = readJsonOr(join31(env.root, "runs", run3.id, "caller.json"), null);
|
|
7326
7776
|
const context = recorded ?? {
|
|
7327
7777
|
runId: run3.id,
|
|
7328
7778
|
sessionId: run3.sessionId,
|
|
@@ -7421,12 +7871,12 @@ var Maintenance = class {
|
|
|
7421
7871
|
let request2;
|
|
7422
7872
|
let shown2 = "";
|
|
7423
7873
|
if (job.kind === "ingest") {
|
|
7424
|
-
const directory =
|
|
7425
|
-
const name = readJsonOr(
|
|
7874
|
+
const directory = join31(wiki, "raw", job.sourceId);
|
|
7875
|
+
const name = readJsonOr(join31(directory, "meta.json"), null)?.name;
|
|
7426
7876
|
const sourceName = typeof name === "string" ? name : "source";
|
|
7427
7877
|
let text6 = null;
|
|
7428
7878
|
try {
|
|
7429
|
-
text6 = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
7879
|
+
text6 = new TextDecoder("utf-8", { fatal: true }).decode(readFileSync17(join31(directory, sourceName)));
|
|
7430
7880
|
} catch {
|
|
7431
7881
|
text6 = null;
|
|
7432
7882
|
}
|
|
@@ -7473,7 +7923,7 @@ var Maintenance = class {
|
|
|
7473
7923
|
const context = trigger.kind === "consolidate" ? { pages: [], memories: [] } : maintainerContext(root, namespace, shown2);
|
|
7474
7924
|
let index = "";
|
|
7475
7925
|
try {
|
|
7476
|
-
index = takeChars(
|
|
7926
|
+
index = takeChars(readFileSync17(join31(wiki, "index.md"), "utf8"), 8e3);
|
|
7477
7927
|
} catch {
|
|
7478
7928
|
index = "";
|
|
7479
7929
|
}
|
|
@@ -7534,28 +7984,28 @@ var Maintenance = class {
|
|
|
7534
7984
|
};
|
|
7535
7985
|
|
|
7536
7986
|
// src/memory/migrate.ts
|
|
7537
|
-
import { existsSync as
|
|
7538
|
-
import { join as
|
|
7987
|
+
import { existsSync as existsSync11, renameSync as renameSync7, rmSync as rmSync7 } from "node:fs";
|
|
7988
|
+
import { join as join32 } from "node:path";
|
|
7539
7989
|
var WIKI_VERSION = 3;
|
|
7540
|
-
var storagePath = (root) =>
|
|
7990
|
+
var storagePath = (root) => join32(root, "config/storage.json");
|
|
7541
7991
|
function version(root) {
|
|
7542
7992
|
const stored = readJsonOr(storagePath(root), null)?.wikiVersion;
|
|
7543
7993
|
if (typeof stored === "number" && Number.isInteger(stored) && stored >= 0) return stored;
|
|
7544
7994
|
const wiki = wikiDir(root);
|
|
7545
|
-
if (isDir(
|
|
7546
|
-
if (!
|
|
7995
|
+
if (isDir(join32(root, "knowledge"))) return 1;
|
|
7996
|
+
if (!existsSync11(join32(wiki, "blocks")) && (isDir(join32(wiki, "docs")) || isDir(join32(wiki, "memory")))) return 2;
|
|
7547
7997
|
return WIKI_VERSION;
|
|
7548
7998
|
}
|
|
7549
7999
|
var stamp = () => now().replace(/[:.]/g, "-");
|
|
7550
8000
|
function backUp2(root, from, name) {
|
|
7551
|
-
if (!
|
|
7552
|
-
renameSync7(from,
|
|
8001
|
+
if (!existsSync11(from)) return;
|
|
8002
|
+
renameSync7(from, existsSync11(join32(root, name)) ? join32(root, `${name}-${stamp()}`) : join32(root, name));
|
|
7553
8003
|
}
|
|
7554
8004
|
function run2(env, agentIds = []) {
|
|
7555
8005
|
const { root } = env;
|
|
7556
8006
|
if (version(root) >= WIKI_VERSION) return false;
|
|
7557
8007
|
const failed = (detail) => new HarnessError("migration_failed", `The Wiki was not migrated to version 3; your Wiki is unchanged. ${detail}`);
|
|
7558
|
-
const staging =
|
|
8008
|
+
const staging = join32(root, "wiki.migrating");
|
|
7559
8009
|
rmSync7(staging, { recursive: true, force: true });
|
|
7560
8010
|
try {
|
|
7561
8011
|
ensureLayoutAt(staging, env.productName);
|
|
@@ -7569,7 +8019,7 @@ function run2(env, agentIds = []) {
|
|
|
7569
8019
|
throw failed(error instanceof Error ? error.message : String(error));
|
|
7570
8020
|
}
|
|
7571
8021
|
renameSync7(staging, wikiDir(root));
|
|
7572
|
-
backUp2(root,
|
|
8022
|
+
backUp2(root, join32(root, "knowledge"), "knowledge.pre-v2");
|
|
7573
8023
|
ensureLayout(root, env.productName);
|
|
7574
8024
|
for (const project of listProjects(root)) ensureProjectNamespace(root, project.slug, project.name);
|
|
7575
8025
|
const enablement2 = enablement(root);
|
|
@@ -7578,7 +8028,7 @@ function run2(env, agentIds = []) {
|
|
|
7578
8028
|
for (const id of enablementIds(enablement2, "personal")) if (!ids.includes(id)) ids.push(id);
|
|
7579
8029
|
delete enablement2.personal;
|
|
7580
8030
|
enablement2[GLOBAL_KEY] = ids;
|
|
7581
|
-
writeJson(
|
|
8031
|
+
writeJson(join32(root, "config/registry-enabled.json"), enablement2);
|
|
7582
8032
|
}
|
|
7583
8033
|
writeIndex(wikiDir(root));
|
|
7584
8034
|
writeJson(storagePath(root), { wikiVersion: WIKI_VERSION, migratedAt: now() });
|
|
@@ -7754,8 +8204,8 @@ function searchAll(env, query, filter = {}) {
|
|
|
7754
8204
|
}
|
|
7755
8205
|
|
|
7756
8206
|
// src/plugins/hooks.ts
|
|
7757
|
-
import { chmodSync as
|
|
7758
|
-
import { join as
|
|
8207
|
+
import { chmodSync as chmodSync8 } from "node:fs";
|
|
8208
|
+
import { join as join33, sep as sep5 } from "node:path";
|
|
7759
8209
|
import { pathToFileURL } from "node:url";
|
|
7760
8210
|
var HOOK_EVENTS = ["memory.saved", "memory.block.changed", "wiki.page.saved", "history.run.ended"];
|
|
7761
8211
|
var importFile = new Function("address", "return import(address)");
|
|
@@ -7770,7 +8220,7 @@ function entryEvent(change) {
|
|
|
7770
8220
|
var Hooks = class {
|
|
7771
8221
|
constructor(options2) {
|
|
7772
8222
|
this.options = options2;
|
|
7773
|
-
this.statusPath =
|
|
8223
|
+
this.statusPath = join33(options2.stateDir, "plugin-hooks.json");
|
|
7774
8224
|
this.stop = onChange((change) => {
|
|
7775
8225
|
if (change.wiki === this.wiki) this.emit(entryEvent(change));
|
|
7776
8226
|
});
|
|
@@ -7797,7 +8247,7 @@ var Hooks = class {
|
|
|
7797
8247
|
if (JSON.stringify(before) === JSON.stringify(next) && all2[path3]) return;
|
|
7798
8248
|
all2[path3] = next;
|
|
7799
8249
|
writeJson(this.statusPath, all2);
|
|
7800
|
-
|
|
8250
|
+
chmodSync8(this.statusPath, 384);
|
|
7801
8251
|
if (before.lastError !== next.lastError || before.events.join() !== next.events.join()) this.options.bus.emit({ type: "plugins.changed" });
|
|
7802
8252
|
}
|
|
7803
8253
|
/** Loads the hooks files that should run and drops the ones that should not. Safe to call often. */
|
|
@@ -7882,8 +8332,8 @@ var Hooks = class {
|
|
|
7882
8332
|
};
|
|
7883
8333
|
|
|
7884
8334
|
// src/sessions/approvals.ts
|
|
7885
|
-
import { chmodSync as
|
|
7886
|
-
import { join as
|
|
8335
|
+
import { chmodSync as chmodSync9 } from "node:fs";
|
|
8336
|
+
import { join as join34 } from "node:path";
|
|
7887
8337
|
var APPROVAL_KEYS = ["chat", "pi"];
|
|
7888
8338
|
function decide(policy, key, sessionId, target) {
|
|
7889
8339
|
if (policy.auto[key]) return "always";
|
|
@@ -7897,7 +8347,7 @@ var approvalKey = (run3) => run3.mode === "chat" ? "chat" : run3.harness;
|
|
|
7897
8347
|
var Approvals = class {
|
|
7898
8348
|
constructor(stateDir, bus) {
|
|
7899
8349
|
this.bus = bus;
|
|
7900
|
-
this.path =
|
|
8350
|
+
this.path = join34(stateDir, "approvals.json");
|
|
7901
8351
|
}
|
|
7902
8352
|
path;
|
|
7903
8353
|
read() {
|
|
@@ -7909,7 +8359,7 @@ var Approvals = class {
|
|
|
7909
8359
|
update2(policy);
|
|
7910
8360
|
writeJson(this.path, policy);
|
|
7911
8361
|
try {
|
|
7912
|
-
|
|
8362
|
+
chmodSync9(this.path, 384);
|
|
7913
8363
|
} catch {
|
|
7914
8364
|
}
|
|
7915
8365
|
this.bus.emit({ type: "approvals.changed" });
|
|
@@ -8029,7 +8479,7 @@ var RunRegistry = class {
|
|
|
8029
8479
|
}
|
|
8030
8480
|
emit("approval.requested", { approvalId, ...payload });
|
|
8031
8481
|
emit("status.changed", { status: "waiting_for_approval" });
|
|
8032
|
-
return new Promise((
|
|
8482
|
+
return new Promise((resolve9) => {
|
|
8033
8483
|
const timer = timeoutMs ? setTimeout(() => this.approvals.get(approvalId)?.resolve("denied"), timeoutMs) : null;
|
|
8034
8484
|
this.approvals.set(approvalId, {
|
|
8035
8485
|
runId,
|
|
@@ -8038,7 +8488,7 @@ var RunRegistry = class {
|
|
|
8038
8488
|
this.approvals.delete(approvalId);
|
|
8039
8489
|
emit("approval.resolved", { approvalId, decision });
|
|
8040
8490
|
emit("status.changed", { status: waitingStatus() });
|
|
8041
|
-
|
|
8491
|
+
resolve9(decision);
|
|
8042
8492
|
}
|
|
8043
8493
|
});
|
|
8044
8494
|
});
|
|
@@ -8049,7 +8499,7 @@ var RunRegistry = class {
|
|
|
8049
8499
|
if (finished || this.nativeCleared.delete(questionId)) return Promise.resolve({ selected: [] });
|
|
8050
8500
|
emit("question.requested", { questionId, ...payload });
|
|
8051
8501
|
emit("status.changed", { status: "waiting_for_input" });
|
|
8052
|
-
return new Promise((
|
|
8502
|
+
return new Promise((resolve9) => {
|
|
8053
8503
|
this.questions.set(questionId, {
|
|
8054
8504
|
runId,
|
|
8055
8505
|
input,
|
|
@@ -8057,7 +8507,7 @@ var RunRegistry = class {
|
|
|
8057
8507
|
this.questions.delete(questionId);
|
|
8058
8508
|
emit("question.answered", { questionId, ...answer });
|
|
8059
8509
|
emit("status.changed", { status: waitingStatus() });
|
|
8060
|
-
|
|
8510
|
+
resolve9(answer);
|
|
8061
8511
|
}
|
|
8062
8512
|
});
|
|
8063
8513
|
});
|
|
@@ -8136,7 +8586,7 @@ var RunRegistry = class {
|
|
|
8136
8586
|
}
|
|
8137
8587
|
/** Interrupts every live run, giving each a moment to stop cleanly. */
|
|
8138
8588
|
async interruptAll(timeoutMs = 3e3) {
|
|
8139
|
-
await Promise.race([Promise.allSettled(this.activeRunIds().map((id) => this.interrupt(id))), new Promise((
|
|
8589
|
+
await Promise.race([Promise.allSettled(this.activeRunIds().map((id) => this.interrupt(id))), new Promise((resolve9) => setTimeout(resolve9, timeoutMs))]);
|
|
8140
8590
|
}
|
|
8141
8591
|
resolveApproval(runId, approvalId, decision) {
|
|
8142
8592
|
if (decision !== "approved" && decision !== "denied" && decision !== "native") throw new HarnessError("invalid_request", "Unknown decision.");
|
|
@@ -8167,13 +8617,13 @@ var RunRegistry = class {
|
|
|
8167
8617
|
};
|
|
8168
8618
|
|
|
8169
8619
|
// src/sessions/sessions.ts
|
|
8170
|
-
import { existsSync as
|
|
8171
|
-
import { join as
|
|
8620
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync11 } from "node:fs";
|
|
8621
|
+
import { join as join36 } from "node:path";
|
|
8172
8622
|
|
|
8173
8623
|
// src/capabilities/runSnapshot.ts
|
|
8174
|
-
import { existsSync as
|
|
8175
|
-
import { join as
|
|
8176
|
-
var path2 = (root, runId) =>
|
|
8624
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
8625
|
+
import { join as join35 } from "node:path";
|
|
8626
|
+
var path2 = (root, runId) => join35(root, "runs", safeId(runId), "capabilities.json");
|
|
8177
8627
|
function strings2(value) {
|
|
8178
8628
|
return Array.isArray(value) ? [...new Set(value.filter((item) => typeof item === "string" && item.length > 0))] : [];
|
|
8179
8629
|
}
|
|
@@ -8191,7 +8641,7 @@ function freezeRun(root, runId, caller, set2) {
|
|
|
8191
8641
|
connectors: strings2(set2.connectors),
|
|
8192
8642
|
contributions: strings2(set2.contributions)
|
|
8193
8643
|
};
|
|
8194
|
-
const existing =
|
|
8644
|
+
const existing = existsSync12(target) ? readJson(target) : null;
|
|
8195
8645
|
if (existing) {
|
|
8196
8646
|
if (JSON.stringify(existing) !== JSON.stringify(snapshot3)) throw new HarnessError("capabilities_frozen", "This run already has a different frozen capability set.");
|
|
8197
8647
|
return existing;
|
|
@@ -8214,7 +8664,7 @@ function loadRun2(root, runId) {
|
|
|
8214
8664
|
}
|
|
8215
8665
|
|
|
8216
8666
|
// src/memory/dispatch.ts
|
|
8217
|
-
function dispatch(env, context, call, params) {
|
|
8667
|
+
function dispatch(env, context, call, params, contributions2) {
|
|
8218
8668
|
switch (call) {
|
|
8219
8669
|
case "wiki.entrypoint": {
|
|
8220
8670
|
const recorded = context.runId ? safely(() => load3(env.root, context.runId)) : null;
|
|
@@ -8238,7 +8688,7 @@ function dispatch(env, context, call, params) {
|
|
|
8238
8688
|
case "wiki.list":
|
|
8239
8689
|
return list4(env, params);
|
|
8240
8690
|
case "wiki.skill":
|
|
8241
|
-
return skill(env, context, params);
|
|
8691
|
+
return skill(env, context, params, contributions2);
|
|
8242
8692
|
case "wiki.memory":
|
|
8243
8693
|
return memories(env, context, params);
|
|
8244
8694
|
case "wiki.propose": {
|
|
@@ -8260,7 +8710,7 @@ function safely(read3) {
|
|
|
8260
8710
|
}
|
|
8261
8711
|
|
|
8262
8712
|
// src/tools/memoryTools.ts
|
|
8263
|
-
function memoryTools(env, context) {
|
|
8713
|
+
function memoryTools(env, context, contributions2) {
|
|
8264
8714
|
return WIKI_AGENT_TOOLS.map((tool) => ({
|
|
8265
8715
|
name: tool.name,
|
|
8266
8716
|
label: tool.label,
|
|
@@ -8268,7 +8718,7 @@ function memoryTools(env, context) {
|
|
|
8268
8718
|
parameters: (Type) => Type.Unsafe(tool.parameters),
|
|
8269
8719
|
execute: async (input) => {
|
|
8270
8720
|
try {
|
|
8271
|
-
return dispatch(env, context, tool.call, input) || "Nothing found.";
|
|
8721
|
+
return dispatch(env, context, tool.call, input, contributions2) || "Nothing found.";
|
|
8272
8722
|
} catch (error) {
|
|
8273
8723
|
return `The Wiki could not do that: ${error instanceof Error ? error.message : String(error)}`;
|
|
8274
8724
|
}
|
|
@@ -8277,14 +8727,14 @@ function memoryTools(env, context) {
|
|
|
8277
8727
|
}
|
|
8278
8728
|
|
|
8279
8729
|
// src/sessions/history.ts
|
|
8280
|
-
import { readFileSync as
|
|
8730
|
+
import { readFileSync as readFileSync18, realpathSync } from "node:fs";
|
|
8281
8731
|
import { isAbsolute as isAbsolute4, relative as relative5 } from "node:path";
|
|
8282
8732
|
var unavailable = () => new HarnessError("native_resume_unavailable", "The saved conversation is unavailable; this reply cannot be continued.");
|
|
8283
8733
|
function nativeId(sessionDir, path3) {
|
|
8284
8734
|
const file = realpathSync(path3);
|
|
8285
8735
|
const within = relative5(realpathSync(sessionDir), file);
|
|
8286
8736
|
if (within.startsWith("..") || isAbsolute4(within) || !file.endsWith(".jsonl")) throw new Error("Outside history");
|
|
8287
|
-
const header = JSON.parse(
|
|
8737
|
+
const header = JSON.parse(readFileSync18(file, "utf8").split("\n", 1)[0]);
|
|
8288
8738
|
if (header.type !== "session" || !header.id) throw new Error("Missing session identity");
|
|
8289
8739
|
return header.id;
|
|
8290
8740
|
}
|
|
@@ -8336,7 +8786,7 @@ ${text6}`);
|
|
|
8336
8786
|
async function waitForChildren(root, runId, signal) {
|
|
8337
8787
|
while (!signal?.aborted) {
|
|
8338
8788
|
if (settled(root, runId)) return true;
|
|
8339
|
-
await new Promise((
|
|
8789
|
+
await new Promise((resolve9) => setTimeout(resolve9, 250));
|
|
8340
8790
|
}
|
|
8341
8791
|
return false;
|
|
8342
8792
|
}
|
|
@@ -8417,11 +8867,11 @@ var MAX_TEXT = 4e3;
|
|
|
8417
8867
|
var clip2 = (text6) => text6.length > MAX_TEXT ? `${text6.slice(0, MAX_TEXT)}\u2026` : text6;
|
|
8418
8868
|
function dismissable(answer, fallback, options2) {
|
|
8419
8869
|
if (!options2?.signal && !options2?.timeout) return answer;
|
|
8420
|
-
return new Promise((
|
|
8870
|
+
return new Promise((resolve9) => {
|
|
8421
8871
|
let timer;
|
|
8422
8872
|
const done = (value) => {
|
|
8423
8873
|
if (timer) clearTimeout(timer);
|
|
8424
|
-
|
|
8874
|
+
resolve9(value);
|
|
8425
8875
|
};
|
|
8426
8876
|
if (options2.signal?.aborted) return done(fallback);
|
|
8427
8877
|
options2.signal?.addEventListener("abort", () => done(fallback), { once: true });
|
|
@@ -8903,10 +9353,10 @@ var piSessionFactory = async ({ params, allow, gate, tools: tools3, runtime: loa
|
|
|
8903
9353
|
};
|
|
8904
9354
|
|
|
8905
9355
|
// src/sessions/sessions.ts
|
|
8906
|
-
function firstPartyFor(root,
|
|
9356
|
+
function firstPartyFor(root, contributions2) {
|
|
8907
9357
|
const choices = load2(root);
|
|
8908
|
-
const
|
|
8909
|
-
return firstPartyExtensions((id) =>
|
|
9358
|
+
const allowed2 = new Set(contributions2);
|
|
9359
|
+
return firstPartyExtensions((id) => FIRST_PARTY_PLUGINS.find((plugin) => plugin.id === id)?.code?.some((code) => allowed2.has(`${id}/code/${code.name}`)) ?? false, choices.settings);
|
|
8910
9360
|
}
|
|
8911
9361
|
var Sessions = class {
|
|
8912
9362
|
constructor(options2) {
|
|
@@ -8969,7 +9419,7 @@ var Sessions = class {
|
|
|
8969
9419
|
...WIKI_TOOL_NAMES,
|
|
8970
9420
|
...broker?.toolNames ?? []
|
|
8971
9421
|
])];
|
|
8972
|
-
const available = this.options.resolveCapabilities ? await this.options.resolveCapabilities(caller, requestedTools) : { version: 1, tools: requestedTools, connectors: [], contributions: [] };
|
|
9422
|
+
const available = this.options.resolveCapabilities ? await this.options.resolveCapabilities(caller, requestedTools, input.cwd) : { version: 1, tools: requestedTools, connectors: [], contributions: [] };
|
|
8973
9423
|
const requested = input.capabilities ? capabilitySet(input.capabilities) : available;
|
|
8974
9424
|
const capabilities = {
|
|
8975
9425
|
version: 1,
|
|
@@ -9004,7 +9454,7 @@ var Sessions = class {
|
|
|
9004
9454
|
trigger: input.run.trigger ?? null
|
|
9005
9455
|
});
|
|
9006
9456
|
try {
|
|
9007
|
-
writeJson(
|
|
9457
|
+
writeJson(join36(runDir(root, runId), "caller.json"), caller);
|
|
9008
9458
|
} catch {
|
|
9009
9459
|
}
|
|
9010
9460
|
const fail = (error, code = "provider_unavailable") => {
|
|
@@ -9015,7 +9465,7 @@ var Sessions = class {
|
|
|
9015
9465
|
let system;
|
|
9016
9466
|
let update2;
|
|
9017
9467
|
try {
|
|
9018
|
-
if (!
|
|
9468
|
+
if (!existsSync13(input.sessionDir)) mkdirSync11(input.sessionDir, { recursive: true });
|
|
9019
9469
|
const launch = input.launch ?? {};
|
|
9020
9470
|
const effectiveLaunch = {
|
|
9021
9471
|
...launch,
|
|
@@ -9023,7 +9473,7 @@ var Sessions = class {
|
|
|
9023
9473
|
connectionTools: (launch.connectionTools ?? []).filter((name) => capabilities.tools.includes(name)),
|
|
9024
9474
|
toolGroups: launch.toolGroups?.map((group) => ({ ...group, tools: group.tools.filter((name) => capabilities.tools.includes(name)) })).filter((group) => group.tools.length > 0)
|
|
9025
9475
|
};
|
|
9026
|
-
({ system, update: update2 } = sessionLaunch(this.env, caller, effectiveLaunch, historyFile));
|
|
9476
|
+
({ system, update: update2 } = sessionLaunch(this.env, caller, effectiveLaunch, historyFile, capabilities.contributions));
|
|
9027
9477
|
} catch (error) {
|
|
9028
9478
|
fail(error);
|
|
9029
9479
|
throw error;
|
|
@@ -9037,7 +9487,7 @@ var Sessions = class {
|
|
|
9037
9487
|
try {
|
|
9038
9488
|
const tools3 = [
|
|
9039
9489
|
...broker ? broker.tools(session, { readOnly: typeof input.connections === "object" && input.connections.readOnly === true }) : [],
|
|
9040
|
-
...memoryTools(this.options.env, caller),
|
|
9490
|
+
...memoryTools(this.options.env, caller, capabilities.contributions),
|
|
9041
9491
|
askUserTool(run3),
|
|
9042
9492
|
pluginProposeTool(this.options.env, caller)
|
|
9043
9493
|
];
|
|
@@ -9055,9 +9505,9 @@ var Sessions = class {
|
|
|
9055
9505
|
preamble: input.preamble ?? null,
|
|
9056
9506
|
automatic: input.automatic,
|
|
9057
9507
|
account: context,
|
|
9058
|
-
resources: resources(root, caller.projectId ?? null, caller.agentId ?? null),
|
|
9059
|
-
inline: [...firstPartyFor(root,
|
|
9060
|
-
...plugins ? { plugins: { loading: () => plugins.loading({ cwd: input.cwd, agentDir: context.configDir, install: true, caller: { agentId: caller.agentId ?? null, projectId: caller.projectId ?? null } }), loaded: (extensions) => plugins.loaded(extensions) } } : {},
|
|
9508
|
+
resources: resources(root, caller.projectId ?? null, caller.agentId ?? null, capabilities.contributions),
|
|
9509
|
+
inline: [...firstPartyFor(root, capabilities.contributions), runLineageJoin(root, runId)],
|
|
9510
|
+
...plugins ? { plugins: { loading: () => plugins.loading({ cwd: input.cwd, agentDir: context.configDir, install: true, caller: { agentId: caller.agentId ?? null, projectId: caller.projectId ?? null }, contributions: capabilities.contributions }), loaded: (extensions) => plugins.loaded(extensions) } } : {},
|
|
9061
9511
|
system,
|
|
9062
9512
|
update: update2
|
|
9063
9513
|
};
|
|
@@ -9093,8 +9543,8 @@ var Sessions = class {
|
|
|
9093
9543
|
};
|
|
9094
9544
|
|
|
9095
9545
|
// src/terminal/terminals.ts
|
|
9096
|
-
import { chmodSync as
|
|
9097
|
-
import { dirname as dirname13, join as
|
|
9546
|
+
import { chmodSync as chmodSync11, existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync19, writeFileSync as writeFileSync3 } from "node:fs";
|
|
9547
|
+
import { dirname as dirname13, join as join38 } from "node:path";
|
|
9098
9548
|
import { fileURLToPath } from "node:url";
|
|
9099
9549
|
|
|
9100
9550
|
// src/terminal/client.ts
|
|
@@ -9114,7 +9564,7 @@ function connectionFromEnv(env = process.env) {
|
|
|
9114
9564
|
return { socket, token };
|
|
9115
9565
|
}
|
|
9116
9566
|
function request(connection, body2, what, timeoutMs) {
|
|
9117
|
-
return new Promise((
|
|
9567
|
+
return new Promise((resolve9, reject) => {
|
|
9118
9568
|
const stream = createConnection(connection.socket);
|
|
9119
9569
|
let buffer = "";
|
|
9120
9570
|
let settled2 = false;
|
|
@@ -9140,7 +9590,7 @@ function request(connection, body2, what, timeoutMs) {
|
|
|
9140
9590
|
if (reply.error) {
|
|
9141
9591
|
const error = typeof reply.error === "string" ? { message: reply.error } : reply.error;
|
|
9142
9592
|
finish(() => reject(new SocketError(error.code ?? "error", error.message ?? `${what} failed.`)));
|
|
9143
|
-
} else finish(() =>
|
|
9593
|
+
} else finish(() => resolve9(reply.result));
|
|
9144
9594
|
} catch {
|
|
9145
9595
|
finish(() => reject(new SocketError("invalid_reply", "The harness sent an unreadable reply.")));
|
|
9146
9596
|
}
|
|
@@ -9344,15 +9794,15 @@ var TerminalController = class {
|
|
|
9344
9794
|
if (this.disposed) throw new HarnessError("terminal_not_ready", "Reopen the terminal before sending instructions.");
|
|
9345
9795
|
if (this.activeRun || this.delivery) throw new HarnessError("session_busy", "This terminal has an active task or delivery.");
|
|
9346
9796
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
9347
|
-
while (!this.ready && !this.startupError && !this.disposed && Date.now() < deadline) await new Promise((
|
|
9797
|
+
while (!this.ready && !this.startupError && !this.disposed && Date.now() < deadline) await new Promise((resolve9) => setTimeout(resolve9, 50));
|
|
9348
9798
|
if (this.disposed) throw new HarnessError("terminal_not_ready", "The terminal stopped during initialization.");
|
|
9349
9799
|
if (this.startupError) throw new HarnessError("terminal_not_ready", this.startupError);
|
|
9350
9800
|
if (!this.ready) throw new HarnessError("terminal_not_ready", `The native telemetry handshake is missing (${this.startupPhase}). Inspect the terminal sign-in or trust screen.`);
|
|
9351
9801
|
if (this.dirty) throw new HarnessError("terminal_has_draft", "The terminal contains user input. Submit or clear it before handing back control.");
|
|
9352
9802
|
let timer;
|
|
9353
|
-
const ack = new Promise((
|
|
9803
|
+
const ack = new Promise((resolve9, reject) => {
|
|
9354
9804
|
timer = setTimeout(() => reject(new HarnessError("delivery_uncertain", "The native prompt acknowledgement was not received. Inspect the terminal; this instruction will not be resent.")), ACK_TIMEOUT_MS);
|
|
9355
|
-
this.delivery = { runId, text: text6, resolve:
|
|
9805
|
+
this.delivery = { runId, text: text6, resolve: resolve9, reject };
|
|
9356
9806
|
});
|
|
9357
9807
|
void ack.catch(() => {
|
|
9358
9808
|
});
|
|
@@ -9469,16 +9919,16 @@ var TerminalController = class {
|
|
|
9469
9919
|
|
|
9470
9920
|
// src/terminal/socket.ts
|
|
9471
9921
|
import { createHash as createHash7 } from "node:crypto";
|
|
9472
|
-
import { chmodSync as
|
|
9922
|
+
import { chmodSync as chmodSync10, mkdirSync as mkdirSync12, rmSync as rmSync8 } from "node:fs";
|
|
9473
9923
|
import { createServer as createServer2 } from "node:net";
|
|
9474
9924
|
import { tmpdir } from "node:os";
|
|
9475
|
-
import { dirname as dirname12, join as
|
|
9925
|
+
import { dirname as dirname12, join as join37 } from "node:path";
|
|
9476
9926
|
var MAX_REQUEST_BYTES = 1024 * 1024;
|
|
9477
9927
|
var MAX_SOCKET_PATH = 100;
|
|
9478
9928
|
function socketPath(stateDir) {
|
|
9479
|
-
const path3 =
|
|
9929
|
+
const path3 = join37(stateDir, "run", "harness.sock");
|
|
9480
9930
|
if (Buffer.byteLength(path3) <= MAX_SOCKET_PATH) return path3;
|
|
9481
|
-
return
|
|
9931
|
+
return join37(tmpdir(), `xh-${createHash7("sha256").update(path3).digest("hex").slice(0, 16)}.sock`);
|
|
9482
9932
|
}
|
|
9483
9933
|
var SessionSocket = class {
|
|
9484
9934
|
constructor(path3, handlers) {
|
|
@@ -9575,7 +10025,7 @@ var SessionSocket = class {
|
|
|
9575
10025
|
}
|
|
9576
10026
|
/** Binds the socket once; later calls wait for the same bind. */
|
|
9577
10027
|
listen() {
|
|
9578
|
-
this.listening ??= new Promise((
|
|
10028
|
+
this.listening ??= new Promise((resolve9, reject) => {
|
|
9579
10029
|
mkdirSync12(dirname12(this.path), { recursive: true, mode: 448 });
|
|
9580
10030
|
rmSync8(this.path, { force: true });
|
|
9581
10031
|
const server = createServer2((client) => this.serve(client));
|
|
@@ -9585,12 +10035,12 @@ var SessionSocket = class {
|
|
|
9585
10035
|
});
|
|
9586
10036
|
server.listen(this.path, () => {
|
|
9587
10037
|
try {
|
|
9588
|
-
|
|
10038
|
+
chmodSync10(this.path, 384);
|
|
9589
10039
|
} catch (error) {
|
|
9590
10040
|
log("session socket permissions", error instanceof Error ? error.message : String(error));
|
|
9591
10041
|
}
|
|
9592
10042
|
this.server = server;
|
|
9593
|
-
|
|
10043
|
+
resolve9();
|
|
9594
10044
|
});
|
|
9595
10045
|
});
|
|
9596
10046
|
return this.listening;
|
|
@@ -9601,7 +10051,7 @@ var SessionSocket = class {
|
|
|
9601
10051
|
this.server = null;
|
|
9602
10052
|
this.listening = null;
|
|
9603
10053
|
for (const client of this.clients) client.destroy();
|
|
9604
|
-
if (server) await new Promise((
|
|
10054
|
+
if (server) await new Promise((resolve9) => server.close(() => resolve9()));
|
|
9605
10055
|
rmSync8(this.path, { force: true });
|
|
9606
10056
|
}
|
|
9607
10057
|
};
|
|
@@ -9609,8 +10059,8 @@ var SessionSocket = class {
|
|
|
9609
10059
|
// src/terminal/terminals.ts
|
|
9610
10060
|
function defaultAssets() {
|
|
9611
10061
|
const here = dirname13(fileURLToPath(import.meta.url));
|
|
9612
|
-
const dist = [here,
|
|
9613
|
-
return { node: process.execPath, cli:
|
|
10062
|
+
const dist = [here, join38(here, "..", "..", "dist"), join38(here, "..", "dist")].find((folder) => existsSync14(join38(folder, "terminal-pi.js"))) ?? here;
|
|
10063
|
+
return { node: process.execPath, cli: join38(dist, "cli.js"), piExtension: join38(dist, "terminal-pi.js") };
|
|
9614
10064
|
}
|
|
9615
10065
|
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["read", "grep", "find", "ls"]);
|
|
9616
10066
|
var Terminals = class {
|
|
@@ -9628,23 +10078,23 @@ var Terminals = class {
|
|
|
9628
10078
|
assets;
|
|
9629
10079
|
/** The commands a terminal session finds on PATH. Rewritten only when they change. */
|
|
9630
10080
|
ensureCommands() {
|
|
9631
|
-
const bin =
|
|
10081
|
+
const bin = join38(this.options.stateDir, "run", "bin");
|
|
9632
10082
|
mkdirSync13(bin, { recursive: true });
|
|
9633
10083
|
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
9634
10084
|
for (const [name, mode] of [[WIKI_COMMAND, "wiki"], [APPS_COMMAND, "mcp"]]) {
|
|
9635
|
-
const wrapper =
|
|
10085
|
+
const wrapper = join38(bin, name);
|
|
9636
10086
|
const text6 = `#!/bin/sh
|
|
9637
10087
|
exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
|
|
9638
10088
|
`;
|
|
9639
10089
|
let current = null;
|
|
9640
10090
|
try {
|
|
9641
|
-
current =
|
|
10091
|
+
current = readFileSync19(wrapper, "utf8");
|
|
9642
10092
|
} catch {
|
|
9643
10093
|
current = null;
|
|
9644
10094
|
}
|
|
9645
10095
|
if (current !== text6) {
|
|
9646
10096
|
writeFileSync3(wrapper, text6);
|
|
9647
|
-
|
|
10097
|
+
chmodSync11(wrapper, 493);
|
|
9648
10098
|
}
|
|
9649
10099
|
}
|
|
9650
10100
|
return bin;
|
|
@@ -9711,7 +10161,7 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
|
|
|
9711
10161
|
return live;
|
|
9712
10162
|
}
|
|
9713
10163
|
historyDir(input) {
|
|
9714
|
-
return
|
|
10164
|
+
return join38(input.sessionFolder, "pi");
|
|
9715
10165
|
}
|
|
9716
10166
|
runRecord(live, runId, text6) {
|
|
9717
10167
|
const { input } = live;
|
|
@@ -9943,12 +10393,12 @@ var Harness = class {
|
|
|
9943
10393
|
constructor(options2) {
|
|
9944
10394
|
this.stateDir = options2.stateDir;
|
|
9945
10395
|
mkdirSync14(this.stateDir, { recursive: true });
|
|
9946
|
-
isolateProcess(
|
|
10396
|
+
isolateProcess(join39(this.stateDir, "accounts"));
|
|
9947
10397
|
this.env = new Env(options2.dataRoot || null, options2.directory ?? EMPTY_DIRECTORY, options2.productName ?? DEFAULT_PRODUCT_NAME, this.bus);
|
|
9948
10398
|
this.policy = options2.policy ?? {};
|
|
9949
10399
|
configureBuiltins(options2.builtins ?? []);
|
|
9950
|
-
const secrets = options2.secrets ?? fileSecretStore(
|
|
9951
|
-
this.accounts = new Accounts({ dir:
|
|
10400
|
+
const secrets = options2.secrets ?? fileSecretStore(join39(this.stateDir, "secrets.json"));
|
|
10401
|
+
this.accounts = new Accounts({ dir: join39(this.stateDir, "accounts"), secrets, bus: this.bus });
|
|
9952
10402
|
this.approvals = new Approvals(this.stateDir, this.bus);
|
|
9953
10403
|
this.runs = new RunRegistry({
|
|
9954
10404
|
root: () => this.env.root,
|
|
@@ -9960,7 +10410,7 @@ var Harness = class {
|
|
|
9960
10410
|
ended: (runId, sessionId) => this.apps.revokeRun(runId, sessionId)
|
|
9961
10411
|
});
|
|
9962
10412
|
this.apps = new ConnectedApps({ env: this.env, stateDir: this.stateDir, secrets, bus: this.bus, runs: this.runs, policy: this.policy });
|
|
9963
|
-
this.plugins = new Plugins({ stateDir: this.stateDir, bus: this.bus, resolver: options2.resolver, attachments: () => this.env.dataRoot ? load2(this.env.dataRoot) : null, packagesDir: () => this.env.dataRoot ?
|
|
10413
|
+
this.plugins = new Plugins({ stateDir: this.stateDir, bus: this.bus, resolver: options2.resolver, attachments: () => this.env.dataRoot ? load2(this.env.dataRoot) : null, packagesDir: () => this.env.dataRoot ? join39(this.env.dataRoot, "plugins") : null });
|
|
9964
10414
|
this.hooks = new Hooks({
|
|
9965
10415
|
stateDir: this.stateDir,
|
|
9966
10416
|
bus: this.bus,
|
|
@@ -9968,9 +10418,9 @@ var Harness = class {
|
|
|
9968
10418
|
// The shared folder stands in for an account: packages are the same for all of them.
|
|
9969
10419
|
wanted: async () => {
|
|
9970
10420
|
if (!this.env.dataRoot) return [];
|
|
9971
|
-
const shared = ensureSharedDir(
|
|
9972
|
-
const named2 = readJsonOr(
|
|
9973
|
-
if (!packageFolders(
|
|
10421
|
+
const shared = ensureSharedDir(join39(this.stateDir, "accounts"));
|
|
10422
|
+
const named2 = readJsonOr(join39(shared, "settings.json"), {}).packages;
|
|
10423
|
+
if (!packageFolders(join39(this.env.dataRoot, "plugins")).length && !(Array.isArray(named2) && named2.length)) return [];
|
|
9974
10424
|
return (await this.plugins.loading({ cwd: this.stateDir, agentDir: shared, caller: {} })).hooks;
|
|
9975
10425
|
},
|
|
9976
10426
|
call: (connection, tool, args) => this.apps.callUnattended(connection, tool, args ?? {}),
|
|
@@ -9993,7 +10443,7 @@ var Harness = class {
|
|
|
9993
10443
|
policy: this.policy,
|
|
9994
10444
|
broker: this.apps,
|
|
9995
10445
|
plugins: this.plugins,
|
|
9996
|
-
resolveCapabilities: (caller, tools3) =>
|
|
10446
|
+
resolveCapabilities: (caller, tools3, cwd) => resolve5(this, { agentId: caller.agentId ?? null, projectId: caller.projectId ?? null, cwd, tools: tools3 }),
|
|
9997
10447
|
factory: options2.sessionFactory
|
|
9998
10448
|
});
|
|
9999
10449
|
this.maintenance = new Maintenance({ env: this.env, bus: this.bus, accounts: this.accounts, factory: options2.maintainerFactory, idleMs: options2.idleMs });
|
|
@@ -10029,15 +10479,24 @@ var Harness = class {
|
|
|
10029
10479
|
const root = dataRoot;
|
|
10030
10480
|
this.hooks.watch(wikiDir(root));
|
|
10031
10481
|
void this.hooks.reload();
|
|
10032
|
-
for (const folder of ["registry/skills", "registry/prompts", "registry/rules", "config", "runs"]) mkdirSync14(
|
|
10482
|
+
for (const folder of ["registry/skills", "registry/prompts", "registry/rules", "config", "runs"]) mkdirSync14(join39(root, folder), { recursive: true });
|
|
10033
10483
|
let migrationError = null;
|
|
10484
|
+
let wikiMigrationError = null;
|
|
10034
10485
|
try {
|
|
10035
10486
|
if (run2(this.env, agentIds)) log(`started Wiki storage version ${WIKI_VERSION}; the earlier Wiki is kept as a backup in the data root`);
|
|
10036
10487
|
} catch (error) {
|
|
10037
10488
|
migrationError = error instanceof Error ? error.message : String(error);
|
|
10489
|
+
wikiMigrationError = migrationError;
|
|
10038
10490
|
log("Wiki migration failed", migrationError);
|
|
10039
10491
|
}
|
|
10040
|
-
|
|
10492
|
+
try {
|
|
10493
|
+
migrateConnectionScopes(root, this.stateDir);
|
|
10494
|
+
} catch (error) {
|
|
10495
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
10496
|
+
migrationError = migrationError ? `${migrationError}; connector scope migration: ${detail}` : detail;
|
|
10497
|
+
log("connector scope migration failed", detail);
|
|
10498
|
+
}
|
|
10499
|
+
if (!wikiMigrationError) {
|
|
10041
10500
|
try {
|
|
10042
10501
|
ensureLayout(root, this.env.productName);
|
|
10043
10502
|
for (const project of projectsOrNone(root)) ensureProjectNamespace(root, project.slug, project.name);
|
|
@@ -10095,18 +10554,18 @@ function createHarness(options2) {
|
|
|
10095
10554
|
|
|
10096
10555
|
// src/plugins/cli.ts
|
|
10097
10556
|
import { homedir as homedir5 } from "node:os";
|
|
10098
|
-
import { join as
|
|
10557
|
+
import { join as join41, resolve as resolve7 } from "node:path";
|
|
10099
10558
|
import { createInterface } from "node:readline/promises";
|
|
10100
10559
|
|
|
10101
10560
|
// src/plugins/install.ts
|
|
10102
|
-
import { existsSync as
|
|
10561
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync15, renameSync as renameSync8, statSync as statSync6 } from "node:fs";
|
|
10103
10562
|
import { homedir as homedir4 } from "node:os";
|
|
10104
|
-
import { basename as basename8, isAbsolute as isAbsolute5, join as
|
|
10563
|
+
import { basename as basename8, isAbsolute as isAbsolute5, join as join40, resolve as resolve6 } from "node:path";
|
|
10105
10564
|
function normalizeSource(input) {
|
|
10106
10565
|
const source2 = input.trim();
|
|
10107
10566
|
if (!source2) throw invalid("Name a package: a folder, an npm name, or a git address.");
|
|
10108
|
-
if (source2.startsWith("~/")) return
|
|
10109
|
-
if (isAbsolute5(source2)) return
|
|
10567
|
+
if (source2.startsWith("~/")) return join40(process.env.HOME || homedir4(), source2.slice(2));
|
|
10568
|
+
if (isAbsolute5(source2)) return resolve6(source2);
|
|
10110
10569
|
if (source2.startsWith("./") || source2.startsWith("../")) throw invalid("Give the whole path of the folder.");
|
|
10111
10570
|
return /^(npm:|git:|https?:\/\/|ssh:\/\/|git@)/.test(source2) ? source2 : `npm:${source2}`;
|
|
10112
10571
|
}
|
|
@@ -10119,7 +10578,7 @@ async function manager(harness) {
|
|
|
10119
10578
|
var changed = (harness) => harness.bus.emit({ type: "plugins.changed" });
|
|
10120
10579
|
async function install2(harness, params) {
|
|
10121
10580
|
const source2 = normalizeSource(params.source);
|
|
10122
|
-
if (isAbsolute5(source2) && !(
|
|
10581
|
+
if (isAbsolute5(source2) && !(existsSync15(source2) && statSync6(source2).isDirectory())) throw invalid("That folder does not exist.");
|
|
10123
10582
|
await ensureAdopted(harness);
|
|
10124
10583
|
const before = new Set((await list2(harness, params)).map((plugin) => plugin.id));
|
|
10125
10584
|
const { settings, packages } = await manager(harness);
|
|
@@ -10149,9 +10608,9 @@ function installed(plugins, pluginId) {
|
|
|
10149
10608
|
async function remove(harness, params) {
|
|
10150
10609
|
const plugin = installed(await list2(harness, params), params.pluginId);
|
|
10151
10610
|
if (plugin.install.kind === "folder") {
|
|
10152
|
-
const trash =
|
|
10611
|
+
const trash = join40(harness.env.root, "trash", "plugins");
|
|
10153
10612
|
mkdirSync15(trash, { recursive: true });
|
|
10154
|
-
renameSync8(plugin.install.source,
|
|
10613
|
+
renameSync8(plugin.install.source, join40(trash, `${basename8(plugin.install.source)}-${now().replace(/[:.]/g, "-")}`));
|
|
10155
10614
|
} else {
|
|
10156
10615
|
await (await manager(harness)).packages.removeAndPersist(plugin.install.source);
|
|
10157
10616
|
}
|
|
@@ -10240,10 +10699,10 @@ async function runPluginsCli(argv, dependencies = {}) {
|
|
|
10240
10699
|
return command ? 0 : 2;
|
|
10241
10700
|
}
|
|
10242
10701
|
const base = dependencies.cwd ?? process.cwd();
|
|
10243
|
-
const root =
|
|
10244
|
-
const options2 = { dataRoot: parsed2.values.data ?
|
|
10702
|
+
const root = join41(dependencies.home ?? homedir5(), ".xingular-harness");
|
|
10703
|
+
const options2 = { dataRoot: parsed2.values.data ? resolve7(base, parsed2.values.data) : join41(root, "data"), stateDir: parsed2.values.state ? resolve7(base, parsed2.values.state) : join41(root, "state"), productName: "Xingular Harness" };
|
|
10245
10704
|
const harness = dependencies.createHarness ? dependencies.createHarness(options2) : new Harness(options2);
|
|
10246
|
-
const scope = { cwd: parsed2.values.cwd ?
|
|
10705
|
+
const scope = { cwd: parsed2.values.cwd ? resolve7(base, parsed2.values.cwd) : null };
|
|
10247
10706
|
const need = (what) => {
|
|
10248
10707
|
if (!target) throw new Error(`${command} needs ${what}.`);
|
|
10249
10708
|
return target;
|
|
@@ -10429,7 +10888,7 @@ var RpcPeer = class {
|
|
|
10429
10888
|
/** Asks the host to perform a named operation. */
|
|
10430
10889
|
callHost(call, params, timeoutMs = 3e4) {
|
|
10431
10890
|
const id = `s${++this.nextCallId}`;
|
|
10432
|
-
return new Promise((
|
|
10891
|
+
return new Promise((resolve9, reject) => {
|
|
10433
10892
|
const timer = setTimeout(() => {
|
|
10434
10893
|
this.pendingCalls.delete(id);
|
|
10435
10894
|
reject(new HarnessError("host_timeout", `The host did not answer ${call}.`));
|
|
@@ -10437,7 +10896,7 @@ var RpcPeer = class {
|
|
|
10437
10896
|
this.pendingCalls.set(id, {
|
|
10438
10897
|
resolve: (value) => {
|
|
10439
10898
|
clearTimeout(timer);
|
|
10440
|
-
|
|
10899
|
+
resolve9(value);
|
|
10441
10900
|
},
|
|
10442
10901
|
reject: (error) => {
|
|
10443
10902
|
clearTimeout(timer);
|
|
@@ -10488,7 +10947,7 @@ var RpcPeer = class {
|
|
|
10488
10947
|
};
|
|
10489
10948
|
|
|
10490
10949
|
// src/server/methods.ts
|
|
10491
|
-
import { readFileSync as
|
|
10950
|
+
import { readFileSync as readFileSync20, statSync as statSync7 } from "node:fs";
|
|
10492
10951
|
import { basename as basename9 } from "node:path";
|
|
10493
10952
|
|
|
10494
10953
|
// src/memory/graph.ts
|
|
@@ -10702,8 +11161,8 @@ function methods(harness) {
|
|
|
10702
11161
|
"wiki.sources": () => listSources(wikiDir2()),
|
|
10703
11162
|
"wiki.ingest": (params) => {
|
|
10704
11163
|
const path3 = text5(params, "path");
|
|
10705
|
-
if (
|
|
10706
|
-
const source2 = maintenance.ingest(optional2(params, "name") ?? basename9(path3),
|
|
11164
|
+
if (statSync7(path3).size > MAX_SOURCE_BYTES) throw invalid("Sources larger than 50 MB are not imported.");
|
|
11165
|
+
const source2 = maintenance.ingest(optional2(params, "name") ?? basename9(path3), readFileSync20(path3), optional2(params, "namespace") ?? LIBRARY_NAMESPACE);
|
|
10707
11166
|
harness.bus.emit({ type: "wiki.changed" });
|
|
10708
11167
|
return source2;
|
|
10709
11168
|
},
|
|
@@ -10800,8 +11259,13 @@ function methods(harness) {
|
|
|
10800
11259
|
"capabilities.inventory": (params) => inventory2(harness, { ...pluginScope(params), context: params.context ?? {}, launch: params.launch ?? {}, tools: Array.isArray(params.tools) ? params.tools : [], connections: params.connections }),
|
|
10801
11260
|
"capabilities.attach": (params) => switchCapability(harness, { ...pluginScope(params), id: text5(params, "id"), scope: params.scope ?? null }, true),
|
|
10802
11261
|
"capabilities.detach": (params) => switchCapability(harness, { ...pluginScope(params), id: text5(params, "id"), scope: params.scope ?? null }, false),
|
|
10803
|
-
"capabilities.resolve": (params) =>
|
|
11262
|
+
"capabilities.resolve": (params) => resolve5(harness, { ...pluginScope(params), tools: Array.isArray(params.tools) ? params.tools.filter((item) => typeof item === "string") : [] }),
|
|
10804
11263
|
"capabilities.run": (params) => loadRun2(root(), text5(params, "runId")),
|
|
11264
|
+
"capabilities.intersect": (params) => toolIntersection(
|
|
11265
|
+
capabilitySet(params.parent),
|
|
11266
|
+
capabilitySet(params.assignee),
|
|
11267
|
+
params.tools === void 0 ? void 0 : Array.isArray(params.tools) ? params.tools.filter((item) => typeof item === "string") : []
|
|
11268
|
+
),
|
|
10805
11269
|
// ---- Runs --------------------------------------------------------------------------------------
|
|
10806
11270
|
"runs.load": (params) => snapshot2(root(), text5(params, "runId")),
|
|
10807
11271
|
"runs.family": (params) => runFamily(root(), text5(params, "runId")),
|
|
@@ -10863,24 +11327,24 @@ function attach3(harness, peer) {
|
|
|
10863
11327
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
10864
11328
|
import { createHash as createHash8, randomUUID as randomUUID8 } from "node:crypto";
|
|
10865
11329
|
import { homedir as homedir6 } from "node:os";
|
|
10866
|
-
import { join as
|
|
11330
|
+
import { join as join42, resolve as resolve8 } from "node:path";
|
|
10867
11331
|
function standaloneArguments(argv) {
|
|
10868
11332
|
const command = argv[0];
|
|
10869
11333
|
if (!command || command.startsWith("--") || command === "-h") return argv;
|
|
10870
11334
|
return command === "terminal" ? argv.slice(1) : null;
|
|
10871
11335
|
}
|
|
10872
11336
|
function parseStandaloneOptions(argv, home = homedir6(), cwd = process.cwd()) {
|
|
10873
|
-
const root =
|
|
11337
|
+
const root = join42(home, ".xingular-harness");
|
|
10874
11338
|
const paths = {
|
|
10875
|
-
dataRoot:
|
|
10876
|
-
stateDir:
|
|
11339
|
+
dataRoot: join42(root, "data"),
|
|
11340
|
+
stateDir: join42(root, "state")
|
|
10877
11341
|
};
|
|
10878
11342
|
for (let index = 0; index < argv.length; index += 1) {
|
|
10879
11343
|
const argument = argv[index];
|
|
10880
11344
|
if (argument !== "--data" && argument !== "--state") throw new Error(`Unknown standalone option: ${argument}`);
|
|
10881
11345
|
const value = argv[index + 1];
|
|
10882
11346
|
if (!value || value.startsWith("--")) throw new Error(`${argument} needs a directory path.`);
|
|
10883
|
-
paths[argument === "--data" ? "dataRoot" : "stateDir"] =
|
|
11347
|
+
paths[argument === "--data" ? "dataRoot" : "stateDir"] = resolve8(cwd, value);
|
|
10884
11348
|
index += 1;
|
|
10885
11349
|
}
|
|
10886
11350
|
return paths;
|
|
@@ -10935,7 +11399,7 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
|
|
|
10935
11399
|
if (!(dependencies.stdinIsTTY ?? process.stdin.isTTY) || !(dependencies.stdoutIsTTY ?? process.stdout.isTTY)) {
|
|
10936
11400
|
throw new Error("The interactive Harness terminal needs a TTY. Run it from a terminal, or use `xingular-harness serve` for a host process.");
|
|
10937
11401
|
}
|
|
10938
|
-
const cwd =
|
|
11402
|
+
const cwd = resolve8(dependencies.cwd ?? process.cwd());
|
|
10939
11403
|
const paths = parseStandaloneOptions(argv, dependencies.home, cwd);
|
|
10940
11404
|
const makeHarness = dependencies.createHarness ?? createHarness;
|
|
10941
11405
|
const harness = makeHarness({ dataRoot: paths.dataRoot, stateDir: paths.stateDir, productName: "Xingular Harness" });
|
|
@@ -10944,7 +11408,7 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
|
|
|
10944
11408
|
await harness.ready;
|
|
10945
11409
|
if (harness.accounts.overview().accounts.length === 0) harness.accounts.create("Default", "subscription");
|
|
10946
11410
|
const cwdHash = createHash8("sha256").update(cwd).digest("hex");
|
|
10947
|
-
const sessionFolder =
|
|
11411
|
+
const sessionFolder = join42(paths.dataRoot, "sessions", "terminal", cwdHash);
|
|
10948
11412
|
const launch = await harness.terminals.prepare({ sessionId, cwd, sessionFolder });
|
|
10949
11413
|
return await spawnPi(launch, cwd, dependencies.spawn);
|
|
10950
11414
|
} finally {
|
|
@@ -11005,12 +11469,12 @@ function serveCommand(argv) {
|
|
|
11005
11469
|
const harness = createHarness(options2);
|
|
11006
11470
|
if (switches.has("host-tools")) harness.sessions.provide(remoteTools(peer));
|
|
11007
11471
|
attach3(harness, peer);
|
|
11008
|
-
return new Promise((
|
|
11472
|
+
return new Promise((resolve9) => {
|
|
11009
11473
|
let closing = false;
|
|
11010
11474
|
const shutdown = () => {
|
|
11011
11475
|
if (closing) return;
|
|
11012
11476
|
closing = true;
|
|
11013
|
-
void Promise.race([harness.close(), new Promise((done) => setTimeout(done, 5e3))]).then(() =>
|
|
11477
|
+
void Promise.race([harness.close(), new Promise((done) => setTimeout(done, 5e3))]).then(() => resolve9(0));
|
|
11014
11478
|
};
|
|
11015
11479
|
peer.handle("harness.shutdown", () => void setImmediate(shutdown));
|
|
11016
11480
|
process.stdin.on("end", shutdown);
|