@xingular/harness 0.1.3 → 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,22 +9966,98 @@ 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
- import { createHash as createHash7 } from "node:crypto";
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
- const path3 = join37(stateDir, "run", "harness.sock");
10045
+ if (process.platform === "win32") return `\\\\.\\pipe\\xingular-harness-${randomBytes(12).toString("hex")}`;
10046
+ const path3 = join38(stateDir, "run", "harness.sock");
9933
10047
  if (Buffer.byteLength(path3) <= MAX_SOCKET_PATH) return path3;
9934
- 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`);
9935
10049
  }
10050
+ var isPipePath = (path3) => path3.startsWith("\\\\.\\pipe\\");
9936
10051
  function refusedBind(path3, error) {
9937
10052
  const code = error?.code;
9938
10053
  if (code !== "EACCES" && code !== "EPERM") return error;
10054
+ if (isPipePath(path3)) {
10055
+ return new HarnessError(
10056
+ "session_socket_denied",
10057
+ `The session pipe ${path3} could not be created (${code}).`,
10058
+ "Start the harness again from your own user session. If it keeps failing, report this code and the Windows version."
10059
+ );
10060
+ }
9939
10061
  let folder;
9940
10062
  try {
9941
10063
  const stat = statSync6(dirname12(path3));
@@ -10045,22 +10167,26 @@ var SessionSocket = class {
10045
10167
  }
10046
10168
  /** Binds the socket once; later calls wait for the same bind. */
10047
10169
  listen() {
10048
- this.listening ??= new Promise((resolve10, reject) => {
10049
- mkdirSync12(dirname12(this.path), { recursive: true, mode: 448 });
10050
- rmSync8(this.path, { force: true });
10170
+ this.listening ??= new Promise((resolve11, reject) => {
10171
+ if (!isPipePath(this.path)) {
10172
+ mkdirSync12(dirname12(this.path), { recursive: true, mode: 448 });
10173
+ rmSync8(this.path, { force: true });
10174
+ }
10051
10175
  const server = createServer2((client) => this.serve(client));
10052
10176
  server.once("error", (error) => {
10053
10177
  this.listening = null;
10054
10178
  reject(refusedBind(this.path, error));
10055
10179
  });
10056
10180
  server.listen(this.path, () => {
10057
- try {
10058
- chmodSync10(this.path, 384);
10059
- } catch (error) {
10060
- log("session socket permissions", error instanceof Error ? error.message : String(error));
10181
+ if (!isPipePath(this.path)) {
10182
+ try {
10183
+ chmodSync10(this.path, 384);
10184
+ } catch (error) {
10185
+ log("session socket permissions", error instanceof Error ? error.message : String(error));
10186
+ }
10061
10187
  }
10062
10188
  this.server = server;
10063
- resolve10();
10189
+ resolve11();
10064
10190
  });
10065
10191
  });
10066
10192
  return this.listening;
@@ -10071,16 +10197,16 @@ var SessionSocket = class {
10071
10197
  this.server = null;
10072
10198
  this.listening = null;
10073
10199
  for (const client of this.clients) client.destroy();
10074
- if (server) await new Promise((resolve10) => server.close(() => resolve10()));
10075
- rmSync8(this.path, { force: true });
10200
+ if (server) await new Promise((resolve11) => server.close(() => resolve11()));
10201
+ if (!isPipePath(this.path)) rmSync8(this.path, { force: true });
10076
10202
  }
10077
10203
  };
10078
10204
 
10079
10205
  // src/terminal/terminals.ts
10080
10206
  function defaultAssets() {
10081
10207
  const here = dirname13(fileURLToPath(import.meta.url));
10082
- const dist = [here, join38(here, "..", "..", "dist"), join38(here, "..", "dist")].find((folder) => existsSync14(join38(folder, "terminal-pi.js"))) ?? here;
10083
- 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") };
10084
10210
  }
10085
10211
  var READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["read", "grep", "find", "ls"]);
10086
10212
  var Terminals = class {
@@ -10098,23 +10224,28 @@ var Terminals = class {
10098
10224
  assets;
10099
10225
  /** The commands a terminal session finds on PATH. Rewritten only when they change. */
10100
10226
  ensureCommands() {
10101
- const bin = join38(this.options.stateDir, "run", "bin");
10227
+ const bin = join39(this.options.stateDir, "run", "bin");
10102
10228
  mkdirSync13(bin, { recursive: true });
10103
- const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
10104
- for (const [name, mode] of [[WIKI_COMMAND, "wiki"], [APPS_COMMAND, "mcp"]]) {
10105
- const wrapper = join38(bin, name);
10106
- const text6 = `#!/bin/sh
10107
- exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
10108
- `;
10109
- let current = null;
10229
+ for (const name of ["agentx-wiki", "agentx-mcp", "agentx-wiki.cmd", "agentx-mcp.cmd"]) {
10230
+ const path3 = join39(bin, name);
10110
10231
  try {
10111
- current = readFileSync19(wrapper, "utf8");
10232
+ if (lstatSync4(path3).isFile()) unlinkSync4(path3);
10112
10233
  } catch {
10113
- current = null;
10114
10234
  }
10115
- if (current !== text6) {
10116
- writeFileSync3(wrapper, text6);
10117
- 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
+ }
10118
10249
  }
10119
10250
  }
10120
10251
  return bin;
@@ -10181,7 +10312,7 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
10181
10312
  return live;
10182
10313
  }
10183
10314
  historyDir(input) {
10184
- return join38(input.sessionFolder, "pi");
10315
+ return join39(input.sessionFolder, "pi");
10185
10316
  }
10186
10317
  runRecord(live, runId, text6) {
10187
10318
  const { input } = live;
@@ -10256,8 +10387,7 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
10256
10387
  const { root } = env;
10257
10388
  if (!input?.sessionId || !input.cwd || !input.sessionFolder) throw invalid("A terminal session needs an id, a working folder, and its own folder.");
10258
10389
  await this.dispose(input.sessionId);
10259
- const program = this.options.program ? this.options.program() : findBinary("pi");
10260
- 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 });
10261
10391
  const account = input.accountId ? accounts.find(input.accountId) : accounts.resolve(null, null);
10262
10392
  const launch = launchEnv(accounts.dir, account);
10263
10393
  const loading = await this.options.plugins?.loading({ cwd: input.cwd, agentDir: configDir(accounts.dir, account), install: true, caller: { projectId: input.projectId ?? null } }) ?? null;
@@ -10295,7 +10425,7 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
10295
10425
  });
10296
10426
  live.token = token;
10297
10427
  this.sessions.set(sessionId, live);
10298
- const args = ["--append-system-prompt", render2(env, pack)];
10428
+ const args = [...command.argsPrefix, "--append-system-prompt", render2(env, pack)];
10299
10429
  if (input.model) args.push("--model", input.model);
10300
10430
  if (loading) {
10301
10431
  args.push("--no-extensions", "--no-skills", "--no-prompt-templates", "--no-themes");
@@ -10308,9 +10438,15 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
10308
10438
  args.push("--extension", this.assets.piExtension, "--session-dir", this.historyDir(input));
10309
10439
  if (input.nativeSessionId) args.push("--session", input.nativeSessionId);
10310
10440
  return {
10311
- program,
10441
+ program: command.program,
10312
10442
  args,
10313
- 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
+ },
10314
10450
  accountId: account.id,
10315
10451
  nativeSessionId: input.nativeSessionId ?? null
10316
10452
  };
@@ -10413,12 +10549,12 @@ var Harness = class {
10413
10549
  constructor(options2) {
10414
10550
  this.stateDir = options2.stateDir;
10415
10551
  mkdirSync14(this.stateDir, { recursive: true });
10416
- isolateProcess(join39(this.stateDir, "accounts"));
10552
+ isolateProcess(join40(this.stateDir, "accounts"));
10417
10553
  this.env = new Env(options2.dataRoot || null, options2.directory ?? EMPTY_DIRECTORY, options2.productName ?? DEFAULT_PRODUCT_NAME, this.bus);
10418
10554
  this.policy = options2.policy ?? {};
10419
10555
  configureBuiltins(options2.builtins ?? []);
10420
- const secrets = options2.secrets ?? fileSecretStore(join39(this.stateDir, "secrets.json"));
10421
- 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 });
10422
10558
  this.approvals = new Approvals(this.stateDir, this.bus);
10423
10559
  this.runs = new RunRegistry({
10424
10560
  root: () => this.env.root,
@@ -10430,7 +10566,7 @@ var Harness = class {
10430
10566
  ended: (runId, sessionId) => this.apps.revokeRun(runId, sessionId)
10431
10567
  });
10432
10568
  this.apps = new ConnectedApps({ env: this.env, stateDir: this.stateDir, secrets, bus: this.bus, runs: this.runs, policy: this.policy });
10433
- 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 });
10434
10570
  this.hooks = new Hooks({
10435
10571
  stateDir: this.stateDir,
10436
10572
  bus: this.bus,
@@ -10438,9 +10574,9 @@ var Harness = class {
10438
10574
  // The shared folder stands in for an account: packages are the same for all of them.
10439
10575
  wanted: async () => {
10440
10576
  if (!this.env.dataRoot) return [];
10441
- const shared = ensureSharedDir(join39(this.stateDir, "accounts"));
10442
- const named2 = readJsonOr(join39(shared, "settings.json"), {}).packages;
10443
- 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 [];
10444
10580
  return (await this.plugins.loading({ cwd: this.stateDir, agentDir: shared, caller: {} })).hooks;
10445
10581
  },
10446
10582
  call: (connection, tool, args) => this.apps.callUnattended(connection, tool, args ?? {}),
@@ -10499,7 +10635,7 @@ var Harness = class {
10499
10635
  const root = dataRoot;
10500
10636
  this.hooks.watch(wikiDir(root));
10501
10637
  void this.hooks.reload();
10502
- 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 });
10503
10639
  let migrationError = null;
10504
10640
  let wikiMigrationError = null;
10505
10641
  try {
@@ -10574,18 +10710,18 @@ function createHarness(options2) {
10574
10710
 
10575
10711
  // src/plugins/cli.ts
10576
10712
  import { homedir as homedir5 } from "node:os";
10577
- import { join as join41, resolve as resolve7 } from "node:path";
10713
+ import { join as join42, resolve as resolve8 } from "node:path";
10578
10714
  import { createInterface } from "node:readline/promises";
10579
10715
 
10580
10716
  // src/plugins/install.ts
10581
- 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";
10582
10718
  import { homedir as homedir4 } from "node:os";
10583
- 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";
10584
10720
  function normalizeSource(input) {
10585
10721
  const source2 = input.trim();
10586
10722
  if (!source2) throw invalid("Name a package: a folder, an npm name, or a git address.");
10587
- if (source2.startsWith("~/")) return join40(process.env.HOME || homedir4(), source2.slice(2));
10588
- 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);
10589
10725
  if (source2.startsWith("./") || source2.startsWith("../")) throw invalid("Give the whole path of the folder.");
10590
10726
  return /^(npm:|git:|https?:\/\/|ssh:\/\/|git@)/.test(source2) ? source2 : `npm:${source2}`;
10591
10727
  }
@@ -10598,7 +10734,7 @@ async function manager(harness) {
10598
10734
  var changed = (harness) => harness.bus.emit({ type: "plugins.changed" });
10599
10735
  async function install2(harness, params) {
10600
10736
  const source2 = normalizeSource(params.source);
10601
- 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.");
10602
10738
  await ensureAdopted(harness);
10603
10739
  const before = new Set((await list2(harness, params)).map((plugin) => plugin.id));
10604
10740
  const { settings, packages } = await manager(harness);
@@ -10628,9 +10764,9 @@ function installed(plugins, pluginId) {
10628
10764
  async function remove(harness, params) {
10629
10765
  const plugin = installed(await list2(harness, params), params.pluginId);
10630
10766
  if (plugin.install.kind === "folder") {
10631
- const trash = join40(harness.env.root, "trash", "plugins");
10767
+ const trash = join41(harness.env.root, "trash", "plugins");
10632
10768
  mkdirSync15(trash, { recursive: true });
10633
- 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, "-")}`));
10634
10770
  } else {
10635
10771
  await (await manager(harness)).packages.removeAndPersist(plugin.install.source);
10636
10772
  }
