@botbuddy/cli 1.61.3 → 1.61.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/daemon/bin/darwin-arm64/botbuddy-daemon +0 -0
- package/daemon/bin/linux-x64/botbuddy-daemon +0 -0
- package/daemon-release/darwin-arm64.json +4 -4
- package/daemon-release/linux-x64.json +4 -4
- package/daemon-release/manifest.json +5 -5
- package/package.json +1 -1
- package/src/daemon/bootstrap.mjs +47 -2
- package/src/daemon/commands.mjs +3 -0
- package/src/daemon/profile-delivery.mjs +29 -0
- package/src/stack.mjs +18 -1
|
Binary file
|
|
Binary file
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"release_version": "1.61.
|
|
3
|
-
"source_commit": "
|
|
4
|
-
"source_sha256": "
|
|
2
|
+
"release_version": "1.61.5",
|
|
3
|
+
"source_commit": "f53c37db06f6fc5ab544623f2a273c4e21de1b72",
|
|
4
|
+
"source_sha256": "7280c8ff78ee957cc06bc7d07ed12a4b36a4c64c3a5968a710a1d371ab1b33c9",
|
|
5
5
|
"target": "darwin-arm64",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "c57ada939dcf3d5c0ad7cefdbf27ae9995a06a14446ca8774a94c76c61083b00"
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"release_version": "1.61.
|
|
3
|
-
"source_commit": "
|
|
4
|
-
"source_sha256": "
|
|
2
|
+
"release_version": "1.61.5",
|
|
3
|
+
"source_commit": "f53c37db06f6fc5ab544623f2a273c4e21de1b72",
|
|
4
|
+
"source_sha256": "7280c8ff78ee957cc06bc7d07ed12a4b36a4c64c3a5968a710a1d371ab1b33c9",
|
|
5
5
|
"target": "linux-x64",
|
|
6
|
-
"sha256": "
|
|
6
|
+
"sha256": "1002356a262f413e793af8779bec4208426d47bc38d6e5ef115a06b0fa4fd64c"
|
|
7
7
|
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"release_version": "1.61.
|
|
4
|
-
"source_commit": "
|
|
5
|
-
"source_sha256": "
|
|
3
|
+
"release_version": "1.61.5",
|
|
4
|
+
"source_commit": "f53c37db06f6fc5ab544623f2a273c4e21de1b72",
|
|
5
|
+
"source_sha256": "7280c8ff78ee957cc06bc7d07ed12a4b36a4c64c3a5968a710a1d371ab1b33c9",
|
|
6
6
|
"targets": {
|
|
7
7
|
"darwin-arm64": {
|
|
8
8
|
"goos": "darwin",
|
|
9
9
|
"goarch": "arm64",
|
|
10
10
|
"binary": "daemon/bin/darwin-arm64/botbuddy-daemon",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "c57ada939dcf3d5c0ad7cefdbf27ae9995a06a14446ca8774a94c76c61083b00",
|
|
12
12
|
"mode": "0755"
|
|
13
13
|
},
|
|
14
14
|
"linux-x64": {
|
|
15
15
|
"goos": "linux",
|
|
16
16
|
"goarch": "amd64",
|
|
17
17
|
"binary": "daemon/bin/linux-x64/botbuddy-daemon",
|
|
18
|
-
"sha256": "
|
|
18
|
+
"sha256": "1002356a262f413e793af8779bec4208426d47bc38d6e5ef115a06b0fa4fd64c",
|
|
19
19
|
"mode": "0755"
|
|
20
20
|
}
|
|
21
21
|
}
|
package/package.json
CHANGED
package/src/daemon/bootstrap.mjs
CHANGED
|
@@ -4,15 +4,18 @@
|
|
|
4
4
|
// no local approval polling loop: host policy wakes only through the pushed
|
|
5
5
|
// BOT-2324 raw wait condition, then this flow re-registers server-side.
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
+
import { dirname } from "node:path";
|
|
7
8
|
|
|
8
9
|
import { DAEMON_PRODUCTION_MCP_SERVER, enrollDaemonDeviceProfileFromBootstrapIdentity } from "./enrollment.mjs";
|
|
9
10
|
import { installRemoteService, installedBinaryPath, stageRemoteDaemonRelease } from "./installer.mjs";
|
|
10
11
|
import {
|
|
11
12
|
createRemoteBootstrapIdentity,
|
|
12
13
|
deliverDaemonProfileFromBootstrapIdentityOverSSH,
|
|
14
|
+
installDaemonRuntimeConfigOverSSH,
|
|
13
15
|
inspectRemoteBootstrapIdentity,
|
|
14
16
|
verifyRemoteServiceIdentity,
|
|
15
17
|
} from "./profile-delivery.mjs";
|
|
18
|
+
import { buildRuntimeConfig } from "./runtime-config.mjs";
|
|
16
19
|
import { validateDaemonRelease } from "./release.mjs";
|
|
17
20
|
import { renderLinuxUnit, renderMacDaemon } from "./service.mjs";
|
|
18
21
|
|
|
@@ -45,7 +48,7 @@ function exactObject(value, keys) {
|
|
|
45
48
|
function checkedSpec(spec) {
|
|
46
49
|
const keys = [
|
|
47
50
|
"mode", "serverUrl", "sshHost", "serviceUser", "daemonBinary", "profilePath", "requestingHost",
|
|
48
|
-
"runtimeEngineId", "platform", "arch", "releaseRoot", "home", "config", "approvalTimeoutSeconds",
|
|
51
|
+
"runtimeEngineId", "platform", "arch", "releaseRoot", "home", "config", "hostPolicy", "approvalTimeoutSeconds",
|
|
49
52
|
];
|
|
50
53
|
const optional = ["caller", "callerHarness", "callerTicket"];
|
|
51
54
|
if (!spec || typeof spec !== "object" || Array.isArray(spec) ||
|
|
@@ -59,7 +62,11 @@ function checkedSpec(spec) {
|
|
|
59
62
|
typeof spec.requestingHost !== "string" || !spec.requestingHost || Buffer.byteLength(spec.requestingHost, "utf8") > 80 ||
|
|
60
63
|
typeof spec.runtimeEngineId !== "string" || !ENGINE_ID_RE.test(spec.runtimeEngineId) ||
|
|
61
64
|
!["darwin", "linux"].includes(spec.platform) || typeof spec.arch !== "string" || !spec.arch ||
|
|
62
|
-
!validPath(spec.releaseRoot) || !validPath(spec.home) || !validPath(spec.config) ||
|
|
65
|
+
!validPath(spec.releaseRoot) || !validPath(spec.home) || !validPath(spec.config) || !spec.config.endsWith("/daemon.json") ||
|
|
66
|
+
dirname(spec.config) !== dirname(spec.profilePath) ||
|
|
67
|
+
!spec.hostPolicy || typeof spec.hostPolicy !== "object" || Array.isArray(spec.hostPolicy) ||
|
|
68
|
+
Object.keys(spec.hostPolicy).length !== 4 ||
|
|
69
|
+
!["machineId", "dockerEndpoint", "supabaseBinary", "dockerCLI"].every((key) => typeof spec.hostPolicy[key] === "string" && spec.hostPolicy[key]) ||
|
|
63
70
|
!Number.isSafeInteger(spec.approvalTimeoutSeconds) || spec.approvalTimeoutSeconds < 1 || spec.approvalTimeoutSeconds > 3_600 ||
|
|
64
71
|
optional.some((key) => spec[key] !== undefined && (typeof spec[key] !== "string" || !spec[key]))
|
|
65
72
|
) fail("daemon_bootstrap_spec_invalid");
|
|
@@ -215,12 +222,14 @@ export async function bootstrapFreshDaemon(input, {
|
|
|
215
222
|
enroll = enrollDaemonDeviceProfileFromBootstrapIdentity,
|
|
216
223
|
deliver = deliverDaemonProfileFromBootstrapIdentityOverSSH,
|
|
217
224
|
installRemote = installRemoteService,
|
|
225
|
+
installRuntimeConfig = installDaemonRuntimeConfigOverSSH,
|
|
218
226
|
} = {}) {
|
|
219
227
|
const spec = checkedSpec(input);
|
|
220
228
|
if (typeof registerHost !== "function" || typeof verifyRemoteIdentity !== "function" ||
|
|
221
229
|
typeof createIdentity !== "function" || typeof inspectIdentity !== "function" ||
|
|
222
230
|
typeof validateRelease !== "function" || typeof stageRemote !== "function" || typeof enroll !== "function" ||
|
|
223
231
|
typeof deliver !== "function" || typeof installRemote !== "function" ||
|
|
232
|
+
typeof installRuntimeConfig !== "function" ||
|
|
224
233
|
(awaitApproval !== null && typeof awaitApproval !== "function") ||
|
|
225
234
|
(onApprovalRequired !== null && typeof onApprovalRequired !== "function") ||
|
|
226
235
|
(resumeStore !== null && (typeof resumeStore !== "object" || typeof resumeStore.save !== "function" ||
|
|
@@ -355,6 +364,42 @@ export async function bootstrapFreshDaemon(input, {
|
|
|
355
364
|
}
|
|
356
365
|
}
|
|
357
366
|
|
|
367
|
+
let runtimeConfig;
|
|
368
|
+
try {
|
|
369
|
+
runtimeConfig = buildRuntimeConfig({
|
|
370
|
+
host_key: publicIdentity.hostKey,
|
|
371
|
+
state_dir: dirname(spec.config),
|
|
372
|
+
docker_endpoint: spec.hostPolicy.dockerEndpoint,
|
|
373
|
+
expected_engine_id: spec.runtimeEngineId,
|
|
374
|
+
machine_id: spec.hostPolicy.machineId,
|
|
375
|
+
supabase_binary: spec.hostPolicy.supabaseBinary,
|
|
376
|
+
docker_cli: spec.hostPolicy.dockerCLI,
|
|
377
|
+
host_credential_profile: spec.profilePath,
|
|
378
|
+
device_consent: true,
|
|
379
|
+
runtime_version: `go-daemon/${release.releaseVersion}`,
|
|
380
|
+
runtime_git_sha: release.sourceCommit,
|
|
381
|
+
runtime_protocol_version: 2,
|
|
382
|
+
});
|
|
383
|
+
} catch {
|
|
384
|
+
fail("daemon_bootstrap_runtime_config_invalid");
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
const installed = await installRuntimeConfig({
|
|
388
|
+
config: JSON.stringify(runtimeConfig),
|
|
389
|
+
configPath: spec.config,
|
|
390
|
+
sshHost: spec.sshHost,
|
|
391
|
+
serviceUser: spec.serviceUser,
|
|
392
|
+
daemonBinary: trustedDaemonBinary,
|
|
393
|
+
profilePath: spec.profilePath,
|
|
394
|
+
expectedHostKey: publicIdentity.hostKey,
|
|
395
|
+
directInstall: remote.directInstall,
|
|
396
|
+
});
|
|
397
|
+
if (!installed || installed.installed !== true) fail("daemon_bootstrap_runtime_config_failed");
|
|
398
|
+
} catch (error) {
|
|
399
|
+
if (error instanceof DaemonBootstrapError) throw error;
|
|
400
|
+
fail("daemon_bootstrap_runtime_config_failed");
|
|
401
|
+
}
|
|
402
|
+
|
|
358
403
|
const serviceSpec = {
|
|
359
404
|
user: spec.serviceUser,
|
|
360
405
|
home: spec.home,
|
package/src/daemon/commands.mjs
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
createRemoteBootstrapIdentity,
|
|
30
30
|
deliverDaemonProfileFromBootstrapIdentityOverSSH,
|
|
31
31
|
deliverDaemonProfileFromHelperIdentityOverSSH,
|
|
32
|
+
installDaemonRuntimeConfigOverSSH,
|
|
32
33
|
inspectRemoteBootstrapIdentity,
|
|
33
34
|
verifyRemoteHelperIdentity as verifyRemoteHelperIdentityOnTarget,
|
|
34
35
|
verifyRemoteServiceIdentity,
|
|
@@ -285,6 +286,7 @@ export async function cmdDaemon(args, {
|
|
|
285
286
|
verifyRuntime = checkRuntimeConfig,
|
|
286
287
|
enroll = enrollDaemonDeviceProfileFromHelperIdentity,
|
|
287
288
|
deliver = deliverDaemonProfileFromHelperIdentityOverSSH,
|
|
289
|
+
installRuntimeConfig = installDaemonRuntimeConfigOverSSH,
|
|
288
290
|
verifyRemoteIdentity = verifyRemoteServiceIdentity,
|
|
289
291
|
verifyRemoteHelperIdentity = verifyRemoteHelperIdentityOnTarget,
|
|
290
292
|
bootstrap = bootstrapFreshDaemon,
|
|
@@ -332,6 +334,7 @@ export async function cmdDaemon(args, {
|
|
|
332
334
|
},
|
|
333
335
|
enroll: enrollBootstrap,
|
|
334
336
|
deliver: deliverBootstrap,
|
|
337
|
+
installRuntimeConfig,
|
|
335
338
|
installRemote,
|
|
336
339
|
onApprovalRequired: ({ approval_url: approvalUrl, host_key: hostKey }) => {
|
|
337
340
|
notice(`bb daemon bootstrap: owner approval required for ${hostKey}: ${approvalUrl}`);
|
|
@@ -334,3 +334,32 @@ export async function deliverDaemonProfileFromBootstrapIdentityOverSSH({
|
|
|
334
334
|
installFlag: "--install-profile-from-bootstrap-identity",
|
|
335
335
|
});
|
|
336
336
|
}
|
|
337
|
+
|
|
338
|
+
// Runtime policy is public host metadata, never a credential. It is still
|
|
339
|
+
// installed through the daemon's descriptor-pinned private root so a caller
|
|
340
|
+
// cannot redirect it with a symlink or broaden a prior host's policy.
|
|
341
|
+
export async function installDaemonRuntimeConfigOverSSH({
|
|
342
|
+
config,
|
|
343
|
+
configPath,
|
|
344
|
+
sshHost,
|
|
345
|
+
serviceUser,
|
|
346
|
+
daemonBinary,
|
|
347
|
+
profilePath,
|
|
348
|
+
expectedHostKey,
|
|
349
|
+
sshBin = "ssh",
|
|
350
|
+
spawnImpl = realSpawn,
|
|
351
|
+
env = process.env,
|
|
352
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
353
|
+
directInstall = false,
|
|
354
|
+
} = {}) {
|
|
355
|
+
safeInputs({ sshHost, serviceUser, daemonBinary, profilePath, expectedHostKey, sshBin, spawnImpl, env, timeoutMs, directInstall });
|
|
356
|
+
if (typeof config !== "string" || config.length === 0 || Buffer.byteLength(config, "utf8") > 64 * 1024 ||
|
|
357
|
+
typeof configPath !== "string" || !SAFE_ABSOLUTE_PATH.test(configPath) || !configPath.endsWith("/daemon.json")) throw invalid();
|
|
358
|
+
const command = remoteDaemonCommand({
|
|
359
|
+
serviceUser, daemonBinary, directInstall,
|
|
360
|
+
args: ["--install-runtime-config", "--config", configPath, "--profile", profilePath, "--expected-host-key", expectedHostKey],
|
|
361
|
+
});
|
|
362
|
+
const stdout = await fixedSsh({ sshHost, sshBin, spawnImpl, env, timeoutMs, command, stdin: Buffer.from(config, "utf8") });
|
|
363
|
+
if (!stdout.equals(Buffer.from('{"installed":true}\n'))) throw failed();
|
|
364
|
+
return { installed: true };
|
|
365
|
+
}
|
package/src/stack.mjs
CHANGED
|
@@ -219,6 +219,8 @@ ${bold("run OPTIONS")}
|
|
|
219
219
|
--local-only Require a co-located host (execution "local"); no bundle is built.
|
|
220
220
|
Default (neither flag): execution "auto" — co-located if it admits now,
|
|
221
221
|
else a remote host with capacity; the bundle digest is sent up front.
|
|
222
|
+
--execution-ring R Capacity rollout ring: stable (default) excludes beta daemon hosts.
|
|
223
|
+
beta is explicit opt-in and requires \`stack run --remote\`.
|
|
222
224
|
--callback-port N The caller runs a listener the stack calls back through
|
|
223
225
|
host.docker.internal:N (e.g. 4600): sent as callback_port. On a REMOTE
|
|
224
226
|
lease run first probes the host with a throwaway reverse forward and opens
|
|
@@ -365,7 +367,7 @@ export function parseStackArgs(argv) {
|
|
|
365
367
|
connectionFile: null, noWait: false, claimOnly: false, localExec: false, keepVolumes: false, waitCapacity: null, holdOnTimeout: null,
|
|
366
368
|
dockerContext: null, dockerEndpoint: null,
|
|
367
369
|
// BOT-2142: execution placement (null = command default), remote-lease callback + bundle cap.
|
|
368
|
-
execution: null, remote: false, localOnly: false, callbackPort: null, bundleMaxBytes: null,
|
|
370
|
+
execution: null, remote: false, localOnly: false, executionRing: "stable", callbackPort: null, bundleMaxBytes: null,
|
|
369
371
|
// BOT-2269: exact stack-relative files shipped in the remote-lease bundle despite the
|
|
370
372
|
// default exclusions (`--bundle-include`, repeatable).
|
|
371
373
|
bundleInclude: [],
|
|
@@ -453,6 +455,14 @@ export function parseStackArgs(argv) {
|
|
|
453
455
|
}
|
|
454
456
|
case "--remote": opts.remote = true; break;
|
|
455
457
|
case "--local-only": opts.localOnly = true; break;
|
|
458
|
+
case "--execution-ring": {
|
|
459
|
+
if (need(a, rest[i + 1])) {
|
|
460
|
+
const ring = rest[++i];
|
|
461
|
+
if (ring !== "stable" && ring !== "beta") errors.push("--execution-ring must be stable or beta");
|
|
462
|
+
else opts.executionRing = ring;
|
|
463
|
+
}
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
456
466
|
case "--callback-port": {
|
|
457
467
|
if (need(a, rest[i + 1])) {
|
|
458
468
|
const n = Number(rest[++i]);
|
|
@@ -508,6 +518,12 @@ export function parseStackArgs(argv) {
|
|
|
508
518
|
if (opts.remote && opts.localExec) {
|
|
509
519
|
errors.push("--remote cannot be combined with --local-exec: a local-exec stack is started on THIS machine");
|
|
510
520
|
}
|
|
521
|
+
if (opts.executionRing === "beta" && command !== "run") {
|
|
522
|
+
errors.push("--execution-ring beta is valid only with `stack run`");
|
|
523
|
+
}
|
|
524
|
+
if (opts.executionRing === "beta" && !opts.remote) {
|
|
525
|
+
errors.push("--execution-ring beta requires --remote so the beta daemon can serve the stack");
|
|
526
|
+
}
|
|
511
527
|
if ((opts.remote || opts.localOnly) && !["up", "run"].includes(command)) {
|
|
512
528
|
errors.push("--remote and --local-only are valid only with `stack up` or `stack run`");
|
|
513
529
|
} else if (command === "up" && opts.remote) {
|
|
@@ -3846,6 +3862,7 @@ async function runStackLifecycleOnce(opts, childArgv, adapters, incidentBox) {
|
|
|
3846
3862
|
stack_path: execution.stackPath, worktree_root: execution.worktreeRoot,
|
|
3847
3863
|
machine_uuid: hardwareUuid,
|
|
3848
3864
|
execution: executionRequest,
|
|
3865
|
+
execution_ring: opts.executionRing,
|
|
3849
3866
|
...(bundle ? { bundle_sha256: bundle.sha256 } : {}),
|
|
3850
3867
|
...(stackDigest ? { stack_digest: stackDigest } : {}),
|
|
3851
3868
|
// BOT-1803 (Codex #1160 P1): a run never re-adopts an idle stack it cannot prove current.
|