@indigoai-us/hq-cli 5.276.0 → 5.278.0
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/CHANGELOG.md +99 -0
- package/assets/scaffold/core/scripts/hq-delegate-pickup.sh +99 -0
- package/assets/scaffold/core/scripts/hq-delegate-repo.sh +158 -0
- package/assets/scaffold/core/scripts/hq-delegate-resolve.sh +145 -0
- package/assets/scaffold/core/scripts/hq-delegate-secrets.sh +129 -0
- package/assets/scaffold/core/scripts/hq-delegate-transfer.sh +175 -0
- package/assets/scaffold/core/scripts/lib/secret-patterns.sh +58 -0
- package/dist/command-catalog.generated.d.ts +239 -1
- package/dist/command-catalog.generated.js +302 -1
- package/dist/commands/core-delegate.d.ts +6 -0
- package/dist/commands/core-delegate.js +987 -0
- package/dist/commands/core.js +10 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.js +173 -1
- package/dist/commands/dm.d.ts +2 -0
- package/dist/commands/dm.js +11 -4
- package/dist/commands/lanes-accounts.js +44 -2
- package/dist/commands/lanes-message.js +69 -12
- package/dist/commands/lanes-resume.js +2 -0
- package/dist/commands/lanes-shared.d.ts +3 -0
- package/dist/commands/lanes-shared.js +12 -2
- package/dist/commands/lanes.d.ts +5 -1
- package/dist/commands/lanes.js +399 -47
- package/dist/commands/secrets.d.ts +16 -1
- package/dist/commands/secrets.js +77 -48
- package/dist/commands/sync-mode.d.ts +11 -0
- package/dist/commands/sync-mode.js +30 -0
- package/dist/fast-scaffold.d.ts +17 -0
- package/dist/fast-scaffold.js +91 -0
- package/dist/index.js +17 -1
- package/dist/lanes-drain-hook.js +154 -7
- package/dist/lib/agent-kit/run/inbox.js +3 -2
- package/dist/lib/checkpoint-ontology/apply.d.ts +6 -0
- package/dist/lib/checkpoint-ontology/apply.js +111 -3
- package/dist/lib/checkpoint-ontology/plan.d.ts +23 -0
- package/dist/lib/checkpoint-ontology/plan.js +23 -0
- package/dist/lib/checkpoint-ontology/tree.d.ts +2 -0
- package/dist/lib/checkpoint-ontology/tree.js +34 -0
- package/dist/lib/core-utils/jq-json-serializer.d.ts +5 -0
- package/dist/lib/core-utils/jq-json-serializer.js +10 -0
- package/dist/lib/core-utils/secret-patterns.d.ts +41 -0
- package/dist/lib/core-utils/secret-patterns.js +33 -0
- package/dist/lib/daemon/catalog.d.ts +18 -2
- package/dist/lib/daemon/catalog.js +18 -2
- package/dist/lib/daemon/config.d.ts +6 -0
- package/dist/lib/daemon/config.js +15 -2
- package/dist/lib/daemon/requests.d.ts +4 -0
- package/dist/lib/daemon/requests.js +2 -0
- package/dist/lib/daemon/run.js +94 -8
- package/dist/lib/daemon/supervisor.d.ts +8 -0
- package/dist/lib/daemon/supervisor.js +101 -1
- package/dist/lib/daemon/sync-runner.d.ts +58 -0
- package/dist/lib/daemon/sync-runner.js +277 -0
- package/dist/lib/doctor/__testing__/run-doctor.d.ts +8 -0
- package/dist/lib/doctor/__testing__/run-doctor.js +52 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/lanes/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +46 -8
- package/dist/lib/lanes/config.d.ts +2 -1
- package/dist/lib/lanes/config.js +31 -0
- package/dist/lib/lanes/hire-grant-dispatch.d.ts +2 -0
- package/dist/lib/lanes/hire-grant-dispatch.js +41 -0
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +4 -0
- package/dist/lib/lanes/outpost-dispatch.js +11 -2
- package/dist/lib/lanes/reconcile.d.ts +9 -0
- package/dist/lib/lanes/reconcile.js +127 -2
- package/dist/lib/lanes/runaway-tool-guard.d.ts +38 -0
- package/dist/lib/lanes/runaway-tool-guard.js +284 -0
- package/dist/lib/lanes/spawn.js +17 -10
- package/dist/lib/lanes/store.js +45 -1
- package/dist/lib/lanes/types.d.ts +37 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.d.ts +2 -0
- package/dist/lib/lanes/watcher.js +13 -2
- package/dist/secrets-env-fast-path.d.ts +5 -0
- package/dist/secrets-env-fast-path.js +16 -0
- package/dist/secrets-env-fast-route.d.ts +18 -0
- package/dist/secrets-env-fast-route.js +54 -0
- package/package.json +2 -2
package/dist/commands/core.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import { Option } from "commander";
|
|
32
32
|
import { registerCoreCheckpointCommand } from "./core-checkpoint.js";
|
|
33
|
+
import { registerCoreDelegateCommand } from "./core-delegate.js";
|
|
33
34
|
import { registerCoreOntologyCommand } from "./core-ontology.js";
|
|
34
35
|
import { registerCoreCheckpointGateSettingCommand } from "./core-checkpoint-gate-setting.js";
|
|
35
36
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
@@ -213,6 +214,14 @@ export const SCAFFOLD_ONLY_ASSETS = [
|
|
|
213
214
|
{ asset: "core/scripts/lib/session-scope-capability.sh", root: "live", summary: "Session scope-capability lib sourced by hq-session" },
|
|
214
215
|
{ asset: "core/scripts/lib/session-id.sh", root: "live", summary: "Session-id resolver lib sourced by hq-session" },
|
|
215
216
|
{ asset: "core/scripts/lib/work-mesh-enqueue.sh", root: "live", summary: "Work Mesh Live spool enqueue lib sourced by hooks" },
|
|
217
|
+
// Wave 1a delegate shells remain bundled as the default-off rollback path.
|
|
218
|
+
// Their roots mirror the original scripts; pickup does not read an HQ root.
|
|
219
|
+
{ asset: "core/scripts/hq-delegate-resolve.sh", root: "live", summary: "Rollback shell for native delegate resolve" },
|
|
220
|
+
{ asset: "core/scripts/hq-delegate-pickup.sh", root: "cwd", summary: "Rollback shell for native delegate pickup (root independent)" },
|
|
221
|
+
{ asset: "core/scripts/hq-delegate-repo.sh", root: "live", summary: "Rollback shell for native delegate repo" },
|
|
222
|
+
{ asset: "core/scripts/hq-delegate-secrets.sh", root: "live", summary: "Rollback shell for native delegate secrets" },
|
|
223
|
+
{ asset: "core/scripts/hq-delegate-transfer.sh", root: "live", summary: "Rollback shell for native delegate transfer" },
|
|
224
|
+
{ asset: "core/scripts/lib/secret-patterns.sh", root: "live", summary: "Secret-pattern library sourced by the delegate rollback shells" },
|
|
216
225
|
...NATIVE_UTILITY_COMMANDS.map(({ asset, root, summary }) => ({
|
|
217
226
|
asset,
|
|
218
227
|
root,
|
|
@@ -288,6 +297,7 @@ export function registerCoreCommands(program) {
|
|
|
288
297
|
registerCoreCheckpointCommand(core);
|
|
289
298
|
registerCoreOntologyCommand(core);
|
|
290
299
|
registerCoreCheckpointGateSettingCommand(core);
|
|
300
|
+
registerCoreDelegateCommand(core);
|
|
291
301
|
// These command names intentionally own native implementations. Their
|
|
292
302
|
// similarly named scaffold assets are forwarders, so sending them through
|
|
293
303
|
// runEntry would recurse back into this command forever.
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import type { Command } from "commander";
|
|
17
17
|
import { type LegacyUnit } from "../lib/daemon/legacy.js";
|
|
18
18
|
import type { HostApp } from "../lib/daemon/supervisor.js";
|
|
19
|
+
import { type SyncModeVaultClient } from "./sync-mode.js";
|
|
19
20
|
import type { RunCommand } from "../lib/service-manager/launchd.js";
|
|
20
21
|
export interface DaemonCommandDeps {
|
|
21
22
|
env: NodeJS.ProcessEnv;
|
|
@@ -38,6 +39,8 @@ export interface DaemonCommandDeps {
|
|
|
38
39
|
userHome: (user: string) => string;
|
|
39
40
|
/** The pid is a live `hq daemon run`. */
|
|
40
41
|
daemonAlive: (pid: number) => boolean;
|
|
42
|
+
/** Test seam for the company sync-mode commands. */
|
|
43
|
+
syncModeClient?: () => Promise<SyncModeVaultClient>;
|
|
41
44
|
windowsUser?: string;
|
|
42
45
|
windowsHeadlessLauncher?: string;
|
|
43
46
|
systemUnitDir?: string;
|
package/dist/commands/daemon.js
CHANGED
|
@@ -18,7 +18,7 @@ import * as os from "node:os";
|
|
|
18
18
|
import * as path from "node:path";
|
|
19
19
|
import { major as semverMajor, valid as validSemver } from "semver";
|
|
20
20
|
import { DEFAULT_SERVICES, SERVICE_DESCRIPTIONS, enabledServices, hqCloudUnits } from "../lib/daemon/catalog.js";
|
|
21
|
-
import { applyServiceToggle, readDaemonConfig, writeDaemonConfig } from "../lib/daemon/config.js";
|
|
21
|
+
import { applyServiceToggle, effectiveSyncOwner, readDaemonConfig, writeDaemonConfig } from "../lib/daemon/config.js";
|
|
22
22
|
import { daemonUnitInstalled } from "../lib/daemon/handoff.js";
|
|
23
23
|
import { adoptLegacyUnits, readAdoptedUnits, restoreLegacyUnits } from "../lib/daemon/migrate.js";
|
|
24
24
|
import { ROOT_REFUSAL, SYSTEM_UNIT_DIR, daemonRootRefusal, installDaemonUnit, installScopeFor, uninstallDaemonUnit, } from "../lib/daemon/install.js";
|
|
@@ -27,7 +27,10 @@ import { isDaemonProcess, runningDaemonPid } from "../lib/daemon/lock.js";
|
|
|
27
27
|
import { bundledHqCloud } from "../lib/daemon/hq-cloud.js";
|
|
28
28
|
import { MACHINE_TYPE_MARKER_PATH, detectMachineType, homeFromPasswd, } from "../lib/daemon/machine-type.js";
|
|
29
29
|
import { daemonPaths } from "../lib/daemon/paths.js";
|
|
30
|
+
import { processCommandLine } from "../lib/daemon/lock.js";
|
|
30
31
|
import { writeControlRequest } from "../lib/daemon/requests.js";
|
|
32
|
+
import { isSyncRunnerCommandLine, readWatchRunnerOwnerStatus, watchRunnerOwnerStatusPath } from "../lib/daemon/sync-runner.js";
|
|
33
|
+
import { getSyncModeForCompany, setSyncMode } from "./sync-mode.js";
|
|
31
34
|
import { resolveDefaultHqRoot } from "../utils/cognito-session.js";
|
|
32
35
|
class DaemonCommandError extends Error {
|
|
33
36
|
}
|
|
@@ -96,6 +99,7 @@ async function loadDefaults(over) {
|
|
|
96
99
|
windowsUser: over.windowsUser ?? (platform === "win32" ? mesh.resolveWindowsUser(env) : ""),
|
|
97
100
|
windowsHeadlessLauncher: over.windowsHeadlessLauncher ?? (platform === "win32" ? mesh.resolveWindowsHeadlessLauncher(env) : undefined),
|
|
98
101
|
systemUnitDir: over.systemUnitDir ?? SYSTEM_UNIT_DIR,
|
|
102
|
+
syncModeClient: over.syncModeClient,
|
|
99
103
|
systemRecordPath: over.systemRecordPath ?? path.join(SYSTEM_ADOPTED_ROOT, "adopted.json"),
|
|
100
104
|
};
|
|
101
105
|
}
|
|
@@ -373,6 +377,135 @@ function queueRequest(d, request, what) {
|
|
|
373
377
|
const running = runningDaemonPid(paths.lockPath, d.daemonAlive) !== null;
|
|
374
378
|
d.out(running ? `Asked the daemon to ${what}.` : `Queued "${what}"; the daemon is not running, so it applies when it starts.`);
|
|
375
379
|
}
|
|
380
|
+
function daemonSyncModeClient(d) {
|
|
381
|
+
if (d.syncModeClient)
|
|
382
|
+
return d.syncModeClient();
|
|
383
|
+
return (async () => {
|
|
384
|
+
const [{ VaultClient }, { buildVaultConfig, ensureCognitoToken }] = await Promise.all([
|
|
385
|
+
import("@indigoai-us/hq-cloud"),
|
|
386
|
+
import("../utils/cognito-session.js"),
|
|
387
|
+
]);
|
|
388
|
+
const token = await ensureCognitoToken();
|
|
389
|
+
return new VaultClient(buildVaultConfig(token));
|
|
390
|
+
})();
|
|
391
|
+
}
|
|
392
|
+
function daemonSyncStatus(d) {
|
|
393
|
+
const host = currentHost(d);
|
|
394
|
+
const { paths } = host;
|
|
395
|
+
const config = readDaemonConfig(paths.configPath);
|
|
396
|
+
const daemonPid = runningDaemonPid(paths.lockPath, d.daemonAlive);
|
|
397
|
+
const daemonState = daemonPid === null ? null : readState(paths);
|
|
398
|
+
const syncUnit = daemonState?.units.find((unit) => unit.id === "sync");
|
|
399
|
+
const hqRoot = config.hqRoot ?? d.env.HQ_ROOT?.trim();
|
|
400
|
+
let ownerStatusCandidate = null;
|
|
401
|
+
if (hqRoot) {
|
|
402
|
+
try {
|
|
403
|
+
ownerStatusCandidate = readWatchRunnerOwnerStatus(watchRunnerOwnerStatusPath(hqRoot, d.home, d.env));
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
407
|
+
d.err(`Could not read sync runner owner status; treating it as unavailable: ${detail}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const ownerCommandLine = ownerStatusCandidate
|
|
411
|
+
? processCommandLine(ownerStatusCandidate.pid, { platform: d.platform, run: d.run })
|
|
412
|
+
: null;
|
|
413
|
+
const ownerStatus = ownerCommandLine && isSyncRunnerCommandLine(ownerCommandLine)
|
|
414
|
+
? ownerStatusCandidate
|
|
415
|
+
: null;
|
|
416
|
+
return {
|
|
417
|
+
running: syncUnit?.status === "running",
|
|
418
|
+
paused: config.syncPaused === true,
|
|
419
|
+
syncOwner: effectiveSyncOwner(config, host.detected.type),
|
|
420
|
+
owner: ownerStatus?.owner ?? (syncUnit?.status === "running" ? "hq-daemon" : null),
|
|
421
|
+
runnerPid: ownerStatus?.pid ?? syncUnit?.pid ?? null,
|
|
422
|
+
hqCloudVersion: ownerStatus?.version ?? null,
|
|
423
|
+
lastHeartbeat: syncUnit?.lastHeartbeatAt ?? ownerStatus?.heartbeatAt ?? null,
|
|
424
|
+
lastPassResult: syncUnit?.lastPassResult ?? null,
|
|
425
|
+
unitStatus: syncUnit?.status ?? "not-running",
|
|
426
|
+
reason: syncUnit?.reason ?? null,
|
|
427
|
+
logPath: paths.unitLogPath("sync"),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
async function runDaemonSyncStatus(d, opts) {
|
|
431
|
+
const status = daemonSyncStatus(d);
|
|
432
|
+
if (opts.json) {
|
|
433
|
+
d.out(JSON.stringify(status, null, 2));
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
d.out(`Sync ${status.running ? "running" : status.unitStatus}${status.paused ? " (paused)" : ""}.`);
|
|
437
|
+
d.out(`Configured sync owner: ${status.syncOwner}.`);
|
|
438
|
+
d.out(`Owner: ${status.owner ?? "none"}; runner pid: ${status.runnerPid ?? "none"}; hq-cloud: ${status.hqCloudVersion ?? "unknown"}.`);
|
|
439
|
+
if (status.reason)
|
|
440
|
+
d.out(`Reason: ${status.reason}`);
|
|
441
|
+
d.out(`Log: ${status.logPath}`);
|
|
442
|
+
}
|
|
443
|
+
function queueDaemonSyncControl(d, request) {
|
|
444
|
+
const host = currentHost(d);
|
|
445
|
+
writeControlRequest(host.paths.requestsDir, request);
|
|
446
|
+
handOver(d, host);
|
|
447
|
+
return runningDaemonPid(host.paths.lockPath, d.daemonAlive) !== null;
|
|
448
|
+
}
|
|
449
|
+
async function runDaemonSyncNow(d, opts) {
|
|
450
|
+
const host = currentHost(d);
|
|
451
|
+
const config = readDaemonConfig(host.paths.configPath);
|
|
452
|
+
if (config.syncPaused)
|
|
453
|
+
throw new DaemonCommandError("sync is paused; run `hq daemon sync resume` first");
|
|
454
|
+
if (!enabledServices(host.detected.type, config).includes("sync")) {
|
|
455
|
+
throw new DaemonCommandError("sync is disabled for this machine; run `hq daemon enable sync` first");
|
|
456
|
+
}
|
|
457
|
+
const running = queueDaemonSyncControl(d, { action: "restart", unit: "sync" });
|
|
458
|
+
const result = { accepted: true, action: "restart-sync-runner", daemonRunning: running };
|
|
459
|
+
d.out(opts.json ? JSON.stringify(result, null, 2) : running ? "Asked the daemon to start a fresh sync pass." : "Queued a fresh sync pass for the next daemon start.");
|
|
460
|
+
}
|
|
461
|
+
async function runDaemonSyncPause(d, paused, opts) {
|
|
462
|
+
const host = currentHost(d);
|
|
463
|
+
const config = readDaemonConfig(host.paths.configPath);
|
|
464
|
+
if (config.syncPaused !== paused)
|
|
465
|
+
writeDaemonConfig(host.paths.configPath, { ...config, syncPaused: paused });
|
|
466
|
+
const daemonRunning = queueDaemonSyncControl(d, { action: "reconcile" });
|
|
467
|
+
const result = { paused, persisted: true, daemonRunning };
|
|
468
|
+
d.out(opts.json ? JSON.stringify(result, null, 2) : `Sync ${paused ? "paused" : "resumed"}${daemonRunning ? "" : "; the setting will apply when the daemon starts"}.`);
|
|
469
|
+
}
|
|
470
|
+
async function runDaemonSyncTakeOwnership(d, opts) {
|
|
471
|
+
const host = currentHost(d);
|
|
472
|
+
const config = readDaemonConfig(host.paths.configPath);
|
|
473
|
+
if (config.syncOwner !== "daemon")
|
|
474
|
+
writeDaemonConfig(host.paths.configPath, { ...config, syncOwner: "daemon" });
|
|
475
|
+
const daemonRunning = queueDaemonSyncControl(d, { action: "reconcile" });
|
|
476
|
+
const result = { syncOwner: "daemon", persisted: true, daemonRunning };
|
|
477
|
+
d.out(opts.json ? JSON.stringify(result, null, 2) : `Daemon sync ownership persisted${daemonRunning ? "; asked the daemon to reconcile" : "; it applies when the daemon starts"}.`);
|
|
478
|
+
}
|
|
479
|
+
async function runDaemonSyncModeGet(d, companySlug, opts) {
|
|
480
|
+
const result = await getSyncModeForCompany({ companySlug, vaultClient: await daemonSyncModeClient(d) });
|
|
481
|
+
const value = {
|
|
482
|
+
companySlug: result.companySlug,
|
|
483
|
+
membershipId: result.membershipId,
|
|
484
|
+
mode: result.config.syncMode,
|
|
485
|
+
isDefault: result.config.isDefault,
|
|
486
|
+
...(result.config.updatedAt ? { updatedAt: result.config.updatedAt } : {}),
|
|
487
|
+
};
|
|
488
|
+
d.out(opts.json ? JSON.stringify(value, null, 2) : `${value.companySlug}: ${value.mode}${value.isDefault ? " (default)" : ""}`);
|
|
489
|
+
}
|
|
490
|
+
async function runDaemonSyncModeSet(d, companySlug, mode, opts) {
|
|
491
|
+
if (mode !== "all" && mode !== "shared") {
|
|
492
|
+
throw new DaemonCommandError(`Invalid sync mode '${mode}'. Use all or shared.`);
|
|
493
|
+
}
|
|
494
|
+
const result = await setSyncMode({
|
|
495
|
+
mode,
|
|
496
|
+
companySlug,
|
|
497
|
+
vaultClient: await daemonSyncModeClient(d),
|
|
498
|
+
});
|
|
499
|
+
const value = {
|
|
500
|
+
companySlug: result.companySlug,
|
|
501
|
+
membershipId: result.membershipId,
|
|
502
|
+
previousMode: result.previousMode,
|
|
503
|
+
mode: result.newMode,
|
|
504
|
+
isDefault: result.newConfig.isDefault,
|
|
505
|
+
...(result.newConfig.updatedAt ? { updatedAt: result.newConfig.updatedAt } : {}),
|
|
506
|
+
};
|
|
507
|
+
d.out(opts.json ? JSON.stringify(value, null, 2) : `${value.companySlug}: ${value.previousMode} → ${value.mode}`);
|
|
508
|
+
}
|
|
376
509
|
async function runToggle(d, action, services) {
|
|
377
510
|
const host = currentHost(d);
|
|
378
511
|
const { paths } = host;
|
|
@@ -609,6 +742,45 @@ export function registerDaemonCommand(program, over = {}) {
|
|
|
609
742
|
.description("Show the machine type and each service's state")
|
|
610
743
|
.option("--json", "Print machine-readable JSON")
|
|
611
744
|
.action(wrap((d, opts) => runStatus(d, opts)));
|
|
745
|
+
const sync = daemon
|
|
746
|
+
.command("sync")
|
|
747
|
+
.description("Inspect and control the daemon's company sync runner");
|
|
748
|
+
sync
|
|
749
|
+
.command("status")
|
|
750
|
+
.description("Show sync ownership, heartbeat, last pass and log path")
|
|
751
|
+
.option("--json", "Print machine-readable JSON")
|
|
752
|
+
.action(wrap((d, opts) => runDaemonSyncStatus(d, opts)));
|
|
753
|
+
sync
|
|
754
|
+
.command("now")
|
|
755
|
+
.description("Start a fresh sync pass immediately")
|
|
756
|
+
.option("--json", "Print machine-readable JSON")
|
|
757
|
+
.action(wrap((d, opts) => runDaemonSyncNow(d, opts)));
|
|
758
|
+
sync
|
|
759
|
+
.command("pause")
|
|
760
|
+
.description("Pause daemon sync until resumed")
|
|
761
|
+
.option("--json", "Print machine-readable JSON")
|
|
762
|
+
.action(wrap((d, opts) => runDaemonSyncPause(d, true, opts)));
|
|
763
|
+
sync
|
|
764
|
+
.command("resume")
|
|
765
|
+
.description("Resume daemon sync after a pause")
|
|
766
|
+
.option("--json", "Print machine-readable JSON")
|
|
767
|
+
.action(wrap((d, opts) => runDaemonSyncPause(d, false, opts)));
|
|
768
|
+
sync
|
|
769
|
+
.command("take-ownership")
|
|
770
|
+
.description("Persist daemon ownership of desktop sync and reconcile")
|
|
771
|
+
.option("--json", "Print machine-readable JSON")
|
|
772
|
+
.action(wrap((d, opts) => runDaemonSyncTakeOwnership(d, opts)));
|
|
773
|
+
const mode = sync.command("mode").description("Read or change a company's sync footprint");
|
|
774
|
+
mode
|
|
775
|
+
.command("get <company>")
|
|
776
|
+
.description("Read a company's current all/shared sync mode")
|
|
777
|
+
.option("--json", "Print machine-readable JSON")
|
|
778
|
+
.action(wrap((d, company, opts) => runDaemonSyncModeGet(d, company, opts)));
|
|
779
|
+
mode
|
|
780
|
+
.command("set <company> <mode>")
|
|
781
|
+
.description("Set a company's sync mode to all or shared")
|
|
782
|
+
.option("--json", "Print machine-readable JSON")
|
|
783
|
+
.action(wrap((d, company, mode, opts) => runDaemonSyncModeSet(d, company, mode, opts)));
|
|
612
784
|
daemon
|
|
613
785
|
.command("logs [unit]")
|
|
614
786
|
.description("Print the supervisor log, or one service's log")
|
package/dist/commands/dm.d.ts
CHANGED
|
@@ -368,5 +368,7 @@ export declare function agentCallerUid(token: string, machine?: () => boolean):
|
|
|
368
368
|
export declare const AGENT_THREAD_UNAVAILABLE: string;
|
|
369
369
|
/** One-line summary of an agent inbox item (DM, email, … — shapes vary by channel). Pure. */
|
|
370
370
|
export declare function formatAgentInboxItem(item: Record<string, unknown>): string;
|
|
371
|
+
/** Remove delegated execution context before agent inbox items can be emitted. */
|
|
372
|
+
export declare function redactAgentInboxItem<T extends object>(item: T): Omit<T, "actingContext">;
|
|
371
373
|
export declare function registerDmCommand(program: Command): void;
|
|
372
374
|
//# sourceMappingURL=dm.d.ts.map
|
package/dist/commands/dm.js
CHANGED
|
@@ -1064,16 +1064,22 @@ export function formatAgentInboxItem(item) {
|
|
|
1064
1064
|
const text = str("text") || str("body") || str("subject");
|
|
1065
1065
|
return `${chalk.bold(from)} ${chalk.dim(`[${channel}] ${id}${when ? ` ${when}` : ""}`)}\n ${firstLine(text)}`;
|
|
1066
1066
|
}
|
|
1067
|
+
/** Remove delegated execution context before agent inbox items can be emitted. */
|
|
1068
|
+
export function redactAgentInboxItem(item) {
|
|
1069
|
+
const { actingContext: _actingContext, ...safe } = item;
|
|
1070
|
+
return safe;
|
|
1071
|
+
}
|
|
1067
1072
|
async function runAgentInbox(token, agentUid, opts) {
|
|
1068
1073
|
const route = `/v1/agents/${encodeURIComponent(agentUid)}/inbox`;
|
|
1069
1074
|
const res = await vaultApiFetch({ token, path: route });
|
|
1070
1075
|
if (!res.ok) {
|
|
1071
|
-
|
|
1072
|
-
|
|
1076
|
+
// Error bodies are not constrained to the success response shape. Do not
|
|
1077
|
+
// echo them: a proxy or partial failure could include inbox items.
|
|
1078
|
+
console.error(chalk.red(`GET ${route} → ${res.status}`));
|
|
1073
1079
|
process.exit(1);
|
|
1074
1080
|
}
|
|
1075
1081
|
const data = (await res.json().catch(() => ({})));
|
|
1076
|
-
let items = Array.isArray(data.messages) ? data.messages : [];
|
|
1082
|
+
let items = (Array.isArray(data.messages) ? data.messages : []).map(redactAgentInboxItem);
|
|
1077
1083
|
if (opts.limit && Number(opts.limit) > 0)
|
|
1078
1084
|
items = items.slice(0, Number(opts.limit));
|
|
1079
1085
|
if (opts.json) {
|
|
@@ -1251,7 +1257,8 @@ async function runDmChannel(target, opts) {
|
|
|
1251
1257
|
}
|
|
1252
1258
|
const data = (await res.json());
|
|
1253
1259
|
const messages = data.messages ?? [];
|
|
1254
|
-
const
|
|
1260
|
+
const agentCaller = agentCallerUid(token) !== null;
|
|
1261
|
+
const ordered = [...messages].reverse().map((message) => agentCaller ? redactAgentInboxItem(message) : message); // oldest-first for reading; agent JSON must not expose delegated context
|
|
1255
1262
|
if (opts.json) {
|
|
1256
1263
|
console.log(JSON.stringify(ordered, null, 2));
|
|
1257
1264
|
}
|
|
@@ -130,6 +130,10 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
130
130
|
return {
|
|
131
131
|
...status,
|
|
132
132
|
effective_home: status.home,
|
|
133
|
+
...(status.auth_state === "home_missing" ? {
|
|
134
|
+
error: "home_missing",
|
|
135
|
+
fix_command: `hq lanes accounts set-home ${provider} ${status.id} <host-home>`,
|
|
136
|
+
} : {}),
|
|
133
137
|
model: accountCfg?.model ?? null,
|
|
134
138
|
effort: accountCfg?.effort ?? null,
|
|
135
139
|
effective_model: model ?? null,
|
|
@@ -165,6 +169,7 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
165
169
|
// Retain the established response field for clients; balancing is now
|
|
166
170
|
// unconditional, so this compatibility value is always true.
|
|
167
171
|
const balanceGateEnabled = true;
|
|
172
|
+
const recentUsageEnabled = await deps.readClaudeAccountBalanceSignalsEnabled(ctx);
|
|
168
173
|
const loads = accountLoadsForReport({ hosts: loaded.snapshots, config });
|
|
169
174
|
const suggestions = planClaudeBalanceMoves({ hosts: loaded.snapshots, config });
|
|
170
175
|
const selections = loaded.snapshots.map((host) => {
|
|
@@ -173,6 +178,8 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
173
178
|
hosts: loaded.snapshots,
|
|
174
179
|
targetHost: host.capacity.host,
|
|
175
180
|
config,
|
|
181
|
+
recentUsageEnabled,
|
|
182
|
+
now: ctx.now(),
|
|
176
183
|
unavailableHosts: loaded.unavailableHosts,
|
|
177
184
|
});
|
|
178
185
|
return {
|
|
@@ -192,11 +199,19 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
192
199
|
});
|
|
193
200
|
const accounts = Object.keys(config.providers.claude.accounts).sort().map((account) => {
|
|
194
201
|
const load = loads.find((row) => row.account === account);
|
|
202
|
+
const currentStatus = loaded.snapshots[0]?.capacity.providers.claude.accounts[account];
|
|
203
|
+
const currentHomeMissing = config.providers.claude.accounts[account]?.enabled === true &&
|
|
204
|
+
currentStatus?.auth_state === "home_missing";
|
|
195
205
|
return {
|
|
196
206
|
account,
|
|
197
207
|
enabled: config.providers.claude.accounts[account]?.enabled === true,
|
|
198
208
|
live_lanes: load?.live_lanes ?? 0,
|
|
199
209
|
counts_complete: load?.counts_complete ?? false,
|
|
210
|
+
...(load ? { recent_usage_at: load.recent_usage_at } : {}),
|
|
211
|
+
...(currentHomeMissing ? {
|
|
212
|
+
error: "home_missing",
|
|
213
|
+
fix_command: `hq lanes accounts set-home claude ${account} <host-home>`,
|
|
214
|
+
} : {}),
|
|
200
215
|
hosts: loaded.snapshots.map((host) => {
|
|
201
216
|
const accountStatus = host.capacity.providers.claude.accounts[account];
|
|
202
217
|
const accountLoad = load?.hosts.find((row) => row.host.kind === host.capacity.host.kind && row.host.id === host.capacity.host.id);
|
|
@@ -204,6 +219,11 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
204
219
|
host: host.capacity.host,
|
|
205
220
|
enabled: accountStatus?.enabled ?? false,
|
|
206
221
|
logged_in: accountStatus?.logged_in ?? null,
|
|
222
|
+
...(accountStatus?.auth_state !== undefined ? { auth_state: accountStatus.auth_state } : {}),
|
|
223
|
+
...(accountStatus?.auth_state === "home_missing" ? {
|
|
224
|
+
error: "home_missing",
|
|
225
|
+
fix_command: `hq lanes accounts set-home claude ${account} <host-home>`,
|
|
226
|
+
} : {}),
|
|
207
227
|
live_lanes: accountLoad?.live_lanes ?? null,
|
|
208
228
|
lane_counts: accountLoad?.lane_counts ?? { running: null, queued: null, awaiting_input: null },
|
|
209
229
|
count_source: accountLoad?.count_source ?? "unavailable",
|
|
@@ -251,6 +271,10 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
251
271
|
suggestions,
|
|
252
272
|
...(opts.apply ? { moved, skipped } : {}),
|
|
253
273
|
});
|
|
274
|
+
const currentHostMissing = Object.entries(config.providers.claude.accounts).some(([account, accountConfig]) => accountConfig.enabled &&
|
|
275
|
+
loaded.snapshots[0]?.capacity.providers.claude.accounts[account]?.auth_state === "home_missing");
|
|
276
|
+
if (currentHostMissing)
|
|
277
|
+
process.exitCode = 1;
|
|
254
278
|
}
|
|
255
279
|
async function runAccountsLogin(ctx, providerRaw, accountId, opts) {
|
|
256
280
|
const callerCwd = ctx.callerCwd();
|
|
@@ -276,6 +300,12 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
276
300
|
if (opts.company !== undefined) {
|
|
277
301
|
await resolveSelectedAccountHomeCompany(ctx, opts.company);
|
|
278
302
|
}
|
|
303
|
+
if (process.platform === "linux" &&
|
|
304
|
+
requestedHome === undefined &&
|
|
305
|
+
registeredAccount !== undefined &&
|
|
306
|
+
effectiveAccountHome(registeredAccount, hostname).source !== "host") {
|
|
307
|
+
throw new LanesError("account_host_home_required", `Account ${id} for ${provider} has no home for this Linux host; pass --home <host-home> to store a host-specific home`, { provider, account: id, hostname });
|
|
308
|
+
}
|
|
279
309
|
const homeRaw = requestedHome ??
|
|
280
310
|
(registeredAccount !== undefined
|
|
281
311
|
? effectiveAccountHome(registeredAccount, hostname).home
|
|
@@ -291,10 +321,22 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
291
321
|
existsSync: (p) => fs.existsSync(p),
|
|
292
322
|
});
|
|
293
323
|
const nextAccount = registeredAccount === undefined
|
|
294
|
-
?
|
|
324
|
+
? process.platform === "linux"
|
|
325
|
+
? {
|
|
326
|
+
home: id === "a" ? DEFAULT_ACCOUNT_HOMES[provider] : `~/.${provider}-${id}`,
|
|
327
|
+
hosts: { [hostname]: { home } },
|
|
328
|
+
enabled: true,
|
|
329
|
+
}
|
|
330
|
+
: { home, enabled: true }
|
|
295
331
|
: { ...registeredAccount, enabled: true };
|
|
296
332
|
if (registeredAccount !== undefined && requestedHome !== undefined) {
|
|
297
|
-
if (
|
|
333
|
+
if (process.platform === "linux") {
|
|
334
|
+
nextAccount.hosts = {
|
|
335
|
+
...(registeredAccount.hosts ?? {}),
|
|
336
|
+
[hostname]: { home },
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
else if (home === registeredAccount.home) {
|
|
298
340
|
if (nextAccount.hosts !== undefined) {
|
|
299
341
|
delete nextAccount.hosts[hostname];
|
|
300
342
|
if (Object.keys(nextAccount.hosts).length === 0)
|
|
@@ -9,7 +9,7 @@ import { dropboxInboxHasMessages, drainDropbox, formatDropboxMessages, formatMon
|
|
|
9
9
|
import { isPidAlive, runWatcherLoop, WATCHER_OUT_MAX_BYTES, watcherOutLogPath, writeWatcherDecision } from "../lib/lanes/watcher.js";
|
|
10
10
|
import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
|
|
11
11
|
import { laneRunDir, probePidRecord, readPidRecord, readProcStarttime, spawnLaneWorker } from "../lib/lanes/spawn.js";
|
|
12
|
-
import { keepAliveExclusion, keepAliveSupersededBy, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds } from "../lib/lanes/keep-alive.js";
|
|
12
|
+
import { keepAliveExclusion, keepAliveSupersededBy, LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds } from "../lib/lanes/keep-alive.js";
|
|
13
13
|
import { managerStatusGateEnabledForLane } from "../lib/lanes/manager-status-gate.js";
|
|
14
14
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
15
15
|
import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
|
|
@@ -90,20 +90,20 @@ export function createLaneMessageHandlers(deps) {
|
|
|
90
90
|
company: companyForLane(lane),
|
|
91
91
|
env: ctx.env,
|
|
92
92
|
});
|
|
93
|
-
|
|
93
|
+
let currentLane = readLane(ctx.hqRoot(), lane.lane_id);
|
|
94
94
|
if (currentLane.state === "killed" || currentLane.state === "cancelled") {
|
|
95
95
|
return { lane: currentLane, exclusion: currentLane.state };
|
|
96
96
|
}
|
|
97
97
|
const localLanes = loadAllLanes(ctx.hqRoot());
|
|
98
98
|
let lanes = localLanes;
|
|
99
|
-
|
|
99
|
+
let retiredLaneIds = loadRetiredLaneIds(ctx.hqRoot());
|
|
100
100
|
const dispatchedCallerId = grantContext?.superseded_message_caller_id;
|
|
101
101
|
const dispatchedSessionId = grantContext?.superseded_message_session_id;
|
|
102
|
-
|
|
102
|
+
let dispatchedContextBound = grantContext !== undefined &&
|
|
103
103
|
ctx.isSsmOwnerDispatch() &&
|
|
104
104
|
sameHireGrantSenior(grantContext.expected_senior, currentLane.senior);
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
let dispatchedSessionContext = dispatchedContextBound && currentLane.senior.kind === "session";
|
|
106
|
+
let dispatchedContextAuthorized = dispatchedContextBound && (dispatchedCallerId !== undefined ||
|
|
107
107
|
(dispatchedSessionId !== undefined && dispatchedSessionId === currentLane.senior.id));
|
|
108
108
|
if (dispatchedContextBound && grantContext &&
|
|
109
109
|
(dispatchedCallerId !== undefined || dispatchedSessionContext)) {
|
|
@@ -116,7 +116,61 @@ export function createLaneMessageHandlers(deps) {
|
|
|
116
116
|
merged.set(currentLane.lane_id, currentLane);
|
|
117
117
|
lanes = [...merged.values()];
|
|
118
118
|
}
|
|
119
|
-
|
|
119
|
+
let exclusion = keepAliveExclusion(currentLane, lanes, retiredLaneIds, lineageAware, companyForLane);
|
|
120
|
+
if (exclusion === "superseded" && currentLane.state !== "done" && currentLane.state !== "interrupted") {
|
|
121
|
+
const runningSupersededMessageDeliveryEnabled = await readLanesRegistryFlag(LANE_RUNNING_SUPERSEDED_MESSAGE_DELIVERY_FLAG, { company: companyForLane(currentLane), env: ctx.env });
|
|
122
|
+
if (runningSupersededMessageDeliveryEnabled) {
|
|
123
|
+
const latestLane = readLane(ctx.hqRoot(), currentLane.lane_id);
|
|
124
|
+
if (latestLane.state === "killed" || latestLane.state === "cancelled") {
|
|
125
|
+
return { lane: latestLane, exclusion: latestLane.state };
|
|
126
|
+
}
|
|
127
|
+
const latestLocalLanes = loadAllLanes(ctx.hqRoot());
|
|
128
|
+
currentLane = latestLane;
|
|
129
|
+
let latestLanes = latestLocalLanes;
|
|
130
|
+
dispatchedContextBound =
|
|
131
|
+
grantContext !== undefined &&
|
|
132
|
+
ctx.isSsmOwnerDispatch() &&
|
|
133
|
+
sameHireGrantSenior(grantContext.expected_senior, currentLane.senior);
|
|
134
|
+
dispatchedSessionContext = dispatchedContextBound && currentLane.senior.kind === "session";
|
|
135
|
+
dispatchedContextAuthorized = dispatchedContextBound && (dispatchedCallerId !== undefined ||
|
|
136
|
+
(dispatchedSessionId !== undefined && dispatchedSessionId === currentLane.senior.id));
|
|
137
|
+
if (dispatchedContextBound && grantContext &&
|
|
138
|
+
(dispatchedCallerId !== undefined || dispatchedSessionContext)) {
|
|
139
|
+
const merged = new Map(latestLocalLanes.map((record) => [record.lane_id, record]));
|
|
140
|
+
for (const snapshot of grantContext.hierarchy) {
|
|
141
|
+
const contextLane = keepAliveLaneFromSnapshot(snapshot);
|
|
142
|
+
if (contextLane)
|
|
143
|
+
merged.set(contextLane.lane_id, contextLane);
|
|
144
|
+
}
|
|
145
|
+
merged.set(currentLane.lane_id, currentLane);
|
|
146
|
+
latestLanes = [...merged.values()];
|
|
147
|
+
}
|
|
148
|
+
lanes = latestLanes;
|
|
149
|
+
retiredLaneIds = loadRetiredLaneIds(ctx.hqRoot());
|
|
150
|
+
exclusion = keepAliveExclusion(currentLane, lanes, retiredLaneIds, lineageAware, companyForLane);
|
|
151
|
+
if (exclusion === "superseded" && currentLane.state !== "done" && currentLane.state !== "interrupted") {
|
|
152
|
+
const callerLaneId = dispatchedContextBound ? dispatchedCallerId : undefined;
|
|
153
|
+
const callerSessionId = dispatchedContextBound
|
|
154
|
+
? (dispatchedSessionId === undefined ? null : dispatchedSessionId)
|
|
155
|
+
: undefined;
|
|
156
|
+
const callerMayMessage = callerMayMessageSupersededLane(ctx, currentLane, lanes, lineageAware, companyForLane, callerLaneId, callerSessionId);
|
|
157
|
+
return {
|
|
158
|
+
lane: currentLane,
|
|
159
|
+
exclusion: undefined,
|
|
160
|
+
...(!dispatchedContextAuthorized && callerMayMessage && ctx.env.HQ_LANE_ID?.trim()
|
|
161
|
+
? { supersededMessageCallerId: ctx.env.HQ_LANE_ID.trim() }
|
|
162
|
+
: {}),
|
|
163
|
+
...(callerMayMessage && !dispatchedContextBound && currentLane.senior.kind === "session" &&
|
|
164
|
+
typeof currentLane.senior.id === "string" &&
|
|
165
|
+
currentLane.senior.id.length > 0 &&
|
|
166
|
+
callerMatchesSessionSenior(ctx, currentLane.senior.id)
|
|
167
|
+
? { supersededMessageSessionId: currentLane.senior.id }
|
|
168
|
+
: {}),
|
|
169
|
+
supersededMessageWarning: `A newer lead supersedes this ${currentLane.state} lane; message delivery is allowed while the lane remains active.`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
120
174
|
const callerLaneId = dispatchedContextBound ? dispatchedCallerId : undefined;
|
|
121
175
|
const callerSessionId = dispatchedContextBound
|
|
122
176
|
? (dispatchedSessionId === undefined ? null : dispatchedSessionId)
|
|
@@ -552,11 +606,14 @@ export function createLaneMessageHandlers(deps) {
|
|
|
552
606
|
: mechanism === "dropbox"
|
|
553
607
|
? "awaiting_next_tool_call"
|
|
554
608
|
: "worker_spawned";
|
|
555
|
-
const warning =
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
? `The
|
|
559
|
-
:
|
|
609
|
+
const warning = [
|
|
610
|
+
eligibility.supersededMessageWarning,
|
|
611
|
+
mechanism === "dropbox" && drainHook !== null && !drainHook.registered
|
|
612
|
+
? `The drain hook is not registered in ${drainHook.path}; the running worker will not receive this message. It stays in the inbox and is delivered on the lane's next start.`
|
|
613
|
+
: mechanism === "dropbox" && next.stalled === true
|
|
614
|
+
? `The worker has made no tool call since ${next.stalled_at ?? "an unknown time"} (${next.stalled_reason ?? "stalled"}); delivery waits on its next tool call.`
|
|
615
|
+
: null,
|
|
616
|
+
].filter((message) => message !== undefined && message !== null).join(" ") || null;
|
|
560
617
|
if (!opts.silent)
|
|
561
618
|
printJson({
|
|
562
619
|
ok: true,
|
|
@@ -223,6 +223,7 @@ export function createLaneResumeHandlers(deps) {
|
|
|
223
223
|
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
224
224
|
...(profileRepin !== undefined ? { profileRepin } : {}),
|
|
225
225
|
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
226
|
+
...(hireGrantContext !== undefined ? { grantContext: hireGrantContext } : {}),
|
|
226
227
|
pending: {
|
|
227
228
|
trigger: "resume",
|
|
228
229
|
resumed_from: resumedFrom,
|
|
@@ -252,6 +253,7 @@ export function createLaneResumeHandlers(deps) {
|
|
|
252
253
|
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
253
254
|
...(profileRepin !== undefined ? { profileRepin } : {}),
|
|
254
255
|
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
256
|
+
...(hireGrantContext !== undefined ? { grantContext: hireGrantContext } : {}),
|
|
255
257
|
pending: {
|
|
256
258
|
trigger: "resume",
|
|
257
259
|
resumed_from: resumedFrom,
|
|
@@ -245,9 +245,11 @@ export interface Ctx {
|
|
|
245
245
|
mergeReviewEnabled?: (company: LaneCompanyRef) => boolean | Promise<boolean>;
|
|
246
246
|
}
|
|
247
247
|
export declare const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-balancing";
|
|
248
|
+
export declare const CLAUDE_ACCOUNT_BALANCE_SIGNALS_FLAG = "lanes.claude-account-balance-signals";
|
|
248
249
|
export declare function defaultLanePlacementEnabled(company: LaneCompanyRef | undefined): Promise<boolean>;
|
|
249
250
|
export declare function readLanePlacementEnabled(ctx: Ctx, company: LaneCompanyRef | undefined): Promise<boolean>;
|
|
250
251
|
export declare function processLanePlacementCompany(ctx: Ctx): LaneCompanyRef | undefined;
|
|
252
|
+
export declare function readClaudeAccountBalanceSignalsEnabled(ctx: Ctx, company?: LaneCompanyRef): Promise<boolean>;
|
|
251
253
|
export declare function parsePlacementPolicy(raw: string): LanePlacementPolicy;
|
|
252
254
|
export declare function assertPlacementEnabled(ctx: Ctx, company: LaneCompanyRef | undefined, readPlacementEnabled?: () => Promise<boolean>): Promise<void>;
|
|
253
255
|
/** Match a direct session-senior caller while refusing lane-worker identities. */
|
|
@@ -349,6 +351,7 @@ export interface LaneAccountsHandlerDeps {
|
|
|
349
351
|
to: string;
|
|
350
352
|
}>;
|
|
351
353
|
printJson(value: unknown): void;
|
|
354
|
+
readClaudeAccountBalanceSignalsEnabled(ctx: Ctx): Promise<boolean>;
|
|
352
355
|
reconcileHostFromCtx(ctx: Ctx): ReconcileHost;
|
|
353
356
|
resolveCallerPath(callerCwd: string, filePath: string): string;
|
|
354
357
|
}
|
|
@@ -72,9 +72,9 @@ export function resolveResumeEffort(ctx, lane, input) {
|
|
|
72
72
|
? undefined
|
|
73
73
|
: providerConfig.accounts[accountId];
|
|
74
74
|
const profileDefaultsApply = input.profile?.profile.meta.provider === lane.provider;
|
|
75
|
-
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli" || lane.model_source === "preserved"
|
|
75
|
+
const pinnedModel = lane.pending_settings?.model ?? (lane.model_source === undefined || lane.model_source === "cli" || lane.model_source === "preserved"
|
|
76
76
|
? lane.model
|
|
77
|
-
: undefined;
|
|
77
|
+
: undefined);
|
|
78
78
|
const model = resolveLaneModel({
|
|
79
79
|
cliModel: input.model?.trim() || pinnedModel,
|
|
80
80
|
profileModel: profileDefaultsApply ? input.profile?.profile.meta.model : undefined,
|
|
@@ -90,12 +90,16 @@ export function resolveResumeEffort(ctx, lane, input) {
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
export function recordedResumeEffort(lane) {
|
|
93
|
+
if (lane.pending_settings?.effort !== undefined) {
|
|
94
|
+
return { effort: lane.pending_settings.effort, source: "cli" };
|
|
95
|
+
}
|
|
93
96
|
return {
|
|
94
97
|
...(lane.effort !== undefined ? { effort: lane.effort } : {}),
|
|
95
98
|
source: lane.effort_source ?? "none",
|
|
96
99
|
};
|
|
97
100
|
}
|
|
98
101
|
export const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-balancing";
|
|
102
|
+
export const CLAUDE_ACCOUNT_BALANCE_SIGNALS_FLAG = "lanes.claude-account-balance-signals";
|
|
99
103
|
function lanePlacementFlagErrorCode(error) {
|
|
100
104
|
if (error && typeof error === "object") {
|
|
101
105
|
for (const key of ["code", "error", "name"]) {
|
|
@@ -135,6 +139,12 @@ export function processLanePlacementCompany(ctx) {
|
|
|
135
139
|
return undefined;
|
|
136
140
|
return { slug: context.companySlug, uid: context.companyUid ?? "" };
|
|
137
141
|
}
|
|
142
|
+
export function readClaudeAccountBalanceSignalsEnabled(ctx, company) {
|
|
143
|
+
return readLanesRegistryFlag(CLAUDE_ACCOUNT_BALANCE_SIGNALS_FLAG, {
|
|
144
|
+
company: company ?? processLanePlacementCompany(ctx),
|
|
145
|
+
env: ctx.env,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
138
148
|
export function parsePlacementPolicy(raw) {
|
|
139
149
|
const value = raw.trim();
|
|
140
150
|
if (value === "local")
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import { type LanesMesh, type SpawnLoginResult } from "./lanes-shared.js";
|
|
10
10
|
export type { LanesMesh, SpawnLoginResult } from "./lanes-shared.js";
|
|
11
|
-
export { LANE_OUTPOST_LOAD_BALANCING_FLAG } from "./lanes-shared.js";
|
|
11
|
+
export { CLAUDE_ACCOUNT_BALANCE_SIGNALS_FLAG, LANE_OUTPOST_LOAD_BALANCING_FLAG } from "./lanes-shared.js";
|
|
12
12
|
export { resolveAnswerChoice } from "./lanes-questions.js";
|
|
13
13
|
import type { OutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
14
14
|
export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
@@ -139,5 +139,9 @@ export declare function pickupFailureUpdater(reasonRaw: string, details: Record<
|
|
|
139
139
|
export declare function applyOutpostAccountPickup(hqRoot: string, laneId: string, modelDeps?: KnownModelsDeps): LaneRecord;
|
|
140
140
|
export declare function cancelPendingQuestionsForLane(hqRoot: string, laneId: string, nowIso: string, operatorReason?: string): QuestionRecord[];
|
|
141
141
|
export declare function stopCommittedState(state: LaneRecord["state"]): "cancelled" | "killed";
|
|
142
|
+
/** Stderr notice that `stop` ends a lane for good, naming the resumable path. */
|
|
143
|
+
export declare function stopTerminalWarning(laneId: string): string;
|
|
144
|
+
/** Stderr notice when an interrupted lane has no session to resume from. */
|
|
145
|
+
export declare function interruptNotResumableWarning(laneId: string): string;
|
|
142
146
|
export declare function registerLanesCommand(parent: Command, deps?: LanesCommandDeps): Command;
|
|
143
147
|
//# sourceMappingURL=lanes.d.ts.map
|