@corenel/cli 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +794 -248
- package/package.json +7 -7
package/dist/main.js
CHANGED
|
@@ -454,7 +454,7 @@ var init_fileService = __esm({
|
|
|
454
454
|
// ../tools-node/auth.ts
|
|
455
455
|
import { promises as fs3 } from "node:fs";
|
|
456
456
|
import { homedir } from "node:os";
|
|
457
|
-
import { join as join3 } from "node:path";
|
|
457
|
+
import { join as join3, resolve as resolve2 } from "node:path";
|
|
458
458
|
import { stateDirName, DEFAULT_API_BASE } from "@corenel/protocol";
|
|
459
459
|
function corenelDir(base = homedir()) {
|
|
460
460
|
return join3(base, stateDirName());
|
|
@@ -500,6 +500,83 @@ async function writeAuthToken(token, stateDir) {
|
|
|
500
500
|
} catch {
|
|
501
501
|
}
|
|
502
502
|
}
|
|
503
|
+
function authObjectOf(raw) {
|
|
504
|
+
let parsed;
|
|
505
|
+
try {
|
|
506
|
+
parsed = JSON.parse(raw);
|
|
507
|
+
} catch {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
511
|
+
return parsed;
|
|
512
|
+
}
|
|
513
|
+
function withoutSignIn(doc) {
|
|
514
|
+
const rest = { ...doc };
|
|
515
|
+
for (const k of SIGN_IN_KEYS) delete rest[k];
|
|
516
|
+
return rest;
|
|
517
|
+
}
|
|
518
|
+
function codeOf2(err) {
|
|
519
|
+
const code3 = typeof err === "object" && err !== null ? err.code : void 0;
|
|
520
|
+
return typeof code3 === "string" && code3 ? code3 : "unknown error";
|
|
521
|
+
}
|
|
522
|
+
function authDocLocks() {
|
|
523
|
+
const host = globalThis;
|
|
524
|
+
let m = host[AUTH_DOC_LOCKS];
|
|
525
|
+
if (!m) {
|
|
526
|
+
m = /* @__PURE__ */ new Map();
|
|
527
|
+
host[AUTH_DOC_LOCKS] = m;
|
|
528
|
+
}
|
|
529
|
+
return m;
|
|
530
|
+
}
|
|
531
|
+
async function withAuthDocLock(path, fn) {
|
|
532
|
+
const abs = resolve2(path);
|
|
533
|
+
const key = process.platform === "win32" ? abs.toLowerCase() : abs;
|
|
534
|
+
const locks = authDocLocks();
|
|
535
|
+
const prev = locks.get(key) ?? Promise.resolve();
|
|
536
|
+
let release = () => {
|
|
537
|
+
};
|
|
538
|
+
const mine = new Promise((r) => {
|
|
539
|
+
release = r;
|
|
540
|
+
});
|
|
541
|
+
const tail = prev.then(() => mine);
|
|
542
|
+
locks.set(key, tail);
|
|
543
|
+
await prev;
|
|
544
|
+
try {
|
|
545
|
+
return await fn();
|
|
546
|
+
} finally {
|
|
547
|
+
release();
|
|
548
|
+
if (locks.get(key) === tail) locks.delete(key);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async function readAuthDocForWrite(path, writer) {
|
|
552
|
+
let bytes;
|
|
553
|
+
try {
|
|
554
|
+
bytes = await fs3.readFile(path);
|
|
555
|
+
} catch (err) {
|
|
556
|
+
if (isMissing(err)) return { kind: "missing" };
|
|
557
|
+
const code3 = codeOf2(err);
|
|
558
|
+
const what = writer === "sign-in" ? "sign-in aborted so the MCP and agent credentials in it are not lost" : "not saved so the credentials in it are not lost";
|
|
559
|
+
throw Object.assign(new Error(`Could not read ${path} (${code3}); ${what}. Close whatever holds it and retry.`), { code: code3 });
|
|
560
|
+
}
|
|
561
|
+
const doc = authObjectOf(bytes.toString("utf8"));
|
|
562
|
+
return doc ? { kind: "doc", doc } : { kind: "corrupt", bytes };
|
|
563
|
+
}
|
|
564
|
+
async function backupCorruptAuthDoc(path, bytes, notify = toStderr) {
|
|
565
|
+
const backup = `${path}.bad-${Date.now()}`;
|
|
566
|
+
await fs3.writeFile(backup, bytes, { mode: 384, flag: "wx" });
|
|
567
|
+
try {
|
|
568
|
+
await fs3.chmod(backup, 384);
|
|
569
|
+
} catch {
|
|
570
|
+
}
|
|
571
|
+
notify(`${path} was not valid JSON; a copy was kept at ${backup} and it was replaced.`);
|
|
572
|
+
return backup;
|
|
573
|
+
}
|
|
574
|
+
async function readForeignAuthSections(path, notify) {
|
|
575
|
+
const read = await readAuthDocForWrite(path, "sign-in");
|
|
576
|
+
if (read.kind === "doc") return withoutSignIn(read.doc);
|
|
577
|
+
if (read.kind === "corrupt") await backupCorruptAuthDoc(path, read.bytes, notify);
|
|
578
|
+
return {};
|
|
579
|
+
}
|
|
503
580
|
async function writeLoginCredential(token, opts = {}) {
|
|
504
581
|
const cred = {
|
|
505
582
|
accessToken: token.trim(),
|
|
@@ -510,9 +587,17 @@ async function writeLoginCredential(token, opts = {}) {
|
|
|
510
587
|
const credPath = credentialFilePath(opts.stateDir);
|
|
511
588
|
const tokPath = tokenFilePath(opts.stateDir);
|
|
512
589
|
await fs3.mkdir(dir, { recursive: true, mode: 448 });
|
|
590
|
+
await withAuthDocLock(credPath, () => publishSignIn(credPath, tokPath, cred, opts.notify ?? toStderr));
|
|
591
|
+
try {
|
|
592
|
+
await fs3.chmod(dir, 448);
|
|
593
|
+
} catch {
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
async function publishSignIn(credPath, tokPath, cred, notify) {
|
|
597
|
+
const record = { ...await readForeignAuthSections(credPath, notify), ...cred };
|
|
513
598
|
const credTmp = `${credPath}.tmp-${process.pid.toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
514
599
|
const tokTmp = `${tokPath}.tmp-${process.pid.toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
515
|
-
await fs3.writeFile(credTmp, `${JSON.stringify(
|
|
600
|
+
await fs3.writeFile(credTmp, `${JSON.stringify(record, null, 2)}
|
|
516
601
|
`, { mode: 384 });
|
|
517
602
|
await fs3.writeFile(tokTmp, `${cred.accessToken}
|
|
518
603
|
`, { mode: 384 });
|
|
@@ -538,10 +623,6 @@ async function writeLoginCredential(token, opts = {}) {
|
|
|
538
623
|
}
|
|
539
624
|
throw e;
|
|
540
625
|
}
|
|
541
|
-
try {
|
|
542
|
-
await fs3.chmod(dir, 448);
|
|
543
|
-
} catch {
|
|
544
|
-
}
|
|
545
626
|
}
|
|
546
627
|
function isMissing(err) {
|
|
547
628
|
return typeof err === "object" && err !== null && err.code === "ENOENT";
|
|
@@ -591,12 +672,48 @@ async function readCredentialFileRead(stateDir) {
|
|
|
591
672
|
return { value: null, unreadable: false };
|
|
592
673
|
}
|
|
593
674
|
}
|
|
594
|
-
async function clearAllCredentials(stateDir) {
|
|
595
|
-
|
|
675
|
+
async function clearAllCredentials(stateDir, opts = {}) {
|
|
676
|
+
const failures = [];
|
|
677
|
+
const tokPath = tokenFilePath(stateDir);
|
|
596
678
|
try {
|
|
597
|
-
await fs3.rm(
|
|
598
|
-
} catch {
|
|
679
|
+
await fs3.rm(tokPath, { force: true });
|
|
680
|
+
} catch (err) {
|
|
681
|
+
failures.push(`${tokPath} (${codeOf2(err)})`);
|
|
682
|
+
}
|
|
683
|
+
const credPath = credentialFilePath(stateDir);
|
|
684
|
+
try {
|
|
685
|
+
await withAuthDocLock(credPath, () => clearSignInRecord(credPath, opts.notify ?? toStderr));
|
|
686
|
+
} catch (err) {
|
|
687
|
+
failures.push(`${credPath} (${codeOf2(err)})`);
|
|
688
|
+
}
|
|
689
|
+
if (failures.length) {
|
|
690
|
+
throw new Error(`Could not remove the sign-in from ${failures.join(" and ")}; it is still on disk.`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async function clearSignInRecord(path, notify) {
|
|
694
|
+
let raw;
|
|
695
|
+
try {
|
|
696
|
+
raw = await fs3.readFile(path, "utf8");
|
|
697
|
+
} catch (err) {
|
|
698
|
+
if (isMissing(err)) return;
|
|
699
|
+
await fs3.rm(path, { force: true });
|
|
700
|
+
notify("auth.json could not be read and was removed; MCP and agent credentials in it are gone");
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const doc = authObjectOf(raw);
|
|
704
|
+
const rest = doc ? withoutSignIn(doc) : {};
|
|
705
|
+
if (Object.keys(rest).length > 0) {
|
|
706
|
+
try {
|
|
707
|
+
await writeFilePrivateAtomic(path, `${JSON.stringify(rest, null, 2)}
|
|
708
|
+
`);
|
|
709
|
+
return;
|
|
710
|
+
} catch {
|
|
711
|
+
await fs3.rm(path, { force: true });
|
|
712
|
+
notify("auth.json could not be rewritten and was removed; MCP and agent credentials in it are gone");
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
599
715
|
}
|
|
716
|
+
await fs3.rm(path, { force: true });
|
|
600
717
|
}
|
|
601
718
|
async function resolveNodeCredential(opts = {}) {
|
|
602
719
|
const env = opts.env ?? process.env;
|
|
@@ -644,6 +761,7 @@ async function resolveApiBase(opts = {}) {
|
|
|
644
761
|
if (explicit) return { base: explicit, source: "explicit" };
|
|
645
762
|
const fromEnv = (opts.env ?? process.env)[API_BASE_ENV]?.trim();
|
|
646
763
|
if (fromEnv) return { base: fromEnv, source: "env" };
|
|
764
|
+
if (opts.ignoreCredential) return { base: DEFAULT_API_BASE, source: "default" };
|
|
647
765
|
const cred = await resolveNodeCredential({
|
|
648
766
|
env: {},
|
|
649
767
|
...opts.stateDir ? { stateDir: opts.stateDir } : {}
|
|
@@ -669,12 +787,17 @@ function maskAccount(account) {
|
|
|
669
787
|
function nodeAuthToken(stateDir) {
|
|
670
788
|
return { getToken: async () => (await resolveNodeCredential(stateDir ? { stateDir } : {})).token };
|
|
671
789
|
}
|
|
672
|
-
var TOKEN_ENV, API_BASE_ENV;
|
|
790
|
+
var TOKEN_ENV, API_BASE_ENV, SIGN_IN_KEYS, toStderr, AUTH_DOC_LOCKS;
|
|
673
791
|
var init_auth = __esm({
|
|
674
792
|
"../tools-node/auth.ts"() {
|
|
675
793
|
"use strict";
|
|
676
794
|
TOKEN_ENV = "CORENEL_TOKEN";
|
|
677
795
|
API_BASE_ENV = "CORENEL_API_BASE";
|
|
796
|
+
SIGN_IN_KEYS = ["accessToken", "refreshToken", "expiresAt", "account", "apiBase"];
|
|
797
|
+
toStderr = (line) => {
|
|
798
|
+
console.error(line);
|
|
799
|
+
};
|
|
800
|
+
AUTH_DOC_LOCKS = Symbol.for("corenel.tools-node.authDocLocks");
|
|
678
801
|
}
|
|
679
802
|
});
|
|
680
803
|
|
|
@@ -12547,7 +12670,7 @@ var require_runtime = __commonJS({
|
|
|
12547
12670
|
}
|
|
12548
12671
|
return p && p.lookup(name);
|
|
12549
12672
|
};
|
|
12550
|
-
_proto.resolve = function
|
|
12673
|
+
_proto.resolve = function resolve5(name, forWrite) {
|
|
12551
12674
|
var p = forWrite && this.isolateWrites ? void 0 : this.parent;
|
|
12552
12675
|
var val = this.variables[name];
|
|
12553
12676
|
if (val !== void 0) {
|
|
@@ -14277,7 +14400,7 @@ var require_loader = __commonJS({
|
|
|
14277
14400
|
return _EmitterObj.apply(this, arguments) || this;
|
|
14278
14401
|
}
|
|
14279
14402
|
var _proto = Loader2.prototype;
|
|
14280
|
-
_proto.resolve = function
|
|
14403
|
+
_proto.resolve = function resolve5(from, to) {
|
|
14281
14404
|
return path.resolve(path.dirname(from), to);
|
|
14282
14405
|
};
|
|
14283
14406
|
_proto.isRelative = function isRelative(filename) {
|
|
@@ -17813,10 +17936,10 @@ ${content}` : parentData.content;
|
|
|
17813
17936
|
});
|
|
17814
17937
|
this.registerFiltersOnEnv(env);
|
|
17815
17938
|
const renderTimeout = 5e3;
|
|
17816
|
-
const renderPromise = new Promise((
|
|
17939
|
+
const renderPromise = new Promise((resolve5, reject) => {
|
|
17817
17940
|
try {
|
|
17818
17941
|
const result = env.renderString(content, context.parameters);
|
|
17819
|
-
|
|
17942
|
+
resolve5(result);
|
|
17820
17943
|
} catch (error) {
|
|
17821
17944
|
reject(error);
|
|
17822
17945
|
}
|
|
@@ -18839,7 +18962,7 @@ var init_openBrowser = __esm({
|
|
|
18839
18962
|
// ../tools-node/approvalsStore.ts
|
|
18840
18963
|
import { promises as fs5, realpath } from "node:fs";
|
|
18841
18964
|
import { createHash, randomBytes as randomBytes2 } from "node:crypto";
|
|
18842
|
-
import { basename as basename3, dirname as dirname4, join as join5, relative as relative2, resolve as
|
|
18965
|
+
import { basename as basename3, dirname as dirname4, join as join5, relative as relative2, resolve as resolve3 } from "node:path";
|
|
18843
18966
|
function approvalsHomeDir(base) {
|
|
18844
18967
|
return join5(corenelDir(base), "approvals");
|
|
18845
18968
|
}
|
|
@@ -18907,7 +19030,7 @@ async function realOrAncestor(abs) {
|
|
|
18907
19030
|
}
|
|
18908
19031
|
}
|
|
18909
19032
|
async function workspaceApprovalsId(dir) {
|
|
18910
|
-
const real = await realOrAncestor(
|
|
19033
|
+
const real = await realOrAncestor(resolve3(dir));
|
|
18911
19034
|
const norm = process.platform === "win32" ? real.toLowerCase() : real;
|
|
18912
19035
|
return createHash("sha256").update(norm).digest("hex").slice(0, 32);
|
|
18913
19036
|
}
|
|
@@ -18926,7 +19049,7 @@ function sameIdentity(a, b) {
|
|
|
18926
19049
|
return a !== null && b !== null && a.dev === b.dev && a.ino === b.ino;
|
|
18927
19050
|
}
|
|
18928
19051
|
async function existingAncestor(abs) {
|
|
18929
|
-
let cur =
|
|
19052
|
+
let cur = resolve3(abs);
|
|
18930
19053
|
const rest = [];
|
|
18931
19054
|
for (; ; ) {
|
|
18932
19055
|
if (await identity(cur) !== null) return { dir: cur, rest };
|
|
@@ -18937,12 +19060,12 @@ async function existingAncestor(abs) {
|
|
|
18937
19060
|
}
|
|
18938
19061
|
}
|
|
18939
19062
|
async function insideAnyRoot(roots, dir) {
|
|
18940
|
-
const target =
|
|
19063
|
+
const target = resolve3(dir);
|
|
18941
19064
|
const realTarget = await realOrAncestor(target);
|
|
18942
19065
|
const targetAnc = await existingAncestor(realTarget);
|
|
18943
19066
|
const targetAncId = await identity(targetAnc.dir);
|
|
18944
19067
|
for (const r of roots) {
|
|
18945
|
-
const root =
|
|
19068
|
+
const root = resolve3(r);
|
|
18946
19069
|
if (contains(root, target)) return r;
|
|
18947
19070
|
const realRoot = await realOrAncestor(root);
|
|
18948
19071
|
if (contains(realRoot, realTarget)) return r;
|
|
@@ -18975,19 +19098,130 @@ var init_approvalsStore = __esm({
|
|
|
18975
19098
|
}
|
|
18976
19099
|
});
|
|
18977
19100
|
|
|
19101
|
+
// ../tools-node/deviceLogin.ts
|
|
19102
|
+
function deadlineSignal(ms, outer) {
|
|
19103
|
+
const ac = new AbortController();
|
|
19104
|
+
const onOuter = () => ac.abort();
|
|
19105
|
+
const timer = setTimeout(() => ac.abort(), ms);
|
|
19106
|
+
if (outer) {
|
|
19107
|
+
if (outer.aborted) ac.abort();
|
|
19108
|
+
else outer.addEventListener("abort", onOuter, { once: true });
|
|
19109
|
+
}
|
|
19110
|
+
return {
|
|
19111
|
+
signal: ac.signal,
|
|
19112
|
+
dispose: () => {
|
|
19113
|
+
clearTimeout(timer);
|
|
19114
|
+
outer?.removeEventListener("abort", onOuter);
|
|
19115
|
+
}
|
|
19116
|
+
};
|
|
19117
|
+
}
|
|
19118
|
+
async function whoIsThis(base, token, f, outer, deadlineMs = ACCOUNT_LOOKUP_MS) {
|
|
19119
|
+
const { signal, dispose } = deadlineSignal(deadlineMs, outer);
|
|
19120
|
+
try {
|
|
19121
|
+
const res = await f(`${base}/auth/me`, {
|
|
19122
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
19123
|
+
signal
|
|
19124
|
+
});
|
|
19125
|
+
if (!res.ok) return void 0;
|
|
19126
|
+
const body = JSON.parse(await res.text());
|
|
19127
|
+
const email = body?.user?.email;
|
|
19128
|
+
if (typeof email !== "string") return void 0;
|
|
19129
|
+
const trimmed = email.trim();
|
|
19130
|
+
return trimmed ? trimmed : void 0;
|
|
19131
|
+
} catch {
|
|
19132
|
+
return void 0;
|
|
19133
|
+
} finally {
|
|
19134
|
+
dispose();
|
|
19135
|
+
}
|
|
19136
|
+
}
|
|
19137
|
+
function defaultOpen(uri, code3) {
|
|
19138
|
+
process.stdout.write(`
|
|
19139
|
+
To authorize, open: ${uri}
|
|
19140
|
+
and enter the code: ${code3}
|
|
19141
|
+
|
|
19142
|
+
Waiting for authorization\u2026
|
|
19143
|
+
`);
|
|
19144
|
+
}
|
|
19145
|
+
async function deviceLogin(opts) {
|
|
19146
|
+
const f = opts.fetchImpl ?? fetch;
|
|
19147
|
+
const base = opts.base.replace(/\/$/, "");
|
|
19148
|
+
const post = async (path, body) => {
|
|
19149
|
+
const url = `${base}${path}`;
|
|
19150
|
+
const res = await f(url, {
|
|
19151
|
+
method: "POST",
|
|
19152
|
+
headers: { "Content-Type": "application/json" },
|
|
19153
|
+
body: JSON.stringify(body),
|
|
19154
|
+
signal: opts.signal
|
|
19155
|
+
});
|
|
19156
|
+
const text = await res.text();
|
|
19157
|
+
try {
|
|
19158
|
+
return JSON.parse(text);
|
|
19159
|
+
} catch {
|
|
19160
|
+
const looksLikeHtml = /^\s*</.test(text);
|
|
19161
|
+
throw new Error(
|
|
19162
|
+
`${url} answered ${res.status} with ${looksLikeHtml ? "an HTML page" : "a non-JSON body"}, not JSON.` + (res.status === 404 ? `
|
|
19163
|
+
That usually means --base is wrong. The device endpoints live under /api \u2014 try --base ${base.replace(/\/api$/, "")}/api` : "")
|
|
19164
|
+
);
|
|
19165
|
+
}
|
|
19166
|
+
};
|
|
19167
|
+
const dc = await post("/oauth/device/code", { client_id: "corenel-cli", scope: "agent pair" });
|
|
19168
|
+
if (!dc.device_code) throw new Error("device authorization failed");
|
|
19169
|
+
(opts.open ?? defaultOpen)(dc.verification_uri, dc.user_code);
|
|
19170
|
+
let interval = opts.pollMs ?? Math.max(MIN_POLL_MS, (dc.interval ?? 5) * 1e3);
|
|
19171
|
+
const lifetimeMs = (typeof dc.expires_in === "number" && dc.expires_in > 0 ? dc.expires_in : DEFAULT_LIFETIME_S) * 1e3;
|
|
19172
|
+
const deadline = Date.now() + lifetimeMs;
|
|
19173
|
+
for (; ; ) {
|
|
19174
|
+
if (opts.signal?.aborted) throw new Error("login cancelled");
|
|
19175
|
+
if (Date.now() >= deadline) {
|
|
19176
|
+
throw new Error(`the code ${dc.user_code} expired before it was authorized -- run \`corenel login\` again`);
|
|
19177
|
+
}
|
|
19178
|
+
await sleep2(interval);
|
|
19179
|
+
const tok = await post("/oauth/token", {
|
|
19180
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
19181
|
+
device_code: dc.device_code,
|
|
19182
|
+
client_id: "corenel-cli"
|
|
19183
|
+
});
|
|
19184
|
+
if (tok.access_token) {
|
|
19185
|
+
const claimed = typeof tok.account === "string" ? tok.account.trim() : "";
|
|
19186
|
+
const account = claimed || await whoIsThis(base, tok.access_token, f, opts.signal, opts.accountLookupMs);
|
|
19187
|
+
await opts.store(tok.access_token, account || void 0);
|
|
19188
|
+
return tok.access_token;
|
|
19189
|
+
}
|
|
19190
|
+
if (tok.error === "authorization_pending") continue;
|
|
19191
|
+
if (tok.error === "slow_down") {
|
|
19192
|
+
interval += 5e3;
|
|
19193
|
+
continue;
|
|
19194
|
+
}
|
|
19195
|
+
throw new Error(tok.error_description || tok.error || "login failed");
|
|
19196
|
+
}
|
|
19197
|
+
}
|
|
19198
|
+
var sleep2, MIN_POLL_MS, DEFAULT_LIFETIME_S, ACCOUNT_LOOKUP_MS;
|
|
19199
|
+
var init_deviceLogin = __esm({
|
|
19200
|
+
"../tools-node/deviceLogin.ts"() {
|
|
19201
|
+
"use strict";
|
|
19202
|
+
sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
19203
|
+
MIN_POLL_MS = 1e3;
|
|
19204
|
+
DEFAULT_LIFETIME_S = 900;
|
|
19205
|
+
ACCOUNT_LOOKUP_MS = 5e3;
|
|
19206
|
+
}
|
|
19207
|
+
});
|
|
19208
|
+
|
|
18978
19209
|
// ../tools-node/index.ts
|
|
18979
19210
|
var tools_node_exports = {};
|
|
18980
19211
|
__export(tools_node_exports, {
|
|
18981
19212
|
API_BASE_ENV: () => API_BASE_ENV,
|
|
18982
19213
|
NodeFileService: () => NodeFileService,
|
|
19214
|
+
SIGN_IN_KEYS: () => SIGN_IN_KEYS,
|
|
18983
19215
|
TOKEN_ENV: () => TOKEN_ENV,
|
|
18984
19216
|
approvalsHomeDir: () => approvalsHomeDir,
|
|
18985
19217
|
authTokenPath: () => authTokenPath,
|
|
19218
|
+
backupCorruptAuthDoc: () => backupCorruptAuthDoc,
|
|
18986
19219
|
clearAllCredentials: () => clearAllCredentials,
|
|
18987
19220
|
clearAuthToken: () => clearAuthToken,
|
|
18988
19221
|
corenelDir: () => corenelDir,
|
|
18989
19222
|
credentialDir: () => credentialDir,
|
|
18990
19223
|
credentialFilePath: () => credentialFilePath,
|
|
19224
|
+
deviceLogin: () => deviceLogin,
|
|
18991
19225
|
ensurePrivateDir: () => ensurePrivateDir,
|
|
18992
19226
|
fileTextStore: () => fileTextStore,
|
|
18993
19227
|
insideAnyRoot: () => insideAnyRoot,
|
|
@@ -18997,6 +19231,7 @@ __export(tools_node_exports, {
|
|
|
18997
19231
|
nodeAuthToken: () => nodeAuthToken,
|
|
18998
19232
|
nodePromptHost: () => nodePromptHost,
|
|
18999
19233
|
openBrowser: () => openBrowser,
|
|
19234
|
+
readAuthDocForWrite: () => readAuthDocForWrite,
|
|
19000
19235
|
readAuthToken: () => readAuthToken,
|
|
19001
19236
|
readAuthTokenRead: () => readAuthTokenRead,
|
|
19002
19237
|
readCredentialFile: () => readCredentialFile,
|
|
@@ -19005,8 +19240,11 @@ __export(tools_node_exports, {
|
|
|
19005
19240
|
resolveNodeCredential: () => resolveNodeCredential,
|
|
19006
19241
|
sidecarDir: () => sidecarDir,
|
|
19007
19242
|
tokenFilePath: () => tokenFilePath,
|
|
19243
|
+
whoIsThis: () => whoIsThis,
|
|
19244
|
+
withAuthDocLock: () => withAuthDocLock,
|
|
19008
19245
|
workspaceApprovalsId: () => workspaceApprovalsId,
|
|
19009
19246
|
writeAuthToken: () => writeAuthToken,
|
|
19247
|
+
writeFilePrivateAtomic: () => writeFilePrivateAtomic,
|
|
19010
19248
|
writeLoginCredential: () => writeLoginCredential
|
|
19011
19249
|
});
|
|
19012
19250
|
var init_tools_node = __esm({
|
|
@@ -19018,6 +19256,7 @@ var init_tools_node = __esm({
|
|
|
19018
19256
|
init_migrateHome();
|
|
19019
19257
|
init_openBrowser();
|
|
19020
19258
|
init_approvalsStore();
|
|
19259
|
+
init_deviceLogin();
|
|
19021
19260
|
}
|
|
19022
19261
|
});
|
|
19023
19262
|
|
|
@@ -22828,9 +23067,19 @@ __export(crew_exports, {
|
|
|
22828
23067
|
CREW_EXCLUDED_TOOLS: () => CREW_EXCLUDED_TOOLS,
|
|
22829
23068
|
CREW_SEG: () => CREW_SEG,
|
|
22830
23069
|
DEPLOYMENT_FILE: () => DEPLOYMENT_FILE,
|
|
23070
|
+
DONTS_DUPLICATE: () => DONTS_DUPLICATE,
|
|
23071
|
+
DONTS_MODEL_INVALID: () => DONTS_MODEL_INVALID,
|
|
23072
|
+
DONTS_TOO_MANY: () => DONTS_TOO_MANY,
|
|
23073
|
+
DONT_BLANK: () => DONT_BLANK,
|
|
23074
|
+
DONT_CONTROL: () => DONT_CONTROL,
|
|
23075
|
+
DONT_TOO_LONG: () => DONT_TOO_LONG,
|
|
22831
23076
|
MAX_ROOM_BRIEF: () => MAX_ROOM_BRIEF,
|
|
23077
|
+
MEMORY_USERS_DIR: () => MEMORY_USERS_DIR,
|
|
23078
|
+
NOT_IN_YOUR_MEMORY: () => NOT_IN_YOUR_MEMORY,
|
|
23079
|
+
NO_ASKER_MEMORY_READ_ONLY: () => NO_ASKER_MEMORY_READ_ONLY,
|
|
22832
23080
|
PROJECT_MARKER_FILE: () => PROJECT_MARKER_FILE,
|
|
22833
23081
|
ROOM_KIND: () => ROOM_KIND,
|
|
23082
|
+
ROOM_MEMORY_READ_ONLY: () => ROOM_MEMORY_READ_ONLY,
|
|
22834
23083
|
RUN_STATUS_VALUES: () => RUN_STATUS_VALUES,
|
|
22835
23084
|
RoomReadError: () => RoomReadError,
|
|
22836
23085
|
RoomStore: () => RoomStore,
|
|
@@ -22859,6 +23108,7 @@ __export(crew_exports, {
|
|
|
22859
23108
|
deploymentKey: () => deploymentKey,
|
|
22860
23109
|
deploymentProjectFor: () => deploymentProjectFor,
|
|
22861
23110
|
deploymentRootFor: () => deploymentRootFor,
|
|
23111
|
+
dontsProblems: () => dontsProblems,
|
|
22862
23112
|
editFindingByHuman: () => editFindingByHuman,
|
|
22863
23113
|
emptyDeployment: () => emptyDeployment,
|
|
22864
23114
|
encodeRoomLine: () => encodeRoomLine,
|
|
@@ -22883,6 +23133,8 @@ __export(crew_exports, {
|
|
|
22883
23133
|
loadCrewSummaryAt: () => loadCrewSummaryAt,
|
|
22884
23134
|
makeAgentMemoryProvider: () => makeAgentMemoryProvider,
|
|
22885
23135
|
makeCrewTurnRuntime: () => makeCrewTurnRuntime,
|
|
23136
|
+
makePartitionedMemoryProvider: () => makePartitionedMemoryProvider,
|
|
23137
|
+
makeReadOnlyMemoryProvider: () => makeReadOnlyMemoryProvider,
|
|
22886
23138
|
matchFileChange: () => matchFileChange,
|
|
22887
23139
|
matchRunComplete: () => matchRunComplete,
|
|
22888
23140
|
newCrewDraft: () => newCrewDraft,
|
|
@@ -22921,6 +23173,7 @@ __export(crew_exports, {
|
|
|
22921
23173
|
takeTurn: () => takeTurn,
|
|
22922
23174
|
tightenAutonomy: () => tightenAutonomy,
|
|
22923
23175
|
turnCosts: () => turnCosts,
|
|
23176
|
+
userMemoryRoot: () => userMemoryRoot,
|
|
22924
23177
|
validateRoomRequest: () => validateRoomRequest,
|
|
22925
23178
|
writeCrewAgentAt: () => writeCrewAgentAt
|
|
22926
23179
|
});
|
|
@@ -22945,16 +23198,25 @@ function parseFrontmatter(text) {
|
|
|
22945
23198
|
|
|
22946
23199
|
// ../crew/policy.ts
|
|
22947
23200
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
22948
|
-
var LEVELS = ["allow", "ask", "deny"];
|
|
23201
|
+
var LEVELS = ["allow", "ask", "owner", "deny"];
|
|
22949
23202
|
var UNATTENDED = ["deny", "park", "allow"];
|
|
22950
23203
|
function asLevel(v) {
|
|
22951
23204
|
return typeof v === "string" && LEVELS.includes(v) ? v : void 0;
|
|
22952
23205
|
}
|
|
23206
|
+
function asDefaultLevel(v) {
|
|
23207
|
+
const l = asLevel(v);
|
|
23208
|
+
return l === "owner" ? void 0 : l;
|
|
23209
|
+
}
|
|
22953
23210
|
function asStringArray(v) {
|
|
22954
23211
|
if (!Array.isArray(v)) return void 0;
|
|
22955
23212
|
const out = v.filter((x) => typeof x === "string");
|
|
22956
23213
|
return out.length ? out : void 0;
|
|
22957
23214
|
}
|
|
23215
|
+
function asDonts(v) {
|
|
23216
|
+
if (!Array.isArray(v)) return void 0;
|
|
23217
|
+
const out = v.filter((x) => typeof x === "string" || typeof x === "number" || typeof x === "boolean").map((x) => String(x).trim()).filter((s) => s !== "");
|
|
23218
|
+
return out.length ? out : void 0;
|
|
23219
|
+
}
|
|
22958
23220
|
function coercePermissions(v) {
|
|
22959
23221
|
const out = {};
|
|
22960
23222
|
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
@@ -22986,6 +23248,8 @@ function parsePolicyYaml(text, agentName) {
|
|
|
22986
23248
|
const rawPush = raw.pushWaitMs;
|
|
22987
23249
|
const pushWaitMs = typeof rawPush === "number" && Number.isFinite(rawPush) && rawPush > 0 ? rawPush : void 0;
|
|
22988
23250
|
const skills = Array.isArray(raw.skills) ? raw.skills.filter((s) => typeof s === "string" && s.trim() !== "").map((s) => s.trim()) : void 0;
|
|
23251
|
+
const donts = asDonts(raw.donts);
|
|
23252
|
+
const dontsModel = typeof raw.donts_model === "string" && raw.donts_model.trim() ? raw.donts_model.trim() : void 0;
|
|
22989
23253
|
const policy = {
|
|
22990
23254
|
version: 1,
|
|
22991
23255
|
name: typeof raw.name === "string" ? raw.name : `crew:${agentName}`,
|
|
@@ -23005,15 +23269,17 @@ function parsePolicyYaml(text, agentName) {
|
|
|
23005
23269
|
* sub-agents and make outbound search calls without being asked. Say it
|
|
23006
23270
|
* here rather than let the word "reads" imply a narrower grant than the
|
|
23007
23271
|
* code makes. */
|
|
23008
|
-
defaultPermission:
|
|
23009
|
-
mutatingDefault:
|
|
23272
|
+
defaultPermission: asDefaultLevel(raw.defaultPermission),
|
|
23273
|
+
mutatingDefault: asDefaultLevel(raw.mutatingDefault) ?? "deny",
|
|
23010
23274
|
permissions: coercePermissions(raw.permissions),
|
|
23011
23275
|
maxTurns: typeof raw.maxTurns === "number" ? raw.maxTurns : void 0,
|
|
23012
23276
|
maxToolCalls: typeof raw.maxToolCalls === "number" ? raw.maxToolCalls : void 0,
|
|
23013
23277
|
unattended,
|
|
23014
23278
|
parkTimeoutMs,
|
|
23015
23279
|
...pushWaitMs !== void 0 ? { pushWaitMs } : {},
|
|
23016
|
-
...skills && skills.length ? { skills } : {}
|
|
23280
|
+
...skills && skills.length ? { skills } : {},
|
|
23281
|
+
...donts ? { donts } : {},
|
|
23282
|
+
...dontsModel ? { donts_model: dontsModel } : {}
|
|
23017
23283
|
};
|
|
23018
23284
|
if (toolsAllow || toolsDeny) {
|
|
23019
23285
|
policy.tools = { ...toolsAllow ? { allow: toolsAllow } : {}, ...toolsDeny ? { deny: toolsDeny } : {} };
|
|
@@ -23021,6 +23287,47 @@ function parsePolicyYaml(text, agentName) {
|
|
|
23021
23287
|
return policy;
|
|
23022
23288
|
}
|
|
23023
23289
|
|
|
23290
|
+
// ../crew/donts.ts
|
|
23291
|
+
import { DONTS_MAX, DONT_MAX_CHARS, hasControlChars } from "@corenel/protocol";
|
|
23292
|
+
var DONT_BLANK = "A don't cannot be blank.";
|
|
23293
|
+
var DONT_TOO_LONG = `A don't can be at most ${DONT_MAX_CHARS} characters.`;
|
|
23294
|
+
var DONTS_TOO_MANY = `An agent can have at most ${DONTS_MAX} don'ts.`;
|
|
23295
|
+
var DONTS_DUPLICATE = "Each don't must be different from the others.";
|
|
23296
|
+
var DONT_CONTROL = "A don't cannot contain control characters.";
|
|
23297
|
+
var DONTS_MODEL_INVALID = "The model for checking don'ts must be a model id.";
|
|
23298
|
+
function dontsProblems(policy) {
|
|
23299
|
+
const out = /* @__PURE__ */ new Set();
|
|
23300
|
+
const list = policy.donts ?? [];
|
|
23301
|
+
if (list.length > DONTS_MAX) out.add(DONTS_TOO_MANY);
|
|
23302
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23303
|
+
let blank = false;
|
|
23304
|
+
let long = false;
|
|
23305
|
+
let dup = false;
|
|
23306
|
+
let control = false;
|
|
23307
|
+
for (const raw of list) {
|
|
23308
|
+
const t = raw.trim();
|
|
23309
|
+
if (t === "") {
|
|
23310
|
+
blank = true;
|
|
23311
|
+
continue;
|
|
23312
|
+
}
|
|
23313
|
+
if (t.length > DONT_MAX_CHARS) long = true;
|
|
23314
|
+
if (hasControlChars(t)) control = true;
|
|
23315
|
+
const key = t.toLowerCase();
|
|
23316
|
+
if (seen.has(key)) dup = true;
|
|
23317
|
+
seen.add(key);
|
|
23318
|
+
}
|
|
23319
|
+
if (blank) out.add(DONT_BLANK);
|
|
23320
|
+
if (long) out.add(DONT_TOO_LONG);
|
|
23321
|
+
if (dup) out.add(DONTS_DUPLICATE);
|
|
23322
|
+
if (control) out.add(DONT_CONTROL);
|
|
23323
|
+
if (policy.donts_model !== void 0) {
|
|
23324
|
+
const m = policy.donts_model.trim();
|
|
23325
|
+
if (m === "" || hasControlChars(m)) out.add(DONTS_MODEL_INVALID);
|
|
23326
|
+
}
|
|
23327
|
+
const order = [DONTS_TOO_MANY, DONT_BLANK, DONT_TOO_LONG, DONTS_DUPLICATE, DONT_CONTROL, DONTS_MODEL_INVALID];
|
|
23328
|
+
return order.filter((s) => out.has(s));
|
|
23329
|
+
}
|
|
23330
|
+
|
|
23024
23331
|
// ../crew/paths.ts
|
|
23025
23332
|
import { stateDirName as stateDirName2 } from "@corenel/protocol";
|
|
23026
23333
|
var CREW_SEG = "crew";
|
|
@@ -23098,7 +23405,7 @@ function applyAutonomy(def, preset) {
|
|
|
23098
23405
|
const orchestration = preset === "interactive" ? { ...rest, checkIn: true } : rest;
|
|
23099
23406
|
return { policy: { ...def.policy, permissions }, orchestration };
|
|
23100
23407
|
}
|
|
23101
|
-
var ORDER = { deny: 0,
|
|
23408
|
+
var ORDER = { deny: 0, owner: 1, ask: 2, allow: 3 };
|
|
23102
23409
|
var LABEL = { interactive: "Interactive", hitl: "HITL", autonomous: "Autonomous" };
|
|
23103
23410
|
var PRESETS = ["interactive", "hitl", "autonomous"];
|
|
23104
23411
|
function isAutonomyPreset(v) {
|
|
@@ -23346,6 +23653,8 @@ function serializePolicy(policy) {
|
|
|
23346
23653
|
if (policy.mutatingDefault) ordered.mutatingDefault = policy.mutatingDefault;
|
|
23347
23654
|
if (policy.permissions && Object.keys(policy.permissions).length) ordered.permissions = policy.permissions;
|
|
23348
23655
|
if (policy.skills && policy.skills.length) ordered.skills = policy.skills;
|
|
23656
|
+
if (policy.donts && policy.donts.length) ordered.donts = policy.donts;
|
|
23657
|
+
if (policy.donts_model) ordered.donts_model = policy.donts_model;
|
|
23349
23658
|
if (policy.tools) ordered.tools = policy.tools;
|
|
23350
23659
|
if (typeof policy.maxTurns === "number") ordered.maxTurns = policy.maxTurns;
|
|
23351
23660
|
if (typeof policy.maxToolCalls === "number") ordered.maxToolCalls = policy.maxToolCalls;
|
|
@@ -23421,9 +23730,24 @@ function stripWorkRoots(settings) {
|
|
|
23421
23730
|
const { workRoots: _workRoots, ...rest } = settings;
|
|
23422
23731
|
return rest;
|
|
23423
23732
|
}
|
|
23733
|
+
async function dontsUnchanged(files, dir, def) {
|
|
23734
|
+
let text;
|
|
23735
|
+
try {
|
|
23736
|
+
text = await files.read(`${dir}/POLICY.yaml`);
|
|
23737
|
+
} catch {
|
|
23738
|
+
return false;
|
|
23739
|
+
}
|
|
23740
|
+
const stored = parsePolicyYaml(text, def.name).donts ?? [];
|
|
23741
|
+
const next = (def.policy.donts ?? []).map((d) => d.trim());
|
|
23742
|
+
return stored.length === next.length && stored.every((d, i) => d === next[i]);
|
|
23743
|
+
}
|
|
23424
23744
|
async function writeCrewAgentAt(files, root, def) {
|
|
23425
23745
|
if (!isValidAgentName(def.name)) throw new Error(`invalid crew agent name: ${def.name}`);
|
|
23426
23746
|
const dir = `${root}/${def.name}`;
|
|
23747
|
+
if (!await dontsUnchanged(files, dir, def)) {
|
|
23748
|
+
const problems = dontsProblems(def.policy);
|
|
23749
|
+
if (problems.length) throw new Error(problems.join(" "));
|
|
23750
|
+
}
|
|
23427
23751
|
await put(files, `${dir}/AGENT.md`, serializeAgentMd(def));
|
|
23428
23752
|
await put(files, `${dir}/SOUL.md`, def.soul);
|
|
23429
23753
|
await put(files, `${dir}/PERSONA.md`, def.persona);
|
|
@@ -23553,6 +23877,7 @@ function crewBudgetConstraints(def) {
|
|
|
23553
23877
|
};
|
|
23554
23878
|
}
|
|
23555
23879
|
var MEMORY_BLOCK = registerSyncConfig("crew/memory.md");
|
|
23880
|
+
var MEMORY_TOOLS_ONLY_BLOCK = registerSyncConfig("crew/memory-tools-only.md");
|
|
23556
23881
|
var SELF_DIRECTION_BLOCK = registerSyncConfig("crew/self-direction.md");
|
|
23557
23882
|
var CHECK_IN_BLOCK = "--- Check-ins ---\nYou are in interactive mode. After each step of the approved plan (each call_agent result, each sidecar_run_check, each git step), write the user a short check-in: what just happened, what is next, and anything that changes the plan. Then continue; do not end your turn to wait. The user can stop the run at any time, and submit_plan, sidecar_git_push and sidecar_open_pr still ask for approval.";
|
|
23558
23883
|
function identityBlock() {
|
|
@@ -23574,10 +23899,17 @@ ${def.soul.trim()}`);
|
|
|
23574
23899
|
${def.persona.trim()}`);
|
|
23575
23900
|
if (def.instructions.trim()) parts.push(`--- Instructions ---
|
|
23576
23901
|
${def.instructions.trim()}`);
|
|
23902
|
+
if (memoryLocation === null) {
|
|
23903
|
+
parts.push(readConfigSyncRendered(MEMORY_TOOLS_ONLY_BLOCK));
|
|
23904
|
+
return withTail(def, parts);
|
|
23905
|
+
}
|
|
23577
23906
|
const location = memoryLocation ?? "memory/";
|
|
23578
23907
|
const dir = location.replace(/\/+$/, "");
|
|
23579
23908
|
const index = `${dir}/MEMORY.md`;
|
|
23580
23909
|
parts.push(readConfigSyncRendered(MEMORY_BLOCK, { dir, index }));
|
|
23910
|
+
return withTail(def, parts);
|
|
23911
|
+
}
|
|
23912
|
+
function withTail(def, parts) {
|
|
23581
23913
|
const selfDirection = selfDirectionBlock(def);
|
|
23582
23914
|
if (selfDirection) parts.push(selfDirection);
|
|
23583
23915
|
if (def.orchestration?.checkIn === true) parts.push(CHECK_IN_BLOCK);
|
|
@@ -23689,6 +24021,9 @@ var RUN_STATUS_ALIASES = [
|
|
|
23689
24021
|
["max_tokens", "stuck"],
|
|
23690
24022
|
["refusal", "policy"],
|
|
23691
24023
|
["content_filter", "policy"],
|
|
24024
|
+
// Team don'ts (spec 2026-10-07 Part F): the agent's own rules stopped the
|
|
24025
|
+
// run. A guard refusing to let it finish, the same fact `policy` names.
|
|
24026
|
+
["donts", "policy"],
|
|
23692
24027
|
["interrupted", "aborted"]
|
|
23693
24028
|
];
|
|
23694
24029
|
var RUN_STATUS_BY_RAW = new Map([
|
|
@@ -23905,21 +24240,27 @@ function grantOfferedTools(policy, offered) {
|
|
|
23905
24240
|
}
|
|
23906
24241
|
|
|
23907
24242
|
// ../crew/memory.ts
|
|
24243
|
+
import { MEMORY_USERS_DIR } from "@corenel/protocol";
|
|
23908
24244
|
import { makeFactStore } from "@corenel/harness/memory/store";
|
|
23909
24245
|
function agentMemoryRoot(crewRoot2, agentName) {
|
|
23910
24246
|
return `${crewRoot2}/${agentName}/memory`;
|
|
23911
24247
|
}
|
|
24248
|
+
var USER_KEY = /^[0-9a-f]{64}$/;
|
|
24249
|
+
function userMemoryRoot(sharedRoot, userKey) {
|
|
24250
|
+
if (!USER_KEY.test(userKey)) throw new Error("invalid memory partition key");
|
|
24251
|
+
return `${sharedRoot}/${MEMORY_USERS_DIR}/${userKey}`;
|
|
24252
|
+
}
|
|
24253
|
+
var toItem = (f) => ({
|
|
24254
|
+
id: f.name,
|
|
24255
|
+
name: f.name,
|
|
24256
|
+
type: f.type,
|
|
24257
|
+
description: f.description,
|
|
24258
|
+
text: f.body,
|
|
24259
|
+
tags: [],
|
|
24260
|
+
createdAt: f.created
|
|
24261
|
+
});
|
|
23912
24262
|
function makeAgentMemoryProvider(getFiles, memoryRoot) {
|
|
23913
24263
|
const store = makeFactStore(getFiles, memoryRoot);
|
|
23914
|
-
const toItem = (f) => ({
|
|
23915
|
-
id: f.name,
|
|
23916
|
-
name: f.name,
|
|
23917
|
-
type: f.type,
|
|
23918
|
-
description: f.description,
|
|
23919
|
-
text: f.body,
|
|
23920
|
-
tags: [],
|
|
23921
|
-
createdAt: f.created
|
|
23922
|
-
});
|
|
23923
24264
|
return {
|
|
23924
24265
|
async save(input) {
|
|
23925
24266
|
return toItem(await store.save(input));
|
|
@@ -23943,6 +24284,68 @@ function makeAgentMemoryProvider(getFiles, memoryRoot) {
|
|
|
23943
24284
|
}
|
|
23944
24285
|
};
|
|
23945
24286
|
}
|
|
24287
|
+
var NOT_IN_YOUR_MEMORY = "not in your memory: that name is shared memory, which this run cannot change";
|
|
24288
|
+
var ROOM_MEMORY_READ_ONLY = "in a room, memory is read only: a room has no single asker";
|
|
24289
|
+
var NO_ASKER_MEMORY_READ_ONLY = "memory is read only for this run: it has no verified asker";
|
|
24290
|
+
function makeReadOnlyMemoryProvider(getFiles, memoryRoot, refusal) {
|
|
24291
|
+
const inner = makeAgentMemoryProvider(getFiles, memoryRoot);
|
|
24292
|
+
return {
|
|
24293
|
+
async save() {
|
|
24294
|
+
throw new Error(refusal);
|
|
24295
|
+
},
|
|
24296
|
+
read: (name) => inner.read(name),
|
|
24297
|
+
recall: (query, limit) => inner.recall(query, limit),
|
|
24298
|
+
list: (limit) => inner.list(limit),
|
|
24299
|
+
async forget() {
|
|
24300
|
+
throw new Error(refusal);
|
|
24301
|
+
},
|
|
24302
|
+
core: () => inner.core ? inner.core() : Promise.resolve({ items: [], archivedCount: 0 })
|
|
24303
|
+
};
|
|
24304
|
+
}
|
|
24305
|
+
function makePartitionedMemoryProvider(getFiles, sharedRoot, userKey) {
|
|
24306
|
+
const shared = makeFactStore(getFiles, sharedRoot);
|
|
24307
|
+
const mine = makeFactStore(getFiles, userMemoryRoot(sharedRoot, userKey));
|
|
24308
|
+
const ownNames = async () => new Set(await mine.names());
|
|
24309
|
+
return {
|
|
24310
|
+
async save(input) {
|
|
24311
|
+
return toItem(await mine.save(input));
|
|
24312
|
+
},
|
|
24313
|
+
async read(name) {
|
|
24314
|
+
const hit = await mine.read(name) ?? await shared.read(name);
|
|
24315
|
+
return hit ? toItem(hit) : null;
|
|
24316
|
+
},
|
|
24317
|
+
async recall(query, limit = 5) {
|
|
24318
|
+
const [m, s, own] = await Promise.all([mine.rankCandidates(query, limit), shared.rankCandidates(query, limit), ownNames()]);
|
|
24319
|
+
const merged = [
|
|
24320
|
+
...m.map((c) => ({ ...c, from: "mine" })),
|
|
24321
|
+
...s.filter((c) => !own.has(c.name)).map((c) => ({ ...c, from: "shared" }))
|
|
24322
|
+
].sort((a, b) => b.score - a.score).slice(0, limit);
|
|
24323
|
+
const [mh, sh] = await Promise.all([
|
|
24324
|
+
mine.hydrateRecalled(merged.filter((c) => c.from === "mine").map((c) => c.name)),
|
|
24325
|
+
shared.hydrateRecalled(merged.filter((c) => c.from === "shared").map((c) => c.name))
|
|
24326
|
+
]);
|
|
24327
|
+
const byName = /* @__PURE__ */ new Map();
|
|
24328
|
+
for (const f of sh) byName.set(f.name, f);
|
|
24329
|
+
for (const f of mh) byName.set(f.name, f);
|
|
24330
|
+
return merged.map((c) => byName.get(c.name)).filter((f) => f !== void 0).map(toItem);
|
|
24331
|
+
},
|
|
24332
|
+
async list(limit) {
|
|
24333
|
+
const [m, s, own] = await Promise.all([mine.list(limit), shared.list(limit), ownNames()]);
|
|
24334
|
+
return [...m, ...s.filter((f) => !own.has(f.name))].sort((a, b) => b.updated - a.updated).slice(0, limit ?? 50).map(toItem);
|
|
24335
|
+
},
|
|
24336
|
+
async forget(name) {
|
|
24337
|
+
if (!(await ownNames()).has(name) && (await shared.names()).includes(name)) throw new Error(NOT_IN_YOUR_MEMORY);
|
|
24338
|
+
return mine.forget(name);
|
|
24339
|
+
},
|
|
24340
|
+
async core() {
|
|
24341
|
+
const [m, s, own] = await Promise.all([mine.core(), shared.core(), ownNames()]);
|
|
24342
|
+
return {
|
|
24343
|
+
items: [...m.facts, ...s.facts.filter((f) => !own.has(f.name))].map(toItem),
|
|
24344
|
+
archivedCount: m.archivedCount + s.archivedCount
|
|
24345
|
+
};
|
|
24346
|
+
}
|
|
24347
|
+
};
|
|
24348
|
+
}
|
|
23946
24349
|
|
|
23947
24350
|
// ../crew/room/types.ts
|
|
23948
24351
|
function roleOf(p) {
|
|
@@ -24808,107 +25211,8 @@ function tokenGuard(getToken) {
|
|
|
24808
25211
|
};
|
|
24809
25212
|
}
|
|
24810
25213
|
|
|
24811
|
-
// src/
|
|
24812
|
-
|
|
24813
|
-
var MIN_POLL_MS = 1e3;
|
|
24814
|
-
var DEFAULT_LIFETIME_S = 900;
|
|
24815
|
-
var ACCOUNT_LOOKUP_MS = 5e3;
|
|
24816
|
-
function deadlineSignal(ms, outer) {
|
|
24817
|
-
const ac = new AbortController();
|
|
24818
|
-
const onOuter = () => ac.abort();
|
|
24819
|
-
const timer = setTimeout(() => ac.abort(), ms);
|
|
24820
|
-
if (outer) {
|
|
24821
|
-
if (outer.aborted) ac.abort();
|
|
24822
|
-
else outer.addEventListener("abort", onOuter, { once: true });
|
|
24823
|
-
}
|
|
24824
|
-
return {
|
|
24825
|
-
signal: ac.signal,
|
|
24826
|
-
dispose: () => {
|
|
24827
|
-
clearTimeout(timer);
|
|
24828
|
-
outer?.removeEventListener("abort", onOuter);
|
|
24829
|
-
}
|
|
24830
|
-
};
|
|
24831
|
-
}
|
|
24832
|
-
async function whoIsThis(base, token, f, outer, deadlineMs = ACCOUNT_LOOKUP_MS) {
|
|
24833
|
-
const { signal, dispose } = deadlineSignal(deadlineMs, outer);
|
|
24834
|
-
try {
|
|
24835
|
-
const res = await f(`${base}/auth/me`, {
|
|
24836
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
24837
|
-
signal
|
|
24838
|
-
});
|
|
24839
|
-
if (!res.ok) return void 0;
|
|
24840
|
-
const body = JSON.parse(await res.text());
|
|
24841
|
-
const email = body?.user?.email;
|
|
24842
|
-
if (typeof email !== "string") return void 0;
|
|
24843
|
-
const trimmed = email.trim();
|
|
24844
|
-
return trimmed ? trimmed : void 0;
|
|
24845
|
-
} catch {
|
|
24846
|
-
return void 0;
|
|
24847
|
-
} finally {
|
|
24848
|
-
dispose();
|
|
24849
|
-
}
|
|
24850
|
-
}
|
|
24851
|
-
function defaultOpen(uri, code3) {
|
|
24852
|
-
process.stdout.write(`
|
|
24853
|
-
To authorize, open: ${uri}
|
|
24854
|
-
and enter the code: ${code3}
|
|
24855
|
-
|
|
24856
|
-
Waiting for authorization\u2026
|
|
24857
|
-
`);
|
|
24858
|
-
}
|
|
24859
|
-
async function deviceLogin(opts) {
|
|
24860
|
-
const f = opts.fetchImpl ?? fetch;
|
|
24861
|
-
const base = opts.base.replace(/\/$/, "");
|
|
24862
|
-
const post = async (path, body) => {
|
|
24863
|
-
const url = `${base}${path}`;
|
|
24864
|
-
const res = await f(url, {
|
|
24865
|
-
method: "POST",
|
|
24866
|
-
headers: { "Content-Type": "application/json" },
|
|
24867
|
-
body: JSON.stringify(body),
|
|
24868
|
-
signal: opts.signal
|
|
24869
|
-
});
|
|
24870
|
-
const text = await res.text();
|
|
24871
|
-
try {
|
|
24872
|
-
return JSON.parse(text);
|
|
24873
|
-
} catch {
|
|
24874
|
-
const looksLikeHtml = /^\s*</.test(text);
|
|
24875
|
-
throw new Error(
|
|
24876
|
-
`${url} answered ${res.status} with ${looksLikeHtml ? "an HTML page" : "a non-JSON body"}, not JSON.` + (res.status === 404 ? `
|
|
24877
|
-
That usually means --base is wrong. The device endpoints live under /api \u2014 try --base ${base.replace(/\/api$/, "")}/api` : "")
|
|
24878
|
-
);
|
|
24879
|
-
}
|
|
24880
|
-
};
|
|
24881
|
-
const dc = await post("/oauth/device/code", { client_id: "corenel-cli", scope: "agent pair" });
|
|
24882
|
-
if (!dc.device_code) throw new Error("device authorization failed");
|
|
24883
|
-
(opts.open ?? defaultOpen)(dc.verification_uri, dc.user_code);
|
|
24884
|
-
let interval = opts.pollMs ?? Math.max(MIN_POLL_MS, (dc.interval ?? 5) * 1e3);
|
|
24885
|
-
const lifetimeMs = (typeof dc.expires_in === "number" && dc.expires_in > 0 ? dc.expires_in : DEFAULT_LIFETIME_S) * 1e3;
|
|
24886
|
-
const deadline = Date.now() + lifetimeMs;
|
|
24887
|
-
for (; ; ) {
|
|
24888
|
-
if (opts.signal?.aborted) throw new Error("login cancelled");
|
|
24889
|
-
if (Date.now() >= deadline) {
|
|
24890
|
-
throw new Error(`the code ${dc.user_code} expired before it was authorized -- run \`corenel login\` again`);
|
|
24891
|
-
}
|
|
24892
|
-
await sleep2(interval);
|
|
24893
|
-
const tok = await post("/oauth/token", {
|
|
24894
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
24895
|
-
device_code: dc.device_code,
|
|
24896
|
-
client_id: "corenel-cli"
|
|
24897
|
-
});
|
|
24898
|
-
if (tok.access_token) {
|
|
24899
|
-
const claimed = typeof tok.account === "string" ? tok.account.trim() : "";
|
|
24900
|
-
const account = claimed || await whoIsThis(base, tok.access_token, f, opts.signal, opts.accountLookupMs);
|
|
24901
|
-
await opts.store(tok.access_token, account || void 0);
|
|
24902
|
-
return tok.access_token;
|
|
24903
|
-
}
|
|
24904
|
-
if (tok.error === "authorization_pending") continue;
|
|
24905
|
-
if (tok.error === "slow_down") {
|
|
24906
|
-
interval += 5e3;
|
|
24907
|
-
continue;
|
|
24908
|
-
}
|
|
24909
|
-
throw new Error(tok.error_description || tok.error || "login failed");
|
|
24910
|
-
}
|
|
24911
|
-
}
|
|
25214
|
+
// src/cli.ts
|
|
25215
|
+
init_tools_node();
|
|
24912
25216
|
|
|
24913
25217
|
// src/runAgent.ts
|
|
24914
25218
|
import { runAgent } from "@corenel/harness/core/loop";
|
|
@@ -24952,11 +25256,13 @@ async function apiBase(explicit, opts = {}) {
|
|
|
24952
25256
|
return (await resolveCliApiBase(explicit, opts)).base;
|
|
24953
25257
|
}
|
|
24954
25258
|
async function resolveCliApiBase(explicit, opts = {}) {
|
|
25259
|
+
const env = opts.env ?? process.env;
|
|
24955
25260
|
const resolved = applySourceRunDevBase(
|
|
24956
25261
|
await resolveApiBase({
|
|
24957
25262
|
explicit,
|
|
24958
|
-
env
|
|
24959
|
-
...opts.stateDir ? { stateDir: opts.stateDir } : {}
|
|
25263
|
+
env,
|
|
25264
|
+
...opts.stateDir ? { stateDir: opts.stateDir } : {},
|
|
25265
|
+
...opts.ignoreCredential ? { ignoreCredential: true } : {}
|
|
24960
25266
|
}),
|
|
24961
25267
|
opts.sourceRun ?? sourceRunDefault
|
|
24962
25268
|
);
|
|
@@ -25011,6 +25317,13 @@ function guestCrewRoot() {
|
|
|
25011
25317
|
function guestAgentMemoryRoot(agent) {
|
|
25012
25318
|
return agentMemoryRoot(guestCrewRoot(), agent);
|
|
25013
25319
|
}
|
|
25320
|
+
function guestMemoryFor(files, agent, mode) {
|
|
25321
|
+
const root = guestAgentMemoryRoot(agent);
|
|
25322
|
+
if (mode?.kind === "read-only") {
|
|
25323
|
+
return { provider: makeReadOnlyMemoryProvider(() => files, root, NO_ASKER_MEMORY_READ_ONLY), promptRoot: null };
|
|
25324
|
+
}
|
|
25325
|
+
return { provider: makeAgentMemoryProvider(() => files, root), promptRoot: root };
|
|
25326
|
+
}
|
|
25014
25327
|
function mutatingCeiling(tools, defaultLevel = "deny") {
|
|
25015
25328
|
const overrides = {};
|
|
25016
25329
|
for (const t of tools) if (!t.mutates || t.roomSafe) overrides[toolAddress3(t)] = "allow";
|
|
@@ -25027,8 +25340,7 @@ function eventLine(e) {
|
|
|
25027
25340
|
async function runCrewAgentCli(opts) {
|
|
25028
25341
|
const files = new NodeFileService(opts.cwd);
|
|
25029
25342
|
const root = guestCrewRoot();
|
|
25030
|
-
const memoryRoot =
|
|
25031
|
-
const memory = makeAgentMemoryProvider(() => files, memoryRoot);
|
|
25343
|
+
const { provider: memory, promptRoot: memoryRoot } = guestMemoryFor(files, opts.agent, opts.memory);
|
|
25032
25344
|
const def = await loadCrewAgentAtIfExists(files, root, opts.agent, "workspace");
|
|
25033
25345
|
if (!def) {
|
|
25034
25346
|
opts.warn(`no such agent "${opts.agent}" under ${opts.cwd}/${root}`);
|
|
@@ -25059,13 +25371,13 @@ ${extra}` : base;
|
|
|
25059
25371
|
} catch {
|
|
25060
25372
|
}
|
|
25061
25373
|
const realConsole = { log: console.log, info: console.info, warn: console.warn, debug: console.debug };
|
|
25062
|
-
const
|
|
25374
|
+
const toStderr2 = (...parts) => {
|
|
25063
25375
|
opts.warn(parts.map((x) => typeof x === "string" ? x : JSON.stringify(x)).join(" "));
|
|
25064
25376
|
};
|
|
25065
|
-
console.log =
|
|
25066
|
-
console.info =
|
|
25067
|
-
console.warn =
|
|
25068
|
-
console.debug =
|
|
25377
|
+
console.log = toStderr2;
|
|
25378
|
+
console.info = toStderr2;
|
|
25379
|
+
console.warn = toStderr2;
|
|
25380
|
+
console.debug = toStderr2;
|
|
25069
25381
|
const ac = new AbortController();
|
|
25070
25382
|
const effectivePolicy = opts.policy ?? def.policy ?? STANDARD_POLICY;
|
|
25071
25383
|
const operatorUnattended = opts.policy?.unattended;
|
|
@@ -25239,10 +25551,10 @@ async function runExtension(ext, rest, look = {}) {
|
|
|
25239
25551
|
process.on("SIGINT", onInt);
|
|
25240
25552
|
process.on("SIGTERM", onTerm);
|
|
25241
25553
|
try {
|
|
25242
|
-
return await new Promise((
|
|
25554
|
+
return await new Promise((resolve5, reject) => {
|
|
25243
25555
|
child.on("error", reject);
|
|
25244
25556
|
child.on("exit", (code3, signal) => {
|
|
25245
|
-
|
|
25557
|
+
resolve5(code3 ?? (signal === "SIGINT" ? 130 : signal ? 143 : 1));
|
|
25246
25558
|
});
|
|
25247
25559
|
});
|
|
25248
25560
|
} finally {
|
|
@@ -25374,8 +25686,8 @@ init_tools_node();
|
|
|
25374
25686
|
function hintOf(token) {
|
|
25375
25687
|
return maskToken(token);
|
|
25376
25688
|
}
|
|
25377
|
-
async function authState(
|
|
25378
|
-
const r = await
|
|
25689
|
+
async function authState(resolve5 = (env2) => resolveNodeCredential({ env: env2 }), env = process.env) {
|
|
25690
|
+
const r = await resolve5(env);
|
|
25379
25691
|
if (!r.token) return { source: "none", path: authTokenPath() };
|
|
25380
25692
|
const source = r.source === "env" ? "env" : "file";
|
|
25381
25693
|
return { source, path: r.path ?? authTokenPath(), hint: hintOf(r.token) };
|
|
@@ -25405,13 +25717,21 @@ async function runAuth(out = (s) => process.stdout.write(s)) {
|
|
|
25405
25717
|
}
|
|
25406
25718
|
async function runLogout(out = (s) => process.stdout.write(s)) {
|
|
25407
25719
|
const before = await authState();
|
|
25408
|
-
|
|
25720
|
+
try {
|
|
25721
|
+
await clearAllCredentials();
|
|
25722
|
+
} catch (err) {
|
|
25723
|
+
out(`Sign-out did not complete: ${err instanceof Error ? err.message : String(err)}
|
|
25724
|
+
`);
|
|
25725
|
+
out(" This machine is still signed in. Close whatever holds the file and run `corenel logout` again.\n");
|
|
25726
|
+
return 1;
|
|
25727
|
+
}
|
|
25409
25728
|
if (before.source === "file") out(`Signed out. Removed ${before.path}
|
|
25410
25729
|
`);
|
|
25411
25730
|
else out("Nothing to forget \u2014 no stored token.\n");
|
|
25412
25731
|
if (process.env.CORENEL_TOKEN) {
|
|
25413
25732
|
out(" NOTE: CORENEL_TOKEN is still set in this environment, so you are still authenticated.\n");
|
|
25414
25733
|
}
|
|
25734
|
+
return 0;
|
|
25415
25735
|
}
|
|
25416
25736
|
|
|
25417
25737
|
// src/doctor.ts
|
|
@@ -25486,7 +25806,11 @@ var KNOWN_KEYS = [
|
|
|
25486
25806
|
"audit",
|
|
25487
25807
|
"sendTelemetry",
|
|
25488
25808
|
"approvalsThreshold",
|
|
25489
|
-
"approvalsCaptureRawArgs"
|
|
25809
|
+
"approvalsCaptureRawArgs",
|
|
25810
|
+
"speech",
|
|
25811
|
+
"speechModel",
|
|
25812
|
+
"peerCallTimeoutMs",
|
|
25813
|
+
"peers"
|
|
25490
25814
|
];
|
|
25491
25815
|
function daemonConfigPath(stateDir) {
|
|
25492
25816
|
return join8(stateDir, "daemon.json");
|
|
@@ -25619,12 +25943,19 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
25619
25943
|
"allow-shell",
|
|
25620
25944
|
"allow-mutations",
|
|
25621
25945
|
// see hasAllowMutationsFlag() below
|
|
25946
|
+
"memory-read-only",
|
|
25947
|
+
// corenel run-agent: the daemon's read-only memory mode
|
|
25622
25948
|
"global"
|
|
25623
25949
|
// corenel approvals revoke/clear-log --global
|
|
25624
25950
|
]);
|
|
25625
25951
|
function isBooleanFlag(name) {
|
|
25626
25952
|
return BOOLEAN_FLAGS.has(name);
|
|
25627
25953
|
}
|
|
25954
|
+
function consumesNext(parsed, next) {
|
|
25955
|
+
if (parsed.value !== void 0 || isBooleanFlag(parsed.name)) return false;
|
|
25956
|
+
if (parsed.name === "resume") return next !== void 0 && !next.startsWith("--");
|
|
25957
|
+
return true;
|
|
25958
|
+
}
|
|
25628
25959
|
function classifyToken(tok) {
|
|
25629
25960
|
if (tok === "=") throw new MalformedFlagError(tok);
|
|
25630
25961
|
if (!tok.startsWith("--")) return null;
|
|
@@ -25638,7 +25969,11 @@ function classifyToken(tok) {
|
|
|
25638
25969
|
function flag(argv, name, fallback) {
|
|
25639
25970
|
for (let i = 0; i < argv.length; i++) {
|
|
25640
25971
|
const parsed = classifyToken(argv[i]);
|
|
25641
|
-
if (!parsed
|
|
25972
|
+
if (!parsed) continue;
|
|
25973
|
+
if (parsed.name !== name) {
|
|
25974
|
+
if (consumesNext(parsed, argv[i + 1])) i++;
|
|
25975
|
+
continue;
|
|
25976
|
+
}
|
|
25642
25977
|
if (parsed.value !== void 0) return parsed.value;
|
|
25643
25978
|
if (!argv[i + 1]) throw new MalformedFlagError(argv[i]);
|
|
25644
25979
|
return argv[i + 1];
|
|
@@ -25650,7 +25985,7 @@ function forEachPositional(argv, visit) {
|
|
|
25650
25985
|
const tok = argv[i];
|
|
25651
25986
|
const parsed = classifyToken(tok);
|
|
25652
25987
|
if (parsed) {
|
|
25653
|
-
if (parsed
|
|
25988
|
+
if (consumesNext(parsed, argv[i + 1])) i++;
|
|
25654
25989
|
continue;
|
|
25655
25990
|
}
|
|
25656
25991
|
visit(tok);
|
|
@@ -25674,11 +26009,10 @@ function hasBooleanFlag(argv, name) {
|
|
|
25674
26009
|
for (let i = 0; i < argv.length; i++) {
|
|
25675
26010
|
const parsed = classifyToken(argv[i]);
|
|
25676
26011
|
if (!parsed) continue;
|
|
25677
|
-
if (parsed.
|
|
25678
|
-
i++;
|
|
26012
|
+
if (parsed.name !== name) {
|
|
26013
|
+
if (consumesNext(parsed, argv[i + 1])) i++;
|
|
25679
26014
|
continue;
|
|
25680
26015
|
}
|
|
25681
|
-
if (parsed.name !== name) continue;
|
|
25682
26016
|
if (isOnValue(parsed.value)) return true;
|
|
25683
26017
|
}
|
|
25684
26018
|
return false;
|
|
@@ -25697,10 +26031,13 @@ function resumeFlag(argv) {
|
|
|
25697
26031
|
return void 0;
|
|
25698
26032
|
}
|
|
25699
26033
|
function hasValueFlag(argv, name) {
|
|
25700
|
-
|
|
25701
|
-
const parsed = classifyToken(
|
|
25702
|
-
|
|
25703
|
-
|
|
26034
|
+
for (let i = 0; i < argv.length; i++) {
|
|
26035
|
+
const parsed = classifyToken(argv[i]);
|
|
26036
|
+
if (!parsed) continue;
|
|
26037
|
+
if (parsed.name === name) return true;
|
|
26038
|
+
if (consumesNext(parsed, argv[i + 1])) i++;
|
|
26039
|
+
}
|
|
26040
|
+
return false;
|
|
25704
26041
|
}
|
|
25705
26042
|
|
|
25706
26043
|
// src/doctor.ts
|
|
@@ -25980,12 +26317,12 @@ async function runRepl(deps) {
|
|
|
25980
26317
|
rl.on("close", () => {
|
|
25981
26318
|
closed = true;
|
|
25982
26319
|
});
|
|
25983
|
-
const ask = (q) => closed ? Promise.resolve(null) : new Promise((
|
|
25984
|
-
const onClose = () =>
|
|
26320
|
+
const ask = (q) => closed ? Promise.resolve(null) : new Promise((resolve5) => {
|
|
26321
|
+
const onClose = () => resolve5(null);
|
|
25985
26322
|
rl.once("close", onClose);
|
|
25986
26323
|
rl.question(q, (answer) => {
|
|
25987
26324
|
rl.off("close", onClose);
|
|
25988
|
-
|
|
26325
|
+
resolve5(answer);
|
|
25989
26326
|
});
|
|
25990
26327
|
});
|
|
25991
26328
|
const askSuggestions = async () => {
|
|
@@ -26089,7 +26426,7 @@ async function runRepl(deps) {
|
|
|
26089
26426
|
|
|
26090
26427
|
// src/approvals.ts
|
|
26091
26428
|
init_tools_node();
|
|
26092
|
-
import { join as join9, resolve as
|
|
26429
|
+
import { join as join9, resolve as resolve4 } from "node:path";
|
|
26093
26430
|
import { approvalsRoot } from "@corenel/protocol";
|
|
26094
26431
|
import { createApprovals } from "@corenel/harness/approvals";
|
|
26095
26432
|
function guardStanding(inner, reason) {
|
|
@@ -26106,7 +26443,7 @@ function guardStanding(inner, reason) {
|
|
|
26106
26443
|
};
|
|
26107
26444
|
}
|
|
26108
26445
|
async function openCliApprovals(opts) {
|
|
26109
|
-
const cwd =
|
|
26446
|
+
const cwd = resolve4(opts.cwd);
|
|
26110
26447
|
const home = approvalsHomeDir(opts.homeBase);
|
|
26111
26448
|
await ensurePrivateDir(home);
|
|
26112
26449
|
const hit = await insideAnyRoot([cwd], home);
|
|
@@ -26334,7 +26671,7 @@ function killTree(child) {
|
|
|
26334
26671
|
}
|
|
26335
26672
|
}
|
|
26336
26673
|
function execute(command, opts) {
|
|
26337
|
-
return new Promise((
|
|
26674
|
+
return new Promise((resolve5) => {
|
|
26338
26675
|
const child = spawn3(command, {
|
|
26339
26676
|
cwd: opts.cwd,
|
|
26340
26677
|
shell: true,
|
|
@@ -26366,7 +26703,7 @@ function execute(command, opts) {
|
|
|
26366
26703
|
const finish = (code3) => {
|
|
26367
26704
|
clearTimeout(timer);
|
|
26368
26705
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
26369
|
-
|
|
26706
|
+
resolve5({ code: code3, stdout, stderr, timedOut });
|
|
26370
26707
|
};
|
|
26371
26708
|
child.on("error", (e) => {
|
|
26372
26709
|
stderr += String(e);
|
|
@@ -26423,7 +26760,7 @@ import { errMessage } from "@corenel/protocol";
|
|
|
26423
26760
|
import { wsChannel } from "@corenel/harness/sidecar/wsChannel";
|
|
26424
26761
|
function nodeSidecarChannel(opts) {
|
|
26425
26762
|
const { url, signal } = opts;
|
|
26426
|
-
return new Promise((
|
|
26763
|
+
return new Promise((resolve5, reject) => {
|
|
26427
26764
|
if (signal?.aborted) {
|
|
26428
26765
|
reject(new Error("sidecar connection aborted"));
|
|
26429
26766
|
return;
|
|
@@ -26441,7 +26778,7 @@ function nodeSidecarChannel(opts) {
|
|
|
26441
26778
|
if (settled) return;
|
|
26442
26779
|
settled = true;
|
|
26443
26780
|
cleanup();
|
|
26444
|
-
|
|
26781
|
+
resolve5(wsChannel(ws));
|
|
26445
26782
|
};
|
|
26446
26783
|
const settleReject = (err) => {
|
|
26447
26784
|
if (settled) return;
|
|
@@ -26647,15 +26984,20 @@ async function sidecarCredential(url, env, store) {
|
|
|
26647
26984
|
};
|
|
26648
26985
|
}
|
|
26649
26986
|
|
|
26650
|
-
//
|
|
26987
|
+
// ../tools-node/pairingAssertion.ts
|
|
26651
26988
|
function cliIdentity(o) {
|
|
26989
|
+
const who = o.who ?? "this CLI";
|
|
26990
|
+
const signIn = o.signIn ?? "corenel login";
|
|
26652
26991
|
const fetchImpl = o.fetchImpl ?? fetch;
|
|
26653
26992
|
const timeoutMs = o.timeoutMs ?? 8e3;
|
|
26654
26993
|
let last = null;
|
|
26994
|
+
let lastDetail = null;
|
|
26655
26995
|
return {
|
|
26656
26996
|
problem: () => last,
|
|
26997
|
+
detail: () => lastDetail ?? last,
|
|
26657
26998
|
answer: async ({ nonce, sidecarId, jkt, orgId }) => {
|
|
26658
26999
|
last = null;
|
|
27000
|
+
lastDetail = null;
|
|
26659
27001
|
let token;
|
|
26660
27002
|
try {
|
|
26661
27003
|
token = await o.token();
|
|
@@ -26663,7 +27005,7 @@ function cliIdentity(o) {
|
|
|
26663
27005
|
token = null;
|
|
26664
27006
|
}
|
|
26665
27007
|
if (!token) {
|
|
26666
|
-
last =
|
|
27008
|
+
last = `${who} is not signed in, and the sidecar only admits a verified account. Run \`${signIn}\` (or set CORENEL_TOKEN), then try again.`;
|
|
26667
27009
|
return null;
|
|
26668
27010
|
}
|
|
26669
27011
|
try {
|
|
@@ -26682,7 +27024,7 @@ function cliIdentity(o) {
|
|
|
26682
27024
|
code3 = void 0;
|
|
26683
27025
|
}
|
|
26684
27026
|
if (code3 === "CLI_SCOPE") {
|
|
26685
|
-
last =
|
|
27027
|
+
last = `${who}'s sign-in is not allowed to connect to sidecars. Run \`${signIn}\` again and allow sidecar access when you approve it.`;
|
|
26686
27028
|
return null;
|
|
26687
27029
|
}
|
|
26688
27030
|
if (orgId) {
|
|
@@ -26691,7 +27033,7 @@ function cliIdentity(o) {
|
|
|
26691
27033
|
}
|
|
26692
27034
|
}
|
|
26693
27035
|
if (res.status === 401) {
|
|
26694
|
-
last =
|
|
27036
|
+
last = `the API refused ${who}'s sign-in. Run \`${signIn}\` again, then try again.`;
|
|
26695
27037
|
return null;
|
|
26696
27038
|
}
|
|
26697
27039
|
if (!res.ok) {
|
|
@@ -26706,7 +27048,9 @@ function cliIdentity(o) {
|
|
|
26706
27048
|
}
|
|
26707
27049
|
return assertion;
|
|
26708
27050
|
} catch (e) {
|
|
26709
|
-
|
|
27051
|
+
const reason = `could not reach the API for a pairing assertion (${e instanceof Error ? e.message : String(e)}).`;
|
|
27052
|
+
last = o.unreachable ?? reason;
|
|
27053
|
+
lastDetail = reason;
|
|
26710
27054
|
return null;
|
|
26711
27055
|
}
|
|
26712
27056
|
}
|
|
@@ -27358,7 +27702,7 @@ function isMcpTool(value) {
|
|
|
27358
27702
|
|
|
27359
27703
|
// ../mcp/src/config.ts
|
|
27360
27704
|
init_tools_node();
|
|
27361
|
-
import { readFile as readFile3,
|
|
27705
|
+
import { readFile as readFile3, mkdir as mkdir2 } from "node:fs/promises";
|
|
27362
27706
|
function isUsableServer(v) {
|
|
27363
27707
|
if (typeof v !== "object" || v === null) return false;
|
|
27364
27708
|
const r = v;
|
|
@@ -27394,6 +27738,20 @@ async function readAuthJson(stateDir) {
|
|
|
27394
27738
|
return {};
|
|
27395
27739
|
}
|
|
27396
27740
|
}
|
|
27741
|
+
function locked(stateDir, fn) {
|
|
27742
|
+
return withAuthDocLock(credentialFilePath(stateDir), fn);
|
|
27743
|
+
}
|
|
27744
|
+
async function docForWrite(stateDir) {
|
|
27745
|
+
const path = credentialFilePath(stateDir);
|
|
27746
|
+
const read = await readAuthDocForWrite(path, "save");
|
|
27747
|
+
if (read.kind === "doc") return read.doc;
|
|
27748
|
+
if (read.kind === "corrupt") await backupCorruptAuthDoc(path, read.bytes);
|
|
27749
|
+
return {};
|
|
27750
|
+
}
|
|
27751
|
+
async function docForClear(stateDir) {
|
|
27752
|
+
const read = await readAuthDocForWrite(credentialFilePath(stateDir), "save");
|
|
27753
|
+
return read.kind === "doc" ? read.doc : null;
|
|
27754
|
+
}
|
|
27397
27755
|
function isMcpAuthSection(v) {
|
|
27398
27756
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
27399
27757
|
}
|
|
@@ -27430,68 +27788,165 @@ async function readMcpClient(stateDir, server) {
|
|
|
27430
27788
|
return isUsableClient(rec) ? rec : null;
|
|
27431
27789
|
}
|
|
27432
27790
|
async function writeMcpClient(stateDir, server, client) {
|
|
27433
|
-
|
|
27434
|
-
|
|
27435
|
-
|
|
27436
|
-
|
|
27437
|
-
|
|
27438
|
-
|
|
27439
|
-
|
|
27440
|
-
|
|
27441
|
-
`, { mode: 384 });
|
|
27442
|
-
try {
|
|
27443
|
-
await chmod(path, 384);
|
|
27444
|
-
} catch {
|
|
27445
|
-
}
|
|
27791
|
+
assertServerName(server);
|
|
27792
|
+
await locked(stateDir, async () => {
|
|
27793
|
+
const doc = await docForWrite(stateDir);
|
|
27794
|
+
const prev = doc.mcpClients;
|
|
27795
|
+
const section = typeof prev === "object" && prev !== null && !Array.isArray(prev) ? { ...prev } : {};
|
|
27796
|
+
section[server] = client;
|
|
27797
|
+
await writeAuthJson(stateDir, { ...doc, mcpClients: section });
|
|
27798
|
+
});
|
|
27446
27799
|
}
|
|
27447
27800
|
async function clearMcpClient(stateDir, server) {
|
|
27448
|
-
|
|
27449
|
-
|
|
27450
|
-
|
|
27451
|
-
|
|
27452
|
-
|
|
27453
|
-
|
|
27454
|
-
|
|
27455
|
-
|
|
27456
|
-
|
|
27457
|
-
|
|
27458
|
-
try {
|
|
27459
|
-
await chmod(path, 384);
|
|
27460
|
-
} catch {
|
|
27461
|
-
}
|
|
27801
|
+
await locked(stateDir, async () => {
|
|
27802
|
+
const doc = await docForClear(stateDir);
|
|
27803
|
+
if (!doc) return;
|
|
27804
|
+
const prev = doc.mcpClients;
|
|
27805
|
+
if (typeof prev !== "object" || prev === null || Array.isArray(prev)) return;
|
|
27806
|
+
const section = { ...prev };
|
|
27807
|
+
if (!(server in section)) return;
|
|
27808
|
+
delete section[server];
|
|
27809
|
+
await writeAuthJson(stateDir, { ...doc, mcpClients: section });
|
|
27810
|
+
});
|
|
27462
27811
|
}
|
|
27463
27812
|
async function clearMcpCredential(stateDir, server) {
|
|
27464
|
-
|
|
27465
|
-
|
|
27466
|
-
|
|
27467
|
-
|
|
27468
|
-
|
|
27469
|
-
|
|
27470
|
-
|
|
27471
|
-
|
|
27472
|
-
|
|
27473
|
-
try {
|
|
27474
|
-
await chmod(path, 384);
|
|
27475
|
-
} catch {
|
|
27476
|
-
}
|
|
27813
|
+
await locked(stateDir, async () => {
|
|
27814
|
+
const doc = await docForClear(stateDir);
|
|
27815
|
+
if (!doc) return;
|
|
27816
|
+
if (!isMcpAuthSection(doc.mcp)) return;
|
|
27817
|
+
const section = { ...doc.mcp };
|
|
27818
|
+
if (!(server in section)) return;
|
|
27819
|
+
delete section[server];
|
|
27820
|
+
await writeAuthJson(stateDir, { ...doc, mcp: section });
|
|
27821
|
+
});
|
|
27477
27822
|
}
|
|
27478
27823
|
async function forgetMcpServerSecrets(stateDir, server) {
|
|
27479
27824
|
await clearMcpCredential(stateDir, server);
|
|
27480
27825
|
await clearMcpClient(stateDir, server);
|
|
27826
|
+
await clearServerFromAllAgents(stateDir, server);
|
|
27481
27827
|
}
|
|
27482
27828
|
async function writeMcpCredential(stateDir, server, cred) {
|
|
27483
|
-
|
|
27484
|
-
|
|
27485
|
-
|
|
27486
|
-
|
|
27487
|
-
|
|
27829
|
+
assertServerName(server);
|
|
27830
|
+
await locked(stateDir, async () => {
|
|
27831
|
+
const doc = await docForWrite(stateDir);
|
|
27832
|
+
const section = isMcpAuthSection(doc.mcp) ? { ...doc.mcp } : {};
|
|
27833
|
+
section[server] = cred;
|
|
27834
|
+
await writeAuthJson(stateDir, { ...doc, mcp: section });
|
|
27835
|
+
});
|
|
27836
|
+
}
|
|
27837
|
+
var SERVER_SLOT = { kind: "server" };
|
|
27838
|
+
var MAX_ACCESS_KEY_LEN = 512;
|
|
27839
|
+
var FORBIDDEN_KEY = "__proto__";
|
|
27840
|
+
function assertAccessKey(accessKey) {
|
|
27841
|
+
if (typeof accessKey !== "string" || !accessKey || accessKey.length > MAX_ACCESS_KEY_LEN || accessKey === FORBIDDEN_KEY) {
|
|
27842
|
+
throw new Error("invalid agent access key");
|
|
27843
|
+
}
|
|
27844
|
+
}
|
|
27845
|
+
function assertServerName(server) {
|
|
27846
|
+
if (typeof server !== "string" || !server || server === FORBIDDEN_KEY) {
|
|
27847
|
+
throw new Error("invalid MCP server name");
|
|
27848
|
+
}
|
|
27849
|
+
}
|
|
27850
|
+
function agentsOf(doc) {
|
|
27851
|
+
const s = doc.agents;
|
|
27852
|
+
return typeof s === "object" && s !== null && !Array.isArray(s) ? { ...s } : {};
|
|
27853
|
+
}
|
|
27854
|
+
async function writeAuthJson(stateDir, next) {
|
|
27488
27855
|
await mkdir2(credentialDir(stateDir), { recursive: true });
|
|
27489
|
-
await writeFile2(path, `${JSON.stringify(next, null, 2)}
|
|
27490
|
-
`, { mode: 384 });
|
|
27491
27856
|
try {
|
|
27492
|
-
await
|
|
27493
|
-
|
|
27857
|
+
await writeFilePrivateAtomic(credentialFilePath(stateDir), `${JSON.stringify(next, null, 2)}
|
|
27858
|
+
`);
|
|
27859
|
+
} catch (err) {
|
|
27860
|
+
const code3 = typeof err === "object" && err !== null ? err.code : void 0;
|
|
27861
|
+
if (code3 === "EPERM" || code3 === "EBUSY") {
|
|
27862
|
+
throw Object.assign(new Error(`auth.json is in use; retry (${code3})`), { code: code3 });
|
|
27863
|
+
}
|
|
27864
|
+
throw err;
|
|
27865
|
+
}
|
|
27866
|
+
}
|
|
27867
|
+
async function readMcpCredentialFor(stateDir, server, slot) {
|
|
27868
|
+
if (slot.kind === "server") return readMcpCredential(stateDir, server);
|
|
27869
|
+
assertAccessKey(slot.accessKey);
|
|
27870
|
+
assertServerName(server);
|
|
27871
|
+
const agents = agentsOf(await readAuthJson(stateDir));
|
|
27872
|
+
if (!Object.prototype.hasOwnProperty.call(agents, slot.accessKey)) return null;
|
|
27873
|
+
const perAgent = agents[slot.accessKey];
|
|
27874
|
+
if (!isMcpAuthSection(perAgent)) return null;
|
|
27875
|
+
if (!Object.prototype.hasOwnProperty.call(perAgent, server)) return null;
|
|
27876
|
+
const cred = perAgent[server];
|
|
27877
|
+
return isUsableCredential(cred) ? cred : null;
|
|
27878
|
+
}
|
|
27879
|
+
async function writeMcpCredentialFor(stateDir, server, slot, cred) {
|
|
27880
|
+
if (slot.kind === "server") return writeMcpCredential(stateDir, server, cred);
|
|
27881
|
+
assertAccessKey(slot.accessKey);
|
|
27882
|
+
assertServerName(server);
|
|
27883
|
+
const key = slot.accessKey;
|
|
27884
|
+
await locked(stateDir, async () => {
|
|
27885
|
+
const doc = await docForWrite(stateDir);
|
|
27886
|
+
const agents = agentsOf(doc);
|
|
27887
|
+
const prev = agents[key];
|
|
27888
|
+
const perAgent = isMcpAuthSection(prev) ? { ...prev } : {};
|
|
27889
|
+
perAgent[server] = cred;
|
|
27890
|
+
agents[key] = perAgent;
|
|
27891
|
+
await writeAuthJson(stateDir, { ...doc, agents });
|
|
27892
|
+
});
|
|
27893
|
+
}
|
|
27894
|
+
function sameCredential(stored, expected) {
|
|
27895
|
+
if (stored.kind === "oauth" && expected.kind === "oauth") {
|
|
27896
|
+
return expected.refreshToken !== void 0 ? stored.refreshToken === expected.refreshToken : stored.refreshToken === void 0 && stored.accessToken === expected.accessToken;
|
|
27897
|
+
}
|
|
27898
|
+
if (stored.kind === "headers" && expected.kind === "headers") {
|
|
27899
|
+
return JSON.stringify(stored.headers) === JSON.stringify(expected.headers);
|
|
27900
|
+
}
|
|
27901
|
+
return false;
|
|
27902
|
+
}
|
|
27903
|
+
async function replaceMcpCredentialIfUnchanged(stateDir, server, slot, expected, next) {
|
|
27904
|
+
assertServerName(server);
|
|
27905
|
+
if (slot.kind === "agent") assertAccessKey(slot.accessKey);
|
|
27906
|
+
return locked(stateDir, () => replaceIfUnchangedLocked(stateDir, server, slot, expected, next));
|
|
27907
|
+
}
|
|
27908
|
+
async function replaceIfUnchangedLocked(stateDir, server, slot, expected, next) {
|
|
27909
|
+
const doc = await docForClear(stateDir);
|
|
27910
|
+
if (!doc) return false;
|
|
27911
|
+
if (slot.kind === "server") {
|
|
27912
|
+
if (!isMcpAuthSection(doc.mcp)) return false;
|
|
27913
|
+
const section = { ...doc.mcp };
|
|
27914
|
+
if (!Object.prototype.hasOwnProperty.call(section, server)) return false;
|
|
27915
|
+
const stored2 = section[server];
|
|
27916
|
+
if (!isUsableCredential(stored2) || !sameCredential(stored2, expected)) return false;
|
|
27917
|
+
section[server] = next;
|
|
27918
|
+
await writeAuthJson(stateDir, { ...doc, mcp: section });
|
|
27919
|
+
return true;
|
|
27920
|
+
}
|
|
27921
|
+
const agents = agentsOf(doc);
|
|
27922
|
+
if (!Object.prototype.hasOwnProperty.call(agents, slot.accessKey)) return false;
|
|
27923
|
+
const prev = agents[slot.accessKey];
|
|
27924
|
+
if (!isMcpAuthSection(prev) || !Object.prototype.hasOwnProperty.call(prev, server)) return false;
|
|
27925
|
+
const perAgent = { ...prev };
|
|
27926
|
+
const stored = perAgent[server];
|
|
27927
|
+
if (!isUsableCredential(stored) || !sameCredential(stored, expected)) return false;
|
|
27928
|
+
perAgent[server] = next;
|
|
27929
|
+
agents[slot.accessKey] = perAgent;
|
|
27930
|
+
await writeAuthJson(stateDir, { ...doc, agents });
|
|
27931
|
+
return true;
|
|
27932
|
+
}
|
|
27933
|
+
async function clearServerFromAllAgents(stateDir, server) {
|
|
27934
|
+
await locked(stateDir, () => clearServerFromAllAgentsLocked(stateDir, server));
|
|
27935
|
+
}
|
|
27936
|
+
async function clearServerFromAllAgentsLocked(stateDir, server) {
|
|
27937
|
+
const doc = await docForClear(stateDir);
|
|
27938
|
+
if (!doc) return;
|
|
27939
|
+
const agents = agentsOf(doc);
|
|
27940
|
+
let changed = false;
|
|
27941
|
+
for (const [key, prev] of Object.entries(agents)) {
|
|
27942
|
+
if (!isMcpAuthSection(prev) || !Object.prototype.hasOwnProperty.call(prev, server)) continue;
|
|
27943
|
+
const perAgent = { ...prev };
|
|
27944
|
+
delete perAgent[server];
|
|
27945
|
+
if (Object.keys(perAgent).length) agents[key] = perAgent;
|
|
27946
|
+
else delete agents[key];
|
|
27947
|
+
changed = true;
|
|
27494
27948
|
}
|
|
27949
|
+
if (changed) await writeAuthJson(stateDir, { ...doc, agents });
|
|
27495
27950
|
}
|
|
27496
27951
|
|
|
27497
27952
|
// ../mcp/src/oauth/discovery.ts
|
|
@@ -27671,7 +28126,7 @@ function isExpired(cred, nowMs) {
|
|
|
27671
28126
|
}
|
|
27672
28127
|
|
|
27673
28128
|
// ../mcp/src/auth.ts
|
|
27674
|
-
import { DEFAULT_API_BASE as DEFAULT_API_BASE4 } from "@corenel/protocol";
|
|
28129
|
+
import { DEFAULT_API_BASE as DEFAULT_API_BASE4, AGENT_CREDENTIAL_MISSING } from "@corenel/protocol";
|
|
27675
28130
|
function safeOrigin(url) {
|
|
27676
28131
|
try {
|
|
27677
28132
|
return new URL(url).origin;
|
|
@@ -27702,22 +28157,30 @@ function isLoopbackHost(hostname2) {
|
|
|
27702
28157
|
const h = hostname2.replace(/^\[|\]$/g, "").toLowerCase();
|
|
27703
28158
|
return h === "localhost" || h === "127.0.0.1" || h === "::1";
|
|
27704
28159
|
}
|
|
27705
|
-
function authProviderFor(stateDir, server, fetchImpl = fetch, accountToken = async () => null, relayBases = async () => [], devApiBases = []) {
|
|
28160
|
+
function authProviderFor(stateDir, server, fetchImpl = fetch, accountToken = async () => null, relayBases = async () => [], devApiBases = [], slot = SERVER_SLOT) {
|
|
27706
28161
|
async function doRefresh(cred) {
|
|
27707
28162
|
if (!cred.refreshToken) {
|
|
28163
|
+
if (slot.kind === "agent") {
|
|
28164
|
+
throw new Error(`${AGENT_CREDENTIAL_MISSING}: the agent credential for MCP server "${server.name}" has expired and has no refresh token; the agent's owner must sign the agent in again.`);
|
|
28165
|
+
}
|
|
27708
28166
|
throw new Error(`"${server.name}" has no refresh token on file -- run: corenel mcp login ${server.name}`);
|
|
27709
28167
|
}
|
|
27710
28168
|
const meta = await discoverAuthMetadata(cred.authServer, fetchImpl);
|
|
27711
28169
|
const refreshed = await refresh(meta, cred, fetchImpl);
|
|
27712
|
-
await
|
|
28170
|
+
if (!await replaceMcpCredentialIfUnchanged(stateDir, server.name, slot, cred, refreshed)) {
|
|
28171
|
+
throw new Error(`The credential for MCP server "${server.name}" changed while it was being refreshed; retry the call.`);
|
|
28172
|
+
}
|
|
27713
28173
|
return refreshed;
|
|
27714
28174
|
}
|
|
27715
28175
|
return {
|
|
27716
28176
|
async headers() {
|
|
27717
|
-
const cred = await
|
|
28177
|
+
const cred = await readMcpCredentialFor(stateDir, server.name, slot);
|
|
27718
28178
|
if (server.hosted && !isCorenelRelayUrl(server.url, [DEFAULT_API_BASE4, ...await relayBases()], devApiBases)) {
|
|
27719
28179
|
throw new Error(`"${server.name}" is marked hosted but ${safeOrigin(server.url)} is not a Corenel relay; refusing to send the account token there.`);
|
|
27720
28180
|
}
|
|
28181
|
+
if (!cred && slot.kind === "agent") {
|
|
28182
|
+
throw new Error(`${AGENT_CREDENTIAL_MISSING}: no agent credential is set for MCP server "${server.name}"`);
|
|
28183
|
+
}
|
|
27721
28184
|
if (!cred && !server.hosted) {
|
|
27722
28185
|
throw new Error(`No credential stored for MCP server "${server.name}" -- run: corenel mcp login ${server.name} (or set-header, for a static token).`);
|
|
27723
28186
|
}
|
|
@@ -27728,7 +28191,7 @@ function authProviderFor(stateDir, server, fetchImpl = fetch, accountToken = asy
|
|
|
27728
28191
|
return account ? [...moved, { name: "Authorization", value: `Bearer ${account}` }] : moved;
|
|
27729
28192
|
},
|
|
27730
28193
|
async onUnauthorized() {
|
|
27731
|
-
const cred = await
|
|
28194
|
+
const cred = await readMcpCredentialFor(stateDir, server.name, slot);
|
|
27732
28195
|
if (!cred || cred.kind !== "oauth") return false;
|
|
27733
28196
|
try {
|
|
27734
28197
|
await doRefresh(cred);
|
|
@@ -27768,15 +28231,15 @@ async function accountTokenFor(stateDir) {
|
|
|
27768
28231
|
}
|
|
27769
28232
|
function mcpClientFactory(opts = {}) {
|
|
27770
28233
|
const devApiBases = [...opts.devApiBases ?? []];
|
|
27771
|
-
return (stateDir, server) => buildClient(stateDir, server, devApiBases);
|
|
28234
|
+
return (stateDir, server, slot = SERVER_SLOT) => buildClient(stateDir, server, devApiBases, slot);
|
|
27772
28235
|
}
|
|
27773
|
-
function defaultClientFactory(stateDir, server) {
|
|
27774
|
-
return buildClient(stateDir, server, []);
|
|
28236
|
+
function defaultClientFactory(stateDir, server, slot = SERVER_SLOT) {
|
|
28237
|
+
return buildClient(stateDir, server, [], slot);
|
|
27775
28238
|
}
|
|
27776
|
-
function buildClient(stateDir, server, devApiBases) {
|
|
28239
|
+
function buildClient(stateDir, server, devApiBases, slot) {
|
|
27777
28240
|
return new McpHttpClient(
|
|
27778
28241
|
server,
|
|
27779
|
-
authProviderFor(stateDir, server, timedFetch(AUTH_REQUEST_TIMEOUT_MS), () => accountTokenFor(stateDir), () => relayBasesFor(stateDir), devApiBases)
|
|
28242
|
+
authProviderFor(stateDir, server, timedFetch(AUTH_REQUEST_TIMEOUT_MS), () => accountTokenFor(stateDir), () => relayBasesFor(stateDir), devApiBases, slot)
|
|
27780
28243
|
);
|
|
27781
28244
|
}
|
|
27782
28245
|
async function relayBasesFor(stateDir) {
|
|
@@ -27788,6 +28251,21 @@ async function relayBasesFor(stateDir) {
|
|
|
27788
28251
|
}
|
|
27789
28252
|
}
|
|
27790
28253
|
var SERVER_CONNECT_TIMEOUT_MS = 3e4;
|
|
28254
|
+
function mcpClientKey(server, slot) {
|
|
28255
|
+
return JSON.stringify(slot.kind === "server" ? [server] : [server, slot.accessKey]);
|
|
28256
|
+
}
|
|
28257
|
+
function accessKeyOf(key) {
|
|
28258
|
+
const parts = JSON.parse(key);
|
|
28259
|
+
return Array.isArray(parts) && parts.length === 2 && typeof parts[1] === "string" ? parts[1] : void 0;
|
|
28260
|
+
}
|
|
28261
|
+
function withDeadline(p, ms, message) {
|
|
28262
|
+
let timer;
|
|
28263
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
28264
|
+
timer = setTimeout(() => reject(new Error(message)), ms);
|
|
28265
|
+
});
|
|
28266
|
+
p.catch(() => void 0);
|
|
28267
|
+
return Promise.race([p, deadline]).finally(() => clearTimeout(timer));
|
|
28268
|
+
}
|
|
27791
28269
|
var McpRegistry = class {
|
|
27792
28270
|
constructor(stateDir, clientFactory = defaultClientFactory, configEnv) {
|
|
27793
28271
|
this.stateDir = stateDir;
|
|
@@ -27817,6 +28295,11 @@ var McpRegistry = class {
|
|
|
27817
28295
|
// that land on the same cold server in the same turn share one connection
|
|
27818
28296
|
// instead of racing three.
|
|
27819
28297
|
connecting = /* @__PURE__ */ new Map();
|
|
28298
|
+
// Agent-slot clients (spec 2026-10-01 Part D), keyed by mcpClientKey. Kept
|
|
28299
|
+
// apart from `connected`: those are the daemon's own credential, and the
|
|
28300
|
+
// two must never be confused for each other.
|
|
28301
|
+
slotted = /* @__PURE__ */ new Map();
|
|
28302
|
+
slotConnecting = /* @__PURE__ */ new Map();
|
|
27820
28303
|
/**
|
|
27821
28304
|
* Connect to every configured server and cache its tools. Never throws --
|
|
27822
28305
|
* a server that fails to initialize, or fails listing tools, is recorded
|
|
@@ -27847,8 +28330,8 @@ var McpRegistry = class {
|
|
|
27847
28330
|
* tell doctor one thing and the registered tools another. */
|
|
27848
28331
|
async connectOne(server, timeoutMs, onServer) {
|
|
27849
28332
|
let timer;
|
|
27850
|
-
const deadline = new Promise((
|
|
27851
|
-
timer = setTimeout(() =>
|
|
28333
|
+
const deadline = new Promise((resolve5) => {
|
|
28334
|
+
timer = setTimeout(() => resolve5("timeout"), timeoutMs);
|
|
27852
28335
|
});
|
|
27853
28336
|
const attempt = (async () => {
|
|
27854
28337
|
const client = this.clientFactory(this.stateDir, server);
|
|
@@ -27947,6 +28430,55 @@ var McpRegistry = class {
|
|
|
27947
28430
|
}
|
|
27948
28431
|
return entry.client.callTool(tool, args);
|
|
27949
28432
|
}
|
|
28433
|
+
/** `server`'s tools as `slot`'s credential sees them. Throws when the
|
|
28434
|
+
* server is not configured or cannot be reached with that credential. */
|
|
28435
|
+
async listAs(server, slot) {
|
|
28436
|
+
return (await this.connectedAs(server, slot)).tools;
|
|
28437
|
+
}
|
|
28438
|
+
/** Call one tool with `slot`'s credential. The server slot is exactly
|
|
28439
|
+
* `call()`; an agent slot never touches the server's own client. */
|
|
28440
|
+
async callAs(server, tool, args, slot) {
|
|
28441
|
+
if (slot.kind === "server") return this.call(server, tool, args);
|
|
28442
|
+
return (await this.connectedAs(server, slot)).client.callTool(tool, args);
|
|
28443
|
+
}
|
|
28444
|
+
/** Drop every agent-slot client of `accessKey` (credentials changed, or the
|
|
28445
|
+
* agent was deleted); the next call connects afresh. In-flight connects are
|
|
28446
|
+
* dropped too: one that settles after this is never cached. */
|
|
28447
|
+
forgetAgentClients(accessKey) {
|
|
28448
|
+
for (const key of [...this.slotted.keys()]) if (accessKeyOf(key) === accessKey) this.slotted.delete(key);
|
|
28449
|
+
for (const key of [...this.slotConnecting.keys()]) if (accessKeyOf(key) === accessKey) this.slotConnecting.delete(key);
|
|
28450
|
+
}
|
|
28451
|
+
async connectedAs(server, slot) {
|
|
28452
|
+
if (slot.kind === "server") {
|
|
28453
|
+
await this.ensureConnected(server);
|
|
28454
|
+
const entry = this.connected.get(server);
|
|
28455
|
+
if (!entry) throw new Error(`MCP server "${server}" is not connected -- run: corenel mcp doctor`);
|
|
28456
|
+
return entry;
|
|
28457
|
+
}
|
|
28458
|
+
const key = mcpClientKey(server, slot);
|
|
28459
|
+
const hit = this.slotted.get(key);
|
|
28460
|
+
if (hit) return hit;
|
|
28461
|
+
const inflight = this.slotConnecting.get(key);
|
|
28462
|
+
if (inflight) return inflight;
|
|
28463
|
+
const self = {};
|
|
28464
|
+
const current = () => self.attempt !== void 0 && this.slotConnecting.get(key) === self.attempt;
|
|
28465
|
+
const attempt = (async () => {
|
|
28466
|
+
const def = (await readMcpServers(this.stateDir, this.configEnv)).find((s) => s.name === server);
|
|
28467
|
+
if (!def) throw new Error(`MCP server "${server}" is not configured on this daemon`);
|
|
28468
|
+
const client = this.clientFactory(this.stateDir, def, slot);
|
|
28469
|
+
const entry = await withDeadline((async () => {
|
|
28470
|
+
await client.initialize();
|
|
28471
|
+
return { client, tools: await client.listTools() };
|
|
28472
|
+
})(), SERVER_CONNECT_TIMEOUT_MS, `MCP server "${server}" timed out after ${SERVER_CONNECT_TIMEOUT_MS / 1e3}s`);
|
|
28473
|
+
if (current()) this.slotted.set(key, entry);
|
|
28474
|
+
return entry;
|
|
28475
|
+
})().finally(() => {
|
|
28476
|
+
if (current()) this.slotConnecting.delete(key);
|
|
28477
|
+
});
|
|
28478
|
+
self.attempt = attempt;
|
|
28479
|
+
this.slotConnecting.set(key, attempt);
|
|
28480
|
+
return attempt;
|
|
28481
|
+
}
|
|
27950
28482
|
/** Per-server connection state, for `doctor` (Task 10) to print. A server
|
|
27951
28483
|
* with `connected: false` always carries `error` -- there is no code path
|
|
27952
28484
|
* that records a failure without a reason, because "no tools" alone tells
|
|
@@ -28043,6 +28575,10 @@ function toAgentTool(registry, server, tool) {
|
|
|
28043
28575
|
};
|
|
28044
28576
|
}
|
|
28045
28577
|
|
|
28578
|
+
// ../mcp/src/portTools.ts
|
|
28579
|
+
import { MCP_CALL_FAILED, canEncodeMcpWireName as canEncodeMcpWireName2, mcpWireName as mcpWireName2, parseMcpToolAddress as parseMcpToolAddress2 } from "@corenel/protocol";
|
|
28580
|
+
var CALL_FAILED_TEXT = `Error: ${MCP_CALL_FAILED}: the MCP call failed before a result came back; it may or may not have run, so do not repeat it without checking.`;
|
|
28581
|
+
|
|
28046
28582
|
// ../mcp/src/gatedAgentTools.ts
|
|
28047
28583
|
function gatedAgentTools(registry, policy) {
|
|
28048
28584
|
const base = asAgentTools(registry);
|
|
@@ -28068,7 +28604,7 @@ function gatedAgentTools(registry, policy) {
|
|
|
28068
28604
|
|
|
28069
28605
|
// ../mcp/src/cache.ts
|
|
28070
28606
|
import { createHash as createHash2 } from "node:crypto";
|
|
28071
|
-
import { readFile as readFile4, writeFile as
|
|
28607
|
+
import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir3, rename as rename2 } from "node:fs/promises";
|
|
28072
28608
|
import { dirname as dirname8, join as join12 } from "node:path";
|
|
28073
28609
|
function toolCachePath(stateDir) {
|
|
28074
28610
|
return join12(stateDir, "mcp-tools.json");
|
|
@@ -28101,7 +28637,7 @@ async function writeToolCache(stateDir, cache) {
|
|
|
28101
28637
|
const path = toolCachePath(stateDir);
|
|
28102
28638
|
await mkdir3(dirname8(path), { recursive: true });
|
|
28103
28639
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
28104
|
-
await
|
|
28640
|
+
await writeFile2(tmp, `${JSON.stringify(cache, null, 2)}
|
|
28105
28641
|
`, "utf8");
|
|
28106
28642
|
await rename2(tmp, path);
|
|
28107
28643
|
}
|
|
@@ -28241,7 +28777,7 @@ var PAGE_FAIL = `<!doctype html><meta charset="utf-8"><title>Sign-in failed</tit
|
|
|
28241
28777
|
var DEFAULT_TIMEOUT_MS2 = 5 * 60 * 1e3;
|
|
28242
28778
|
function awaitRedirect(port, expectedState, opts = {}) {
|
|
28243
28779
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
28244
|
-
return new Promise((
|
|
28780
|
+
return new Promise((resolve5, reject) => {
|
|
28245
28781
|
let settled = false;
|
|
28246
28782
|
const v6 = createServer((req, res) => onRequest(req, res));
|
|
28247
28783
|
const finish = (fn) => {
|
|
@@ -28273,7 +28809,7 @@ function awaitRedirect(port, expectedState, opts = {}) {
|
|
|
28273
28809
|
return;
|
|
28274
28810
|
}
|
|
28275
28811
|
res.writeHead(200, { "content-type": "text/html" }).end(PAGE_OK);
|
|
28276
|
-
finish(() =>
|
|
28812
|
+
finish(() => resolve5({ code: code3 }));
|
|
28277
28813
|
};
|
|
28278
28814
|
const server = createServer(onRequest);
|
|
28279
28815
|
const timer = setTimeout(() => {
|
|
@@ -28307,21 +28843,21 @@ function awaitRedirect(port, expectedState, opts = {}) {
|
|
|
28307
28843
|
});
|
|
28308
28844
|
}
|
|
28309
28845
|
function portFree(port) {
|
|
28310
|
-
return new Promise((
|
|
28846
|
+
return new Promise((resolve5) => {
|
|
28311
28847
|
const probe = createServer();
|
|
28312
|
-
probe.once("error", () =>
|
|
28848
|
+
probe.once("error", () => resolve5(false));
|
|
28313
28849
|
probe.listen(port, "127.0.0.1", () => {
|
|
28314
|
-
probe.close(() =>
|
|
28850
|
+
probe.close(() => resolve5(true));
|
|
28315
28851
|
});
|
|
28316
28852
|
});
|
|
28317
28853
|
}
|
|
28318
28854
|
function getFreePort() {
|
|
28319
|
-
return new Promise((
|
|
28855
|
+
return new Promise((resolve5, reject) => {
|
|
28320
28856
|
const probe = createServer();
|
|
28321
28857
|
probe.once("error", reject);
|
|
28322
28858
|
probe.listen(0, "127.0.0.1", () => {
|
|
28323
28859
|
const addr = probe.address();
|
|
28324
|
-
probe.close(() =>
|
|
28860
|
+
probe.close(() => resolve5(addr.port));
|
|
28325
28861
|
});
|
|
28326
28862
|
});
|
|
28327
28863
|
}
|
|
@@ -28431,7 +28967,7 @@ async function loginToMcpServer(stateDir, server, deps = {}) {
|
|
|
28431
28967
|
fetchImpl,
|
|
28432
28968
|
now
|
|
28433
28969
|
);
|
|
28434
|
-
await
|
|
28970
|
+
await writeMcpCredentialFor(stateDir, server.name, deps.slot ?? SERVER_SLOT, cred);
|
|
28435
28971
|
return { ok: true };
|
|
28436
28972
|
} catch (e) {
|
|
28437
28973
|
redirectAbort.abort();
|
|
@@ -28511,6 +29047,9 @@ import { lookup as dnsLookup } from "node:dns/promises";
|
|
|
28511
29047
|
import { setOutboundResolver } from "@corenel/harness/tools/urlGuard";
|
|
28512
29048
|
var EXIT_POLICY_PARK_REFUSED = 3;
|
|
28513
29049
|
var EXIT_BUDGET_EXCEEDED = 4;
|
|
29050
|
+
function loginApiBase(rest, opts = {}) {
|
|
29051
|
+
return apiBase(flag(rest, "base", ""), { ...opts, ignoreCredential: true });
|
|
29052
|
+
}
|
|
28514
29053
|
function replSessionId(id) {
|
|
28515
29054
|
return id === UNATTACHED_SESSION ? null : id;
|
|
28516
29055
|
}
|
|
@@ -28941,10 +29480,15 @@ async function main() {
|
|
|
28941
29480
|
case "run-agent": {
|
|
28942
29481
|
const agent = positional(rest)[0];
|
|
28943
29482
|
if (!agent) {
|
|
28944
|
-
process.stderr.write('usage: corenel run-agent <name> --input "<text>" [--model M] [--base URL] [--allow-mutations]\n');
|
|
29483
|
+
process.stderr.write('usage: corenel run-agent <name> --input "<text>" [--model M] [--base URL] [--allow-mutations] [--memory-read-only]\n');
|
|
28945
29484
|
process.exit(2);
|
|
28946
29485
|
}
|
|
28947
29486
|
const policyOverride = hasValueFlag(rest, "policy") ? (await resolvePolicy({ flag: flag(rest, "policy", ""), cwd: process.cwd() })).policy : void 0;
|
|
29487
|
+
if (hasValueFlag(rest, "memory-partition")) {
|
|
29488
|
+
process.stderr.write("corenel: --memory-partition is not supported: a guest run has no per-user memory\n");
|
|
29489
|
+
process.exit(2);
|
|
29490
|
+
}
|
|
29491
|
+
const guestMemory = hasBooleanFlag(rest, "memory-read-only") ? { kind: "read-only" } : void 0;
|
|
28948
29492
|
const code3 = await runCrewAgentCli({
|
|
28949
29493
|
cwd: process.cwd(),
|
|
28950
29494
|
agent,
|
|
@@ -28955,6 +29499,7 @@ async function main() {
|
|
|
28955
29499
|
...hasValueFlag(rest, "model") ? { model: flag(rest, "model", "") } : {},
|
|
28956
29500
|
...hasValueFlag(rest, "base") ? { base: flag(rest, "base", "") } : {},
|
|
28957
29501
|
...hasAllowMutationsFlag(rest) ? { allowMutations: true } : {},
|
|
29502
|
+
...guestMemory ? { memory: guestMemory } : {},
|
|
28958
29503
|
...policyOverride ? { policy: policyOverride } : {},
|
|
28959
29504
|
emit: (line) => process.stdout.write(`${line}
|
|
28960
29505
|
`),
|
|
@@ -28973,7 +29518,7 @@ async function main() {
|
|
|
28973
29518
|
break;
|
|
28974
29519
|
}
|
|
28975
29520
|
case "login": {
|
|
28976
|
-
const base = await
|
|
29521
|
+
const base = await loginApiBase(rest);
|
|
28977
29522
|
await deviceLogin({
|
|
28978
29523
|
base,
|
|
28979
29524
|
store: (token, account) => writeLoginCredential(token, { apiBase: base, ...account ? { account } : {} })
|
|
@@ -28988,7 +29533,7 @@ Logged in. Token saved to ${authTokenPath()}
|
|
|
28988
29533
|
await runAuth();
|
|
28989
29534
|
break;
|
|
28990
29535
|
case "logout":
|
|
28991
|
-
await runLogout();
|
|
29536
|
+
process.exitCode = await runLogout();
|
|
28992
29537
|
break;
|
|
28993
29538
|
case "doctor": {
|
|
28994
29539
|
process.exitCode = await runDoctor(void 0, void 0, flag(rest, "model", DEFAULT_MODEL2), resolveDoctorEnv(rest));
|
|
@@ -29065,6 +29610,7 @@ export {
|
|
|
29065
29610
|
EXIT_BUDGET_EXCEEDED,
|
|
29066
29611
|
EXIT_POLICY_PARK_REFUSED,
|
|
29067
29612
|
buildRunPermission,
|
|
29613
|
+
loginApiBase,
|
|
29068
29614
|
replCompaction,
|
|
29069
29615
|
replSessionId
|
|
29070
29616
|
};
|