@corenel/cli 0.7.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.
Files changed (3) hide show
  1. package/README.md +77 -68
  2. package/dist/main.js +466 -125
  3. 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
- ## Releasing
22
-
23
- `@corenel/term` (the terminal face) is a real runtime dependency, not bundled
24
- into `dist/main.js` -- see the note at the top of `build.mjs`. (`dist/cli.js`,
25
- the bin, is only a Node >=22 check that then imports `main.js`.) `@corenel/term`'s
26
- own dist is bundled but keeps `@corenel/harness` and `@corenel/protocol`
27
- EXTERNAL (see `packages/term/build.mjs`) -- both the CLI and term import the
28
- harness directly, and they must resolve to the SAME installed copy, or the
29
- `llm-execution` feature guard and `--debug` logging silently apply to the
30
- wrong module instance. That makes all four packages real runtime dependencies
31
- of each other, and publish order matters:
32
-
33
- pnpm --filter @corenel/protocol build && pnpm --filter @corenel/protocol publish
34
- pnpm --filter @corenel/harness build && pnpm --filter @corenel/harness publish
35
- pnpm --filter @corenel/term build && pnpm --filter @corenel/term publish
36
- pnpm --filter @corenel/cli build && pnpm --filter @corenel/cli publish
37
-
38
- Publishing any package against an unpublished dependency yields one that
39
- cannot install at all, so `npx @corenel/cli` fails before any verb runs.
40
-
41
- Publish with **pnpm, never npm**: this CLI depends on `@corenel/term` as
42
- `workspace:*`, and pnpm rewrites the `workspace:` protocol to a real version at
43
- pack/publish time while npm ships it verbatim -- an `npm publish` from this
44
- workspace produces a manifest whose install fails with
45
- `EUNSUPPORTEDPROTOCOL / Unsupported URL Type "workspace:"`, which is the very
46
- breakage this section exists to prevent.
47
-
48
- **All four versions must be bumped and published together, in the order
49
- above, every release -- even one that only touches a single package.** pnpm
50
- rewrites every `workspace:*` dependency to the EXACT current version of that
51
- dependency at pack time. All four packages are pinned to the same harness
52
- version today, so a normal `npm install` of `@corenel/cli` resolves to one
53
- harness copy. Bump `@corenel/harness` and publish a new `@corenel/term`
54
- without also republishing `@corenel/cli`, and the tree that installs
55
- `@corenel/cli` gets the OLD harness version nested under it (frozen into the
56
- manifest `@corenel/cli` published against) plus the NEW harness version
57
- hoisted at the top level for `@corenel/term` -- two separate harness module
58
- instances again, silently. That is exactly the bug this section's ordering
59
- exists to prevent (see `harnessSingleInstance.test.ts` in this package's
60
- `test/`): `registerGuard()`/`setAgentLogging()` calls made through one
61
- instance configure a module the other never reads, so the `llm-execution`
62
- feature gate silently stops running and `--debug` logging can't be turned
63
- off. Every test in this repo stays green when that happens, because the
64
- in-repo workspace is symlinked (one instance by construction) and the
65
- build-output regression test only exercises that same symlinked layout --
66
- neither one can see a real, version-skewed `npm install`.
67
-
68
- Licensed under Elastic-2.0.
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
@@ -390,6 +390,19 @@ var init_fileService = __esm({
390
390
  await this.assertReal(a);
391
391
  await writeFileAtomic(a, bytes, { shownPath: path });
392
392
  }
393
+ /** O_APPEND, NOT atomic-rename: the point is that another process appending
394
+ * to the same file at the same moment lands too, and a rename would replace
395
+ * whatever it wrote. Same confinement as write. */
396
+ async append(path, content) {
397
+ const a = await this.writeTarget(path);
398
+ await fs2.mkdir(dirname2(a), { recursive: true });
399
+ await this.assertReal(a);
400
+ try {
401
+ await fs2.appendFile(a, content, "utf8");
402
+ } catch (e) {
403
+ throw diskFullError(path, e) ?? e;
404
+ }
405
+ }
393
406
  async create(path, content = "") {
394
407
  const a = await this.writeTarget(path);
395
408
  await fs2.mkdir(dirname2(a), { recursive: true });
@@ -7878,7 +7891,7 @@ var require_public_api = __commonJS({
7878
7891
  }
7879
7892
  return doc;
7880
7893
  }
7881
- function parse2(src, reviver, options) {
7894
+ function parse3(src, reviver, options) {
7882
7895
  let _reviver = void 0;
7883
7896
  if (typeof reviver === "function") {
7884
7897
  _reviver = reviver;
@@ -7919,7 +7932,7 @@ var require_public_api = __commonJS({
7919
7932
  return value.toString(options);
7920
7933
  return new Document.Document(value, _replacer, options).toString(options);
7921
7934
  }
7922
- exports.parse = parse2;
7935
+ exports.parse = parse3;
7923
7936
  exports.parseAllDocuments = parseAllDocuments;
7924
7937
  exports.parseDocument = parseDocument;
7925
7938
  exports.stringify = stringify2;
@@ -8445,7 +8458,7 @@ var require_parse = __commonJS({
8445
8458
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/parse.js"(exports, module) {
8446
8459
  "use strict";
8447
8460
  var SemVer = require_semver();
8448
- var parse2 = (version2, options, throwErrors = false) => {
8461
+ var parse3 = (version2, options, throwErrors = false) => {
8449
8462
  if (version2 instanceof SemVer) {
8450
8463
  return version2;
8451
8464
  }
@@ -8458,7 +8471,7 @@ var require_parse = __commonJS({
8458
8471
  throw er;
8459
8472
  }
8460
8473
  };
8461
- module.exports = parse2;
8474
+ module.exports = parse3;
8462
8475
  }
8463
8476
  });
8464
8477
 
@@ -8466,9 +8479,9 @@ var require_parse = __commonJS({
8466
8479
  var require_valid = __commonJS({
8467
8480
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/valid.js"(exports, module) {
8468
8481
  "use strict";
8469
- var parse2 = require_parse();
8482
+ var parse3 = require_parse();
8470
8483
  var valid = (version2, options) => {
8471
- const v = parse2(version2, options);
8484
+ const v = parse3(version2, options);
8472
8485
  return v ? v.version : null;
8473
8486
  };
8474
8487
  module.exports = valid;
@@ -8479,9 +8492,9 @@ var require_valid = __commonJS({
8479
8492
  var require_clean = __commonJS({
8480
8493
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/clean.js"(exports, module) {
8481
8494
  "use strict";
8482
- var parse2 = require_parse();
8495
+ var parse3 = require_parse();
8483
8496
  var clean = (version2, options) => {
8484
- const s = parse2(version2.trim().replace(/^[=v]+/, ""), options);
8497
+ const s = parse3(version2.trim().replace(/^[=v]+/, ""), options);
8485
8498
  return s ? s.version : null;
8486
8499
  };
8487
8500
  module.exports = clean;
@@ -8516,10 +8529,10 @@ var require_inc = __commonJS({
8516
8529
  var require_diff = __commonJS({
8517
8530
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/diff.js"(exports, module) {
8518
8531
  "use strict";
8519
- var parse2 = require_parse();
8532
+ var parse3 = require_parse();
8520
8533
  var diff = (version1, version2) => {
8521
- const v1 = parse2(version1, null, true);
8522
- const v2 = parse2(version2, null, true);
8534
+ const v1 = parse3(version1, null, true);
8535
+ const v2 = parse3(version2, null, true);
8523
8536
  const comparison = v1.compare(v2);
8524
8537
  if (comparison === 0) {
8525
8538
  return null;
@@ -8590,9 +8603,9 @@ var require_patch = __commonJS({
8590
8603
  var require_prerelease = __commonJS({
8591
8604
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/prerelease.js"(exports, module) {
8592
8605
  "use strict";
8593
- var parse2 = require_parse();
8606
+ var parse3 = require_parse();
8594
8607
  var prerelease = (version2, options) => {
8595
- const parsed = parse2(version2, options);
8608
+ const parsed = parse3(version2, options);
8596
8609
  return parsed && parsed.prerelease.length ? parsed.prerelease : null;
8597
8610
  };
8598
8611
  module.exports = prerelease;
@@ -8778,7 +8791,7 @@ var require_coerce = __commonJS({
8778
8791
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/coerce.js"(exports, module) {
8779
8792
  "use strict";
8780
8793
  var SemVer = require_semver();
8781
- var parse2 = require_parse();
8794
+ var parse3 = require_parse();
8782
8795
  var { safeRe: re, t } = require_re();
8783
8796
  var coerce = (version2, options) => {
8784
8797
  if (version2 instanceof SemVer) {
@@ -8813,7 +8826,7 @@ var require_coerce = __commonJS({
8813
8826
  const patch = match[4] || "0";
8814
8827
  const prerelease = options.includePrerelease && match[5] ? `-${match[5]}` : "";
8815
8828
  const build = options.includePrerelease && match[6] ? `+${match[6]}` : "";
8816
- return parse2(`${major}.${minor}.${patch}${prerelease}${build}`, options);
8829
+ return parse3(`${major}.${minor}.${patch}${prerelease}${build}`, options);
8817
8830
  };
8818
8831
  module.exports = coerce;
8819
8832
  }
@@ -8823,7 +8836,7 @@ var require_coerce = __commonJS({
8823
8836
  var require_truncate = __commonJS({
8824
8837
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/truncate.js"(exports, module) {
8825
8838
  "use strict";
8826
- var parse2 = require_parse();
8839
+ var parse3 = require_parse();
8827
8840
  var constants2 = require_constants();
8828
8841
  var SemVer = require_semver();
8829
8842
  var truncate2 = (version2, truncation, options) => {
@@ -8835,7 +8848,7 @@ var require_truncate = __commonJS({
8835
8848
  };
8836
8849
  var cloneInputVersion = (version2, options) => {
8837
8850
  const versionStringToParse = version2 instanceof SemVer ? version2.version : version2;
8838
- return parse2(versionStringToParse, options);
8851
+ return parse3(versionStringToParse, options);
8839
8852
  };
8840
8853
  var doTruncation = (version2, truncation) => {
8841
8854
  if (isPrerelease(truncation)) {
@@ -9877,7 +9890,7 @@ var require_semver2 = __commonJS({
9877
9890
  var constants2 = require_constants();
9878
9891
  var SemVer = require_semver();
9879
9892
  var identifiers = require_identifiers();
9880
- var parse2 = require_parse();
9893
+ var parse3 = require_parse();
9881
9894
  var valid = require_valid();
9882
9895
  var clean = require_clean();
9883
9896
  var inc = require_inc();
@@ -9916,7 +9929,7 @@ var require_semver2 = __commonJS({
9916
9929
  var simplifyRange = require_simplify();
9917
9930
  var subset = require_subset();
9918
9931
  module.exports = {
9919
- parse: parse2,
9932
+ parse: parse3,
9920
9933
  valid,
9921
9934
  clean,
9922
9935
  inc,
@@ -12303,7 +12316,7 @@ var require_parser2 = __commonJS({
12303
12316
  }
12304
12317
  return buf;
12305
12318
  };
12306
- _proto.parse = function parse2() {
12319
+ _proto.parse = function parse3() {
12307
12320
  return new nodes.NodeList(0, 0, this.parseNodes());
12308
12321
  };
12309
12322
  _proto.parseAsRoot = function parseAsRoot() {
@@ -12312,7 +12325,7 @@ var require_parser2 = __commonJS({
12312
12325
  return Parser2;
12313
12326
  }(Obj);
12314
12327
  module.exports = {
12315
- parse: function parse2(src, extensions, opts) {
12328
+ parse: function parse3(src, extensions, opts) {
12316
12329
  var p = new Parser(lexer.lex(src, opts));
12317
12330
  if (extensions !== void 0) {
12318
12331
  p.extensions = extensions;
@@ -13894,7 +13907,7 @@ var require_filters = __commonJS({
13894
13907
  return r.copySafeness(str, res);
13895
13908
  }
13896
13909
  _exports.indent = indent;
13897
- function join13(arr, del, attr) {
13910
+ function join14(arr, del, attr) {
13898
13911
  del = del || "";
13899
13912
  if (attr) {
13900
13913
  arr = lib.map(arr, function(v) {
@@ -13903,7 +13916,7 @@ var require_filters = __commonJS({
13903
13916
  }
13904
13917
  return arr.join(del);
13905
13918
  }
13906
- _exports.join = join13;
13919
+ _exports.join = join14;
13907
13920
  function last(arr) {
13908
13921
  return arr[arr.length - 1];
13909
13922
  }
@@ -15246,8 +15259,8 @@ var require_precompile = __commonJS({
15246
15259
  fs6.readdirSync(dir).forEach(function(file) {
15247
15260
  var filepath = path.join(dir, file);
15248
15261
  var subpath = filepath.substr(path.join(input, "/").length);
15249
- var stat = fs6.statSync(filepath);
15250
- if (stat && stat.isDirectory()) {
15262
+ var stat2 = fs6.statSync(filepath);
15263
+ if (stat2 && stat2.isDirectory()) {
15251
15264
  subpath += "/";
15252
15265
  if (!match(subpath, opts.exclude)) {
15253
15266
  addTemplates(filepath);
@@ -16426,8 +16439,8 @@ ${yamlContent}` : yamlContent;
16426
16439
  if (resolvedInheritsRef.startsWith("./") || resolvedInheritsRef.startsWith("../") || resolvedInheritsRef.startsWith("/") || !resolvedInheritsRef.startsWith("@") && isPrompdFile(resolvedInheritsRef)) {
16427
16440
  const sourceDir = context.fileSystem.dirname(context.sourceFile);
16428
16441
  const parentPath = context.fileSystem.resolve(sourceDir, resolvedInheritsRef);
16429
- const fileExists = await context.fileSystem.exists(parentPath);
16430
- if (!fileExists) {
16442
+ const fileExists2 = await context.fileSystem.exists(parentPath);
16443
+ if (!fileExists2) {
16431
16444
  const location = context.findLocation(/inherits:/);
16432
16445
  context.addDiagnostic({
16433
16446
  message: `Inherited file not found: "${resolvedInheritsRef}" (resolved to: ${parentPath})`,
@@ -16454,8 +16467,8 @@ ${yamlContent}` : yamlContent;
16454
16467
  }
16455
16468
  const packagePath = await this.resolvePackage(context, packageRef);
16456
16469
  const fullFilePath = resolvePackageFile(packagePath, filePathInPackage);
16457
- const fileExists = await context.fileSystem.exists(fullFilePath);
16458
- if (!fileExists) {
16470
+ const fileExists2 = await context.fileSystem.exists(fullFilePath);
16471
+ if (!fileExists2) {
16459
16472
  const location = context.findLocation(/inherits:/);
16460
16473
  context.addDiagnostic({
16461
16474
  message: `Inherited file not found in package: "${filePathInPackage}" (package: ${packageRef})`,
@@ -21103,7 +21116,7 @@ var require_extension = __commonJS({
21103
21116
  if (dest[name] === void 0) dest[name] = [elem];
21104
21117
  else dest[name].push(elem);
21105
21118
  }
21106
- function parse2(header) {
21119
+ function parse3(header) {
21107
21120
  const offers = /* @__PURE__ */ Object.create(null);
21108
21121
  let params = /* @__PURE__ */ Object.create(null);
21109
21122
  let mustUnescape = false;
@@ -21243,7 +21256,7 @@ var require_extension = __commonJS({
21243
21256
  }).join(", ");
21244
21257
  }).join(", ");
21245
21258
  }
21246
- module.exports = { format, parse: parse2 };
21259
+ module.exports = { format, parse: parse3 };
21247
21260
  }
21248
21261
  });
21249
21262
 
@@ -21277,7 +21290,7 @@ var require_websocket = __commonJS({
21277
21290
  var {
21278
21291
  EventTarget: { addEventListener, removeEventListener }
21279
21292
  } = require_event_target();
21280
- var { format, parse: parse2 } = require_extension();
21293
+ var { format, parse: parse3 } = require_extension();
21281
21294
  var { toBuffer } = require_buffer_util();
21282
21295
  var kAborted = Symbol("kAborted");
21283
21296
  var protocolVersions = [8, 13];
@@ -21954,7 +21967,7 @@ var require_websocket = __commonJS({
21954
21967
  }
21955
21968
  let extensions;
21956
21969
  try {
21957
- extensions = parse2(secWebSocketExtensions);
21970
+ extensions = parse3(secWebSocketExtensions);
21958
21971
  } catch (err) {
21959
21972
  const message = "Invalid Sec-WebSocket-Extensions header";
21960
21973
  abortHandshake(websocket, socket, message);
@@ -22246,7 +22259,7 @@ var require_subprotocol = __commonJS({
22246
22259
  "../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/lib/subprotocol.js"(exports, module) {
22247
22260
  "use strict";
22248
22261
  var { tokenChars } = require_validation();
22249
- function parse2(header) {
22262
+ function parse3(header) {
22250
22263
  const protocols = /* @__PURE__ */ new Set();
22251
22264
  let start = -1;
22252
22265
  let end = -1;
@@ -22282,7 +22295,7 @@ var require_subprotocol = __commonJS({
22282
22295
  protocols.add(protocol);
22283
22296
  return protocols;
22284
22297
  }
22285
- module.exports = { parse: parse2 };
22298
+ module.exports = { parse: parse3 };
22286
22299
  }
22287
22300
  });
22288
22301
 
@@ -22691,7 +22704,7 @@ var require_websocket_server = __commonJS({
22691
22704
  init_tools_node();
22692
22705
  import { runAgent as runAgent2 } from "@corenel/harness/core/loop";
22693
22706
  import { createChatClient as createChatClient2, setGatewayBase as setGatewayBase2 } from "@corenel/harness/providers/gateway";
22694
- import { resolveContextWindow as resolveContextWindow2, resolveContextWindowWithHydration } from "@corenel/harness/providers/contextWindow";
22707
+ import { resolveContextWindow as resolveContextWindow3, resolveContextWindowWithHydration } from "@corenel/harness/providers/contextWindow";
22695
22708
  import { basename as basename5 } from "node:path";
22696
22709
  import { randomUUID } from "node:crypto";
22697
22710
  import { allTools as allTools3 } from "@corenel/harness/tools/builtins";
@@ -22708,9 +22721,9 @@ function configWorkspaceRoot(base) {
22708
22721
 
22709
22722
  // src/cli.ts
22710
22723
  import { FileRecallStore, UNATTACHED_SESSION } from "@corenel/harness/sessions/fileRecallStore";
22711
- import { makeCheckpointStore } from "@corenel/harness/sessions/checkpointStore";
22724
+ import { makeSessionFoldStorage } from "@corenel/harness/sessions/foldStorage";
22712
22725
  import { makeSummarizer } from "@corenel/harness/core/summarize";
22713
- import { stateDirName as stateDirName7, sessionsRoot } from "@corenel/protocol";
22726
+ import { stateDirName as stateDirName7, sessionsRoot, isSourceRun } from "@corenel/protocol";
22714
22727
  import { PermissionService as PermissionService6 } from "@corenel/harness/guardrail/permission-service";
22715
22728
 
22716
22729
  // src/nodeContext.ts
@@ -24865,7 +24878,7 @@ async function deviceLogin(opts) {
24865
24878
  );
24866
24879
  }
24867
24880
  };
24868
- const dc = await post("/oauth/device/code", { client_id: "corenel-cli", scope: "agent" });
24881
+ const dc = await post("/oauth/device/code", { client_id: "corenel-cli", scope: "agent pair" });
24869
24882
  if (!dc.device_code) throw new Error("device authorization failed");
24870
24883
  (opts.open ?? defaultOpen)(dc.verification_uri, dc.user_code);
24871
24884
  let interval = opts.pollMs ?? Math.max(MIN_POLL_MS, (dc.interval ?? 5) * 1e3);
@@ -24905,6 +24918,7 @@ import { createChatClient, setGatewayBase } from "@corenel/harness/providers/gat
24905
24918
  // src/apiBase.ts
24906
24919
  init_tools_node();
24907
24920
  import { DEFAULT_API_BASE as DEFAULT_API_BASE2 } from "@corenel/protocol";
24921
+ import { DEV_PROFILE } from "@corenel/protocol";
24908
24922
  var InvalidApiBaseError = class extends Error {
24909
24923
  constructor(value, why) {
24910
24924
  super(`invalid API base ${JSON.stringify(value)} -- ${why}`);
@@ -24913,17 +24927,39 @@ var InvalidApiBaseError = class extends Error {
24913
24927
  this.name = "InvalidApiBaseError";
24914
24928
  }
24915
24929
  };
24916
- function isLoopback(hostname) {
24917
- const h = hostname.replace(/^\[|\]$/g, "").toLowerCase();
24930
+ function isLoopback(hostname2) {
24931
+ const h = hostname2.replace(/^\[|\]$/g, "").toLowerCase();
24918
24932
  return h === "localhost" || h === "127.0.0.1" || h === "::1" || h.endsWith(".localhost");
24919
24933
  }
24920
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
+ }
24921
24951
  async function apiBase(explicit, opts = {}) {
24922
- const resolved = await resolveApiBase({
24923
- explicit,
24924
- env: opts.env ?? process.env,
24925
- ...opts.stateDir ? { stateDir: opts.stateDir } : {}
24926
- });
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
+ );
24927
24963
  const value = resolved.base;
24928
24964
  let url;
24929
24965
  try {
@@ -24941,7 +24977,7 @@ async function apiBase(explicit, opts = {}) {
24941
24977
  `
24942
24978
  );
24943
24979
  }
24944
- return value;
24980
+ return resolved;
24945
24981
  }
24946
24982
 
24947
24983
  // src/runAgent.ts
@@ -25257,7 +25293,9 @@ function helpText(version2) {
25257
25293
  " --model <id> model for this turn (default gpt-4o-mini)",
25258
25294
  " --base <url> API base (default https://api.corenel.ai/api, or CORENEL_API_BASE)",
25259
25295
  " --policy <name|path> a builtin policy name (e.g. read-only) or a policy YAML file;",
25260
- " default is a project/user policy.yaml if one exists, else standard",
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.",
25261
25299
  " --allow-mutations allow write/shell/other mutating tools (run/run-agent refuse them",
25262
25300
  " outright without this; interactively it skips the per-command",
25263
25301
  " prompt instead). --allow-shell still works as an alias.",
@@ -25342,23 +25380,27 @@ async function authState(resolve4 = (env2) => resolveNodeCredential({ env: env2
25342
25380
  const source = r.source === "env" ? "env" : "file";
25343
25381
  return { source, path: r.path ?? authTokenPath(), hint: hintOf(r.token) };
25344
25382
  }
25345
- 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})` : ""}`;
25346
25386
  if (s.source === "none") {
25347
25387
  return [
25348
25388
  "Not signed in.",
25349
25389
  ` Run \`corenel login\`, or set CORENEL_TOKEN=<token>.`,
25350
- ` Token would be stored at ${s.path}`
25351
- ].join("\n");
25390
+ ` Token would be stored at ${s.path}`,
25391
+ note ? apiLine : null
25392
+ ].filter(Boolean).join("\n");
25352
25393
  }
25353
25394
  return [
25354
25395
  `Signed in (${s.source === "env" ? "CORENEL_TOKEN environment variable" : s.path}).`,
25355
25396
  ` token ${s.hint}`,
25356
- ` api ${base}`,
25397
+ apiLine,
25357
25398
  s.source === "env" ? " NOTE: the environment wins over any stored token, so `corenel logout` will not change this." : null
25358
25399
  ].filter(Boolean).join("\n");
25359
25400
  }
25360
25401
  async function runAuth(out = (s) => process.stdout.write(s)) {
25361
- out(`${describeAuth(await authState(), await apiBase())}
25402
+ const { base, source } = await resolveCliApiBase();
25403
+ out(`${describeAuth(await authState(), base, source)}
25362
25404
  `);
25363
25405
  }
25364
25406
  async function runLogout(out = (s) => process.stdout.write(s)) {
@@ -25417,6 +25459,7 @@ var KNOWN_KEYS = [
25417
25459
  "addUnder",
25418
25460
  "cloneUnder",
25419
25461
  "allowLan",
25462
+ "behindTlsProxy",
25420
25463
  "allowOrigins",
25421
25464
  "tlsCert",
25422
25465
  "tlsKey",
@@ -25438,7 +25481,7 @@ var KNOWN_KEYS = [
25438
25481
  "mcpServers",
25439
25482
  "mcpAllow",
25440
25483
  "identityIssuer",
25441
- "identity",
25484
+ "org",
25442
25485
  "push",
25443
25486
  "audit",
25444
25487
  "sendTelemetry",
@@ -25707,17 +25750,19 @@ async function checkSidecarEnv(base, env, stateDir = corenelDir()) {
25707
25750
  return { name: "sidecar", status: "fail", detail: `${base} (env: ${env}) -- ${e instanceof Error ? e.message : String(e)}` };
25708
25751
  }
25709
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
+ }
25710
25758
  async function collectChecks(f = fetch, modelId = DEFAULT_MODEL, env) {
25711
- const base = await apiBase();
25759
+ const resolvedBase = await resolveCliApiBase();
25760
+ const base = resolvedBase.base;
25712
25761
  const checks = [
25713
25762
  { name: "binary", status: "ok", detail: selfPath() },
25714
25763
  { name: "version", status: "ok", detail: `@corenel/cli ${version()}` },
25715
25764
  { name: "node", status: "ok", detail: process.version },
25716
- {
25717
- name: "api",
25718
- status: "ok",
25719
- detail: base === DEFAULT_API_BASE2 ? base : `${base} (overridden; default is ${DEFAULT_API_BASE2})`
25720
- }
25765
+ apiCheck(resolvedBase)
25721
25766
  ];
25722
25767
  try {
25723
25768
  const u = new URL(base);
@@ -25765,7 +25810,7 @@ import { createInterface } from "node:readline";
25765
25810
  import { PermissionService as PermissionService4 } from "@corenel/harness/guardrail/permission-service";
25766
25811
  import { STANDARD_POLICY as STANDARD_POLICY2 } from "@corenel/harness/guardrail/builtins";
25767
25812
  import { approvalsHooks } from "@corenel/harness/approvals";
25768
- import { TermSession, parseSuggestionChoice, suggestionKeys, suggestionLine, suggestionOutcome } from "@corenel/term/session";
25813
+ import { TermSession, startupResumeOptions, parseSuggestionChoice, suggestionKeys, suggestionLine, suggestionOutcome } from "@corenel/term/session";
25769
25814
  import { describeToolCall, formatOrd } from "@corenel/term/describe";
25770
25815
  import { buildWritePreview } from "@corenel/term/writePreview";
25771
25816
  import { formatEndingChange } from "@corenel/term/diff";
@@ -25923,10 +25968,10 @@ async function runRepl(deps) {
25923
25968
  }
25924
25969
  });
25925
25970
  if (deps.resume) {
25926
- await session.resume(deps.resume.id);
25971
+ await session.resume(deps.resume.id, startupResumeOptions(deps.resume));
25927
25972
  const restored = session.getSnapshot();
25928
25973
  if (restored.session) {
25929
- writeLine(` resumed ${restored.session.id} - ${restored.committed.length} messages
25974
+ writeLine(` resumed ${restored.session.id} - ${restored.committed.filter((i) => i.kind !== "note").length} messages
25930
25975
  `);
25931
25976
  }
25932
25977
  }
@@ -26213,6 +26258,44 @@ ${rules(r.workspace)}`);
26213
26258
  import { approvalsHooks as approvalsHooks2 } from "@corenel/harness/approvals";
26214
26259
  import { createSessionPersistence } from "@corenel/term/persist";
26215
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
+
26216
26299
  // src/shellTool.ts
26217
26300
  import { spawn as spawn3 } from "node:child_process";
26218
26301
  var MAX_OUTPUT = 3e4;
@@ -26323,7 +26406,7 @@ import { procTools } from "@corenel/harness/tools/procs";
26323
26406
 
26324
26407
  // src/sidecarConnect.ts
26325
26408
  import { stateDirName as stateDirName5 } from "@corenel/protocol";
26326
- import { SidecarHost } from "@corenel/harness/sidecar/host";
26409
+ import { SidecarHost, isSidecarKeyMismatch, isSidecarAccountRefusal } from "@corenel/harness/sidecar/host";
26327
26410
 
26328
26411
  // ../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/wrapper.mjs
26329
26412
  var import_stream = __toESM(require_stream(), 1);
@@ -26486,12 +26569,209 @@ function hostBackedProcs(host, seed = []) {
26486
26569
  };
26487
26570
  }
26488
26571
 
26572
+ // src/sidecarDevice.ts
26573
+ init_tools_node();
26574
+ import { join as join10 } from "node:path";
26575
+ import { asEcPublicJwk, generatePrivateJwk, signerFromPrivateJwk } from "@corenel/harness/sidecar/deviceKeys";
26576
+ function cliDevicesPath(base) {
26577
+ return join10(corenelDir(base), "cli", "sidecar-devices.json");
26578
+ }
26579
+ var B64 = /^[A-Za-z0-9_-]{1,64}$/;
26580
+ var B43 = /^[A-Za-z0-9_-]{43}$/;
26581
+ function isCliDevice(v) {
26582
+ if (typeof v !== "object" || v === null) return false;
26583
+ const r = v;
26584
+ const k = r.privateJwk;
26585
+ return typeof r.id === "string" && B64.test(r.id) && typeof r.sidecarJkt === "string" && B43.test(r.sidecarJkt) && typeof k === "object" && k !== null && !!asEcPublicJwk({ kty: k.kty, crv: k.crv, x: k.x, y: k.y }) && typeof k.d === "string" && B43.test(k.d);
26586
+ }
26587
+ function parse2(text) {
26588
+ let raw;
26589
+ try {
26590
+ raw = JSON.parse(text);
26591
+ } catch {
26592
+ return null;
26593
+ }
26594
+ const r = raw;
26595
+ if (typeof r !== "object" || r === null || r.version !== 1) return null;
26596
+ if (typeof r.devices !== "object" || r.devices === null || Array.isArray(r.devices)) return null;
26597
+ const devices = /* @__PURE__ */ Object.create(null);
26598
+ for (const [url, d] of Object.entries(r.devices)) {
26599
+ if (!isCliDevice(d)) return null;
26600
+ const k = d.privateJwk;
26601
+ devices[url] = { id: d.id, sidecarJkt: d.sidecarJkt, privateJwk: { kty: k.kty, crv: k.crv, x: k.x, y: k.y, d: k.d } };
26602
+ }
26603
+ return { version: 1, devices };
26604
+ }
26605
+ function cliDeviceStore(path = cliDevicesPath()) {
26606
+ const file = fileTextStore(path);
26607
+ const dir = join10(path, "..");
26608
+ const load = async () => {
26609
+ const text = await file.read();
26610
+ return text === null ? { version: 1, devices: /* @__PURE__ */ Object.create(null) } : parse2(text);
26611
+ };
26612
+ const change = async (edit) => {
26613
+ const f = await load();
26614
+ if (!f) throw new Error(`${path} is not a file this CLI understands. Remove it to pair again.`);
26615
+ edit(f);
26616
+ await ensurePrivateDir(dir);
26617
+ await file.write(`${JSON.stringify(f, null, 2)}
26618
+ `);
26619
+ };
26620
+ return {
26621
+ async get(url) {
26622
+ try {
26623
+ const f = await load();
26624
+ return f && Object.prototype.hasOwnProperty.call(f.devices, url) ? f.devices[url] : void 0;
26625
+ } catch {
26626
+ return void 0;
26627
+ }
26628
+ },
26629
+ set: (url, device) => change((f) => {
26630
+ f.devices[url] = device;
26631
+ }),
26632
+ forget: (url) => change((f) => {
26633
+ delete f.devices[url];
26634
+ })
26635
+ };
26636
+ }
26637
+ async function sidecarCredential(url, env, store) {
26638
+ const kept = await store.get(url);
26639
+ const enroll = env.CORENEL_SIDECAR_CODE || void 0;
26640
+ if (!kept && !enroll) return null;
26641
+ const privateJwk = kept?.privateJwk ?? await generatePrivateJwk();
26642
+ return {
26643
+ ...enroll ? { enroll } : {},
26644
+ ...kept ? { device: { id: kept.id, sidecarJkt: kept.sidecarJkt } } : {},
26645
+ deviceKey: await signerFromPrivateJwk(privateJwk),
26646
+ privateJwk
26647
+ };
26648
+ }
26649
+
26650
+ // src/sidecarIdentity.ts
26651
+ function cliIdentity(o) {
26652
+ const fetchImpl = o.fetchImpl ?? fetch;
26653
+ const timeoutMs = o.timeoutMs ?? 8e3;
26654
+ let last = null;
26655
+ return {
26656
+ problem: () => last,
26657
+ answer: async ({ nonce, sidecarId, jkt, orgId }) => {
26658
+ last = null;
26659
+ let token;
26660
+ try {
26661
+ token = await o.token();
26662
+ } catch {
26663
+ token = null;
26664
+ }
26665
+ if (!token) {
26666
+ last = "this CLI is not signed in, and the sidecar only admits a verified account. Run `corenel login` (or set CORENEL_TOKEN), then try again.";
26667
+ return null;
26668
+ }
26669
+ try {
26670
+ const base = (await o.base()).replace(/\/+$/, "");
26671
+ const res = await fetchImpl(`${base}/v1/pairing-assertions`, {
26672
+ method: "POST",
26673
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
26674
+ body: JSON.stringify({ aud: sidecarId, nonce, jkt, ...orgId ? { org_id: orgId } : {} }),
26675
+ signal: AbortSignal.timeout(timeoutMs)
26676
+ });
26677
+ if (res.status === 403) {
26678
+ let code3;
26679
+ try {
26680
+ code3 = (await res.json())?.code;
26681
+ } catch {
26682
+ code3 = void 0;
26683
+ }
26684
+ if (code3 === "CLI_SCOPE") {
26685
+ last = "this CLI sign-in is not allowed to connect to sidecars. Run `corenel login` again and allow sidecar access when you approve it.";
26686
+ return null;
26687
+ }
26688
+ if (orgId) {
26689
+ last = `your account is not a member of the organization this sidecar admits (${orgId}).`;
26690
+ return null;
26691
+ }
26692
+ }
26693
+ if (res.status === 401) {
26694
+ last = "the API refused this CLI sign-in. Run `corenel login` again, then try again.";
26695
+ return null;
26696
+ }
26697
+ if (!res.ok) {
26698
+ last = `could not get a pairing assertion from ${base} (HTTP ${res.status}).`;
26699
+ return null;
26700
+ }
26701
+ const body = await res.json();
26702
+ const assertion = body?.assertion;
26703
+ if (typeof assertion !== "string" || !assertion) {
26704
+ last = `${base} answered without a pairing assertion.`;
26705
+ return null;
26706
+ }
26707
+ return assertion;
26708
+ } catch (e) {
26709
+ last = `could not reach the API for a pairing assertion (${e instanceof Error ? e.message : String(e)}).`;
26710
+ return null;
26711
+ }
26712
+ }
26713
+ };
26714
+ }
26715
+
26489
26716
  // src/sidecarConnect.ts
26717
+ init_tools_node();
26718
+ import { hostname } from "node:os";
26490
26719
  var CONNECT_TIMEOUT_MS = 3e3;
26491
- async function connectSidecarProcs(env = process.env) {
26720
+ var HANDSHAKE_TIMEOUT_MS = 15e3;
26721
+ function cliDeviceName(host = hostname) {
26722
+ let h = "";
26723
+ try {
26724
+ h = host().trim();
26725
+ } catch {
26726
+ h = "";
26727
+ }
26728
+ return h ? `CLI on ${h}` : "CLI";
26729
+ }
26730
+ function defaultCliIdentity(env) {
26731
+ return cliIdentity({ base: () => apiBase(void 0, { env }), token: () => nodeAuthToken().getToken() });
26732
+ }
26733
+ async function reportHandshakeFailure(e, url, usedDevice, store, identity2, fallback) {
26734
+ const msg = e instanceof Error ? e.message : String(e);
26735
+ if (isSidecarKeyMismatch(e)) {
26736
+ process.stderr.write(`corenel: sidecar connection at ${url} failed to complete: ${msg} -- ${fallback}
26737
+ `);
26738
+ process.stderr.write(mismatchNote(url));
26739
+ return;
26740
+ }
26741
+ if (isSidecarAccountRefusal(e)) {
26742
+ const problem = identity2.problem() ?? "the sidecar verified this account and did not admit it (not its owner, or not a member of its organization, or a device another member paired).";
26743
+ process.stderr.write(`corenel: the sidecar at ${url} did not accept this CLI: ${problem} -- ${fallback}
26744
+ `);
26745
+ return;
26746
+ }
26747
+ process.stderr.write(`corenel: sidecar connection at ${url} failed to complete: ${msg} -- ${fallback}
26748
+ `);
26749
+ if (usedDevice && /denied/.test(msg)) await store.forget(url).catch(() => void 0);
26750
+ }
26751
+ async function credentialOptions(url, env, store) {
26752
+ const cred = await sidecarCredential(url, env, store);
26753
+ if (!cred) return null;
26754
+ const { privateJwk, ...host } = cred;
26755
+ return {
26756
+ ...host,
26757
+ usedDevice: !!cred.device,
26758
+ onDeviceIssued: (device) => {
26759
+ store.set(url, { ...device, privateJwk }).catch((e) => {
26760
+ process.stderr.write(`corenel: paired, but could not keep the device key: ${e instanceof Error ? e.message : String(e)}
26761
+ `);
26762
+ });
26763
+ }
26764
+ };
26765
+ }
26766
+ function mismatchNote(url) {
26767
+ return `corenel: the sidecar at ${url} is not the sidecar this CLI paired with (its key does not match the pinned fingerprint). Check the address. If that sidecar was reinstalled, pair again with a fresh code (CORENEL_SIDECAR_CODE from \`corenel-sidecar pair\`).
26768
+ `;
26769
+ }
26770
+ async function connectSidecarProcs(env = process.env, store = cliDeviceStore(), identity2 = defaultCliIdentity(env)) {
26492
26771
  const url = env.CORENEL_SIDECAR_URL;
26493
- const token = env.CORENEL_SIDECAR_TOKEN;
26494
- if (!url || !token) return null;
26772
+ if (!url) return null;
26773
+ const cred = await credentialOptions(url, env, store);
26774
+ if (!cred) return null;
26495
26775
  const ac = new AbortController();
26496
26776
  const timer = setTimeout(() => ac.abort(), CONNECT_TIMEOUT_MS);
26497
26777
  let channel;
@@ -26506,24 +26786,23 @@ async function connectSidecarProcs(env = process.env) {
26506
26786
  } finally {
26507
26787
  clearTimeout(timer);
26508
26788
  }
26789
+ const { usedDevice, ...credential } = cred;
26509
26790
  const host = new SidecarHost({
26510
26791
  channel,
26511
- token,
26792
+ ...credential,
26512
26793
  stateDir: stateDirName5(),
26513
26794
  clientId: `corenel-cli-${process.pid}`,
26514
- clientName: "corenel-cli",
26795
+ clientName: cliDeviceName(),
26796
+ onIdentityChallenge: identity2.answer,
26515
26797
  providers: {},
26516
26798
  registerTool: () => {
26517
26799
  }
26518
26800
  });
26519
26801
  try {
26520
- await host.connect(CONNECT_TIMEOUT_MS);
26802
+ await host.connect(HANDSHAKE_TIMEOUT_MS);
26521
26803
  } catch (e) {
26522
- process.stderr.write(
26523
- `corenel: sidecar connection at ${url} failed to complete: ${e instanceof Error ? e.message : String(e)} -- continuing without proc_start/proc_stop/proc_list.
26524
- `
26525
- );
26526
26804
  host.disconnect("handshake failed");
26805
+ await reportHandshakeFailure(e, url, usedDevice, store, identity2, "continuing without proc_start/proc_stop/proc_list.");
26527
26806
  return null;
26528
26807
  }
26529
26808
  if (!host.supportsProcs()) {
@@ -26533,10 +26812,11 @@ async function connectSidecarProcs(env = process.env) {
26533
26812
  const seed = await host.procList().catch(() => []);
26534
26813
  return { procs: hostBackedProcs(host, seed), disconnect: () => host.disconnect() };
26535
26814
  }
26536
- async function connectMcpAdmin(env = process.env) {
26815
+ async function connectMcpAdmin(env = process.env, store = cliDeviceStore(), identity2 = defaultCliIdentity(env)) {
26537
26816
  const url = env.CORENEL_SIDECAR_URL;
26538
- const token = env.CORENEL_SIDECAR_TOKEN;
26539
- if (!url || !token) return null;
26817
+ if (!url) return null;
26818
+ const cred = await credentialOptions(url, env, store);
26819
+ if (!cred) return null;
26540
26820
  const ac = new AbortController();
26541
26821
  const timer = setTimeout(() => ac.abort(), CONNECT_TIMEOUT_MS);
26542
26822
  let channel;
@@ -26551,24 +26831,23 @@ async function connectMcpAdmin(env = process.env) {
26551
26831
  } finally {
26552
26832
  clearTimeout(timer);
26553
26833
  }
26834
+ const { usedDevice, ...credential } = cred;
26554
26835
  const host = new SidecarHost({
26555
26836
  channel,
26556
- token,
26837
+ ...credential,
26557
26838
  stateDir: stateDirName5(),
26558
26839
  clientId: `corenel-cli-${process.pid}`,
26559
- clientName: "corenel-cli",
26840
+ clientName: cliDeviceName(),
26841
+ onIdentityChallenge: identity2.answer,
26560
26842
  providers: {},
26561
26843
  registerTool: () => {
26562
26844
  }
26563
26845
  });
26564
26846
  try {
26565
- await host.connect(CONNECT_TIMEOUT_MS);
26847
+ await host.connect(HANDSHAKE_TIMEOUT_MS);
26566
26848
  } catch (e) {
26567
- process.stderr.write(
26568
- `corenel: sidecar connection at ${url} failed to complete: ${e instanceof Error ? e.message : String(e)} -- writing MCP config locally instead.
26569
- `
26570
- );
26571
26849
  host.disconnect("handshake failed");
26850
+ await reportHandshakeFailure(e, url, usedDevice, store, identity2, "writing MCP config locally instead.");
26572
26851
  return null;
26573
26852
  }
26574
26853
  if (!host.supportsMcpAdmin()) {
@@ -26821,9 +27100,9 @@ async function resolveStrategyPrompt(files, id) {
26821
27100
  }
26822
27101
 
26823
27102
  // src/policyResolve.ts
26824
- import { readFile as readFile2 } from "node:fs/promises";
26825
- import { homedir as homedir3 } from "node:os";
26826
- import { join as join10 } from "node:path";
27103
+ init_tools_node();
27104
+ import { readFile as readFile2, stat } from "node:fs/promises";
27105
+ import { join as join11 } from "node:path";
26827
27106
  import { stateDirName as stateDirName6 } from "@corenel/protocol";
26828
27107
  import { BUILTIN_POLICIES, STANDARD_POLICY as STANDARD_POLICY3, policyFromYaml } from "@corenel/harness/guardrail";
26829
27108
  var PolicyResolutionError = class extends Error {
@@ -26871,25 +27150,55 @@ function validateOrThrow(text, path) {
26871
27150
  }
26872
27151
  return result.policy;
26873
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
+ }
26874
27164
  async function resolvePolicy(opts) {
26875
- const { flag: flag2, cwd } = opts;
27165
+ const { flag: flag2, cwd, homeBase } = opts;
27166
+ const warnings = [];
26876
27167
  if (flag2) {
26877
27168
  if (Object.prototype.hasOwnProperty.call(BUILTIN_POLICIES, flag2)) {
26878
- return { policy: BUILTIN_POLICIES[flag2], source: { kind: "builtin", name: flag2 } };
27169
+ return { policy: BUILTIN_POLICIES[flag2], source: { kind: "builtin", name: flag2 }, warnings };
26879
27170
  }
26880
27171
  const policy = await loadRequiredPolicyFile(
26881
27172
  flag2,
26882
27173
  `corenel: unknown --policy "${flag2}" -- not a builtin (${Object.keys(BUILTIN_POLICIES).join(", ")}) and no file exists at that path.`
26883
27174
  );
26884
- 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
+ );
26885
27196
  }
26886
- const projectPath = join10(cwd, stateDirName6(), "policy.yaml");
26887
27197
  const project = await loadDiscoveredPolicyFile(projectPath);
26888
- if (project) return { policy: project, source: { kind: "project", path: projectPath } };
26889
- const userPath = join10(homedir3(), stateDirName6(), "policy.yaml");
27198
+ if (project) return { policy: project, source: { kind: "project", path: projectPath }, warnings };
26890
27199
  const user = await loadDiscoveredPolicyFile(userPath);
26891
- if (user) return { policy: user, source: { kind: "user", path: userPath } };
26892
- 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 };
26893
27202
  }
26894
27203
 
26895
27204
  // ../mcp/src/httpClient.ts
@@ -27362,7 +27671,7 @@ function isExpired(cred, nowMs) {
27362
27671
  }
27363
27672
 
27364
27673
  // ../mcp/src/auth.ts
27365
- import { DEFAULT_API_BASE as DEFAULT_API_BASE3 } from "@corenel/protocol";
27674
+ import { DEFAULT_API_BASE as DEFAULT_API_BASE4 } from "@corenel/protocol";
27366
27675
  function safeOrigin(url) {
27367
27676
  try {
27368
27677
  return new URL(url).origin;
@@ -27370,24 +27679,30 @@ function safeOrigin(url) {
27370
27679
  return "its URL";
27371
27680
  }
27372
27681
  }
27373
- function isCorenelRelayUrl(url, bases) {
27682
+ function isCorenelRelayUrl(url, bases, devApiBases = []) {
27374
27683
  let u;
27375
27684
  try {
27376
27685
  u = new URL(url);
27377
27686
  } catch {
27378
27687
  return false;
27379
27688
  }
27380
- if (u.protocol !== "https:") return false;
27381
27689
  if (!/\/mcp\/hosted\/[^/]+\/?$/.test(u.pathname)) return false;
27382
- return bases.some((b) => {
27690
+ const sameOrigin = (b) => {
27383
27691
  try {
27384
27692
  return new URL(b).origin === u.origin;
27385
27693
  } catch {
27386
27694
  return false;
27387
27695
  }
27388
- });
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";
27389
27704
  }
27390
- function authProviderFor(stateDir, server, fetchImpl = fetch, accountToken = async () => null, relayBases = async () => []) {
27705
+ function authProviderFor(stateDir, server, fetchImpl = fetch, accountToken = async () => null, relayBases = async () => [], devApiBases = []) {
27391
27706
  async function doRefresh(cred) {
27392
27707
  if (!cred.refreshToken) {
27393
27708
  throw new Error(`"${server.name}" has no refresh token on file -- run: corenel mcp login ${server.name}`);
@@ -27400,7 +27715,7 @@ function authProviderFor(stateDir, server, fetchImpl = fetch, accountToken = asy
27400
27715
  return {
27401
27716
  async headers() {
27402
27717
  const cred = await readMcpCredential(stateDir, server.name);
27403
- if (server.hosted && !isCorenelRelayUrl(server.url, [DEFAULT_API_BASE3, ...await relayBases()])) {
27718
+ if (server.hosted && !isCorenelRelayUrl(server.url, [DEFAULT_API_BASE4, ...await relayBases()], devApiBases)) {
27404
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.`);
27405
27720
  }
27406
27721
  if (!cred && !server.hosted) {
@@ -27451,10 +27766,17 @@ async function accountTokenFor(stateDir) {
27451
27766
  return null;
27452
27767
  }
27453
27768
  }
27769
+ function mcpClientFactory(opts = {}) {
27770
+ const devApiBases = [...opts.devApiBases ?? []];
27771
+ return (stateDir, server) => buildClient(stateDir, server, devApiBases);
27772
+ }
27454
27773
  function defaultClientFactory(stateDir, server) {
27774
+ return buildClient(stateDir, server, []);
27775
+ }
27776
+ function buildClient(stateDir, server, devApiBases) {
27455
27777
  return new McpHttpClient(
27456
27778
  server,
27457
- 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)
27458
27780
  );
27459
27781
  }
27460
27782
  async function relayBasesFor(stateDir) {
@@ -27747,9 +28069,9 @@ function gatedAgentTools(registry, policy) {
27747
28069
  // ../mcp/src/cache.ts
27748
28070
  import { createHash as createHash2 } from "node:crypto";
27749
28071
  import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir3, rename as rename2 } from "node:fs/promises";
27750
- import { dirname as dirname8, join as join11 } from "node:path";
28072
+ import { dirname as dirname8, join as join12 } from "node:path";
27751
28073
  function toolCachePath(stateDir) {
27752
- return join11(stateDir, "mcp-tools.json");
28074
+ return join12(stateDir, "mcp-tools.json");
27753
28075
  }
27754
28076
  function credentialFingerprint(cred) {
27755
28077
  if (!cred) return "none";
@@ -27792,11 +28114,11 @@ function cacheEntryFor(cache, server, cred) {
27792
28114
  }
27793
28115
 
27794
28116
  // src/mcpRegistry.ts
27795
- async function openCliMcp(stateDir, policy) {
28117
+ async function openCliMcp(stateDir, policy, opts = {}) {
27796
28118
  try {
27797
28119
  const servers = await readMcpServers(stateDir);
27798
28120
  if (servers.length === 0) return null;
27799
- const registry = new McpRegistry(stateDir);
28121
+ const registry = new McpRegistry(stateDir, mcpClientFactory({ devApiBases: opts.devApiBases ?? [] }));
27800
28122
  const cache = await readToolCache(stateDir);
27801
28123
  const usable = {};
27802
28124
  for (const s of servers) {
@@ -28189,8 +28511,18 @@ import { lookup as dnsLookup } from "node:dns/promises";
28189
28511
  import { setOutboundResolver } from "@corenel/harness/tools/urlGuard";
28190
28512
  var EXIT_POLICY_PARK_REFUSED = 3;
28191
28513
  var EXIT_BUDGET_EXCEEDED = 4;
28192
- async function agentSetup(argv) {
28193
- setGatewayBase2(await apiBase(flag(argv, "base", "")));
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);
28194
28526
  const token = await nodeAuthToken().getToken();
28195
28527
  if (!token) {
28196
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");
@@ -28232,19 +28564,22 @@ async function agentSetup(argv) {
28232
28564
  let policy;
28233
28565
  let policySource;
28234
28566
  try {
28235
- ({ policy, source: policySource } = await resolvePolicy({
28567
+ const resolved = await resolvePolicy({
28236
28568
  ...hasValueFlag(argv, "policy") ? { flag: flag(argv, "policy", "") } : {},
28237
28569
  cwd: process.cwd()
28238
- }));
28570
+ });
28571
+ ({ policy, source: policySource } = resolved);
28572
+ for (const w of resolved.warnings) process.stderr.write(`${w}
28573
+ `);
28239
28574
  } catch (e) {
28240
28575
  sidecarProcs?.disconnect();
28241
28576
  throw e;
28242
28577
  }
28243
- const mcp = await openCliMcp(configWorkspaceRoot(), policy);
28578
+ const mcp = await openCliMcp(configWorkspaceRoot(), policy, { devApiBases: cliDevRelayBases(gatewayBase) });
28244
28579
  if (mcp) tools.push(...mcp.tools);
28245
28580
  return {
28246
28581
  client: createChatClient2({ getToken: async () => token }),
28247
- model: flag(argv, "model", "gpt-4o-mini"),
28582
+ model: chosenModel ?? flag(argv, "model", DEFAULT_MODEL2),
28248
28583
  system,
28249
28584
  tools,
28250
28585
  identity: identity2,
@@ -28348,10 +28683,10 @@ async function run(argv) {
28348
28683
  * with a real window buys exactly that awareness without turning on
28349
28684
  * rung-1 offload here (this path builds no recall store).
28350
28685
  *
28351
- * No `onCheckpoint`: a one-shot run has no session to write one beside.
28686
+ * No fold storage: a one-shot run has no session to write one beside.
28352
28687
  * It still compacts — surviving the wall matters more than recording why. */
28353
- compression: { mode: "off", contextWindow: resolveContextWindow2(model).window },
28354
- compaction: { summarize: makeSummarizer(client, model) },
28688
+ compression: { mode: "off", contextWindow: resolveContextWindow3(model).window },
28689
+ compaction: { summarize: makeSummarizer(client, model, { contextWindow: resolveContextWindow3(model).window }) },
28355
28690
  // A spawned sub-agent runs under the SAME gate as the parent turn -- never
28356
28691
  // looser, so it can't be used to route around the default-deny above.
28357
28692
  toolCtx: nodeToolCtx(ac.signal, {
@@ -28389,7 +28724,12 @@ async function run(argv) {
28389
28724
  }
28390
28725
  }
28391
28726
  async function interactive(argv) {
28392
- const { client, model, tools, identity: identity2, policy, policySource, memory, mcp, disconnect } = await agentSetup(argv);
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);
28393
28733
  try {
28394
28734
  await runInteractive();
28395
28735
  } finally {
@@ -28404,7 +28744,7 @@ async function interactive(argv) {
28404
28744
  const approvalsContext = { runId: randomUUID(), workspace: basename5(cwd) };
28405
28745
  let modelRates = null;
28406
28746
  let priceTable = /* @__PURE__ */ new Map();
28407
- const resolvedWindow = resolveContextWindow2(model);
28747
+ const resolvedWindow = resolveContextWindow3(model);
28408
28748
  alog("context window", model, resolvedWindow.window, `(${resolvedWindow.source})`);
28409
28749
  const compression = { mode: "balanced", contextWindow: resolvedWindow.window, strategy: "deferred" };
28410
28750
  const priceBase = await apiBase(flag(argv, "base", ""));
@@ -28472,11 +28812,9 @@ async function interactive(argv) {
28472
28812
  const onSessionId = (id) => {
28473
28813
  currentSessionId = id;
28474
28814
  };
28475
- const compaction = {
28476
- summarize: makeSummarizer(client, model),
28477
- onCheckpoint: (body) => makeCheckpointStore(() => files, sessionsRoot()).append(currentSessionId, body)
28478
- };
28479
- 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;
28480
28818
  if (chooseFace(currentFaceEnv(argv)) === "tui") {
28481
28819
  try {
28482
28820
  const { runTui } = await import("@corenel/term/ink");
@@ -28575,6 +28913,7 @@ async function interactive(argv) {
28575
28913
  }
28576
28914
  async function main() {
28577
28915
  const [cmd, ...rest] = process.argv.slice(2);
28916
+ setSourceRun(isSourceRun(import.meta.url));
28578
28917
  setOutboundResolver(async (h) => (await dnsLookup(h, { all: true, verbatim: true })).map((a) => a.address));
28579
28918
  try {
28580
28919
  setAgentLogging(hasBooleanFlag(process.argv, "debug") || process.env.CORENEL_DEBUG === "1");
@@ -28652,7 +28991,7 @@ Logged in. Token saved to ${authTokenPath()}
28652
28991
  await runLogout();
28653
28992
  break;
28654
28993
  case "doctor": {
28655
- process.exitCode = await runDoctor(void 0, void 0, flag(rest, "model", "gpt-4o-mini"), resolveDoctorEnv(rest));
28994
+ process.exitCode = await runDoctor(void 0, void 0, flag(rest, "model", DEFAULT_MODEL2), resolveDoctorEnv(rest));
28656
28995
  break;
28657
28996
  }
28658
28997
  case "approvals":
@@ -28725,5 +29064,7 @@ main().catch((e) => {
28725
29064
  export {
28726
29065
  EXIT_BUDGET_EXCEEDED,
28727
29066
  EXIT_POLICY_PARK_REFUSED,
28728
- buildRunPermission
29067
+ buildRunPermission,
29068
+ replCompaction,
29069
+ replSessionId
28729
29070
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corenel/cli",
3
- "version": "0.7.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.4.0",
13
- "@corenel/harness": "0.11.0",
14
- "@corenel/mcp": "0.3.0",
15
- "@corenel/protocol": "0.10.0",
16
- "@corenel/term": "0.5.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.5.1"
25
+ "@corenel/tools-node": "0.7.1"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"