@corenel/cli 0.5.0 → 0.6.0
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 +257 -188
- package/package.json +7 -7
package/dist/main.js
CHANGED
|
@@ -18404,6 +18404,25 @@ ${firstUserMsg.content}`;
|
|
|
18404
18404
|
}
|
|
18405
18405
|
});
|
|
18406
18406
|
|
|
18407
|
+
// ../tools-node/toolGuidance.ts
|
|
18408
|
+
function nodeToolGuidance(toolNames) {
|
|
18409
|
+
const has = new Set(toolNames);
|
|
18410
|
+
const parts = [];
|
|
18411
|
+
if (has.has(SHELL_TOOL)) parts.push(SHELL);
|
|
18412
|
+
if (PROC_TOOLS.some((t) => has.has(t))) parts.push(PROCS);
|
|
18413
|
+
return parts.join("\n\n");
|
|
18414
|
+
}
|
|
18415
|
+
var SHELL_TOOL, PROC_TOOLS, SHELL, PROCS;
|
|
18416
|
+
var init_toolGuidance = __esm({
|
|
18417
|
+
"../tools-node/toolGuidance.ts"() {
|
|
18418
|
+
"use strict";
|
|
18419
|
+
SHELL_TOOL = "shell";
|
|
18420
|
+
PROC_TOOLS = ["proc_start", "proc_stop", "proc_list"];
|
|
18421
|
+
SHELL = `Running a command is often the shortest correct answer: use \`${SHELL_TOOL}\` for anything a command already does well -- listing, searching, git, package managers, build and test runners -- instead of reconstructing it from file reads. It waits for the command to finish and returns its output, so it is the wrong tool for anything that does not exit on its own.`;
|
|
18422
|
+
PROCS = `A command that keeps running (a dev server, a watcher, a queue worker) belongs to \`${PROC_TOOLS[0]}\`, not \`${SHELL_TOOL}\` -- starting one with \`${SHELL_TOOL}\` hangs the turn until it is killed. Use \`${PROC_TOOLS[2]}\` to see what is already running before starting anything, and \`${PROC_TOOLS[1]}\` to stop what you started. Do not stop a process you did not start.`;
|
|
18423
|
+
}
|
|
18424
|
+
});
|
|
18425
|
+
|
|
18407
18426
|
// ../tools-node/promptHost.ts
|
|
18408
18427
|
import { normalizeFilePath as normalizeFilePath2, flattenFiles as flattenFiles2 } from "@corenel/protocol";
|
|
18409
18428
|
function backend(svc) {
|
|
@@ -18462,8 +18481,22 @@ function nodePromptHost(workspace, project) {
|
|
|
18462
18481
|
sources[p] = liveSrc;
|
|
18463
18482
|
return compile("", values, new HybridFileSystem(sources, backend(workspace)), "/" + p);
|
|
18464
18483
|
},
|
|
18465
|
-
siteIndex: () => ""
|
|
18484
|
+
siteIndex: () => "",
|
|
18466
18485
|
// no site index in node (the help chat is browser-only)
|
|
18486
|
+
/* Who the agent says it IS. Without this, `composeAgentSystem` falls back
|
|
18487
|
+
* to its `'Prompd'` default and the `corenel` binary's agent introduced
|
|
18488
|
+
* itself as "the Prompd assistant" -- the same leak already fixed for the
|
|
18489
|
+
* browser help chat (composeHelpSystem) and missed here, because the node
|
|
18490
|
+
* host is the one PromptHost that never implemented the hook.
|
|
18491
|
+
*
|
|
18492
|
+
* Constant, not a parameter: every node consumer of this host (the CLI and
|
|
18493
|
+
* the sidecar) IS Corenel. A host that is not should implement its own. */
|
|
18494
|
+
appName: () => "Corenel",
|
|
18495
|
+
/* The product's guidance for the tools this run actually has -- the shell,
|
|
18496
|
+
* long-running processes, and deferred MCP tools. Gated per tool inside
|
|
18497
|
+
* nodeToolGuidance, because this same host serves the sidecar, whose shell
|
|
18498
|
+
* is behind --allow-shell. */
|
|
18499
|
+
toolGuidance: (names) => nodeToolGuidance(names)
|
|
18467
18500
|
};
|
|
18468
18501
|
}
|
|
18469
18502
|
var parser, compiler;
|
|
@@ -18471,6 +18504,7 @@ var init_promptHost = __esm({
|
|
|
18471
18504
|
"../tools-node/promptHost.ts"() {
|
|
18472
18505
|
"use strict";
|
|
18473
18506
|
init_dist();
|
|
18507
|
+
init_toolGuidance();
|
|
18474
18508
|
parser = new PrompdParser();
|
|
18475
18509
|
compiler = new PrompdCompiler();
|
|
18476
18510
|
}
|
|
@@ -22305,7 +22339,7 @@ var require_websocket_server = __commonJS({
|
|
|
22305
22339
|
init_tools_node();
|
|
22306
22340
|
import { runAgent as runAgent2 } from "@corenel/harness/core/loop";
|
|
22307
22341
|
import { createChatClient as createChatClient2, setGatewayBase as setGatewayBase2 } from "@corenel/harness/providers/gateway";
|
|
22308
|
-
import { resolveContextWindow as resolveContextWindow2 } from "@corenel/harness/providers/contextWindow";
|
|
22342
|
+
import { resolveContextWindow as resolveContextWindow2, resolveContextWindowWithHydration } from "@corenel/harness/providers/contextWindow";
|
|
22309
22343
|
import { allTools as allTools3 } from "@corenel/harness/tools/builtins";
|
|
22310
22344
|
import { registerGuard as registerGuard2 } from "@corenel/harness/guards";
|
|
22311
22345
|
import { setPromptHost } from "@corenel/harness/prompts/host";
|
|
@@ -22355,8 +22389,8 @@ function inMemoryMemory() {
|
|
|
22355
22389
|
async forget(name) {
|
|
22356
22390
|
items.delete(name);
|
|
22357
22391
|
}
|
|
22358
|
-
// No core(): this stub has no tiers, and
|
|
22359
|
-
// pretending it has a memory block.
|
|
22392
|
+
// No core(): this stub has no tiers, and compose injects nothing rather
|
|
22393
|
+
// than pretending it has a memory block.
|
|
22360
22394
|
};
|
|
22361
22395
|
}
|
|
22362
22396
|
function inMemoryKv() {
|
|
@@ -22416,168 +22450,6 @@ function nodeToolCtx(signal, opts = {}) {
|
|
|
22416
22450
|
return { signal, ...base, ...spawn4 ? { spawn: spawn4 } : {} };
|
|
22417
22451
|
}
|
|
22418
22452
|
|
|
22419
|
-
// src/tokenGuard.ts
|
|
22420
|
-
function tokenGuard(getToken) {
|
|
22421
|
-
return {
|
|
22422
|
-
id: "token",
|
|
22423
|
-
appliesTo: (f) => f === "llm-execution",
|
|
22424
|
-
check: () => getToken() ? { allowed: true } : { allowed: false, guardId: "token", reason: "no CORENEL_TOKEN \u2014 run `corenel login`", action: { kind: "sign-in" } }
|
|
22425
|
-
};
|
|
22426
|
-
}
|
|
22427
|
-
|
|
22428
|
-
// src/login.ts
|
|
22429
|
-
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
22430
|
-
var MIN_POLL_MS = 1e3;
|
|
22431
|
-
var DEFAULT_LIFETIME_S = 900;
|
|
22432
|
-
var ACCOUNT_LOOKUP_MS = 5e3;
|
|
22433
|
-
function deadlineSignal(ms, outer) {
|
|
22434
|
-
const ac = new AbortController();
|
|
22435
|
-
const onOuter = () => ac.abort();
|
|
22436
|
-
const timer = setTimeout(() => ac.abort(), ms);
|
|
22437
|
-
if (outer) {
|
|
22438
|
-
if (outer.aborted) ac.abort();
|
|
22439
|
-
else outer.addEventListener("abort", onOuter, { once: true });
|
|
22440
|
-
}
|
|
22441
|
-
return {
|
|
22442
|
-
signal: ac.signal,
|
|
22443
|
-
dispose: () => {
|
|
22444
|
-
clearTimeout(timer);
|
|
22445
|
-
outer?.removeEventListener("abort", onOuter);
|
|
22446
|
-
}
|
|
22447
|
-
};
|
|
22448
|
-
}
|
|
22449
|
-
async function whoIsThis(base, token, f, outer, deadlineMs = ACCOUNT_LOOKUP_MS) {
|
|
22450
|
-
const { signal, dispose } = deadlineSignal(deadlineMs, outer);
|
|
22451
|
-
try {
|
|
22452
|
-
const res = await f(`${base}/auth/me`, {
|
|
22453
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
22454
|
-
signal
|
|
22455
|
-
});
|
|
22456
|
-
if (!res.ok) return void 0;
|
|
22457
|
-
const body = JSON.parse(await res.text());
|
|
22458
|
-
const email = body?.user?.email;
|
|
22459
|
-
if (typeof email !== "string") return void 0;
|
|
22460
|
-
const trimmed = email.trim();
|
|
22461
|
-
return trimmed ? trimmed : void 0;
|
|
22462
|
-
} catch {
|
|
22463
|
-
return void 0;
|
|
22464
|
-
} finally {
|
|
22465
|
-
dispose();
|
|
22466
|
-
}
|
|
22467
|
-
}
|
|
22468
|
-
function defaultOpen(uri, code2) {
|
|
22469
|
-
process.stdout.write(`
|
|
22470
|
-
To authorize, open: ${uri}
|
|
22471
|
-
and enter the code: ${code2}
|
|
22472
|
-
|
|
22473
|
-
Waiting for authorization\u2026
|
|
22474
|
-
`);
|
|
22475
|
-
}
|
|
22476
|
-
async function deviceLogin(opts) {
|
|
22477
|
-
const f = opts.fetchImpl ?? fetch;
|
|
22478
|
-
const base = opts.base.replace(/\/$/, "");
|
|
22479
|
-
const post = async (path, body) => {
|
|
22480
|
-
const url = `${base}${path}`;
|
|
22481
|
-
const res = await f(url, {
|
|
22482
|
-
method: "POST",
|
|
22483
|
-
headers: { "Content-Type": "application/json" },
|
|
22484
|
-
body: JSON.stringify(body),
|
|
22485
|
-
signal: opts.signal
|
|
22486
|
-
});
|
|
22487
|
-
const text = await res.text();
|
|
22488
|
-
try {
|
|
22489
|
-
return JSON.parse(text);
|
|
22490
|
-
} catch {
|
|
22491
|
-
const looksLikeHtml = /^\s*</.test(text);
|
|
22492
|
-
throw new Error(
|
|
22493
|
-
`${url} answered ${res.status} with ${looksLikeHtml ? "an HTML page" : "a non-JSON body"}, not JSON.` + (res.status === 404 ? `
|
|
22494
|
-
That usually means --base is wrong. The device endpoints live under /api \u2014 try --base ${base.replace(/\/api$/, "")}/api` : "")
|
|
22495
|
-
);
|
|
22496
|
-
}
|
|
22497
|
-
};
|
|
22498
|
-
const dc = await post("/oauth/device/code", { client_id: "corenel-cli", scope: "agent" });
|
|
22499
|
-
if (!dc.device_code) throw new Error("device authorization failed");
|
|
22500
|
-
(opts.open ?? defaultOpen)(dc.verification_uri, dc.user_code);
|
|
22501
|
-
let interval = opts.pollMs ?? Math.max(MIN_POLL_MS, (dc.interval ?? 5) * 1e3);
|
|
22502
|
-
const lifetimeMs = (typeof dc.expires_in === "number" && dc.expires_in > 0 ? dc.expires_in : DEFAULT_LIFETIME_S) * 1e3;
|
|
22503
|
-
const deadline = Date.now() + lifetimeMs;
|
|
22504
|
-
for (; ; ) {
|
|
22505
|
-
if (opts.signal?.aborted) throw new Error("login cancelled");
|
|
22506
|
-
if (Date.now() >= deadline) {
|
|
22507
|
-
throw new Error(`the code ${dc.user_code} expired before it was authorized -- run \`corenel login\` again`);
|
|
22508
|
-
}
|
|
22509
|
-
await sleep(interval);
|
|
22510
|
-
const tok = await post("/oauth/token", {
|
|
22511
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
22512
|
-
device_code: dc.device_code,
|
|
22513
|
-
client_id: "corenel-cli"
|
|
22514
|
-
});
|
|
22515
|
-
if (tok.access_token) {
|
|
22516
|
-
const claimed = typeof tok.account === "string" ? tok.account.trim() : "";
|
|
22517
|
-
const account = claimed || await whoIsThis(base, tok.access_token, f, opts.signal, opts.accountLookupMs);
|
|
22518
|
-
await opts.store(tok.access_token, account || void 0);
|
|
22519
|
-
return tok.access_token;
|
|
22520
|
-
}
|
|
22521
|
-
if (tok.error === "authorization_pending") continue;
|
|
22522
|
-
if (tok.error === "slow_down") {
|
|
22523
|
-
interval += 5e3;
|
|
22524
|
-
continue;
|
|
22525
|
-
}
|
|
22526
|
-
throw new Error(tok.error_description || tok.error || "login failed");
|
|
22527
|
-
}
|
|
22528
|
-
}
|
|
22529
|
-
|
|
22530
|
-
// src/runAgent.ts
|
|
22531
|
-
import { runAgent } from "@corenel/harness/core/loop";
|
|
22532
|
-
import { createChatClient, setGatewayBase } from "@corenel/harness/providers/gateway";
|
|
22533
|
-
|
|
22534
|
-
// src/apiBase.ts
|
|
22535
|
-
init_tools_node();
|
|
22536
|
-
import { DEFAULT_API_BASE as DEFAULT_API_BASE2 } from "@corenel/protocol";
|
|
22537
|
-
var InvalidApiBaseError = class extends Error {
|
|
22538
|
-
constructor(value, why) {
|
|
22539
|
-
super(`invalid API base ${JSON.stringify(value)} -- ${why}`);
|
|
22540
|
-
this.value = value;
|
|
22541
|
-
this.why = why;
|
|
22542
|
-
this.name = "InvalidApiBaseError";
|
|
22543
|
-
}
|
|
22544
|
-
};
|
|
22545
|
-
function isLoopback(hostname) {
|
|
22546
|
-
const h = hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
22547
|
-
return h === "localhost" || h === "127.0.0.1" || h === "::1" || h.endsWith(".localhost");
|
|
22548
|
-
}
|
|
22549
|
-
var warnedCleartext = false;
|
|
22550
|
-
async function apiBase(explicit, opts = {}) {
|
|
22551
|
-
const resolved = await resolveApiBase({
|
|
22552
|
-
explicit,
|
|
22553
|
-
env: opts.env ?? process.env,
|
|
22554
|
-
...opts.stateDir ? { stateDir: opts.stateDir } : {}
|
|
22555
|
-
});
|
|
22556
|
-
const value = resolved.base;
|
|
22557
|
-
let url;
|
|
22558
|
-
try {
|
|
22559
|
-
url = new URL(value);
|
|
22560
|
-
} catch {
|
|
22561
|
-
throw new InvalidApiBaseError(value, "it is not a URL (a base needs a scheme, e.g. https://api.corenel.ai/api)");
|
|
22562
|
-
}
|
|
22563
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
22564
|
-
throw new InvalidApiBaseError(value, `the scheme must be https or http, not ${url.protocol.replace(/:$/, "")}`);
|
|
22565
|
-
}
|
|
22566
|
-
if (url.protocol === "http:" && !isLoopback(url.hostname) && !warnedCleartext) {
|
|
22567
|
-
warnedCleartext = true;
|
|
22568
|
-
process.stderr.write(
|
|
22569
|
-
`corenel: warning: ${url.host} is plain http, so your access token is sent in cleartext. Use https unless this host is on a private network you trust.
|
|
22570
|
-
`
|
|
22571
|
-
);
|
|
22572
|
-
}
|
|
22573
|
-
return value;
|
|
22574
|
-
}
|
|
22575
|
-
|
|
22576
|
-
// src/runAgent.ts
|
|
22577
|
-
import { allTools as allTools2 } from "@corenel/harness/tools/builtins";
|
|
22578
|
-
import { registerGuard } from "@corenel/harness/guards";
|
|
22579
|
-
import { composeAgentSystem } from "@corenel/harness/prompts";
|
|
22580
|
-
|
|
22581
22453
|
// ../crew/frontmatter.ts
|
|
22582
22454
|
var import_yaml = __toESM(require_dist(), 1);
|
|
22583
22455
|
var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
@@ -22944,7 +22816,181 @@ import { makeRoomTools, ROOM_TOOL_NAMES } from "@corenel/harness/tools/roomTools
|
|
|
22944
22816
|
import { describeParticipants, resolveAddressee } from "@corenel/harness/tools/roomTools";
|
|
22945
22817
|
import { planFindingEdit, planFindingEdits } from "@corenel/harness/rooms/findings";
|
|
22946
22818
|
|
|
22819
|
+
// src/operatorMemory.ts
|
|
22820
|
+
init_tools_node();
|
|
22821
|
+
var OPERATOR_MEMORY_DIR = "memory";
|
|
22822
|
+
function operatorMemory() {
|
|
22823
|
+
const files = new NodeFileService(configWorkspaceRoot());
|
|
22824
|
+
return makeAgentMemoryProvider(() => files, OPERATOR_MEMORY_DIR);
|
|
22825
|
+
}
|
|
22826
|
+
function operatorMemoryIndex(memory) {
|
|
22827
|
+
return async () => {
|
|
22828
|
+
const items = await memory.list().catch(() => []);
|
|
22829
|
+
return items.map((i) => `- **${i.name}** \u2014 ${i.description || "(no description)"}`).join("\n");
|
|
22830
|
+
};
|
|
22831
|
+
}
|
|
22832
|
+
|
|
22833
|
+
// src/tokenGuard.ts
|
|
22834
|
+
function tokenGuard(getToken) {
|
|
22835
|
+
return {
|
|
22836
|
+
id: "token",
|
|
22837
|
+
appliesTo: (f) => f === "llm-execution",
|
|
22838
|
+
check: () => getToken() ? { allowed: true } : { allowed: false, guardId: "token", reason: "no CORENEL_TOKEN \u2014 run `corenel login`", action: { kind: "sign-in" } }
|
|
22839
|
+
};
|
|
22840
|
+
}
|
|
22841
|
+
|
|
22842
|
+
// src/login.ts
|
|
22843
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
22844
|
+
var MIN_POLL_MS = 1e3;
|
|
22845
|
+
var DEFAULT_LIFETIME_S = 900;
|
|
22846
|
+
var ACCOUNT_LOOKUP_MS = 5e3;
|
|
22847
|
+
function deadlineSignal(ms, outer) {
|
|
22848
|
+
const ac = new AbortController();
|
|
22849
|
+
const onOuter = () => ac.abort();
|
|
22850
|
+
const timer = setTimeout(() => ac.abort(), ms);
|
|
22851
|
+
if (outer) {
|
|
22852
|
+
if (outer.aborted) ac.abort();
|
|
22853
|
+
else outer.addEventListener("abort", onOuter, { once: true });
|
|
22854
|
+
}
|
|
22855
|
+
return {
|
|
22856
|
+
signal: ac.signal,
|
|
22857
|
+
dispose: () => {
|
|
22858
|
+
clearTimeout(timer);
|
|
22859
|
+
outer?.removeEventListener("abort", onOuter);
|
|
22860
|
+
}
|
|
22861
|
+
};
|
|
22862
|
+
}
|
|
22863
|
+
async function whoIsThis(base, token, f, outer, deadlineMs = ACCOUNT_LOOKUP_MS) {
|
|
22864
|
+
const { signal, dispose } = deadlineSignal(deadlineMs, outer);
|
|
22865
|
+
try {
|
|
22866
|
+
const res = await f(`${base}/auth/me`, {
|
|
22867
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
22868
|
+
signal
|
|
22869
|
+
});
|
|
22870
|
+
if (!res.ok) return void 0;
|
|
22871
|
+
const body = JSON.parse(await res.text());
|
|
22872
|
+
const email = body?.user?.email;
|
|
22873
|
+
if (typeof email !== "string") return void 0;
|
|
22874
|
+
const trimmed = email.trim();
|
|
22875
|
+
return trimmed ? trimmed : void 0;
|
|
22876
|
+
} catch {
|
|
22877
|
+
return void 0;
|
|
22878
|
+
} finally {
|
|
22879
|
+
dispose();
|
|
22880
|
+
}
|
|
22881
|
+
}
|
|
22882
|
+
function defaultOpen(uri, code2) {
|
|
22883
|
+
process.stdout.write(`
|
|
22884
|
+
To authorize, open: ${uri}
|
|
22885
|
+
and enter the code: ${code2}
|
|
22886
|
+
|
|
22887
|
+
Waiting for authorization\u2026
|
|
22888
|
+
`);
|
|
22889
|
+
}
|
|
22890
|
+
async function deviceLogin(opts) {
|
|
22891
|
+
const f = opts.fetchImpl ?? fetch;
|
|
22892
|
+
const base = opts.base.replace(/\/$/, "");
|
|
22893
|
+
const post = async (path, body) => {
|
|
22894
|
+
const url = `${base}${path}`;
|
|
22895
|
+
const res = await f(url, {
|
|
22896
|
+
method: "POST",
|
|
22897
|
+
headers: { "Content-Type": "application/json" },
|
|
22898
|
+
body: JSON.stringify(body),
|
|
22899
|
+
signal: opts.signal
|
|
22900
|
+
});
|
|
22901
|
+
const text = await res.text();
|
|
22902
|
+
try {
|
|
22903
|
+
return JSON.parse(text);
|
|
22904
|
+
} catch {
|
|
22905
|
+
const looksLikeHtml = /^\s*</.test(text);
|
|
22906
|
+
throw new Error(
|
|
22907
|
+
`${url} answered ${res.status} with ${looksLikeHtml ? "an HTML page" : "a non-JSON body"}, not JSON.` + (res.status === 404 ? `
|
|
22908
|
+
That usually means --base is wrong. The device endpoints live under /api \u2014 try --base ${base.replace(/\/api$/, "")}/api` : "")
|
|
22909
|
+
);
|
|
22910
|
+
}
|
|
22911
|
+
};
|
|
22912
|
+
const dc = await post("/oauth/device/code", { client_id: "corenel-cli", scope: "agent" });
|
|
22913
|
+
if (!dc.device_code) throw new Error("device authorization failed");
|
|
22914
|
+
(opts.open ?? defaultOpen)(dc.verification_uri, dc.user_code);
|
|
22915
|
+
let interval = opts.pollMs ?? Math.max(MIN_POLL_MS, (dc.interval ?? 5) * 1e3);
|
|
22916
|
+
const lifetimeMs = (typeof dc.expires_in === "number" && dc.expires_in > 0 ? dc.expires_in : DEFAULT_LIFETIME_S) * 1e3;
|
|
22917
|
+
const deadline = Date.now() + lifetimeMs;
|
|
22918
|
+
for (; ; ) {
|
|
22919
|
+
if (opts.signal?.aborted) throw new Error("login cancelled");
|
|
22920
|
+
if (Date.now() >= deadline) {
|
|
22921
|
+
throw new Error(`the code ${dc.user_code} expired before it was authorized -- run \`corenel login\` again`);
|
|
22922
|
+
}
|
|
22923
|
+
await sleep(interval);
|
|
22924
|
+
const tok = await post("/oauth/token", {
|
|
22925
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
22926
|
+
device_code: dc.device_code,
|
|
22927
|
+
client_id: "corenel-cli"
|
|
22928
|
+
});
|
|
22929
|
+
if (tok.access_token) {
|
|
22930
|
+
const claimed = typeof tok.account === "string" ? tok.account.trim() : "";
|
|
22931
|
+
const account = claimed || await whoIsThis(base, tok.access_token, f, opts.signal, opts.accountLookupMs);
|
|
22932
|
+
await opts.store(tok.access_token, account || void 0);
|
|
22933
|
+
return tok.access_token;
|
|
22934
|
+
}
|
|
22935
|
+
if (tok.error === "authorization_pending") continue;
|
|
22936
|
+
if (tok.error === "slow_down") {
|
|
22937
|
+
interval += 5e3;
|
|
22938
|
+
continue;
|
|
22939
|
+
}
|
|
22940
|
+
throw new Error(tok.error_description || tok.error || "login failed");
|
|
22941
|
+
}
|
|
22942
|
+
}
|
|
22943
|
+
|
|
22944
|
+
// src/runAgent.ts
|
|
22945
|
+
import { runAgent } from "@corenel/harness/core/loop";
|
|
22946
|
+
import { createChatClient, setGatewayBase } from "@corenel/harness/providers/gateway";
|
|
22947
|
+
|
|
22948
|
+
// src/apiBase.ts
|
|
22949
|
+
init_tools_node();
|
|
22950
|
+
import { DEFAULT_API_BASE as DEFAULT_API_BASE2 } from "@corenel/protocol";
|
|
22951
|
+
var InvalidApiBaseError = class extends Error {
|
|
22952
|
+
constructor(value, why) {
|
|
22953
|
+
super(`invalid API base ${JSON.stringify(value)} -- ${why}`);
|
|
22954
|
+
this.value = value;
|
|
22955
|
+
this.why = why;
|
|
22956
|
+
this.name = "InvalidApiBaseError";
|
|
22957
|
+
}
|
|
22958
|
+
};
|
|
22959
|
+
function isLoopback(hostname) {
|
|
22960
|
+
const h = hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
22961
|
+
return h === "localhost" || h === "127.0.0.1" || h === "::1" || h.endsWith(".localhost");
|
|
22962
|
+
}
|
|
22963
|
+
var warnedCleartext = false;
|
|
22964
|
+
async function apiBase(explicit, opts = {}) {
|
|
22965
|
+
const resolved = await resolveApiBase({
|
|
22966
|
+
explicit,
|
|
22967
|
+
env: opts.env ?? process.env,
|
|
22968
|
+
...opts.stateDir ? { stateDir: opts.stateDir } : {}
|
|
22969
|
+
});
|
|
22970
|
+
const value = resolved.base;
|
|
22971
|
+
let url;
|
|
22972
|
+
try {
|
|
22973
|
+
url = new URL(value);
|
|
22974
|
+
} catch {
|
|
22975
|
+
throw new InvalidApiBaseError(value, "it is not a URL (a base needs a scheme, e.g. https://api.corenel.ai/api)");
|
|
22976
|
+
}
|
|
22977
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
22978
|
+
throw new InvalidApiBaseError(value, `the scheme must be https or http, not ${url.protocol.replace(/:$/, "")}`);
|
|
22979
|
+
}
|
|
22980
|
+
if (url.protocol === "http:" && !isLoopback(url.hostname) && !warnedCleartext) {
|
|
22981
|
+
warnedCleartext = true;
|
|
22982
|
+
process.stderr.write(
|
|
22983
|
+
`corenel: warning: ${url.host} is plain http, so your access token is sent in cleartext. Use https unless this host is on a private network you trust.
|
|
22984
|
+
`
|
|
22985
|
+
);
|
|
22986
|
+
}
|
|
22987
|
+
return value;
|
|
22988
|
+
}
|
|
22989
|
+
|
|
22947
22990
|
// src/runAgent.ts
|
|
22991
|
+
import { allTools as allTools2 } from "@corenel/harness/tools/builtins";
|
|
22992
|
+
import { registerGuard } from "@corenel/harness/guards";
|
|
22993
|
+
import { composeAgentSystem } from "@corenel/harness/prompts";
|
|
22948
22994
|
init_tools_node();
|
|
22949
22995
|
import { stateDirName as stateDirName3 } from "@corenel/protocol";
|
|
22950
22996
|
import { PermissionService as PermissionService2 } from "@corenel/harness/guardrail/permission-service";
|
|
@@ -24275,7 +24321,7 @@ function trim(v) {
|
|
|
24275
24321
|
}
|
|
24276
24322
|
|
|
24277
24323
|
// src/cli.ts
|
|
24278
|
-
import { hydratePrices } from "@corenel/harness/core/pricing";
|
|
24324
|
+
import { hydratePrices, hydrateCatalog } from "@corenel/harness/core/pricing";
|
|
24279
24325
|
|
|
24280
24326
|
// src/modelCatalog.ts
|
|
24281
24327
|
function modelCatalogEntries(table) {
|
|
@@ -25796,9 +25842,22 @@ async function agentSetup(argv) {
|
|
|
25796
25842
|
}
|
|
25797
25843
|
setPromptHost(nodePromptHost(new NodeFileService(configWorkspaceRoot()), new NodeFileService(process.cwd())));
|
|
25798
25844
|
const identity = createIdentityState();
|
|
25845
|
+
const memory = operatorMemory();
|
|
25799
25846
|
let system = "You are corenel, a concise CLI assistant.";
|
|
25800
25847
|
try {
|
|
25801
|
-
system = await composeAgentSystem3({
|
|
25848
|
+
system = await composeAgentSystem3({
|
|
25849
|
+
mode: identity.mode,
|
|
25850
|
+
tools: tools.map((t) => ({ name: t.name, description: t.description })),
|
|
25851
|
+
// What this face knows that the model cannot infer. `process.cwd()` and
|
|
25852
|
+
// not the `--root` union: this is where the shell tool runs and where a
|
|
25853
|
+
// relative path resolves, which is the question the facts answer.
|
|
25854
|
+
runFacts: [
|
|
25855
|
+
"Surface: terminal",
|
|
25856
|
+
`Working directory: ${process.cwd()}`,
|
|
25857
|
+
`Platform: ${process.platform}`
|
|
25858
|
+
],
|
|
25859
|
+
memoryIndex: operatorMemoryIndex(memory)
|
|
25860
|
+
});
|
|
25802
25861
|
} catch {
|
|
25803
25862
|
}
|
|
25804
25863
|
let policy;
|
|
@@ -25822,6 +25881,7 @@ async function agentSetup(argv) {
|
|
|
25822
25881
|
identity,
|
|
25823
25882
|
policy,
|
|
25824
25883
|
policySource,
|
|
25884
|
+
memory,
|
|
25825
25885
|
mcp,
|
|
25826
25886
|
disconnect: () => sidecarProcs?.disconnect()
|
|
25827
25887
|
};
|
|
@@ -25842,7 +25902,7 @@ async function run(argv) {
|
|
|
25842
25902
|
process.exitCode = ext ? await runExtension(ext, ["run", session, prompt]) : 2;
|
|
25843
25903
|
return;
|
|
25844
25904
|
}
|
|
25845
|
-
const { client, model, system, tools, policy, disconnect } = await agentSetup(argv);
|
|
25905
|
+
const { client, model, system, tools, policy, memory, disconnect } = await agentSetup(argv);
|
|
25846
25906
|
try {
|
|
25847
25907
|
await runOnce();
|
|
25848
25908
|
} finally {
|
|
@@ -25915,7 +25975,8 @@ async function run(argv) {
|
|
|
25915
25975
|
// looser, so it can't be used to route around the default-deny above.
|
|
25916
25976
|
toolCtx: nodeToolCtx(ac.signal, {
|
|
25917
25977
|
files: new NodeFileService(process.cwd()),
|
|
25918
|
-
spawn: { tools, client, defaultModel: model, permission }
|
|
25978
|
+
spawn: { tools, client, defaultModel: model, permission },
|
|
25979
|
+
memory
|
|
25919
25980
|
}),
|
|
25920
25981
|
signal: ac.signal,
|
|
25921
25982
|
// The meter is fed from the SAME event stream, one `usage` per model call.
|
|
@@ -25943,7 +26004,7 @@ async function run(argv) {
|
|
|
25943
26004
|
}
|
|
25944
26005
|
}
|
|
25945
26006
|
async function interactive(argv) {
|
|
25946
|
-
const { client, model, tools, identity, policy, policySource, mcp, disconnect } = await agentSetup(argv);
|
|
26007
|
+
const { client, model, tools, identity, policy, policySource, memory, mcp, disconnect } = await agentSetup(argv);
|
|
25947
26008
|
try {
|
|
25948
26009
|
await runInteractive();
|
|
25949
26010
|
} finally {
|
|
@@ -25954,10 +26015,18 @@ async function interactive(argv) {
|
|
|
25954
26015
|
const cwd = process.cwd();
|
|
25955
26016
|
let modelRates = null;
|
|
25956
26017
|
let priceTable = /* @__PURE__ */ new Map();
|
|
26018
|
+
const resolvedWindow = resolveContextWindow2(model);
|
|
26019
|
+
alog("context window", model, resolvedWindow.window, `(${resolvedWindow.source})`);
|
|
26020
|
+
const compression = { mode: "balanced", contextWindow: resolvedWindow.window, strategy: "deferred" };
|
|
25957
26021
|
const priceBase = await apiBase(flag(argv, "base", ""));
|
|
25958
|
-
void
|
|
25959
|
-
priceTable =
|
|
25960
|
-
modelRates =
|
|
26022
|
+
void hydrateCatalog(priceBase, nodeAuthToken().getToken).then(({ prices, windows }) => {
|
|
26023
|
+
priceTable = prices;
|
|
26024
|
+
modelRates = prices.get(model) ?? null;
|
|
26025
|
+
const rehydrated = resolveContextWindowWithHydration(model, windows);
|
|
26026
|
+
if (rehydrated.window !== compression.contextWindow) {
|
|
26027
|
+
compression.contextWindow = rehydrated.window;
|
|
26028
|
+
alog("context window", model, rehydrated.window, `(${rehydrated.source})`);
|
|
26029
|
+
}
|
|
25961
26030
|
}).catch(() => {
|
|
25962
26031
|
});
|
|
25963
26032
|
const files = new NodeFileService(cwd);
|
|
@@ -26014,9 +26083,6 @@ async function interactive(argv) {
|
|
|
26014
26083
|
const onSessionId = (id) => {
|
|
26015
26084
|
currentSessionId = id;
|
|
26016
26085
|
};
|
|
26017
|
-
const resolvedWindow = resolveContextWindow2(model);
|
|
26018
|
-
alog("context window", model, resolvedWindow.window, `(${resolvedWindow.source})`);
|
|
26019
|
-
const compression = { mode: "balanced", contextWindow: resolvedWindow.window, strategy: "deferred" };
|
|
26020
26086
|
const compaction = {
|
|
26021
26087
|
summarize: makeSummarizer(client, model),
|
|
26022
26088
|
onCheckpoint: (body) => makeCheckpointStore(() => files, sessionsRoot()).append(currentSessionId, body)
|
|
@@ -26030,13 +26096,14 @@ async function interactive(argv) {
|
|
|
26030
26096
|
model,
|
|
26031
26097
|
rates: () => modelRates,
|
|
26032
26098
|
// `/context`'s fill fraction. A getter for the reason
|
|
26033
|
-
// TermSessionDeps.contextWindow's field doc gives --
|
|
26034
|
-
//
|
|
26035
|
-
//
|
|
26036
|
-
//
|
|
26037
|
-
//
|
|
26038
|
-
//
|
|
26039
|
-
|
|
26099
|
+
// TermSessionDeps.contextWindow's field doc gives -- and, unlike a
|
|
26100
|
+
// captured literal, it reads `compression.contextWindow` LIVE: that
|
|
26101
|
+
// is the same object the price/window hydration above mutates in
|
|
26102
|
+
// place once the backend answers, so `/context` reports the
|
|
26103
|
+
// hydrated window from the very next turn on, not just what was
|
|
26104
|
+
// known synchronously at launch. Same object compaction itself is
|
|
26105
|
+
// sized from (`compression`, above), so the two can never disagree.
|
|
26106
|
+
contextWindow: () => compression.contextWindow ?? resolvedWindow.window,
|
|
26040
26107
|
system: systemSource,
|
|
26041
26108
|
client,
|
|
26042
26109
|
tools,
|
|
@@ -26052,7 +26119,8 @@ async function interactive(argv) {
|
|
|
26052
26119
|
files,
|
|
26053
26120
|
ask,
|
|
26054
26121
|
spawn: { tools, client, defaultModel: model, permission },
|
|
26055
|
-
recall
|
|
26122
|
+
recall,
|
|
26123
|
+
memory
|
|
26056
26124
|
}),
|
|
26057
26125
|
...approveAll ? { approveAll: true } : {},
|
|
26058
26126
|
policy,
|
|
@@ -26078,8 +26146,8 @@ async function interactive(argv) {
|
|
|
26078
26146
|
client,
|
|
26079
26147
|
model,
|
|
26080
26148
|
rates: () => modelRates,
|
|
26081
|
-
// See the TUI branch above -- same getter, same `
|
|
26082
|
-
contextWindow: () => resolvedWindow.window,
|
|
26149
|
+
// See the TUI branch above -- same getter, same `compression` object.
|
|
26150
|
+
contextWindow: () => compression.contextWindow ?? resolvedWindow.window,
|
|
26083
26151
|
system: systemSource,
|
|
26084
26152
|
tools,
|
|
26085
26153
|
// The readline face attaches its own PermissionService too (repl.ts) and
|
|
@@ -26090,7 +26158,8 @@ async function interactive(argv) {
|
|
|
26090
26158
|
files,
|
|
26091
26159
|
...ask ? { ask } : {},
|
|
26092
26160
|
spawn: { tools, client, defaultModel: model, permission },
|
|
26093
|
-
recall
|
|
26161
|
+
recall,
|
|
26162
|
+
memory
|
|
26094
26163
|
}),
|
|
26095
26164
|
cwd,
|
|
26096
26165
|
// --allow-mutations (or its --allow-shell alias) skips the per-command
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@corenel/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Corenel CLI — runs the harness in node, in-proc, no transport. The headless proof the kernel is host-agnostic. (corenel run/ask/chat/login + start --sidecar to follow.)",
|
|
6
6
|
"bin": {
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"openai": "^4.77.0",
|
|
11
11
|
"ws": "^8.18.0",
|
|
12
|
-
"@corenel/config": "0.
|
|
13
|
-
"@corenel/harness": "0.
|
|
14
|
-
"@corenel/
|
|
15
|
-
"@corenel/
|
|
16
|
-
"@corenel/
|
|
12
|
+
"@corenel/config": "0.2.0",
|
|
13
|
+
"@corenel/harness": "0.9.0",
|
|
14
|
+
"@corenel/protocol": "0.8.0",
|
|
15
|
+
"@corenel/mcp": "0.2.0",
|
|
16
|
+
"@corenel/term": "0.4.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"esbuild": "^0.21.5",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"tsx": "^4.19.2",
|
|
23
23
|
"typescript": "^5.9.3",
|
|
24
24
|
"@corenel/crew": "0.0.0",
|
|
25
|
-
"@corenel/tools-node": "0.
|
|
25
|
+
"@corenel/tools-node": "0.5.0"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|