@xingular/harness 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { resolve as resolve9 } from "node:path";
4
+ import { resolve as resolve10 } from "node:path";
5
5
 
6
6
  // src/core/bus.ts
7
7
  var Bus = class {
@@ -27,7 +27,7 @@ function log(...parts) {
27
27
 
28
28
  // src/harness.ts
29
29
  import { mkdirSync as mkdirSync14 } from "node:fs";
30
- import { join as join39 } from "node:path";
30
+ import { join as join40 } from "node:path";
31
31
 
32
32
  // src/accounts/accounts.ts
33
33
  import { chmodSync as chmodSync3, rmSync as rmSync2 } from "node:fs";
@@ -200,7 +200,7 @@ var modified = (path3) => {
200
200
  }
201
201
  };
202
202
  function backUp(path3) {
203
- const backup2 = join2(dirname2(path3), `${basename2(path3)}.agentx-backup-${Math.floor(Date.now() / 1e3)}`);
203
+ const backup2 = join2(dirname2(path3), `${basename2(path3)}.xagent-backup-${Math.floor(Date.now() / 1e3)}`);
204
204
  renameSync2(path3, backup2);
205
205
  return backup2;
206
206
  }
@@ -890,9 +890,9 @@ var Accounts = class {
890
890
  const interaction = {
891
891
  signal: controller.signal,
892
892
  notify: (notification) => event(notification),
893
- prompt: (prompt2) => new Promise((resolve10, reject) => {
893
+ prompt: (prompt2) => new Promise((resolve11, reject) => {
894
894
  const promptId = randomUUID2();
895
- login.prompts.set(promptId, { resolve: resolve10, reject });
895
+ login.prompts.set(promptId, { resolve: resolve11, reject });
896
896
  prompt2.signal?.addEventListener("abort", () => {
897
897
  login.prompts.delete(promptId);
898
898
  event({ type: "promptClosed", promptId });
@@ -2033,7 +2033,7 @@ function undoLast(wiki, blockId) {
2033
2033
  }
2034
2034
  function render(blocks, caller, cli) {
2035
2035
  if (!blocks.length) return "";
2036
- const how = cli ? "`agentx-wiki block append <label> <text>`, `block replace <label> --old <text> --new <text>`, or `block rethink <label> <text>`" : "memory_append, memory_replace, or memory_rethink";
2036
+ const how = cli ? "`xagent-wiki block append <label> <text>`, `block replace <label> --old <text> --new <text>`, or `block rethink <label> <text>`" : "memory_append, memory_replace, or memory_rethink";
2037
2037
  return `## Memory blocks
2038
2038
 
2039
2039
  These blocks are your memory that is always in view, and they persist across conversations. When you learn something worth keeping, edit a block you may change with ${how}. Keep each block within its limit.
@@ -2627,6 +2627,12 @@ var contextSnapshotPayload = z.object({
2627
2627
  contextUpdateTruncated: z.boolean().optional(),
2628
2628
  toolsTruncated: z.boolean().optional()
2629
2629
  });
2630
+ var toolAttachmentRefSchema = z.object({
2631
+ id: z.string().min(1).max(300),
2632
+ name: z.string().min(1).max(500),
2633
+ mimeType: z.string().min(1).max(100)
2634
+ }).strict();
2635
+ var toolAttachmentRefsSchema = z.array(toolAttachmentRefSchema).max(16);
2630
2636
  var toolPayload = z.object({
2631
2637
  toolCallId: z.string(),
2632
2638
  name: z.string(),
@@ -2638,7 +2644,9 @@ var toolPayload = z.object({
2638
2644
  ok: z.boolean().optional(),
2639
2645
  /** On `tool.completed`: how long the call ran, and the size of what it returned to the model. */
2640
2646
  durationMs: z.number().optional(),
2641
- resultChars: z.number().optional()
2647
+ resultChars: z.number().optional(),
2648
+ /** Opaque attachment references. Content stays outside the run event stream. */
2649
+ attachments: toolAttachmentRefsSchema.optional()
2642
2650
  });
2643
2651
  var retryStartedPayload = z.object({ attempt: z.number(), maxAttempts: z.number(), delayMs: z.number(), error: z.string().max(1e3).optional() });
2644
2652
  var retryEndedPayload = z.object({ attempt: z.number(), success: z.boolean(), error: z.string().max(1e3).optional() });
@@ -3743,13 +3751,13 @@ function toolGroups(context, launch) {
3743
3751
  group: "Memory",
3744
3752
  tools: MEMORY_GROUP,
3745
3753
  order: GROUP_ORDER.Memory,
3746
- guidance: cli ? "Memory: `agentx-wiki block append <label> <text>`, `block replace <label> --old <text> --new <text>`, and `block rethink <label> <text>` edit your memory blocks. `agentx-wiki facts [query]` recalls saved facts. `agentx-wiki history <query>` searches earlier runs and `agentx-wiki history-read <run id>` returns one." : "Memory: memory_append, memory_replace, and memory_rethink edit your memory blocks; memory_facts recalls saved facts. history_search finds what was said and done in earlier conversations and runs, including a chat that was cleared; history_read returns one run."
3754
+ guidance: cli ? "Memory: `xagent-wiki block append <label> <text>`, `block replace <label> --old <text> --new <text>`, and `block rethink <label> <text>` edit your memory blocks. `xagent-wiki facts [query]` recalls saved facts. `xagent-wiki history <query>` searches earlier runs and `xagent-wiki history-read <run id>` returns one." : "Memory: memory_append, memory_replace, and memory_rethink edit your memory blocks; memory_facts recalls saved facts. history_search finds what was said and done in earlier conversations and runs, including a chat that was cleared; history_read returns one run."
3747
3755
  },
3748
3756
  {
3749
3757
  group: "Knowledge",
3750
3758
  tools: KNOWLEDGE_GROUP,
3751
3759
  order: GROUP_ORDER.Knowledge,
3752
- guidance: cli ? "Knowledge: `agentx-wiki search <query>`, `read <id or path>`, `list [namespace]`, `skill [name]`, and `propose <json>` to record durable knowledge. Cite what you use as [id@revision]." : "Knowledge: wiki_search, wiki_read, and wiki_list read the Wiki; skill_load loads a skill's steps; wiki_propose records durable knowledge, quoting its evidence (the Wiki validates it and may send it to the Inbox). Cite what you use as [id@revision]."
3760
+ guidance: cli ? "Knowledge: `xagent-wiki search <query>`, `read <id or path>`, `list [namespace]`, `skill [name]`, and `propose <json>` to record durable knowledge. Cite what you use as [id@revision]." : "Knowledge: wiki_search, wiki_read, and wiki_list read the Wiki; skill_load loads a skill's steps; wiki_propose records durable knowledge, quoting its evidence (the Wiki validates it and may send it to the Inbox). Cite what you use as [id@revision]."
3753
3761
  },
3754
3762
  {
3755
3763
  group: "Native",
@@ -4320,7 +4328,7 @@ var WIKI_TOOLS = [
4320
4328
  name: "wiki_read",
4321
4329
  call: "wiki.read",
4322
4330
  label: "Read Wiki entry",
4323
- description: "Read one Wiki page, fact, skill, rule, or prompt in full, with its sources, links, and backlinks. Pass an id (arch, skills/review) or a Wiki path (/projects/agent-x/arch).",
4331
+ description: "Read one Wiki page, fact, skill, rule, or prompt in full, with its sources, links, and backlinks. Pass an id (arch, skills/review) or a Wiki path (/projects/xagent/arch).",
4324
4332
  parameters: { type: "object", properties: { id: text4("Entry id or Wiki path"), revision: text4("Optional pinned revision selected for this reply"), offset: { type: "integer", minimum: 0, description: "Character offset when reading a pinned revision; use nextOffset to continue." } }, required: ["id"] }
4325
4333
  },
4326
4334
  {
@@ -4625,14 +4633,14 @@ var piResolver = async ({ cwd, agentDir, install: install3, packages }) => {
4625
4633
  const settings = pi.SettingsManager.create(cwd, agentDir);
4626
4634
  const shared = new Set((settings.getGlobalSettings().packages ?? []).map((entry) => typeof entry === "string" ? entry : entry.source));
4627
4635
  const manager2 = () => new pi.DefaultPackageManager({ cwd, agentDir, settingsManager: settings, builtinExtensions: [...PI_BUILTINS] });
4628
- const resolve10 = (installing) => manager2().resolve(async (source2) => installing && shared.has(source2) ? "install" : "skip");
4636
+ const resolve11 = (installing) => manager2().resolve(async (source2) => installing && shared.has(source2) ? "install" : "skip");
4629
4637
  let resolved2;
4630
4638
  try {
4631
- resolved2 = install3 ? await withoutInstallScripts(() => resolve10(true)) : await resolve10(false);
4639
+ resolved2 = install3 ? await withoutInstallScripts(() => resolve11(true)) : await resolve11(false);
4632
4640
  } catch (error) {
4633
4641
  if (!install3) throw error;
4634
4642
  log("a plugin package could not be installed", error instanceof Error ? error.message : String(error));
4635
- resolved2 = await resolve10(false);
4643
+ resolved2 = await resolve11(false);
4636
4644
  }
4637
4645
  const found = [];
4638
4646
  const add = (paths) => {
@@ -5173,36 +5181,63 @@ import { isAbsolute as isAbsolute3, join as join27 } from "node:path";
5173
5181
  import { execFileSync } from "node:child_process";
5174
5182
  import { accessSync, constants, statSync as statSync5 } from "node:fs";
5175
5183
  import { homedir as homedir3 } from "node:os";
5176
- import { delimiter, isAbsolute as isAbsolute2, join as join25 } from "node:path";
5184
+ import { delimiter, extname, isAbsolute as isAbsolute2, join as join25 } from "node:path";
5177
5185
  var resolved = null;
5178
5186
  function searchPath() {
5179
- if (resolved !== null) return resolved;
5187
+ const platform = process.platform;
5188
+ const pathKey = pathVariableName(process.env);
5189
+ const inherited = process.env[pathKey] ?? "";
5190
+ if (resolved?.platform === platform && resolved.inherited === inherited) return resolved.path;
5191
+ const separator = pathSeparator(platform);
5180
5192
  const entries2 = [];
5181
- try {
5182
- const output = execFileSync(process.env.SHELL || "/bin/zsh", ["-ilc", 'printf %s "$PATH"'], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] });
5183
- entries2.push(...(output.split("\n").filter(Boolean).at(-1) ?? "").split(delimiter));
5184
- } catch {
5193
+ if (platform !== "win32") {
5194
+ try {
5195
+ const output = execFileSync(process.env.SHELL || "/bin/zsh", ["-ilc", 'printf %s "$PATH"'], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] });
5196
+ entries2.push(...(output.split("\n").filter(Boolean).at(-1) ?? "").split(separator));
5197
+ } catch {
5198
+ }
5199
+ }
5200
+ entries2.push(...inherited.split(separator));
5201
+ if (platform !== "win32") {
5202
+ const home = homedir3();
5203
+ if (home) entries2.push(...[".local/bin", ".bun/bin", ".npm-global/bin", ".cargo/bin"].map((folder) => join25(home, folder)));
5204
+ entries2.push("/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin");
5185
5205
  }
5186
- entries2.push(...(process.env.PATH ?? "").split(delimiter));
5187
- const home = homedir3();
5188
- if (home) entries2.push(...[".local/bin", ".bun/bin", ".npm-global/bin", ".cargo/bin"].map((folder) => join25(home, folder)));
5189
- entries2.push("/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin");
5190
- resolved = [...new Set(entries2.filter(Boolean))].join(delimiter);
5191
- return resolved;
5206
+ const path3 = [...new Set(entries2.filter(Boolean))].join(separator);
5207
+ resolved = { platform, inherited, path: path3 };
5208
+ return path3;
5209
+ }
5210
+ function pathVariableName(environment) {
5211
+ return Object.keys(environment).find((name) => name.toLowerCase() === "path") ?? "PATH";
5212
+ }
5213
+ function prependSearchPath(directory, existing, environment = process.env, platform = process.platform) {
5214
+ const values2 = [directory, existing].filter(Boolean);
5215
+ return { [pathVariableName(environment)]: values2.join(pathSeparator(platform)) };
5216
+ }
5217
+ function pathSeparator(platform) {
5218
+ return platform === "win32" ? ";" : delimiter;
5192
5219
  }
5193
5220
  function isExecutable(path3) {
5194
5221
  try {
5195
- accessSync(path3, constants.X_OK);
5222
+ accessSync(path3, process.platform === "win32" ? constants.F_OK : constants.X_OK);
5196
5223
  return statSync5(path3).isFile();
5197
5224
  } catch {
5198
5225
  return false;
5199
5226
  }
5200
5227
  }
5201
5228
  function findBinary(name) {
5202
- if (isAbsolute2(name)) return isExecutable(name) ? name : null;
5203
- for (const directory of searchPath().split(delimiter)) {
5204
- const candidate = join25(directory, name);
5205
- if (isExecutable(candidate)) return candidate;
5229
+ const platform = process.platform;
5230
+ const suffixes = platform === "win32" && !extname(name) ? (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [];
5231
+ const names = [name, ...suffixes.map((suffix) => `${name}${suffix}`)];
5232
+ if (isAbsolute2(name)) {
5233
+ for (const candidate of names) if (isExecutable(candidate)) return candidate;
5234
+ return null;
5235
+ }
5236
+ for (const directory of searchPath().split(pathSeparator(platform))) {
5237
+ for (const candidateName of names) {
5238
+ const candidate = join25(directory, candidateName);
5239
+ if (isExecutable(candidate)) return candidate;
5240
+ }
5206
5241
  }
5207
5242
  return null;
5208
5243
  }
@@ -5772,7 +5807,7 @@ var Connections = class {
5772
5807
  }
5773
5808
  let command = findBinary(connection.command) ?? (isAbsolute3(connection.command) ? connection.command : null);
5774
5809
  if (!command) throw new HarnessError("mcp_unavailable", "MCP executable was not found. Select its absolute path.");
5775
- const env = { PATH: searchPath() };
5810
+ const env = { [pathVariableName(process.env)]: searchPath() };
5776
5811
  for (const key of ["HOME", "TMPDIR", "LANG"]) if (process.env[key]) env[key] = process.env[key];
5777
5812
  for (const key of connection.envKeys) {
5778
5813
  const value = await this.secret(connection.id, key);
@@ -6252,8 +6287,8 @@ var cancelActive = null;
6252
6287
  function listenForCallback(expectedState, clientName) {
6253
6288
  return new Promise((ready, failed) => {
6254
6289
  let settle;
6255
- const result = new Promise((resolve10, reject) => {
6256
- settle = { resolve: resolve10, reject };
6290
+ const result = new Promise((resolve11, reject) => {
6291
+ settle = { resolve: resolve11, reject };
6257
6292
  });
6258
6293
  result.catch(() => void 0);
6259
6294
  const server = createServer((request2, response3) => {
@@ -6277,7 +6312,7 @@ function listenForCallback(expectedState, clientName) {
6277
6312
  clearTimeout(timer);
6278
6313
  failed(new HarnessError("invalid_request", error.code === "EADDRINUSE" ? `Port ${MCP_OAUTH_PORT} is in use. Finish or cancel the other sign-in, or quit the program using that port, then try again.` : "The sign-in listener could not start."));
6279
6314
  });
6280
- const closed = new Promise((resolve10) => server.once("close", resolve10));
6315
+ const closed = new Promise((resolve11) => server.once("close", resolve11));
6281
6316
  const close = () => {
6282
6317
  clearTimeout(timer);
6283
6318
  if (cancelActive === cancel) cancelActive = null;
@@ -6359,7 +6394,7 @@ var McpManager = class {
6359
6394
  }
6360
6395
  }
6361
6396
  async connectStdio(config) {
6362
- const client = new Client({ name: "agent-x", version: "0.1.0" });
6397
+ const client = new Client({ name: "xagent", version: "0.1.0" });
6363
6398
  const transport = new StdioClientTransport({ command: config.command, args: config.args, cwd: config.cwd ?? void 0, env: config.env, stderr: "pipe" });
6364
6399
  try {
6365
6400
  await client.connect(transport, { timeout: 3e4 });
@@ -6375,7 +6410,7 @@ var McpManager = class {
6375
6410
  if (authProvider && !await authProvider.tokens()) throw signInRequired(config);
6376
6411
  const options2 = { authProvider, requestInit: { headers: config.headers } };
6377
6412
  for (const Transport of [StreamableHTTPClientTransport, SSEClientTransport]) {
6378
- const client = new Client({ name: "agent-x", version: "0.1.0" });
6413
+ const client = new Client({ name: "xagent", version: "0.1.0" });
6379
6414
  const transport = new Transport(new URL(config.url), options2);
6380
6415
  try {
6381
6416
  await client.connect(transport, { timeout: 3e4 });
@@ -8482,7 +8517,7 @@ var RunRegistry = class {
8482
8517
  }
8483
8518
  emit("approval.requested", { approvalId, ...payload });
8484
8519
  emit("status.changed", { status: "waiting_for_approval" });
8485
- return new Promise((resolve10) => {
8520
+ return new Promise((resolve11) => {
8486
8521
  const timer = timeoutMs ? setTimeout(() => this.approvals.get(approvalId)?.resolve("denied"), timeoutMs) : null;
8487
8522
  this.approvals.set(approvalId, {
8488
8523
  runId,
@@ -8491,7 +8526,7 @@ var RunRegistry = class {
8491
8526
  this.approvals.delete(approvalId);
8492
8527
  emit("approval.resolved", { approvalId, decision });
8493
8528
  emit("status.changed", { status: waitingStatus() });
8494
- resolve10(decision);
8529
+ resolve11(decision);
8495
8530
  }
8496
8531
  });
8497
8532
  });
@@ -8502,7 +8537,7 @@ var RunRegistry = class {
8502
8537
  if (finished || this.nativeCleared.delete(questionId)) return Promise.resolve({ selected: [] });
8503
8538
  emit("question.requested", { questionId, ...payload });
8504
8539
  emit("status.changed", { status: "waiting_for_input" });
8505
- return new Promise((resolve10) => {
8540
+ return new Promise((resolve11) => {
8506
8541
  this.questions.set(questionId, {
8507
8542
  runId,
8508
8543
  input,
@@ -8510,7 +8545,7 @@ var RunRegistry = class {
8510
8545
  this.questions.delete(questionId);
8511
8546
  emit("question.answered", { questionId, ...answer });
8512
8547
  emit("status.changed", { status: waitingStatus() });
8513
- resolve10(answer);
8548
+ resolve11(answer);
8514
8549
  }
8515
8550
  });
8516
8551
  });
@@ -8589,7 +8624,7 @@ var RunRegistry = class {
8589
8624
  }
8590
8625
  /** Interrupts every live run, giving each a moment to stop cleanly. */
8591
8626
  async interruptAll(timeoutMs = 3e3) {
8592
- await Promise.race([Promise.allSettled(this.activeRunIds().map((id) => this.interrupt(id))), new Promise((resolve10) => setTimeout(resolve10, timeoutMs))]);
8627
+ await Promise.race([Promise.allSettled(this.activeRunIds().map((id) => this.interrupt(id))), new Promise((resolve11) => setTimeout(resolve11, timeoutMs))]);
8593
8628
  }
8594
8629
  resolveApproval(runId, approvalId, decision) {
8595
8630
  if (decision !== "approved" && decision !== "denied" && decision !== "native") throw new HarnessError("invalid_request", "Unknown decision.");
@@ -8789,7 +8824,7 @@ ${text6}`);
8789
8824
  async function waitForChildren(root, runId, signal) {
8790
8825
  while (!signal?.aborted) {
8791
8826
  if (settled(root, runId)) return true;
8792
- await new Promise((resolve10) => setTimeout(resolve10, 250));
8827
+ await new Promise((resolve11) => setTimeout(resolve11, 250));
8793
8828
  }
8794
8829
  return false;
8795
8830
  }
@@ -8870,11 +8905,11 @@ var MAX_TEXT = 4e3;
8870
8905
  var clip2 = (text6) => text6.length > MAX_TEXT ? `${text6.slice(0, MAX_TEXT)}\u2026` : text6;
8871
8906
  function dismissable(answer, fallback, options2) {
8872
8907
  if (!options2?.signal && !options2?.timeout) return answer;
8873
- return new Promise((resolve10) => {
8908
+ return new Promise((resolve11) => {
8874
8909
  let timer;
8875
8910
  const done = (value) => {
8876
8911
  if (timer) clearTimeout(timer);
8877
- resolve10(value);
8912
+ resolve11(value);
8878
8913
  };
8879
8914
  if (options2.signal?.aborted) return done(fallback);
8880
8915
  options2.signal?.addEventListener("abort", () => done(fallback), { once: true });
@@ -8995,6 +9030,12 @@ function evidenceText2(value) {
8995
9030
  return String(value);
8996
9031
  }
8997
9032
  }
9033
+ function toolAttachmentRefs(result) {
9034
+ const details = result?.details;
9035
+ const refs = details && typeof details === "object" ? details.attachments : void 0;
9036
+ const parsed2 = toolAttachmentRefsSchema.safeParse(refs);
9037
+ return parsed2.success && parsed2.data.length ? parsed2.data : void 0;
9038
+ }
8998
9039
  function boundedSchema(value, budget, depth = 0) {
8999
9040
  if (budget.remaining <= 0 || depth > 12) return { value: "[truncated]", truncated: true };
9000
9041
  if (typeof value === "string") {
@@ -9177,12 +9218,14 @@ async function runTurn(run3, session, params) {
9177
9218
  const resultChars = resultSize(tool.result);
9178
9219
  const output = evidenceText2(tool.result);
9179
9220
  const boundedOutput = output === null ? null : limitedText(output);
9221
+ const attachments = toolAttachmentRefs(tool.result);
9180
9222
  run3.emit("tool.completed", {
9181
9223
  toolCallId: tool.toolCallId,
9182
9224
  name: tool.toolName,
9183
9225
  ok: !tool.isError,
9184
9226
  ...started === void 0 ? {} : { durationMs: Date.now() - started },
9185
9227
  ...resultChars === null ? {} : { resultChars },
9228
+ ...attachments ? { attachments } : {},
9186
9229
  ...boundedOutput ? { output: boundedOutput.text, ...boundedOutput.truncated ? { outputTruncated: true } : {} } : {}
9187
9230
  });
9188
9231
  break;
@@ -9259,7 +9302,7 @@ async function loadResources(pi, params, gate, automaticSettings) {
9259
9302
  agentDir: params.account.configDir,
9260
9303
  // Inline extensions load after the ones on disk, and the gate after all of them, so it sees
9261
9304
  // what every plugin made of a call.
9262
- extensionFactories: [...(params.inline ?? []).map((extension) => ({ name: extension.name, factory: extension.factory, hidden: true })), { name: "agent-x-approvals", factory: gate, hidden: true }],
9305
+ extensionFactories: [...(params.inline ?? []).map((extension) => ({ name: extension.name, factory: extension.factory, hidden: true })), { name: "xagent-approvals", factory: gate, hidden: true }],
9263
9306
  additionalSkillPaths: [...params.resources?.skillPaths ?? [], ...loading?.skillPaths ?? []],
9264
9307
  additionalPromptTemplatePaths: [...params.resources?.promptPaths ?? [], ...loading?.promptPaths ?? []],
9265
9308
  // The compiler writes one prompt; nothing else is appended here.
@@ -9313,7 +9356,10 @@ var piSessionFactory = async ({ params, allow, gate, tools: tools3, runtime: loa
9313
9356
  const result = await spec.execute(input);
9314
9357
  const output = typeof result === "string" ? { text: result } : result;
9315
9358
  if (output.image && !supportsImageInput) throw new HarnessError("image_input_unsupported", "This model cannot inspect images. Choose a model that supports image input.");
9316
- return { content: [{ type: "text", text: output.text }, ...output.image ? [{ type: "image", data: output.image.data, mimeType: output.image.mimeType }] : []], details: {} };
9359
+ return {
9360
+ content: [{ type: "text", text: output.text }, ...output.image ? [{ type: "image", data: output.image.data, mimeType: output.image.mimeType }] : []],
9361
+ details: output.attachments?.length ? { attachments: output.attachments } : {}
9362
+ };
9317
9363
  }
9318
9364
  })
9319
9365
  );
@@ -9546,8 +9592,8 @@ var Sessions = class {
9546
9592
  };
9547
9593
 
9548
9594
  // src/terminal/terminals.ts
9549
- import { chmodSync as chmodSync11, existsSync as existsSync14, mkdirSync as mkdirSync13, readFileSync as readFileSync19, writeFileSync as writeFileSync3 } from "node:fs";
9550
- import { dirname as dirname13, join as join38 } from "node:path";
9595
+ import { chmodSync as chmodSync11, existsSync as existsSync15, lstatSync as lstatSync4, mkdirSync as mkdirSync13, readFileSync as readFileSync20, unlinkSync as unlinkSync4, writeFileSync as writeFileSync3 } from "node:fs";
9596
+ import { dirname as dirname13, join as join39 } from "node:path";
9551
9597
  import { fileURLToPath } from "node:url";
9552
9598
 
9553
9599
  // src/terminal/client.ts
@@ -9567,7 +9613,7 @@ function connectionFromEnv(env = process.env) {
9567
9613
  return { socket, token };
9568
9614
  }
9569
9615
  function request(connection, body2, what, timeoutMs) {
9570
- return new Promise((resolve10, reject) => {
9616
+ return new Promise((resolve11, reject) => {
9571
9617
  const stream = createConnection(connection.socket);
9572
9618
  let buffer = "";
9573
9619
  let settled2 = false;
@@ -9593,7 +9639,7 @@ function request(connection, body2, what, timeoutMs) {
9593
9639
  if (reply.error) {
9594
9640
  const error = typeof reply.error === "string" ? { message: reply.error } : reply.error;
9595
9641
  finish(() => reject(new SocketError(error.code ?? "error", error.message ?? `${what} failed.`)));
9596
- } else finish(() => resolve10(reply.result));
9642
+ } else finish(() => resolve11(reply.result));
9597
9643
  } catch {
9598
9644
  finish(() => reject(new SocketError("invalid_reply", "The harness sent an unreadable reply.")));
9599
9645
  }
@@ -9607,8 +9653,8 @@ function socketCall(connection, call, params = {}, timeoutMs = 12e4, control) {
9607
9653
  }
9608
9654
 
9609
9655
  // src/terminal/cli.ts
9610
- var WIKI_COMMAND = "agentx-wiki";
9611
- var APPS_COMMAND = "agentx-mcp";
9656
+ var WIKI_COMMAND = "xagent-wiki";
9657
+ var APPS_COMMAND = "xagent-mcp";
9612
9658
  var USAGE = `Usage: ${WIKI_COMMAND} <command> [arguments]
9613
9659
 
9614
9660
  entrypoint Your context pack: memory blocks, rules, skills, project map
@@ -9797,15 +9843,15 @@ var TerminalController = class {
9797
9843
  if (this.disposed) throw new HarnessError("terminal_not_ready", "Reopen the terminal before sending instructions.");
9798
9844
  if (this.activeRun || this.delivery) throw new HarnessError("session_busy", "This terminal has an active task or delivery.");
9799
9845
  const deadline = Date.now() + READY_TIMEOUT_MS;
9800
- while (!this.ready && !this.startupError && !this.disposed && Date.now() < deadline) await new Promise((resolve10) => setTimeout(resolve10, 50));
9846
+ while (!this.ready && !this.startupError && !this.disposed && Date.now() < deadline) await new Promise((resolve11) => setTimeout(resolve11, 50));
9801
9847
  if (this.disposed) throw new HarnessError("terminal_not_ready", "The terminal stopped during initialization.");
9802
9848
  if (this.startupError) throw new HarnessError("terminal_not_ready", this.startupError);
9803
9849
  if (!this.ready) throw new HarnessError("terminal_not_ready", `The native telemetry handshake is missing (${this.startupPhase}). Inspect the terminal sign-in or trust screen.`);
9804
9850
  if (this.dirty) throw new HarnessError("terminal_has_draft", "The terminal contains user input. Submit or clear it before handing back control.");
9805
9851
  let timer;
9806
- const ack = new Promise((resolve10, reject) => {
9852
+ const ack = new Promise((resolve11, reject) => {
9807
9853
  timer = setTimeout(() => reject(new HarnessError("delivery_uncertain", "The native prompt acknowledgement was not received. Inspect the terminal; this instruction will not be resent.")), ACK_TIMEOUT_MS);
9808
- this.delivery = { runId, text: text6, resolve: resolve10, reject };
9854
+ this.delivery = { runId, text: text6, resolve: resolve11, reject };
9809
9855
  });
9810
9856
  void ack.catch(() => {
9811
9857
  });
@@ -9920,19 +9966,86 @@ var TerminalController = class {
9920
9966
  }
9921
9967
  };
9922
9968
 
9969
+ // src/terminal/launch.ts
9970
+ import { existsSync as existsSync14, readFileSync as readFileSync19 } from "node:fs";
9971
+ import { createRequire } from "node:module";
9972
+ import { join as join37, resolve as resolve6 } from "node:path";
9973
+ var PI_PACKAGE = "@earendil-works/pi-coding-agent";
9974
+ function bundledPiCli() {
9975
+ const require2 = createRequire(import.meta.url);
9976
+ for (const nodeModules of require2.resolve.paths(PI_PACKAGE) ?? []) {
9977
+ const directory = join37(nodeModules, ...PI_PACKAGE.split("/"));
9978
+ const manifestPath = join37(directory, "package.json");
9979
+ if (!existsSync14(manifestPath)) continue;
9980
+ try {
9981
+ const manifest = JSON.parse(readFileSync19(manifestPath, "utf8"));
9982
+ if (manifest.name === PI_PACKAGE) {
9983
+ const executable = typeof manifest.bin === "string" ? manifest.bin : manifest.bin && typeof manifest.bin === "object" && typeof manifest.bin.pi === "string" ? manifest.bin.pi : null;
9984
+ if (!executable) throw new Error("the package does not declare its pi command");
9985
+ const cli = resolve6(directory, executable);
9986
+ if (!existsSync14(cli)) throw new Error(`the declared command does not exist at ${cli}`);
9987
+ return cli;
9988
+ }
9989
+ } catch {
9990
+ continue;
9991
+ }
9992
+ }
9993
+ throw new HarnessError("harness_unavailable", `The bundled ${PI_PACKAGE} package does not expose a usable pi command. Reinstall the Harness package and try again.`);
9994
+ }
9995
+ function resolvePiCommand(options2) {
9996
+ if (options2.programOverride) {
9997
+ const program2 = options2.programOverride();
9998
+ if (!program2) throw new HarnessError("harness_unavailable", "The Pi CLI was not found on PATH.");
9999
+ return { program: program2, argsPrefix: [] };
10000
+ }
10001
+ if (options2.platform === "win32") return { program: options2.node, argsPrefix: [bundledPiCli()] };
10002
+ const program = findBinary("pi");
10003
+ if (!program) throw new HarnessError("harness_unavailable", "The Pi CLI was not found on PATH.");
10004
+ return { program, argsPrefix: [] };
10005
+ }
10006
+ function bashPath(path3) {
10007
+ const drive = /^([a-z]):[\\/](.*)$/i.exec(path3);
10008
+ if (drive) return `/${drive[1].toLowerCase()}/${drive[2].replaceAll("\\", "/")}`;
10009
+ if (path3.startsWith("\\\\")) return `//${path3.slice(2).replaceAll("\\", "/")}`;
10010
+ return path3;
10011
+ }
10012
+ function shellQuote(value) {
10013
+ return `'${value.replaceAll("'", "'\\''")}'`;
10014
+ }
10015
+ function terminalCommandFiles(platform, node, cli, name, mode) {
10016
+ const shellNode = platform === "win32" ? bashPath(node) : node;
10017
+ const shellCli = platform === "win32" ? bashPath(cli) : cli;
10018
+ const files = [{
10019
+ name,
10020
+ content: `#!/bin/sh
10021
+ exec ${shellQuote(shellNode)} ${shellQuote(shellCli)} ${mode} "$@"
10022
+ `
10023
+ }];
10024
+ if (platform === "win32") {
10025
+ files.push({
10026
+ name: `${name}.cmd`,
10027
+ content: `@echo off\r
10028
+ "%XINGULAR_HARNESS_NODE%" "%XINGULAR_HARNESS_CLI%" ${mode} %*\r
10029
+ exit /b %ERRORLEVEL%\r
10030
+ `
10031
+ });
10032
+ }
10033
+ return files;
10034
+ }
10035
+
9923
10036
  // src/terminal/socket.ts
9924
10037
  import { createHash as createHash7, randomBytes } from "node:crypto";
9925
10038
  import { chmodSync as chmodSync10, mkdirSync as mkdirSync12, rmSync as rmSync8, statSync as statSync6 } from "node:fs";
9926
10039
  import { createServer as createServer2 } from "node:net";
9927
10040
  import { tmpdir } from "node:os";
9928
- import { dirname as dirname12, join as join37 } from "node:path";
10041
+ import { dirname as dirname12, join as join38 } from "node:path";
9929
10042
  var MAX_REQUEST_BYTES = 1024 * 1024;
9930
10043
  var MAX_SOCKET_PATH = 100;
9931
10044
  function socketPath(stateDir) {
9932
10045
  if (process.platform === "win32") return `\\\\.\\pipe\\xingular-harness-${randomBytes(12).toString("hex")}`;
9933
- const path3 = join37(stateDir, "run", "harness.sock");
10046
+ const path3 = join38(stateDir, "run", "harness.sock");
9934
10047
  if (Buffer.byteLength(path3) <= MAX_SOCKET_PATH) return path3;
9935
- return join37(tmpdir(), `xh-${createHash7("sha256").update(path3).digest("hex").slice(0, 16)}.sock`);
10048
+ return join38(tmpdir(), `xh-${createHash7("sha256").update(path3).digest("hex").slice(0, 16)}.sock`);
9936
10049
  }
9937
10050
  var isPipePath = (path3) => path3.startsWith("\\\\.\\pipe\\");
9938
10051
  function refusedBind(path3, error) {
@@ -10054,7 +10167,7 @@ var SessionSocket = class {
10054
10167
  }
10055
10168
  /** Binds the socket once; later calls wait for the same bind. */
10056
10169
  listen() {
10057
- this.listening ??= new Promise((resolve10, reject) => {
10170
+ this.listening ??= new Promise((resolve11, reject) => {
10058
10171
  if (!isPipePath(this.path)) {
10059
10172
  mkdirSync12(dirname12(this.path), { recursive: true, mode: 448 });
10060
10173
  rmSync8(this.path, { force: true });
@@ -10073,7 +10186,7 @@ var SessionSocket = class {
10073
10186
  }
10074
10187
  }
10075
10188
  this.server = server;
10076
- resolve10();
10189
+ resolve11();
10077
10190
  });
10078
10191
  });
10079
10192
  return this.listening;
@@ -10084,7 +10197,7 @@ var SessionSocket = class {
10084
10197
  this.server = null;
10085
10198
  this.listening = null;
10086
10199
  for (const client of this.clients) client.destroy();
10087
- if (server) await new Promise((resolve10) => server.close(() => resolve10()));
10200
+ if (server) await new Promise((resolve11) => server.close(() => resolve11()));
10088
10201
  if (!isPipePath(this.path)) rmSync8(this.path, { force: true });
10089
10202
  }
10090
10203
  };
@@ -10092,8 +10205,8 @@ var SessionSocket = class {
10092
10205
  // src/terminal/terminals.ts
10093
10206
  function defaultAssets() {
10094
10207
  const here = dirname13(fileURLToPath(import.meta.url));
10095
- const dist = [here, join38(here, "..", "..", "dist"), join38(here, "..", "dist")].find((folder) => existsSync14(join38(folder, "terminal-pi.js"))) ?? here;
10096
- return { node: process.execPath, cli: join38(dist, "cli.js"), piExtension: join38(dist, "terminal-pi.js") };
10208
+ const dist = [here, join39(here, "..", "..", "dist"), join39(here, "..", "dist")].find((folder) => existsSync15(join39(folder, "terminal-pi.js"))) ?? here;
10209
+ return { node: process.execPath, cli: join39(dist, "cli.js"), piExtension: join39(dist, "terminal-pi.js") };
10097
10210
  }
10098
10211
  var READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["read", "grep", "find", "ls"]);
10099
10212
  var Terminals = class {
@@ -10111,23 +10224,28 @@ var Terminals = class {
10111
10224
  assets;
10112
10225
  /** The commands a terminal session finds on PATH. Rewritten only when they change. */
10113
10226
  ensureCommands() {
10114
- const bin = join38(this.options.stateDir, "run", "bin");
10227
+ const bin = join39(this.options.stateDir, "run", "bin");
10115
10228
  mkdirSync13(bin, { recursive: true });
10116
- const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
10117
- for (const [name, mode] of [[WIKI_COMMAND, "wiki"], [APPS_COMMAND, "mcp"]]) {
10118
- const wrapper = join38(bin, name);
10119
- const text6 = `#!/bin/sh
10120
- exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
10121
- `;
10122
- let current = null;
10229
+ for (const name of ["agentx-wiki", "agentx-mcp", "agentx-wiki.cmd", "agentx-mcp.cmd"]) {
10230
+ const path3 = join39(bin, name);
10123
10231
  try {
10124
- current = readFileSync19(wrapper, "utf8");
10232
+ if (lstatSync4(path3).isFile()) unlinkSync4(path3);
10125
10233
  } catch {
10126
- current = null;
10127
10234
  }
10128
- if (current !== text6) {
10129
- writeFileSync3(wrapper, text6);
10130
- chmodSync11(wrapper, 493);
10235
+ }
10236
+ for (const [name, mode] of [[WIKI_COMMAND, "wiki"], [APPS_COMMAND, "mcp"]]) {
10237
+ for (const file of terminalCommandFiles(process.platform, this.assets.node, this.assets.cli, name, mode)) {
10238
+ const path3 = join39(bin, file.name);
10239
+ let current = null;
10240
+ try {
10241
+ current = readFileSync20(path3, "utf8");
10242
+ } catch {
10243
+ current = null;
10244
+ }
10245
+ if (current !== file.content) {
10246
+ writeFileSync3(path3, file.content);
10247
+ chmodSync11(path3, 493);
10248
+ }
10131
10249
  }
10132
10250
  }
10133
10251
  return bin;
@@ -10194,7 +10312,7 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
10194
10312
  return live;
10195
10313
  }
10196
10314
  historyDir(input) {
10197
- return join38(input.sessionFolder, "pi");
10315
+ return join39(input.sessionFolder, "pi");
10198
10316
  }
10199
10317
  runRecord(live, runId, text6) {
10200
10318
  const { input } = live;
@@ -10269,8 +10387,7 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
10269
10387
  const { root } = env;
10270
10388
  if (!input?.sessionId || !input.cwd || !input.sessionFolder) throw invalid("A terminal session needs an id, a working folder, and its own folder.");
10271
10389
  await this.dispose(input.sessionId);
10272
- const program = this.options.program ? this.options.program() : findBinary("pi");
10273
- if (!program) throw new HarnessError("harness_unavailable", "The Pi CLI was not found on PATH.");
10390
+ const command = resolvePiCommand({ platform: process.platform, node: this.assets.node, programOverride: this.options.program });
10274
10391
  const account = input.accountId ? accounts.find(input.accountId) : accounts.resolve(null, null);
10275
10392
  const launch = launchEnv(accounts.dir, account);
10276
10393
  const loading = await this.options.plugins?.loading({ cwd: input.cwd, agentDir: configDir(accounts.dir, account), install: true, caller: { projectId: input.projectId ?? null } }) ?? null;
@@ -10308,7 +10425,7 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
10308
10425
  });
10309
10426
  live.token = token;
10310
10427
  this.sessions.set(sessionId, live);
10311
- const args = ["--append-system-prompt", render2(env, pack)];
10428
+ const args = [...command.argsPrefix, "--append-system-prompt", render2(env, pack)];
10312
10429
  if (input.model) args.push("--model", input.model);
10313
10430
  if (loading) {
10314
10431
  args.push("--no-extensions", "--no-skills", "--no-prompt-templates", "--no-themes");
@@ -10321,9 +10438,15 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
10321
10438
  args.push("--extension", this.assets.piExtension, "--session-dir", this.historyDir(input));
10322
10439
  if (input.nativeSessionId) args.push("--session", input.nativeSessionId);
10323
10440
  return {
10324
- program,
10441
+ program: command.program,
10325
10442
  args,
10326
- env: { ...launch, PATH: `${this.ensureCommands()}:${searchPath()}`, [SOCKET_ENV]: this.socket.path, [TOKEN_ENV]: token },
10443
+ env: {
10444
+ ...launch,
10445
+ ...process.platform === "win32" ? { XINGULAR_HARNESS_NODE: this.assets.node, XINGULAR_HARNESS_CLI: this.assets.cli } : {},
10446
+ ...prependSearchPath(this.ensureCommands(), searchPath()),
10447
+ [SOCKET_ENV]: this.socket.path,
10448
+ [TOKEN_ENV]: token
10449
+ },
10327
10450
  accountId: account.id,
10328
10451
  nativeSessionId: input.nativeSessionId ?? null
10329
10452
  };
@@ -10426,12 +10549,12 @@ var Harness = class {
10426
10549
  constructor(options2) {
10427
10550
  this.stateDir = options2.stateDir;
10428
10551
  mkdirSync14(this.stateDir, { recursive: true });
10429
- isolateProcess(join39(this.stateDir, "accounts"));
10552
+ isolateProcess(join40(this.stateDir, "accounts"));
10430
10553
  this.env = new Env(options2.dataRoot || null, options2.directory ?? EMPTY_DIRECTORY, options2.productName ?? DEFAULT_PRODUCT_NAME, this.bus);
10431
10554
  this.policy = options2.policy ?? {};
10432
10555
  configureBuiltins(options2.builtins ?? []);
10433
- const secrets = options2.secrets ?? fileSecretStore(join39(this.stateDir, "secrets.json"));
10434
- this.accounts = new Accounts({ dir: join39(this.stateDir, "accounts"), secrets, bus: this.bus });
10556
+ const secrets = options2.secrets ?? fileSecretStore(join40(this.stateDir, "secrets.json"));
10557
+ this.accounts = new Accounts({ dir: join40(this.stateDir, "accounts"), secrets, bus: this.bus });
10435
10558
  this.approvals = new Approvals(this.stateDir, this.bus);
10436
10559
  this.runs = new RunRegistry({
10437
10560
  root: () => this.env.root,
@@ -10443,7 +10566,7 @@ var Harness = class {
10443
10566
  ended: (runId, sessionId) => this.apps.revokeRun(runId, sessionId)
10444
10567
  });
10445
10568
  this.apps = new ConnectedApps({ env: this.env, stateDir: this.stateDir, secrets, bus: this.bus, runs: this.runs, policy: this.policy });
10446
- this.plugins = new Plugins({ stateDir: this.stateDir, bus: this.bus, resolver: options2.resolver, attachments: () => this.env.dataRoot ? load2(this.env.dataRoot) : null, packagesDir: () => this.env.dataRoot ? join39(this.env.dataRoot, "plugins") : null });
10569
+ this.plugins = new Plugins({ stateDir: this.stateDir, bus: this.bus, resolver: options2.resolver, attachments: () => this.env.dataRoot ? load2(this.env.dataRoot) : null, packagesDir: () => this.env.dataRoot ? join40(this.env.dataRoot, "plugins") : null });
10447
10570
  this.hooks = new Hooks({
10448
10571
  stateDir: this.stateDir,
10449
10572
  bus: this.bus,
@@ -10451,9 +10574,9 @@ var Harness = class {
10451
10574
  // The shared folder stands in for an account: packages are the same for all of them.
10452
10575
  wanted: async () => {
10453
10576
  if (!this.env.dataRoot) return [];
10454
- const shared = ensureSharedDir(join39(this.stateDir, "accounts"));
10455
- const named2 = readJsonOr(join39(shared, "settings.json"), {}).packages;
10456
- if (!packageFolders(join39(this.env.dataRoot, "plugins")).length && !(Array.isArray(named2) && named2.length)) return [];
10577
+ const shared = ensureSharedDir(join40(this.stateDir, "accounts"));
10578
+ const named2 = readJsonOr(join40(shared, "settings.json"), {}).packages;
10579
+ if (!packageFolders(join40(this.env.dataRoot, "plugins")).length && !(Array.isArray(named2) && named2.length)) return [];
10457
10580
  return (await this.plugins.loading({ cwd: this.stateDir, agentDir: shared, caller: {} })).hooks;
10458
10581
  },
10459
10582
  call: (connection, tool, args) => this.apps.callUnattended(connection, tool, args ?? {}),
@@ -10512,7 +10635,7 @@ var Harness = class {
10512
10635
  const root = dataRoot;
10513
10636
  this.hooks.watch(wikiDir(root));
10514
10637
  void this.hooks.reload();
10515
- for (const folder of ["registry/skills", "registry/prompts", "registry/rules", "config", "runs"]) mkdirSync14(join39(root, folder), { recursive: true });
10638
+ for (const folder of ["registry/skills", "registry/prompts", "registry/rules", "config", "runs"]) mkdirSync14(join40(root, folder), { recursive: true });
10516
10639
  let migrationError = null;
10517
10640
  let wikiMigrationError = null;
10518
10641
  try {
@@ -10587,18 +10710,18 @@ function createHarness(options2) {
10587
10710
 
10588
10711
  // src/plugins/cli.ts
10589
10712
  import { homedir as homedir5 } from "node:os";
10590
- import { join as join41, resolve as resolve7 } from "node:path";
10713
+ import { join as join42, resolve as resolve8 } from "node:path";
10591
10714
  import { createInterface } from "node:readline/promises";
10592
10715
 
10593
10716
  // src/plugins/install.ts
10594
- import { existsSync as existsSync15, mkdirSync as mkdirSync15, renameSync as renameSync8, statSync as statSync7 } from "node:fs";
10717
+ import { existsSync as existsSync16, mkdirSync as mkdirSync15, renameSync as renameSync8, statSync as statSync7 } from "node:fs";
10595
10718
  import { homedir as homedir4 } from "node:os";
10596
- import { basename as basename8, isAbsolute as isAbsolute5, join as join40, resolve as resolve6 } from "node:path";
10719
+ import { basename as basename8, isAbsolute as isAbsolute5, join as join41, resolve as resolve7 } from "node:path";
10597
10720
  function normalizeSource(input) {
10598
10721
  const source2 = input.trim();
10599
10722
  if (!source2) throw invalid("Name a package: a folder, an npm name, or a git address.");
10600
- if (source2.startsWith("~/")) return join40(process.env.HOME || homedir4(), source2.slice(2));
10601
- if (isAbsolute5(source2)) return resolve6(source2);
10723
+ if (source2.startsWith("~/")) return join41(process.env.HOME || homedir4(), source2.slice(2));
10724
+ if (isAbsolute5(source2)) return resolve7(source2);
10602
10725
  if (source2.startsWith("./") || source2.startsWith("../")) throw invalid("Give the whole path of the folder.");
10603
10726
  return /^(npm:|git:|https?:\/\/|ssh:\/\/|git@)/.test(source2) ? source2 : `npm:${source2}`;
10604
10727
  }
@@ -10611,7 +10734,7 @@ async function manager(harness) {
10611
10734
  var changed = (harness) => harness.bus.emit({ type: "plugins.changed" });
10612
10735
  async function install2(harness, params) {
10613
10736
  const source2 = normalizeSource(params.source);
10614
- if (isAbsolute5(source2) && !(existsSync15(source2) && statSync7(source2).isDirectory())) throw invalid("That folder does not exist.");
10737
+ if (isAbsolute5(source2) && !(existsSync16(source2) && statSync7(source2).isDirectory())) throw invalid("That folder does not exist.");
10615
10738
  await ensureAdopted(harness);
10616
10739
  const before = new Set((await list2(harness, params)).map((plugin) => plugin.id));
10617
10740
  const { settings, packages } = await manager(harness);
@@ -10641,9 +10764,9 @@ function installed(plugins, pluginId) {
10641
10764
  async function remove(harness, params) {
10642
10765
  const plugin = installed(await list2(harness, params), params.pluginId);
10643
10766
  if (plugin.install.kind === "folder") {
10644
- const trash = join40(harness.env.root, "trash", "plugins");
10767
+ const trash = join41(harness.env.root, "trash", "plugins");
10645
10768
  mkdirSync15(trash, { recursive: true });
10646
- renameSync8(plugin.install.source, join40(trash, `${basename8(plugin.install.source)}-${now().replace(/[:.]/g, "-")}`));
10769
+ renameSync8(plugin.install.source, join41(trash, `${basename8(plugin.install.source)}-${now().replace(/[:.]/g, "-")}`));
10647
10770
  } else {
10648
10771
  await (await manager(harness)).packages.removeAndPersist(plugin.install.source);
10649
10772
  }
@@ -10732,10 +10855,10 @@ async function runPluginsCli(argv, dependencies = {}) {
10732
10855
  return command ? 0 : 2;
10733
10856
  }
10734
10857
  const base = dependencies.cwd ?? process.cwd();
10735
- const root = join41(dependencies.home ?? homedir5(), ".xingular-harness");
10736
- const options2 = { dataRoot: parsed2.values.data ? resolve7(base, parsed2.values.data) : join41(root, "data"), stateDir: parsed2.values.state ? resolve7(base, parsed2.values.state) : join41(root, "state"), productName: "Xingular Harness" };
10858
+ const root = join42(dependencies.home ?? homedir5(), ".xingular-harness");
10859
+ const options2 = { dataRoot: parsed2.values.data ? resolve8(base, parsed2.values.data) : join42(root, "data"), stateDir: parsed2.values.state ? resolve8(base, parsed2.values.state) : join42(root, "state"), productName: "Xingular Harness" };
10737
10860
  const harness = dependencies.createHarness ? dependencies.createHarness(options2) : new Harness(options2);
10738
- const scope = { cwd: parsed2.values.cwd ? resolve7(base, parsed2.values.cwd) : null };
10861
+ const scope = { cwd: parsed2.values.cwd ? resolve8(base, parsed2.values.cwd) : null };
10739
10862
  const need = (what) => {
10740
10863
  if (!target) throw new Error(`${command} needs ${what}.`);
10741
10864
  return target;
@@ -10921,7 +11044,7 @@ var RpcPeer = class {
10921
11044
  /** Asks the host to perform a named operation. */
10922
11045
  callHost(call, params, timeoutMs = 3e4) {
10923
11046
  const id = `s${++this.nextCallId}`;
10924
- return new Promise((resolve10, reject) => {
11047
+ return new Promise((resolve11, reject) => {
10925
11048
  const timer = setTimeout(() => {
10926
11049
  this.pendingCalls.delete(id);
10927
11050
  reject(new HarnessError("host_timeout", `The host did not answer ${call}.`));
@@ -10929,7 +11052,7 @@ var RpcPeer = class {
10929
11052
  this.pendingCalls.set(id, {
10930
11053
  resolve: (value) => {
10931
11054
  clearTimeout(timer);
10932
- resolve10(value);
11055
+ resolve11(value);
10933
11056
  },
10934
11057
  reject: (error) => {
10935
11058
  clearTimeout(timer);
@@ -10980,7 +11103,7 @@ var RpcPeer = class {
10980
11103
  };
10981
11104
 
10982
11105
  // src/server/methods.ts
10983
- import { readFileSync as readFileSync20, statSync as statSync8 } from "node:fs";
11106
+ import { readFileSync as readFileSync21, statSync as statSync8 } from "node:fs";
10984
11107
  import { basename as basename9 } from "node:path";
10985
11108
 
10986
11109
  // src/memory/graph.ts
@@ -11195,7 +11318,7 @@ function methods(harness) {
11195
11318
  "wiki.ingest": (params) => {
11196
11319
  const path3 = text5(params, "path");
11197
11320
  if (statSync8(path3).size > MAX_SOURCE_BYTES) throw invalid("Sources larger than 50 MB are not imported.");
11198
- const source2 = maintenance.ingest(optional2(params, "name") ?? basename9(path3), readFileSync20(path3), optional2(params, "namespace") ?? LIBRARY_NAMESPACE);
11321
+ const source2 = maintenance.ingest(optional2(params, "name") ?? basename9(path3), readFileSync21(path3), optional2(params, "namespace") ?? LIBRARY_NAMESPACE);
11199
11322
  harness.bus.emit({ type: "wiki.changed" });
11200
11323
  return source2;
11201
11324
  },
@@ -11360,17 +11483,17 @@ function attach3(harness, peer) {
11360
11483
  import { spawn as nodeSpawn } from "node:child_process";
11361
11484
  import { createHash as createHash8, randomUUID as randomUUID8 } from "node:crypto";
11362
11485
  import { homedir as homedir6 } from "node:os";
11363
- import { isAbsolute as isAbsolute6, join as join42, resolve as resolve8 } from "node:path";
11486
+ import { isAbsolute as isAbsolute6, join as join43, resolve as resolve9 } from "node:path";
11364
11487
  function standaloneArguments(argv) {
11365
11488
  const command = argv[0];
11366
11489
  if (!command || command.startsWith("--") || command === "-h") return argv;
11367
11490
  return command === "terminal" ? argv.slice(1) : null;
11368
11491
  }
11369
11492
  function parseStandaloneOptions(argv, home = homedir6(), cwd = process.cwd()) {
11370
- const root = join42(home, ".xingular-harness");
11493
+ const root = join43(home, ".xingular-harness");
11371
11494
  const paths = {
11372
- dataRoot: join42(root, "data"),
11373
- stateDir: join42(root, "state")
11495
+ dataRoot: join43(root, "data"),
11496
+ stateDir: join43(root, "state")
11374
11497
  };
11375
11498
  const given = /* @__PURE__ */ new Set();
11376
11499
  for (let index = 0; index < argv.length; index += 1) {
@@ -11378,7 +11501,7 @@ function parseStandaloneOptions(argv, home = homedir6(), cwd = process.cwd()) {
11378
11501
  if (argument !== "--data" && argument !== "--state") throw new Error(`Unknown standalone option: ${argument}`);
11379
11502
  const value = argv[index + 1];
11380
11503
  if (!value || value.startsWith("--")) throw new Error(`${argument} needs a directory path.`);
11381
- paths[argument === "--data" ? "dataRoot" : "stateDir"] = resolve8(cwd, value);
11504
+ paths[argument === "--data" ? "dataRoot" : "stateDir"] = resolve9(cwd, value);
11382
11505
  given.add(argument);
11383
11506
  index += 1;
11384
11507
  }
@@ -11402,6 +11525,15 @@ function exitCode(code, signal) {
11402
11525
  if (signal === "SIGHUP") return 129;
11403
11526
  return 1;
11404
11527
  }
11528
+ function spawnError(error, launch) {
11529
+ const code = error.code;
11530
+ if (code !== "ENOENT" && code !== "EACCES" && code !== "EPERM") return error;
11531
+ if (launch.program === process.execPath || launch.env.XINGULAR_HARNESS_NODE === launch.program) {
11532
+ return new Error(`Could not start the Pi terminal because Node could not run "${launch.args[0] ?? "the bundled Pi CLI"}" (${code}). Verify Node and the bundled Pi package are installed.`, { cause: error });
11533
+ }
11534
+ const problem = code === "ENOENT" ? "was not found" : `could not start (${code})`;
11535
+ return new Error(`Could not start Pi terminal. Pi executable "${launch.program}" ${problem}; verify it is installed and executable or check the configured terminal executable.`, { cause: error });
11536
+ }
11405
11537
  function spawnPi(launch, cwd, spawn = nodeSpawn) {
11406
11538
  return new Promise((resolveExit, reject) => {
11407
11539
  const child = spawn(launch.program, launch.args, {
@@ -11429,7 +11561,7 @@ function spawnPi(launch, cwd, spawn = nodeSpawn) {
11429
11561
  process.on("SIGINT", onInterrupt);
11430
11562
  process.on("SIGTERM", onTerminate);
11431
11563
  process.on("SIGHUP", onHangup);
11432
- child.once("error", (error) => finish(() => reject(error)));
11564
+ child.once("error", (error) => finish(() => reject(spawnError(error, launch))));
11433
11565
  child.once("exit", (code, signal) => finish(() => resolveExit(exitCode(code, signal))));
11434
11566
  });
11435
11567
  }
@@ -11437,7 +11569,7 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
11437
11569
  if (!(dependencies.stdinIsTTY ?? process.stdin.isTTY) || !(dependencies.stdoutIsTTY ?? process.stdout.isTTY)) {
11438
11570
  throw new Error("The interactive Harness terminal needs a TTY. Run it from a terminal, or use `xingular-harness serve` for a host process.");
11439
11571
  }
11440
- const cwd = resolve8(dependencies.cwd ?? process.cwd());
11572
+ const cwd = resolve9(dependencies.cwd ?? process.cwd());
11441
11573
  const paths = parseStandaloneOptions(argv, dependencies.home, cwd);
11442
11574
  const makeHarness = dependencies.createHarness ?? createHarness;
11443
11575
  const harness = makeHarness({ dataRoot: paths.dataRoot, stateDir: paths.stateDir, productName: "Xingular Harness" });
@@ -11446,7 +11578,7 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
11446
11578
  await harness.ready;
11447
11579
  if (harness.accounts.overview().accounts.length === 0) harness.accounts.create("Default", "subscription");
11448
11580
  const cwdHash = createHash8("sha256").update(cwd).digest("hex");
11449
- const sessionFolder = join42(paths.dataRoot, "sessions", "terminal", cwdHash);
11581
+ const sessionFolder = join43(paths.dataRoot, "sessions", "terminal", cwdHash);
11450
11582
  const launch = await harness.terminals.prepare({ sessionId, cwd, sessionFolder });
11451
11583
  return await spawnPi(launch, cwd, dependencies.spawn);
11452
11584
  } finally {
@@ -11494,11 +11626,11 @@ function serveCommand(argv) {
11494
11626
  process.stderr.write("serve needs --state <dir>.\n");
11495
11627
  return Promise.resolve(2);
11496
11628
  }
11497
- const stateDir = resolve9(stateArgument);
11629
+ const stateDir = resolve10(stateArgument);
11498
11630
  const peer = new RpcPeer(stdioTransport());
11499
11631
  const options2 = {
11500
11632
  stateDir,
11501
- dataRoot: values2.data?.[0] ? resolve9(values2.data[0]) : null,
11633
+ dataRoot: values2.data?.[0] ? resolve10(values2.data[0]) : null,
11502
11634
  productName: values2.name?.[0],
11503
11635
  builtins: values2.builtins,
11504
11636
  ...switches.has("host-secrets") ? { secrets: remoteSecrets(peer) } : {},
@@ -11508,12 +11640,12 @@ function serveCommand(argv) {
11508
11640
  const harness = createHarness(options2);
11509
11641
  if (switches.has("host-tools")) harness.sessions.provide(remoteTools(peer));
11510
11642
  attach3(harness, peer);
11511
- return new Promise((resolve10) => {
11643
+ return new Promise((resolve11) => {
11512
11644
  let closing = false;
11513
11645
  const shutdown = () => {
11514
11646
  if (closing) return;
11515
11647
  closing = true;
11516
- void Promise.race([harness.close(), new Promise((done) => setTimeout(done, 5e3))]).then(() => resolve10(0));
11648
+ void Promise.race([harness.close(), new Promise((done) => setTimeout(done, 5e3))]).then(() => resolve11(0));
11517
11649
  };
11518
11650
  peer.handle("harness.shutdown", () => void setImmediate(shutdown));
11519
11651
  process.stdin.on("end", shutdown);