@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/README.md +7 -3
- package/dist/cli.js +270 -125
- package/package.json +1 -1
- package/src/accounts/accounts.test.ts +1 -1
- package/src/accounts/env.test.ts +1 -1
- package/src/accounts/env.ts +1 -1
- package/src/core/path.test.ts +14 -0
- package/src/core/path.ts +56 -22
- package/src/mcp/connections.ts +2 -2
- package/src/mcp/manager.ts +2 -2
- package/src/memory/blocks.ts +2 -2
- package/src/memory/caller.ts +1 -1
- package/src/memory/dispatch.ts +1 -1
- package/src/memory/facts.ts +1 -1
- package/src/memory/store.ts +1 -1
- package/src/prompt/compile.test.ts +2 -2
- package/src/prompt/compile.ts +2 -2
- package/src/sessions/contracts.ts +12 -1
- package/src/sessions/runner.ts +16 -3
- package/src/sessions/sessions.test.ts +21 -0
- package/src/terminal/cli.ts +5 -5
- package/src/terminal/client.ts +2 -2
- package/src/terminal/launch.test.ts +34 -0
- package/src/terminal/launch.ts +88 -0
- package/src/terminal/socket.test.ts +44 -1
- package/src/terminal/socket.ts +32 -12
- package/src/terminal/standalone.test.ts +10 -0
- package/src/terminal/standalone.ts +11 -1
- package/src/terminal/terminal.test.ts +33 -5
- package/src/terminal/terminals.ts +32 -18
- package/src/tools/definitions.ts +2 -2
- package/src/tools/types.ts +3 -0
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
|
|
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
|
|
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)}.
|
|
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((
|
|
893
|
+
prompt: (prompt2) => new Promise((resolve11, reject) => {
|
|
894
894
|
const promptId = randomUUID2();
|
|
895
|
-
login.prompts.set(promptId, { resolve:
|
|
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 ? "`
|
|
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: `
|
|
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: `
|
|
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/
|
|
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
|
|
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(() =>
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
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.
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
return
|
|
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
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
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 = {
|
|
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((
|
|
6256
|
-
settle = { resolve:
|
|
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((
|
|
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: "
|
|
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: "
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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((
|
|
8908
|
+
return new Promise((resolve11) => {
|
|
8874
8909
|
let timer;
|
|
8875
8910
|
const done = (value) => {
|
|
8876
8911
|
if (timer) clearTimeout(timer);
|
|
8877
|
-
|
|
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: "
|
|
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 {
|
|
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
|
|
9550
|
-
import { dirname as dirname13, join as
|
|
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((
|
|
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(() =>
|
|
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 = "
|
|
9611
|
-
var APPS_COMMAND = "
|
|
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((
|
|
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((
|
|
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:
|
|
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
|
|
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")}`;
|
|
10046
|
+
const path3 = join38(stateDir, "run", "harness.sock");
|
|
9933
10047
|
if (Buffer.byteLength(path3) <= MAX_SOCKET_PATH) return path3;
|
|
9934
|
-
return
|
|
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((
|
|
10049
|
-
|
|
10050
|
-
|
|
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
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
|
|
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
|
-
|
|
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((
|
|
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,
|
|
10083
|
-
return { node: process.execPath, cli:
|
|
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 =
|
|
10227
|
+
const bin = join39(this.options.stateDir, "run", "bin");
|
|
10102
10228
|
mkdirSync13(bin, { recursive: true });
|
|
10103
|
-
const
|
|
10104
|
-
|
|
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
|
-
|
|
10232
|
+
if (lstatSync4(path3).isFile()) unlinkSync4(path3);
|
|
10112
10233
|
} catch {
|
|
10113
|
-
current = null;
|
|
10114
10234
|
}
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
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
|
|
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
|
|
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: {
|
|
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(
|
|
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(
|
|
10421
|
-
this.accounts = new Accounts({ dir:
|
|
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 ?
|
|
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(
|
|
10442
|
-
const named2 = readJsonOr(
|
|
10443
|
-
if (!packageFolders(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
10588
|
-
if (isAbsolute5(source2)) return
|
|
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) && !(
|
|
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 =
|
|
10767
|
+
const trash = join41(harness.env.root, "trash", "plugins");
|
|
10632
10768
|
mkdirSync15(trash, { recursive: true });
|
|
10633
|
-
renameSync8(plugin.install.source,
|
|
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 =
|
|
10723
|
-
const options2 = { dataRoot: parsed2.values.data ?
|
|
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 ?
|
|
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((
|
|
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
|
-
|
|
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
|
|
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),
|
|
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
|
|
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 =
|
|
11493
|
+
const root = join43(home, ".xingular-harness");
|
|
11358
11494
|
const paths = {
|
|
11359
|
-
dataRoot:
|
|
11360
|
-
stateDir:
|
|
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"] =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
11629
|
+
const stateDir = resolve10(stateArgument);
|
|
11485
11630
|
const peer = new RpcPeer(stdioTransport());
|
|
11486
11631
|
const options2 = {
|
|
11487
11632
|
stateDir,
|
|
11488
|
-
dataRoot: values2.data?.[0] ?
|
|
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((
|
|
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(() =>
|
|
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);
|