@@ -10719,10 +10855,10 @@ async function runPluginsCli(argv, dependencies = {}) {
10719
10855
  return command ? 0 : 2;
10720
10856
  }
10721
10857
  const base = dependencies.cwd ?? process.cwd();
10722
- const root = join41(dependencies.home ?? homedir5(), ".xingular-harness");
10723
- 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" };
10724
10860
  const harness = dependencies.createHarness ? dependencies.createHarness(options2) : new Harness(options2);
10725
- 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 };
10726
10862
  const need = (what) => {
10727
10863
  if (!target) throw new Error(`${command} needs ${what}.`);
10728
10864
  return target;
@@ -10908,7 +11044,7 @@ var RpcPeer = class {
10908
11044
  /** Asks the host to perform a named operation. */
10909
11045
  callHost(call, params, timeoutMs = 3e4) {
10910
11046
  const id = `s${++this.nextCallId}`;
10911
- return new Promise((resolve10, reject) => {
11047
+ return new Promise((resolve11, reject) => {
10912
11048
  const timer = setTimeout(() => {
10913
11049
  this.pendingCalls.delete(id);
10914
11050
  reject(new HarnessError("host_timeout", `The host did not answer ${call}.`));
@@ -10916,7 +11052,7 @@ var RpcPeer = class {
10916
11052
  this.pendingCalls.set(id, {
10917
11053
  resolve: (value) => {
10918
11054
  clearTimeout(timer);
10919
- resolve10(value);
11055
+ resolve11(value);
10920
11056
  },
10921
11057
  reject: (error) => {
10922
11058
  clearTimeout(timer);
@@ -10967,7 +11103,7 @@ var RpcPeer = class {
10967
11103
  };
10968
11104
 
10969
11105
  // src/server/methods.ts
10970
- import { readFileSync as readFileSync20, statSync as statSync8 } from "node:fs";
11106
+ import { readFileSync as readFileSync21, statSync as statSync8 } from "node:fs";
10971
11107
  import { basename as basename9 } from "node:path";
10972
11108
 
10973
11109
  // src/memory/graph.ts
@@ -11182,7 +11318,7 @@ function methods(harness) {
11182
11318
  "wiki.ingest": (params) => {
11183
11319
  const path3 = text5(params, "path");
11184
11320
  if (statSync8(path3).size > MAX_SOURCE_BYTES) throw invalid("Sources larger than 50 MB are not imported.");
11185
- 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);
11186
11322
  harness.bus.emit({ type: "wiki.changed" });
11187
11323
  return source2;
11188
11324
  },
@@ -11347,17 +11483,17 @@ function attach3(harness, peer) {
11347
11483
  import { spawn as nodeSpawn } from "node:child_process";
11348
11484
  import { createHash as createHash8, randomUUID as randomUUID8 } from "node:crypto";
11349
11485
  import { homedir as homedir6 } from "node:os";
11350
- 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";
11351
11487
  function standaloneArguments(argv) {
11352
11488
  const command = argv[0];
11353
11489
  if (!command || command.startsWith("--") || command === "-h") return argv;
11354
11490
  return command === "terminal" ? argv.slice(1) : null;
11355
11491
  }
11356
11492
  function parseStandaloneOptions(argv, home = homedir6(), cwd = process.cwd()) {
11357
- const root = join42(home, ".xingular-harness");
11493
+ const root = join43(home, ".xingular-harness");
11358
11494
  const paths = {
11359
- dataRoot: join42(root, "data"),
11360
- stateDir: join42(root, "state")
11495
+ dataRoot: join43(root, "data"),
11496
+ stateDir: join43(root, "state")
11361
11497
  };
11362
11498
  const given = /* @__PURE__ */ new Set();
11363
11499
  for (let index = 0; index < argv.length; index += 1) {
@@ -11365,7 +11501,7 @@ function parseStandaloneOptions(argv, home = homedir6(), cwd = process.cwd()) {
11365
11501
  if (argument !== "--data" && argument !== "--state") throw new Error(`Unknown standalone option: ${argument}`);
11366
11502
  const value = argv[index + 1];
11367
11503
  if (!value || value.startsWith("--")) throw new Error(`${argument} needs a directory path.`);
11368
- paths[argument === "--data" ? "dataRoot" : "stateDir"] = resolve8(cwd, value);
11504
+ paths[argument === "--data" ? "dataRoot" : "stateDir"] = resolve9(cwd, value);
11369
11505
  given.add(argument);
11370
11506
  index += 1;
11371
11507
  }
@@ -11389,6 +11525,15 @@ function exitCode(code, signal) {
11389
11525
  if (signal === "SIGHUP") return 129;
11390
11526
  return 1;
11391
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
+ }
11392
11537
  function spawnPi(launch, cwd, spawn = nodeSpawn) {
11393
11538
  return new Promise((resolveExit, reject) => {
11394
11539
  const child = spawn(launch.program, launch.args, {
@@ -11416,7 +11561,7 @@ function spawnPi(launch, cwd, spawn = nodeSpawn) {
11416
11561
  process.on("SIGINT", onInterrupt);
11417
11562
  process.on("SIGTERM", onTerminate);
11418
11563
  process.on("SIGHUP", onHangup);
11419
- child.once("error", (error) => finish(() => reject(error)));
11564
+ child.once("error", (error) => finish(() => reject(spawnError(error, launch))));
11420
11565
  child.once("exit", (code, signal) => finish(() => resolveExit(exitCode(code, signal))));
11421
11566
  });
11422
11567
  }
@@ -11424,7 +11569,7 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
11424
11569
  if (!(dependencies.stdinIsTTY ?? process.stdin.isTTY) || !(dependencies.stdoutIsTTY ?? process.stdout.isTTY)) {
11425
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.");
11426
11571
  }
11427
- const cwd = resolve8(dependencies.cwd ?? process.cwd());
11572
+ const cwd = resolve9(dependencies.cwd ?? process.cwd());
11428
11573
  const paths = parseStandaloneOptions(argv, dependencies.home, cwd);
11429
11574
  const makeHarness = dependencies.createHarness ?? createHarness;
11430
11575
  const harness = makeHarness({ dataRoot: paths.dataRoot, stateDir: paths.stateDir, productName: "Xingular Harness" });
@@ -11433,7 +11578,7 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
11433
11578
  await harness.ready;
11434
11579
  if (harness.accounts.overview().accounts.length === 0) harness.accounts.create("Default", "subscription");
11435
11580
  const cwdHash = createHash8("sha256").update(cwd).digest("hex");
11436
- const sessionFolder = join42(paths.dataRoot, "sessions", "terminal", cwdHash);
11581
+ const sessionFolder = join43(paths.dataRoot, "sessions", "terminal", cwdHash);
11437
11582
  const launch = await harness.terminals.prepare({ sessionId, cwd, sessionFolder });
11438
11583
  return await spawnPi(launch, cwd, dependencies.spawn);
11439
11584
  } finally {
@@ -11481,11 +11626,11 @@ function serveCommand(argv) {
11481
11626
  process.stderr.write("serve needs --state <dir>.\n");
11482
11627
  return Promise.resolve(2);
11483
11628
  }
11484
- const stateDir = resolve9(stateArgument);
11629
+ const stateDir = resolve10(stateArgument);
11485
11630
  const peer = new RpcPeer(stdioTransport());
11486
11631
  const options2 = {
11487
11632
  stateDir,
11488
- dataRoot: values2.data?.[0] ? resolve9(values2.data[0]) : null,
11633
+ dataRoot: values2.data?.[0] ? resolve10(values2.data[0]) : null,
11489
11634
  productName: values2.name?.[0],
11490
11635
  builtins: values2.builtins,
11491
11636
  ...switches.has("host-secrets") ? { secrets: remoteSecrets(peer) } : {},
@@ -11495,12 +11640,12 @@ function serveCommand(argv) {
11495
11640
  const harness = createHarness(options2);
11496
11641
  if (switches.has("host-tools")) harness.sessions.provide(remoteTools(peer));
11497
11642
  attach3(harness, peer);
11498
- return new Promise((resolve10) => {
11643
+ return new Promise((resolve11) => {
11499
11644
  let closing = false;
11500
11645
  const shutdown = () => {
11501
11646
  if (closing) return;
11502
11647
  closing = true;
11503
- 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));
11504
11649
  };
11505
11650
  peer.handle("harness.shutdown", () => void setImmediate(shutdown));
11506
11651
  process.stdin.on("end", shutdown);