@corenel/cli 0.8.0 → 0.9.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/README.md +77 -68
- package/dist/main.js +199 -68
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,68 +1,77 @@
|
|
|
1
|
-
# @corenel/cli
|
|
2
|
-
|
|
3
|
-
The Corenel CLI — runs the host-agnostic agent harness in Node, in-process (no
|
|
4
|
-
transport). Early/beta: the headless proof the kernel runs outside the browser.
|
|
5
|
-
|
|
6
|
-
```bash
|
|
7
|
-
npx @corenel/cli
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
Requires Node 22+. Bring your provider key via the environment.
|
|
11
|
-
|
|
12
|
-
Running `corenel` with no command opens an interactive agent in the current
|
|
13
|
-
directory. On a terminal it opens the full terminal face; when output is piped,
|
|
14
|
-
under `TERM=dumb`, in CI, or with `--no-tui`, it falls back to a plain
|
|
15
|
-
line-by-line loop. `--tui` demands the full face and fails rather than falling
|
|
16
|
-
back. `--resume` reopens the last conversation.
|
|
17
|
-
|
|
18
|
-
Assistant replies render as markdown. More verbs (`run` / `ask` / `chat` /
|
|
19
|
-
`login`, and `start --sidecar`) are in progress.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
1
|
+
# @corenel/cli
|
|
2
|
+
|
|
3
|
+
The Corenel CLI — runs the host-agnostic agent harness in Node, in-process (no
|
|
4
|
+
transport). Early/beta: the headless proof the kernel runs outside the browser.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx @corenel/cli
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Requires Node 22+. Bring your provider key via the environment.
|
|
11
|
+
|
|
12
|
+
Running `corenel` with no command opens an interactive agent in the current
|
|
13
|
+
directory. On a terminal it opens the full terminal face; when output is piped,
|
|
14
|
+
under `TERM=dumb`, in CI, or with `--no-tui`, it falls back to a plain
|
|
15
|
+
line-by-line loop. `--tui` demands the full face and fails rather than falling
|
|
16
|
+
back. `--resume` reopens the last conversation.
|
|
17
|
+
|
|
18
|
+
Assistant replies render as markdown. More verbs (`run` / `ask` / `chat` /
|
|
19
|
+
`login`, and `start --sidecar`) are in progress.
|
|
20
|
+
|
|
21
|
+
The API base is `--base`, then `CORENEL_API_BASE`, then the base recorded at
|
|
22
|
+
`corenel login`, then `https://api.corenel.ai/api`. Run from source
|
|
23
|
+
(`tsx src/cli.ts`), that last default becomes the local API
|
|
24
|
+
(`http://localhost:3010/api`) instead; `corenel auth` and `corenel doctor`
|
|
25
|
+
say so, and a `login` from source records the local base on its credential.
|
|
26
|
+
`--base` and `CORENEL_API_BASE` turn that off, and a recorded login base wins over
|
|
27
|
+
it; to move a credential between production and the local API, run
|
|
28
|
+
`corenel logout` before `corenel login`.
|
|
29
|
+
|
|
30
|
+
## Releasing
|
|
31
|
+
|
|
32
|
+
`@corenel/term` (the terminal face) is a real runtime dependency, not bundled
|
|
33
|
+
into `dist/main.js` -- see the note at the top of `build.mjs`. (`dist/cli.js`,
|
|
34
|
+
the bin, is only a Node >=22 check that then imports `main.js`.) `@corenel/term`'s
|
|
35
|
+
own dist is bundled but keeps `@corenel/harness` and `@corenel/protocol`
|
|
36
|
+
EXTERNAL (see `packages/term/build.mjs`) -- both the CLI and term import the
|
|
37
|
+
harness directly, and they must resolve to the SAME installed copy, or the
|
|
38
|
+
`llm-execution` feature guard and `--debug` logging silently apply to the
|
|
39
|
+
wrong module instance. That makes all four packages real runtime dependencies
|
|
40
|
+
of each other, and publish order matters:
|
|
41
|
+
|
|
42
|
+
pnpm --filter @corenel/protocol build && pnpm --filter @corenel/protocol publish
|
|
43
|
+
pnpm --filter @corenel/harness build && pnpm --filter @corenel/harness publish
|
|
44
|
+
pnpm --filter @corenel/term build && pnpm --filter @corenel/term publish
|
|
45
|
+
pnpm --filter @corenel/cli build && pnpm --filter @corenel/cli publish
|
|
46
|
+
|
|
47
|
+
Publishing any package against an unpublished dependency yields one that
|
|
48
|
+
cannot install at all, so `npx @corenel/cli` fails before any verb runs.
|
|
49
|
+
|
|
50
|
+
Publish with **pnpm, never npm**: this CLI depends on `@corenel/term` as
|
|
51
|
+
`workspace:*`, and pnpm rewrites the `workspace:` protocol to a real version at
|
|
52
|
+
pack/publish time while npm ships it verbatim -- an `npm publish` from this
|
|
53
|
+
workspace produces a manifest whose install fails with
|
|
54
|
+
`EUNSUPPORTEDPROTOCOL / Unsupported URL Type "workspace:"`, which is the very
|
|
55
|
+
breakage this section exists to prevent.
|
|
56
|
+
|
|
57
|
+
**All four versions must be bumped and published together, in the order
|
|
58
|
+
above, every release -- even one that only touches a single package.** pnpm
|
|
59
|
+
rewrites every `workspace:*` dependency to the EXACT current version of that
|
|
60
|
+
dependency at pack time. All four packages are pinned to the same harness
|
|
61
|
+
version today, so a normal `npm install` of `@corenel/cli` resolves to one
|
|
62
|
+
harness copy. Bump `@corenel/harness` and publish a new `@corenel/term`
|
|
63
|
+
without also republishing `@corenel/cli`, and the tree that installs
|
|
64
|
+
`@corenel/cli` gets the OLD harness version nested under it (frozen into the
|
|
65
|
+
manifest `@corenel/cli` published against) plus the NEW harness version
|
|
66
|
+
hoisted at the top level for `@corenel/term` -- two separate harness module
|
|
67
|
+
instances again, silently. That is exactly the bug this section's ordering
|
|
68
|
+
exists to prevent (see `harnessSingleInstance.test.ts` in this package's
|
|
69
|
+
`test/`): `registerGuard()`/`setAgentLogging()` calls made through one
|
|
70
|
+
instance configure a module the other never reads, so the `llm-execution`
|
|
71
|
+
feature gate silently stops running and `--debug` logging can't be turned
|
|
72
|
+
off. Every test in this repo stays green when that happens, because the
|
|
73
|
+
in-repo workspace is symlinked (one instance by construction) and the
|
|
74
|
+
build-output regression test only exercises that same symlinked layout --
|
|
75
|
+
neither one can see a real, version-skewed `npm install`.
|
|
76
|
+
|
|
77
|
+
Licensed under Elastic-2.0.
|
package/dist/main.js
CHANGED
|
@@ -15259,8 +15259,8 @@ var require_precompile = __commonJS({
|
|
|
15259
15259
|
fs6.readdirSync(dir).forEach(function(file) {
|
|
15260
15260
|
var filepath = path.join(dir, file);
|
|
15261
15261
|
var subpath = filepath.substr(path.join(input, "/").length);
|
|
15262
|
-
var
|
|
15263
|
-
if (
|
|
15262
|
+
var stat2 = fs6.statSync(filepath);
|
|
15263
|
+
if (stat2 && stat2.isDirectory()) {
|
|
15264
15264
|
subpath += "/";
|
|
15265
15265
|
if (!match(subpath, opts.exclude)) {
|
|
15266
15266
|
addTemplates(filepath);
|
|
@@ -16439,8 +16439,8 @@ ${yamlContent}` : yamlContent;
|
|
|
16439
16439
|
if (resolvedInheritsRef.startsWith("./") || resolvedInheritsRef.startsWith("../") || resolvedInheritsRef.startsWith("/") || !resolvedInheritsRef.startsWith("@") && isPrompdFile(resolvedInheritsRef)) {
|
|
16440
16440
|
const sourceDir = context.fileSystem.dirname(context.sourceFile);
|
|
16441
16441
|
const parentPath = context.fileSystem.resolve(sourceDir, resolvedInheritsRef);
|
|
16442
|
-
const
|
|
16443
|
-
if (!
|
|
16442
|
+
const fileExists2 = await context.fileSystem.exists(parentPath);
|
|
16443
|
+
if (!fileExists2) {
|
|
16444
16444
|
const location = context.findLocation(/inherits:/);
|
|
16445
16445
|
context.addDiagnostic({
|
|
16446
16446
|
message: `Inherited file not found: "${resolvedInheritsRef}" (resolved to: ${parentPath})`,
|
|
@@ -16467,8 +16467,8 @@ ${yamlContent}` : yamlContent;
|
|
|
16467
16467
|
}
|
|
16468
16468
|
const packagePath = await this.resolvePackage(context, packageRef);
|
|
16469
16469
|
const fullFilePath = resolvePackageFile(packagePath, filePathInPackage);
|
|
16470
|
-
const
|
|
16471
|
-
if (!
|
|
16470
|
+
const fileExists2 = await context.fileSystem.exists(fullFilePath);
|
|
16471
|
+
if (!fileExists2) {
|
|
16472
16472
|
const location = context.findLocation(/inherits:/);
|
|
16473
16473
|
context.addDiagnostic({
|
|
16474
16474
|
message: `Inherited file not found in package: "${filePathInPackage}" (package: ${packageRef})`,
|
|
@@ -22704,7 +22704,7 @@ var require_websocket_server = __commonJS({
|
|
|
22704
22704
|
init_tools_node();
|
|
22705
22705
|
import { runAgent as runAgent2 } from "@corenel/harness/core/loop";
|
|
22706
22706
|
import { createChatClient as createChatClient2, setGatewayBase as setGatewayBase2 } from "@corenel/harness/providers/gateway";
|
|
22707
|
-
import { resolveContextWindow as
|
|
22707
|
+
import { resolveContextWindow as resolveContextWindow3, resolveContextWindowWithHydration } from "@corenel/harness/providers/contextWindow";
|
|
22708
22708
|
import { basename as basename5 } from "node:path";
|
|
22709
22709
|
import { randomUUID } from "node:crypto";
|
|
22710
22710
|
import { allTools as allTools3 } from "@corenel/harness/tools/builtins";
|
|
@@ -22721,9 +22721,9 @@ function configWorkspaceRoot(base) {
|
|
|
22721
22721
|
|
|
22722
22722
|
// src/cli.ts
|
|
22723
22723
|
import { FileRecallStore, UNATTACHED_SESSION } from "@corenel/harness/sessions/fileRecallStore";
|
|
22724
|
-
import {
|
|
22724
|
+
import { makeSessionFoldStorage } from "@corenel/harness/sessions/foldStorage";
|
|
22725
22725
|
import { makeSummarizer } from "@corenel/harness/core/summarize";
|
|
22726
|
-
import { stateDirName as stateDirName7, sessionsRoot } from "@corenel/protocol";
|
|
22726
|
+
import { stateDirName as stateDirName7, sessionsRoot, isSourceRun } from "@corenel/protocol";
|
|
22727
22727
|
import { PermissionService as PermissionService6 } from "@corenel/harness/guardrail/permission-service";
|
|
22728
22728
|
|
|
22729
22729
|
// src/nodeContext.ts
|
|
@@ -24918,6 +24918,7 @@ import { createChatClient, setGatewayBase } from "@corenel/harness/providers/gat
|
|
|
24918
24918
|
// src/apiBase.ts
|
|
24919
24919
|
init_tools_node();
|
|
24920
24920
|
import { DEFAULT_API_BASE as DEFAULT_API_BASE2 } from "@corenel/protocol";
|
|
24921
|
+
import { DEV_PROFILE } from "@corenel/protocol";
|
|
24921
24922
|
var InvalidApiBaseError = class extends Error {
|
|
24922
24923
|
constructor(value, why) {
|
|
24923
24924
|
super(`invalid API base ${JSON.stringify(value)} -- ${why}`);
|
|
@@ -24931,12 +24932,34 @@ function isLoopback(hostname2) {
|
|
|
24931
24932
|
return h === "localhost" || h === "127.0.0.1" || h === "::1" || h.endsWith(".localhost");
|
|
24932
24933
|
}
|
|
24933
24934
|
var warnedCleartext = false;
|
|
24935
|
+
var sourceRunDefault = false;
|
|
24936
|
+
function setSourceRun(sourceRun) {
|
|
24937
|
+
sourceRunDefault = sourceRun;
|
|
24938
|
+
}
|
|
24939
|
+
function applySourceRunDevBase(resolved, sourceRun) {
|
|
24940
|
+
if (!sourceRun || resolved.source !== "default") return resolved;
|
|
24941
|
+
return { base: DEV_PROFILE.apiBase, source: "dev-profile" };
|
|
24942
|
+
}
|
|
24943
|
+
function cliDevRelayBases(resolved, sourceRun = sourceRunDefault) {
|
|
24944
|
+
if (resolved.source === "dev-profile") return [resolved.base];
|
|
24945
|
+
if (sourceRun && resolved.source === "credential" && resolved.base.replace(/\/+$/, "") === DEV_PROFILE.apiBase) return [resolved.base];
|
|
24946
|
+
return [];
|
|
24947
|
+
}
|
|
24948
|
+
function apiBaseNote(source) {
|
|
24949
|
+
return source === "dev-profile" ? "dev profile: running from source" : void 0;
|
|
24950
|
+
}
|
|
24934
24951
|
async function apiBase(explicit, opts = {}) {
|
|
24935
|
-
|
|
24936
|
-
|
|
24937
|
-
|
|
24938
|
-
|
|
24939
|
-
|
|
24952
|
+
return (await resolveCliApiBase(explicit, opts)).base;
|
|
24953
|
+
}
|
|
24954
|
+
async function resolveCliApiBase(explicit, opts = {}) {
|
|
24955
|
+
const resolved = applySourceRunDevBase(
|
|
24956
|
+
await resolveApiBase({
|
|
24957
|
+
explicit,
|
|
24958
|
+
env: opts.env ?? process.env,
|
|
24959
|
+
...opts.stateDir ? { stateDir: opts.stateDir } : {}
|
|
24960
|
+
}),
|
|
24961
|
+
opts.sourceRun ?? sourceRunDefault
|
|
24962
|
+
);
|
|
24940
24963
|
const value = resolved.base;
|
|
24941
24964
|
let url;
|
|
24942
24965
|
try {
|
|
@@ -24954,7 +24977,7 @@ async function apiBase(explicit, opts = {}) {
|
|
|
24954
24977
|
`
|
|
24955
24978
|
);
|
|
24956
24979
|
}
|
|
24957
|
-
return
|
|
24980
|
+
return resolved;
|
|
24958
24981
|
}
|
|
24959
24982
|
|
|
24960
24983
|
// src/runAgent.ts
|
|
@@ -25270,7 +25293,9 @@ function helpText(version2) {
|
|
|
25270
25293
|
" --model <id> model for this turn (default gpt-4o-mini)",
|
|
25271
25294
|
" --base <url> API base (default https://api.corenel.ai/api, or CORENEL_API_BASE)",
|
|
25272
25295
|
" --policy <name|path> a builtin policy name (e.g. read-only) or a policy YAML file;",
|
|
25273
|
-
" default is
|
|
25296
|
+
" default is the per-project policy (policies/ws-<id>.yaml in your home",
|
|
25297
|
+
" state dir), else the user policy.yaml there, else standard. A",
|
|
25298
|
+
" policy.yaml inside the project folder is ignored.",
|
|
25274
25299
|
" --allow-mutations allow write/shell/other mutating tools (run/run-agent refuse them",
|
|
25275
25300
|
" outright without this; interactively it skips the per-command",
|
|
25276
25301
|
" prompt instead). --allow-shell still works as an alias.",
|
|
@@ -25355,23 +25380,27 @@ async function authState(resolve4 = (env2) => resolveNodeCredential({ env: env2
|
|
|
25355
25380
|
const source = r.source === "env" ? "env" : "file";
|
|
25356
25381
|
return { source, path: r.path ?? authTokenPath(), hint: hintOf(r.token) };
|
|
25357
25382
|
}
|
|
25358
|
-
function describeAuth(s, base) {
|
|
25383
|
+
function describeAuth(s, base, source) {
|
|
25384
|
+
const note = source ? apiBaseNote(source) : void 0;
|
|
25385
|
+
const apiLine = ` api ${base}${note ? ` (${note})` : ""}`;
|
|
25359
25386
|
if (s.source === "none") {
|
|
25360
25387
|
return [
|
|
25361
25388
|
"Not signed in.",
|
|
25362
25389
|
` Run \`corenel login\`, or set CORENEL_TOKEN=<token>.`,
|
|
25363
|
-
` Token would be stored at ${s.path}
|
|
25364
|
-
|
|
25390
|
+
` Token would be stored at ${s.path}`,
|
|
25391
|
+
note ? apiLine : null
|
|
25392
|
+
].filter(Boolean).join("\n");
|
|
25365
25393
|
}
|
|
25366
25394
|
return [
|
|
25367
25395
|
`Signed in (${s.source === "env" ? "CORENEL_TOKEN environment variable" : s.path}).`,
|
|
25368
25396
|
` token ${s.hint}`,
|
|
25369
|
-
|
|
25397
|
+
apiLine,
|
|
25370
25398
|
s.source === "env" ? " NOTE: the environment wins over any stored token, so `corenel logout` will not change this." : null
|
|
25371
25399
|
].filter(Boolean).join("\n");
|
|
25372
25400
|
}
|
|
25373
25401
|
async function runAuth(out = (s) => process.stdout.write(s)) {
|
|
25374
|
-
|
|
25402
|
+
const { base, source } = await resolveCliApiBase();
|
|
25403
|
+
out(`${describeAuth(await authState(), base, source)}
|
|
25375
25404
|
`);
|
|
25376
25405
|
}
|
|
25377
25406
|
async function runLogout(out = (s) => process.stdout.write(s)) {
|
|
@@ -25721,17 +25750,19 @@ async function checkSidecarEnv(base, env, stateDir = corenelDir()) {
|
|
|
25721
25750
|
return { name: "sidecar", status: "fail", detail: `${base} (env: ${env}) -- ${e instanceof Error ? e.message : String(e)}` };
|
|
25722
25751
|
}
|
|
25723
25752
|
}
|
|
25753
|
+
function apiCheck(resolved) {
|
|
25754
|
+
const note = apiBaseNote(resolved.source);
|
|
25755
|
+
const detail = note ? `${resolved.base} (${note}; installed default is ${DEFAULT_API_BASE2})` : resolved.base === DEFAULT_API_BASE2 ? resolved.base : `${resolved.base} (overridden; default is ${DEFAULT_API_BASE2})`;
|
|
25756
|
+
return { name: "api", status: "ok", detail };
|
|
25757
|
+
}
|
|
25724
25758
|
async function collectChecks(f = fetch, modelId = DEFAULT_MODEL, env) {
|
|
25725
|
-
const
|
|
25759
|
+
const resolvedBase = await resolveCliApiBase();
|
|
25760
|
+
const base = resolvedBase.base;
|
|
25726
25761
|
const checks = [
|
|
25727
25762
|
{ name: "binary", status: "ok", detail: selfPath() },
|
|
25728
25763
|
{ name: "version", status: "ok", detail: `@corenel/cli ${version()}` },
|
|
25729
25764
|
{ name: "node", status: "ok", detail: process.version },
|
|
25730
|
-
|
|
25731
|
-
name: "api",
|
|
25732
|
-
status: "ok",
|
|
25733
|
-
detail: base === DEFAULT_API_BASE2 ? base : `${base} (overridden; default is ${DEFAULT_API_BASE2})`
|
|
25734
|
-
}
|
|
25765
|
+
apiCheck(resolvedBase)
|
|
25735
25766
|
];
|
|
25736
25767
|
try {
|
|
25737
25768
|
const u = new URL(base);
|
|
@@ -25779,7 +25810,7 @@ import { createInterface } from "node:readline";
|
|
|
25779
25810
|
import { PermissionService as PermissionService4 } from "@corenel/harness/guardrail/permission-service";
|
|
25780
25811
|
import { STANDARD_POLICY as STANDARD_POLICY2 } from "@corenel/harness/guardrail/builtins";
|
|
25781
25812
|
import { approvalsHooks } from "@corenel/harness/approvals";
|
|
25782
|
-
import { TermSession, parseSuggestionChoice, suggestionKeys, suggestionLine, suggestionOutcome } from "@corenel/term/session";
|
|
25813
|
+
import { TermSession, startupResumeOptions, parseSuggestionChoice, suggestionKeys, suggestionLine, suggestionOutcome } from "@corenel/term/session";
|
|
25783
25814
|
import { describeToolCall, formatOrd } from "@corenel/term/describe";
|
|
25784
25815
|
import { buildWritePreview } from "@corenel/term/writePreview";
|
|
25785
25816
|
import { formatEndingChange } from "@corenel/term/diff";
|
|
@@ -25937,10 +25968,10 @@ async function runRepl(deps) {
|
|
|
25937
25968
|
}
|
|
25938
25969
|
});
|
|
25939
25970
|
if (deps.resume) {
|
|
25940
|
-
await session.resume(deps.resume.id);
|
|
25971
|
+
await session.resume(deps.resume.id, startupResumeOptions(deps.resume));
|
|
25941
25972
|
const restored = session.getSnapshot();
|
|
25942
25973
|
if (restored.session) {
|
|
25943
|
-
writeLine(` resumed ${restored.session.id} - ${restored.committed.length} messages
|
|
25974
|
+
writeLine(` resumed ${restored.session.id} - ${restored.committed.filter((i) => i.kind !== "note").length} messages
|
|
25944
25975
|
`);
|
|
25945
25976
|
}
|
|
25946
25977
|
}
|
|
@@ -26227,6 +26258,44 @@ ${rules(r.workspace)}`);
|
|
|
26227
26258
|
import { approvalsHooks as approvalsHooks2 } from "@corenel/harness/approvals";
|
|
26228
26259
|
import { createSessionPersistence } from "@corenel/term/persist";
|
|
26229
26260
|
|
|
26261
|
+
// src/resumeModel.ts
|
|
26262
|
+
import { estimateFill, COMPACT_AT_FILL } from "@corenel/harness/core/compactHistory";
|
|
26263
|
+
import { resolveContextWindow as resolveContextWindow2 } from "@corenel/harness/providers/contextWindow";
|
|
26264
|
+
var DEFAULT_MODEL2 = "gpt-4o-mini";
|
|
26265
|
+
function resumeModel(flagModel, stored, fallback) {
|
|
26266
|
+
return flagModel || stored || fallback;
|
|
26267
|
+
}
|
|
26268
|
+
async function storedModelOf(resume, persistence) {
|
|
26269
|
+
if (!resume) return void 0;
|
|
26270
|
+
const loaded = await persistence.load(resume.id);
|
|
26271
|
+
if (!loaded) return void 0;
|
|
26272
|
+
return { id: loaded.id, messages: loaded.messages, ...loaded.model ? { model: loaded.model } : {} };
|
|
26273
|
+
}
|
|
26274
|
+
async function resolveSessionModel(input) {
|
|
26275
|
+
const { argv, persistence, fallback, isKnownModel } = input;
|
|
26276
|
+
const windowOf = input.contextWindow ?? ((m) => resolveContextWindow2(m).window);
|
|
26277
|
+
const flagModel = hasValueFlag(argv, "model") ? flag(argv, "model", "") || void 0 : void 0;
|
|
26278
|
+
const resume = resumeFlag(argv);
|
|
26279
|
+
let chat;
|
|
26280
|
+
try {
|
|
26281
|
+
chat = await storedModelOf(resume, persistence);
|
|
26282
|
+
} catch {
|
|
26283
|
+
const model = flagModel ?? fallback;
|
|
26284
|
+
return { model, notice: `could not read this chat's model, so it will run on ${model}.` };
|
|
26285
|
+
}
|
|
26286
|
+
const stored = chat?.model;
|
|
26287
|
+
if (flagModel) {
|
|
26288
|
+
if (!chat || !stored || stored === flagModel) return { model: flagModel };
|
|
26289
|
+
const folds = estimateFill(chat.messages, windowOf(flagModel)) >= COMPACT_AT_FILL;
|
|
26290
|
+
const notice = `chat ${chat.id} was on ${stored}; continuing on ${flagModel} (this changes the chat's model)` + (folds ? ", and older history will be folded to fit." : ".");
|
|
26291
|
+
return { model: flagModel, notice };
|
|
26292
|
+
}
|
|
26293
|
+
if (stored && isKnownModel && !isKnownModel(stored)) {
|
|
26294
|
+
return { model: fallback, notice: `this chat used ${stored}, which is not available. It will run on ${fallback}.` };
|
|
26295
|
+
}
|
|
26296
|
+
return { model: resumeModel(void 0, stored, fallback) };
|
|
26297
|
+
}
|
|
26298
|
+
|
|
26230
26299
|
// src/shellTool.ts
|
|
26231
26300
|
import { spawn as spawn3 } from "node:child_process";
|
|
26232
26301
|
var MAX_OUTPUT = 3e4;
|
|
@@ -27031,8 +27100,8 @@ async function resolveStrategyPrompt(files, id) {
|
|
|
27031
27100
|
}
|
|
27032
27101
|
|
|
27033
27102
|
// src/policyResolve.ts
|
|
27034
|
-
|
|
27035
|
-
import {
|
|
27103
|
+
init_tools_node();
|
|
27104
|
+
import { readFile as readFile2, stat } from "node:fs/promises";
|
|
27036
27105
|
import { join as join11 } from "node:path";
|
|
27037
27106
|
import { stateDirName as stateDirName6 } from "@corenel/protocol";
|
|
27038
27107
|
import { BUILTIN_POLICIES, STANDARD_POLICY as STANDARD_POLICY3, policyFromYaml } from "@corenel/harness/guardrail";
|
|
@@ -27081,25 +27150,55 @@ function validateOrThrow(text, path) {
|
|
|
27081
27150
|
}
|
|
27082
27151
|
return result.policy;
|
|
27083
27152
|
}
|
|
27153
|
+
async function projectPolicyPath(cwd, homeBase) {
|
|
27154
|
+
return join11(corenelDir(homeBase), "policies", `ws-${await workspaceApprovalsId(cwd)}.yaml`);
|
|
27155
|
+
}
|
|
27156
|
+
async function fileExists(path) {
|
|
27157
|
+
try {
|
|
27158
|
+
await stat(path);
|
|
27159
|
+
return true;
|
|
27160
|
+
} catch (e) {
|
|
27161
|
+
return !(isErrnoException(e) && e.code === "ENOENT");
|
|
27162
|
+
}
|
|
27163
|
+
}
|
|
27084
27164
|
async function resolvePolicy(opts) {
|
|
27085
|
-
const { flag: flag2, cwd } = opts;
|
|
27165
|
+
const { flag: flag2, cwd, homeBase } = opts;
|
|
27166
|
+
const warnings = [];
|
|
27086
27167
|
if (flag2) {
|
|
27087
27168
|
if (Object.prototype.hasOwnProperty.call(BUILTIN_POLICIES, flag2)) {
|
|
27088
|
-
return { policy: BUILTIN_POLICIES[flag2], source: { kind: "builtin", name: flag2 } };
|
|
27169
|
+
return { policy: BUILTIN_POLICIES[flag2], source: { kind: "builtin", name: flag2 }, warnings };
|
|
27089
27170
|
}
|
|
27090
27171
|
const policy = await loadRequiredPolicyFile(
|
|
27091
27172
|
flag2,
|
|
27092
27173
|
`corenel: unknown --policy "${flag2}" -- not a builtin (${Object.keys(BUILTIN_POLICIES).join(", ")}) and no file exists at that path.`
|
|
27093
27174
|
);
|
|
27094
|
-
return { policy, source: { kind: "file", path: flag2 } };
|
|
27175
|
+
return { policy, source: { kind: "file", path: flag2 }, warnings };
|
|
27176
|
+
}
|
|
27177
|
+
const stateDir = corenelDir(homeBase);
|
|
27178
|
+
const projectPath = await projectPolicyPath(cwd, homeBase);
|
|
27179
|
+
const userPath = join11(stateDir, "policy.yaml");
|
|
27180
|
+
if (await insideAnyRoot([cwd], stateDir) !== null) {
|
|
27181
|
+
const present2 = [];
|
|
27182
|
+
if (await fileExists(projectPath)) present2.push(projectPath);
|
|
27183
|
+
if (await fileExists(userPath)) present2.push(userPath);
|
|
27184
|
+
if (present2.length) {
|
|
27185
|
+
throw new PolicyResolutionError(
|
|
27186
|
+
`corenel: not loading ${present2.join(" or ")}: ${stateDir} is inside this folder (${cwd}), where the agent's file tools could have written it. Start corenel from a folder that does not contain it, or pass --policy <name|path> explicitly.`
|
|
27187
|
+
);
|
|
27188
|
+
}
|
|
27189
|
+
return { policy: STANDARD_POLICY3, source: { kind: "default" }, warnings };
|
|
27190
|
+
}
|
|
27191
|
+
const workspacePath = join11(cwd, stateDirName6(), "policy.yaml");
|
|
27192
|
+
if (await fileExists(workspacePath)) {
|
|
27193
|
+
warnings.push(
|
|
27194
|
+
`corenel: ${workspacePath} ignored -- a policy inside the workspace can be written by the agent or arrive in a cloned repo. To use it for this folder, move it to ${projectPath}, or pass --policy ${workspacePath} explicitly.`
|
|
27195
|
+
);
|
|
27095
27196
|
}
|
|
27096
|
-
const projectPath = join11(cwd, stateDirName6(), "policy.yaml");
|
|
27097
27197
|
const project = await loadDiscoveredPolicyFile(projectPath);
|
|
27098
|
-
if (project) return { policy: project, source: { kind: "project", path: projectPath } };
|
|
27099
|
-
const userPath = join11(homedir3(), stateDirName6(), "policy.yaml");
|
|
27198
|
+
if (project) return { policy: project, source: { kind: "project", path: projectPath }, warnings };
|
|
27100
27199
|
const user = await loadDiscoveredPolicyFile(userPath);
|
|
27101
|
-
if (user) return { policy: user, source: { kind: "user", path: userPath } };
|
|
27102
|
-
return { policy: STANDARD_POLICY3, source: { kind: "default" } };
|
|
27200
|
+
if (user) return { policy: user, source: { kind: "user", path: userPath }, warnings };
|
|
27201
|
+
return { policy: STANDARD_POLICY3, source: { kind: "default" }, warnings };
|
|
27103
27202
|
}
|
|
27104
27203
|
|
|
27105
27204
|
// ../mcp/src/httpClient.ts
|
|
@@ -27572,7 +27671,7 @@ function isExpired(cred, nowMs) {
|
|
|
27572
27671
|
}
|
|
27573
27672
|
|
|
27574
27673
|
// ../mcp/src/auth.ts
|
|
27575
|
-
import { DEFAULT_API_BASE as
|
|
27674
|
+
import { DEFAULT_API_BASE as DEFAULT_API_BASE4 } from "@corenel/protocol";
|
|
27576
27675
|
function safeOrigin(url) {
|
|
27577
27676
|
try {
|
|
27578
27677
|
return new URL(url).origin;
|
|
@@ -27580,24 +27679,30 @@ function safeOrigin(url) {
|
|
|
27580
27679
|
return "its URL";
|
|
27581
27680
|
}
|
|
27582
27681
|
}
|
|
27583
|
-
function isCorenelRelayUrl(url, bases) {
|
|
27682
|
+
function isCorenelRelayUrl(url, bases, devApiBases = []) {
|
|
27584
27683
|
let u;
|
|
27585
27684
|
try {
|
|
27586
27685
|
u = new URL(url);
|
|
27587
27686
|
} catch {
|
|
27588
27687
|
return false;
|
|
27589
27688
|
}
|
|
27590
|
-
if (u.protocol !== "https:") return false;
|
|
27591
27689
|
if (!/\/mcp\/hosted\/[^/]+\/?$/.test(u.pathname)) return false;
|
|
27592
|
-
|
|
27690
|
+
const sameOrigin = (b) => {
|
|
27593
27691
|
try {
|
|
27594
27692
|
return new URL(b).origin === u.origin;
|
|
27595
27693
|
} catch {
|
|
27596
27694
|
return false;
|
|
27597
27695
|
}
|
|
27598
|
-
}
|
|
27696
|
+
};
|
|
27697
|
+
if (u.protocol === "https:") return [...bases, ...devApiBases].some(sameOrigin);
|
|
27698
|
+
if (u.protocol === "http:" && isLoopbackHost(u.hostname)) return devApiBases.some(sameOrigin);
|
|
27699
|
+
return false;
|
|
27700
|
+
}
|
|
27701
|
+
function isLoopbackHost(hostname2) {
|
|
27702
|
+
const h = hostname2.replace(/^\[|\]$/g, "").toLowerCase();
|
|
27703
|
+
return h === "localhost" || h === "127.0.0.1" || h === "::1";
|
|
27599
27704
|
}
|
|
27600
|
-
function authProviderFor(stateDir, server, fetchImpl = fetch, accountToken = async () => null, relayBases = async () => []) {
|
|
27705
|
+
function authProviderFor(stateDir, server, fetchImpl = fetch, accountToken = async () => null, relayBases = async () => [], devApiBases = []) {
|
|
27601
27706
|
async function doRefresh(cred) {
|
|
27602
27707
|
if (!cred.refreshToken) {
|
|
27603
27708
|
throw new Error(`"${server.name}" has no refresh token on file -- run: corenel mcp login ${server.name}`);
|
|
@@ -27610,7 +27715,7 @@ function authProviderFor(stateDir, server, fetchImpl = fetch, accountToken = asy
|
|
|
27610
27715
|
return {
|
|
27611
27716
|
async headers() {
|
|
27612
27717
|
const cred = await readMcpCredential(stateDir, server.name);
|
|
27613
|
-
if (server.hosted && !isCorenelRelayUrl(server.url, [
|
|
27718
|
+
if (server.hosted && !isCorenelRelayUrl(server.url, [DEFAULT_API_BASE4, ...await relayBases()], devApiBases)) {
|
|
27614
27719
|
throw new Error(`"${server.name}" is marked hosted but ${safeOrigin(server.url)} is not a Corenel relay; refusing to send the account token there.`);
|
|
27615
27720
|
}
|
|
27616
27721
|
if (!cred && !server.hosted) {
|
|
@@ -27661,10 +27766,17 @@ async function accountTokenFor(stateDir) {
|
|
|
27661
27766
|
return null;
|
|
27662
27767
|
}
|
|
27663
27768
|
}
|
|
27769
|
+
function mcpClientFactory(opts = {}) {
|
|
27770
|
+
const devApiBases = [...opts.devApiBases ?? []];
|
|
27771
|
+
return (stateDir, server) => buildClient(stateDir, server, devApiBases);
|
|
27772
|
+
}
|
|
27664
27773
|
function defaultClientFactory(stateDir, server) {
|
|
27774
|
+
return buildClient(stateDir, server, []);
|
|
27775
|
+
}
|
|
27776
|
+
function buildClient(stateDir, server, devApiBases) {
|
|
27665
27777
|
return new McpHttpClient(
|
|
27666
27778
|
server,
|
|
27667
|
-
authProviderFor(stateDir, server, timedFetch(AUTH_REQUEST_TIMEOUT_MS), () => accountTokenFor(stateDir), () => relayBasesFor(stateDir))
|
|
27779
|
+
authProviderFor(stateDir, server, timedFetch(AUTH_REQUEST_TIMEOUT_MS), () => accountTokenFor(stateDir), () => relayBasesFor(stateDir), devApiBases)
|
|
27668
27780
|
);
|
|
27669
27781
|
}
|
|
27670
27782
|
async function relayBasesFor(stateDir) {
|
|
@@ -28002,11 +28114,11 @@ function cacheEntryFor(cache, server, cred) {
|
|
|
28002
28114
|
}
|
|
28003
28115
|
|
|
28004
28116
|
// src/mcpRegistry.ts
|
|
28005
|
-
async function openCliMcp(stateDir, policy) {
|
|
28117
|
+
async function openCliMcp(stateDir, policy, opts = {}) {
|
|
28006
28118
|
try {
|
|
28007
28119
|
const servers = await readMcpServers(stateDir);
|
|
28008
28120
|
if (servers.length === 0) return null;
|
|
28009
|
-
const registry = new McpRegistry(stateDir);
|
|
28121
|
+
const registry = new McpRegistry(stateDir, mcpClientFactory({ devApiBases: opts.devApiBases ?? [] }));
|
|
28010
28122
|
const cache = await readToolCache(stateDir);
|
|
28011
28123
|
const usable = {};
|
|
28012
28124
|
for (const s of servers) {
|
|
@@ -28399,8 +28511,18 @@ import { lookup as dnsLookup } from "node:dns/promises";
|
|
|
28399
28511
|
import { setOutboundResolver } from "@corenel/harness/tools/urlGuard";
|
|
28400
28512
|
var EXIT_POLICY_PARK_REFUSED = 3;
|
|
28401
28513
|
var EXIT_BUDGET_EXCEEDED = 4;
|
|
28402
|
-
|
|
28403
|
-
|
|
28514
|
+
function replSessionId(id) {
|
|
28515
|
+
return id === UNATTACHED_SESSION ? null : id;
|
|
28516
|
+
}
|
|
28517
|
+
function replCompaction(client, model, contextWindow, files, getSessionId) {
|
|
28518
|
+
return {
|
|
28519
|
+
summarize: makeSummarizer(client, model, { contextWindow }),
|
|
28520
|
+
storage: makeSessionFoldStorage({ getFiles: () => files, root: sessionsRoot(), sessionId: getSessionId })
|
|
28521
|
+
};
|
|
28522
|
+
}
|
|
28523
|
+
async function agentSetup(argv, chosenModel) {
|
|
28524
|
+
const gatewayBase = await resolveCliApiBase(flag(argv, "base", ""));
|
|
28525
|
+
setGatewayBase2(gatewayBase.base);
|
|
28404
28526
|
const token = await nodeAuthToken().getToken();
|
|
28405
28527
|
if (!token) {
|
|
28406
28528
|
process.stderr.write("corenel: not signed in.\n Run `corenel login`, or set CORENEL_TOKEN=<token> (a gateway bearer token), then try again.\n");
|
|
@@ -28442,19 +28564,22 @@ async function agentSetup(argv) {
|
|
|
28442
28564
|
let policy;
|
|
28443
28565
|
let policySource;
|
|
28444
28566
|
try {
|
|
28445
|
-
|
|
28567
|
+
const resolved = await resolvePolicy({
|
|
28446
28568
|
...hasValueFlag(argv, "policy") ? { flag: flag(argv, "policy", "") } : {},
|
|
28447
28569
|
cwd: process.cwd()
|
|
28448
|
-
})
|
|
28570
|
+
});
|
|
28571
|
+
({ policy, source: policySource } = resolved);
|
|
28572
|
+
for (const w of resolved.warnings) process.stderr.write(`${w}
|
|
28573
|
+
`);
|
|
28449
28574
|
} catch (e) {
|
|
28450
28575
|
sidecarProcs?.disconnect();
|
|
28451
28576
|
throw e;
|
|
28452
28577
|
}
|
|
28453
|
-
const mcp = await openCliMcp(configWorkspaceRoot(), policy);
|
|
28578
|
+
const mcp = await openCliMcp(configWorkspaceRoot(), policy, { devApiBases: cliDevRelayBases(gatewayBase) });
|
|
28454
28579
|
if (mcp) tools.push(...mcp.tools);
|
|
28455
28580
|
return {
|
|
28456
28581
|
client: createChatClient2({ getToken: async () => token }),
|
|
28457
|
-
model: flag(argv, "model",
|
|
28582
|
+
model: chosenModel ?? flag(argv, "model", DEFAULT_MODEL2),
|
|
28458
28583
|
system,
|
|
28459
28584
|
tools,
|
|
28460
28585
|
identity: identity2,
|
|
@@ -28558,10 +28683,10 @@ async function run(argv) {
|
|
|
28558
28683
|
* with a real window buys exactly that awareness without turning on
|
|
28559
28684
|
* rung-1 offload here (this path builds no recall store).
|
|
28560
28685
|
*
|
|
28561
|
-
* No
|
|
28686
|
+
* No fold storage: a one-shot run has no session to write one beside.
|
|
28562
28687
|
* It still compacts — surviving the wall matters more than recording why. */
|
|
28563
|
-
compression: { mode: "off", contextWindow:
|
|
28564
|
-
compaction: { summarize: makeSummarizer(client, model) },
|
|
28688
|
+
compression: { mode: "off", contextWindow: resolveContextWindow3(model).window },
|
|
28689
|
+
compaction: { summarize: makeSummarizer(client, model, { contextWindow: resolveContextWindow3(model).window }) },
|
|
28565
28690
|
// A spawned sub-agent runs under the SAME gate as the parent turn -- never
|
|
28566
28691
|
// looser, so it can't be used to route around the default-deny above.
|
|
28567
28692
|
toolCtx: nodeToolCtx(ac.signal, {
|
|
@@ -28599,7 +28724,12 @@ async function run(argv) {
|
|
|
28599
28724
|
}
|
|
28600
28725
|
}
|
|
28601
28726
|
async function interactive(argv) {
|
|
28602
|
-
const
|
|
28727
|
+
const chosen = await resolveSessionModel({
|
|
28728
|
+
argv,
|
|
28729
|
+
persistence: createSessionPersistence(new NodeFileService(process.cwd())),
|
|
28730
|
+
fallback: DEFAULT_MODEL2
|
|
28731
|
+
});
|
|
28732
|
+
const { client, model, tools, identity: identity2, policy, policySource, memory, mcp, disconnect } = await agentSetup(argv, chosen.model);
|
|
28603
28733
|
try {
|
|
28604
28734
|
await runInteractive();
|
|
28605
28735
|
} finally {
|
|
@@ -28614,7 +28744,7 @@ async function interactive(argv) {
|
|
|
28614
28744
|
const approvalsContext = { runId: randomUUID(), workspace: basename5(cwd) };
|
|
28615
28745
|
let modelRates = null;
|
|
28616
28746
|
let priceTable = /* @__PURE__ */ new Map();
|
|
28617
|
-
const resolvedWindow =
|
|
28747
|
+
const resolvedWindow = resolveContextWindow3(model);
|
|
28618
28748
|
alog("context window", model, resolvedWindow.window, `(${resolvedWindow.source})`);
|
|
28619
28749
|
const compression = { mode: "balanced", contextWindow: resolvedWindow.window, strategy: "deferred" };
|
|
28620
28750
|
const priceBase = await apiBase(flag(argv, "base", ""));
|
|
@@ -28682,11 +28812,9 @@ async function interactive(argv) {
|
|
|
28682
28812
|
const onSessionId = (id) => {
|
|
28683
28813
|
currentSessionId = id;
|
|
28684
28814
|
};
|
|
28685
|
-
const compaction =
|
|
28686
|
-
|
|
28687
|
-
|
|
28688
|
-
};
|
|
28689
|
-
const resume = resumeFlag(argv);
|
|
28815
|
+
const compaction = replCompaction(client, model, () => compression.contextWindow, files, () => replSessionId(currentSessionId));
|
|
28816
|
+
const resumeBase = resumeFlag(argv);
|
|
28817
|
+
const resume = resumeBase ? { ...resumeBase, ...chosen.notice ? { notice: chosen.notice } : {} } : void 0;
|
|
28690
28818
|
if (chooseFace(currentFaceEnv(argv)) === "tui") {
|
|
28691
28819
|
try {
|
|
28692
28820
|
const { runTui } = await import("@corenel/term/ink");
|
|
@@ -28785,6 +28913,7 @@ async function interactive(argv) {
|
|
|
28785
28913
|
}
|
|
28786
28914
|
async function main() {
|
|
28787
28915
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
28916
|
+
setSourceRun(isSourceRun(import.meta.url));
|
|
28788
28917
|
setOutboundResolver(async (h) => (await dnsLookup(h, { all: true, verbatim: true })).map((a) => a.address));
|
|
28789
28918
|
try {
|
|
28790
28919
|
setAgentLogging(hasBooleanFlag(process.argv, "debug") || process.env.CORENEL_DEBUG === "1");
|
|
@@ -28862,7 +28991,7 @@ Logged in. Token saved to ${authTokenPath()}
|
|
|
28862
28991
|
await runLogout();
|
|
28863
28992
|
break;
|
|
28864
28993
|
case "doctor": {
|
|
28865
|
-
process.exitCode = await runDoctor(void 0, void 0, flag(rest, "model",
|
|
28994
|
+
process.exitCode = await runDoctor(void 0, void 0, flag(rest, "model", DEFAULT_MODEL2), resolveDoctorEnv(rest));
|
|
28866
28995
|
break;
|
|
28867
28996
|
}
|
|
28868
28997
|
case "approvals":
|
|
@@ -28935,5 +29064,7 @@ main().catch((e) => {
|
|
|
28935
29064
|
export {
|
|
28936
29065
|
EXIT_BUDGET_EXCEEDED,
|
|
28937
29066
|
EXIT_POLICY_PARK_REFUSED,
|
|
28938
|
-
buildRunPermission
|
|
29067
|
+
buildRunPermission,
|
|
29068
|
+
replCompaction,
|
|
29069
|
+
replSessionId
|
|
28939
29070
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@corenel/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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.6.
|
|
13
|
-
"@corenel/
|
|
14
|
-
"@corenel/
|
|
15
|
-
"@corenel/
|
|
16
|
-
"@corenel/term": "0.
|
|
12
|
+
"@corenel/config": "0.6.1",
|
|
13
|
+
"@corenel/harness": "0.14.0",
|
|
14
|
+
"@corenel/mcp": "0.5.0",
|
|
15
|
+
"@corenel/protocol": "0.13.0",
|
|
16
|
+
"@corenel/term": "0.8.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.7.
|
|
25
|
+
"@corenel/tools-node": "0.7.1"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|