@corenel/cli 0.2.0 → 0.3.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 (2) hide show
  1. package/dist/cli.js +613 -58
  2. package/package.json +14 -12
package/dist/cli.js CHANGED
@@ -1500,7 +1500,7 @@ var require_stringify = __commonJS({
1500
1500
  props.push(doc.directives.tagString(tag));
1501
1501
  return props.join(" ");
1502
1502
  }
1503
- function stringify2(item, ctx, onComment, onChompKeep) {
1503
+ function stringify3(item, ctx, onComment, onChompKeep) {
1504
1504
  if (identity.isPair(item))
1505
1505
  return item.toString(ctx, onComment, onChompKeep);
1506
1506
  if (identity.isAlias(item)) {
@@ -1529,7 +1529,7 @@ var require_stringify = __commonJS({
1529
1529
  ${ctx.indent}${str5}`;
1530
1530
  }
1531
1531
  exports.createStringifyContext = createStringifyContext;
1532
- exports.stringify = stringify2;
1532
+ exports.stringify = stringify3;
1533
1533
  }
1534
1534
  });
1535
1535
 
@@ -1539,7 +1539,7 @@ var require_stringifyPair = __commonJS({
1539
1539
  "use strict";
1540
1540
  var identity = require_identity();
1541
1541
  var Scalar = require_Scalar();
1542
- var stringify2 = require_stringify();
1542
+ var stringify3 = require_stringify();
1543
1543
  var stringifyComment = require_stringifyComment();
1544
1544
  function stringifyPair({ key, value }, ctx, onComment, onChompKeep) {
1545
1545
  const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx;
@@ -1561,7 +1561,7 @@ var require_stringifyPair = __commonJS({
1561
1561
  });
1562
1562
  let keyCommentDone = false;
1563
1563
  let chompKeep = false;
1564
- let str5 = stringify2.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
1564
+ let str5 = stringify3.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true);
1565
1565
  if (!explicitKey && !ctx.inFlow && str5.length > 1024) {
1566
1566
  if (simpleKeys)
1567
1567
  throw new Error("With simple keys, single line scalar must not span more than 1024 characters");
@@ -1613,7 +1613,7 @@ ${indent}:`;
1613
1613
  ctx.indent = ctx.indent.substring(2);
1614
1614
  }
1615
1615
  let valueCommentDone = false;
1616
- const valueStr = stringify2.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
1616
+ const valueStr = stringify3.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true);
1617
1617
  let ws = " ";
1618
1618
  if (keyComment || vsb || vcb) {
1619
1619
  ws = vsb ? "\n" : "";
@@ -1754,7 +1754,7 @@ var require_addPairToJSMap = __commonJS({
1754
1754
  "use strict";
1755
1755
  var log = require_log();
1756
1756
  var merge = require_merge();
1757
- var stringify2 = require_stringify();
1757
+ var stringify3 = require_stringify();
1758
1758
  var identity = require_identity();
1759
1759
  var toJS = require_toJS();
1760
1760
  function addPairToJSMap(ctx, map, { key, value }) {
@@ -1790,7 +1790,7 @@ var require_addPairToJSMap = __commonJS({
1790
1790
  if (typeof jsKey !== "object")
1791
1791
  return String(jsKey);
1792
1792
  if (identity.isNode(key) && ctx?.doc) {
1793
- const strCtx = stringify2.createStringifyContext(ctx.doc, {});
1793
+ const strCtx = stringify3.createStringifyContext(ctx.doc, {});
1794
1794
  strCtx.anchors = /* @__PURE__ */ new Set();
1795
1795
  for (const node of ctx.anchors.keys())
1796
1796
  strCtx.anchors.add(node.anchor);
@@ -1857,12 +1857,12 @@ var require_stringifyCollection = __commonJS({
1857
1857
  "../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/stringify/stringifyCollection.js"(exports) {
1858
1858
  "use strict";
1859
1859
  var identity = require_identity();
1860
- var stringify2 = require_stringify();
1860
+ var stringify3 = require_stringify();
1861
1861
  var stringifyComment = require_stringifyComment();
1862
1862
  function stringifyCollection(collection, ctx, options) {
1863
1863
  const flow = ctx.inFlow ?? collection.flow;
1864
- const stringify3 = flow ? stringifyFlowCollection : stringifyBlockCollection;
1865
- return stringify3(collection, ctx, options);
1864
+ const stringify4 = flow ? stringifyFlowCollection : stringifyBlockCollection;
1865
+ return stringify4(collection, ctx, options);
1866
1866
  }
1867
1867
  function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) {
1868
1868
  const { indent, options: { commentString } } = ctx;
@@ -1887,7 +1887,7 @@ var require_stringifyCollection = __commonJS({
1887
1887
  }
1888
1888
  }
1889
1889
  chompKeep = false;
1890
- let str6 = stringify2.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
1890
+ let str6 = stringify3.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true);
1891
1891
  if (comment2)
1892
1892
  str6 += stringifyComment.lineComment(str6, itemIndent, commentString(comment2));
1893
1893
  if (chompKeep && comment2)
@@ -1954,7 +1954,7 @@ ${indent}${line}` : "\n";
1954
1954
  }
1955
1955
  if (comment)
1956
1956
  reqNewline = true;
1957
- let str5 = stringify2.stringify(item, itemCtx, () => comment = null);
1957
+ let str5 = stringify3.stringify(item, itemCtx, () => comment = null);
1958
1958
  reqNewline || (reqNewline = lines.length > linesAtValue || str5.includes("\n"));
1959
1959
  if (i < items.length - 1) {
1960
1960
  str5 += ",";
@@ -3315,7 +3315,7 @@ var require_stringifyDocument = __commonJS({
3315
3315
  "../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/stringify/stringifyDocument.js"(exports) {
3316
3316
  "use strict";
3317
3317
  var identity = require_identity();
3318
- var stringify2 = require_stringify();
3318
+ var stringify3 = require_stringify();
3319
3319
  var stringifyComment = require_stringifyComment();
3320
3320
  function stringifyDocument(doc, options) {
3321
3321
  const lines = [];
@@ -3330,7 +3330,7 @@ var require_stringifyDocument = __commonJS({
3330
3330
  }
3331
3331
  if (hasDirectives)
3332
3332
  lines.push("---");
3333
- const ctx = stringify2.createStringifyContext(doc, options);
3333
+ const ctx = stringify3.createStringifyContext(doc, options);
3334
3334
  const { commentString } = ctx.options;
3335
3335
  if (doc.commentBefore) {
3336
3336
  if (lines.length !== 1)
@@ -3352,7 +3352,7 @@ var require_stringifyDocument = __commonJS({
3352
3352
  contentComment = doc.contents.comment;
3353
3353
  }
3354
3354
  const onChompKeep = contentComment ? void 0 : () => chompKeep = true;
3355
- let body = stringify2.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
3355
+ let body = stringify3.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep);
3356
3356
  if (contentComment)
3357
3357
  body += stringifyComment.lineComment(body, "", commentString(contentComment));
3358
3358
  if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") {
@@ -3360,7 +3360,7 @@ var require_stringifyDocument = __commonJS({
3360
3360
  } else
3361
3361
  lines.push(body);
3362
3362
  } else {
3363
- lines.push(stringify2.stringify(doc.contents, ctx));
3363
+ lines.push(stringify3.stringify(doc.contents, ctx));
3364
3364
  }
3365
3365
  if (doc.directives?.docEnd) {
3366
3366
  if (doc.comment) {
@@ -5490,7 +5490,7 @@ var require_cst_scalar = __commonJS({
5490
5490
  var require_cst_stringify = __commonJS({
5491
5491
  "../../node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/dist/parse/cst-stringify.js"(exports) {
5492
5492
  "use strict";
5493
- var stringify2 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
5493
+ var stringify3 = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst);
5494
5494
  function stringifyToken(token) {
5495
5495
  switch (token.type) {
5496
5496
  case "block-scalar": {
@@ -5543,7 +5543,7 @@ var require_cst_stringify = __commonJS({
5543
5543
  res += stringifyToken(value);
5544
5544
  return res;
5545
5545
  }
5546
- exports.stringify = stringify2;
5546
+ exports.stringify = stringify3;
5547
5547
  }
5548
5548
  });
5549
5549
 
@@ -7245,7 +7245,7 @@ var require_public_api = __commonJS({
7245
7245
  }
7246
7246
  return doc;
7247
7247
  }
7248
- function parse2(src, reviver, options) {
7248
+ function parse3(src, reviver, options) {
7249
7249
  let _reviver = void 0;
7250
7250
  if (typeof reviver === "function") {
7251
7251
  _reviver = reviver;
@@ -7264,7 +7264,7 @@ var require_public_api = __commonJS({
7264
7264
  }
7265
7265
  return doc.toJS(Object.assign({ reviver: _reviver }, options));
7266
7266
  }
7267
- function stringify2(value, replacer, options) {
7267
+ function stringify3(value, replacer, options) {
7268
7268
  let _replacer = null;
7269
7269
  if (typeof replacer === "function" || Array.isArray(replacer)) {
7270
7270
  _replacer = replacer;
@@ -7286,10 +7286,10 @@ var require_public_api = __commonJS({
7286
7286
  return value.toString(options);
7287
7287
  return new Document.Document(value, _replacer, options).toString(options);
7288
7288
  }
7289
- exports.parse = parse2;
7289
+ exports.parse = parse3;
7290
7290
  exports.parseAllDocuments = parseAllDocuments;
7291
7291
  exports.parseDocument = parseDocument;
7292
- exports.stringify = stringify2;
7292
+ exports.stringify = stringify3;
7293
7293
  }
7294
7294
  });
7295
7295
 
@@ -7812,7 +7812,7 @@ var require_parse = __commonJS({
7812
7812
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/parse.js"(exports, module) {
7813
7813
  "use strict";
7814
7814
  var SemVer = require_semver();
7815
- var parse2 = (version, options, throwErrors = false) => {
7815
+ var parse3 = (version, options, throwErrors = false) => {
7816
7816
  if (version instanceof SemVer) {
7817
7817
  return version;
7818
7818
  }
@@ -7825,7 +7825,7 @@ var require_parse = __commonJS({
7825
7825
  throw er;
7826
7826
  }
7827
7827
  };
7828
- module.exports = parse2;
7828
+ module.exports = parse3;
7829
7829
  }
7830
7830
  });
7831
7831
 
@@ -7833,9 +7833,9 @@ var require_parse = __commonJS({
7833
7833
  var require_valid = __commonJS({
7834
7834
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/valid.js"(exports, module) {
7835
7835
  "use strict";
7836
- var parse2 = require_parse();
7836
+ var parse3 = require_parse();
7837
7837
  var valid = (version, options) => {
7838
- const v = parse2(version, options);
7838
+ const v = parse3(version, options);
7839
7839
  return v ? v.version : null;
7840
7840
  };
7841
7841
  module.exports = valid;
@@ -7846,9 +7846,9 @@ var require_valid = __commonJS({
7846
7846
  var require_clean = __commonJS({
7847
7847
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/clean.js"(exports, module) {
7848
7848
  "use strict";
7849
- var parse2 = require_parse();
7849
+ var parse3 = require_parse();
7850
7850
  var clean = (version, options) => {
7851
- const s = parse2(version.trim().replace(/^[=v]+/, ""), options);
7851
+ const s = parse3(version.trim().replace(/^[=v]+/, ""), options);
7852
7852
  return s ? s.version : null;
7853
7853
  };
7854
7854
  module.exports = clean;
@@ -7883,10 +7883,10 @@ var require_inc = __commonJS({
7883
7883
  var require_diff = __commonJS({
7884
7884
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/diff.js"(exports, module) {
7885
7885
  "use strict";
7886
- var parse2 = require_parse();
7886
+ var parse3 = require_parse();
7887
7887
  var diff = (version1, version2) => {
7888
- const v1 = parse2(version1, null, true);
7889
- const v2 = parse2(version2, null, true);
7888
+ const v1 = parse3(version1, null, true);
7889
+ const v2 = parse3(version2, null, true);
7890
7890
  const comparison = v1.compare(v2);
7891
7891
  if (comparison === 0) {
7892
7892
  return null;
@@ -7957,9 +7957,9 @@ var require_patch = __commonJS({
7957
7957
  var require_prerelease = __commonJS({
7958
7958
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/prerelease.js"(exports, module) {
7959
7959
  "use strict";
7960
- var parse2 = require_parse();
7960
+ var parse3 = require_parse();
7961
7961
  var prerelease = (version, options) => {
7962
- const parsed = parse2(version, options);
7962
+ const parsed = parse3(version, options);
7963
7963
  return parsed && parsed.prerelease.length ? parsed.prerelease : null;
7964
7964
  };
7965
7965
  module.exports = prerelease;
@@ -8145,7 +8145,7 @@ var require_coerce = __commonJS({
8145
8145
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/coerce.js"(exports, module) {
8146
8146
  "use strict";
8147
8147
  var SemVer = require_semver();
8148
- var parse2 = require_parse();
8148
+ var parse3 = require_parse();
8149
8149
  var { safeRe: re, t } = require_re();
8150
8150
  var coerce = (version, options) => {
8151
8151
  if (version instanceof SemVer) {
@@ -8180,7 +8180,7 @@ var require_coerce = __commonJS({
8180
8180
  const patch = match[4] || "0";
8181
8181
  const prerelease = options.includePrerelease && match[5] ? `-${match[5]}` : "";
8182
8182
  const build = options.includePrerelease && match[6] ? `+${match[6]}` : "";
8183
- return parse2(`${major}.${minor}.${patch}${prerelease}${build}`, options);
8183
+ return parse3(`${major}.${minor}.${patch}${prerelease}${build}`, options);
8184
8184
  };
8185
8185
  module.exports = coerce;
8186
8186
  }
@@ -8190,7 +8190,7 @@ var require_coerce = __commonJS({
8190
8190
  var require_truncate = __commonJS({
8191
8191
  "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/truncate.js"(exports, module) {
8192
8192
  "use strict";
8193
- var parse2 = require_parse();
8193
+ var parse3 = require_parse();
8194
8194
  var constants = require_constants();
8195
8195
  var SemVer = require_semver();
8196
8196
  var truncate = (version, truncation, options) => {
@@ -8202,7 +8202,7 @@ var require_truncate = __commonJS({
8202
8202
  };
8203
8203
  var cloneInputVersion = (version, options) => {
8204
8204
  const versionStringToParse = version instanceof SemVer ? version.version : version;
8205
- return parse2(versionStringToParse, options);
8205
+ return parse3(versionStringToParse, options);
8206
8206
  };
8207
8207
  var doTruncation = (version, truncation) => {
8208
8208
  if (isPrerelease(truncation)) {
@@ -9244,7 +9244,7 @@ var require_semver2 = __commonJS({
9244
9244
  var constants = require_constants();
9245
9245
  var SemVer = require_semver();
9246
9246
  var identifiers = require_identifiers();
9247
- var parse2 = require_parse();
9247
+ var parse3 = require_parse();
9248
9248
  var valid = require_valid();
9249
9249
  var clean = require_clean();
9250
9250
  var inc = require_inc();
@@ -9283,7 +9283,7 @@ var require_semver2 = __commonJS({
9283
9283
  var simplifyRange = require_simplify();
9284
9284
  var subset = require_subset();
9285
9285
  module.exports = {
9286
- parse: parse2,
9286
+ parse: parse3,
9287
9287
  valid,
9288
9288
  clean,
9289
9289
  inc,
@@ -11670,7 +11670,7 @@ var require_parser2 = __commonJS({
11670
11670
  }
11671
11671
  return buf;
11672
11672
  };
11673
- _proto.parse = function parse2() {
11673
+ _proto.parse = function parse3() {
11674
11674
  return new nodes.NodeList(0, 0, this.parseNodes());
11675
11675
  };
11676
11676
  _proto.parseAsRoot = function parseAsRoot() {
@@ -11679,7 +11679,7 @@ var require_parser2 = __commonJS({
11679
11679
  return Parser2;
11680
11680
  }(Obj);
11681
11681
  module.exports = {
11682
- parse: function parse2(src, extensions, opts) {
11682
+ parse: function parse3(src, extensions, opts) {
11683
11683
  var p = new Parser(lexer.lex(src, opts));
11684
11684
  if (extensions !== void 0) {
11685
11685
  p.extensions = extensions;
@@ -13261,7 +13261,7 @@ var require_filters = __commonJS({
13261
13261
  return r.copySafeness(str5, res);
13262
13262
  }
13263
13263
  _exports.indent = indent;
13264
- function join4(arr, del, attr) {
13264
+ function join5(arr, del, attr) {
13265
13265
  del = del || "";
13266
13266
  if (attr) {
13267
13267
  arr = lib.map(arr, function(v) {
@@ -13270,7 +13270,7 @@ var require_filters = __commonJS({
13270
13270
  }
13271
13271
  return arr.join(del);
13272
13272
  }
13273
- _exports.join = join4;
13273
+ _exports.join = join5;
13274
13274
  function last(arr) {
13275
13275
  return arr[arr.length - 1];
13276
13276
  }
@@ -15077,6 +15077,9 @@ async function streamCompletion(client, params, signal, onText) {
15077
15077
  // Undefined is dropped by the SDK's JSON serialization, so an unset cap
15078
15078
  // leaves output unbounded (unchanged behavior).
15079
15079
  max_completion_tokens: params.max_completion_tokens,
15080
+ // Undefined is dropped by the SDK, so an unset temperature leaves the
15081
+ // provider default in place (unchanged behavior).
15082
+ temperature: params.temperature,
15080
15083
  stream: true,
15081
15084
  stream_options: { include_usage: true }
15082
15085
  },
@@ -15380,7 +15383,25 @@ var BUILTIN_NAMESPACES = {
15380
15383
  ask_user: "agent",
15381
15384
  spawn_agent: "agent",
15382
15385
  spawn_agents: "agent",
15383
- todo_write: "agent"
15386
+ todo_write: "agent",
15387
+ // Named here as a backstop; the sidecar's own decls also carry `namespace`,
15388
+ // and an explicit field always wins (resolveNamespace). Both say the same
15389
+ // thing so a sidecar that predates this still lands in the right namespace.
15390
+ sidecar_fs_read_file: "sidecar",
15391
+ sidecar_fs_write_file: "sidecar",
15392
+ sidecar_fs_read_bytes: "sidecar",
15393
+ sidecar_fs_list_dir: "sidecar",
15394
+ sidecar_fs_delete: "sidecar",
15395
+ sidecar_shell_exec: "sidecar",
15396
+ // Host-side orchestration tools. They are not in allTools() - they reach the
15397
+ // agent as remote decls (workerFactory) - but they are still governed by the
15398
+ // policy, so they need a canonical address like everything else.
15399
+ create_strategy: "agent",
15400
+ edit_strategy: "agent",
15401
+ view_strategy: "agent",
15402
+ run_strategy: "agent",
15403
+ list_strategies: "agent",
15404
+ list_saved_strategies: "agent"
15384
15405
  };
15385
15406
  function resolveNamespace(tool) {
15386
15407
  if (tool.namespace) return tool.namespace;
@@ -15400,6 +15421,14 @@ function toolAddress(tool) {
15400
15421
  const m = new RegExp(`^${ns}__?`).exec(leaf);
15401
15422
  if (m) leaf = leaf.slice(m[0].length);
15402
15423
  }
15424
+ if (ns === "sidecar") {
15425
+ for (const p of ["sidecar_fs_", "sidecar_"]) {
15426
+ if (leaf.startsWith(p)) {
15427
+ leaf = leaf.slice(p.length);
15428
+ break;
15429
+ }
15430
+ }
15431
+ }
15403
15432
  return `${ns}:${leaf}`;
15404
15433
  }
15405
15434
 
@@ -15484,7 +15513,8 @@ async function runAgent(args) {
15484
15513
  messages,
15485
15514
  tools: toolDefs.length ? toolDefs : void 0,
15486
15515
  tool_choice: toolDefs.length ? "auto" : void 0,
15487
- max_completion_tokens: args.maxCompletionTokens
15516
+ max_completion_tokens: args.maxCompletionTokens,
15517
+ temperature: args.temperature
15488
15518
  },
15489
15519
  signal,
15490
15520
  (delta) => emit({ type: "assistant-delta", delta })
@@ -15678,7 +15708,7 @@ function reconcileDanglingToolCalls(messages) {
15678
15708
  }
15679
15709
 
15680
15710
  // src/cli.ts
15681
- import { join as join3 } from "node:path";
15711
+ import { join as join4 } from "node:path";
15682
15712
 
15683
15713
  // ../harness/tools/registry.ts
15684
15714
  var registry = /* @__PURE__ */ new Map();
@@ -15932,6 +15962,18 @@ var todoWriteTool = {
15932
15962
  name: "todo_write",
15933
15963
  noOffload: true,
15934
15964
  // the task list is what the user wants to see — keep it inline
15965
+ /* DECLARED, not left to the policy's default. It is a write in name only: it
15966
+ * touches no file, no network and no state outside the run's own task panel,
15967
+ * so there is nothing for a person to approve. Without this it inherited
15968
+ * `policy.defaultPermission` — `ask` under any careful policy — and an agent
15969
+ * doing multi-step work stopped to request approval every time it ticked off a
15970
+ * step, which trains people to approve without reading.
15971
+ *
15972
+ * `permission` sits BELOW an explicit `policy.permissions` entry in the
15973
+ * precedence chain (permission-service.ts), so anyone who genuinely wants to
15974
+ * gate it still can; it only replaces the blanket default. And it is not
15975
+ * `mutates`, so a policy's mutating backstop correctly does not catch it. */
15976
+ permission: "allow",
15935
15977
  description: 'Maintain a visible task list for a multi-step job. Pass the COMPLETE list each time (a rewrite). Keep exactly one task "in_progress" while you work it; mark tasks "completed" as you finish; use "blocked" + blockedBy for tasks waiting on others; nest a subtask one level with parentId. Reuse a short stable id per task across updates so its progress and activity persist. Call it when you start a multi-step task and after each meaningful step.',
15936
15978
  parameters: {
15937
15979
  type: "object",
@@ -16125,17 +16167,29 @@ async function prmdCompileError(content, ctx) {
16125
16167
  }
16126
16168
  var proposeEditTool = {
16127
16169
  name: "propose_edit",
16128
- description: "Propose a replacement for the current editor file. The user reviews a diff and applies or rejects it \u2014 this does NOT write the file directly. Provide the COMPLETE new file content (not a patch) plus a short explanation. Use the editor file shown in the system context as the basis. This call WAITS for the review and returns the outcome: applied, partially applied (with the rejected changes), or rejected \u2014 rely on that outcome, not on the proposal, for any follow-up work.",
16170
+ description: "Propose a replacement for the CURRENTLY OPEN editor file. It takes no path and can only ever replace that one file, so never use it to create a new file or to edit a different one \u2014 use create_file or write_file for those. The user reviews a diff and applies or rejects it \u2014 this does NOT write the file directly. Provide the COMPLETE new file content (not a patch) plus a short explanation. Use the editor file shown in the system context as the basis. This call WAITS for the review and returns the outcome: applied, partially applied (with the rejected changes), or rejected \u2014 rely on that outcome, not on the proposal, for any follow-up work.",
16129
16171
  parameters: {
16130
16172
  type: "object",
16131
16173
  properties: {
16132
16174
  explanation: { type: "string", description: "One line describing the change." },
16133
- new_content: { type: "string", description: "The full proposed file content." }
16175
+ new_content: { type: "string", description: "The full proposed file content." },
16176
+ path: { type: "string", description: "The file being replaced. Must be the file currently open in the editor." }
16134
16177
  },
16135
- required: ["explanation", "new_content"]
16178
+ required: ["explanation", "new_content", "path"]
16136
16179
  },
16137
16180
  async run(args, ctx) {
16138
16181
  const explanation = str4(args, "explanation") || "Proposed an edit";
16182
+ if (!ctx.editorFile) {
16183
+ return "Cannot propose an edit: no file is open in the editor, and propose_edit only replaces the open file. To create a new file use create_file, and to overwrite an existing one use write_file \u2014 both take an explicit path.";
16184
+ }
16185
+ const target = str4(args, "path");
16186
+ if (!target) {
16187
+ return `Cannot propose an edit: propose_edit needs a path, and it must be the open file (${ctx.editorFile}). To create a new file use create_file, or to overwrite a different one use write_file.`;
16188
+ }
16189
+ const norm = (p) => p.trim().replace(/\\/g, "/").replace(/^\.\//, "");
16190
+ if (norm(target) !== norm(ctx.editorFile)) {
16191
+ return `Cannot propose an edit: the open file is ${ctx.editorFile}, but this proposal targets ${target}. propose_edit can only replace the open file. Use create_file for a new file, or write_file to overwrite a different one.`;
16192
+ }
16139
16193
  if (!ctx.reviewEdit || !ctx.callId) {
16140
16194
  return `Proposed an edit: ${explanation}. Awaiting the user's review (apply/reject).`;
16141
16195
  }
@@ -16232,6 +16286,19 @@ var recallResultTool = {
16232
16286
  name: "recall_result",
16233
16287
  noOffload: true,
16234
16288
  // recalling a large result must never re-offload itself
16289
+ /* DECLARED, like todo_write's and ask_user's. This one grants no new access at
16290
+ * all: it reads back a tool result THIS SESSION ALREADY PRODUCED, which means
16291
+ * the original call already passed the policy check that produced it. The
16292
+ * store is session-scoped (`<root>/<sessionId>/blobs/<id>.json`) and the id
16293
+ * shape is validated, so it cannot reach another session's data or escape the
16294
+ * directory.
16295
+ *
16296
+ * The deciding argument is that offloading is a CONTEXT-MANAGEMENT detail,
16297
+ * triggered by size. Gating recall means the same information costs an
16298
+ * approval prompt depending on how large it happened to be - a performance
16299
+ * heuristic leaking into governance, and an approval the user cannot act on
16300
+ * meaningfully because they already approved the thing that produced it. */
16301
+ permission: "allow",
16235
16302
  description: 'Fetch the full content of a tool result that was offloaded to save context. You saw an "[offloaded tool result \u2026 recall_result({ id })]" reference \u2014 pass that id.',
16236
16303
  parameters: {
16237
16304
  type: "object",
@@ -16247,6 +16314,21 @@ var recallResultTool = {
16247
16314
  var askUserTool = {
16248
16315
  name: "ask_user",
16249
16316
  noOffload: true,
16317
+ /* DECLARED, like todo_write's. Gating this behind an approval prompt is asking
16318
+ * the user for permission to ask the user a question — two dialogs for one
16319
+ * decision, the first of which carries no information.
16320
+ *
16321
+ * It changes nothing outside the run: no file, no network, no state. Its whole
16322
+ * effect is to put a question in front of a person who is already there.
16323
+ *
16324
+ * Nothing depended on its previous `ask` tier. The case that looks like it
16325
+ * might — an unattended run parking forever on a question nobody will answer —
16326
+ * is handled by EXCLUSION rather than by permission: CREW_EXCLUDED_TOOLS drops
16327
+ * ask_user from every crew agent (see @corenel/crew's capabilities.ts), and the
16328
+ * daemon's executor honours the same set. On a surface where it IS offered,
16329
+ * someone is by definition attending. And with no `ask` handler wired, `run`
16330
+ * already returns "unavailable in this environment" rather than hanging. */
16331
+ permission: "allow",
16250
16332
  description: "Ask the user one or more questions when you need a decision you cannot make from context \u2014 to choose between approaches, get a missing requirement, or confirm direction. Prefer this over guessing. Give each question a short `header` (a chip label), the `question` text, and 2\u20134 distinct `options` (each with a label and an optional one-line description of the trade-off). Set `multiSelect: true` when more than one option may apply. The user can also type a custom answer. Returns the chosen answers per question.",
16251
16333
  parameters: {
16252
16334
  type: "object",
@@ -16354,14 +16436,14 @@ var spawnAgentsTool = {
16354
16436
  },
16355
16437
  async run(args, ctx) {
16356
16438
  if (!ctx.spawn) return "Sub-agents are not available here (depth limit reached or unsupported context).";
16357
- const spawn = ctx.spawn;
16439
+ const spawn2 = ctx.spawn;
16358
16440
  const raw = Array.isArray(args.tasks) ? args.tasks : [];
16359
16441
  const reqs = raw.map((t) => spawnReq(t && typeof t === "object" ? t : {})).filter((r) => r !== null);
16360
16442
  if (!reqs.length) return "Error: `tasks` must contain at least one task with a non-empty `task` string.";
16361
16443
  const results = await Promise.all(
16362
16444
  reqs.map(async (req, i) => {
16363
16445
  try {
16364
- const r = await spawn(req);
16446
+ const r = await spawn2(req);
16365
16447
  return `### Sub-agent ${i + 1}
16366
16448
  ${r.text}${spawnFooter(r)}`;
16367
16449
  } catch (e) {
@@ -16540,7 +16622,63 @@ Add a constraint :: Add a constraint or rule to the prompt instructions (what to
16540
16622
  "personas/senior-dev.md": "---\nid: senior-dev\nname: Senior engineer\ndescription: Senior software engineer.\n---\nYou are a senior software engineer. Bring that craft to every change:\n- Fit the codebase. Study neighboring code and follow its conventions before writing. Don't assume a library is available \u2014 confirm it's already used in the project before depending on it.\n- Write the minimum that solves the task. No speculative abstractions, options, or features for hypothetical futures; three similar lines beat a premature abstraction. No half-finished or stubbed implementations.\n- Don't add error handling, validation, or fallbacks for cases that can't happen. Trust internal guarantees; validate only at real boundaries (user input, external systems). No backwards-compat shims unless asked.\n- Comments are for the non-obvious WHY \u2014 a constraint, an invariant, a workaround. Don't narrate what the code already says; default to none.\n- Verify your work before calling a change done. If you couldn't verify something, say so plainly.\n- Be security-minded: no injection, no leaked secrets, least privilege.\n",
16541
16623
  "personas/terse.md": "---\nid: terse\nname: Terse\ndescription: Extremely concise.\n---\nBe extremely concise. One sentence per update. No preamble. No summary unless asked.\n",
16542
16624
  "memory/MEMORY.md": "# Memory index\n\nSaved notes appear here, one line each. The agent adds them with save_memory and\nreads them with recall_memory. This is the global (~/.prompd) memory; a project's\nown notes live in ./.prompd/memory.\n",
16543
- "systems/system-base.md": '---\nid: system-base\nname: Operational system prompt\nversion: 1.0.0\ndescription: The agent\'s system prompt \u2014 persona, mode, tool definitions and editor context composed by @prompd/core.\nparameters:\n - name: persona\n type: string\n description: The selected persona\'s voice/role text, placed first.\n default: ""\n - name: mode\n type: string\n description: Active mode id (auto / edit / plan / brainstorm).\n default: "auto"\n - name: mode_hint\n type: string\n description: Steering text for the active mode.\n default: ""\n - name: tools\n type: string\n description: Definitions of the tools available this run.\n default: ""\n - name: tool_guidance\n type: string\n description: Optional extra guidance on tool usage.\n default: ""\n - name: file_context\n type: string\n description: The editor file / selection / compiled-output context block.\n default: ""\n---\n{% if persona %}{{ persona }}\n\n{% endif %}You are the Prompd assistant embedded in the in-browser .prmd editor. You help the user author, compile, and improve .prmd/.md prompts (YAML frontmatter + Nunjucks body with typed parameters). When the user asks about the .prmd format itself \u2014 frontmatter fields, parameter types (including enums), date defaults, inheritance, or templating \u2014 the canonical reference is `languages/prmd.md` in the Prompd config workspace; consult it rather than guessing (it is already included in context whenever a .prmd/.md file is open).\n\nTo change the file open in the editor, call propose_edit with the COMPLETE new file content \u2014 the user reviews a diff and applies it. To read or change other files in the workspace, use the file tools (writes ask the user for permission first). Use compile_prompd to preview rendered output and search_packages to find registry packages. For any multi-step job, maintain a visible task list with todo_write: lay out the steps, keep one task in_progress, and mark each completed as you go. Prefer calling a tool over guessing, and be concise.\n\n## Tools available this run\n{{ tools }}\n{% if tool_guidance %}\n\n{{ tool_guidance }}\n{% endif %}\n{% if mode_hint %}\n\n## Mode\n{{ mode_hint }}\n{% endif %}\n{% if file_context %}\n\n{{ file_context }}\n{% endif %}\n',
16625
+ "systems/system-base.md": `---
16626
+ id: system-base
16627
+ name: Operational system prompt
16628
+ version: 1.0.0
16629
+ description: The agent's system prompt \u2014 persona, mode, tool definitions and editor context composed by @prompd/core.
16630
+ parameters:
16631
+ - name: persona
16632
+ type: string
16633
+ description: The selected persona's voice/role text, placed first.
16634
+ default: ""
16635
+ - name: mode
16636
+ type: string
16637
+ description: Active mode id (auto / edit / plan / brainstorm).
16638
+ default: "auto"
16639
+ - name: mode_hint
16640
+ type: string
16641
+ description: Steering text for the active mode.
16642
+ default: ""
16643
+ - name: tools
16644
+ type: string
16645
+ description: Definitions of the tools available this run.
16646
+ default: ""
16647
+ - name: tool_guidance
16648
+ type: string
16649
+ description: Optional extra guidance on tool usage.
16650
+ default: ""
16651
+ - name: file_context
16652
+ type: string
16653
+ description: The editor file / selection / compiled-output context block.
16654
+ default: ""
16655
+ - name: app
16656
+ type: string
16657
+ description: Product name of the host application, used for the agent's own identity.
16658
+ default: "Prompd"
16659
+ ---
16660
+ {% if persona %}{{ persona }}
16661
+
16662
+ {% endif %}You are the {{ app }} assistant embedded in the in-browser .prmd editor. You help the user author, compile, and improve .prmd/.md prompts (YAML frontmatter + Nunjucks body with typed parameters). When the user asks about the .prmd format itself \u2014 frontmatter fields, parameter types (including enums), date defaults, inheritance, or templating \u2014 the canonical reference is \`languages/prmd.md\` in the {{ app }} config workspace; consult it rather than guessing (it is already included in context whenever a .prmd/.md file is open).
16663
+
16664
+ To change the file open in the editor, call propose_edit with the COMPLETE new file content \u2014 the user reviews a diff and applies it. To read or change other files in the workspace, use the file tools (writes ask the user for permission first). Use compile_prompd to preview rendered output and search_packages to find registry packages. For any multi-step job, maintain a visible task list with todo_write: lay out the steps, keep one task in_progress, and mark each completed as you go. Prefer calling a tool over guessing, and be concise.
16665
+
16666
+ ## Tools available this run
16667
+ {{ tools }}
16668
+ {% if tool_guidance %}
16669
+
16670
+ {{ tool_guidance }}
16671
+ {% endif %}
16672
+ {% if mode_hint %}
16673
+
16674
+ ## Mode
16675
+ {{ mode_hint }}
16676
+ {% endif %}
16677
+ {% if file_context %}
16678
+
16679
+ {{ file_context }}
16680
+ {% endif %}
16681
+ `,
16544
16682
  "systems/system-pdflow.md": '---\nid: system-pdflow\nname: Workflow file system prompt\nversion: 1.0.0\ndescription: Adds .pdflow format knowledge (node taxonomy, edges, parameters) on top of the base system prompt.\ninherits: system-base.md\n---\n\n## Active file format \u2014 Prompd workflow (.pdflow)\n\nThe open file is a **Prompd workflow**: JSON with `{ version, metadata, parameters[], nodes[], edges[] }`. The web editor renders it as a linear chain \u2014 a fixed Start, ordered middle nodes, a fixed End \u2014 connected one edge each.\n\nEach node is `{ id, type, position, data }`. Node types and their key `data`:\n\n- `trigger` \u2014 Start (entry point). Exactly one.\n- `prompt` \u2014 runs a `.prmd`/`.md` prompt. `data.sourceType` is `\'file\'` (`data.source` = a WORKSPACE-RELATIVE path) or `\'raw\'` (`data.rawPrompt`). Optional `data.model` / `data.providerNodeId`.\n- `user-input` \u2014 pauses to collect input. `data.prompt`, `data.inputType` (`text` / `textarea` / `choice` / `confirm` / `number`).\n- `guardrail` \u2014 validates/gates. `data.systemPrompt`, `data.passExpression` or `data.scoreThreshold`.\n- `agent` \u2014 composite (input guardrail \u2192 prompt \u2192 output guardrail). `data.systemPrompt`, `data.userPrompt`, `data.maxIterations`; internal guardrails in `data.inputGuardrail` / `data.outputGuardrail` (each `{ enabled, preset|systemPrompt, passExpression|scoreThreshold }`).\n- `transformer` \u2014 JSON\u2192JSON map. `data.mode: \'template\'` with `data.template` (`{{ input }}` substitution).\n- `callback` / `checkpoint` \u2014 debug/observation point (logs node I/O).\n- `output` \u2014 End (workflow result). Exactly one.\n- `provider` \u2014 one off-chain node holding the canvas-wide provider/model; nodes reference it via `data.providerNodeId`.\n\nEdges thread each node\'s output to the next; the runner exposes `{{ input }}` / `{{ previous_output }}` to downstream nodes.\n\n### Example (minimal chain)\n\n```\n{\n "version": "1.0",\n "metadata": { "name": "summarize" },\n "parameters": [],\n "nodes": [\n { "id": "start", "type": "trigger", "position": { "x": 0, "y": 0 }, "data": {} },\n { "id": "p1", "type": "prompt", "position": { "x": 0, "y": 120 }, "data": { "sourceType": "file", "source": "prompts/summarize.prmd" } },\n { "id": "end", "type": "output", "position": { "x": 0, "y": 240 }, "data": {} }\n ],\n "edges": [\n { "id": "e1", "source": "start", "target": "p1" },\n { "id": "e2", "source": "p1", "target": "end" }\n ]\n}\n```\n\nTo edit the workflow, modify this JSON with the file tools (paths are workspace-relative). Keep exactly one `trigger` and one `output`.\n',
16545
16683
  "systems/system-prmd-md.md": '---\nid: system-prmd-md\nname: Prompt file system prompt (.prmd / .md)\nversion: 1.0.0\ninherits: ./system-base.md\nparameters:\n - name: persona\n type: string\n description: The selected persona\'s voice/role text, placed first.\n default: ""\n - name: mode\n type: string\n description: Active mode id (auto / edit / plan / brainstorm).\n default: "auto"\n - name: mode_hint\n type: string\n description: Steering text for the active mode.\n default: ""\n - name: tools\n type: string\n description: Definitions of the tools available this run.\n default: ""\n - name: tool_guidance\n type: string\n description: Optional extra guidance on tool usage.\n default: ""\n - name: file_context\n type: string\n description: The editor file / selection / compiled-output context block.\n default: ""\n---\n\n{% include "../languages/prmd.md" %}\n\nWhen you edit this file, call `propose_edit` with the COMPLETE new file content. Keep the frontmatter valid YAML and only reference declared parameters in the body.\n',
16546
16684
  "templates/index.json": '[\n {\n "_comment": "EXAMPLE of a data-driven New File type. Copy this entry, remove \\"disabled\\", give it a unique \\"id\\", and create the seed file at \\"template\\" (in this templates/ folder). Tokens __ID__ / __NAME__ are substituted on create. Icon is a ui-kit name (e.g. IcFileCode, IcUser, IcFlow) or \\"svg:<markup>\\". See docs/file-types.md.",\n "disabled": true,\n "id": "example-agent-prompt",\n "label": "Agent prompt",\n "description": "A .prmd tuned for tool-using agents.",\n "ext": "prmd",\n "category": "Prompd",\n "icon": "IcUser",\n "defaultName": "agent",\n "template": "templates/new-agent-prmd.prmd",\n "accent": true\n }\n]\n',
@@ -16963,6 +17101,10 @@ Notes you've saved. Call recall_memory to read one in full; save_memory to add o
16963
17101
 
16964
17102
  ${memoryIdx.trim()}` : "";
16965
17103
  return compileConfigTemplate(systemPath, {
17104
+ // Who the agent says it IS. Injected rather than written into the prompt,
17105
+ // because the same shipped default is seeded into every host's config
17106
+ // workspace and each one carries a different product name.
17107
+ app: promptHost().appName?.() || "Prompd",
16966
17108
  persona: persona.trim(),
16967
17109
  mode,
16968
17110
  mode_hint,
@@ -20235,13 +20377,23 @@ async function deviceLogin(opts) {
20235
20377
  const f = opts.fetchImpl ?? fetch;
20236
20378
  const base = opts.base.replace(/\/$/, "");
20237
20379
  const post = async (path, body) => {
20238
- const res = await f(`${base}${path}`, {
20380
+ const url = `${base}${path}`;
20381
+ const res = await f(url, {
20239
20382
  method: "POST",
20240
20383
  headers: { "Content-Type": "application/json" },
20241
20384
  body: JSON.stringify(body),
20242
20385
  signal: opts.signal
20243
20386
  });
20244
- return await res.json();
20387
+ const text = await res.text();
20388
+ try {
20389
+ return JSON.parse(text);
20390
+ } catch {
20391
+ const looksLikeHtml = /^\s*</.test(text);
20392
+ throw new Error(
20393
+ `${url} answered ${res.status} with ${looksLikeHtml ? "an HTML page" : "a non-JSON body"}, not JSON.` + (res.status === 404 ? `
20394
+ That usually means --base is wrong. The device endpoints live under /api \u2014 try --base ${base.replace(/\/api$/, "")}/api` : "")
20395
+ );
20396
+ }
20245
20397
  };
20246
20398
  const dc = await post("/oauth/device/code", { client_id: "corenel-cli", scope: "agent" });
20247
20399
  if (!dc.device_code) throw new Error("device authorization failed");
@@ -20268,11 +20420,383 @@ async function deviceLogin(opts) {
20268
20420
  }
20269
20421
  }
20270
20422
 
20423
+ // ../crew/frontmatter.ts
20424
+ var import_yaml = __toESM(require_dist(), 1);
20425
+ var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
20426
+ function parseFrontmatter2(text) {
20427
+ const m = text.match(FRONTMATTER);
20428
+ if (!m) return { meta: {}, body: text };
20429
+ let meta = {};
20430
+ try {
20431
+ const parsed = import_yaml.default.parse(m[1]);
20432
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
20433
+ meta = parsed;
20434
+ }
20435
+ } catch {
20436
+ meta = {};
20437
+ }
20438
+ return { meta, body: m[2] };
20439
+ }
20440
+
20441
+ // ../crew/policy.ts
20442
+ var import_yaml2 = __toESM(require_dist(), 1);
20443
+ var LEVELS = ["allow", "ask", "deny"];
20444
+ var UNATTENDED = ["deny", "park", "allow"];
20445
+ function asLevel(v) {
20446
+ return typeof v === "string" && LEVELS.includes(v) ? v : void 0;
20447
+ }
20448
+ function asStringArray(v) {
20449
+ if (!Array.isArray(v)) return void 0;
20450
+ const out = v.filter((x) => typeof x === "string");
20451
+ return out.length ? out : void 0;
20452
+ }
20453
+ function coercePermissions(v) {
20454
+ const out = {};
20455
+ if (v && typeof v === "object" && !Array.isArray(v)) {
20456
+ for (const [k, val] of Object.entries(v)) {
20457
+ const lvl = asLevel(val);
20458
+ if (lvl) out[k] = lvl;
20459
+ }
20460
+ }
20461
+ return out;
20462
+ }
20463
+ function parsePolicyYaml(text, agentName) {
20464
+ let raw = {};
20465
+ if (text.trim()) {
20466
+ try {
20467
+ const parsed = import_yaml2.default.parse(text);
20468
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
20469
+ raw = parsed;
20470
+ }
20471
+ } catch {
20472
+ raw = {};
20473
+ }
20474
+ }
20475
+ const toolsAllow = raw.tools && typeof raw.tools === "object" ? asStringArray(raw.tools.allow) : void 0;
20476
+ const toolsDeny = raw.tools && typeof raw.tools === "object" ? asStringArray(raw.tools.deny) : void 0;
20477
+ const rawUnattended = raw.unattended;
20478
+ const unattended = typeof rawUnattended === "string" && UNATTENDED.includes(rawUnattended) ? rawUnattended : "deny";
20479
+ const rawPark = raw.parkTimeoutMs;
20480
+ const parkTimeoutMs = typeof rawPark === "number" && Number.isFinite(rawPark) && rawPark > 0 ? rawPark : void 0;
20481
+ const policy = {
20482
+ version: 1,
20483
+ name: typeof raw.name === "string" ? raw.name : `crew:${agentName}`,
20484
+ description: typeof raw.description === "string" ? raw.description : void 0,
20485
+ onViolation: raw.onViolation === "warn" ? "warn" : "stop",
20486
+ defaultPermission: asLevel(raw.defaultPermission) ?? "ask",
20487
+ mutatingDefault: asLevel(raw.mutatingDefault) ?? "deny",
20488
+ permissions: coercePermissions(raw.permissions),
20489
+ maxTurns: typeof raw.maxTurns === "number" ? raw.maxTurns : void 0,
20490
+ maxToolCalls: typeof raw.maxToolCalls === "number" ? raw.maxToolCalls : void 0,
20491
+ unattended,
20492
+ parkTimeoutMs
20493
+ };
20494
+ if (toolsAllow || toolsDeny) {
20495
+ policy.tools = { ...toolsAllow ? { allow: toolsAllow } : {}, ...toolsDeny ? { deny: toolsDeny } : {} };
20496
+ }
20497
+ return policy;
20498
+ }
20499
+
20500
+ // ../crew/paths.ts
20501
+ var CREW_SEG = "crew";
20502
+ var NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
20503
+ function isValidAgentName(name) {
20504
+ return NAME_RE.test(name);
20505
+ }
20506
+
20507
+ // ../crew/store.ts
20508
+ var import_yaml3 = __toESM(require_dist(), 1);
20509
+ var TRIGGER_TYPES = [
20510
+ "manual",
20511
+ "file-change",
20512
+ "run-complete",
20513
+ "interval-while-open",
20514
+ "app-init",
20515
+ "workspace-init",
20516
+ "schedule",
20517
+ "continuous",
20518
+ "self-directed",
20519
+ "external-webhook"
20520
+ ];
20521
+ async function readOr(files, path, fallback = "") {
20522
+ try {
20523
+ return await files.read(path);
20524
+ } catch {
20525
+ return fallback;
20526
+ }
20527
+ }
20528
+ function parseTriggers(meta) {
20529
+ const raw = meta.triggers;
20530
+ if (!Array.isArray(raw)) return [];
20531
+ const out = [];
20532
+ for (const item of raw) {
20533
+ if (item && typeof item === "object") {
20534
+ const t = item.type;
20535
+ if (typeof t === "string" && TRIGGER_TYPES.includes(t)) {
20536
+ const cfg = item.config;
20537
+ out.push({
20538
+ type: t,
20539
+ config: cfg && typeof cfg === "object" && !Array.isArray(cfg) ? cfg : void 0
20540
+ });
20541
+ }
20542
+ }
20543
+ }
20544
+ return out;
20545
+ }
20546
+ function parseBudget(meta) {
20547
+ const raw = meta.budget;
20548
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
20549
+ const o = raw;
20550
+ const out = {};
20551
+ for (const k of ["maxUsd", "maxTokens", "maxMs"]) {
20552
+ const v = o[k];
20553
+ if (typeof v === "number" && v > 0) out[k] = v;
20554
+ }
20555
+ return out;
20556
+ }
20557
+ function parseCanCall(meta) {
20558
+ const raw = meta.canCall;
20559
+ if (!Array.isArray(raw)) return [];
20560
+ return raw.filter((x) => typeof x === "string" && isValidAgentName(x));
20561
+ }
20562
+ function parseSettings(text) {
20563
+ try {
20564
+ const raw = JSON.parse(text);
20565
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
20566
+ const o = raw;
20567
+ return {
20568
+ model: typeof o.model === "string" ? o.model : void 0,
20569
+ temperature: typeof o.temperature === "number" ? o.temperature : void 0
20570
+ };
20571
+ }
20572
+ } catch {
20573
+ }
20574
+ return {};
20575
+ }
20576
+ async function loadCrewAgentAt(files, root, name, scope = "workspace") {
20577
+ if (!isValidAgentName(name)) throw new Error(`invalid crew agent name: ${name}`);
20578
+ const dir = `${root}/${name}`;
20579
+ const { meta, body } = parseFrontmatter2(await readOr(files, `${dir}/AGENT.md`));
20580
+ return {
20581
+ name,
20582
+ jobTitle: typeof meta.jobTitle === "string" ? meta.jobTitle : "",
20583
+ description: typeof meta.description === "string" ? meta.description : "",
20584
+ scope,
20585
+ enabled: meta.enabled !== false,
20586
+ // absent/anything-but-false -> enabled
20587
+ triggers: parseTriggers(meta),
20588
+ instructions: body,
20589
+ soul: await readOr(files, `${dir}/SOUL.md`),
20590
+ persona: await readOr(files, `${dir}/PERSONA.md`),
20591
+ policy: parsePolicyYaml(await readOr(files, `${dir}/POLICY.yaml`), name),
20592
+ settings: parseSettings(await readOr(files, `${dir}/settings.json`, "{}")),
20593
+ budget: parseBudget(meta),
20594
+ canCall: parseCanCall(meta)
20595
+ };
20596
+ }
20597
+ async function loadCrewAgentAtIfExists(files, root, name, scope = "workspace") {
20598
+ if (!isValidAgentName(name)) return null;
20599
+ try {
20600
+ const st = await files.stat(`${root}/${name}/AGENT.md`);
20601
+ if (st?.kind !== "file") return null;
20602
+ return await loadCrewAgentAt(files, root, name, scope);
20603
+ } catch {
20604
+ return null;
20605
+ }
20606
+ }
20607
+
20608
+ // ../harness/guardrail/serialize.ts
20609
+ var import_yaml4 = __toESM(require_dist(), 1);
20610
+
20611
+ // ../crew/runtime.ts
20612
+ function buildSystemExtra(def) {
20613
+ const parts = [];
20614
+ if (def.jobTitle?.trim()) parts.push(`--- Role ---
20615
+ ${def.jobTitle.trim()}`);
20616
+ if (def.soul.trim()) parts.push(`--- Soul ---
20617
+ ${def.soul.trim()}`);
20618
+ if (def.persona.trim()) parts.push(`--- Persona ---
20619
+ ${def.persona.trim()}`);
20620
+ if (def.instructions.trim()) parts.push(`--- Instructions ---
20621
+ ${def.instructions.trim()}`);
20622
+ return parts.join("\n\n");
20623
+ }
20624
+
20625
+ // src/runAgent.ts
20626
+ function workspaceStateDir() {
20627
+ const fromEnv = process.env.CORENEL_STATE_DIR;
20628
+ return fromEnv && /^\.[A-Za-z0-9][A-Za-z0-9._-]*$/.test(fromEnv) ? fromEnv : stateDirName();
20629
+ }
20630
+ function eventLine(e) {
20631
+ try {
20632
+ return JSON.stringify(e);
20633
+ } catch {
20634
+ const type = e.type;
20635
+ return JSON.stringify({ type: typeof type === "string" ? type : "unknown", unserializable: true });
20636
+ }
20637
+ }
20638
+ async function runCrewAgentCli(opts) {
20639
+ const files = new NodeFileService(opts.cwd);
20640
+ const root = `${workspaceStateDir()}/${CREW_SEG}`;
20641
+ const def = await loadCrewAgentAtIfExists(files, root, opts.agent, "workspace");
20642
+ if (!def) {
20643
+ opts.warn(`no such agent "${opts.agent}" under ${opts.cwd}/${root}`);
20644
+ return 2;
20645
+ }
20646
+ if (!def.enabled) {
20647
+ opts.warn(`agent "${opts.agent}" is disabled; enable it before running`);
20648
+ return 2;
20649
+ }
20650
+ setGatewayBase(opts.base ?? process.env.CORENEL_API_BASE ?? "https://api.corenel.ai/api");
20651
+ const token = await nodeAuthToken().getToken();
20652
+ if (!token) {
20653
+ opts.warn("not signed in: set CORENEL_TOKEN, or run `corenel login` on this machine");
20654
+ return 2;
20655
+ }
20656
+ registerGuard(tokenGuard(() => token));
20657
+ const tools = allTools();
20658
+ const extra = buildSystemExtra(def);
20659
+ let system = extra;
20660
+ try {
20661
+ const base = await composeAgentSystem({
20662
+ mode: "auto",
20663
+ tools: tools.map((t) => ({ name: t.name, description: t.description }))
20664
+ });
20665
+ system = extra ? `${base}
20666
+
20667
+ ${extra}` : base;
20668
+ } catch {
20669
+ }
20670
+ const realConsole = { log: console.log, info: console.info, warn: console.warn, debug: console.debug };
20671
+ const toStderr = (...parts) => {
20672
+ opts.warn(parts.map((x) => typeof x === "string" ? x : JSON.stringify(x)).join(" "));
20673
+ };
20674
+ console.log = toStderr;
20675
+ console.info = toStderr;
20676
+ console.warn = toStderr;
20677
+ console.debug = toStderr;
20678
+ const ac = new AbortController();
20679
+ try {
20680
+ const result = await runAgent({
20681
+ client: createChatClient({ getToken: async () => token }),
20682
+ model: opts.model ?? def.settings.model ?? "gpt-4o-mini",
20683
+ system,
20684
+ messages: [{ role: "user", content: opts.input }],
20685
+ tools,
20686
+ toolCtx: nodeToolCtx(ac.signal, { files }),
20687
+ policy: def.policy,
20688
+ signal: ac.signal,
20689
+ onEvent: (e) => opts.emit(eventLine(e))
20690
+ });
20691
+ opts.emit(eventLine({ type: "done", text: result.text }));
20692
+ return 0;
20693
+ } catch (e) {
20694
+ opts.warn(`run failed: ${e instanceof Error ? e.message : String(e)}`);
20695
+ return 1;
20696
+ } finally {
20697
+ Object.assign(console, realConsole);
20698
+ }
20699
+ }
20700
+
20701
+ // src/sidecar.ts
20702
+ import { spawn } from "node:child_process";
20703
+ import { createRequire } from "node:module";
20704
+ import { existsSync, readFileSync } from "node:fs";
20705
+ import { dirname as dirname2, join as join3 } from "node:path";
20706
+ import { fileURLToPath } from "node:url";
20707
+ function sidecarArgs(rest) {
20708
+ return rest[0] === "start" ? rest.slice(1) : rest;
20709
+ }
20710
+ function findSidecarEntry(look = {}) {
20711
+ const exists = look.exists ?? existsSync;
20712
+ const req = look.resolve ?? createRequire(import.meta.url).resolve;
20713
+ try {
20714
+ const direct = req("@corenel/sidecar/dist/cli.js");
20715
+ if (exists(direct)) return direct;
20716
+ } catch {
20717
+ }
20718
+ let dir = look.fromDir ?? dirname2(fileURLToPath(import.meta.url));
20719
+ for (let up = 0; up < 8; up++) {
20720
+ const candidate = join3(dir, "node_modules", "@corenel", "sidecar", "dist", "cli.js");
20721
+ if (exists(candidate)) return candidate;
20722
+ const parent = dirname2(dir);
20723
+ if (parent === dir) break;
20724
+ dir = parent;
20725
+ }
20726
+ return null;
20727
+ }
20728
+ function versionAt(entry, read = readFileSyncUtf8) {
20729
+ try {
20730
+ const parsed = JSON.parse(read(join3(dirname2(dirname2(entry)), "package.json")));
20731
+ const v = parsed.version;
20732
+ return typeof v === "string" ? v : "?";
20733
+ } catch {
20734
+ return "?";
20735
+ }
20736
+ }
20737
+ function readFileSyncUtf8(p) {
20738
+ return readFileSync(p, "utf8");
20739
+ }
20740
+ var SIDECAR_MISSING = [
20741
+ "the sidecar is not installed.",
20742
+ "",
20743
+ " npm install -g @corenel/sidecar",
20744
+ " pnpm add -g @corenel/sidecar",
20745
+ "",
20746
+ "It ships separately because it is a server with its own release line.",
20747
+ "Once installed, `corenel sidecar \u2026` and `corenel-sidecar \u2026` are the same thing."
20748
+ ].join("\n");
20749
+ async function proxyToSidecar(rest, look = {}) {
20750
+ const entry = findSidecarEntry(look);
20751
+ if (!entry) {
20752
+ process.stderr.write(`corenel: ${SIDECAR_MISSING}
20753
+ `);
20754
+ return 127;
20755
+ }
20756
+ process.stderr.write(`corenel: sidecar ${versionAt(entry)} (${entry})
20757
+ `);
20758
+ const child = spawn(process.execPath, [entry, ...sidecarArgs(rest)], {
20759
+ stdio: "inherit",
20760
+ // So `corenel sidecar --help` prints `corenel sidecar …` in its usage rather
20761
+ // than the standalone name the user did not type.
20762
+ env: { ...process.env, CORENEL_ARGV0: "corenel sidecar" }
20763
+ });
20764
+ const forward = (sig) => () => {
20765
+ if (!child.killed) child.kill(sig);
20766
+ };
20767
+ const onInt = forward("SIGINT");
20768
+ const onTerm = forward("SIGTERM");
20769
+ process.on("SIGINT", onInt);
20770
+ process.on("SIGTERM", onTerm);
20771
+ try {
20772
+ return await new Promise((resolve2, reject) => {
20773
+ child.on("error", reject);
20774
+ child.on("exit", (code, signal) => {
20775
+ resolve2(code ?? (signal === "SIGINT" ? 130 : signal ? 143 : 1));
20776
+ });
20777
+ });
20778
+ } finally {
20779
+ process.off("SIGINT", onInt);
20780
+ process.off("SIGTERM", onTerm);
20781
+ }
20782
+ }
20783
+
20271
20784
  // src/cli.ts
20272
20785
  function flag(argv, name, fallback) {
20273
20786
  const i = argv.indexOf(`--${name}`);
20274
20787
  return i >= 0 && argv[i + 1] ? argv[i + 1] : fallback;
20275
20788
  }
20789
+ function positional(argv) {
20790
+ const out = [];
20791
+ for (let i = 0; i < argv.length; i++) {
20792
+ if (argv[i].startsWith("--")) {
20793
+ i++;
20794
+ continue;
20795
+ }
20796
+ out.push(argv[i]);
20797
+ }
20798
+ return out;
20799
+ }
20276
20800
  function promptOf(argv) {
20277
20801
  const parts = [];
20278
20802
  for (let i = 0; i < argv.length; i++) {
@@ -20299,7 +20823,7 @@ async function run(argv) {
20299
20823
  registerGuard(tokenGuard(() => token));
20300
20824
  const client = createChatClient({ getToken: async () => token });
20301
20825
  const tools = allTools();
20302
- setPromptHost(nodePromptHost(new NodeFileService(join3(corenelDir(), "config"))));
20826
+ setPromptHost(nodePromptHost(new NodeFileService(join4(corenelDir(), "config"))));
20303
20827
  let system = "You are corenel, a concise CLI assistant.";
20304
20828
  try {
20305
20829
  system = await composeAgentSystem({ mode: "auto", tools: tools.map((t) => ({ name: t.name, description: t.description })) });
@@ -20323,8 +20847,32 @@ async function main() {
20323
20847
  case "run":
20324
20848
  await run(rest);
20325
20849
  break;
20850
+ case "run-agent": {
20851
+ const agent = positional(rest)[0];
20852
+ if (!agent) {
20853
+ process.stderr.write('usage: corenel run-agent <name> --input "<text>" [--model M] [--base URL]\n');
20854
+ process.exit(2);
20855
+ }
20856
+ const code = await runCrewAgentCli({
20857
+ cwd: process.cwd(),
20858
+ agent,
20859
+ input: flag(rest, "input", ""),
20860
+ ...rest.includes("--model") ? { model: flag(rest, "model", "") } : {},
20861
+ ...rest.includes("--base") ? { base: flag(rest, "base", "") } : {},
20862
+ emit: (line) => process.stdout.write(`${line}
20863
+ `),
20864
+ warn: (line) => process.stderr.write(`corenel: ${line}
20865
+ `)
20866
+ });
20867
+ process.exitCode = code;
20868
+ break;
20869
+ }
20870
+ case "sidecar": {
20871
+ process.exitCode = await proxyToSidecar(rest);
20872
+ break;
20873
+ }
20326
20874
  case "login": {
20327
- const base = flag(rest, "base", process.env.CORENEL_API_BASE || "https://api.corenel.ai");
20875
+ const base = flag(rest, "base", process.env.CORENEL_API_BASE || "https://api.corenel.ai/api");
20328
20876
  await deviceLogin({ base, store: writeAuthToken });
20329
20877
  process.stdout.write(`
20330
20878
  Logged in. Token saved to ${authTokenPath()}
@@ -20337,8 +20885,13 @@ Logged in. Token saved to ${authTokenPath()}
20337
20885
  "",
20338
20886
  " corenel login [--base URL] OAuth device flow -> ~/.corenel/token",
20339
20887
  ' corenel run "<prompt>" [--model M] [--base URL] one-shot turn (needs a login or CORENEL_TOKEN)',
20888
+ ' corenel run-agent <name> --input "<text>" run a crew agent; events as JSON Lines on stdout',
20889
+ " corenel sidecar [start] [flags] run the sidecar on this machine (needs @corenel/sidecar)",
20890
+ "",
20891
+ " `corenel sidecar --help` lists its flags. It is also installed standalone",
20892
+ " as `corenel-sidecar`, for machines that run only the sidecar.",
20340
20893
  "",
20341
- " (ask/chat + start --sidecar to follow.)",
20894
+ " (ask/chat to follow.)",
20342
20895
  ""
20343
20896
  ].join("\n"));
20344
20897
  }
@@ -20352,5 +20905,7 @@ function friendlyError(msg) {
20352
20905
  main().catch((e) => {
20353
20906
  process.stderr.write(`corenel: ${friendlyError(e instanceof Error ? e.message : String(e))}
20354
20907
  `);
20355
- process.exit(1);
20908
+ process.exitCode = 1;
20909
+ const bail = setTimeout(() => process.exit(1), 2e3);
20910
+ bail.unref();
20356
20911
  });
package/package.json CHANGED
@@ -1,22 +1,30 @@
1
1
  {
2
2
  "name": "@corenel/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.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": {
7
7
  "corenel": "dist/cli.js"
8
8
  },
9
+ "scripts": {
10
+ "typecheck": "tsc --noEmit",
11
+ "start": "tsx src/cli.ts",
12
+ "verify": "tsx src/verify.ts",
13
+ "build": "node build.mjs",
14
+ "prepublishOnly": "node build.mjs"
15
+ },
9
16
  "dependencies": {
10
17
  "openai": "^4.77.0"
11
18
  },
12
19
  "devDependencies": {
20
+ "@corenel/crew": "workspace:*",
21
+ "@corenel/harness": "workspace:*",
22
+ "@corenel/protocol": "workspace:*",
23
+ "@corenel/tools-node": "workspace:*",
13
24
  "esbuild": "^0.21.5",
14
25
  "@types/node": "^24.13.1",
15
26
  "tsx": "^4.19.2",
16
- "typescript": "^5.9.3",
17
- "@corenel/harness": "0.2.0",
18
- "@corenel/tools-node": "0.2.0",
19
- "@corenel/protocol": "0.2.0"
27
+ "typescript": "^5.9.3"
20
28
  },
21
29
  "publishConfig": {
22
30
  "access": "public"
@@ -28,11 +36,5 @@
28
36
  ],
29
37
  "engines": {
30
38
  "node": ">=18"
31
- },
32
- "scripts": {
33
- "typecheck": "tsc --noEmit",
34
- "start": "tsx src/cli.ts",
35
- "verify": "tsx src/verify.ts",
36
- "build": "node build.mjs"
37
39
  }
38
- }
40
+ }