@xingular/harness 0.1.4 → 0.1.6
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 +249 -117
- 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.ts +1 -1
- 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,19 +9966,86 @@ var TerminalController = class {
|
|
|
9920
9966
|
}
|
|
9921
9967
|
};
|
|
9922
9968
|
|
|
9969
|
+
// src/terminal/launch.ts
|
|
9970
|
+
import { existsSync as existsSync14, readFileSync as readFileSync19 } from "node:fs";
|
|
9971
|
+
import { createRequire } from "node:module";
|
|
9972
|
+
import { join as join37, resolve as resolve6 } from "node:path";
|
|
9973
|
+
var PI_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
9974
|
+
function bundledPiCli() {
|
|
9975
|
+
const require2 = createRequire(import.meta.url);
|
|
9976
|
+
for (const nodeModules of require2.resolve.paths(PI_PACKAGE) ?? []) {
|
|
9977
|
+
const directory = join37(nodeModules, ...PI_PACKAGE.split("/"));
|
|
9978
|
+
const manifestPath = join37(directory, "package.json");
|
|
9979
|
+
if (!existsSync14(manifestPath)) continue;
|
|
9980
|
+
try {
|
|
9981
|
+
const manifest = JSON.parse(readFileSync19(manifestPath, "utf8"));
|
|
9982
|
+
if (manifest.name === PI_PACKAGE) {
|
|
9983
|
+
const executable = typeof manifest.bin === "string" ? manifest.bin : manifest.bin && typeof manifest.bin === "object" && typeof manifest.bin.pi === "string" ? manifest.bin.pi : null;
|
|
9984
|
+
if (!executable) throw new Error("the package does not declare its pi command");
|
|
9985
|
+
const cli = resolve6(directory, executable);
|
|
9986
|
+
if (!existsSync14(cli)) throw new Error(`the declared command does not exist at ${cli}`);
|
|
9987
|
+
return cli;
|
|
9988
|
+
}
|
|
9989
|
+
} catch {
|
|
9990
|
+
continue;
|
|
9991
|
+
}
|
|
9992
|
+
}
|
|
9993
|
+
throw new HarnessError("harness_unavailable", `The bundled ${PI_PACKAGE} package does not expose a usable pi command. Reinstall the Harness package and try again.`);
|
|
9994
|
+
}
|
|
9995
|
+
function resolvePiCommand(options2) {
|
|
9996
|
+
if (options2.programOverride) {
|
|
9997
|
+
const program2 = options2.programOverride();
|
|
9998
|
+
if (!program2) throw new HarnessError("harness_unavailable", "The Pi CLI was not found on PATH.");
|
|
9999
|
+
return { program: program2, argsPrefix: [] };
|
|
10000
|
+
}
|
|
10001
|
+
if (options2.platform === "win32") return { program: options2.node, argsPrefix: [bundledPiCli()] };
|
|
10002
|
+
const program = findBinary("pi");
|
|
10003
|
+
if (!program) throw new HarnessError("harness_unavailable", "The Pi CLI was not found on PATH.");
|
|
10004
|
+
return { program, argsPrefix: [] };
|
|
10005
|
+
}
|
|
10006
|
+
function bashPath(path3) {
|
|
10007
|
+
const drive = /^([a-z]):[\\/](.*)$/i.exec(path3);
|
|
10008
|
+
if (drive) return `/${drive[1].toLowerCase()}/${drive[2].replaceAll("\\", "/")}`;
|
|
10009
|
+
if (path3.startsWith("\\\\")) return `//${path3.slice(2).replaceAll("\\", "/")}`;
|
|
10010
|
+
return path3;
|
|
10011
|
+
}
|
|
10012
|
+
function shellQuote(value) {
|
|
10013
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
10014
|
+
}
|
|
10015
|
+
function terminalCommandFiles(platform, node, cli, name, mode) {
|
|
10016
|
+
const shellNode = platform === "win32" ? bashPath(node) : node;
|
|
10017
|
+
const shellCli = platform === "win32" ? bashPath(cli) : cli;
|
|
10018
|
+
const files = [{
|
|
10019
|
+
name,
|
|
10020
|
+
content: `#!/bin/sh
|
|
10021
|
+
exec ${shellQuote(shellNode)} ${shellQuote(shellCli)} ${mode} "$@"
|
|
10022
|
+
`
|
|
10023
|
+
}];
|
|
10024
|
+
if (platform === "win32") {
|
|
10025
|
+
files.push({
|
|
10026
|
+
name: `${name}.cmd`,
|
|
10027
|
+
content: `@echo off\r
|
|
10028
|
+
"%XINGULAR_HARNESS_NODE%" "%XINGULAR_HARNESS_CLI%" ${mode} %*\r
|
|
10029
|
+
exit /b %ERRORLEVEL%\r
|
|
10030
|
+
`
|
|
10031
|
+
});
|
|
10032
|
+
}
|
|
10033
|
+
return files;
|
|
10034
|
+
}
|
|
10035
|
+
|
|
9923
10036
|
// src/terminal/socket.ts
|
|
9924
10037
|
import { createHash as createHash7, randomBytes } from "node:crypto";
|
|
9925
10038
|
import { chmodSync as chmodSync10, mkdirSync as mkdirSync12, rmSync as rmSync8, statSync as statSync6 } from "node:fs";
|
|
9926
10039
|
import { createServer as createServer2 } from "node:net";
|
|
9927
10040
|
import { tmpdir } from "node:os";
|
|
9928
|
-
import { dirname as dirname12, join as
|
|
10041
|
+
import { dirname as dirname12, join as join38 } from "node:path";
|
|
9929
10042
|
var MAX_REQUEST_BYTES = 1024 * 1024;
|
|
9930
10043
|
var MAX_SOCKET_PATH = 100;
|
|
9931
10044
|
function socketPath(stateDir) {
|
|
9932
10045
|
if (process.platform === "win32") return `\\\\.\\pipe\\xingular-harness-${randomBytes(12).toString("hex")}`;
|
|
9933
|
-
const path3 =
|
|
10046
|
+
const path3 = join38(stateDir, "run", "harness.sock");
|
|
9934
10047
|
if (Buffer.byteLength(path3) <= MAX_SOCKET_PATH) return path3;
|
|
9935
|
-
return
|
|
10048
|
+
return join38(tmpdir(), `xh-${createHash7("sha256").update(path3).digest("hex").slice(0, 16)}.sock`);
|
|
9936
10049
|
}
|
|
9937
10050
|
var isPipePath = (path3) => path3.startsWith("\\\\.\\pipe\\");
|
|
9938
10051
|
function refusedBind(path3, error) {
|
|
@@ -10054,7 +10167,7 @@ var SessionSocket = class {
|
|
|
10054
10167
|
}
|
|
10055
10168
|
/** Binds the socket once; later calls wait for the same bind. */
|
|
10056
10169
|
listen() {
|
|
10057
|
-
this.listening ??= new Promise((
|
|
10170
|
+
this.listening ??= new Promise((resolve11, reject) => {
|
|
10058
10171
|
if (!isPipePath(this.path)) {
|
|
10059
10172
|
mkdirSync12(dirname12(this.path), { recursive: true, mode: 448 });
|
|
10060
10173
|
rmSync8(this.path, { force: true });
|
|
@@ -10073,7 +10186,7 @@ var SessionSocket = class {
|
|
|
10073
10186
|
}
|
|
10074
10187
|
}
|
|
10075
10188
|
this.server = server;
|
|
10076
|
-
|
|
10189
|
+
resolve11();
|
|
10077
10190
|
});
|
|
10078
10191
|
});
|
|
10079
10192
|
return this.listening;
|
|
@@ -10084,7 +10197,7 @@ var SessionSocket = class {
|
|
|
10084
10197
|
this.server = null;
|
|
10085
10198
|
this.listening = null;
|
|
10086
10199
|
for (const client of this.clients) client.destroy();
|
|
10087
|
-
if (server) await new Promise((
|
|
10200
|
+
if (server) await new Promise((resolve11) => server.close(() => resolve11()));
|
|
10088
10201
|
if (!isPipePath(this.path)) rmSync8(this.path, { force: true });
|
|
10089
10202
|
}
|
|
10090
10203
|
};
|
|
@@ -10092,8 +10205,8 @@ var SessionSocket = class {
|
|
|
10092
10205
|
// src/terminal/terminals.ts
|
|
10093
10206
|
function defaultAssets() {
|
|
10094
10207
|
const here = dirname13(fileURLToPath(import.meta.url));
|
|
10095
|
-
const dist = [here,
|
|
10096
|
-
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") };
|
|
10097
10210
|
}
|
|
10098
10211
|
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["read", "grep", "find", "ls"]);
|
|
10099
10212
|
var Terminals = class {
|
|
@@ -10111,23 +10224,28 @@ var Terminals = class {
|
|
|
10111
10224
|
assets;
|
|
10112
10225
|
/** The commands a terminal session finds on PATH. Rewritten only when they change. */
|
|
10113
10226
|
ensureCommands() {
|
|
10114
|
-
const bin =
|
|
10227
|
+
const bin = join39(this.options.stateDir, "run", "bin");
|
|
10115
10228
|
mkdirSync13(bin, { recursive: true });
|
|
10116
|
-
const
|
|
10117
|
-
|
|
10118
|
-
const wrapper = join38(bin, name);
|
|
10119
|
-
const text6 = `#!/bin/sh
|
|
10120
|
-
exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
|
|
10121
|
-
`;
|
|
10122
|
-
let current = null;
|
|
10229
|
+
for (const name of ["agentx-wiki", "agentx-mcp", "agentx-wiki.cmd", "agentx-mcp.cmd"]) {
|
|
10230
|
+
const path3 = join39(bin, name);
|
|
10123
10231
|
try {
|
|
10124
|
-
|
|
10232
|
+
if (lstatSync4(path3).isFile()) unlinkSync4(path3);
|
|
10125
10233
|
} catch {
|
|
10126
|
-
current = null;
|
|
10127
10234
|
}
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10235
|
+
}
|
|
10236
|
+
for (const [name, mode] of [[WIKI_COMMAND, "wiki"], [APPS_COMMAND, "mcp"]]) {
|
|
10237
|
+
for (const file of terminalCommandFiles(process.platform, this.assets.node, this.assets.cli, name, mode)) {
|
|
10238
|
+
const path3 = join39(bin, file.name);
|
|
10239
|
+
let current = null;
|
|
10240
|
+
try {
|
|
10241
|
+
current = readFileSync20(path3, "utf8");
|
|
10242
|
+
} catch {
|
|
10243
|
+
current = null;
|
|
10244
|
+
}
|
|
10245
|
+
if (current !== file.content) {
|
|
10246
|
+
writeFileSync3(path3, file.content);
|
|
10247
|
+
chmodSync11(path3, 493);
|
|
10248
|
+
}
|
|
10131
10249
|
}
|
|
10132
10250
|
}
|
|
10133
10251
|
return bin;
|
|
@@ -10194,7 +10312,7 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
|
|
|
10194
10312
|
return live;
|
|
10195
10313
|
}
|
|
10196
10314
|
historyDir(input) {
|
|
10197
|
-
return
|
|
10315
|
+
return join39(input.sessionFolder, "pi");
|
|
10198
10316
|
}
|
|
10199
10317
|
runRecord(live, runId, text6) {
|
|
10200
10318
|
const { input } = live;
|
|
@@ -10269,8 +10387,7 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
|
|
|
10269
10387
|
const { root } = env;
|
|
10270
10388
|
if (!input?.sessionId || !input.cwd || !input.sessionFolder) throw invalid("A terminal session needs an id, a working folder, and its own folder.");
|
|
10271
10389
|
await this.dispose(input.sessionId);
|
|
10272
|
-
const
|
|
10273
|
-
if (!program) throw new HarnessError("harness_unavailable", "The Pi CLI was not found on PATH.");
|
|
10390
|
+
const command = resolvePiCommand({ platform: process.platform, node: this.assets.node, programOverride: this.options.program });
|
|
10274
10391
|
const account = input.accountId ? accounts.find(input.accountId) : accounts.resolve(null, null);
|
|
10275
10392
|
const launch = launchEnv(accounts.dir, account);
|
|
10276
10393
|
const loading = await this.options.plugins?.loading({ cwd: input.cwd, agentDir: configDir(accounts.dir, account), install: true, caller: { projectId: input.projectId ?? null } }) ?? null;
|
|
@@ -10308,7 +10425,7 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
|
|
|
10308
10425
|
});
|
|
10309
10426
|
live.token = token;
|
|
10310
10427
|
this.sessions.set(sessionId, live);
|
|
10311
|
-
const args = ["--append-system-prompt", render2(env, pack)];
|
|
10428
|
+
const args = [...command.argsPrefix, "--append-system-prompt", render2(env, pack)];
|
|
10312
10429
|
if (input.model) args.push("--model", input.model);
|
|
10313
10430
|
if (loading) {
|
|
10314
10431
|
args.push("--no-extensions", "--no-skills", "--no-prompt-templates", "--no-themes");
|
|
@@ -10321,9 +10438,15 @@ exec ${quote(this.assets.node)} ${quote(this.assets.cli)} ${mode} "$@"
|
|
|
10321
10438
|
args.push("--extension", this.assets.piExtension, "--session-dir", this.historyDir(input));
|
|
10322
10439
|
if (input.nativeSessionId) args.push("--session", input.nativeSessionId);
|
|
10323
10440
|
return {
|
|
10324
|
-
program,
|
|
10441
|
+
program: command.program,
|
|
10325
10442
|
args,
|
|
10326
|
-
env: {
|
|
10443
|
+
env: {
|
|
10444
|
+
...launch,
|
|
10445
|
+
...process.platform === "win32" ? { XINGULAR_HARNESS_NODE: this.assets.node, XINGULAR_HARNESS_CLI: this.assets.cli } : {},
|
|
10446
|
+
...prependSearchPath(this.ensureCommands(), searchPath()),
|
|
10447
|
+
[SOCKET_ENV]: this.socket.path,
|
|
10448
|
+
[TOKEN_ENV]: token
|
|
10449
|
+
},
|
|
10327
10450
|
accountId: account.id,
|
|
10328
10451
|
nativeSessionId: input.nativeSessionId ?? null
|
|
10329
10452
|
};
|
|
@@ -10426,12 +10549,12 @@ var Harness = class {
|
|
|
10426
10549
|
constructor(options2) {
|
|
10427
10550
|
this.stateDir = options2.stateDir;
|
|
10428
10551
|
mkdirSync14(this.stateDir, { recursive: true });
|
|
10429
|
-
isolateProcess(
|
|
10552
|
+
isolateProcess(join40(this.stateDir, "accounts"));
|
|
10430
10553
|
this.env = new Env(options2.dataRoot || null, options2.directory ?? EMPTY_DIRECTORY, options2.productName ?? DEFAULT_PRODUCT_NAME, this.bus);
|
|
10431
10554
|
this.policy = options2.policy ?? {};
|
|
10432
10555
|
configureBuiltins(options2.builtins ?? []);
|
|
10433
|
-
const secrets = options2.secrets ?? fileSecretStore(
|
|
10434
|
-
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 });
|
|
10435
10558
|
this.approvals = new Approvals(this.stateDir, this.bus);
|
|
10436
10559
|
this.runs = new RunRegistry({
|
|
10437
10560
|
root: () => this.env.root,
|
|
@@ -10443,7 +10566,7 @@ var Harness = class {
|
|
|
10443
10566
|
ended: (runId, sessionId) => this.apps.revokeRun(runId, sessionId)
|
|
10444
10567
|
});
|
|
10445
10568
|
this.apps = new ConnectedApps({ env: this.env, stateDir: this.stateDir, secrets, bus: this.bus, runs: this.runs, policy: this.policy });
|
|
10446
|
-
this.plugins = new Plugins({ stateDir: this.stateDir, bus: this.bus, resolver: options2.resolver, attachments: () => this.env.dataRoot ? load2(this.env.dataRoot) : null, packagesDir: () => this.env.dataRoot ?
|
|
10569
|
+
this.plugins = new Plugins({ stateDir: this.stateDir, bus: this.bus, resolver: options2.resolver, attachments: () => this.env.dataRoot ? load2(this.env.dataRoot) : null, packagesDir: () => this.env.dataRoot ? join40(this.env.dataRoot, "plugins") : null });
|
|
10447
10570
|
this.hooks = new Hooks({
|
|
10448
10571
|
stateDir: this.stateDir,
|
|
10449
10572
|
bus: this.bus,
|
|
@@ -10451,9 +10574,9 @@ var Harness = class {
|
|
|
10451
10574
|
// The shared folder stands in for an account: packages are the same for all of them.
|
|
10452
10575
|
wanted: async () => {
|
|
10453
10576
|
if (!this.env.dataRoot) return [];
|
|
10454
|
-
const shared = ensureSharedDir(
|
|
10455
|
-
const named2 = readJsonOr(
|
|
10456
|
-
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 [];
|
|
10457
10580
|
return (await this.plugins.loading({ cwd: this.stateDir, agentDir: shared, caller: {} })).hooks;
|
|
10458
10581
|
},
|
|
10459
10582
|
call: (connection, tool, args) => this.apps.callUnattended(connection, tool, args ?? {}),
|
|
@@ -10512,7 +10635,7 @@ var Harness = class {
|
|
|
10512
10635
|
const root = dataRoot;
|
|
10513
10636
|
this.hooks.watch(wikiDir(root));
|
|
10514
10637
|
void this.hooks.reload();
|
|
10515
|
-
for (const folder of ["registry/skills", "registry/prompts", "registry/rules", "config", "runs"]) mkdirSync14(
|
|
10638
|
+
for (const folder of ["registry/skills", "registry/prompts", "registry/rules", "config", "runs"]) mkdirSync14(join40(root, folder), { recursive: true });
|
|
10516
10639
|
let migrationError = null;
|
|
10517
10640
|
let wikiMigrationError = null;
|
|
10518
10641
|
try {
|
|
@@ -10587,18 +10710,18 @@ function createHarness(options2) {
|
|
|
10587
10710
|
|
|
10588
10711
|
// src/plugins/cli.ts
|
|
10589
10712
|
import { homedir as homedir5 } from "node:os";
|
|
10590
|
-
import { join as
|
|
10713
|
+
import { join as join42, resolve as resolve8 } from "node:path";
|
|
10591
10714
|
import { createInterface } from "node:readline/promises";
|
|
10592
10715
|
|
|
10593
10716
|
// src/plugins/install.ts
|
|
10594
|
-
import { existsSync as
|
|
10717
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync15, renameSync as renameSync8, statSync as statSync7 } from "node:fs";
|
|
10595
10718
|
import { homedir as homedir4 } from "node:os";
|
|
10596
|
-
import { basename as basename8, isAbsolute as isAbsolute5, join as
|
|
10719
|
+
import { basename as basename8, isAbsolute as isAbsolute5, join as join41, resolve as resolve7 } from "node:path";
|
|
10597
10720
|
function normalizeSource(input) {
|
|
10598
10721
|
const source2 = input.trim();
|
|
10599
10722
|
if (!source2) throw invalid("Name a package: a folder, an npm name, or a git address.");
|
|
10600
|
-
if (source2.startsWith("~/")) return
|
|
10601
|
-
if (isAbsolute5(source2)) return
|
|
10723
|
+
if (source2.startsWith("~/")) return join41(process.env.HOME || homedir4(), source2.slice(2));
|
|
10724
|
+
if (isAbsolute5(source2)) return resolve7(source2);
|
|
10602
10725
|
if (source2.startsWith("./") || source2.startsWith("../")) throw invalid("Give the whole path of the folder.");
|
|
10603
10726
|
return /^(npm:|git:|https?:\/\/|ssh:\/\/|git@)/.test(source2) ? source2 : `npm:${source2}`;
|
|
10604
10727
|
}
|
|
@@ -10611,7 +10734,7 @@ async function manager(harness) {
|
|
|
10611
10734
|
var changed = (harness) => harness.bus.emit({ type: "plugins.changed" });
|
|
10612
10735
|
async function install2(harness, params) {
|
|
10613
10736
|
const source2 = normalizeSource(params.source);
|
|
10614
|
-
if (isAbsolute5(source2) && !(
|
|
10737
|
+
if (isAbsolute5(source2) && !(existsSync16(source2) && statSync7(source2).isDirectory())) throw invalid("That folder does not exist.");
|
|
10615
10738
|
await ensureAdopted(harness);
|
|
10616
10739
|
const before = new Set((await list2(harness, params)).map((plugin) => plugin.id));
|
|
10617
10740
|
const { settings, packages } = await manager(harness);
|
|
@@ -10641,9 +10764,9 @@ function installed(plugins, pluginId) {
|
|
|
10641
10764
|
async function remove(harness, params) {
|
|
10642
10765
|
const plugin = installed(await list2(harness, params), params.pluginId);
|
|
10643
10766
|
if (plugin.install.kind === "folder") {
|
|
10644
|
-
const trash =
|
|
10767
|
+
const trash = join41(harness.env.root, "trash", "plugins");
|
|
10645
10768
|
mkdirSync15(trash, { recursive: true });
|
|
10646
|
-
renameSync8(plugin.install.source,
|
|
10769
|
+
renameSync8(plugin.install.source, join41(trash, `${basename8(plugin.install.source)}-${now().replace(/[:.]/g, "-")}`));
|
|
10647
10770
|
} else {
|
|
10648
10771
|
await (await manager(harness)).packages.removeAndPersist(plugin.install.source);
|
|
10649
10772
|
}
|
|
@@ -10732,10 +10855,10 @@ async function runPluginsCli(argv, dependencies = {}) {
|
|
|
10732
10855
|
return command ? 0 : 2;
|
|
10733
10856
|
}
|
|
10734
10857
|
const base = dependencies.cwd ?? process.cwd();
|
|
10735
|
-
const root =
|
|
10736
|
-
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" };
|
|
10737
10860
|
const harness = dependencies.createHarness ? dependencies.createHarness(options2) : new Harness(options2);
|
|
10738
|
-
const scope = { cwd: parsed2.values.cwd ?
|
|
10861
|
+
const scope = { cwd: parsed2.values.cwd ? resolve8(base, parsed2.values.cwd) : null };
|
|
10739
10862
|
const need = (what) => {
|
|
10740
10863
|
if (!target) throw new Error(`${command} needs ${what}.`);
|
|
10741
10864
|
return target;
|
|
@@ -10921,7 +11044,7 @@ var RpcPeer = class {
|
|
|
10921
11044
|
/** Asks the host to perform a named operation. */
|
|
10922
11045
|
callHost(call, params, timeoutMs = 3e4) {
|
|
10923
11046
|
const id = `s${++this.nextCallId}`;
|
|
10924
|
-
return new Promise((
|
|
11047
|
+
return new Promise((resolve11, reject) => {
|
|
10925
11048
|
const timer = setTimeout(() => {
|
|
10926
11049
|
this.pendingCalls.delete(id);
|
|
10927
11050
|
reject(new HarnessError("host_timeout", `The host did not answer ${call}.`));
|
|
@@ -10929,7 +11052,7 @@ var RpcPeer = class {
|
|
|
10929
11052
|
this.pendingCalls.set(id, {
|
|
10930
11053
|
resolve: (value) => {
|
|
10931
11054
|
clearTimeout(timer);
|
|
10932
|
-
|
|
11055
|
+
resolve11(value);
|
|
10933
11056
|
},
|
|
10934
11057
|
reject: (error) => {
|
|
10935
11058
|
clearTimeout(timer);
|
|
@@ -10980,7 +11103,7 @@ var RpcPeer = class {
|
|
|
10980
11103
|
};
|
|
10981
11104
|
|
|
10982
11105
|
// src/server/methods.ts
|
|
10983
|
-
import { readFileSync as
|
|
11106
|
+
import { readFileSync as readFileSync21, statSync as statSync8 } from "node:fs";
|
|
10984
11107
|
import { basename as basename9 } from "node:path";
|
|
10985
11108
|
|
|
10986
11109
|
// src/memory/graph.ts
|
|
@@ -11195,7 +11318,7 @@ function methods(harness) {
|
|
|
11195
11318
|
"wiki.ingest": (params) => {
|
|
11196
11319
|
const path3 = text5(params, "path");
|
|
11197
11320
|
if (statSync8(path3).size > MAX_SOURCE_BYTES) throw invalid("Sources larger than 50 MB are not imported.");
|
|
11198
|
-
const source2 = maintenance.ingest(optional2(params, "name") ?? basename9(path3),
|
|
11321
|
+
const source2 = maintenance.ingest(optional2(params, "name") ?? basename9(path3), readFileSync21(path3), optional2(params, "namespace") ?? LIBRARY_NAMESPACE);
|
|
11199
11322
|
harness.bus.emit({ type: "wiki.changed" });
|
|
11200
11323
|
return source2;
|
|
11201
11324
|
},
|
|
@@ -11360,17 +11483,17 @@ function attach3(harness, peer) {
|
|
|
11360
11483
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
11361
11484
|
import { createHash as createHash8, randomUUID as randomUUID8 } from "node:crypto";
|
|
11362
11485
|
import { homedir as homedir6 } from "node:os";
|
|
11363
|
-
import { isAbsolute as isAbsolute6, join as
|
|
11486
|
+
import { isAbsolute as isAbsolute6, join as join43, resolve as resolve9 } from "node:path";
|
|
11364
11487
|
function standaloneArguments(argv) {
|
|
11365
11488
|
const command = argv[0];
|
|
11366
11489
|
if (!command || command.startsWith("--") || command === "-h") return argv;
|
|
11367
11490
|
return command === "terminal" ? argv.slice(1) : null;
|
|
11368
11491
|
}
|
|
11369
11492
|
function parseStandaloneOptions(argv, home = homedir6(), cwd = process.cwd()) {
|
|
11370
|
-
const root =
|
|
11493
|
+
const root = join43(home, ".xingular-harness");
|
|
11371
11494
|
const paths = {
|
|
11372
|
-
dataRoot:
|
|
11373
|
-
stateDir:
|
|
11495
|
+
dataRoot: join43(root, "data"),
|
|
11496
|
+
stateDir: join43(root, "state")
|
|
11374
11497
|
};
|
|
11375
11498
|
const given = /* @__PURE__ */ new Set();
|
|
11376
11499
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -11378,7 +11501,7 @@ function parseStandaloneOptions(argv, home = homedir6(), cwd = process.cwd()) {
|
|
|
11378
11501
|
if (argument !== "--data" && argument !== "--state") throw new Error(`Unknown standalone option: ${argument}`);
|
|
11379
11502
|
const value = argv[index + 1];
|
|
11380
11503
|
if (!value || value.startsWith("--")) throw new Error(`${argument} needs a directory path.`);
|
|
11381
|
-
paths[argument === "--data" ? "dataRoot" : "stateDir"] =
|
|
11504
|
+
paths[argument === "--data" ? "dataRoot" : "stateDir"] = resolve9(cwd, value);
|
|
11382
11505
|
given.add(argument);
|
|
11383
11506
|
index += 1;
|
|
11384
11507
|
}
|
|
@@ -11402,6 +11525,15 @@ function exitCode(code, signal) {
|
|
|
11402
11525
|
if (signal === "SIGHUP") return 129;
|
|
11403
11526
|
return 1;
|
|
11404
11527
|
}
|
|
11528
|
+
function spawnError(error, launch) {
|
|
11529
|
+
const code = error.code;
|
|
11530
|
+
if (code !== "ENOENT" && code !== "EACCES" && code !== "EPERM") return error;
|
|
11531
|
+
if (launch.program === process.execPath || launch.env.XINGULAR_HARNESS_NODE === launch.program) {
|
|
11532
|
+
return new Error(`Could not start the Pi terminal because Node could not run "${launch.args[0] ?? "the bundled Pi CLI"}" (${code}). Verify Node and the bundled Pi package are installed.`, { cause: error });
|
|
11533
|
+
}
|
|
11534
|
+
const problem = code === "ENOENT" ? "was not found" : `could not start (${code})`;
|
|
11535
|
+
return new Error(`Could not start Pi terminal. Pi executable "${launch.program}" ${problem}; verify it is installed and executable or check the configured terminal executable.`, { cause: error });
|
|
11536
|
+
}
|
|
11405
11537
|
function spawnPi(launch, cwd, spawn = nodeSpawn) {
|
|
11406
11538
|
return new Promise((resolveExit, reject) => {
|
|
11407
11539
|
const child = spawn(launch.program, launch.args, {
|
|
@@ -11429,7 +11561,7 @@ function spawnPi(launch, cwd, spawn = nodeSpawn) {
|
|
|
11429
11561
|
process.on("SIGINT", onInterrupt);
|
|
11430
11562
|
process.on("SIGTERM", onTerminate);
|
|
11431
11563
|
process.on("SIGHUP", onHangup);
|
|
11432
|
-
child.once("error", (error) => finish(() => reject(error)));
|
|
11564
|
+
child.once("error", (error) => finish(() => reject(spawnError(error, launch))));
|
|
11433
11565
|
child.once("exit", (code, signal) => finish(() => resolveExit(exitCode(code, signal))));
|
|
11434
11566
|
});
|
|
11435
11567
|
}
|
|
@@ -11437,7 +11569,7 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
|
|
|
11437
11569
|
if (!(dependencies.stdinIsTTY ?? process.stdin.isTTY) || !(dependencies.stdoutIsTTY ?? process.stdout.isTTY)) {
|
|
11438
11570
|
throw new Error("The interactive Harness terminal needs a TTY. Run it from a terminal, or use `xingular-harness serve` for a host process.");
|
|
11439
11571
|
}
|
|
11440
|
-
const cwd =
|
|
11572
|
+
const cwd = resolve9(dependencies.cwd ?? process.cwd());
|
|
11441
11573
|
const paths = parseStandaloneOptions(argv, dependencies.home, cwd);
|
|
11442
11574
|
const makeHarness = dependencies.createHarness ?? createHarness;
|
|
11443
11575
|
const harness = makeHarness({ dataRoot: paths.dataRoot, stateDir: paths.stateDir, productName: "Xingular Harness" });
|
|
@@ -11446,7 +11578,7 @@ async function runStandaloneTerminal(argv, dependencies = {}) {
|
|
|
11446
11578
|
await harness.ready;
|
|
11447
11579
|
if (harness.accounts.overview().accounts.length === 0) harness.accounts.create("Default", "subscription");
|
|
11448
11580
|
const cwdHash = createHash8("sha256").update(cwd).digest("hex");
|
|
11449
|
-
const sessionFolder =
|
|
11581
|
+
const sessionFolder = join43(paths.dataRoot, "sessions", "terminal", cwdHash);
|
|
11450
11582
|
const launch = await harness.terminals.prepare({ sessionId, cwd, sessionFolder });
|
|
11451
11583
|
return await spawnPi(launch, cwd, dependencies.spawn);
|
|
11452
11584
|
} finally {
|
|
@@ -11494,11 +11626,11 @@ function serveCommand(argv) {
|
|
|
11494
11626
|
process.stderr.write("serve needs --state <dir>.\n");
|
|
11495
11627
|
return Promise.resolve(2);
|
|
11496
11628
|
}
|
|
11497
|
-
const stateDir =
|
|
11629
|
+
const stateDir = resolve10(stateArgument);
|
|
11498
11630
|
const peer = new RpcPeer(stdioTransport());
|
|
11499
11631
|
const options2 = {
|
|
11500
11632
|
stateDir,
|
|
11501
|
-
dataRoot: values2.data?.[0] ?
|
|
11633
|
+
dataRoot: values2.data?.[0] ? resolve10(values2.data[0]) : null,
|
|
11502
11634
|
productName: values2.name?.[0],
|
|
11503
11635
|
builtins: values2.builtins,
|
|
11504
11636
|
...switches.has("host-secrets") ? { secrets: remoteSecrets(peer) } : {},
|
|
@@ -11508,12 +11640,12 @@ function serveCommand(argv) {
|
|
|
11508
11640
|
const harness = createHarness(options2);
|
|
11509
11641
|
if (switches.has("host-tools")) harness.sessions.provide(remoteTools(peer));
|
|
11510
11642
|
attach3(harness, peer);
|
|
11511
|
-
return new Promise((
|
|
11643
|
+
return new Promise((resolve11) => {
|
|
11512
11644
|
let closing = false;
|
|
11513
11645
|
const shutdown = () => {
|
|
11514
11646
|
if (closing) return;
|
|
11515
11647
|
closing = true;
|
|
11516
|
-
void Promise.race([harness.close(), new Promise((done) => setTimeout(done, 5e3))]).then(() =>
|
|
11648
|
+
void Promise.race([harness.close(), new Promise((done) => setTimeout(done, 5e3))]).then(() => resolve11(0));
|
|
11517
11649
|
};
|
|
11518
11650
|
peer.handle("harness.shutdown", () => void setImmediate(shutdown));
|
|
11519
11651
|
process.stdin.on("end", shutdown);
|