@moxt-ai/mobius 0.0.28 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-catalog/acp-readiness-probe.d.ts.map +1 -1
- package/dist/agent-catalog/acp-readiness-probe.js +2 -2
- package/dist/agent-catalog/acp-readiness-probe.js.map +1 -1
- package/dist/agent-catalog/agent-specification.d.ts +3 -0
- package/dist/agent-catalog/agent-specification.d.ts.map +1 -1
- package/dist/agent-catalog/agent-specification.js +3 -0
- package/dist/agent-catalog/agent-specification.js.map +1 -1
- package/dist/agent-catalog/agents/copilot-cli-agent.d.ts +18 -0
- package/dist/agent-catalog/agents/copilot-cli-agent.d.ts.map +1 -0
- package/dist/agent-catalog/agents/copilot-cli-agent.js +29 -0
- package/dist/agent-catalog/agents/copilot-cli-agent.js.map +1 -0
- package/dist/agent-catalog/agents/gemini-cli-agent.d.ts +14 -0
- package/dist/agent-catalog/agents/gemini-cli-agent.d.ts.map +1 -0
- package/dist/agent-catalog/agents/gemini-cli-agent.js +30 -0
- package/dist/agent-catalog/agents/gemini-cli-agent.js.map +1 -0
- package/dist/agent-catalog/agents/hermes-agent.d.ts +18 -0
- package/dist/agent-catalog/agents/hermes-agent.d.ts.map +1 -0
- package/dist/agent-catalog/agents/hermes-agent.js +32 -0
- package/dist/agent-catalog/agents/hermes-agent.js.map +1 -0
- package/dist/agent-catalog/agents/resolve-user-local-executable.d.ts +4 -0
- package/dist/agent-catalog/agents/resolve-user-local-executable.d.ts.map +1 -0
- package/dist/agent-catalog/agents/resolve-user-local-executable.js +14 -0
- package/dist/agent-catalog/agents/resolve-user-local-executable.js.map +1 -0
- package/dist/agent-catalog/agents/supported-agent-specifications.d.ts.map +1 -1
- package/dist/agent-catalog/agents/supported-agent-specifications.js +6 -0
- package/dist/agent-catalog/agents/supported-agent-specifications.js.map +1 -1
- package/dist/agent-catalog/discover-agent.d.ts.map +1 -1
- package/dist/agent-catalog/discover-agent.js +2 -2
- package/dist/agent-catalog/discover-agent.js.map +1 -1
- package/dist/agent-catalog/discover-local-agents.d.ts +3 -0
- package/dist/agent-catalog/discover-local-agents.d.ts.map +1 -1
- package/dist/agent-catalog/discover-local-agents.js +3 -0
- package/dist/agent-catalog/discover-local-agents.js.map +1 -1
- package/dist/agent-catalog/executable-discovery.d.ts +1 -2
- package/dist/agent-catalog/executable-discovery.d.ts.map +1 -1
- package/dist/agent-catalog/executable-discovery.js +4 -6
- package/dist/agent-catalog/executable-discovery.js.map +1 -1
- package/dist/agent-catalog/version-output.d.ts +2 -0
- package/dist/agent-catalog/version-output.d.ts.map +1 -0
- package/dist/agent-catalog/version-output.js +4 -0
- package/dist/agent-catalog/version-output.js.map +1 -0
- package/dist/bots/bot-approve-command.d.ts +2 -0
- package/dist/bots/bot-approve-command.d.ts.map +1 -0
- package/dist/bots/bot-approve-command.js +78 -0
- package/dist/bots/bot-approve-command.js.map +1 -0
- package/dist/bots/local-bot-grant-store.d.ts +7 -0
- package/dist/bots/local-bot-grant-store.d.ts.map +1 -1
- package/dist/bots/local-bot-grant-store.js +89 -0
- package/dist/bots/local-bot-grant-store.js.map +1 -1
- package/dist/cli.js +1300 -941
- package/dist/cli.js.map +4 -4
- package/dist/index.js +476 -236
- package/dist/index.js.map +4 -4
- package/dist/run-daemon.d.ts.map +1 -1
- package/dist/run-daemon.js +18 -14
- package/dist/run-daemon.js.map +1 -1
- package/dist/runtime.js +488 -246
- package/dist/runtime.js.map +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { mkdir as mkdir5 } from "node:fs/promises";
|
|
|
11
11
|
// package.json
|
|
12
12
|
var package_default = {
|
|
13
13
|
name: "@moxt-ai/mobius",
|
|
14
|
-
version: "0.0.
|
|
14
|
+
version: "0.0.30",
|
|
15
15
|
description: "Mobius CLI for pairing and running local AI agent services.",
|
|
16
16
|
type: "module",
|
|
17
17
|
bin: {
|
|
@@ -1158,6 +1158,13 @@ var ManagedNpmPackageInstaller = class {
|
|
|
1158
1158
|
|
|
1159
1159
|
// src/agent-catalog/agent-specification.ts
|
|
1160
1160
|
import { join as join3 } from "node:path";
|
|
1161
|
+
|
|
1162
|
+
// src/agent-catalog/version-output.ts
|
|
1163
|
+
function normalizeVersionOutput(output2) {
|
|
1164
|
+
return output2.trim().split(/\r?\n/, 1)[0]?.trim().slice(0, 128) ?? "";
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// src/agent-catalog/agent-specification.ts
|
|
1161
1168
|
var VERSION_PROBE_TIMEOUT_MILLISECONDS = 3e3;
|
|
1162
1169
|
var ACP_PROBE_TIMEOUT_MILLISECONDS = 5e3;
|
|
1163
1170
|
var DEFAULT_PROMPT_TIMEOUT_MILLISECONDS = 30 * 60 * 1e3;
|
|
@@ -1166,6 +1173,8 @@ var LocalAgentSpecification = class {
|
|
|
1166
1173
|
authenticationCommand = () => "";
|
|
1167
1174
|
adapterVersionArguments = ["--version"];
|
|
1168
1175
|
agentVersionArguments = ["--version"];
|
|
1176
|
+
normalizeAdapterVersion = normalizeVersionOutput;
|
|
1177
|
+
normalizeAgentVersion = normalizeVersionOutput;
|
|
1169
1178
|
promptTimeoutMilliseconds = DEFAULT_PROMPT_TIMEOUT_MILLISECONDS;
|
|
1170
1179
|
versionProbeTimeoutMilliseconds = VERSION_PROBE_TIMEOUT_MILLISECONDS;
|
|
1171
1180
|
resolveManagedAgent = async (stateDirectory, platform, signal) => {
|
|
@@ -1277,6 +1286,20 @@ var AgentProbeError = class extends Error {
|
|
|
1277
1286
|
}
|
|
1278
1287
|
};
|
|
1279
1288
|
|
|
1289
|
+
// src/agent-catalog/agents/resolve-user-local-executable.ts
|
|
1290
|
+
import { isAbsolute as isAbsolute2, join as join4 } from "node:path";
|
|
1291
|
+
async function resolveUserLocalExecutable(executableName, context) {
|
|
1292
|
+
context.signal.throwIfAborted();
|
|
1293
|
+
const platform = context.platform ?? process.platform;
|
|
1294
|
+
const homeDirectory = context.environment["HOME"];
|
|
1295
|
+
if (platform === "win32" || homeDirectory === void 0 || !isAbsolute2(homeDirectory)) {
|
|
1296
|
+
return [];
|
|
1297
|
+
}
|
|
1298
|
+
const executable = await resolveExecutablePath(join4(homeDirectory, ".local", "bin", executableName), platform);
|
|
1299
|
+
context.signal.throwIfAborted();
|
|
1300
|
+
return executable === null ? [] : [executable];
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1280
1303
|
// src/agent-catalog/agents/verify-session-creation.ts
|
|
1281
1304
|
import { methods } from "@agentclientprotocol/sdk";
|
|
1282
1305
|
async function verifySessionCreation(connection, capabilities, defaultWorkspace, signal, label) {
|
|
@@ -1301,6 +1324,37 @@ async function verifySessionCreation(connection, capabilities, defaultWorkspace,
|
|
|
1301
1324
|
}
|
|
1302
1325
|
}
|
|
1303
1326
|
|
|
1327
|
+
// src/agent-catalog/agents/copilot-cli-agent.ts
|
|
1328
|
+
var COPILOT_CLI_TESTED_VERSION = "1.0.83";
|
|
1329
|
+
var CopilotCliAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
1330
|
+
acpProbeTimeoutMilliseconds = 2e4;
|
|
1331
|
+
adapterVersion = COPILOT_CLI_TESTED_VERSION;
|
|
1332
|
+
authenticationCommand = () => "copilot login";
|
|
1333
|
+
agentExecutable = "copilot";
|
|
1334
|
+
id = "github-copilot-cli";
|
|
1335
|
+
label = "GitHub Copilot CLI";
|
|
1336
|
+
versionProbeTimeoutMilliseconds = 1e4;
|
|
1337
|
+
constructor() {
|
|
1338
|
+
super(
|
|
1339
|
+
"MOBIUS_COPILOT_PATH",
|
|
1340
|
+
["--no-auto-update", "--acp", "--stdio"],
|
|
1341
|
+
["agents", "github-copilot-cli", COPILOT_CLI_TESTED_VERSION, "bin", "copilot"]
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
createLaunchEnvironment = async (_agent, _context) => Object.freeze({ COPILOT_AUTO_UPDATE: "false" });
|
|
1345
|
+
resolveInstalledAgentExecutables = async (context) => await resolveUserLocalExecutable(this.agentExecutable, context);
|
|
1346
|
+
verifyAcp = async (connection, initialized, defaultWorkspace, signal) => {
|
|
1347
|
+
const capabilities = initialized.agentCapabilities ?? {};
|
|
1348
|
+
if (capabilities.loadSession !== true || capabilities.sessionCapabilities?.list == null || (initialized.authMethods ?? []).length === 0) {
|
|
1349
|
+
throw new AgentProbeError(
|
|
1350
|
+
"acp_incompatible",
|
|
1351
|
+
"GitHub Copilot CLI reported an unsupported ACP capability set"
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
await verifySessionCreation(connection, capabilities, defaultWorkspace, signal, this.label);
|
|
1355
|
+
};
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1304
1358
|
// src/agent-catalog/agents/cursor-cli-agent.ts
|
|
1305
1359
|
var CURSOR_CLI_TESTED_VERSION = "2026.09.02-c22c1a3";
|
|
1306
1360
|
var CursorCliAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
@@ -1327,7 +1381,7 @@ var CursorCliAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
1327
1381
|
|
|
1328
1382
|
// src/agent-catalog/agents/deepseek-harness-agent.ts
|
|
1329
1383
|
import { realpath as realpath3, stat as stat3 } from "node:fs/promises";
|
|
1330
|
-
import { isAbsolute as
|
|
1384
|
+
import { isAbsolute as isAbsolute3, join as join6, normalize } from "node:path";
|
|
1331
1385
|
|
|
1332
1386
|
// src/agent-installation/deepseek-managed-package.ts
|
|
1333
1387
|
var DeepSeekHarnessManagedPackage = class extends SriManagedNpmPackageDefinition {
|
|
@@ -1345,18 +1399,18 @@ var DEEPSEEK_HARNESS_PACKAGE = new DeepSeekHarnessManagedPackage(
|
|
|
1345
1399
|
|
|
1346
1400
|
// src/storage/state-directory.ts
|
|
1347
1401
|
import { homedir } from "node:os";
|
|
1348
|
-
import { join as
|
|
1402
|
+
import { join as join5, resolve as resolve2 } from "node:path";
|
|
1349
1403
|
function readStateDirectory(environment = process.env) {
|
|
1350
1404
|
const configured = environment["MOBIUS_STATE_DIRECTORY"]?.trim();
|
|
1351
1405
|
if (configured !== void 0 && configured.length > 0) {
|
|
1352
1406
|
return resolve2(configured);
|
|
1353
1407
|
}
|
|
1354
|
-
return
|
|
1408
|
+
return join5(homedir(), ".mobius");
|
|
1355
1409
|
}
|
|
1356
1410
|
|
|
1357
1411
|
// src/agent-catalog/agents/deepseek-harness-agent.ts
|
|
1358
1412
|
async function validateAgentHome(path2) {
|
|
1359
|
-
if (!
|
|
1413
|
+
if (!isAbsolute3(path2) || normalize(path2) !== path2) {
|
|
1360
1414
|
throw new AgentProbeError("agent_environment_invalid", "The configured Agent home is invalid");
|
|
1361
1415
|
}
|
|
1362
1416
|
try {
|
|
@@ -1406,7 +1460,7 @@ var DeepSeekHarnessAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
1406
1460
|
context.signal
|
|
1407
1461
|
);
|
|
1408
1462
|
if (managed.path === agent.path) {
|
|
1409
|
-
selectedHome =
|
|
1463
|
+
selectedHome = join6(readStateDirectory(context.environment), "agents", this.id, "home");
|
|
1410
1464
|
}
|
|
1411
1465
|
} catch {
|
|
1412
1466
|
context.signal.throwIfAborted();
|
|
@@ -1422,7 +1476,7 @@ var DeepSeekHarnessAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
1422
1476
|
resolveManagedAgent = async (stateDirectory, _platform, signal) => {
|
|
1423
1477
|
signal.throwIfAborted();
|
|
1424
1478
|
const executable = await validateManagedNpmPackage(
|
|
1425
|
-
|
|
1479
|
+
join6(stateDirectory, "agents", this.id, DEEPSEEK_HARNESS_PACKAGE.version),
|
|
1426
1480
|
DEEPSEEK_HARNESS_PACKAGE
|
|
1427
1481
|
);
|
|
1428
1482
|
signal.throwIfAborted();
|
|
@@ -1442,9 +1496,41 @@ var DeepSeekHarnessAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
1442
1496
|
};
|
|
1443
1497
|
};
|
|
1444
1498
|
|
|
1499
|
+
// src/agent-catalog/agents/gemini-cli-agent.ts
|
|
1500
|
+
var GEMINI_CLI_TESTED_VERSION = "0.59.0";
|
|
1501
|
+
var GeminiCliAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
1502
|
+
acpProbeTimeoutMilliseconds = 2e4;
|
|
1503
|
+
adapterVersion = GEMINI_CLI_TESTED_VERSION;
|
|
1504
|
+
authenticationCommand = () => "gemini";
|
|
1505
|
+
agentExecutable = "gemini";
|
|
1506
|
+
id = "gemini-cli";
|
|
1507
|
+
label = "Gemini CLI";
|
|
1508
|
+
versionProbeTimeoutMilliseconds = 1e4;
|
|
1509
|
+
constructor() {
|
|
1510
|
+
super(
|
|
1511
|
+
"MOBIUS_GEMINI_PATH",
|
|
1512
|
+
["--acp"],
|
|
1513
|
+
[
|
|
1514
|
+
"agents",
|
|
1515
|
+
"gemini-cli",
|
|
1516
|
+
GEMINI_CLI_TESTED_VERSION,
|
|
1517
|
+
"bin",
|
|
1518
|
+
process.platform === "win32" ? "gemini.cmd" : "gemini"
|
|
1519
|
+
]
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
verifyAcp = async (connection, initialized, defaultWorkspace, signal) => {
|
|
1523
|
+
const capabilities = initialized.agentCapabilities ?? {};
|
|
1524
|
+
if (capabilities.loadSession !== true || (initialized.authMethods ?? []).length === 0) {
|
|
1525
|
+
throw new AgentProbeError("acp_incompatible", "Gemini CLI reported an unsupported ACP capability set");
|
|
1526
|
+
}
|
|
1527
|
+
await verifySessionCreation(connection, capabilities, defaultWorkspace, signal, this.label);
|
|
1528
|
+
};
|
|
1529
|
+
};
|
|
1530
|
+
|
|
1445
1531
|
// src/agent-catalog/agents/grok-build-agent.ts
|
|
1446
1532
|
import { realpath as realpath4 } from "node:fs/promises";
|
|
1447
|
-
import { isAbsolute as
|
|
1533
|
+
import { isAbsolute as isAbsolute4, join as join7, relative as relative2, sep as sep2 } from "node:path";
|
|
1448
1534
|
|
|
1449
1535
|
// src/agent-installation/grok-managed-package.ts
|
|
1450
1536
|
var GROK_BUILD_MANAGED_VERSION = "1.0.21";
|
|
@@ -1507,7 +1593,7 @@ function grokBuildManagedPackage(platform, architecture) {
|
|
|
1507
1593
|
// src/agent-catalog/agents/grok-build-agent.ts
|
|
1508
1594
|
function isContainedPath2(root, candidate) {
|
|
1509
1595
|
const difference = relative2(root, candidate);
|
|
1510
|
-
return difference.length > 0 && difference !== ".." && !difference.startsWith(`..${sep2}`) && !
|
|
1596
|
+
return difference.length > 0 && difference !== ".." && !difference.startsWith(`..${sep2}`) && !isAbsolute4(difference);
|
|
1511
1597
|
}
|
|
1512
1598
|
var GrokBuildAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
1513
1599
|
adapterVersion = GROK_BUILD_MANAGED_VERSION;
|
|
@@ -1535,7 +1621,7 @@ var GrokBuildAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
1535
1621
|
if (managed.path === agent.path) {
|
|
1536
1622
|
return Object.freeze({
|
|
1537
1623
|
GROK_HOME: await realpath4(
|
|
1538
|
-
|
|
1624
|
+
join7(stateDirectory, "agents", this.id, GROK_BUILD_MANAGED_VERSION, "home")
|
|
1539
1625
|
)
|
|
1540
1626
|
});
|
|
1541
1627
|
}
|
|
@@ -1546,9 +1632,9 @@ var GrokBuildAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
1546
1632
|
};
|
|
1547
1633
|
resolveManagedAgent = async (stateDirectory, platform, signal) => {
|
|
1548
1634
|
signal.throwIfAborted();
|
|
1549
|
-
const versionRoot = await realpath4(
|
|
1550
|
-
await validateManagedNpmPackage(
|
|
1551
|
-
const executable = await resolveExecutablePath(
|
|
1635
|
+
const versionRoot = await realpath4(join7(stateDirectory, "agents", this.id, GROK_BUILD_MANAGED_VERSION));
|
|
1636
|
+
await validateManagedNpmPackage(join7(versionRoot, "package"), grokBuildManagedPackage(platform, process.arch));
|
|
1637
|
+
const executable = await resolveExecutablePath(join7(versionRoot, "home", "bin", "grok"), platform);
|
|
1552
1638
|
signal.throwIfAborted();
|
|
1553
1639
|
if (executable === null || !isContainedPath2(versionRoot, executable.path)) {
|
|
1554
1640
|
throw new AgentProbeError("managed_agent_invalid", "The managed Grok Build executable is invalid");
|
|
@@ -1565,14 +1651,39 @@ var GrokBuildAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
1565
1651
|
};
|
|
1566
1652
|
};
|
|
1567
1653
|
|
|
1654
|
+
// src/agent-catalog/agents/hermes-agent.ts
|
|
1655
|
+
var HERMES_TESTED_VERSION = "0.21.1";
|
|
1656
|
+
var HermesAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
1657
|
+
acpProbeTimeoutMilliseconds = 2e4;
|
|
1658
|
+
adapterVersion = HERMES_TESTED_VERSION;
|
|
1659
|
+
adapterVersionArguments = ["acp", "--version"];
|
|
1660
|
+
authenticationCommand = () => "hermes acp --setup";
|
|
1661
|
+
agentExecutable = "hermes";
|
|
1662
|
+
agentVersionArguments = ["acp", "--version"];
|
|
1663
|
+
id = "hermes";
|
|
1664
|
+
label = "Hermes Agent";
|
|
1665
|
+
constructor() {
|
|
1666
|
+
super("MOBIUS_HERMES_PATH", ["acp"], ["agents", "hermes", HERMES_TESTED_VERSION, "bin", "hermes"]);
|
|
1667
|
+
}
|
|
1668
|
+
resolveInstalledAgentExecutables = async (context) => await resolveUserLocalExecutable(this.agentExecutable, context);
|
|
1669
|
+
verifyAcp = async (connection, initialized, defaultWorkspace, signal) => {
|
|
1670
|
+
const capabilities = initialized.agentCapabilities ?? {};
|
|
1671
|
+
const session = capabilities.sessionCapabilities;
|
|
1672
|
+
if (capabilities.loadSession !== true || session?.fork == null || session.list == null || session.resume == null || (initialized.authMethods ?? []).length === 0) {
|
|
1673
|
+
throw new AgentProbeError("acp_incompatible", "Hermes Agent reported an unsupported ACP capability set");
|
|
1674
|
+
}
|
|
1675
|
+
await verifySessionCreation(connection, capabilities, defaultWorkspace, signal, this.label);
|
|
1676
|
+
};
|
|
1677
|
+
};
|
|
1678
|
+
|
|
1568
1679
|
// src/agent-catalog/agents/opencode-agent.ts
|
|
1569
|
-
import { join as
|
|
1680
|
+
import { join as join9 } from "node:path";
|
|
1570
1681
|
|
|
1571
1682
|
// src/agent-installation/managed-agent-archive.ts
|
|
1572
1683
|
import { createHash } from "node:crypto";
|
|
1573
1684
|
import { createReadStream } from "node:fs";
|
|
1574
1685
|
import { chmod, copyFile, mkdir as mkdir2, mkdtemp as mkdtemp2, open as open2, realpath as realpath5, rename as rename2, rm as rm2, stat as stat4, writeFile } from "node:fs/promises";
|
|
1575
|
-
import { dirname as dirname2, isAbsolute as
|
|
1686
|
+
import { dirname as dirname2, isAbsolute as isAbsolute5, join as join8, relative as relative3, sep as sep3 } from "node:path";
|
|
1576
1687
|
|
|
1577
1688
|
// src/agent-installation/verified-download.ts
|
|
1578
1689
|
import { fetch } from "undici";
|
|
@@ -1678,7 +1789,7 @@ function isRecord3(value) {
|
|
|
1678
1789
|
}
|
|
1679
1790
|
function isContainedPath3(root, candidate) {
|
|
1680
1791
|
const difference = relative3(root, candidate);
|
|
1681
|
-
return difference.length > 0 && difference !== ".." && !difference.startsWith(`..${sep3}`) && !
|
|
1792
|
+
return difference.length > 0 && difference !== ".." && !difference.startsWith(`..${sep3}`) && !isAbsolute5(difference);
|
|
1682
1793
|
}
|
|
1683
1794
|
async function hashFile(path2, signal) {
|
|
1684
1795
|
signal.throwIfAborted();
|
|
@@ -1757,11 +1868,11 @@ async function validateManagedAgentArchive(root, definition, signal) {
|
|
|
1757
1868
|
if (!rootMetadata.isDirectory() || typeof process.getuid === "function" && rootMetadata.uid !== process.getuid()) {
|
|
1758
1869
|
throw new Error("Managed Agent root is not user-owned");
|
|
1759
1870
|
}
|
|
1760
|
-
const manifest = await readManifest(
|
|
1871
|
+
const manifest = await readManifest(join8(rootPath, "archive.json"));
|
|
1761
1872
|
if (manifest["archiveSha256"] !== definition.archiveSha256 || manifest["archiveUrl"] !== definition.archiveUrl.toString() || manifest["version"] !== definition.version || typeof manifest["executableSha256"] !== "string") {
|
|
1762
1873
|
throw new Error("Managed Agent archive identity is invalid");
|
|
1763
1874
|
}
|
|
1764
|
-
const executable = await resolveExecutablePath(
|
|
1875
|
+
const executable = await resolveExecutablePath(join8(rootPath, "bin", definition.executableName));
|
|
1765
1876
|
if (executable === null || !isContainedPath3(rootPath, executable.path) || await hashFile(executable.path, signal) !== manifest["executableSha256"]) {
|
|
1766
1877
|
throw new Error("Managed Agent executable is invalid");
|
|
1767
1878
|
}
|
|
@@ -1853,7 +1964,7 @@ var OpenCodeAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
1853
1964
|
}
|
|
1854
1965
|
createLaunchEnvironment = async (_agent, _context) => Object.freeze({ OPENCODE_DISABLE_AUTOUPDATE: "1" });
|
|
1855
1966
|
resolveManagedAgent = async (stateDirectory, platform, signal) => await validateManagedAgentArchive(
|
|
1856
|
-
|
|
1967
|
+
join9(stateDirectory, "agents", this.id, OPENCODE_MANAGED_VERSION),
|
|
1857
1968
|
openCodeManagedArchive(platform, process.arch),
|
|
1858
1969
|
signal
|
|
1859
1970
|
);
|
|
@@ -1871,7 +1982,7 @@ var OpenCodeAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
1871
1982
|
};
|
|
1872
1983
|
|
|
1873
1984
|
// src/agent-catalog/agents/pi-agent.ts
|
|
1874
|
-
import { join as
|
|
1985
|
+
import { join as join10 } from "node:path";
|
|
1875
1986
|
import {
|
|
1876
1987
|
methods as methods2
|
|
1877
1988
|
} from "@agentclientprotocol/sdk";
|
|
@@ -1930,7 +2041,7 @@ var PiAgentSpecification = class extends BridgedAcpAgentDefinition {
|
|
|
1930
2041
|
resolveManagedAgent = async (stateDirectory, _platform, signal) => {
|
|
1931
2042
|
signal.throwIfAborted();
|
|
1932
2043
|
const executable = await validateManagedNpmPackage(
|
|
1933
|
-
|
|
2044
|
+
join10(stateDirectory, "agents", this.id, PI_AGENT_PACKAGE.version),
|
|
1934
2045
|
PI_AGENT_PACKAGE
|
|
1935
2046
|
);
|
|
1936
2047
|
signal.throwIfAborted();
|
|
@@ -1968,7 +2079,7 @@ var PiAgentSpecification = class extends BridgedAcpAgentDefinition {
|
|
|
1968
2079
|
};
|
|
1969
2080
|
|
|
1970
2081
|
// src/agent-catalog/agents/workbuddy-agent.ts
|
|
1971
|
-
import { isAbsolute as
|
|
2082
|
+
import { isAbsolute as isAbsolute6, join as join11 } from "node:path";
|
|
1972
2083
|
|
|
1973
2084
|
// src/agent-installation/workbuddy-managed-package.ts
|
|
1974
2085
|
var WORKBUDDY_MANAGED_VERSION = "2.146.0";
|
|
@@ -2021,7 +2132,7 @@ var WorkBuddyAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
2021
2132
|
resolveManagedAgent = async (stateDirectory, _platform, signal) => {
|
|
2022
2133
|
signal.throwIfAborted();
|
|
2023
2134
|
const executable = await validateManagedNpmPackage(
|
|
2024
|
-
|
|
2135
|
+
join11(stateDirectory, "agents", this.id, WORKBUDDY_MANAGED_VERSION),
|
|
2025
2136
|
WORKBUDDY_MANAGED_PACKAGE
|
|
2026
2137
|
);
|
|
2027
2138
|
signal.throwIfAborted();
|
|
@@ -2032,10 +2143,10 @@ var WorkBuddyAgentSpecification = class extends DirectAcpAgentDefinition {
|
|
|
2032
2143
|
if ((context.platform ?? process.platform) !== "darwin") {
|
|
2033
2144
|
return [];
|
|
2034
2145
|
}
|
|
2035
|
-
const requestedPaths = [
|
|
2146
|
+
const requestedPaths = [join11("/Applications", ...WORKBUDDY_MACOS_EXECUTABLE_PATH)];
|
|
2036
2147
|
const homeDirectory = context.environment["HOME"];
|
|
2037
|
-
if (homeDirectory !== void 0 &&
|
|
2038
|
-
requestedPaths.unshift(
|
|
2148
|
+
if (homeDirectory !== void 0 && isAbsolute6(homeDirectory)) {
|
|
2149
|
+
requestedPaths.unshift(join11(homeDirectory, "Applications", ...WORKBUDDY_MACOS_EXECUTABLE_PATH));
|
|
2039
2150
|
}
|
|
2040
2151
|
const executables = [];
|
|
2041
2152
|
for (const requestedPath of requestedPaths) {
|
|
@@ -2061,12 +2172,15 @@ function supportedLocalAgentSpecifications() {
|
|
|
2061
2172
|
return Object.freeze([
|
|
2062
2173
|
new CodexAgentSpecification(),
|
|
2063
2174
|
new ClaudeCodeAgentSpecification(),
|
|
2175
|
+
new CopilotCliAgentSpecification(),
|
|
2064
2176
|
new PiAgentSpecification(),
|
|
2065
2177
|
new DeepSeekHarnessAgentSpecification(),
|
|
2178
|
+
new HermesAgentSpecification(),
|
|
2066
2179
|
new OpenCodeAgentSpecification(),
|
|
2067
2180
|
new CursorCliAgentSpecification(),
|
|
2068
2181
|
new GrokBuildAgentSpecification(),
|
|
2069
|
-
new WorkBuddyAgentSpecification()
|
|
2182
|
+
new WorkBuddyAgentSpecification(),
|
|
2183
|
+
new GeminiCliAgentSpecification()
|
|
2070
2184
|
]);
|
|
2071
2185
|
}
|
|
2072
2186
|
|
|
@@ -2106,11 +2220,173 @@ var createAcpClient = (handler) => client({ name: "mobius" }).onRequest(
|
|
|
2106
2220
|
outcome: { outcome: "cancelled" }
|
|
2107
2221
|
})).onNotification("cursor/update_todos", discardExtensionParams, async () => Promise.resolve()).onNotification("cursor/task", discardExtensionParams, async () => Promise.resolve()).onNotification("cursor/generate_image", discardExtensionParams, async () => Promise.resolve());
|
|
2108
2222
|
|
|
2223
|
+
// src/agent-catalog/acp-readiness-probe.ts
|
|
2224
|
+
var ACP_OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
2225
|
+
var VerifiedAcpReadiness = class {
|
|
2226
|
+
adapterVersion;
|
|
2227
|
+
capabilities;
|
|
2228
|
+
constructor(adapterVersion, capabilities) {
|
|
2229
|
+
this.adapterVersion = adapterVersion;
|
|
2230
|
+
this.capabilities = capabilities;
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
var DiscoveryAcpHandler = class {
|
|
2234
|
+
onElicitation = (_request, _signal) => Promise.reject(new Error("ACP probe does not accept elicitations"));
|
|
2235
|
+
onElicitationComplete = (_notification) => Promise.resolve();
|
|
2236
|
+
onPermission = (_request, _signal) => Promise.reject(new Error("ACP probe does not grant permissions"));
|
|
2237
|
+
onUpdate = (_notification) => Promise.resolve();
|
|
2238
|
+
};
|
|
2239
|
+
function readableAcpStream(input2) {
|
|
2240
|
+
let byteLength = 0;
|
|
2241
|
+
let open5 = true;
|
|
2242
|
+
return new ReadableStream({
|
|
2243
|
+
start(controller) {
|
|
2244
|
+
input2.on("data", (chunk) => {
|
|
2245
|
+
if (!open5) {
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
if (!(chunk instanceof Uint8Array)) {
|
|
2249
|
+
open5 = false;
|
|
2250
|
+
controller.error(new Error("ACP adapter produced invalid output"));
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
byteLength += chunk.byteLength;
|
|
2254
|
+
if (byteLength > ACP_OUTPUT_LIMIT_BYTES) {
|
|
2255
|
+
open5 = false;
|
|
2256
|
+
controller.error(new Error("ACP adapter output limit exceeded"));
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
controller.enqueue(new Uint8Array(chunk));
|
|
2260
|
+
});
|
|
2261
|
+
input2.once("end", () => {
|
|
2262
|
+
if (open5) {
|
|
2263
|
+
open5 = false;
|
|
2264
|
+
controller.close();
|
|
2265
|
+
}
|
|
2266
|
+
});
|
|
2267
|
+
input2.once("error", (error61) => {
|
|
2268
|
+
if (open5) {
|
|
2269
|
+
open5 = false;
|
|
2270
|
+
controller.error(error61);
|
|
2271
|
+
}
|
|
2272
|
+
});
|
|
2273
|
+
},
|
|
2274
|
+
cancel() {
|
|
2275
|
+
open5 = false;
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
function writableAcpStream(output2) {
|
|
2280
|
+
return new WritableStream({
|
|
2281
|
+
write(chunk) {
|
|
2282
|
+
return new Promise((resolveWrite, rejectWrite) => {
|
|
2283
|
+
output2.write(chunk, (error61) => {
|
|
2284
|
+
if (error61) {
|
|
2285
|
+
rejectWrite(error61);
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
resolveWrite();
|
|
2289
|
+
});
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
async function probeAcpReadiness(specification, agent, environment, defaultWorkspace, signal) {
|
|
2295
|
+
signal.throwIfAborted();
|
|
2296
|
+
const controller = new AbortController();
|
|
2297
|
+
let timedOut = false;
|
|
2298
|
+
const timeout = setTimeout(() => {
|
|
2299
|
+
timedOut = true;
|
|
2300
|
+
controller.abort(new Error("ACP probe timed out"));
|
|
2301
|
+
}, specification.acpProbeTimeoutMilliseconds);
|
|
2302
|
+
const abort = () => controller.abort(signal.reason);
|
|
2303
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
2304
|
+
if (signal.aborted) {
|
|
2305
|
+
abort();
|
|
2306
|
+
}
|
|
2307
|
+
const child = spawn2(agent.adapterPath, [...agent.arguments], {
|
|
2308
|
+
cwd: defaultWorkspace,
|
|
2309
|
+
detached: process.platform !== "win32",
|
|
2310
|
+
env: { ...environment, ...agent.environment },
|
|
2311
|
+
shell: false,
|
|
2312
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2313
|
+
});
|
|
2314
|
+
const processExit = new Promise((_resolve, reject) => {
|
|
2315
|
+
const rejectExit = () => reject(new Error("ACP adapter exited during initialization"));
|
|
2316
|
+
child.once("error", reject);
|
|
2317
|
+
child.once("exit", rejectExit);
|
|
2318
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
2319
|
+
rejectExit();
|
|
2320
|
+
}
|
|
2321
|
+
});
|
|
2322
|
+
const stdin = child.stdin;
|
|
2323
|
+
const stdout = child.stdout;
|
|
2324
|
+
const stderr = child.stderr;
|
|
2325
|
+
if (stdin === null || stdout === null || stderr === null) {
|
|
2326
|
+
await stopProcessTree(child);
|
|
2327
|
+
throw new AgentProbeError("acp_incompatible", "ACP adapter streams are unavailable");
|
|
2328
|
+
}
|
|
2329
|
+
let stderrBytes = 0;
|
|
2330
|
+
const consumeStderr = (chunk) => {
|
|
2331
|
+
stderrBytes += chunk.byteLength;
|
|
2332
|
+
if (stderrBytes > ACP_OUTPUT_LIMIT_BYTES) {
|
|
2333
|
+
controller.abort(new Error("ACP adapter output limit exceeded"));
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2336
|
+
stderr.on("data", consumeStderr);
|
|
2337
|
+
const connection = createAcpClient(new DiscoveryAcpHandler()).connect(
|
|
2338
|
+
ndJsonStream(writableAcpStream(stdin), readableAcpStream(stdout))
|
|
2339
|
+
);
|
|
2340
|
+
try {
|
|
2341
|
+
const initialized = await Promise.race([
|
|
2342
|
+
connection.agent.request(
|
|
2343
|
+
methods4.agent.initialize,
|
|
2344
|
+
{
|
|
2345
|
+
clientCapabilities: {},
|
|
2346
|
+
clientInfo: { name: "mobius", version: "0.0.3" },
|
|
2347
|
+
protocolVersion: PROTOCOL_VERSION
|
|
2348
|
+
},
|
|
2349
|
+
{ cancellationSignal: controller.signal }
|
|
2350
|
+
),
|
|
2351
|
+
processExit
|
|
2352
|
+
]);
|
|
2353
|
+
if (initialized.protocolVersion !== PROTOCOL_VERSION) {
|
|
2354
|
+
throw new AgentProbeError("acp_incompatible", "ACP adapter selected an unsupported protocol version");
|
|
2355
|
+
}
|
|
2356
|
+
await specification.verifyAcp(connection, initialized, defaultWorkspace, controller.signal);
|
|
2357
|
+
return new VerifiedAcpReadiness(
|
|
2358
|
+
normalizeVersionOutput(initialized.agentInfo?.version ?? ""),
|
|
2359
|
+
normalizeAcpCapabilities(initialized)
|
|
2360
|
+
);
|
|
2361
|
+
} catch (error61) {
|
|
2362
|
+
if (signal.aborted) {
|
|
2363
|
+
signal.throwIfAborted();
|
|
2364
|
+
}
|
|
2365
|
+
if (timedOut) {
|
|
2366
|
+
throw new AgentProbeError("adapter_probe_timeout", "ACP adapter probe timed out", { cause: error61 });
|
|
2367
|
+
}
|
|
2368
|
+
if (error61 instanceof AgentProbeError) {
|
|
2369
|
+
throw error61;
|
|
2370
|
+
}
|
|
2371
|
+
if (error61 instanceof RequestError && error61.code === -32e3 && specification.authenticationCommand().length > 0) {
|
|
2372
|
+
throw new AgentProbeError("authentication_required", "Agent authentication is required", { cause: error61 });
|
|
2373
|
+
}
|
|
2374
|
+
throw new AgentProbeError("acp_incompatible", "ACP adapter initialization failed", { cause: error61 });
|
|
2375
|
+
} finally {
|
|
2376
|
+
clearTimeout(timeout);
|
|
2377
|
+
signal.removeEventListener("abort", abort);
|
|
2378
|
+
connection.close();
|
|
2379
|
+
stdin.end();
|
|
2380
|
+
stderr.removeListener("data", consumeStderr);
|
|
2381
|
+
await stopProcessTree(child);
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2109
2385
|
// src/agent-catalog/executable-discovery.ts
|
|
2110
|
-
import { isAbsolute as
|
|
2386
|
+
import { isAbsolute as isAbsolute7, join as join13 } from "node:path";
|
|
2111
2387
|
|
|
2112
2388
|
// src/agent-installation/managed-adapter-installer.ts
|
|
2113
|
-
import { join as
|
|
2389
|
+
import { join as join12 } from "node:path";
|
|
2114
2390
|
var ManagedAdapterInstallError = class extends Error {
|
|
2115
2391
|
code;
|
|
2116
2392
|
constructor(code, message, options = {}) {
|
|
@@ -2167,7 +2443,7 @@ var ManagedAdapterInstaller = class {
|
|
|
2167
2443
|
this.#stateDirectory = options.stateDirectory;
|
|
2168
2444
|
}
|
|
2169
2445
|
install = async (specification, signal) => {
|
|
2170
|
-
const root =
|
|
2446
|
+
const root = join12(this.#stateDirectory, "adapters", specification.id, specification.adapterVersion);
|
|
2171
2447
|
try {
|
|
2172
2448
|
return await this.#npmInstaller.install(
|
|
2173
2449
|
root,
|
|
@@ -2201,7 +2477,7 @@ async function resolveManagedAdapterExecutable(specification, context) {
|
|
|
2201
2477
|
let managed;
|
|
2202
2478
|
try {
|
|
2203
2479
|
managed = await validateManagedAdapter(
|
|
2204
|
-
|
|
2480
|
+
join13(readStateDirectory(context.environment), "adapters", specification.id, specification.adapterVersion),
|
|
2205
2481
|
specification
|
|
2206
2482
|
);
|
|
2207
2483
|
} catch {
|
|
@@ -2212,7 +2488,7 @@ async function resolveManagedAdapterExecutable(specification, context) {
|
|
|
2212
2488
|
async function resolveSpecificationExecutables(executableName, overrideVariable, resolveInstalled, resolveManaged, context) {
|
|
2213
2489
|
const override = context.environment[overrideVariable];
|
|
2214
2490
|
if (override !== void 0) {
|
|
2215
|
-
if (override.length === 0 || !
|
|
2491
|
+
if (override.length === 0 || !isAbsolute7(override)) {
|
|
2216
2492
|
return [];
|
|
2217
2493
|
}
|
|
2218
2494
|
const executable = await resolveExecutablePath(override, context.platform);
|
|
@@ -2266,10 +2542,7 @@ async function resolveAdapterExecutable(specification, context) {
|
|
|
2266
2542
|
const executables = await resolveAdapterExecutables(specification, context);
|
|
2267
2543
|
return executables[0] ?? null;
|
|
2268
2544
|
}
|
|
2269
|
-
function
|
|
2270
|
-
return output2.trim().split(/\r?\n/, 1)[0]?.trim().slice(0, 128) ?? "";
|
|
2271
|
-
}
|
|
2272
|
-
async function probeVersion(executable, arguments_, environment, defaultWorkspace, signal, timeoutMilliseconds) {
|
|
2545
|
+
async function probeVersion(executable, arguments_, environment, defaultWorkspace, signal, timeoutMilliseconds, normalize4 = normalizeVersionOutput) {
|
|
2273
2546
|
const result = await runBoundedProcess({
|
|
2274
2547
|
arguments: arguments_,
|
|
2275
2548
|
command: executable.path,
|
|
@@ -2278,7 +2551,7 @@ async function probeVersion(executable, arguments_, environment, defaultWorkspac
|
|
|
2278
2551
|
signal,
|
|
2279
2552
|
timeoutMilliseconds
|
|
2280
2553
|
});
|
|
2281
|
-
const version2 =
|
|
2554
|
+
const version2 = normalize4(result.stdout || result.stderr);
|
|
2282
2555
|
if (version2.length === 0) {
|
|
2283
2556
|
throw new ProcessExecutionError("process_exit_failed", "The executable did not report a version");
|
|
2284
2557
|
}
|
|
@@ -2302,7 +2575,8 @@ async function findVerifiedAgentExecutable(specification, context) {
|
|
|
2302
2575
|
context.environment,
|
|
2303
2576
|
context.defaultWorkspace,
|
|
2304
2577
|
context.signal,
|
|
2305
|
-
specification.versionProbeTimeoutMilliseconds
|
|
2578
|
+
specification.versionProbeTimeoutMilliseconds,
|
|
2579
|
+
specification.normalizeAgentVersion
|
|
2306
2580
|
);
|
|
2307
2581
|
return new VerifiedAgentExecutable(executable, version2);
|
|
2308
2582
|
} catch {
|
|
@@ -2314,168 +2588,6 @@ async function findVerifiedAgentExecutable(specification, context) {
|
|
|
2314
2588
|
return null;
|
|
2315
2589
|
}
|
|
2316
2590
|
|
|
2317
|
-
// src/agent-catalog/acp-readiness-probe.ts
|
|
2318
|
-
var ACP_OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
2319
|
-
var VerifiedAcpReadiness = class {
|
|
2320
|
-
adapterVersion;
|
|
2321
|
-
capabilities;
|
|
2322
|
-
constructor(adapterVersion, capabilities) {
|
|
2323
|
-
this.adapterVersion = adapterVersion;
|
|
2324
|
-
this.capabilities = capabilities;
|
|
2325
|
-
}
|
|
2326
|
-
};
|
|
2327
|
-
var DiscoveryAcpHandler = class {
|
|
2328
|
-
onElicitation = (_request, _signal) => Promise.reject(new Error("ACP probe does not accept elicitations"));
|
|
2329
|
-
onElicitationComplete = (_notification) => Promise.resolve();
|
|
2330
|
-
onPermission = (_request, _signal) => Promise.reject(new Error("ACP probe does not grant permissions"));
|
|
2331
|
-
onUpdate = (_notification) => Promise.resolve();
|
|
2332
|
-
};
|
|
2333
|
-
function readableAcpStream(input2) {
|
|
2334
|
-
let byteLength = 0;
|
|
2335
|
-
let open5 = true;
|
|
2336
|
-
return new ReadableStream({
|
|
2337
|
-
start(controller) {
|
|
2338
|
-
input2.on("data", (chunk) => {
|
|
2339
|
-
if (!open5) {
|
|
2340
|
-
return;
|
|
2341
|
-
}
|
|
2342
|
-
if (!(chunk instanceof Uint8Array)) {
|
|
2343
|
-
open5 = false;
|
|
2344
|
-
controller.error(new Error("ACP adapter produced invalid output"));
|
|
2345
|
-
return;
|
|
2346
|
-
}
|
|
2347
|
-
byteLength += chunk.byteLength;
|
|
2348
|
-
if (byteLength > ACP_OUTPUT_LIMIT_BYTES) {
|
|
2349
|
-
open5 = false;
|
|
2350
|
-
controller.error(new Error("ACP adapter output limit exceeded"));
|
|
2351
|
-
return;
|
|
2352
|
-
}
|
|
2353
|
-
controller.enqueue(new Uint8Array(chunk));
|
|
2354
|
-
});
|
|
2355
|
-
input2.once("end", () => {
|
|
2356
|
-
if (open5) {
|
|
2357
|
-
open5 = false;
|
|
2358
|
-
controller.close();
|
|
2359
|
-
}
|
|
2360
|
-
});
|
|
2361
|
-
input2.once("error", (error61) => {
|
|
2362
|
-
if (open5) {
|
|
2363
|
-
open5 = false;
|
|
2364
|
-
controller.error(error61);
|
|
2365
|
-
}
|
|
2366
|
-
});
|
|
2367
|
-
},
|
|
2368
|
-
cancel() {
|
|
2369
|
-
open5 = false;
|
|
2370
|
-
}
|
|
2371
|
-
});
|
|
2372
|
-
}
|
|
2373
|
-
function writableAcpStream(output2) {
|
|
2374
|
-
return new WritableStream({
|
|
2375
|
-
write(chunk) {
|
|
2376
|
-
return new Promise((resolveWrite, rejectWrite) => {
|
|
2377
|
-
output2.write(chunk, (error61) => {
|
|
2378
|
-
if (error61) {
|
|
2379
|
-
rejectWrite(error61);
|
|
2380
|
-
return;
|
|
2381
|
-
}
|
|
2382
|
-
resolveWrite();
|
|
2383
|
-
});
|
|
2384
|
-
});
|
|
2385
|
-
}
|
|
2386
|
-
});
|
|
2387
|
-
}
|
|
2388
|
-
async function probeAcpReadiness(specification, agent, environment, defaultWorkspace, signal) {
|
|
2389
|
-
signal.throwIfAborted();
|
|
2390
|
-
const controller = new AbortController();
|
|
2391
|
-
let timedOut = false;
|
|
2392
|
-
const timeout = setTimeout(() => {
|
|
2393
|
-
timedOut = true;
|
|
2394
|
-
controller.abort(new Error("ACP probe timed out"));
|
|
2395
|
-
}, specification.acpProbeTimeoutMilliseconds);
|
|
2396
|
-
const abort = () => controller.abort(signal.reason);
|
|
2397
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
2398
|
-
if (signal.aborted) {
|
|
2399
|
-
abort();
|
|
2400
|
-
}
|
|
2401
|
-
const child = spawn2(agent.adapterPath, [...agent.arguments], {
|
|
2402
|
-
cwd: defaultWorkspace,
|
|
2403
|
-
detached: process.platform !== "win32",
|
|
2404
|
-
env: { ...environment, ...agent.environment },
|
|
2405
|
-
shell: false,
|
|
2406
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
2407
|
-
});
|
|
2408
|
-
const processExit = new Promise((_resolve, reject) => {
|
|
2409
|
-
const rejectExit = () => reject(new Error("ACP adapter exited during initialization"));
|
|
2410
|
-
child.once("error", reject);
|
|
2411
|
-
child.once("exit", rejectExit);
|
|
2412
|
-
if (child.exitCode !== null || child.signalCode !== null) {
|
|
2413
|
-
rejectExit();
|
|
2414
|
-
}
|
|
2415
|
-
});
|
|
2416
|
-
const stdin = child.stdin;
|
|
2417
|
-
const stdout = child.stdout;
|
|
2418
|
-
const stderr = child.stderr;
|
|
2419
|
-
if (stdin === null || stdout === null || stderr === null) {
|
|
2420
|
-
await stopProcessTree(child);
|
|
2421
|
-
throw new AgentProbeError("acp_incompatible", "ACP adapter streams are unavailable");
|
|
2422
|
-
}
|
|
2423
|
-
let stderrBytes = 0;
|
|
2424
|
-
const consumeStderr = (chunk) => {
|
|
2425
|
-
stderrBytes += chunk.byteLength;
|
|
2426
|
-
if (stderrBytes > ACP_OUTPUT_LIMIT_BYTES) {
|
|
2427
|
-
controller.abort(new Error("ACP adapter output limit exceeded"));
|
|
2428
|
-
}
|
|
2429
|
-
};
|
|
2430
|
-
stderr.on("data", consumeStderr);
|
|
2431
|
-
const connection = createAcpClient(new DiscoveryAcpHandler()).connect(
|
|
2432
|
-
ndJsonStream(writableAcpStream(stdin), readableAcpStream(stdout))
|
|
2433
|
-
);
|
|
2434
|
-
try {
|
|
2435
|
-
const initialized = await Promise.race([
|
|
2436
|
-
connection.agent.request(
|
|
2437
|
-
methods4.agent.initialize,
|
|
2438
|
-
{
|
|
2439
|
-
clientCapabilities: {},
|
|
2440
|
-
clientInfo: { name: "mobius", version: "0.0.3" },
|
|
2441
|
-
protocolVersion: PROTOCOL_VERSION
|
|
2442
|
-
},
|
|
2443
|
-
{ cancellationSignal: controller.signal }
|
|
2444
|
-
),
|
|
2445
|
-
processExit
|
|
2446
|
-
]);
|
|
2447
|
-
if (initialized.protocolVersion !== PROTOCOL_VERSION) {
|
|
2448
|
-
throw new AgentProbeError("acp_incompatible", "ACP adapter selected an unsupported protocol version");
|
|
2449
|
-
}
|
|
2450
|
-
await specification.verifyAcp(connection, initialized, defaultWorkspace, controller.signal);
|
|
2451
|
-
return new VerifiedAcpReadiness(
|
|
2452
|
-
normalizedVersion(initialized.agentInfo?.version ?? ""),
|
|
2453
|
-
normalizeAcpCapabilities(initialized)
|
|
2454
|
-
);
|
|
2455
|
-
} catch (error61) {
|
|
2456
|
-
if (signal.aborted) {
|
|
2457
|
-
signal.throwIfAborted();
|
|
2458
|
-
}
|
|
2459
|
-
if (timedOut) {
|
|
2460
|
-
throw new AgentProbeError("adapter_probe_timeout", "ACP adapter probe timed out", { cause: error61 });
|
|
2461
|
-
}
|
|
2462
|
-
if (error61 instanceof AgentProbeError) {
|
|
2463
|
-
throw error61;
|
|
2464
|
-
}
|
|
2465
|
-
if (error61 instanceof RequestError && error61.code === -32e3 && specification.authenticationCommand().length > 0) {
|
|
2466
|
-
throw new AgentProbeError("authentication_required", "Agent authentication is required", { cause: error61 });
|
|
2467
|
-
}
|
|
2468
|
-
throw new AgentProbeError("acp_incompatible", "ACP adapter initialization failed", { cause: error61 });
|
|
2469
|
-
} finally {
|
|
2470
|
-
clearTimeout(timeout);
|
|
2471
|
-
signal.removeEventListener("abort", abort);
|
|
2472
|
-
connection.close();
|
|
2473
|
-
stdin.end();
|
|
2474
|
-
stderr.removeListener("data", consumeStderr);
|
|
2475
|
-
await stopProcessTree(child);
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
|
|
2479
2591
|
// src/agent-catalog/discover-agent.ts
|
|
2480
2592
|
function agentProbeFailure(specification, error61) {
|
|
2481
2593
|
if (error61 instanceof AgentProbeError) {
|
|
@@ -2519,7 +2631,8 @@ async function discoverAgent(specification, context) {
|
|
|
2519
2631
|
context.environment,
|
|
2520
2632
|
context.defaultWorkspace,
|
|
2521
2633
|
context.signal,
|
|
2522
|
-
specification.versionProbeTimeoutMilliseconds
|
|
2634
|
+
specification.versionProbeTimeoutMilliseconds,
|
|
2635
|
+
specification.normalizeAgentVersion
|
|
2523
2636
|
);
|
|
2524
2637
|
} catch (error61) {
|
|
2525
2638
|
if (context.signal.aborted) {
|
|
@@ -2555,7 +2668,8 @@ async function discoverAgent(specification, context) {
|
|
|
2555
2668
|
},
|
|
2556
2669
|
context.defaultWorkspace,
|
|
2557
2670
|
context.signal,
|
|
2558
|
-
specification.versionProbeTimeoutMilliseconds
|
|
2671
|
+
specification.versionProbeTimeoutMilliseconds,
|
|
2672
|
+
specification.normalizeAdapterVersion
|
|
2559
2673
|
);
|
|
2560
2674
|
}
|
|
2561
2675
|
} catch (error61) {
|
|
@@ -2629,166 +2743,9 @@ async function discoverLocalAgentInventory(options) {
|
|
|
2629
2743
|
return new LocalAgentInventory(discoveries);
|
|
2630
2744
|
}
|
|
2631
2745
|
|
|
2632
|
-
// src/bots/bot-
|
|
2633
|
-
import {
|
|
2634
|
-
import {
|
|
2635
|
-
var BOT_IPC_RESPONSE_LIMIT = 64 * 1024;
|
|
2636
|
-
var BOT_IPC_TIMEOUT_MILLISECONDS = 2e4;
|
|
2637
|
-
var BotMessageCommandError = class extends Error {
|
|
2638
|
-
code;
|
|
2639
|
-
constructor(code, message, options = {}) {
|
|
2640
|
-
super(message, options);
|
|
2641
|
-
this.code = code;
|
|
2642
|
-
this.name = "BotMessageCommandError";
|
|
2643
|
-
}
|
|
2644
|
-
};
|
|
2645
|
-
function isRecord4(value) {
|
|
2646
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2647
|
-
}
|
|
2648
|
-
function requiredEnvironment(environment, name) {
|
|
2649
|
-
const value = environment[name]?.trim();
|
|
2650
|
-
if (value === void 0 || value.length === 0) {
|
|
2651
|
-
throw new BotMessageCommandError(
|
|
2652
|
-
"execution_context_required",
|
|
2653
|
-
"This command is available only inside an active Mobius Bot execution"
|
|
2654
|
-
);
|
|
2655
|
-
}
|
|
2656
|
-
return value;
|
|
2657
|
-
}
|
|
2658
|
-
function option(arguments_, name) {
|
|
2659
|
-
const index = arguments_.indexOf(name);
|
|
2660
|
-
if (index < 0 || index + 1 >= arguments_.length || arguments_[index + 1]?.startsWith("--") === true) {
|
|
2661
|
-
throw new BotMessageCommandError(
|
|
2662
|
-
"invalid_request",
|
|
2663
|
-
"Usage: mobius bot message send --target <current|alias> --text <message>"
|
|
2664
|
-
);
|
|
2665
|
-
}
|
|
2666
|
-
const value = arguments_[index + 1];
|
|
2667
|
-
if (value === void 0) {
|
|
2668
|
-
throw new BotMessageCommandError("invalid_request", "The Bot message option is missing");
|
|
2669
|
-
}
|
|
2670
|
-
return value;
|
|
2671
|
-
}
|
|
2672
|
-
function validateArguments(arguments_) {
|
|
2673
|
-
if (arguments_.length !== 4) {
|
|
2674
|
-
throw new BotMessageCommandError(
|
|
2675
|
-
"invalid_request",
|
|
2676
|
-
"Usage: mobius bot message send --target <current|alias> --text <message>"
|
|
2677
|
-
);
|
|
2678
|
-
}
|
|
2679
|
-
const names = [arguments_[0], arguments_[2]];
|
|
2680
|
-
if (!names.includes("--target") || !names.includes("--text") || names[0] === names[1]) {
|
|
2681
|
-
throw new BotMessageCommandError(
|
|
2682
|
-
"invalid_request",
|
|
2683
|
-
"Usage: mobius bot message send --target <current|alias> --text <message>"
|
|
2684
|
-
);
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2687
|
-
function decodeResponse(encoded) {
|
|
2688
|
-
let value;
|
|
2689
|
-
try {
|
|
2690
|
-
value = JSON.parse(encoded);
|
|
2691
|
-
} catch (cause) {
|
|
2692
|
-
throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data", {
|
|
2693
|
-
cause
|
|
2694
|
-
});
|
|
2695
|
-
}
|
|
2696
|
-
if (!isRecord4(value) || typeof value["ok"] !== "boolean") {
|
|
2697
|
-
throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data");
|
|
2698
|
-
}
|
|
2699
|
-
if (value["ok"] === true && typeof value["outboundMessageId"] === "string") {
|
|
2700
|
-
return value["outboundMessageId"];
|
|
2701
|
-
}
|
|
2702
|
-
const error61 = value["error"];
|
|
2703
|
-
if (value["ok"] === false && isRecord4(error61) && typeof error61["code"] === "string" && typeof error61["message"] === "string") {
|
|
2704
|
-
throw new BotMessageCommandError(error61["code"], error61["message"]);
|
|
2705
|
-
}
|
|
2706
|
-
throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data");
|
|
2707
|
-
}
|
|
2708
|
-
async function requestLocalBotService(socketPath, payload, signal) {
|
|
2709
|
-
signal.throwIfAborted();
|
|
2710
|
-
return await new Promise((resolve5, reject) => {
|
|
2711
|
-
const socket = createConnection(socketPath);
|
|
2712
|
-
const chunks = [];
|
|
2713
|
-
let length = 0;
|
|
2714
|
-
let finished = false;
|
|
2715
|
-
const finish = () => {
|
|
2716
|
-
socket.removeAllListeners();
|
|
2717
|
-
socket.destroy();
|
|
2718
|
-
};
|
|
2719
|
-
const fail = (error61) => {
|
|
2720
|
-
if (finished) {
|
|
2721
|
-
return;
|
|
2722
|
-
}
|
|
2723
|
-
finished = true;
|
|
2724
|
-
finish();
|
|
2725
|
-
if (error61 instanceof BotMessageCommandError) {
|
|
2726
|
-
reject(error61);
|
|
2727
|
-
return;
|
|
2728
|
-
}
|
|
2729
|
-
reject(
|
|
2730
|
-
new BotMessageCommandError(
|
|
2731
|
-
"execution_context_required",
|
|
2732
|
-
"The active Mobius Bot execution is unavailable",
|
|
2733
|
-
{ cause: error61 }
|
|
2734
|
-
)
|
|
2735
|
-
);
|
|
2736
|
-
};
|
|
2737
|
-
const abort = () => fail(signal.reason);
|
|
2738
|
-
socket.setTimeout(BOT_IPC_TIMEOUT_MILLISECONDS, () => {
|
|
2739
|
-
fail(new BotMessageCommandError("bot_service_unavailable", "The local Bot service timed out"));
|
|
2740
|
-
});
|
|
2741
|
-
socket.once("error", fail);
|
|
2742
|
-
socket.once("connect", () => socket.end(`${payload}
|
|
2743
|
-
`));
|
|
2744
|
-
socket.on("data", (chunk) => {
|
|
2745
|
-
length += chunk.byteLength;
|
|
2746
|
-
if (length > BOT_IPC_RESPONSE_LIMIT) {
|
|
2747
|
-
fail(new BotMessageCommandError("bot_service_unavailable", "The local Bot response is too large"));
|
|
2748
|
-
return;
|
|
2749
|
-
}
|
|
2750
|
-
chunks.push(chunk);
|
|
2751
|
-
});
|
|
2752
|
-
socket.once("end", () => {
|
|
2753
|
-
if (finished) {
|
|
2754
|
-
return;
|
|
2755
|
-
}
|
|
2756
|
-
finished = true;
|
|
2757
|
-
signal.removeEventListener("abort", abort);
|
|
2758
|
-
try {
|
|
2759
|
-
resolve5(decodeResponse(Buffer.concat(chunks).toString("utf8")));
|
|
2760
|
-
} catch (error61) {
|
|
2761
|
-
reject(error61);
|
|
2762
|
-
} finally {
|
|
2763
|
-
finish();
|
|
2764
|
-
}
|
|
2765
|
-
});
|
|
2766
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
2767
|
-
});
|
|
2768
|
-
}
|
|
2769
|
-
async function executeBotMessageCommand(arguments_, environment, signal) {
|
|
2770
|
-
validateArguments(arguments_);
|
|
2771
|
-
const contextHandle = requiredEnvironment(environment, "MOBIUS_BOT_CONTEXT");
|
|
2772
|
-
const socketPath = requiredEnvironment(environment, "MOBIUS_BOT_SOCKET");
|
|
2773
|
-
const target = option(arguments_, "--target");
|
|
2774
|
-
const text = option(arguments_, "--text");
|
|
2775
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/.test(target) || text.trim().length === 0 || text.length > 1e4) {
|
|
2776
|
-
throw new BotMessageCommandError("invalid_request", "The Bot outbound message is invalid");
|
|
2777
|
-
}
|
|
2778
|
-
return await requestLocalBotService(
|
|
2779
|
-
socketPath,
|
|
2780
|
-
JSON.stringify({
|
|
2781
|
-
contextHandle,
|
|
2782
|
-
idempotencyKey: `cli_${randomUUID()}`,
|
|
2783
|
-
target,
|
|
2784
|
-
text
|
|
2785
|
-
}),
|
|
2786
|
-
signal
|
|
2787
|
-
);
|
|
2788
|
-
}
|
|
2789
|
-
|
|
2790
|
-
// src/config.ts
|
|
2791
|
-
import { join as join13 } from "node:path";
|
|
2746
|
+
// src/bots/bot-approve-command.ts
|
|
2747
|
+
import { readdir as readdir2, realpath as realpath8 } from "node:fs/promises";
|
|
2748
|
+
import { dirname as dirname4, join as join17 } from "node:path";
|
|
2792
2749
|
|
|
2793
2750
|
// ../../node_modules/.pnpm/zod@4.5.4/node_modules/zod/v4/classic/external.js
|
|
2794
2751
|
var external_exports = {};
|
|
@@ -21656,8 +21613,198 @@ function date4(params) {
|
|
|
21656
21613
|
return _coercedDate(ZodDate, params);
|
|
21657
21614
|
}
|
|
21658
21615
|
|
|
21616
|
+
// src/authorization/workspace-directory.ts
|
|
21617
|
+
import { readdir, realpath as realpath6, stat as stat5 } from "node:fs/promises";
|
|
21618
|
+
import { homedir as homedir2 } from "node:os";
|
|
21619
|
+
import { basename, dirname as dirname3, isAbsolute as isAbsolute8, join as join14, normalize as normalize2, relative as relative4 } from "node:path";
|
|
21620
|
+
|
|
21621
|
+
// src/authorization/local-authorization-error.ts
|
|
21622
|
+
var LocalAuthorizationError = class extends Error {
|
|
21623
|
+
code = "local_authorization_failed";
|
|
21624
|
+
constructor(message, options = {}) {
|
|
21625
|
+
super(message, options);
|
|
21626
|
+
this.name = "LocalAuthorizationError";
|
|
21627
|
+
}
|
|
21628
|
+
};
|
|
21629
|
+
|
|
21630
|
+
// src/authorization/workspace-directory.ts
|
|
21631
|
+
var WorkspaceDirectoryListing = class {
|
|
21632
|
+
directoryId;
|
|
21633
|
+
directoryLabel;
|
|
21634
|
+
entries;
|
|
21635
|
+
parentDirectoryId;
|
|
21636
|
+
constructor(directoryId2, directoryLabel, entries, parentDirectoryId) {
|
|
21637
|
+
this.directoryId = directoryId2;
|
|
21638
|
+
this.directoryLabel = directoryLabel;
|
|
21639
|
+
this.entries = entries;
|
|
21640
|
+
this.parentDirectoryId = parentDirectoryId;
|
|
21641
|
+
}
|
|
21642
|
+
};
|
|
21643
|
+
function encodePath(path2) {
|
|
21644
|
+
const encoded = Buffer.from(path2, "utf8").toString("base64url");
|
|
21645
|
+
if (encoded.length === 0 || encoded.length > 2e3) {
|
|
21646
|
+
throw new LocalAuthorizationError("The local directory path is too long to select");
|
|
21647
|
+
}
|
|
21648
|
+
return `path-${encoded}`;
|
|
21649
|
+
}
|
|
21650
|
+
function decodePath(value, prefix) {
|
|
21651
|
+
const encoded = value.slice(prefix.length);
|
|
21652
|
+
if (encoded.length === 0 || encoded.length > 2e3 || !/^[a-zA-Z0-9_-]+$/.test(encoded)) {
|
|
21653
|
+
throw new LocalAuthorizationError("The directory identifier is invalid");
|
|
21654
|
+
}
|
|
21655
|
+
const decoded = Buffer.from(encoded, "base64url").toString("utf8");
|
|
21656
|
+
if (Buffer.from(decoded, "utf8").toString("base64url") !== encoded || decoded.includes("\0") || normalize2(decoded) !== decoded) {
|
|
21657
|
+
throw new LocalAuthorizationError("The directory identifier is invalid");
|
|
21658
|
+
}
|
|
21659
|
+
return decoded;
|
|
21660
|
+
}
|
|
21661
|
+
var WorkspaceDirectoryService = class {
|
|
21662
|
+
#bindingId;
|
|
21663
|
+
#defaultWorkspace;
|
|
21664
|
+
constructor(bindingId, defaultWorkspace) {
|
|
21665
|
+
this.#bindingId = bindingId;
|
|
21666
|
+
this.#defaultWorkspace = defaultWorkspace;
|
|
21667
|
+
}
|
|
21668
|
+
authorizeMachine = (bindingId) => {
|
|
21669
|
+
if (bindingId !== this.#bindingId) {
|
|
21670
|
+
throw new LocalAuthorizationError("The runtime binding is not authorized");
|
|
21671
|
+
}
|
|
21672
|
+
};
|
|
21673
|
+
requestedPath = (id) => {
|
|
21674
|
+
if (id === "default") {
|
|
21675
|
+
return this.#defaultWorkspace;
|
|
21676
|
+
}
|
|
21677
|
+
if (id === "home") {
|
|
21678
|
+
return homedir2();
|
|
21679
|
+
}
|
|
21680
|
+
if (id.startsWith("path-")) {
|
|
21681
|
+
const path2 = decodePath(id, "path-");
|
|
21682
|
+
if (isAbsolute8(path2)) {
|
|
21683
|
+
return path2;
|
|
21684
|
+
}
|
|
21685
|
+
}
|
|
21686
|
+
throw new LocalAuthorizationError("The directory identifier is invalid");
|
|
21687
|
+
};
|
|
21688
|
+
resolvePath = async (requested, signal) => {
|
|
21689
|
+
signal.throwIfAborted();
|
|
21690
|
+
if (!isAbsolute8(requested) || requested.includes("\0") || normalize2(requested) !== requested) {
|
|
21691
|
+
throw new LocalAuthorizationError("The local directory path is invalid");
|
|
21692
|
+
}
|
|
21693
|
+
try {
|
|
21694
|
+
const target = await realpath6(requested);
|
|
21695
|
+
signal.throwIfAborted();
|
|
21696
|
+
const metadata = await stat5(target);
|
|
21697
|
+
signal.throwIfAborted();
|
|
21698
|
+
if (!isAbsolute8(target) || normalize2(target) !== target || !metadata.isDirectory()) {
|
|
21699
|
+
throw new LocalAuthorizationError("The selected location is not a directory");
|
|
21700
|
+
}
|
|
21701
|
+
return target;
|
|
21702
|
+
} catch (cause) {
|
|
21703
|
+
signal.throwIfAborted();
|
|
21704
|
+
if (cause instanceof LocalAuthorizationError) {
|
|
21705
|
+
throw cause;
|
|
21706
|
+
}
|
|
21707
|
+
throw new LocalAuthorizationError("This directory is missing or unavailable to your user account", {
|
|
21708
|
+
cause
|
|
21709
|
+
});
|
|
21710
|
+
}
|
|
21711
|
+
};
|
|
21712
|
+
label = (path2) => {
|
|
21713
|
+
const suffix = relative4(homedir2(), path2);
|
|
21714
|
+
return suffix === "" ? "~" : !isAbsolute8(suffix) && suffix !== ".." && !suffix.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) ? `~/${suffix.replaceAll("\\", "/")}` : path2;
|
|
21715
|
+
};
|
|
21716
|
+
identify = async (bindingId, requestedPath, signal) => {
|
|
21717
|
+
this.authorizeMachine(bindingId);
|
|
21718
|
+
const path2 = await this.resolvePath(requestedPath, signal);
|
|
21719
|
+
return {
|
|
21720
|
+
path: path2,
|
|
21721
|
+
directoryId: encodePath(path2),
|
|
21722
|
+
name: basename(path2) || path2,
|
|
21723
|
+
label: this.label(path2)
|
|
21724
|
+
};
|
|
21725
|
+
};
|
|
21726
|
+
resolve = async (bindingId, id, signal) => {
|
|
21727
|
+
this.authorizeMachine(bindingId);
|
|
21728
|
+
return await this.resolvePath(this.requestedPath(id), signal);
|
|
21729
|
+
};
|
|
21730
|
+
list = async (bindingId, id, signal) => {
|
|
21731
|
+
const target = await this.resolve(bindingId, id, signal);
|
|
21732
|
+
try {
|
|
21733
|
+
const children = await readdir(target, { withFileTypes: true });
|
|
21734
|
+
signal.throwIfAborted();
|
|
21735
|
+
const entries = [];
|
|
21736
|
+
for (const child of children) {
|
|
21737
|
+
if (entries.length === 500) {
|
|
21738
|
+
break;
|
|
21739
|
+
}
|
|
21740
|
+
if (child.isFile() || child.isBlockDevice() || child.isCharacterDevice() || child.isFIFO() || child.isSocket()) {
|
|
21741
|
+
continue;
|
|
21742
|
+
}
|
|
21743
|
+
try {
|
|
21744
|
+
const path2 = await this.resolvePath(join14(target, child.name), signal);
|
|
21745
|
+
entries.push({ directoryId: encodePath(path2), name: child.name });
|
|
21746
|
+
} catch (cause) {
|
|
21747
|
+
signal.throwIfAborted();
|
|
21748
|
+
if (!(cause instanceof LocalAuthorizationError)) {
|
|
21749
|
+
throw cause;
|
|
21750
|
+
}
|
|
21751
|
+
}
|
|
21752
|
+
}
|
|
21753
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
21754
|
+
const parent = dirname3(target);
|
|
21755
|
+
return new WorkspaceDirectoryListing(
|
|
21756
|
+
id,
|
|
21757
|
+
this.label(target),
|
|
21758
|
+
entries,
|
|
21759
|
+
parent === target ? "" : encodePath(parent)
|
|
21760
|
+
);
|
|
21761
|
+
} catch (cause) {
|
|
21762
|
+
signal.throwIfAborted();
|
|
21763
|
+
if (cause instanceof LocalAuthorizationError) {
|
|
21764
|
+
throw cause;
|
|
21765
|
+
}
|
|
21766
|
+
throw new LocalAuthorizationError("This directory cannot be browsed by your user account", { cause });
|
|
21767
|
+
}
|
|
21768
|
+
};
|
|
21769
|
+
resolveMany = async (bindingId, ids, signal) => {
|
|
21770
|
+
const paths = /* @__PURE__ */ new Set();
|
|
21771
|
+
for (const id of ids) {
|
|
21772
|
+
paths.add(await this.resolve(bindingId, id, signal));
|
|
21773
|
+
}
|
|
21774
|
+
return [...paths];
|
|
21775
|
+
};
|
|
21776
|
+
};
|
|
21777
|
+
|
|
21778
|
+
// src/pairing/pair-local-machine.ts
|
|
21779
|
+
import { randomUUID } from "node:crypto";
|
|
21780
|
+
import { mkdir as mkdir3, open as open4, realpath as realpath7, rename as rename3, stat as stat6, writeFile as writeFile2 } from "node:fs/promises";
|
|
21781
|
+
import { hostname as hostname3 } from "node:os";
|
|
21782
|
+
import { basename as basename2, join as join16, resolve as resolve3 } from "node:path";
|
|
21783
|
+
|
|
21659
21784
|
// ../protocol/src/prompt-usage.ts
|
|
21660
21785
|
var TOKEN_COUNT = external_exports.number().int().nonnegative();
|
|
21786
|
+
function normalizeQuotaTokenCount(value) {
|
|
21787
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
21788
|
+
return value;
|
|
21789
|
+
}
|
|
21790
|
+
if (Reflect.has(value, "inputTokens") || Reflect.has(value, "outputTokens") || Reflect.has(value, "totalTokens")) {
|
|
21791
|
+
return value;
|
|
21792
|
+
}
|
|
21793
|
+
if (!Reflect.has(value, "input_tokens") && !Reflect.has(value, "output_tokens")) {
|
|
21794
|
+
return value;
|
|
21795
|
+
}
|
|
21796
|
+
const inputTokens = Reflect.get(value, "input_tokens");
|
|
21797
|
+
const outputTokens = Reflect.get(value, "output_tokens");
|
|
21798
|
+
const reportedTotal = Reflect.get(value, "total_tokens");
|
|
21799
|
+
return {
|
|
21800
|
+
cachedInputTokens: Reflect.get(value, "cached_input_tokens"),
|
|
21801
|
+
cachedWriteTokens: Reflect.get(value, "cached_write_tokens"),
|
|
21802
|
+
inputTokens,
|
|
21803
|
+
outputTokens,
|
|
21804
|
+
reasoningOutputTokens: Reflect.get(value, "reasoning_output_tokens"),
|
|
21805
|
+
totalTokens: reportedTotal === void 0 && typeof inputTokens === "number" && typeof outputTokens === "number" ? inputTokens + outputTokens : reportedTotal
|
|
21806
|
+
};
|
|
21807
|
+
}
|
|
21661
21808
|
var promptUsageSchema = external_exports.object({
|
|
21662
21809
|
cachedReadTokens: TOKEN_COUNT.nullish(),
|
|
21663
21810
|
cachedWriteTokens: TOKEN_COUNT.nullish(),
|
|
@@ -21666,14 +21813,17 @@ var promptUsageSchema = external_exports.object({
|
|
|
21666
21813
|
thoughtTokens: TOKEN_COUNT.nullish(),
|
|
21667
21814
|
totalTokens: TOKEN_COUNT
|
|
21668
21815
|
}).readonly();
|
|
21669
|
-
var quotaTokenCountSchema = external_exports.
|
|
21670
|
-
|
|
21671
|
-
|
|
21672
|
-
|
|
21673
|
-
|
|
21674
|
-
|
|
21675
|
-
|
|
21676
|
-
|
|
21816
|
+
var quotaTokenCountSchema = external_exports.preprocess(
|
|
21817
|
+
normalizeQuotaTokenCount,
|
|
21818
|
+
external_exports.object({
|
|
21819
|
+
cachedInputTokens: TOKEN_COUNT.optional(),
|
|
21820
|
+
cachedWriteTokens: TOKEN_COUNT.optional(),
|
|
21821
|
+
inputTokens: TOKEN_COUNT,
|
|
21822
|
+
outputTokens: TOKEN_COUNT,
|
|
21823
|
+
reasoningOutputTokens: TOKEN_COUNT.optional(),
|
|
21824
|
+
totalTokens: TOKEN_COUNT
|
|
21825
|
+
}).readonly()
|
|
21826
|
+
);
|
|
21677
21827
|
var promptMetadataSchema = external_exports.object({
|
|
21678
21828
|
quota: external_exports.object({
|
|
21679
21829
|
model_usage: external_exports.array(
|
|
@@ -22145,7 +22295,7 @@ var ProtocolMessageError = class extends Error {
|
|
|
22145
22295
|
this.name = "ProtocolMessageError";
|
|
22146
22296
|
}
|
|
22147
22297
|
};
|
|
22148
|
-
function
|
|
22298
|
+
function isRecord4(value) {
|
|
22149
22299
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22150
22300
|
}
|
|
22151
22301
|
function parseJson2(encoded) {
|
|
@@ -22157,7 +22307,7 @@ function parseJson2(encoded) {
|
|
|
22157
22307
|
cause: error61
|
|
22158
22308
|
});
|
|
22159
22309
|
}
|
|
22160
|
-
if (!
|
|
22310
|
+
if (!isRecord4(value)) {
|
|
22161
22311
|
throw new ProtocolMessageError("Message must be a JSON object");
|
|
22162
22312
|
}
|
|
22163
22313
|
return value;
|
|
@@ -22254,7 +22404,7 @@ function readActivityContent(value) {
|
|
|
22254
22404
|
}
|
|
22255
22405
|
function readActivityDetail(value) {
|
|
22256
22406
|
const field = value["detail"];
|
|
22257
|
-
if (!
|
|
22407
|
+
if (!isRecord4(field)) {
|
|
22258
22408
|
throw new ProtocolMessageError("Invalid activity detail field");
|
|
22259
22409
|
}
|
|
22260
22410
|
const kind = readString(field, "kind", 32);
|
|
@@ -22718,7 +22868,7 @@ var BotSessionPromptCommand = class {
|
|
|
22718
22868
|
this.replyRoute = replyRoute;
|
|
22719
22869
|
}
|
|
22720
22870
|
};
|
|
22721
|
-
function
|
|
22871
|
+
function isRecord5(value) {
|
|
22722
22872
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22723
22873
|
}
|
|
22724
22874
|
function parseJson3(encoded) {
|
|
@@ -22728,7 +22878,7 @@ function parseJson3(encoded) {
|
|
|
22728
22878
|
} catch (cause) {
|
|
22729
22879
|
throw new ProtocolMessageError("Message is not valid JSON", { cause });
|
|
22730
22880
|
}
|
|
22731
|
-
if (!
|
|
22881
|
+
if (!isRecord5(value)) {
|
|
22732
22882
|
throw new ProtocolMessageError("Message must be a JSON object");
|
|
22733
22883
|
}
|
|
22734
22884
|
return value;
|
|
@@ -22813,7 +22963,7 @@ function readTargets(value, key) {
|
|
|
22813
22963
|
const identifiers = /* @__PURE__ */ new Set();
|
|
22814
22964
|
const aliases = /* @__PURE__ */ new Set();
|
|
22815
22965
|
for (const candidate of field) {
|
|
22816
|
-
if (!
|
|
22966
|
+
if (!isRecord5(candidate)) {
|
|
22817
22967
|
throw new ProtocolMessageError(`Invalid Bot delivery targets: ${key}`);
|
|
22818
22968
|
}
|
|
22819
22969
|
assertExactKeys2(candidate, ["alias", "deliveryTargetId"]);
|
|
@@ -22829,7 +22979,7 @@ function readTargets(value, key) {
|
|
|
22829
22979
|
return Object.freeze(targets);
|
|
22830
22980
|
}
|
|
22831
22981
|
function readCompletionPolicy(value) {
|
|
22832
|
-
if (!
|
|
22982
|
+
if (!isRecord5(value)) {
|
|
22833
22983
|
throw new ProtocolMessageError("Invalid Bot completion policy");
|
|
22834
22984
|
}
|
|
22835
22985
|
const kind = readText2(value, "kind", 32, false);
|
|
@@ -22847,7 +22997,7 @@ function readCompletionPolicy(value) {
|
|
|
22847
22997
|
throw new ProtocolMessageError("Invalid Bot completion policy");
|
|
22848
22998
|
}
|
|
22849
22999
|
function readReplyRoute(value) {
|
|
22850
|
-
if (!
|
|
23000
|
+
if (!isRecord5(value)) {
|
|
22851
23001
|
throw new ProtocolMessageError("Invalid Bot reply route");
|
|
22852
23002
|
}
|
|
22853
23003
|
const kind = readText2(value, "kind", 32, false);
|
|
@@ -23298,10 +23448,14 @@ var encodeSessionEventEnvelope = (message) => encode4(message, eventEnvelopeSche
|
|
|
23298
23448
|
var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAcknowledgementSchema);
|
|
23299
23449
|
var decodeSessionEventRejection = (encoded) => decode5(encoded, eventRejectionSchema);
|
|
23300
23450
|
|
|
23451
|
+
// src/pairing/pair-local-machine.ts
|
|
23452
|
+
import { fetch as fetch2 } from "undici";
|
|
23453
|
+
|
|
23301
23454
|
// src/config.ts
|
|
23455
|
+
import { join as join15 } from "node:path";
|
|
23302
23456
|
function loadDefaultWorkspace(environment = process.env) {
|
|
23303
23457
|
const { MOBIUS_DEFAULT_WORKSPACE: configuredWorkspace } = environment;
|
|
23304
|
-
return configuredWorkspace ??
|
|
23458
|
+
return configuredWorkspace ?? join15(readStateDirectory(environment), "workspace");
|
|
23305
23459
|
}
|
|
23306
23460
|
function createDaemonConfig(connection, agents = [], installableAgents = []) {
|
|
23307
23461
|
return {
|
|
@@ -23317,11 +23471,6 @@ function createDaemonConfig(connection, agents = [], installableAgents = []) {
|
|
|
23317
23471
|
}
|
|
23318
23472
|
|
|
23319
23473
|
// src/pairing/pair-local-machine.ts
|
|
23320
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
23321
|
-
import { mkdir as mkdir3, open as open4, realpath as realpath6, rename as rename3, stat as stat5, writeFile as writeFile2 } from "node:fs/promises";
|
|
23322
|
-
import { hostname as hostname3 } from "node:os";
|
|
23323
|
-
import { basename, join as join14, resolve as resolve3 } from "node:path";
|
|
23324
|
-
import { fetch as fetch2 } from "undici";
|
|
23325
23474
|
var PAIRING_TIMEOUT_MILLISECONDS = 2e4;
|
|
23326
23475
|
var PAIRING_CODE_PATTERN = /^[A-HJ-NP-Z2-9]{4}(?:-[A-HJ-NP-Z2-9]{4}){4}$/;
|
|
23327
23476
|
var IDENTIFIER_PATTERN5 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
|
|
@@ -23339,7 +23488,7 @@ var PairingClientError = class extends Error {
|
|
|
23339
23488
|
this.name = "PairingClientError";
|
|
23340
23489
|
}
|
|
23341
23490
|
};
|
|
23342
|
-
function
|
|
23491
|
+
function isRecord6(value) {
|
|
23343
23492
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23344
23493
|
}
|
|
23345
23494
|
function hasExactKeys(value, expectedKeys) {
|
|
@@ -23432,8 +23581,8 @@ async function resolveDefaultWorkspace(path2) {
|
|
|
23432
23581
|
const requestedWorkspace = resolve3(path2);
|
|
23433
23582
|
try {
|
|
23434
23583
|
await mkdir3(requestedWorkspace, { recursive: true, mode: 448 });
|
|
23435
|
-
const defaultWorkspace = await
|
|
23436
|
-
const metadata = await
|
|
23584
|
+
const defaultWorkspace = await realpath7(requestedWorkspace);
|
|
23585
|
+
const metadata = await stat6(defaultWorkspace);
|
|
23437
23586
|
if (!metadata.isDirectory()) {
|
|
23438
23587
|
throw new PairingClientError("invalid_workspace", "The default workspace is not a directory");
|
|
23439
23588
|
}
|
|
@@ -23462,9 +23611,9 @@ async function readJsonResponse(response) {
|
|
|
23462
23611
|
}
|
|
23463
23612
|
}
|
|
23464
23613
|
function readRelayError(value) {
|
|
23465
|
-
if (
|
|
23614
|
+
if (isRecord6(value)) {
|
|
23466
23615
|
const error61 = value["error"];
|
|
23467
|
-
if (
|
|
23616
|
+
if (isRecord6(error61)) {
|
|
23468
23617
|
const code = error61["code"];
|
|
23469
23618
|
const message = error61["message"];
|
|
23470
23619
|
if (typeof code === "string" && typeof message === "string") {
|
|
@@ -23475,7 +23624,7 @@ function readRelayError(value) {
|
|
|
23475
23624
|
return new PairingClientError("pairing_failed", "The relay could not pair this machine");
|
|
23476
23625
|
}
|
|
23477
23626
|
function decodePairingResponse(value, credentialField, expectedKeys) {
|
|
23478
|
-
if (!
|
|
23627
|
+
if (!isRecord6(value) || !hasExactKeys(value, expectedKeys)) {
|
|
23479
23628
|
throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
|
|
23480
23629
|
}
|
|
23481
23630
|
const channelId = value["channelId"];
|
|
@@ -23503,7 +23652,7 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
|
|
|
23503
23652
|
installableAgents,
|
|
23504
23653
|
machineName: hostname3(),
|
|
23505
23654
|
pairingCode: options.code,
|
|
23506
|
-
workspaceName:
|
|
23655
|
+
workspaceName: basename2(defaultWorkspace) || "Local workspace"
|
|
23507
23656
|
}),
|
|
23508
23657
|
dispatcher: environmentProxyDispatcher,
|
|
23509
23658
|
headers: { "content-type": "application/json" },
|
|
@@ -23520,10 +23669,10 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
|
|
|
23520
23669
|
return options.pairingId.length > 0 ? decodePairingResponse(value, "runtimeCredential", REDEEM_RESPONSE_KEYS) : decodePairingResponse(value, "daemonToken", CLAIM_RESPONSE_KEYS);
|
|
23521
23670
|
}
|
|
23522
23671
|
async function persistConnection(channelId, relayOrigin, runtimeCredential2, defaultWorkspace, environment) {
|
|
23523
|
-
const connectionsDirectory =
|
|
23672
|
+
const connectionsDirectory = join16(readStateDirectory(environment), "connections");
|
|
23524
23673
|
await mkdir3(connectionsDirectory, { mode: 448, recursive: true });
|
|
23525
|
-
const connectionPath =
|
|
23526
|
-
const temporaryPath =
|
|
23674
|
+
const connectionPath = join16(connectionsDirectory, `${channelId}.json`);
|
|
23675
|
+
const temporaryPath = join16(connectionsDirectory, `.${channelId}.${randomUUID()}.tmp`);
|
|
23527
23676
|
await writeFile2(
|
|
23528
23677
|
temporaryPath,
|
|
23529
23678
|
`${JSON.stringify(
|
|
@@ -23543,7 +23692,7 @@ async function persistConnection(channelId, relayOrigin, runtimeCredential2, def
|
|
|
23543
23692
|
return connectionPath;
|
|
23544
23693
|
}
|
|
23545
23694
|
function decodeSavedConnection(value, expectedChannelId) {
|
|
23546
|
-
if (!
|
|
23695
|
+
if (!isRecord6(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS) && !hasExactKeys(value, LEGACY_SAVED_CONNECTION_KEYS)) {
|
|
23547
23696
|
throw new PairingClientError("invalid_connection", "The saved connection is invalid");
|
|
23548
23697
|
}
|
|
23549
23698
|
const defaultWorkspace = value["defaultWorkspace"];
|
|
@@ -23561,7 +23710,7 @@ function decodeSavedConnection(value, expectedChannelId) {
|
|
|
23561
23710
|
};
|
|
23562
23711
|
}
|
|
23563
23712
|
async function readSavedConnection(channelId, environment) {
|
|
23564
|
-
const connectionPath =
|
|
23713
|
+
const connectionPath = join16(readStateDirectory(environment), "connections", `${channelId}.json`);
|
|
23565
23714
|
let encoded = "";
|
|
23566
23715
|
const handle = await open4(connectionPath, "r");
|
|
23567
23716
|
try {
|
|
@@ -23597,7 +23746,7 @@ function buildPairedDaemon(connection, connectionPath, inventory, environment) {
|
|
|
23597
23746
|
const relayUrl = mobiusApiUrl(connection.relayOrigin, "/v1/runtimes/connect");
|
|
23598
23747
|
relayUrl.protocol = relayUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
23599
23748
|
relayUrl.searchParams.set("token", connection.runtimeCredential);
|
|
23600
|
-
const auditDatabasePath =
|
|
23749
|
+
const auditDatabasePath = join16(readStateDirectory(environment), "audit", `${connection.channelId}.sqlite`);
|
|
23601
23750
|
return {
|
|
23602
23751
|
channelUrl: new URL(`/channels/${encodeURIComponent(connection.channelId)}`, connection.relayOrigin).toString(),
|
|
23603
23752
|
config: createDaemonConfig(
|
|
@@ -23647,7 +23796,7 @@ async function pairLocalMachine(arguments_, signal = new AbortController().signa
|
|
|
23647
23796
|
}
|
|
23648
23797
|
async function resumeMobiusRuntime(channelId, signal, environment = process.env) {
|
|
23649
23798
|
const normalizedChannelId = validatePairingId(channelId);
|
|
23650
|
-
const connectionPath =
|
|
23799
|
+
const connectionPath = join16(readStateDirectory(environment), "connections", `${normalizedChannelId}.json`);
|
|
23651
23800
|
const connection = await readSavedConnection(normalizedChannelId, environment);
|
|
23652
23801
|
const inventory = await discoverLocalAgentInventory({
|
|
23653
23802
|
defaultWorkspace: connection.defaultWorkspace,
|
|
@@ -23660,15 +23809,714 @@ async function resumeLocalMachine(arguments_, signal = new AbortController().sig
|
|
|
23660
23809
|
return await resumeMobiusRuntime(parseChannelArgument(arguments_), signal, process.env);
|
|
23661
23810
|
}
|
|
23662
23811
|
|
|
23812
|
+
// src/bots/local-bot-grant-store.ts
|
|
23813
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
23814
|
+
import { stat as stat7 } from "node:fs/promises";
|
|
23815
|
+
import { DatabaseSync } from "node:sqlite";
|
|
23816
|
+
var LOCAL_BOT_GRANT_LIFETIME_MILLISECONDS = 90 * 24 * 60 * 60 * 1e3;
|
|
23817
|
+
var identifier2 = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/);
|
|
23818
|
+
var savedApprovalSchema = external_exports.object({
|
|
23819
|
+
local_grant_id: identifier2,
|
|
23820
|
+
bot_id: identifier2,
|
|
23821
|
+
binding_id: identifier2,
|
|
23822
|
+
binding_revision: external_exports.number().int().positive(),
|
|
23823
|
+
agent_ref: identifier2,
|
|
23824
|
+
workspace_ref: external_exports.string().min(1).max(2048),
|
|
23825
|
+
revocation_epoch: external_exports.number().int().nonnegative(),
|
|
23826
|
+
agent_identity_digest: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
23827
|
+
workspace_identity_digest: external_exports.string().regex(/^[a-f0-9]{64}$/)
|
|
23828
|
+
}).readonly();
|
|
23829
|
+
var LocalBotAuthorizationError = class extends Error {
|
|
23830
|
+
code;
|
|
23831
|
+
constructor(code, message, options = {}) {
|
|
23832
|
+
super(message, options);
|
|
23833
|
+
this.code = code;
|
|
23834
|
+
this.name = "LocalBotAuthorizationError";
|
|
23835
|
+
}
|
|
23836
|
+
};
|
|
23837
|
+
var LocalBotGrant = class {
|
|
23838
|
+
agent;
|
|
23839
|
+
agentIdentityDigest;
|
|
23840
|
+
allowedDeliveryTargetIds;
|
|
23841
|
+
behaviorDigest;
|
|
23842
|
+
behaviorRevisionId;
|
|
23843
|
+
bindingId;
|
|
23844
|
+
bindingRevision;
|
|
23845
|
+
botId;
|
|
23846
|
+
expiresAt;
|
|
23847
|
+
localGrantId;
|
|
23848
|
+
revocationEpoch;
|
|
23849
|
+
workspaceIdentityDigest;
|
|
23850
|
+
workspacePath;
|
|
23851
|
+
workspaceRef;
|
|
23852
|
+
constructor(localGrantId, botId, behaviorRevisionId, behaviorDigest, bindingId, bindingRevision, agent, agentIdentityDigest, workspaceRef, workspacePath, workspaceIdentityDigest, allowedDeliveryTargetIds, expiresAt, revocationEpoch) {
|
|
23853
|
+
this.localGrantId = localGrantId;
|
|
23854
|
+
this.botId = botId;
|
|
23855
|
+
this.behaviorRevisionId = behaviorRevisionId;
|
|
23856
|
+
this.behaviorDigest = behaviorDigest;
|
|
23857
|
+
this.bindingId = bindingId;
|
|
23858
|
+
this.bindingRevision = bindingRevision;
|
|
23859
|
+
this.agent = agent;
|
|
23860
|
+
this.agentIdentityDigest = agentIdentityDigest;
|
|
23861
|
+
this.workspaceRef = workspaceRef;
|
|
23862
|
+
this.workspacePath = workspacePath;
|
|
23863
|
+
this.workspaceIdentityDigest = workspaceIdentityDigest;
|
|
23864
|
+
this.allowedDeliveryTargetIds = Object.freeze([...allowedDeliveryTargetIds]);
|
|
23865
|
+
this.expiresAt = expiresAt;
|
|
23866
|
+
this.revocationEpoch = revocationEpoch;
|
|
23867
|
+
}
|
|
23868
|
+
};
|
|
23869
|
+
function digest(value) {
|
|
23870
|
+
return createHash2("sha256").update(value, "utf8").digest("hex");
|
|
23871
|
+
}
|
|
23872
|
+
function agentDigest(agent) {
|
|
23873
|
+
return digest(
|
|
23874
|
+
JSON.stringify({
|
|
23875
|
+
adapter: agent.adapterIdentity,
|
|
23876
|
+
agent: agent.agentIdentity,
|
|
23877
|
+
agentVersion: agent.agentVersion,
|
|
23878
|
+
arguments: agent.arguments,
|
|
23879
|
+
id: agent.id
|
|
23880
|
+
})
|
|
23881
|
+
);
|
|
23882
|
+
}
|
|
23883
|
+
async function workspaceDigest(path2) {
|
|
23884
|
+
const metadata = await stat7(path2);
|
|
23885
|
+
if (!metadata.isDirectory()) {
|
|
23886
|
+
throw new LocalBotAuthorizationError("binding_incompatible", "The Bot workspace is not a directory");
|
|
23887
|
+
}
|
|
23888
|
+
return digest(
|
|
23889
|
+
JSON.stringify({
|
|
23890
|
+
changedAtMilliseconds: metadata.ctimeMs,
|
|
23891
|
+
device: metadata.dev,
|
|
23892
|
+
inode: metadata.ino,
|
|
23893
|
+
path: path2
|
|
23894
|
+
})
|
|
23895
|
+
);
|
|
23896
|
+
}
|
|
23897
|
+
function exactTargets(left, right) {
|
|
23898
|
+
if (left.length !== right.length) {
|
|
23899
|
+
return false;
|
|
23900
|
+
}
|
|
23901
|
+
const expected = [...left].sort();
|
|
23902
|
+
const actual = [...right].sort();
|
|
23903
|
+
return expected.every((target, index) => target === actual[index]);
|
|
23904
|
+
}
|
|
23905
|
+
function rowString(row, key) {
|
|
23906
|
+
const value = Reflect.get(row, key);
|
|
23907
|
+
if (typeof value !== "string") {
|
|
23908
|
+
throw new LocalBotAuthorizationError("local_grant_invalid", "The local Bot grant is invalid");
|
|
23909
|
+
}
|
|
23910
|
+
return value;
|
|
23911
|
+
}
|
|
23912
|
+
function rowInteger(row, key) {
|
|
23913
|
+
const value = Reflect.get(row, key);
|
|
23914
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
|
23915
|
+
throw new LocalBotAuthorizationError("local_grant_invalid", "The local Bot grant is invalid");
|
|
23916
|
+
}
|
|
23917
|
+
return value;
|
|
23918
|
+
}
|
|
23919
|
+
function parseTargets(encoded) {
|
|
23920
|
+
let value;
|
|
23921
|
+
try {
|
|
23922
|
+
value = JSON.parse(encoded);
|
|
23923
|
+
} catch (cause) {
|
|
23924
|
+
throw new LocalBotAuthorizationError("local_grant_invalid", "The local Bot grant is invalid", { cause });
|
|
23925
|
+
}
|
|
23926
|
+
if (!Array.isArray(value) || value.some((target) => typeof target !== "string")) {
|
|
23927
|
+
throw new LocalBotAuthorizationError("local_grant_invalid", "The local Bot grant is invalid");
|
|
23928
|
+
}
|
|
23929
|
+
return value.filter((target) => typeof target === "string");
|
|
23930
|
+
}
|
|
23931
|
+
var LocalBotGrantStore = class {
|
|
23932
|
+
#database;
|
|
23933
|
+
static containsBot = (path2, botId) => {
|
|
23934
|
+
const database = new DatabaseSync(path2, { readOnly: true });
|
|
23935
|
+
try {
|
|
23936
|
+
if (database.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'local_bot_grants'").get() === void 0) {
|
|
23937
|
+
return false;
|
|
23938
|
+
}
|
|
23939
|
+
return database.prepare("SELECT local_grant_id FROM local_bot_grants WHERE bot_id = ? LIMIT 1").get(botId) !== void 0;
|
|
23940
|
+
} finally {
|
|
23941
|
+
database.close();
|
|
23942
|
+
}
|
|
23943
|
+
};
|
|
23944
|
+
constructor(path2) {
|
|
23945
|
+
this.#database = new DatabaseSync(path2);
|
|
23946
|
+
this.#database.exec(`
|
|
23947
|
+
CREATE TABLE IF NOT EXISTS local_bot_grants (
|
|
23948
|
+
local_grant_id TEXT PRIMARY KEY,
|
|
23949
|
+
bot_id TEXT NOT NULL,
|
|
23950
|
+
behavior_revision_id TEXT NOT NULL,
|
|
23951
|
+
behavior_digest TEXT NOT NULL,
|
|
23952
|
+
binding_id TEXT NOT NULL UNIQUE,
|
|
23953
|
+
binding_revision INTEGER NOT NULL,
|
|
23954
|
+
agent_ref TEXT NOT NULL,
|
|
23955
|
+
agent_identity_digest TEXT NOT NULL,
|
|
23956
|
+
workspace_ref TEXT NOT NULL,
|
|
23957
|
+
workspace_path TEXT NOT NULL,
|
|
23958
|
+
workspace_identity_digest TEXT NOT NULL,
|
|
23959
|
+
allowed_delivery_target_ids_json TEXT NOT NULL,
|
|
23960
|
+
unattended_policy_json TEXT NOT NULL,
|
|
23961
|
+
created_at INTEGER NOT NULL,
|
|
23962
|
+
expires_at INTEGER NOT NULL,
|
|
23963
|
+
revocation_epoch INTEGER NOT NULL DEFAULT 0,
|
|
23964
|
+
revoked_at INTEGER
|
|
23965
|
+
) STRICT;
|
|
23966
|
+
CREATE TABLE IF NOT EXISTS local_bot_binding_resolutions (
|
|
23967
|
+
request_id TEXT PRIMARY KEY,
|
|
23968
|
+
binding_id TEXT NOT NULL,
|
|
23969
|
+
local_grant_id TEXT NOT NULL,
|
|
23970
|
+
resolved_at INTEGER NOT NULL,
|
|
23971
|
+
result TEXT NOT NULL CHECK (result IN ('approved', 'unavailable')),
|
|
23972
|
+
error_code TEXT,
|
|
23973
|
+
error_message TEXT
|
|
23974
|
+
) STRICT;
|
|
23975
|
+
`);
|
|
23976
|
+
}
|
|
23977
|
+
/** Only the local CLI may invoke this explicit approval transition. */
|
|
23978
|
+
reapprove = async (botId, catalog, directories, bindingAuthorityId, now, signal) => {
|
|
23979
|
+
signal.throwIfAborted();
|
|
23980
|
+
if (!identifier2.safeParse(botId).success) {
|
|
23981
|
+
throw new LocalBotAuthorizationError("invalid_request", "The Bot ID is invalid");
|
|
23982
|
+
}
|
|
23983
|
+
const row = this.#database.prepare("SELECT * FROM local_bot_grants WHERE bot_id = ? ORDER BY binding_revision DESC LIMIT 1").get(botId);
|
|
23984
|
+
if (row === void 0) {
|
|
23985
|
+
throw new LocalBotAuthorizationError("bot_not_found", "This machine has no saved approval for that Bot");
|
|
23986
|
+
}
|
|
23987
|
+
const parsed = savedApprovalSchema.safeParse(row);
|
|
23988
|
+
if (!parsed.success) {
|
|
23989
|
+
throw new LocalBotAuthorizationError("local_grant_invalid", "The saved Bot approval is invalid", {
|
|
23990
|
+
cause: parsed.error
|
|
23991
|
+
});
|
|
23992
|
+
}
|
|
23993
|
+
const saved = parsed.data;
|
|
23994
|
+
const agent = catalog.find(saved.agent_ref);
|
|
23995
|
+
if (!agent.capabilities.embeddedContextPrompt) {
|
|
23996
|
+
throw new LocalBotAuthorizationError("binding_incompatible", "The local Agent cannot apply Bot context");
|
|
23997
|
+
}
|
|
23998
|
+
const [adapterUnchanged, agentUnchanged] = await Promise.all([
|
|
23999
|
+
revalidateExecutable(agent.adapterIdentity),
|
|
24000
|
+
revalidateExecutable(agent.agentIdentity)
|
|
24001
|
+
]);
|
|
24002
|
+
signal.throwIfAborted();
|
|
24003
|
+
if (!adapterUnchanged || !agentUnchanged) {
|
|
24004
|
+
throw new LocalBotAuthorizationError(
|
|
24005
|
+
"binding_incompatible",
|
|
24006
|
+
"The local Agent executable changed before approval"
|
|
24007
|
+
);
|
|
24008
|
+
}
|
|
24009
|
+
const workspacePath = await directories.resolve(bindingAuthorityId, saved.workspace_ref, signal);
|
|
24010
|
+
const workspaceIdentityDigest = await workspaceDigest(workspacePath);
|
|
24011
|
+
signal.throwIfAborted();
|
|
24012
|
+
this.#database.exec("BEGIN IMMEDIATE");
|
|
24013
|
+
try {
|
|
24014
|
+
const result = this.#database.prepare(`UPDATE local_bot_grants
|
|
24015
|
+
SET agent_identity_digest = ?, workspace_identity_digest = ?, workspace_path = ?,
|
|
24016
|
+
revoked_at = NULL, expires_at = ?, revocation_epoch = revocation_epoch + 1
|
|
24017
|
+
WHERE local_grant_id = ? AND revocation_epoch = ?
|
|
24018
|
+
AND agent_identity_digest = ? AND workspace_identity_digest = ?
|
|
24019
|
+
AND binding_revision = (SELECT MAX(binding_revision) FROM local_bot_grants WHERE bot_id = ?)`).run(
|
|
24020
|
+
agentDigest(agent),
|
|
24021
|
+
workspaceIdentityDigest,
|
|
24022
|
+
workspacePath,
|
|
24023
|
+
now + LOCAL_BOT_GRANT_LIFETIME_MILLISECONDS,
|
|
24024
|
+
saved.local_grant_id,
|
|
24025
|
+
saved.revocation_epoch,
|
|
24026
|
+
saved.agent_identity_digest,
|
|
24027
|
+
saved.workspace_identity_digest,
|
|
24028
|
+
botId
|
|
24029
|
+
);
|
|
24030
|
+
if (result.changes !== 1) {
|
|
24031
|
+
throw new LocalBotAuthorizationError(
|
|
24032
|
+
"binding_not_approved",
|
|
24033
|
+
"The Bot binding changed during approval; run the command again"
|
|
24034
|
+
);
|
|
24035
|
+
}
|
|
24036
|
+
this.#database.prepare(`INSERT INTO audit_events (recorded_at, event, command_id, session_id, details_json)
|
|
24037
|
+
VALUES (?, 'bot_binding_locally_reapproved', ?, '', ?)`).run(
|
|
24038
|
+
new Date(now).toISOString(),
|
|
24039
|
+
saved.binding_id,
|
|
24040
|
+
JSON.stringify({ botId, bindingId: saved.binding_id, agentRef: saved.agent_ref })
|
|
24041
|
+
);
|
|
24042
|
+
this.#database.exec("COMMIT");
|
|
24043
|
+
} catch (cause) {
|
|
24044
|
+
this.#database.exec("ROLLBACK");
|
|
24045
|
+
throw cause;
|
|
24046
|
+
}
|
|
24047
|
+
return { bindingId: saved.binding_id, workspacePath, agentRef: saved.agent_ref };
|
|
24048
|
+
};
|
|
24049
|
+
approve = async (request, catalog, directories, bindingAuthorityId, now, signal) => {
|
|
24050
|
+
signal.throwIfAborted();
|
|
24051
|
+
const existing = this.#database.prepare(
|
|
24052
|
+
`SELECT grants.* FROM local_bot_binding_resolutions AS resolutions
|
|
24053
|
+
INNER JOIN local_bot_grants AS grants
|
|
24054
|
+
ON grants.local_grant_id = resolutions.local_grant_id
|
|
24055
|
+
WHERE resolutions.request_id = ? AND resolutions.binding_id = ?
|
|
24056
|
+
AND resolutions.result = 'approved'`
|
|
24057
|
+
).get(request.requestId, request.bindingId);
|
|
24058
|
+
if (existing !== void 0) {
|
|
24059
|
+
return new BotExecutionBindingApprovedEvent(
|
|
24060
|
+
request.requestId,
|
|
24061
|
+
request.botId,
|
|
24062
|
+
request.behaviorRevisionId,
|
|
24063
|
+
request.bindingId,
|
|
24064
|
+
request.bindingRevision,
|
|
24065
|
+
rowString(existing, "local_grant_id"),
|
|
24066
|
+
rowString(existing, "agent_identity_digest"),
|
|
24067
|
+
rowString(existing, "workspace_identity_digest")
|
|
24068
|
+
);
|
|
24069
|
+
}
|
|
24070
|
+
const resolved = await this.resolveProposal(request, catalog, directories, bindingAuthorityId, signal);
|
|
24071
|
+
const localGrantId = `lbg_${randomUUID2()}`;
|
|
24072
|
+
const expiresAt = now + LOCAL_BOT_GRANT_LIFETIME_MILLISECONDS;
|
|
24073
|
+
this.#database.exec("BEGIN IMMEDIATE");
|
|
24074
|
+
try {
|
|
24075
|
+
this.#database.prepare(
|
|
24076
|
+
`INSERT INTO local_bot_grants (
|
|
24077
|
+
local_grant_id, bot_id, behavior_revision_id, behavior_digest,
|
|
24078
|
+
binding_id, binding_revision, agent_ref,
|
|
24079
|
+
agent_identity_digest, workspace_ref, workspace_path,
|
|
24080
|
+
workspace_identity_digest, allowed_delivery_target_ids_json,
|
|
24081
|
+
unattended_policy_json, created_at, expires_at
|
|
24082
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
24083
|
+
).run(
|
|
24084
|
+
localGrantId,
|
|
24085
|
+
request.botId,
|
|
24086
|
+
request.behaviorRevisionId,
|
|
24087
|
+
request.behaviorDigest,
|
|
24088
|
+
request.bindingId,
|
|
24089
|
+
request.bindingRevision,
|
|
24090
|
+
request.agentRef,
|
|
24091
|
+
resolved.agentIdentityDigest,
|
|
24092
|
+
request.workspaceRef,
|
|
24093
|
+
resolved.workspacePath,
|
|
24094
|
+
resolved.workspaceIdentityDigest,
|
|
24095
|
+
JSON.stringify(request.allowedDeliveryTargets.map((target) => target.deliveryTargetId)),
|
|
24096
|
+
JSON.stringify({ localSideEffects: "owner-approval-required" }),
|
|
24097
|
+
now,
|
|
24098
|
+
expiresAt
|
|
24099
|
+
);
|
|
24100
|
+
this.#database.prepare(
|
|
24101
|
+
`INSERT INTO local_bot_binding_resolutions (
|
|
24102
|
+
request_id, binding_id, local_grant_id, resolved_at, result
|
|
24103
|
+
) VALUES (?, ?, ?, ?, 'approved')`
|
|
24104
|
+
).run(request.requestId, request.bindingId, localGrantId, now);
|
|
24105
|
+
this.#database.exec("COMMIT");
|
|
24106
|
+
} catch (cause) {
|
|
24107
|
+
this.#database.exec("ROLLBACK");
|
|
24108
|
+
throw cause;
|
|
24109
|
+
}
|
|
24110
|
+
signal.throwIfAborted();
|
|
24111
|
+
return new BotExecutionBindingApprovedEvent(
|
|
24112
|
+
request.requestId,
|
|
24113
|
+
request.botId,
|
|
24114
|
+
request.behaviorRevisionId,
|
|
24115
|
+
request.bindingId,
|
|
24116
|
+
request.bindingRevision,
|
|
24117
|
+
localGrantId,
|
|
24118
|
+
resolved.agentIdentityDigest,
|
|
24119
|
+
resolved.workspaceIdentityDigest
|
|
24120
|
+
);
|
|
24121
|
+
};
|
|
24122
|
+
resolveExecution = async (command, catalog, directories, bindingAuthorityId, now, signal) => {
|
|
24123
|
+
signal.throwIfAborted();
|
|
24124
|
+
const row = this.#database.prepare(
|
|
24125
|
+
`SELECT * FROM local_bot_grants
|
|
24126
|
+
WHERE local_grant_id = ? AND bot_id = ?
|
|
24127
|
+
AND behavior_revision_id = ? AND behavior_digest = ?
|
|
24128
|
+
AND binding_id = ? AND binding_revision = ?
|
|
24129
|
+
AND agent_ref = ?
|
|
24130
|
+
AND revoked_at IS NULL AND expires_at > ?`
|
|
24131
|
+
).get(
|
|
24132
|
+
command.localGrantId,
|
|
24133
|
+
command.botId,
|
|
24134
|
+
command.behaviorRevisionId,
|
|
24135
|
+
command.behaviorDigest,
|
|
24136
|
+
command.bindingId,
|
|
24137
|
+
command.bindingRevision,
|
|
24138
|
+
command.agentId,
|
|
24139
|
+
now
|
|
24140
|
+
);
|
|
24141
|
+
if (row === void 0) {
|
|
24142
|
+
throw new LocalBotAuthorizationError(
|
|
24143
|
+
"binding_not_approved",
|
|
24144
|
+
"The Bot execution binding is not locally approved"
|
|
24145
|
+
);
|
|
24146
|
+
}
|
|
24147
|
+
const approvedTargets = parseTargets(rowString(row, "allowed_delivery_target_ids_json"));
|
|
24148
|
+
const requestedTargets = command.allowedDeliveryTargets.map((target) => target.deliveryTargetId);
|
|
24149
|
+
if (!exactTargets(approvedTargets, requestedTargets)) {
|
|
24150
|
+
throw new LocalBotAuthorizationError(
|
|
24151
|
+
"binding_not_approved",
|
|
24152
|
+
"The Bot execution target policy changed after local approval"
|
|
24153
|
+
);
|
|
24154
|
+
}
|
|
24155
|
+
const agent = catalog.find(command.agentId);
|
|
24156
|
+
const [adapterUnchanged, agentUnchanged] = await Promise.all([
|
|
24157
|
+
revalidateExecutable(agent.adapterIdentity),
|
|
24158
|
+
revalidateExecutable(agent.agentIdentity)
|
|
24159
|
+
]);
|
|
24160
|
+
signal.throwIfAborted();
|
|
24161
|
+
const workspaceRef = rowString(row, "workspace_ref");
|
|
24162
|
+
const workspacePath = await directories.resolve(bindingAuthorityId, workspaceRef, signal);
|
|
24163
|
+
const currentAgentDigest = agentDigest(agent);
|
|
24164
|
+
const currentWorkspaceDigest = await workspaceDigest(workspacePath);
|
|
24165
|
+
signal.throwIfAborted();
|
|
24166
|
+
if (!adapterUnchanged || !agentUnchanged || currentAgentDigest !== rowString(row, "agent_identity_digest") || currentWorkspaceDigest !== rowString(row, "workspace_identity_digest") || workspacePath !== rowString(row, "workspace_path")) {
|
|
24167
|
+
this.revoke(rowString(row, "local_grant_id"), now);
|
|
24168
|
+
throw new LocalBotAuthorizationError(
|
|
24169
|
+
"binding_not_approved",
|
|
24170
|
+
"The Bot execution target changed and requires local approval"
|
|
24171
|
+
);
|
|
24172
|
+
}
|
|
24173
|
+
if (command.behaviorInstructions.length > 0 && !agent.capabilities.embeddedContextPrompt) {
|
|
24174
|
+
throw new LocalBotAuthorizationError(
|
|
24175
|
+
"binding_incompatible",
|
|
24176
|
+
"The local Agent cannot apply the approved Bot behavior"
|
|
24177
|
+
);
|
|
24178
|
+
}
|
|
24179
|
+
return new LocalBotGrant(
|
|
24180
|
+
rowString(row, "local_grant_id"),
|
|
24181
|
+
command.botId,
|
|
24182
|
+
command.behaviorRevisionId,
|
|
24183
|
+
command.behaviorDigest,
|
|
24184
|
+
command.bindingId,
|
|
24185
|
+
command.bindingRevision,
|
|
24186
|
+
agent,
|
|
24187
|
+
currentAgentDigest,
|
|
24188
|
+
workspaceRef,
|
|
24189
|
+
workspacePath,
|
|
24190
|
+
currentWorkspaceDigest,
|
|
24191
|
+
approvedTargets,
|
|
24192
|
+
rowInteger(row, "expires_at"),
|
|
24193
|
+
rowInteger(row, "revocation_epoch")
|
|
24194
|
+
);
|
|
24195
|
+
};
|
|
24196
|
+
recordUnavailable = (request, code, message, now) => {
|
|
24197
|
+
this.#database.prepare(
|
|
24198
|
+
`INSERT OR IGNORE INTO local_bot_binding_resolutions (
|
|
24199
|
+
request_id, binding_id, local_grant_id, resolved_at,
|
|
24200
|
+
result, error_code, error_message
|
|
24201
|
+
) VALUES (?, ?, '', ?, 'unavailable', ?, ?)`
|
|
24202
|
+
).run(request.requestId, request.bindingId, now, code, message);
|
|
24203
|
+
};
|
|
24204
|
+
close = () => {
|
|
24205
|
+
this.#database.close();
|
|
24206
|
+
};
|
|
24207
|
+
resolveProposal = async (request, catalog, directories, bindingAuthorityId, signal) => {
|
|
24208
|
+
const agent = catalog.find(request.agentRef);
|
|
24209
|
+
if (request.requiredCapabilities.includes("embedded-context") && !agent.capabilities.embeddedContextPrompt) {
|
|
24210
|
+
throw new LocalBotAuthorizationError(
|
|
24211
|
+
"binding_incompatible",
|
|
24212
|
+
"The local Agent cannot apply embedded Bot context"
|
|
24213
|
+
);
|
|
24214
|
+
}
|
|
24215
|
+
if (request.requiredCapabilities.some((capability) => capability !== "embedded-context")) {
|
|
24216
|
+
throw new LocalBotAuthorizationError(
|
|
24217
|
+
"binding_incompatible",
|
|
24218
|
+
"The Bot requires an unsupported local Agent capability"
|
|
24219
|
+
);
|
|
24220
|
+
}
|
|
24221
|
+
const [adapterUnchanged, agentUnchanged] = await Promise.all([
|
|
24222
|
+
revalidateExecutable(agent.adapterIdentity),
|
|
24223
|
+
revalidateExecutable(agent.agentIdentity)
|
|
24224
|
+
]);
|
|
24225
|
+
signal.throwIfAborted();
|
|
24226
|
+
if (!adapterUnchanged || !agentUnchanged) {
|
|
24227
|
+
throw new LocalBotAuthorizationError(
|
|
24228
|
+
"binding_incompatible",
|
|
24229
|
+
"The local Agent executable changed before approval"
|
|
24230
|
+
);
|
|
24231
|
+
}
|
|
24232
|
+
const workspacePath = await directories.resolve(bindingAuthorityId, request.workspaceRef, signal);
|
|
24233
|
+
return {
|
|
24234
|
+
agent,
|
|
24235
|
+
agentIdentityDigest: agentDigest(agent),
|
|
24236
|
+
workspaceIdentityDigest: await workspaceDigest(workspacePath),
|
|
24237
|
+
workspacePath
|
|
24238
|
+
};
|
|
24239
|
+
};
|
|
24240
|
+
revoke = (localGrantId, now) => {
|
|
24241
|
+
this.#database.prepare(
|
|
24242
|
+
`UPDATE local_bot_grants
|
|
24243
|
+
SET revoked_at = COALESCE(revoked_at, ?), revocation_epoch = revocation_epoch + 1
|
|
24244
|
+
WHERE local_grant_id = ? AND revoked_at IS NULL`
|
|
24245
|
+
).run(now, localGrantId);
|
|
24246
|
+
};
|
|
24247
|
+
};
|
|
24248
|
+
|
|
24249
|
+
// src/bots/bot-approve-command.ts
|
|
24250
|
+
var identifier3 = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/);
|
|
24251
|
+
var argumentsSchema = external_exports.union([
|
|
24252
|
+
external_exports.tuple([external_exports.literal("--bot"), identifier3]),
|
|
24253
|
+
external_exports.tuple([external_exports.literal("--bot"), identifier3, external_exports.literal("--channel"), identifier3])
|
|
24254
|
+
]);
|
|
24255
|
+
async function executeBotApproveCommand(arguments_, environment, parent) {
|
|
24256
|
+
const parsed = argumentsSchema.safeParse(arguments_);
|
|
24257
|
+
if (!parsed.success) {
|
|
24258
|
+
throw new LocalBotAuthorizationError(
|
|
24259
|
+
"invalid_arguments",
|
|
24260
|
+
"Usage: mobius bot approve --bot <bot-id> [--channel <channel-id>]"
|
|
24261
|
+
);
|
|
24262
|
+
}
|
|
24263
|
+
const [, botId, , requestedChannel] = parsed.data;
|
|
24264
|
+
const signal = AbortSignal.any([parent, AbortSignal.timeout(3e4)]);
|
|
24265
|
+
signal.throwIfAborted();
|
|
24266
|
+
try {
|
|
24267
|
+
const stateDirectory = await realpath8(readStateDirectory(environment));
|
|
24268
|
+
const auditDirectory = await realpath8(join17(stateDirectory, "audit"));
|
|
24269
|
+
if (dirname4(auditDirectory) !== stateDirectory) {
|
|
24270
|
+
throw new LocalBotAuthorizationError(
|
|
24271
|
+
"local_grant_invalid",
|
|
24272
|
+
"The local approval directory is outside the Mobius state directory"
|
|
24273
|
+
);
|
|
24274
|
+
}
|
|
24275
|
+
const channels = [];
|
|
24276
|
+
for (const entry of await readdir2(auditDirectory, { withFileTypes: true })) {
|
|
24277
|
+
signal.throwIfAborted();
|
|
24278
|
+
if (!entry.isFile() || !entry.name.endsWith(".sqlite")) {
|
|
24279
|
+
continue;
|
|
24280
|
+
}
|
|
24281
|
+
const channelId2 = entry.name.slice(0, -7);
|
|
24282
|
+
if (!identifier3.safeParse(channelId2).success || requestedChannel !== void 0 && requestedChannel !== channelId2) {
|
|
24283
|
+
continue;
|
|
24284
|
+
}
|
|
24285
|
+
const databasePath = await realpath8(join17(auditDirectory, entry.name));
|
|
24286
|
+
if (dirname4(databasePath) !== auditDirectory) {
|
|
24287
|
+
throw new LocalBotAuthorizationError(
|
|
24288
|
+
"local_grant_invalid",
|
|
24289
|
+
"The local approval database is outside the Mobius audit directory"
|
|
24290
|
+
);
|
|
24291
|
+
}
|
|
24292
|
+
if (LocalBotGrantStore.containsBot(databasePath, botId)) {
|
|
24293
|
+
channels.push(channelId2);
|
|
24294
|
+
}
|
|
24295
|
+
}
|
|
24296
|
+
const channelId = channels[0];
|
|
24297
|
+
if (channelId === void 0) {
|
|
24298
|
+
throw new LocalBotAuthorizationError(
|
|
24299
|
+
"bot_not_found",
|
|
24300
|
+
"No saved approval for this Bot was found. Run this command on the machine hosting the Bot."
|
|
24301
|
+
);
|
|
24302
|
+
}
|
|
24303
|
+
if (channels.length !== 1) {
|
|
24304
|
+
throw new LocalBotAuthorizationError(
|
|
24305
|
+
"binding_ambiguous",
|
|
24306
|
+
`This Bot has approvals in multiple local channels. Add --channel with the intended channel: ${channels.join(", ")}`
|
|
24307
|
+
);
|
|
24308
|
+
}
|
|
24309
|
+
const { config: config2 } = await resumeMobiusRuntime(channelId, signal, environment);
|
|
24310
|
+
signal.throwIfAborted();
|
|
24311
|
+
const grants = new LocalBotGrantStore(config2.auditDatabasePath);
|
|
24312
|
+
try {
|
|
24313
|
+
const approval = await grants.reapprove(
|
|
24314
|
+
botId,
|
|
24315
|
+
config2.catalog,
|
|
24316
|
+
new WorkspaceDirectoryService(config2.bindingId, config2.defaultWorkspace),
|
|
24317
|
+
config2.bindingId,
|
|
24318
|
+
Date.now(),
|
|
24319
|
+
signal
|
|
24320
|
+
);
|
|
24321
|
+
return `Approved Bot ${botId}
|
|
24322
|
+
Binding: ${approval.bindingId}
|
|
24323
|
+
Agent: ${approval.agentRef}
|
|
24324
|
+
Workspace: ${approval.workspacePath}
|
|
24325
|
+
Keep your daemon running and resend your message in the same Chat conversation.
|
|
24326
|
+
`;
|
|
24327
|
+
} finally {
|
|
24328
|
+
grants.close();
|
|
24329
|
+
}
|
|
24330
|
+
} catch (cause) {
|
|
24331
|
+
parent.throwIfAborted();
|
|
24332
|
+
if (signal.aborted) {
|
|
24333
|
+
throw new LocalBotAuthorizationError("approval_timeout", "Local Bot approval timed out", { cause });
|
|
24334
|
+
}
|
|
24335
|
+
if (cause instanceof LocalBotAuthorizationError) {
|
|
24336
|
+
throw cause;
|
|
24337
|
+
}
|
|
24338
|
+
if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") {
|
|
24339
|
+
throw new LocalBotAuthorizationError(
|
|
24340
|
+
"bot_not_found",
|
|
24341
|
+
"No saved local approval or Runtime connection was found for this Bot",
|
|
24342
|
+
{ cause }
|
|
24343
|
+
);
|
|
24344
|
+
}
|
|
24345
|
+
throw new LocalBotAuthorizationError(
|
|
24346
|
+
"local_approval_failed",
|
|
24347
|
+
"Local Bot approval failed. Check the saved Runtime connection and local Agent installation.",
|
|
24348
|
+
{ cause }
|
|
24349
|
+
);
|
|
24350
|
+
}
|
|
24351
|
+
}
|
|
24352
|
+
|
|
24353
|
+
// src/bots/bot-message-command.ts
|
|
24354
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
24355
|
+
import { createConnection } from "node:net";
|
|
24356
|
+
var BOT_IPC_RESPONSE_LIMIT = 64 * 1024;
|
|
24357
|
+
var BOT_IPC_TIMEOUT_MILLISECONDS = 2e4;
|
|
24358
|
+
var BotMessageCommandError = class extends Error {
|
|
24359
|
+
code;
|
|
24360
|
+
constructor(code, message, options = {}) {
|
|
24361
|
+
super(message, options);
|
|
24362
|
+
this.code = code;
|
|
24363
|
+
this.name = "BotMessageCommandError";
|
|
24364
|
+
}
|
|
24365
|
+
};
|
|
24366
|
+
function isRecord7(value) {
|
|
24367
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24368
|
+
}
|
|
24369
|
+
function requiredEnvironment(environment, name) {
|
|
24370
|
+
const value = environment[name]?.trim();
|
|
24371
|
+
if (value === void 0 || value.length === 0) {
|
|
24372
|
+
throw new BotMessageCommandError(
|
|
24373
|
+
"execution_context_required",
|
|
24374
|
+
"This command is available only inside an active Mobius Bot execution"
|
|
24375
|
+
);
|
|
24376
|
+
}
|
|
24377
|
+
return value;
|
|
24378
|
+
}
|
|
24379
|
+
function option(arguments_, name) {
|
|
24380
|
+
const index = arguments_.indexOf(name);
|
|
24381
|
+
if (index < 0 || index + 1 >= arguments_.length || arguments_[index + 1]?.startsWith("--") === true) {
|
|
24382
|
+
throw new BotMessageCommandError(
|
|
24383
|
+
"invalid_request",
|
|
24384
|
+
"Usage: mobius bot message send --target <current|alias> --text <message>"
|
|
24385
|
+
);
|
|
24386
|
+
}
|
|
24387
|
+
const value = arguments_[index + 1];
|
|
24388
|
+
if (value === void 0) {
|
|
24389
|
+
throw new BotMessageCommandError("invalid_request", "The Bot message option is missing");
|
|
24390
|
+
}
|
|
24391
|
+
return value;
|
|
24392
|
+
}
|
|
24393
|
+
function validateArguments(arguments_) {
|
|
24394
|
+
if (arguments_.length !== 4) {
|
|
24395
|
+
throw new BotMessageCommandError(
|
|
24396
|
+
"invalid_request",
|
|
24397
|
+
"Usage: mobius bot message send --target <current|alias> --text <message>"
|
|
24398
|
+
);
|
|
24399
|
+
}
|
|
24400
|
+
const names = [arguments_[0], arguments_[2]];
|
|
24401
|
+
if (!names.includes("--target") || !names.includes("--text") || names[0] === names[1]) {
|
|
24402
|
+
throw new BotMessageCommandError(
|
|
24403
|
+
"invalid_request",
|
|
24404
|
+
"Usage: mobius bot message send --target <current|alias> --text <message>"
|
|
24405
|
+
);
|
|
24406
|
+
}
|
|
24407
|
+
}
|
|
24408
|
+
function decodeResponse(encoded) {
|
|
24409
|
+
let value;
|
|
24410
|
+
try {
|
|
24411
|
+
value = JSON.parse(encoded);
|
|
24412
|
+
} catch (cause) {
|
|
24413
|
+
throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data", {
|
|
24414
|
+
cause
|
|
24415
|
+
});
|
|
24416
|
+
}
|
|
24417
|
+
if (!isRecord7(value) || typeof value["ok"] !== "boolean") {
|
|
24418
|
+
throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data");
|
|
24419
|
+
}
|
|
24420
|
+
if (value["ok"] === true && typeof value["outboundMessageId"] === "string") {
|
|
24421
|
+
return value["outboundMessageId"];
|
|
24422
|
+
}
|
|
24423
|
+
const error61 = value["error"];
|
|
24424
|
+
if (value["ok"] === false && isRecord7(error61) && typeof error61["code"] === "string" && typeof error61["message"] === "string") {
|
|
24425
|
+
throw new BotMessageCommandError(error61["code"], error61["message"]);
|
|
24426
|
+
}
|
|
24427
|
+
throw new BotMessageCommandError("bot_service_unavailable", "The local Bot service returned invalid data");
|
|
24428
|
+
}
|
|
24429
|
+
async function requestLocalBotService(socketPath, payload, signal) {
|
|
24430
|
+
signal.throwIfAborted();
|
|
24431
|
+
return await new Promise((resolve5, reject) => {
|
|
24432
|
+
const socket = createConnection(socketPath);
|
|
24433
|
+
const chunks = [];
|
|
24434
|
+
let length = 0;
|
|
24435
|
+
let finished = false;
|
|
24436
|
+
const finish = () => {
|
|
24437
|
+
socket.removeAllListeners();
|
|
24438
|
+
socket.destroy();
|
|
24439
|
+
};
|
|
24440
|
+
const fail = (error61) => {
|
|
24441
|
+
if (finished) {
|
|
24442
|
+
return;
|
|
24443
|
+
}
|
|
24444
|
+
finished = true;
|
|
24445
|
+
finish();
|
|
24446
|
+
if (error61 instanceof BotMessageCommandError) {
|
|
24447
|
+
reject(error61);
|
|
24448
|
+
return;
|
|
24449
|
+
}
|
|
24450
|
+
reject(
|
|
24451
|
+
new BotMessageCommandError(
|
|
24452
|
+
"execution_context_required",
|
|
24453
|
+
"The active Mobius Bot execution is unavailable",
|
|
24454
|
+
{ cause: error61 }
|
|
24455
|
+
)
|
|
24456
|
+
);
|
|
24457
|
+
};
|
|
24458
|
+
const abort = () => fail(signal.reason);
|
|
24459
|
+
socket.setTimeout(BOT_IPC_TIMEOUT_MILLISECONDS, () => {
|
|
24460
|
+
fail(new BotMessageCommandError("bot_service_unavailable", "The local Bot service timed out"));
|
|
24461
|
+
});
|
|
24462
|
+
socket.once("error", fail);
|
|
24463
|
+
socket.once("connect", () => socket.end(`${payload}
|
|
24464
|
+
`));
|
|
24465
|
+
socket.on("data", (chunk) => {
|
|
24466
|
+
length += chunk.byteLength;
|
|
24467
|
+
if (length > BOT_IPC_RESPONSE_LIMIT) {
|
|
24468
|
+
fail(new BotMessageCommandError("bot_service_unavailable", "The local Bot response is too large"));
|
|
24469
|
+
return;
|
|
24470
|
+
}
|
|
24471
|
+
chunks.push(chunk);
|
|
24472
|
+
});
|
|
24473
|
+
socket.once("end", () => {
|
|
24474
|
+
if (finished) {
|
|
24475
|
+
return;
|
|
24476
|
+
}
|
|
24477
|
+
finished = true;
|
|
24478
|
+
signal.removeEventListener("abort", abort);
|
|
24479
|
+
try {
|
|
24480
|
+
resolve5(decodeResponse(Buffer.concat(chunks).toString("utf8")));
|
|
24481
|
+
} catch (error61) {
|
|
24482
|
+
reject(error61);
|
|
24483
|
+
} finally {
|
|
24484
|
+
finish();
|
|
24485
|
+
}
|
|
24486
|
+
});
|
|
24487
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
24488
|
+
});
|
|
24489
|
+
}
|
|
24490
|
+
async function executeBotMessageCommand(arguments_, environment, signal) {
|
|
24491
|
+
validateArguments(arguments_);
|
|
24492
|
+
const contextHandle = requiredEnvironment(environment, "MOBIUS_BOT_CONTEXT");
|
|
24493
|
+
const socketPath = requiredEnvironment(environment, "MOBIUS_BOT_SOCKET");
|
|
24494
|
+
const target = option(arguments_, "--target");
|
|
24495
|
+
const text = option(arguments_, "--text");
|
|
24496
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/.test(target) || text.trim().length === 0 || text.length > 1e4) {
|
|
24497
|
+
throw new BotMessageCommandError("invalid_request", "The Bot outbound message is invalid");
|
|
24498
|
+
}
|
|
24499
|
+
return await requestLocalBotService(
|
|
24500
|
+
socketPath,
|
|
24501
|
+
JSON.stringify({
|
|
24502
|
+
contextHandle,
|
|
24503
|
+
idempotencyKey: `cli_${randomUUID3()}`,
|
|
24504
|
+
target,
|
|
24505
|
+
text
|
|
24506
|
+
}),
|
|
24507
|
+
signal
|
|
24508
|
+
);
|
|
24509
|
+
}
|
|
24510
|
+
|
|
23663
24511
|
// src/audit/audit-store.ts
|
|
23664
24512
|
import { mkdirSync } from "node:fs";
|
|
23665
|
-
import { dirname as
|
|
23666
|
-
import { DatabaseSync } from "node:sqlite";
|
|
24513
|
+
import { dirname as dirname5 } from "node:path";
|
|
24514
|
+
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
23667
24515
|
var AuditStore = class {
|
|
23668
24516
|
database;
|
|
23669
24517
|
constructor(path2) {
|
|
23670
|
-
mkdirSync(
|
|
23671
|
-
this.database = new
|
|
24518
|
+
mkdirSync(dirname5(path2), { recursive: true });
|
|
24519
|
+
this.database = new DatabaseSync2(path2);
|
|
23672
24520
|
this.database.exec(`
|
|
23673
24521
|
CREATE TABLE IF NOT EXISTS audit_events (
|
|
23674
24522
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -23710,7 +24558,7 @@ var AuditStore = class {
|
|
|
23710
24558
|
|
|
23711
24559
|
// src/run-daemon.ts
|
|
23712
24560
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
23713
|
-
import { basename as basename5, join as
|
|
24561
|
+
import { basename as basename5, join as join18 } from "node:path";
|
|
23714
24562
|
import { styleText } from "node:util";
|
|
23715
24563
|
import { fetch as fetch7, WebSocket as WebSocket2 } from "undici";
|
|
23716
24564
|
|
|
@@ -23724,8 +24572,8 @@ import {
|
|
|
23724
24572
|
} from "@agentclientprotocol/sdk";
|
|
23725
24573
|
|
|
23726
24574
|
// src/authorization/local-raster.ts
|
|
23727
|
-
import { readFile, realpath as
|
|
23728
|
-
import { basename as
|
|
24575
|
+
import { readFile, realpath as realpath9, stat as stat8 } from "node:fs/promises";
|
|
24576
|
+
import { basename as basename3, isAbsolute as isAbsolute9, normalize as normalize3, relative as relative5, sep as sep4 } from "node:path";
|
|
23729
24577
|
import { fileURLToPath } from "node:url";
|
|
23730
24578
|
var MAX_LOCAL_RASTER_BYTES = 3e6;
|
|
23731
24579
|
var PNG_SIGNATURE = Uint8Array.of(137, 80, 78, 71);
|
|
@@ -23767,18 +24615,18 @@ function sniffRasterMime(bytes) {
|
|
|
23767
24615
|
return null;
|
|
23768
24616
|
}
|
|
23769
24617
|
function isInsideRoot(root, target) {
|
|
23770
|
-
const relation =
|
|
23771
|
-
return relation !== ".." && !relation.startsWith(`..${sep4}`) && !
|
|
24618
|
+
const relation = relative5(root, target);
|
|
24619
|
+
return relation !== ".." && !relation.startsWith(`..${sep4}`) && !isAbsolute9(relation);
|
|
23772
24620
|
}
|
|
23773
24621
|
async function resolveCanonicalPath(requested, signal) {
|
|
23774
24622
|
signal.throwIfAborted();
|
|
23775
|
-
if (!
|
|
24623
|
+
if (!isAbsolute9(requested) || requested.includes("\0") || normalize3(requested) !== requested) {
|
|
23776
24624
|
return null;
|
|
23777
24625
|
}
|
|
23778
24626
|
try {
|
|
23779
|
-
const target = await
|
|
24627
|
+
const target = await realpath9(requested);
|
|
23780
24628
|
signal.throwIfAborted();
|
|
23781
|
-
if (!
|
|
24629
|
+
if (!isAbsolute9(target) || normalize3(target) !== target) {
|
|
23782
24630
|
return null;
|
|
23783
24631
|
}
|
|
23784
24632
|
return target;
|
|
@@ -23824,7 +24672,7 @@ async function readAuthorizedRaster(uri, roots, signal) {
|
|
|
23824
24672
|
if (target === null) {
|
|
23825
24673
|
return null;
|
|
23826
24674
|
}
|
|
23827
|
-
const targetMetadata = await
|
|
24675
|
+
const targetMetadata = await stat8(target);
|
|
23828
24676
|
signal.throwIfAborted();
|
|
23829
24677
|
if (!targetMetadata.isFile() || targetMetadata.size < 1 || targetMetadata.size > MAX_LOCAL_RASTER_BYTES) {
|
|
23830
24678
|
return null;
|
|
@@ -23835,7 +24683,7 @@ async function readAuthorizedRaster(uri, roots, signal) {
|
|
|
23835
24683
|
if (directory === null) {
|
|
23836
24684
|
continue;
|
|
23837
24685
|
}
|
|
23838
|
-
const metadata = await
|
|
24686
|
+
const metadata = await stat8(directory);
|
|
23839
24687
|
signal.throwIfAborted();
|
|
23840
24688
|
if (metadata.isDirectory()) {
|
|
23841
24689
|
authorizedRoots.push(directory);
|
|
@@ -23853,7 +24701,7 @@ async function readAuthorizedRaster(uri, roots, signal) {
|
|
|
23853
24701
|
if (mimeType === null) {
|
|
23854
24702
|
return null;
|
|
23855
24703
|
}
|
|
23856
|
-
return new LocalRasterImage(
|
|
24704
|
+
return new LocalRasterImage(basename3(target) || "Image", mimeType, bytes);
|
|
23857
24705
|
}
|
|
23858
24706
|
|
|
23859
24707
|
// src/native-sessions/history-images.ts
|
|
@@ -24928,8 +25776,8 @@ var AcpHistoryReplay = class {
|
|
|
24928
25776
|
|
|
24929
25777
|
// src/acp/session-store.ts
|
|
24930
25778
|
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
24931
|
-
import { dirname as
|
|
24932
|
-
import { DatabaseSync as
|
|
25779
|
+
import { dirname as dirname6 } from "node:path";
|
|
25780
|
+
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
24933
25781
|
var AcpStoredSession = class {
|
|
24934
25782
|
additionalDirectories;
|
|
24935
25783
|
agentId;
|
|
@@ -25031,8 +25879,8 @@ var MemoryAcpSessionStore = class {
|
|
|
25031
25879
|
var SqliteAcpSessionStore = class {
|
|
25032
25880
|
#database;
|
|
25033
25881
|
constructor(path2) {
|
|
25034
|
-
mkdirSync2(
|
|
25035
|
-
this.#database = new
|
|
25882
|
+
mkdirSync2(dirname6(path2), { recursive: true });
|
|
25883
|
+
this.#database = new DatabaseSync3(path2);
|
|
25036
25884
|
this.#database.exec(`
|
|
25037
25885
|
CREATE TABLE IF NOT EXISTS acp_deleted_sessions (session_id TEXT PRIMARY KEY) STRICT;
|
|
25038
25886
|
CREATE TABLE IF NOT EXISTS acp_sessions (
|
|
@@ -27901,7 +28749,7 @@ var FixedInstallationExecutor = class {
|
|
|
27901
28749
|
};
|
|
27902
28750
|
|
|
27903
28751
|
// src/artifacts/runtime-artifact-uploader.ts
|
|
27904
|
-
import { createHash as
|
|
28752
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
27905
28753
|
|
|
27906
28754
|
// ../mobius-sdk/src/errors.ts
|
|
27907
28755
|
var MobiusError = class extends Error {
|
|
@@ -28228,7 +29076,7 @@ async function uploadRuntimeArtifact(config2, sessionId, image, signal) {
|
|
|
28228
29076
|
image.name,
|
|
28229
29077
|
image.mimeType,
|
|
28230
29078
|
image.bytes.byteLength,
|
|
28231
|
-
|
|
29079
|
+
createHash3("sha256").update(image.bytes).digest("hex")
|
|
28232
29080
|
);
|
|
28233
29081
|
} catch (cause) {
|
|
28234
29082
|
translateSdkError(cause);
|
|
@@ -28296,508 +29144,6 @@ async function uploadRuntimeArtifact(config2, sessionId, image, signal) {
|
|
|
28296
29144
|
}
|
|
28297
29145
|
}
|
|
28298
29146
|
|
|
28299
|
-
// src/authorization/local-authorization-error.ts
|
|
28300
|
-
var LocalAuthorizationError = class extends Error {
|
|
28301
|
-
code = "local_authorization_failed";
|
|
28302
|
-
constructor(message, options = {}) {
|
|
28303
|
-
super(message, options);
|
|
28304
|
-
this.name = "LocalAuthorizationError";
|
|
28305
|
-
}
|
|
28306
|
-
};
|
|
28307
|
-
|
|
28308
|
-
// src/authorization/workspace-directory.ts
|
|
28309
|
-
import { readdir, realpath as realpath8, stat as stat7 } from "node:fs/promises";
|
|
28310
|
-
import { homedir as homedir2 } from "node:os";
|
|
28311
|
-
import { basename as basename3, dirname as dirname5, isAbsolute as isAbsolute8, join as join15, normalize as normalize3, relative as relative5 } from "node:path";
|
|
28312
|
-
var WorkspaceDirectoryListing = class {
|
|
28313
|
-
directoryId;
|
|
28314
|
-
directoryLabel;
|
|
28315
|
-
entries;
|
|
28316
|
-
parentDirectoryId;
|
|
28317
|
-
constructor(directoryId2, directoryLabel, entries, parentDirectoryId) {
|
|
28318
|
-
this.directoryId = directoryId2;
|
|
28319
|
-
this.directoryLabel = directoryLabel;
|
|
28320
|
-
this.entries = entries;
|
|
28321
|
-
this.parentDirectoryId = parentDirectoryId;
|
|
28322
|
-
}
|
|
28323
|
-
};
|
|
28324
|
-
function encodePath(path2) {
|
|
28325
|
-
const encoded = Buffer.from(path2, "utf8").toString("base64url");
|
|
28326
|
-
if (encoded.length === 0 || encoded.length > 2e3) {
|
|
28327
|
-
throw new LocalAuthorizationError("The local directory path is too long to select");
|
|
28328
|
-
}
|
|
28329
|
-
return `path-${encoded}`;
|
|
28330
|
-
}
|
|
28331
|
-
function decodePath(value, prefix) {
|
|
28332
|
-
const encoded = value.slice(prefix.length);
|
|
28333
|
-
if (encoded.length === 0 || encoded.length > 2e3 || !/^[a-zA-Z0-9_-]+$/.test(encoded)) {
|
|
28334
|
-
throw new LocalAuthorizationError("The directory identifier is invalid");
|
|
28335
|
-
}
|
|
28336
|
-
const decoded = Buffer.from(encoded, "base64url").toString("utf8");
|
|
28337
|
-
if (Buffer.from(decoded, "utf8").toString("base64url") !== encoded || decoded.includes("\0") || normalize3(decoded) !== decoded) {
|
|
28338
|
-
throw new LocalAuthorizationError("The directory identifier is invalid");
|
|
28339
|
-
}
|
|
28340
|
-
return decoded;
|
|
28341
|
-
}
|
|
28342
|
-
var WorkspaceDirectoryService = class {
|
|
28343
|
-
#bindingId;
|
|
28344
|
-
#defaultWorkspace;
|
|
28345
|
-
constructor(bindingId, defaultWorkspace) {
|
|
28346
|
-
this.#bindingId = bindingId;
|
|
28347
|
-
this.#defaultWorkspace = defaultWorkspace;
|
|
28348
|
-
}
|
|
28349
|
-
authorizeMachine = (bindingId) => {
|
|
28350
|
-
if (bindingId !== this.#bindingId) {
|
|
28351
|
-
throw new LocalAuthorizationError("The runtime binding is not authorized");
|
|
28352
|
-
}
|
|
28353
|
-
};
|
|
28354
|
-
requestedPath = (id) => {
|
|
28355
|
-
if (id === "default") {
|
|
28356
|
-
return this.#defaultWorkspace;
|
|
28357
|
-
}
|
|
28358
|
-
if (id === "home") {
|
|
28359
|
-
return homedir2();
|
|
28360
|
-
}
|
|
28361
|
-
if (id.startsWith("path-")) {
|
|
28362
|
-
const path2 = decodePath(id, "path-");
|
|
28363
|
-
if (isAbsolute8(path2)) {
|
|
28364
|
-
return path2;
|
|
28365
|
-
}
|
|
28366
|
-
}
|
|
28367
|
-
throw new LocalAuthorizationError("The directory identifier is invalid");
|
|
28368
|
-
};
|
|
28369
|
-
resolvePath = async (requested, signal) => {
|
|
28370
|
-
signal.throwIfAborted();
|
|
28371
|
-
if (!isAbsolute8(requested) || requested.includes("\0") || normalize3(requested) !== requested) {
|
|
28372
|
-
throw new LocalAuthorizationError("The local directory path is invalid");
|
|
28373
|
-
}
|
|
28374
|
-
try {
|
|
28375
|
-
const target = await realpath8(requested);
|
|
28376
|
-
signal.throwIfAborted();
|
|
28377
|
-
const metadata = await stat7(target);
|
|
28378
|
-
signal.throwIfAborted();
|
|
28379
|
-
if (!isAbsolute8(target) || normalize3(target) !== target || !metadata.isDirectory()) {
|
|
28380
|
-
throw new LocalAuthorizationError("The selected location is not a directory");
|
|
28381
|
-
}
|
|
28382
|
-
return target;
|
|
28383
|
-
} catch (cause) {
|
|
28384
|
-
signal.throwIfAborted();
|
|
28385
|
-
if (cause instanceof LocalAuthorizationError) {
|
|
28386
|
-
throw cause;
|
|
28387
|
-
}
|
|
28388
|
-
throw new LocalAuthorizationError("This directory is missing or unavailable to your user account", {
|
|
28389
|
-
cause
|
|
28390
|
-
});
|
|
28391
|
-
}
|
|
28392
|
-
};
|
|
28393
|
-
label = (path2) => {
|
|
28394
|
-
const suffix = relative5(homedir2(), path2);
|
|
28395
|
-
return suffix === "" ? "~" : !isAbsolute8(suffix) && suffix !== ".." && !suffix.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) ? `~/${suffix.replaceAll("\\", "/")}` : path2;
|
|
28396
|
-
};
|
|
28397
|
-
identify = async (bindingId, requestedPath, signal) => {
|
|
28398
|
-
this.authorizeMachine(bindingId);
|
|
28399
|
-
const path2 = await this.resolvePath(requestedPath, signal);
|
|
28400
|
-
return {
|
|
28401
|
-
path: path2,
|
|
28402
|
-
directoryId: encodePath(path2),
|
|
28403
|
-
name: basename3(path2) || path2,
|
|
28404
|
-
label: this.label(path2)
|
|
28405
|
-
};
|
|
28406
|
-
};
|
|
28407
|
-
resolve = async (bindingId, id, signal) => {
|
|
28408
|
-
this.authorizeMachine(bindingId);
|
|
28409
|
-
return await this.resolvePath(this.requestedPath(id), signal);
|
|
28410
|
-
};
|
|
28411
|
-
list = async (bindingId, id, signal) => {
|
|
28412
|
-
const target = await this.resolve(bindingId, id, signal);
|
|
28413
|
-
try {
|
|
28414
|
-
const children = await readdir(target, { withFileTypes: true });
|
|
28415
|
-
signal.throwIfAborted();
|
|
28416
|
-
const entries = [];
|
|
28417
|
-
for (const child of children) {
|
|
28418
|
-
if (entries.length === 500) {
|
|
28419
|
-
break;
|
|
28420
|
-
}
|
|
28421
|
-
if (child.isFile() || child.isBlockDevice() || child.isCharacterDevice() || child.isFIFO() || child.isSocket()) {
|
|
28422
|
-
continue;
|
|
28423
|
-
}
|
|
28424
|
-
try {
|
|
28425
|
-
const path2 = await this.resolvePath(join15(target, child.name), signal);
|
|
28426
|
-
entries.push({ directoryId: encodePath(path2), name: child.name });
|
|
28427
|
-
} catch (cause) {
|
|
28428
|
-
signal.throwIfAborted();
|
|
28429
|
-
if (!(cause instanceof LocalAuthorizationError)) {
|
|
28430
|
-
throw cause;
|
|
28431
|
-
}
|
|
28432
|
-
}
|
|
28433
|
-
}
|
|
28434
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
28435
|
-
const parent = dirname5(target);
|
|
28436
|
-
return new WorkspaceDirectoryListing(
|
|
28437
|
-
id,
|
|
28438
|
-
this.label(target),
|
|
28439
|
-
entries,
|
|
28440
|
-
parent === target ? "" : encodePath(parent)
|
|
28441
|
-
);
|
|
28442
|
-
} catch (cause) {
|
|
28443
|
-
signal.throwIfAborted();
|
|
28444
|
-
if (cause instanceof LocalAuthorizationError) {
|
|
28445
|
-
throw cause;
|
|
28446
|
-
}
|
|
28447
|
-
throw new LocalAuthorizationError("This directory cannot be browsed by your user account", { cause });
|
|
28448
|
-
}
|
|
28449
|
-
};
|
|
28450
|
-
resolveMany = async (bindingId, ids, signal) => {
|
|
28451
|
-
const paths = /* @__PURE__ */ new Set();
|
|
28452
|
-
for (const id of ids) {
|
|
28453
|
-
paths.add(await this.resolve(bindingId, id, signal));
|
|
28454
|
-
}
|
|
28455
|
-
return [...paths];
|
|
28456
|
-
};
|
|
28457
|
-
};
|
|
28458
|
-
|
|
28459
|
-
// src/bots/local-bot-grant-store.ts
|
|
28460
|
-
import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
|
|
28461
|
-
import { stat as stat8 } from "node:fs/promises";
|
|
28462
|
-
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
28463
|
-
var LOCAL_BOT_GRANT_LIFETIME_MILLISECONDS = 90 * 24 * 60 * 60 * 1e3;
|
|
28464
|
-
var LocalBotAuthorizationError = class extends Error {
|
|
28465
|
-
code;
|
|
28466
|
-
constructor(code, message, options = {}) {
|
|
28467
|
-
super(message, options);
|
|
28468
|
-
this.code = code;
|
|
28469
|
-
this.name = "LocalBotAuthorizationError";
|
|
28470
|
-
}
|
|
28471
|
-
};
|
|
28472
|
-
var LocalBotGrant = class {
|
|
28473
|
-
agent;
|
|
28474
|
-
agentIdentityDigest;
|
|
28475
|
-
allowedDeliveryTargetIds;
|
|
28476
|
-
behaviorDigest;
|
|
28477
|
-
behaviorRevisionId;
|
|
28478
|
-
bindingId;
|
|
28479
|
-
bindingRevision;
|
|
28480
|
-
botId;
|
|
28481
|
-
expiresAt;
|
|
28482
|
-
localGrantId;
|
|
28483
|
-
revocationEpoch;
|
|
28484
|
-
workspaceIdentityDigest;
|
|
28485
|
-
workspacePath;
|
|
28486
|
-
workspaceRef;
|
|
28487
|
-
constructor(localGrantId, botId, behaviorRevisionId, behaviorDigest, bindingId, bindingRevision, agent, agentIdentityDigest, workspaceRef, workspacePath, workspaceIdentityDigest, allowedDeliveryTargetIds, expiresAt, revocationEpoch) {
|
|
28488
|
-
this.localGrantId = localGrantId;
|
|
28489
|
-
this.botId = botId;
|
|
28490
|
-
this.behaviorRevisionId = behaviorRevisionId;
|
|
28491
|
-
this.behaviorDigest = behaviorDigest;
|
|
28492
|
-
this.bindingId = bindingId;
|
|
28493
|
-
this.bindingRevision = bindingRevision;
|
|
28494
|
-
this.agent = agent;
|
|
28495
|
-
this.agentIdentityDigest = agentIdentityDigest;
|
|
28496
|
-
this.workspaceRef = workspaceRef;
|
|
28497
|
-
this.workspacePath = workspacePath;
|
|
28498
|
-
this.workspaceIdentityDigest = workspaceIdentityDigest;
|
|
28499
|
-
this.allowedDeliveryTargetIds = Object.freeze([...allowedDeliveryTargetIds]);
|
|
28500
|
-
this.expiresAt = expiresAt;
|
|
28501
|
-
this.revocationEpoch = revocationEpoch;
|
|
28502
|
-
}
|
|
28503
|
-
};
|
|
28504
|
-
function digest(value) {
|
|
28505
|
-
return createHash3("sha256").update(value, "utf8").digest("hex");
|
|
28506
|
-
}
|
|
28507
|
-
function agentDigest(agent) {
|
|
28508
|
-
return digest(
|
|
28509
|
-
JSON.stringify({
|
|
28510
|
-
adapter: agent.adapterIdentity,
|
|
28511
|
-
agent: agent.agentIdentity,
|
|
28512
|
-
agentVersion: agent.agentVersion,
|
|
28513
|
-
arguments: agent.arguments,
|
|
28514
|
-
id: agent.id
|
|
28515
|
-
})
|
|
28516
|
-
);
|
|
28517
|
-
}
|
|
28518
|
-
async function workspaceDigest(path2) {
|
|
28519
|
-
const metadata = await stat8(path2);
|
|
28520
|
-
if (!metadata.isDirectory()) {
|
|
28521
|
-
throw new LocalBotAuthorizationError("binding_incompatible", "The Bot workspace is not a directory");
|
|
28522
|
-
}
|
|
28523
|
-
return digest(
|
|
28524
|
-
JSON.stringify({
|
|
28525
|
-
changedAtMilliseconds: metadata.ctimeMs,
|
|
28526
|
-
device: metadata.dev,
|
|
28527
|
-
inode: metadata.ino,
|
|
28528
|
-
path: path2
|
|
28529
|
-
})
|
|
28530
|
-
);
|
|
28531
|
-
}
|
|
28532
|
-
function exactTargets(left, right) {
|
|
28533
|
-
if (left.length !== right.length) {
|
|
28534
|
-
return false;
|
|
28535
|
-
}
|
|
28536
|
-
const expected = [...left].sort();
|
|
28537
|
-
const actual = [...right].sort();
|
|
28538
|
-
return expected.every((target, index) => target === actual[index]);
|
|
28539
|
-
}
|
|
28540
|
-
function rowString(row, key) {
|
|
28541
|
-
const value = Reflect.get(row, key);
|
|
28542
|
-
if (typeof value !== "string") {
|
|
28543
|
-
throw new LocalBotAuthorizationError("local_grant_invalid", "The local Bot grant is invalid");
|
|
28544
|
-
}
|
|
28545
|
-
return value;
|
|
28546
|
-
}
|
|
28547
|
-
function rowInteger(row, key) {
|
|
28548
|
-
const value = Reflect.get(row, key);
|
|
28549
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
|
28550
|
-
throw new LocalBotAuthorizationError("local_grant_invalid", "The local Bot grant is invalid");
|
|
28551
|
-
}
|
|
28552
|
-
return value;
|
|
28553
|
-
}
|
|
28554
|
-
function parseTargets(encoded) {
|
|
28555
|
-
let value;
|
|
28556
|
-
try {
|
|
28557
|
-
value = JSON.parse(encoded);
|
|
28558
|
-
} catch (cause) {
|
|
28559
|
-
throw new LocalBotAuthorizationError("local_grant_invalid", "The local Bot grant is invalid", { cause });
|
|
28560
|
-
}
|
|
28561
|
-
if (!Array.isArray(value) || value.some((target) => typeof target !== "string")) {
|
|
28562
|
-
throw new LocalBotAuthorizationError("local_grant_invalid", "The local Bot grant is invalid");
|
|
28563
|
-
}
|
|
28564
|
-
return value.filter((target) => typeof target === "string");
|
|
28565
|
-
}
|
|
28566
|
-
var LocalBotGrantStore = class {
|
|
28567
|
-
#database;
|
|
28568
|
-
constructor(path2) {
|
|
28569
|
-
this.#database = new DatabaseSync3(path2);
|
|
28570
|
-
this.#database.exec(`
|
|
28571
|
-
CREATE TABLE IF NOT EXISTS local_bot_grants (
|
|
28572
|
-
local_grant_id TEXT PRIMARY KEY,
|
|
28573
|
-
bot_id TEXT NOT NULL,
|
|
28574
|
-
behavior_revision_id TEXT NOT NULL,
|
|
28575
|
-
behavior_digest TEXT NOT NULL,
|
|
28576
|
-
binding_id TEXT NOT NULL UNIQUE,
|
|
28577
|
-
binding_revision INTEGER NOT NULL,
|
|
28578
|
-
agent_ref TEXT NOT NULL,
|
|
28579
|
-
agent_identity_digest TEXT NOT NULL,
|
|
28580
|
-
workspace_ref TEXT NOT NULL,
|
|
28581
|
-
workspace_path TEXT NOT NULL,
|
|
28582
|
-
workspace_identity_digest TEXT NOT NULL,
|
|
28583
|
-
allowed_delivery_target_ids_json TEXT NOT NULL,
|
|
28584
|
-
unattended_policy_json TEXT NOT NULL,
|
|
28585
|
-
created_at INTEGER NOT NULL,
|
|
28586
|
-
expires_at INTEGER NOT NULL,
|
|
28587
|
-
revocation_epoch INTEGER NOT NULL DEFAULT 0,
|
|
28588
|
-
revoked_at INTEGER
|
|
28589
|
-
) STRICT;
|
|
28590
|
-
CREATE TABLE IF NOT EXISTS local_bot_binding_resolutions (
|
|
28591
|
-
request_id TEXT PRIMARY KEY,
|
|
28592
|
-
binding_id TEXT NOT NULL,
|
|
28593
|
-
local_grant_id TEXT NOT NULL,
|
|
28594
|
-
resolved_at INTEGER NOT NULL,
|
|
28595
|
-
result TEXT NOT NULL CHECK (result IN ('approved', 'unavailable')),
|
|
28596
|
-
error_code TEXT,
|
|
28597
|
-
error_message TEXT
|
|
28598
|
-
) STRICT;
|
|
28599
|
-
`);
|
|
28600
|
-
}
|
|
28601
|
-
approve = async (request, catalog, directories, bindingAuthorityId, now, signal) => {
|
|
28602
|
-
signal.throwIfAborted();
|
|
28603
|
-
const existing = this.#database.prepare(
|
|
28604
|
-
`SELECT grants.* FROM local_bot_binding_resolutions AS resolutions
|
|
28605
|
-
INNER JOIN local_bot_grants AS grants
|
|
28606
|
-
ON grants.local_grant_id = resolutions.local_grant_id
|
|
28607
|
-
WHERE resolutions.request_id = ? AND resolutions.binding_id = ?
|
|
28608
|
-
AND resolutions.result = 'approved'`
|
|
28609
|
-
).get(request.requestId, request.bindingId);
|
|
28610
|
-
if (existing !== void 0) {
|
|
28611
|
-
return new BotExecutionBindingApprovedEvent(
|
|
28612
|
-
request.requestId,
|
|
28613
|
-
request.botId,
|
|
28614
|
-
request.behaviorRevisionId,
|
|
28615
|
-
request.bindingId,
|
|
28616
|
-
request.bindingRevision,
|
|
28617
|
-
rowString(existing, "local_grant_id"),
|
|
28618
|
-
rowString(existing, "agent_identity_digest"),
|
|
28619
|
-
rowString(existing, "workspace_identity_digest")
|
|
28620
|
-
);
|
|
28621
|
-
}
|
|
28622
|
-
const resolved = await this.resolveProposal(request, catalog, directories, bindingAuthorityId, signal);
|
|
28623
|
-
const localGrantId = `lbg_${randomUUID3()}`;
|
|
28624
|
-
const expiresAt = now + LOCAL_BOT_GRANT_LIFETIME_MILLISECONDS;
|
|
28625
|
-
this.#database.exec("BEGIN IMMEDIATE");
|
|
28626
|
-
try {
|
|
28627
|
-
this.#database.prepare(
|
|
28628
|
-
`INSERT INTO local_bot_grants (
|
|
28629
|
-
local_grant_id, bot_id, behavior_revision_id, behavior_digest,
|
|
28630
|
-
binding_id, binding_revision, agent_ref,
|
|
28631
|
-
agent_identity_digest, workspace_ref, workspace_path,
|
|
28632
|
-
workspace_identity_digest, allowed_delivery_target_ids_json,
|
|
28633
|
-
unattended_policy_json, created_at, expires_at
|
|
28634
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
28635
|
-
).run(
|
|
28636
|
-
localGrantId,
|
|
28637
|
-
request.botId,
|
|
28638
|
-
request.behaviorRevisionId,
|
|
28639
|
-
request.behaviorDigest,
|
|
28640
|
-
request.bindingId,
|
|
28641
|
-
request.bindingRevision,
|
|
28642
|
-
request.agentRef,
|
|
28643
|
-
resolved.agentIdentityDigest,
|
|
28644
|
-
request.workspaceRef,
|
|
28645
|
-
resolved.workspacePath,
|
|
28646
|
-
resolved.workspaceIdentityDigest,
|
|
28647
|
-
JSON.stringify(request.allowedDeliveryTargets.map((target) => target.deliveryTargetId)),
|
|
28648
|
-
JSON.stringify({ localSideEffects: "owner-approval-required" }),
|
|
28649
|
-
now,
|
|
28650
|
-
expiresAt
|
|
28651
|
-
);
|
|
28652
|
-
this.#database.prepare(
|
|
28653
|
-
`INSERT INTO local_bot_binding_resolutions (
|
|
28654
|
-
request_id, binding_id, local_grant_id, resolved_at, result
|
|
28655
|
-
) VALUES (?, ?, ?, ?, 'approved')`
|
|
28656
|
-
).run(request.requestId, request.bindingId, localGrantId, now);
|
|
28657
|
-
this.#database.exec("COMMIT");
|
|
28658
|
-
} catch (cause) {
|
|
28659
|
-
this.#database.exec("ROLLBACK");
|
|
28660
|
-
throw cause;
|
|
28661
|
-
}
|
|
28662
|
-
signal.throwIfAborted();
|
|
28663
|
-
return new BotExecutionBindingApprovedEvent(
|
|
28664
|
-
request.requestId,
|
|
28665
|
-
request.botId,
|
|
28666
|
-
request.behaviorRevisionId,
|
|
28667
|
-
request.bindingId,
|
|
28668
|
-
request.bindingRevision,
|
|
28669
|
-
localGrantId,
|
|
28670
|
-
resolved.agentIdentityDigest,
|
|
28671
|
-
resolved.workspaceIdentityDigest
|
|
28672
|
-
);
|
|
28673
|
-
};
|
|
28674
|
-
resolveExecution = async (command, catalog, directories, bindingAuthorityId, now, signal) => {
|
|
28675
|
-
signal.throwIfAborted();
|
|
28676
|
-
const row = this.#database.prepare(
|
|
28677
|
-
`SELECT * FROM local_bot_grants
|
|
28678
|
-
WHERE local_grant_id = ? AND bot_id = ?
|
|
28679
|
-
AND behavior_revision_id = ? AND behavior_digest = ?
|
|
28680
|
-
AND binding_id = ? AND binding_revision = ?
|
|
28681
|
-
AND agent_ref = ?
|
|
28682
|
-
AND revoked_at IS NULL AND expires_at > ?`
|
|
28683
|
-
).get(
|
|
28684
|
-
command.localGrantId,
|
|
28685
|
-
command.botId,
|
|
28686
|
-
command.behaviorRevisionId,
|
|
28687
|
-
command.behaviorDigest,
|
|
28688
|
-
command.bindingId,
|
|
28689
|
-
command.bindingRevision,
|
|
28690
|
-
command.agentId,
|
|
28691
|
-
now
|
|
28692
|
-
);
|
|
28693
|
-
if (row === void 0) {
|
|
28694
|
-
throw new LocalBotAuthorizationError(
|
|
28695
|
-
"binding_not_approved",
|
|
28696
|
-
"The Bot execution binding is not locally approved"
|
|
28697
|
-
);
|
|
28698
|
-
}
|
|
28699
|
-
const approvedTargets = parseTargets(rowString(row, "allowed_delivery_target_ids_json"));
|
|
28700
|
-
const requestedTargets = command.allowedDeliveryTargets.map((target) => target.deliveryTargetId);
|
|
28701
|
-
if (!exactTargets(approvedTargets, requestedTargets)) {
|
|
28702
|
-
throw new LocalBotAuthorizationError(
|
|
28703
|
-
"binding_not_approved",
|
|
28704
|
-
"The Bot execution target policy changed after local approval"
|
|
28705
|
-
);
|
|
28706
|
-
}
|
|
28707
|
-
const agent = catalog.find(command.agentId);
|
|
28708
|
-
const [adapterUnchanged, agentUnchanged] = await Promise.all([
|
|
28709
|
-
revalidateExecutable(agent.adapterIdentity),
|
|
28710
|
-
revalidateExecutable(agent.agentIdentity)
|
|
28711
|
-
]);
|
|
28712
|
-
signal.throwIfAborted();
|
|
28713
|
-
const workspaceRef = rowString(row, "workspace_ref");
|
|
28714
|
-
const workspacePath = await directories.resolve(bindingAuthorityId, workspaceRef, signal);
|
|
28715
|
-
const currentAgentDigest = agentDigest(agent);
|
|
28716
|
-
const currentWorkspaceDigest = await workspaceDigest(workspacePath);
|
|
28717
|
-
signal.throwIfAborted();
|
|
28718
|
-
if (!adapterUnchanged || !agentUnchanged || currentAgentDigest !== rowString(row, "agent_identity_digest") || currentWorkspaceDigest !== rowString(row, "workspace_identity_digest") || workspacePath !== rowString(row, "workspace_path")) {
|
|
28719
|
-
this.revoke(rowString(row, "local_grant_id"), now);
|
|
28720
|
-
throw new LocalBotAuthorizationError(
|
|
28721
|
-
"binding_not_approved",
|
|
28722
|
-
"The Bot execution target changed and requires local approval"
|
|
28723
|
-
);
|
|
28724
|
-
}
|
|
28725
|
-
if (command.behaviorInstructions.length > 0 && !agent.capabilities.embeddedContextPrompt) {
|
|
28726
|
-
throw new LocalBotAuthorizationError(
|
|
28727
|
-
"binding_incompatible",
|
|
28728
|
-
"The local Agent cannot apply the approved Bot behavior"
|
|
28729
|
-
);
|
|
28730
|
-
}
|
|
28731
|
-
return new LocalBotGrant(
|
|
28732
|
-
rowString(row, "local_grant_id"),
|
|
28733
|
-
command.botId,
|
|
28734
|
-
command.behaviorRevisionId,
|
|
28735
|
-
command.behaviorDigest,
|
|
28736
|
-
command.bindingId,
|
|
28737
|
-
command.bindingRevision,
|
|
28738
|
-
agent,
|
|
28739
|
-
currentAgentDigest,
|
|
28740
|
-
workspaceRef,
|
|
28741
|
-
workspacePath,
|
|
28742
|
-
currentWorkspaceDigest,
|
|
28743
|
-
approvedTargets,
|
|
28744
|
-
rowInteger(row, "expires_at"),
|
|
28745
|
-
rowInteger(row, "revocation_epoch")
|
|
28746
|
-
);
|
|
28747
|
-
};
|
|
28748
|
-
recordUnavailable = (request, code, message, now) => {
|
|
28749
|
-
this.#database.prepare(
|
|
28750
|
-
`INSERT OR IGNORE INTO local_bot_binding_resolutions (
|
|
28751
|
-
request_id, binding_id, local_grant_id, resolved_at,
|
|
28752
|
-
result, error_code, error_message
|
|
28753
|
-
) VALUES (?, ?, '', ?, 'unavailable', ?, ?)`
|
|
28754
|
-
).run(request.requestId, request.bindingId, now, code, message);
|
|
28755
|
-
};
|
|
28756
|
-
close = () => {
|
|
28757
|
-
this.#database.close();
|
|
28758
|
-
};
|
|
28759
|
-
resolveProposal = async (request, catalog, directories, bindingAuthorityId, signal) => {
|
|
28760
|
-
const agent = catalog.find(request.agentRef);
|
|
28761
|
-
if (request.requiredCapabilities.includes("embedded-context") && !agent.capabilities.embeddedContextPrompt) {
|
|
28762
|
-
throw new LocalBotAuthorizationError(
|
|
28763
|
-
"binding_incompatible",
|
|
28764
|
-
"The local Agent cannot apply embedded Bot context"
|
|
28765
|
-
);
|
|
28766
|
-
}
|
|
28767
|
-
if (request.requiredCapabilities.some((capability) => capability !== "embedded-context")) {
|
|
28768
|
-
throw new LocalBotAuthorizationError(
|
|
28769
|
-
"binding_incompatible",
|
|
28770
|
-
"The Bot requires an unsupported local Agent capability"
|
|
28771
|
-
);
|
|
28772
|
-
}
|
|
28773
|
-
const [adapterUnchanged, agentUnchanged] = await Promise.all([
|
|
28774
|
-
revalidateExecutable(agent.adapterIdentity),
|
|
28775
|
-
revalidateExecutable(agent.agentIdentity)
|
|
28776
|
-
]);
|
|
28777
|
-
signal.throwIfAborted();
|
|
28778
|
-
if (!adapterUnchanged || !agentUnchanged) {
|
|
28779
|
-
throw new LocalBotAuthorizationError(
|
|
28780
|
-
"binding_incompatible",
|
|
28781
|
-
"The local Agent executable changed before approval"
|
|
28782
|
-
);
|
|
28783
|
-
}
|
|
28784
|
-
const workspacePath = await directories.resolve(bindingAuthorityId, request.workspaceRef, signal);
|
|
28785
|
-
return {
|
|
28786
|
-
agent,
|
|
28787
|
-
agentIdentityDigest: agentDigest(agent),
|
|
28788
|
-
workspaceIdentityDigest: await workspaceDigest(workspacePath),
|
|
28789
|
-
workspacePath
|
|
28790
|
-
};
|
|
28791
|
-
};
|
|
28792
|
-
revoke = (localGrantId, now) => {
|
|
28793
|
-
this.#database.prepare(
|
|
28794
|
-
`UPDATE local_bot_grants
|
|
28795
|
-
SET revoked_at = COALESCE(revoked_at, ?), revocation_epoch = revocation_epoch + 1
|
|
28796
|
-
WHERE local_grant_id = ? AND revoked_at IS NULL`
|
|
28797
|
-
).run(now, localGrantId);
|
|
28798
|
-
};
|
|
28799
|
-
};
|
|
28800
|
-
|
|
28801
29147
|
// src/bots/bot-context-applicator.ts
|
|
28802
29148
|
var AppliedBotContext = class {
|
|
28803
29149
|
resources;
|
|
@@ -28933,7 +29279,7 @@ var BotExecutionContextRegistry = class {
|
|
|
28933
29279
|
// src/bots/bot-outbound-ipc.ts
|
|
28934
29280
|
import { chmod as chmod2, mkdir as mkdir4, unlink } from "node:fs/promises";
|
|
28935
29281
|
import { createServer } from "node:net";
|
|
28936
|
-
import { dirname as
|
|
29282
|
+
import { dirname as dirname7, isAbsolute as isAbsolute10 } from "node:path";
|
|
28937
29283
|
import { fetch as fetch5 } from "undici";
|
|
28938
29284
|
var BOT_IPC_REQUEST_LIMIT = 32 * 1024;
|
|
28939
29285
|
var BOT_IPC_TIMEOUT_MILLISECONDS2 = 2e4;
|
|
@@ -29031,7 +29377,7 @@ var BotOutboundIpcServer = class {
|
|
|
29031
29377
|
#usesFilesystemSocket;
|
|
29032
29378
|
constructor(socketPath, relayUrl, registry2) {
|
|
29033
29379
|
const usesFilesystemSocket = !socketPath.startsWith(WINDOWS_NAMED_PIPE_PREFIX);
|
|
29034
|
-
if (usesFilesystemSocket && !
|
|
29380
|
+
if (usesFilesystemSocket && !isAbsolute10(socketPath)) {
|
|
29035
29381
|
throw new Error("The Bot IPC path must be absolute");
|
|
29036
29382
|
}
|
|
29037
29383
|
this.#registry = registry2;
|
|
@@ -29046,7 +29392,7 @@ var BotOutboundIpcServer = class {
|
|
|
29046
29392
|
listen = async (signal) => {
|
|
29047
29393
|
signal.throwIfAborted();
|
|
29048
29394
|
if (this.#usesFilesystemSocket) {
|
|
29049
|
-
await mkdir4(
|
|
29395
|
+
await mkdir4(dirname7(this.#socketPath), { mode: 448, recursive: true });
|
|
29050
29396
|
await unlink(this.#socketPath).catch((error61) => {
|
|
29051
29397
|
if (!isRecord12(error61) || error61["code"] !== "ENOENT") {
|
|
29052
29398
|
throw error61;
|
|
@@ -30352,7 +30698,7 @@ var RelayConnectionLiveness = class {
|
|
|
30352
30698
|
|
|
30353
30699
|
// src/relay-connection/session-event-outbox.ts
|
|
30354
30700
|
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
30355
|
-
import { dirname as
|
|
30701
|
+
import { dirname as dirname8 } from "node:path";
|
|
30356
30702
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
30357
30703
|
var DEFAULT_OUTBOX_LIMITS = {
|
|
30358
30704
|
criticalEventBytes: 136 * 1024 * 1024,
|
|
@@ -30420,7 +30766,7 @@ var SessionEventOutbox = class {
|
|
|
30420
30766
|
constructor(path2, limits = DEFAULT_OUTBOX_LIMITS) {
|
|
30421
30767
|
validateLimits(limits);
|
|
30422
30768
|
this.#limits = limits;
|
|
30423
|
-
mkdirSync3(
|
|
30769
|
+
mkdirSync3(dirname8(path2), { recursive: true });
|
|
30424
30770
|
this.#database = new DatabaseSync4(path2);
|
|
30425
30771
|
this.#database.exec(`
|
|
30426
30772
|
CREATE TABLE IF NOT EXISTS relay_session_event_outbox (
|
|
@@ -30971,7 +31317,7 @@ function botIpcSocketPath() {
|
|
|
30971
31317
|
if (process.platform === "win32") {
|
|
30972
31318
|
return `\\\\.\\pipe\\mobius-bot-${randomUUID6()}`;
|
|
30973
31319
|
}
|
|
30974
|
-
return
|
|
31320
|
+
return join18("/tmp", `mobius-bot-${randomUUID6()}.sock`);
|
|
30975
31321
|
}
|
|
30976
31322
|
var DaemonAcpObserver = class {
|
|
30977
31323
|
#config;
|
|
@@ -31641,7 +31987,7 @@ async function executeSessionConfigure(publisher, socket, command, config2, dire
|
|
|
31641
31987
|
reportOperationFailure(socket, command.requestId, command.sessionId, error61);
|
|
31642
31988
|
}
|
|
31643
31989
|
}
|
|
31644
|
-
async function executePrompt(outbox, publisher, command, config2, directories, audit, sessions, attemptId, timer, signal) {
|
|
31990
|
+
async function executePrompt(outbox, publisher, command, config2, directories, audit, sessions, attemptId, activateAttempt, timer, signal) {
|
|
31645
31991
|
const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
|
|
31646
31992
|
publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
|
|
31647
31993
|
});
|
|
@@ -31670,6 +32016,7 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
|
|
|
31670
32016
|
}),
|
|
31671
32017
|
attemptId
|
|
31672
32018
|
);
|
|
32019
|
+
activateAttempt();
|
|
31673
32020
|
const attachments = await resolvePromptArtifacts(config2, command, signal);
|
|
31674
32021
|
let stopReason;
|
|
31675
32022
|
if (command.disposition === SessionPromptDisposition.Steer) {
|
|
@@ -31781,7 +32128,7 @@ function botExecutionAgent(agent, contextHandle, socketPath) {
|
|
|
31781
32128
|
agent.capabilities
|
|
31782
32129
|
);
|
|
31783
32130
|
}
|
|
31784
|
-
async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, timer, signal) {
|
|
32131
|
+
async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, activateAttempt, timer, signal) {
|
|
31785
32132
|
const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
|
|
31786
32133
|
publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
|
|
31787
32134
|
});
|
|
@@ -31862,6 +32209,7 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
|
|
|
31862
32209
|
}),
|
|
31863
32210
|
attemptId
|
|
31864
32211
|
);
|
|
32212
|
+
activateAttempt();
|
|
31865
32213
|
const result = await sessions.runPrompt({
|
|
31866
32214
|
additionalDirectories: resolved.additionalDirectories,
|
|
31867
32215
|
agent,
|
|
@@ -32055,11 +32403,13 @@ async function runRelayConnection(config2, directories, audit, sessions, install
|
|
|
32055
32403
|
}
|
|
32056
32404
|
const turn = new ActiveDaemonTurn(command, receipt.attemptId);
|
|
32057
32405
|
activeTurns.set(command.commandId, turn);
|
|
32058
|
-
|
|
32059
|
-
|
|
32060
|
-
|
|
32406
|
+
const activateAttempt = () => {
|
|
32407
|
+
if (trackActiveAttempt) {
|
|
32408
|
+
activeAttemptIds.set(command.sessionId, receipt.attemptId);
|
|
32409
|
+
}
|
|
32410
|
+
};
|
|
32061
32411
|
try {
|
|
32062
|
-
await execute(receipt.attemptId, turn.signal);
|
|
32412
|
+
await execute(receipt.attemptId, activateAttempt, turn.signal);
|
|
32063
32413
|
} finally {
|
|
32064
32414
|
activeTurns.delete(command.commandId);
|
|
32065
32415
|
if (activeAttemptIds.get(command.sessionId) === receipt.attemptId) {
|
|
@@ -32144,7 +32494,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
|
|
|
32144
32494
|
delivery,
|
|
32145
32495
|
command,
|
|
32146
32496
|
true,
|
|
32147
|
-
(attemptId, executionSignal) => executeBotPrompt(
|
|
32497
|
+
(attemptId, activateAttempt, executionSignal) => executeBotPrompt(
|
|
32148
32498
|
outbox,
|
|
32149
32499
|
publisher,
|
|
32150
32500
|
command,
|
|
@@ -32157,6 +32507,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
|
|
|
32157
32507
|
applicator,
|
|
32158
32508
|
ipc,
|
|
32159
32509
|
attemptId,
|
|
32510
|
+
activateAttempt,
|
|
32160
32511
|
timer,
|
|
32161
32512
|
executionSignal
|
|
32162
32513
|
)
|
|
@@ -32169,7 +32520,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
|
|
|
32169
32520
|
delivery,
|
|
32170
32521
|
command,
|
|
32171
32522
|
command.disposition !== SessionPromptDisposition.Steer,
|
|
32172
|
-
(attemptId, executionSignal) => executePrompt(
|
|
32523
|
+
(attemptId, activateAttempt, executionSignal) => executePrompt(
|
|
32173
32524
|
outbox,
|
|
32174
32525
|
publisher,
|
|
32175
32526
|
command,
|
|
@@ -32178,6 +32529,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
|
|
|
32178
32529
|
audit,
|
|
32179
32530
|
sessions,
|
|
32180
32531
|
attemptId,
|
|
32532
|
+
activateAttempt,
|
|
32181
32533
|
timer,
|
|
32182
32534
|
executionSignal
|
|
32183
32535
|
)
|
|
@@ -32679,7 +33031,6 @@ async function runRelayConnection(config2, directories, audit, sessions, install
|
|
|
32679
33031
|
}
|
|
32680
33032
|
const turn2 = new ActiveDaemonTurn(command2, receipt2.attemptId);
|
|
32681
33033
|
activeTurns.set(command2.commandId, turn2);
|
|
32682
|
-
activeAttemptIds.set(command2.sessionId, receipt2.attemptId);
|
|
32683
33034
|
void executeBotPrompt(
|
|
32684
33035
|
outbox,
|
|
32685
33036
|
publisher,
|
|
@@ -32693,6 +33044,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
|
|
|
32693
33044
|
applicator,
|
|
32694
33045
|
ipc,
|
|
32695
33046
|
receipt2.attemptId,
|
|
33047
|
+
() => activeAttemptIds.set(command2.sessionId, receipt2.attemptId),
|
|
32696
33048
|
timer,
|
|
32697
33049
|
turn2.signal
|
|
32698
33050
|
).catch((error61) => {
|
|
@@ -32719,9 +33071,6 @@ async function runRelayConnection(config2, directories, audit, sessions, install
|
|
|
32719
33071
|
}
|
|
32720
33072
|
const turn = new ActiveDaemonTurn(command, receipt.attemptId);
|
|
32721
33073
|
activeTurns.set(command.commandId, turn);
|
|
32722
|
-
if (command.disposition !== SessionPromptDisposition.Steer) {
|
|
32723
|
-
activeAttemptIds.set(command.sessionId, receipt.attemptId);
|
|
32724
|
-
}
|
|
32725
33074
|
void executePrompt(
|
|
32726
33075
|
outbox,
|
|
32727
33076
|
publisher,
|
|
@@ -32731,6 +33080,11 @@ async function runRelayConnection(config2, directories, audit, sessions, install
|
|
|
32731
33080
|
audit,
|
|
32732
33081
|
sessions,
|
|
32733
33082
|
receipt.attemptId,
|
|
33083
|
+
() => {
|
|
33084
|
+
if (command.disposition !== SessionPromptDisposition.Steer) {
|
|
33085
|
+
activeAttemptIds.set(command.sessionId, receipt.attemptId);
|
|
33086
|
+
}
|
|
33087
|
+
},
|
|
32734
33088
|
timer,
|
|
32735
33089
|
turn.signal
|
|
32736
33090
|
).catch((error61) => {
|
|
@@ -32989,6 +33343,7 @@ Usage:
|
|
|
32989
33343
|
mobius pair --url <relay-url> --pairing <runtime-pairing-id> --code <pairing-code> [--workspace <directory>]
|
|
32990
33344
|
mobius start --channel <channel-id>
|
|
32991
33345
|
mobius agents [--json]
|
|
33346
|
+
mobius bot approve --bot <bot-id> [--channel <channel-id>]
|
|
32992
33347
|
mobius bot message send --target <current|alias> --text <message>
|
|
32993
33348
|
|
|
32994
33349
|
Commands:
|
|
@@ -32996,7 +33351,7 @@ Commands:
|
|
|
32996
33351
|
pair Pair this machine and start its local daemon
|
|
32997
33352
|
start Start the local daemon for a saved channel
|
|
32998
33353
|
agents Show detected, missing, and integration-ready Agents
|
|
32999
|
-
bot
|
|
33354
|
+
bot Approve a saved Bot locally or send a message from an active execution
|
|
33000
33355
|
|
|
33001
33356
|
Pairing grants this browser access to the machine with your user account's permissions.
|
|
33002
33357
|
New conversations default to ~/.mobius/workspace; choose other directories in the browser.
|
|
@@ -33027,6 +33382,10 @@ async function main() {
|
|
|
33027
33382
|
return;
|
|
33028
33383
|
}
|
|
33029
33384
|
if (command === "bot") {
|
|
33385
|
+
if (process.argv[3] === "approve") {
|
|
33386
|
+
process.stdout.write(await executeBotApproveCommand(process.argv.slice(4), process.env, shutdown.signal));
|
|
33387
|
+
return;
|
|
33388
|
+
}
|
|
33030
33389
|
if (process.argv[3] !== "message" || process.argv[4] !== "send") {
|
|
33031
33390
|
throw new BotMessageCommandError(
|
|
33032
33391
|
"invalid_request",
|
|
@@ -33072,7 +33431,7 @@ try {
|
|
|
33072
33431
|
} catch (error61) {
|
|
33073
33432
|
if (!shutdown.signal.aborted) {
|
|
33074
33433
|
const message = error61 instanceof Error ? error61.message : "Unknown failure";
|
|
33075
|
-
const code = error61 instanceof BotMessageCommandError ? `${error61.code}: ` : "";
|
|
33434
|
+
const code = error61 instanceof BotMessageCommandError || error61 instanceof LocalBotAuthorizationError ? `${error61.code}: ` : "";
|
|
33076
33435
|
process.stderr.write(`Mobius daemon failed: ${code}${message}
|
|
33077
33436
|
`);
|
|
33078
33437
|
process.exitCode = 1;
|