@indigoai-us/hq-cli 5.345.147 → 5.345.149
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 +65 -0
- package/README.md +2 -0
- package/assets/bot-workers/setup/worker.yaml +11 -0
- package/dist/command-catalog.generated.d.ts +341 -11
- package/dist/command-catalog.generated.js +421 -11
- package/dist/command-help.js +2 -1
- package/dist/commands/access.d.ts +45 -2
- package/dist/commands/access.js +537 -223
- package/dist/commands/agent-inbox.js +2 -1
- package/dist/commands/billing-usage.d.ts +33 -2
- package/dist/commands/billing-usage.js +75 -4
- package/dist/commands/bot.js +2 -2
- package/dist/commands/channels.d.ts +27 -0
- package/dist/commands/channels.js +108 -4
- package/dist/commands/cloud.js +31 -1
- package/dist/commands/core-refresh-vault-access-native.d.ts +33 -0
- package/dist/commands/core-refresh-vault-access-native.js +356 -28
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +9 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.js +26 -19
- package/dist/commands/core.js +9 -0
- package/dist/commands/daemon.d.ts +7 -0
- package/dist/commands/daemon.js +37 -3
- package/dist/commands/dm.js +18 -3
- package/dist/commands/doctor.d.ts +11 -1
- package/dist/commands/doctor.js +56 -1
- package/dist/commands/install-global.d.ts +8 -8
- package/dist/commands/install-global.js +139 -61
- package/dist/commands/lanes-shared.d.ts +3 -1
- package/dist/commands/lanes.d.ts +4 -4
- package/dist/commands/lanes.js +380 -25
- package/dist/commands/library-add.d.ts +31 -1
- package/dist/commands/library-add.js +69 -2
- package/dist/commands/library.js +37 -3
- package/dist/commands/pack-install.d.ts +2 -2
- package/dist/commands/pack-install.js +133 -35
- package/dist/commands/reindex.js +19 -0
- package/dist/commands/sync-manifest.d.ts +2 -9
- package/dist/commands/sync-manifest.js +4 -15
- package/dist/hook-engine-path.d.ts +4 -0
- package/dist/hook-engine-path.js +43 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2783 -2354
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +76 -5
- package/dist/lazy-commands.js +2 -0
- package/dist/lib/agent-kit/inbox-redact.d.ts +44 -0
- package/dist/lib/agent-kit/inbox-redact.js +361 -0
- package/dist/lib/agent-kit/inbox-state.js +11 -2
- package/dist/lib/agent-kit/mcp/tools.js +2 -1
- package/dist/lib/agent-kit/run/inbox.js +36 -7
- package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
- package/dist/lib/anywhere-runtime-flag-key.js +9 -0
- package/dist/lib/billing/usage-cost.d.ts +31 -1
- package/dist/lib/billing/usage-cost.js +46 -0
- package/dist/lib/company-skill-wrapper.js +6 -4
- package/dist/lib/daemon/install.d.ts +47 -1
- package/dist/lib/daemon/install.js +181 -15
- package/dist/lib/daemon/lock.d.ts +32 -0
- package/dist/lib/daemon/lock.js +55 -2
- package/dist/lib/db/local.d.ts +2 -2
- package/dist/lib/db/local.js +2 -2
- package/dist/lib/db/sql.js +2 -2
- package/dist/lib/db/sqlite-binding.d.ts +22 -0
- package/dist/lib/db/sqlite-binding.js +168 -0
- package/dist/lib/doctor/__testing__/run-doctor.js +4 -0
- package/dist/lib/doctor/checks/anywhere.d.ts +15 -0
- package/dist/lib/doctor/checks/anywhere.js +77 -11
- package/dist/lib/doctor/checks/cli-path.d.ts +12 -0
- package/dist/lib/doctor/checks/cli-path.js +46 -0
- package/dist/lib/doctor/checks/hook-engine.d.ts +4 -0
- package/dist/lib/doctor/checks/hook-engine.js +17 -0
- package/dist/lib/doctor/checks/sync-health.d.ts +7 -0
- package/dist/lib/doctor/checks/sync-health.js +38 -2
- package/dist/lib/doctor/checks/temp-homes.d.ts +19 -0
- package/dist/lib/doctor/checks/temp-homes.js +54 -0
- package/dist/lib/doctor/fix/apply.d.ts +90 -1
- package/dist/lib/doctor/fix/apply.js +176 -10
- package/dist/lib/doctor/fix/json-output.d.ts +4 -1
- package/dist/lib/doctor/fix/json-output.js +6 -0
- package/dist/lib/doctor/fix/remediation.d.ts +7 -1
- package/dist/lib/doctor/fix/remediation.js +23 -1
- package/dist/lib/doctor/registry.js +10 -0
- package/dist/lib/doctor/temp-home-sweeper.d.ts +149 -0
- package/dist/lib/doctor/temp-home-sweeper.js +418 -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/flag-registry.js +9 -1
- package/dist/lib/flag-snapshot-file.js +8 -3
- package/dist/lib/hook-engine/reconcile.d.ts +9 -0
- package/dist/lib/hook-engine/reconcile.js +134 -0
- package/dist/lib/hook-engine/registration.d.ts +17 -0
- package/dist/lib/hook-engine/registration.js +50 -0
- package/dist/lib/hook-engine/resolve.d.ts +31 -0
- package/dist/lib/hook-engine/resolve.js +134 -0
- package/dist/lib/install/claude-targets.d.ts +2 -1
- package/dist/lib/install/claude-targets.js +8 -3
- package/dist/lib/install/codex-targets.d.ts +3 -0
- package/dist/lib/install/codex-targets.js +15 -4
- package/dist/lib/install/hook-target.d.ts +9 -0
- package/dist/lib/install/hook-target.js +39 -0
- package/dist/lib/lanes/admission-host.d.ts +1 -0
- package/dist/lib/lanes/admission-host.js +8 -1
- package/dist/lib/lanes/admission.d.ts +4 -0
- package/dist/lib/lanes/admission.js +6 -2
- package/dist/lib/lanes/envelope-repair-dispatch.d.ts +5 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +12 -1
- package/dist/lib/lanes/index.d.ts +2 -2
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/keep-alive.js +4 -1
- package/dist/lib/lanes/local-host-identity.d.ts +40 -0
- package/dist/lib/lanes/local-host-identity.js +314 -0
- package/dist/lib/lanes/reaper.d.ts +1 -1
- package/dist/lib/lanes/reaper.js +7 -0
- package/dist/lib/lanes/reconcile.d.ts +4 -0
- package/dist/lib/lanes/reconcile.js +16 -8
- package/dist/lib/lanes/spawn.d.ts +20 -0
- package/dist/lib/lanes/spawn.js +48 -2
- package/dist/lib/lanes/status-fields.js +6 -1
- package/dist/lib/lanes/store.js +8 -1
- package/dist/lib/lanes/throttle.d.ts +49 -6
- package/dist/lib/lanes/throttle.js +154 -17
- package/dist/lib/lanes/types.d.ts +22 -1
- package/dist/lib/lanes/types.js +27 -3
- package/dist/lib/lanes/watcher.js +27 -2
- package/dist/lib/lanes/worker-continuity.d.ts +2 -0
- package/dist/lib/lanes/worker-continuity.js +18 -11
- package/dist/lib/manifest-disabled.d.ts +13 -0
- package/dist/lib/manifest-disabled.js +19 -0
- package/dist/lib/service-manager/launchd.d.ts +1 -0
- package/dist/lib/service-manager/launchd.js +1 -1
- package/dist/lib/windows/console-encoding.d.ts +24 -0
- package/dist/lib/windows/console-encoding.js +83 -0
- package/dist/lib/windows/install-followup.d.ts +35 -0
- package/dist/lib/windows/install-followup.js +142 -0
- package/dist/lib/windows/user-path.d.ts +125 -0
- package/dist/lib/windows/user-path.js +275 -0
- package/dist/lib/work-context/auto-bind.d.ts +1 -1
- package/dist/lib/work-context/auto-bind.js +22 -3
- package/dist/lib/work-context/lane-bind.d.ts +31 -0
- package/dist/lib/work-context/lane-bind.js +101 -0
- package/dist/lib/work-context/organize.js +1 -0
- package/dist/lib/work-context/prd-sync.js +7 -0
- package/dist/lib/work-context/project.d.ts +36 -10
- package/dist/lib/work-context/project.js +130 -41
- package/dist/lib/work-context/reconcile.d.ts +6 -0
- package/dist/lib/work-context/reconcile.js +25 -5
- package/dist/lib/work-context/state.d.ts +6 -0
- package/dist/lib/work-context/state.js +29 -1
- package/dist/main.js +2 -1
- package/dist/unknown-command.d.ts +18 -0
- package/dist/unknown-command.js +88 -0
- package/dist/utils/cognito-session.d.ts +8 -0
- package/dist/utils/cognito-session.js +14 -2
- package/dist/utils/copy-tree.d.ts +61 -0
- package/dist/utils/copy-tree.js +249 -0
- package/dist/utils/environmental-error.js +7 -3
- package/dist/utils/hook-trust.d.ts +9 -0
- package/dist/utils/hook-trust.js +30 -2
- package/dist/utils/npm-invocation.d.ts +27 -0
- package/dist/utils/npm-invocation.js +54 -0
- package/dist/utils/qmd-native-binding-error.js +3 -1
- package/dist/utils/resolve-executable.d.ts +2 -0
- package/dist/utils/resolve-executable.js +6 -2
- package/dist/utils/safe-link.d.ts +50 -0
- package/dist/utils/safe-link.js +300 -0
- package/package.json +17 -5
- package/scripts/hook-engine-manifest.mjs +22 -0
- package/scripts/install-hook-engine.mjs +10 -0
package/dist/commands/lanes.js
CHANGED
|
@@ -47,6 +47,7 @@ import { appendProjectFocusOverride, readProjectStoryFocusData, removeProjectFoc
|
|
|
47
47
|
import { appendLaneFocusOverride, assertFocusGuardAllows, makeFocusOverride } from "../lib/lanes/focus-guard.js";
|
|
48
48
|
import { LANE_FOCUS_GUARD_FLAG, LANE_FOCUS_DASHBOARD_FLAG, LANE_HIRE_GRANT_FINISHED_LANES_FLAG, LANE_LIST_SKIP_REMOTE_REFRESH_FLAG, LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG, LANE_ORPHANED_BARE_ID_DROPBOX_REPAIR_FLAG, LANE_PROFILE_PIN_EXHAUSTED_FALLBACK_FLAG, LANE_STATUS_ROW_FIELDS_FLAG, LANE_REAP_STOP_FLAG, LANE_REUSE_REFRESHES_PROFILE_FLAG, LANE_WORKER_CONTINUITY_FLAG, LANE_CONDUCT_LOOP_FLAG, LANE_ENVELOPE_REPAIR_GUIDANCE_FLAG, } from "../lib/lanes/flag-keys.js";
|
|
49
49
|
import { applyContinuitySettings, findWorkerContinuityLane, sameLaneHost, workerContinuityPrompt } from "../lib/lanes/worker-continuity.js";
|
|
50
|
+
import { localHostIdsReferToSameMachine, observeLocalHostIdentity, resolveLocalHostIdentity } from "../lib/lanes/local-host-identity.js";
|
|
50
51
|
import { commitIdleLoopStop, enqueueLoopEnvelope, interruptLoopQueue, interruptLoopQueueForStory, loopControllerIsAlive, loopLaneIsIdle, loopStatus, validateLoopEnvelope } from "../lib/lanes/loop.js";
|
|
51
52
|
import { LOOP_CONTROLLER_RECOVERY_REASON, LOOP_STALL_RESTART_REASON, runLaneLoopController, startLaneLoopController } from "./lanes-loop.js";
|
|
52
53
|
import { configuredCodexRolloutHomes, retainCodexRollouts } from "../lib/lanes/disk-guard.js";
|
|
@@ -92,7 +93,9 @@ import { LanesError } from "../lib/lanes/errors.js";
|
|
|
92
93
|
import { LedgerCorrupt, capacityOptionsFromEnv, readCollectorMark, readNow, rebuildNow, } from "../lib/capacity/ledger.js";
|
|
93
94
|
import { pickAccount } from "../lib/capacity/route.js";
|
|
94
95
|
import { capacityViewAge } from "../lib/capacity/freshness.js";
|
|
95
|
-
import { readLatestEnvelopeRecord } from "../lib/lanes/watcher.js";
|
|
96
|
+
import { applyTerminalToLane, classifyLaneExit, readLatestEnvelopeRecord } from "../lib/lanes/watcher.js";
|
|
97
|
+
import { isSubagentLane, parseLaneEnvelope, parseLaneRunner } from "../lib/lanes/types.js";
|
|
98
|
+
import { nextEnvelopePath } from "../lib/lanes/spawn.js";
|
|
96
99
|
import { loadQuotaState, quotaBlockFor } from "../lib/lanes/quota.js";
|
|
97
100
|
import { SESSION_MOVE_REASON } from "../lib/lanes/move-account.js";
|
|
98
101
|
import { readCompaniesManifestMap } from "../lib/work-context/repo-remote.js";
|
|
@@ -101,7 +104,7 @@ import { blockerFreshnessFingerprint, resolveBlockerFreshnessForLane, } from "..
|
|
|
101
104
|
import { resolveKrFreshnessForShow } from "../lib/lanes/kr-freshness.js";
|
|
102
105
|
import { ensureSessionHome } from "../lib/lanes/session-relink.js";
|
|
103
106
|
import { defaultProbe, validateProfileLaneModel, validateLaneModel, } from "../lib/lanes/models.js";
|
|
104
|
-
import { parseCliModel, refuseOutpostSelfTarget, resolveLaneModel, FOREGROUND_ONLY_RULE, STANDING_AUTHORIZATION, spawnLaneWorker, spawnEnvelopeRepairRequest, laneRunDir, readSpawnManifest, stopLaneProcesses, } from "../lib/lanes/spawn.js";
|
|
107
|
+
import { parseCliModel, refuseOutpostSelfTarget, resolveLaneModel, FOREGROUND_ONLY_RULE, STANDING_AUTHORIZATION, tempHomeRule, spawnLaneWorker, spawnEnvelopeRepairRequest, laneRunDir, readSpawnManifest, stopLaneProcesses, } from "../lib/lanes/spawn.js";
|
|
105
108
|
import { handOffInstallHere } from "../lib/daemon/handoff.js";
|
|
106
109
|
export { spawnLaneWorker };
|
|
107
110
|
export function openPullRequestsForProject(hqRoot, metadata) {
|
|
@@ -609,6 +612,7 @@ async function finalizeIdleLoopStop(ctx, lane) {
|
|
|
609
612
|
function reconcileHostFromCtx(ctx) {
|
|
610
613
|
return resolveAdmissionHost({
|
|
611
614
|
hostname: ctx.hostname,
|
|
615
|
+
homedir: ctx.homedir,
|
|
612
616
|
readOutpostIdentity: ctx.readOutpostIdentity,
|
|
613
617
|
});
|
|
614
618
|
}
|
|
@@ -1409,6 +1413,50 @@ function outpostIdentityUnknownError(message, expectedId, details = {}) {
|
|
|
1409
1413
|
fix: `Run ${command} from a registered host, or write ${JSON.stringify(`${expected}\n`)} to ${OUTPOST_ID_PATH}.`,
|
|
1410
1414
|
});
|
|
1411
1415
|
}
|
|
1416
|
+
function localStableHost(ctx) {
|
|
1417
|
+
const hostname = ctx.hostname().trim();
|
|
1418
|
+
if (!hostname) {
|
|
1419
|
+
throw new LanesError("host_identity_unknown", "Cannot resolve this host for lane placement", {});
|
|
1420
|
+
}
|
|
1421
|
+
const identity = resolveLocalHostIdentity({ homedir: ctx.homedir(), hostname });
|
|
1422
|
+
return { kind: "local", id: identity.id };
|
|
1423
|
+
}
|
|
1424
|
+
/** Read the stable id when one exists. Does not create or update `~/.hq/lane-host.json`. */
|
|
1425
|
+
function recordedLocalHost(ctx) {
|
|
1426
|
+
const hostname = ctx.hostname().trim();
|
|
1427
|
+
if (!hostname) {
|
|
1428
|
+
throw new LanesError("host_identity_unknown", "Cannot resolve this host for lane placement", {});
|
|
1429
|
+
}
|
|
1430
|
+
const observed = observeLocalHostIdentity({ homedir: ctx.homedir(), hostname });
|
|
1431
|
+
if (observed)
|
|
1432
|
+
return { kind: "local", id: observed.id };
|
|
1433
|
+
return { kind: "local", id: hostname };
|
|
1434
|
+
}
|
|
1435
|
+
function observedLocalMatch(ctx, stableId) {
|
|
1436
|
+
const hostname = ctx.hostname().trim();
|
|
1437
|
+
const observed = observeLocalHostIdentity({ homedir: ctx.homedir(), hostname });
|
|
1438
|
+
return {
|
|
1439
|
+
hostname,
|
|
1440
|
+
machineId: observed?.id ?? stableId,
|
|
1441
|
+
...(observed ? { aliases: observed.aliases } : {}),
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
function laneCountsOnHost(lane, host, ctx) {
|
|
1445
|
+
if (lane.host.kind !== host.kind)
|
|
1446
|
+
return false;
|
|
1447
|
+
if (host.kind !== "local")
|
|
1448
|
+
return lane.host.id === host.id;
|
|
1449
|
+
if (lane.host.id === host.id)
|
|
1450
|
+
return true;
|
|
1451
|
+
return localHostIdsReferToSameMachine(lane.host.id, observedLocalMatch(ctx, host.id));
|
|
1452
|
+
}
|
|
1453
|
+
function localLaneContinuesHere(ctx, previous, current) {
|
|
1454
|
+
if (sameLaneHost(previous, current))
|
|
1455
|
+
return true;
|
|
1456
|
+
if (previous.kind !== "local" || current.kind !== "local")
|
|
1457
|
+
return false;
|
|
1458
|
+
return localHostIdsReferToSameMachine(previous.id, observedLocalMatch(ctx, current.id));
|
|
1459
|
+
}
|
|
1412
1460
|
function laneHostFromCtx(ctx, expectedId) {
|
|
1413
1461
|
const identity = ctx.readOutpostIdentity();
|
|
1414
1462
|
if (identity.kind === "unknown") {
|
|
@@ -1416,11 +1464,7 @@ function laneHostFromCtx(ctx, expectedId) {
|
|
|
1416
1464
|
}
|
|
1417
1465
|
if (identity.kind === "id")
|
|
1418
1466
|
return { kind: "outpost", id: identity.id };
|
|
1419
|
-
|
|
1420
|
-
if (!hostname) {
|
|
1421
|
-
throw new LanesError("host_identity_unknown", "Cannot resolve this host for lane placement", {});
|
|
1422
|
-
}
|
|
1423
|
-
return { kind: "local", id: hostname };
|
|
1467
|
+
return recordedLocalHost(ctx);
|
|
1424
1468
|
}
|
|
1425
1469
|
async function readOutpostCapacity(ctx, outpostId) {
|
|
1426
1470
|
return readOutpostCapacityUncached(ctx, outpostId);
|
|
@@ -2118,14 +2162,14 @@ async function loadClaudeBalanceHostSnapshots(ctx, hqRoot, config, options = {})
|
|
|
2118
2162
|
});
|
|
2119
2163
|
const snapshots = [{
|
|
2120
2164
|
capacity: local,
|
|
2121
|
-
lanes: localLanes.filter((lane) => lane
|
|
2165
|
+
lanes: localLanes.filter((lane) => laneCountsOnHost(lane, localHost, ctx)),
|
|
2122
2166
|
laneListAvailable: true,
|
|
2123
2167
|
}];
|
|
2124
2168
|
if (localHost.kind === "outpost") {
|
|
2125
2169
|
const hostname = ctx.hostname().trim();
|
|
2126
2170
|
const actualLocalHost = { kind: "local", id: hostname };
|
|
2127
2171
|
const actualLocalLanes = hostname
|
|
2128
|
-
? localLanes.filter((lane) => lane
|
|
2172
|
+
? localLanes.filter((lane) => laneCountsOnHost(lane, actualLocalHost, ctx))
|
|
2129
2173
|
: [];
|
|
2130
2174
|
if (hostname) {
|
|
2131
2175
|
snapshots.push({
|
|
@@ -2759,6 +2803,7 @@ async function applyThrottle(hqRoot, config, ctx, current, provider, pinnedAccou
|
|
|
2759
2803
|
last_busy: state.last_busy ?? null,
|
|
2760
2804
|
busy_source: state.busy_source,
|
|
2761
2805
|
sampled_at: state.sampled_at,
|
|
2806
|
+
...(state.memory_hold !== undefined ? { memory_hold: state.memory_hold } : {}),
|
|
2762
2807
|
};
|
|
2763
2808
|
if (current.action === "queue")
|
|
2764
2809
|
return { action: "queue", reason: "throttle", throttle };
|
|
@@ -2913,7 +2958,7 @@ export function checkpointInstruction(worker) {
|
|
|
2913
2958
|
...names.map((name) => `- ${name}`),
|
|
2914
2959
|
].join("\n");
|
|
2915
2960
|
}
|
|
2916
|
-
export function composeWorkerBrief(worker, taskBrief) {
|
|
2961
|
+
export function composeWorkerBrief(worker, taskBrief, hqRoot) {
|
|
2917
2962
|
const yaml = worker.workerYaml;
|
|
2918
2963
|
const name = yaml.worker?.name ?? worker.id;
|
|
2919
2964
|
const description = yaml.worker?.description ?? worker.description ?? "";
|
|
@@ -2968,13 +3013,15 @@ export function composeWorkerBrief(worker, taskBrief) {
|
|
|
2968
3013
|
"",
|
|
2969
3014
|
FOREGROUND_ONLY_RULE,
|
|
2970
3015
|
"",
|
|
3016
|
+
tempHomeRule(hqRoot),
|
|
3017
|
+
"",
|
|
2971
3018
|
].join("\n");
|
|
2972
3019
|
}
|
|
2973
3020
|
/**
|
|
2974
|
-
* A lane with no worker still carries the standing authorization paragraph
|
|
2975
|
-
*
|
|
3021
|
+
* A lane with no worker still carries the standing authorization paragraph,
|
|
3022
|
+
* the foreground-only rule and the temp-home rule.
|
|
2976
3023
|
*/
|
|
2977
|
-
export function composeGeneralBrief(taskBrief) {
|
|
3024
|
+
export function composeGeneralBrief(taskBrief, hqRoot) {
|
|
2978
3025
|
return [
|
|
2979
3026
|
taskBrief.replace(/\s+$/, ""),
|
|
2980
3027
|
"",
|
|
@@ -2983,6 +3030,8 @@ export function composeGeneralBrief(taskBrief) {
|
|
|
2983
3030
|
"",
|
|
2984
3031
|
FOREGROUND_ONLY_RULE,
|
|
2985
3032
|
"",
|
|
3033
|
+
tempHomeRule(hqRoot),
|
|
3034
|
+
"",
|
|
2986
3035
|
].join("\n");
|
|
2987
3036
|
}
|
|
2988
3037
|
function parseMaxRuntimeSeconds(raw, workerId) {
|
|
@@ -3387,6 +3436,9 @@ function mintMessageId() {
|
|
|
3387
3436
|
return `msg_${Date.now().toString(36)}_${randomBytes(3).toString("hex")}`;
|
|
3388
3437
|
}
|
|
3389
3438
|
async function runCreate(ctx, opts) {
|
|
3439
|
+
if (opts.runner !== undefined && parseLaneRunner(opts.runner) === "subagent") {
|
|
3440
|
+
return runCreateSubagent(ctx, opts);
|
|
3441
|
+
}
|
|
3390
3442
|
const hqRoot = ctx.hqRoot();
|
|
3391
3443
|
const requestedHireGrant = opts.hireGrant === undefined
|
|
3392
3444
|
? undefined
|
|
@@ -3673,6 +3725,7 @@ async function runCreate(ctx, opts) {
|
|
|
3673
3725
|
provider,
|
|
3674
3726
|
company: company ?? meshCompany,
|
|
3675
3727
|
freshThread: opts.freshThread,
|
|
3728
|
+
runsHere: (lane) => laneRunsHereForOperatorMutation(ctx, lane),
|
|
3676
3729
|
})
|
|
3677
3730
|
: { resumable: false };
|
|
3678
3731
|
const profileDefaultsApply = profile !== undefined && profile.meta.provider === provider;
|
|
@@ -3757,14 +3810,21 @@ async function runCreate(ctx, opts) {
|
|
|
3757
3810
|
if (placementToApply !== undefined) {
|
|
3758
3811
|
const currentHost = laneHostFromCtx(ctx);
|
|
3759
3812
|
if (placementToApply.kind === "local") {
|
|
3760
|
-
|
|
3761
|
-
|
|
3813
|
+
if (currentHost.kind === "local") {
|
|
3814
|
+
const stable = localStableHost(ctx);
|
|
3815
|
+
placementBalanceHostOverride = stable;
|
|
3816
|
+
placementHostOverride = stable;
|
|
3817
|
+
}
|
|
3818
|
+
else {
|
|
3819
|
+
placementBalanceHostOverride = currentHost;
|
|
3820
|
+
placementHostOverride = localStableHost(ctx);
|
|
3821
|
+
}
|
|
3762
3822
|
outpostId = undefined;
|
|
3763
3823
|
}
|
|
3764
3824
|
else if (placementToApply.kind === "outpost") {
|
|
3765
3825
|
if (currentHost.kind === "outpost" && currentHost.id === placementToApply.id) {
|
|
3766
3826
|
placementBalanceHostOverride = currentHost;
|
|
3767
|
-
placementHostOverride =
|
|
3827
|
+
placementHostOverride = localStableHost(ctx);
|
|
3768
3828
|
outpostId = undefined;
|
|
3769
3829
|
}
|
|
3770
3830
|
else {
|
|
@@ -3803,7 +3863,7 @@ async function runCreate(ctx, opts) {
|
|
|
3803
3863
|
if (!localHostname) {
|
|
3804
3864
|
throw new LanesError("host_identity_unknown", "Cannot resolve this host for automatic lane placement", {});
|
|
3805
3865
|
}
|
|
3806
|
-
const localHost =
|
|
3866
|
+
const localHost = localStableHost(ctx);
|
|
3807
3867
|
let chosenHostIsCaller = chosenHost.id === currentHost.id ||
|
|
3808
3868
|
sanitizeHostComponent(chosenHost.id) === sanitizeHostComponent(localHostname);
|
|
3809
3869
|
if (!chosenHostIsCaller && chosenHost.kind === "outpost") {
|
|
@@ -4021,12 +4081,18 @@ async function runCreate(ctx, opts) {
|
|
|
4021
4081
|
const allowedAccounts = validatedAccounts.size > 0 ? [...validatedAccounts] : profileAccounts;
|
|
4022
4082
|
let accountSelection;
|
|
4023
4083
|
if (provider === "claude") {
|
|
4084
|
+
const persistedLocalHost = outpostId === undefined && placementHostOverride === undefined
|
|
4085
|
+
? localStableHost(ctx)
|
|
4086
|
+
: undefined;
|
|
4087
|
+
// Balance against this process. On an Outpost, laneHostFromCtx is that
|
|
4088
|
+
// Outpost id. The stable local id is only what a lane stores when it
|
|
4089
|
+
// spawns here without --outpost.
|
|
4024
4090
|
const targetHost = placementBalanceHostOverride ?? placementHostOverride ?? (outpostId !== undefined
|
|
4025
4091
|
? { kind: "outpost", id: outpostId }
|
|
4026
4092
|
: laneHostFromCtx(ctx));
|
|
4027
4093
|
const actualTargetHost = placementHostOverride ?? (outpostId !== undefined
|
|
4028
4094
|
? { kind: "outpost", id: outpostId }
|
|
4029
|
-
:
|
|
4095
|
+
: persistedLocalHost);
|
|
4030
4096
|
balanceTargetHost = targetHost;
|
|
4031
4097
|
balanceHostLoad = await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config);
|
|
4032
4098
|
const selected = selectClaudeAccount({
|
|
@@ -4059,7 +4125,7 @@ async function runCreate(ctx, opts) {
|
|
|
4059
4125
|
const persist = async () => {
|
|
4060
4126
|
const host = placementHostOverride ?? (outpostId !== undefined
|
|
4061
4127
|
? { kind: "outpost", id: outpostId }
|
|
4062
|
-
:
|
|
4128
|
+
: localStableHost(ctx));
|
|
4063
4129
|
if (opts.loop === true && host.kind === "outpost") {
|
|
4064
4130
|
throw new LanesError("loop_lane_outpost_unsupported", "Loop lanes currently require the local host", { outpost_id: host.id });
|
|
4065
4131
|
}
|
|
@@ -4075,9 +4141,10 @@ async function runCreate(ctx, opts) {
|
|
|
4075
4141
|
provider,
|
|
4076
4142
|
company: company ?? meshCompany,
|
|
4077
4143
|
freshThread: opts.freshThread,
|
|
4144
|
+
runsHere: (lane) => laneRunsHereForOperatorMutation(ctx, lane),
|
|
4078
4145
|
});
|
|
4079
4146
|
if (workerContinuity.resumable && workerContinuity.previous &&
|
|
4080
|
-
!
|
|
4147
|
+
!localLaneContinuesHere(ctx, workerContinuity.previous.host, host)) {
|
|
4081
4148
|
workerContinuity = { previous: workerContinuity.previous, resumable: false, fallbackReason: "host_mismatch" };
|
|
4082
4149
|
}
|
|
4083
4150
|
}
|
|
@@ -4087,7 +4154,7 @@ async function runCreate(ctx, opts) {
|
|
|
4087
4154
|
balanceHostLoad !== undefined && balanceTargetHost !== undefined) {
|
|
4088
4155
|
const localHost = laneHostFromCtx(ctx);
|
|
4089
4156
|
if (localHost.kind === balanceTargetHost.kind && localHost.id === balanceTargetHost.id) {
|
|
4090
|
-
const localLanes = loadAllLanes(hqRoot).filter((lane) => lane
|
|
4157
|
+
const localLanes = loadAllLanes(hqRoot).filter((lane) => laneCountsOnHost(lane, localHost, ctx));
|
|
4091
4158
|
const refreshedLocal = {
|
|
4092
4159
|
capacity: buildHostCapacitySnapshot({
|
|
4093
4160
|
host: localHost,
|
|
@@ -4107,7 +4174,7 @@ async function runCreate(ctx, opts) {
|
|
|
4107
4174
|
: host);
|
|
4108
4175
|
const actualTargetHostForPersist = placementHostOverride ?? (outpostId !== undefined
|
|
4109
4176
|
? { kind: "outpost", id: outpostId }
|
|
4110
|
-
:
|
|
4177
|
+
: localStableHost(ctx));
|
|
4111
4178
|
const selected = selectClaudeAccount({
|
|
4112
4179
|
hosts,
|
|
4113
4180
|
targetHost: balanceTargetHost,
|
|
@@ -4412,8 +4479,8 @@ async function runCreate(ctx, opts) {
|
|
|
4412
4479
|
const briefText = `${workerContinuity.resumable && workerContinuity.previous
|
|
4413
4480
|
? workerContinuityPrompt(workerContinuity.previous, previousResult?.envelope.summary, previousResult?.ref)
|
|
4414
4481
|
: ""}${resolvedWorker
|
|
4415
|
-
? composeWorkerBrief(resolvedWorker, taskBrief)
|
|
4416
|
-
: composeGeneralBrief(taskBrief)}`;
|
|
4482
|
+
? composeWorkerBrief(resolvedWorker, taskBrief, hqRoot)
|
|
4483
|
+
: composeGeneralBrief(taskBrief, hqRoot)}`;
|
|
4417
4484
|
fs.writeFileSync(path.join(dest, "brief.md"), briefText);
|
|
4418
4485
|
if (workerContinuity.resumable && lane.pending_resume) {
|
|
4419
4486
|
const newBriefPath = path.join(dest, "brief.md");
|
|
@@ -7158,6 +7225,7 @@ async function runAdmit(ctx, laneId, opts) {
|
|
|
7158
7225
|
last_busy: state.last_busy ?? null,
|
|
7159
7226
|
busy_source: state.busy_source,
|
|
7160
7227
|
sampled_at: state.sampled_at,
|
|
7228
|
+
...(state.memory_hold !== undefined ? { memory_hold: state.memory_hold } : {}),
|
|
7161
7229
|
};
|
|
7162
7230
|
}
|
|
7163
7231
|
else if (hasEarlierEligibleQueuedLane(lanes, lane, config, host, (row) => row.worker_profile === undefined
|
|
@@ -8810,6 +8878,278 @@ async function dispatchOutpostLaneStop(ctx, lane, reason, quiet = false, behavio
|
|
|
8810
8878
|
},
|
|
8811
8879
|
});
|
|
8812
8880
|
}
|
|
8881
|
+
/**
|
|
8882
|
+
* Register a lane that an in-session subagent runs. The record carries the
|
|
8883
|
+
* same attribution as a process lane (company, project, story, worker, brief,
|
|
8884
|
+
* senior, model) so it shows on the lanes board, but admission and capacity
|
|
8885
|
+
* are skipped and no provider process or watcher starts. The lane is
|
|
8886
|
+
* `running` from creation; `hq lanes progress` reports lines and the result.
|
|
8887
|
+
*/
|
|
8888
|
+
async function runCreateSubagent(ctx, opts) {
|
|
8889
|
+
const unsupported = [
|
|
8890
|
+
["keepAlive", "--keep-alive"],
|
|
8891
|
+
["loop", "--loop"],
|
|
8892
|
+
["remoteControl", "--remote-control"],
|
|
8893
|
+
["title", "--title"],
|
|
8894
|
+
["account", "--account"],
|
|
8895
|
+
["outpost", "--outpost"],
|
|
8896
|
+
["placement", "--placement"],
|
|
8897
|
+
["defaultPlacement", "--default-placement"],
|
|
8898
|
+
["freshThread", "--fresh-thread"],
|
|
8899
|
+
["hireGrant", "--hire-grant"],
|
|
8900
|
+
["timeout", "--timeout"],
|
|
8901
|
+
["allowExtraUsage", "--allow-extra-usage"],
|
|
8902
|
+
];
|
|
8903
|
+
for (const [key, flag] of unsupported) {
|
|
8904
|
+
if (opts[key] !== undefined && opts[key] !== false) {
|
|
8905
|
+
throw new LanesError("subagent_runner_option_unsupported", `${flag} is not supported with --runner subagent; the subagent runs inside its senior session`, { option: flag });
|
|
8906
|
+
}
|
|
8907
|
+
}
|
|
8908
|
+
const hqRoot = ctx.hqRoot();
|
|
8909
|
+
const project = opts.project?.trim();
|
|
8910
|
+
const story = opts.story?.trim();
|
|
8911
|
+
const noStoryReason = opts.noStory?.trim();
|
|
8912
|
+
const additionalStories = (opts.additionalStory ?? []).map((value) => value.trim());
|
|
8913
|
+
const briefFile = opts.briefFile?.trim();
|
|
8914
|
+
if (!project)
|
|
8915
|
+
throw new LanesError("missing_project", "--project is required", {});
|
|
8916
|
+
if (opts.story !== undefined && opts.noStory !== undefined) {
|
|
8917
|
+
throw new LanesError("story_selection_conflict", "Pass either --story or --no-story, not both", {});
|
|
8918
|
+
}
|
|
8919
|
+
if (opts.noStory !== undefined && !noStoryReason) {
|
|
8920
|
+
throw new LanesError("missing_no_story_reason", "--no-story requires a non-empty reason", {});
|
|
8921
|
+
}
|
|
8922
|
+
if (!story && !noStoryReason)
|
|
8923
|
+
throw new LanesError("missing_story", "--story is required", {});
|
|
8924
|
+
if (noStoryReason && additionalStories.length > 0) {
|
|
8925
|
+
throw new LanesError("story_selection_conflict", "--additional-story requires --story and cannot be used with --no-story", {});
|
|
8926
|
+
}
|
|
8927
|
+
if (additionalStories.some((value) => value.length === 0)) {
|
|
8928
|
+
throw new LanesError("missing_story", "--additional-story values must be non-empty", {});
|
|
8929
|
+
}
|
|
8930
|
+
const storyIds = story ? [story, ...additionalStories] : [];
|
|
8931
|
+
if (new Set(storyIds).size !== storyIds.length) {
|
|
8932
|
+
throw new LanesError("duplicate_story", "A lane cannot carry the same project/story more than once", {
|
|
8933
|
+
project_id: project,
|
|
8934
|
+
stories: storyIds,
|
|
8935
|
+
});
|
|
8936
|
+
}
|
|
8937
|
+
if (!briefFile)
|
|
8938
|
+
throw new LanesError("missing_brief_file", "--brief-file is required", {});
|
|
8939
|
+
if (opts.focusOverride !== undefined) {
|
|
8940
|
+
if (!opts.focusOverride.trim()) {
|
|
8941
|
+
throw new LanesError("focus_override_reason_required", "--focus-override requires a non-empty reason", {});
|
|
8942
|
+
}
|
|
8943
|
+
assertNoSecretText("focus override reason", opts.focusOverride.trim());
|
|
8944
|
+
}
|
|
8945
|
+
const provider = opts.provider ? parseLaneProvider(opts.provider) : "claude";
|
|
8946
|
+
if (provider !== "claude") {
|
|
8947
|
+
throw new LanesError("subagent_runner_requires_claude", "--runner subagent records a Claude subagent; use --provider claude or omit it", { provider });
|
|
8948
|
+
}
|
|
8949
|
+
if (ctx.env.HQ_LANE_ID?.trim()) {
|
|
8950
|
+
throw new LanesError("subagent_runner_requires_session", "A subagent lane belongs to the session that runs the subagent; a lane worker cannot register one", { caller_lane_id: ctx.env.HQ_LANE_ID.trim() });
|
|
8951
|
+
}
|
|
8952
|
+
const senior = opts.senior ? parseSeniorRef(opts.senior) : defaultSenior(ctx);
|
|
8953
|
+
if (senior.kind === "lane") {
|
|
8954
|
+
throw new LanesError("subagent_runner_requires_session", "A subagent lane's senior must be the session running the subagent (session:<id>) or user", { senior });
|
|
8955
|
+
}
|
|
8956
|
+
assertSeniorExists(hqRoot, senior, ctx);
|
|
8957
|
+
let company = opts.company?.trim() ? await ctx.resolveCompany(opts.company.trim()) : null;
|
|
8958
|
+
const seniorCompany = companyOfSenior(hqRoot, senior, ctx);
|
|
8959
|
+
if (company === null && seniorCompany.binding === "company") {
|
|
8960
|
+
const seniorCompanyIdentifier = seniorCompany.slug ?? (seniorCompany.uid
|
|
8961
|
+
? canonicalCompanySlugForUid(hqRoot, seniorCompany.uid, senior)
|
|
8962
|
+
: undefined);
|
|
8963
|
+
if (seniorCompanyIdentifier)
|
|
8964
|
+
company = await ctx.resolveCompany(seniorCompanyIdentifier);
|
|
8965
|
+
}
|
|
8966
|
+
let meshCompany = null;
|
|
8967
|
+
if (opts.meshCompany?.trim()) {
|
|
8968
|
+
meshCompany = await ctx.resolveCompany(opts.meshCompany.trim());
|
|
8969
|
+
if (company && (company.uid !== meshCompany.uid || company.slug !== meshCompany.slug)) {
|
|
8970
|
+
throw new LanesError("mesh_company_mismatch", "--mesh-company must match --company when both are provided", {
|
|
8971
|
+
company: company.slug,
|
|
8972
|
+
mesh_company: meshCompany.slug,
|
|
8973
|
+
});
|
|
8974
|
+
}
|
|
8975
|
+
}
|
|
8976
|
+
const depthCap = laneDepthCap(hqRoot, company?.slug);
|
|
8977
|
+
const laneDepth = derivedLaneDepth(hqRoot, senior, ctx);
|
|
8978
|
+
if (laneDepth > depthCap.max) {
|
|
8979
|
+
throw maxLaneDepthError(laneDepth, seniorLaneChain(hqRoot, senior, ctx), `Creating lane at depth ${laneDepth} is refused`, depthCap);
|
|
8980
|
+
}
|
|
8981
|
+
const resolvedWorker = opts.worker !== undefined
|
|
8982
|
+
? resolveLaneWorker(hqRoot, opts.worker, company?.slug)
|
|
8983
|
+
: undefined;
|
|
8984
|
+
const cliModel = parseCliModel(opts.model);
|
|
8985
|
+
const cliEffort = parseCliEffort(provider, opts.effort);
|
|
8986
|
+
const priority = Number.parseInt(opts.priority ?? "1", 10);
|
|
8987
|
+
if (!Number.isInteger(priority)) {
|
|
8988
|
+
throw new LanesError("invalid_priority", "--priority must be an integer", { value: opts.priority });
|
|
8989
|
+
}
|
|
8990
|
+
const briefAbs = resolveCallerPath(ctx.callerCwd(), briefFile);
|
|
8991
|
+
if (!ctx.existsSync(briefAbs)) {
|
|
8992
|
+
throw new LanesError("brief_not_found", `Brief file not found: ${briefAbs}`, { path: briefAbs });
|
|
8993
|
+
}
|
|
8994
|
+
const taskBrief = fs.readFileSync(briefAbs, "utf8");
|
|
8995
|
+
const createdAt = ctx.now().toISOString();
|
|
8996
|
+
const lane = createLane(hqRoot, {
|
|
8997
|
+
company,
|
|
8998
|
+
...(company === null && meshCompany ? { mesh_company: meshCompany } : {}),
|
|
8999
|
+
project_id: project,
|
|
9000
|
+
story_id: story ?? "",
|
|
9001
|
+
...(noStoryReason ? { no_story_reason: noStoryReason } : {}),
|
|
9002
|
+
stories: storyIds.map((storyId) => ({
|
|
9003
|
+
project_id: project,
|
|
9004
|
+
story_id: storyId,
|
|
9005
|
+
status: "in_progress",
|
|
9006
|
+
added_at: createdAt,
|
|
9007
|
+
updated_at: createdAt,
|
|
9008
|
+
})),
|
|
9009
|
+
depth: laneDepth,
|
|
9010
|
+
senior,
|
|
9011
|
+
hire_grant: 0,
|
|
9012
|
+
senior_history: [],
|
|
9013
|
+
state: "running",
|
|
9014
|
+
priority,
|
|
9015
|
+
...(resolvedWorker ? { worker: resolvedWorker.id, worker_path: resolvedWorker.path } : {}),
|
|
9016
|
+
...(resolvedWorker?.pack ? { worker_pack: resolvedWorker.pack } : {}),
|
|
9017
|
+
keep_alive: false,
|
|
9018
|
+
provider,
|
|
9019
|
+
runner: "subagent",
|
|
9020
|
+
provider_source: opts.provider ? "cli" : "default",
|
|
9021
|
+
...(cliModel !== undefined ? { model: cliModel } : {}),
|
|
9022
|
+
model_source: cliModel !== undefined ? "cli" : "none",
|
|
9023
|
+
...(cliEffort !== undefined ? { effort: cliEffort } : {}),
|
|
9024
|
+
effort_source: cliEffort !== undefined ? "cli" : "none",
|
|
9025
|
+
host: { kind: "local", id: ctx.hostname() },
|
|
9026
|
+
attribution_history: [],
|
|
9027
|
+
created_at: createdAt,
|
|
9028
|
+
updated_at: createdAt,
|
|
9029
|
+
});
|
|
9030
|
+
const dest = runDir(hqRoot, lane.lane_id);
|
|
9031
|
+
ensureDir(path.join(dest, "logs"));
|
|
9032
|
+
ensureDir(path.join(dest, "envelopes"));
|
|
9033
|
+
ensureDir(path.join(dest, "questions"));
|
|
9034
|
+
fs.writeFileSync(path.join(dest, "brief.md"), taskBrief);
|
|
9035
|
+
if (!opts.quiet)
|
|
9036
|
+
printJson({
|
|
9037
|
+
ok: true,
|
|
9038
|
+
action: "create",
|
|
9039
|
+
admitted: [],
|
|
9040
|
+
lane_id: lane.lane_id,
|
|
9041
|
+
state: lane.state,
|
|
9042
|
+
runner: "subagent",
|
|
9043
|
+
queued_reason: null,
|
|
9044
|
+
priority: lane.priority,
|
|
9045
|
+
provider: lane.provider,
|
|
9046
|
+
account: null,
|
|
9047
|
+
model: lane.model ?? null,
|
|
9048
|
+
effort: lane.effort ?? null,
|
|
9049
|
+
effort_source: lane.effort_source ?? null,
|
|
9050
|
+
host: lane.host,
|
|
9051
|
+
company: lane.company,
|
|
9052
|
+
project_id: lane.project_id,
|
|
9053
|
+
story_id: lane.story_id,
|
|
9054
|
+
stories: laneStories(lane),
|
|
9055
|
+
thread_id: null,
|
|
9056
|
+
senior: lane.senior,
|
|
9057
|
+
depth: lane.depth,
|
|
9058
|
+
timeout_secs: null,
|
|
9059
|
+
lane,
|
|
9060
|
+
});
|
|
9061
|
+
return lane;
|
|
9062
|
+
}
|
|
9063
|
+
/** Longest progress line kept; `hq lanes list` clips its last line further. */
|
|
9064
|
+
const SUBAGENT_PROGRESS_LINE_MAX = 500;
|
|
9065
|
+
/**
|
|
9066
|
+
* Report on a subagent lane: append a progress line, record the final
|
|
9067
|
+
* envelope (state follows its decision, as for a process lane), or mark it
|
|
9068
|
+
* failed when the subagent ended without one.
|
|
9069
|
+
*/
|
|
9070
|
+
async function runProgress(ctx, laneId, opts) {
|
|
9071
|
+
const hqRoot = ctx.hqRoot();
|
|
9072
|
+
const line = opts.line?.replace(/\s+/g, " ").trim();
|
|
9073
|
+
const failedReason = opts.failed?.replace(/\s+/g, " ").trim();
|
|
9074
|
+
if (opts.line !== undefined && !line)
|
|
9075
|
+
throw new LanesError("progress_line_empty", "--line must not be empty", {});
|
|
9076
|
+
if (opts.failed !== undefined && !failedReason)
|
|
9077
|
+
throw new LanesError("progress_failed_reason_empty", "--failed requires a non-empty reason", {});
|
|
9078
|
+
if (opts.envelope !== undefined && opts.failed !== undefined) {
|
|
9079
|
+
throw new LanesError("progress_result_conflict", "Pass either --envelope or --failed, not both", {});
|
|
9080
|
+
}
|
|
9081
|
+
if (line === undefined && opts.envelope === undefined && failedReason === undefined) {
|
|
9082
|
+
throw new LanesError("progress_nothing_to_report", "Pass --line, --envelope, or --failed", {});
|
|
9083
|
+
}
|
|
9084
|
+
const lane = readLane(hqRoot, laneId);
|
|
9085
|
+
if (!isSubagentLane(lane)) {
|
|
9086
|
+
throw new LanesError("lane_not_subagent", `Lane ${lane.lane_id} runs as an HQ process; its watcher reports progress`, { lane_id: lane.lane_id });
|
|
9087
|
+
}
|
|
9088
|
+
if (isTerminalLaneState(lane.state)) {
|
|
9089
|
+
throw new LanesError("already_terminal", `Lane ${lane.lane_id} is already ${lane.state}`, { lane_id: lane.lane_id, state: lane.state });
|
|
9090
|
+
}
|
|
9091
|
+
const dest = runDir(hqRoot, lane.lane_id);
|
|
9092
|
+
const appendLine = (text) => {
|
|
9093
|
+
const clipped = text.length > SUBAGENT_PROGRESS_LINE_MAX ? `${text.slice(0, SUBAGENT_PROGRESS_LINE_MAX - 1)}…` : text;
|
|
9094
|
+
ensureDir(path.join(dest, "logs"));
|
|
9095
|
+
fs.appendFileSync(path.join(dest, "logs", "last-message.txt"), `${clipped}\n`);
|
|
9096
|
+
};
|
|
9097
|
+
if (line !== undefined) {
|
|
9098
|
+
assertNoSecretText("progress line", line);
|
|
9099
|
+
appendLine(line);
|
|
9100
|
+
}
|
|
9101
|
+
if (failedReason !== undefined)
|
|
9102
|
+
assertNoSecretText("failure reason", failedReason);
|
|
9103
|
+
const at = ctx.now().toISOString();
|
|
9104
|
+
let updated = lane;
|
|
9105
|
+
let envelopeRef;
|
|
9106
|
+
if (opts.envelope !== undefined) {
|
|
9107
|
+
const envelopeAbs = resolveCallerPath(ctx.callerCwd(), opts.envelope);
|
|
9108
|
+
let text;
|
|
9109
|
+
try {
|
|
9110
|
+
text = fs.readFileSync(envelopeAbs, "utf8");
|
|
9111
|
+
}
|
|
9112
|
+
catch (err) {
|
|
9113
|
+
throw new LanesError("envelope_not_found", `Envelope file not readable: ${envelopeAbs}`, {
|
|
9114
|
+
path: envelopeAbs,
|
|
9115
|
+
error: err instanceof Error ? err.message : String(err),
|
|
9116
|
+
});
|
|
9117
|
+
}
|
|
9118
|
+
let envelope;
|
|
9119
|
+
try {
|
|
9120
|
+
envelope = parseLaneEnvelope(JSON.parse(text), { storyBound: lane.no_story_reason === undefined });
|
|
9121
|
+
}
|
|
9122
|
+
catch (err) {
|
|
9123
|
+
throw new LanesError("envelope_invalid", `Envelope is not a valid hq-lane-envelope.v1: ${err instanceof Error ? err.message : String(err)}`, {
|
|
9124
|
+
path: envelopeAbs,
|
|
9125
|
+
});
|
|
9126
|
+
}
|
|
9127
|
+
ensureDir(path.join(dest, "envelopes"));
|
|
9128
|
+
const target = nextEnvelopePath(dest);
|
|
9129
|
+
fs.writeFileSync(target, text.endsWith("\n") ? text : `${text}\n`);
|
|
9130
|
+
envelopeRef = path.posix.join("envelopes", path.basename(target));
|
|
9131
|
+
if (envelope.summary)
|
|
9132
|
+
appendLine(envelope.summary.replace(/\s+/g, " ").trim());
|
|
9133
|
+
updated = applyTerminalToLane(hqRoot, lane.lane_id, classifyLaneExit({ envelope, hasPartialOutput: false }), {
|
|
9134
|
+
envelope,
|
|
9135
|
+
envelopeRef,
|
|
9136
|
+
at,
|
|
9137
|
+
});
|
|
9138
|
+
}
|
|
9139
|
+
else if (failedReason !== undefined) {
|
|
9140
|
+
appendLine(failedReason);
|
|
9141
|
+
updated = applyTerminalToLane(hqRoot, lane.lane_id, { state: "failed", reason: "subagent_failed" }, { at });
|
|
9142
|
+
}
|
|
9143
|
+
printJson({
|
|
9144
|
+
ok: true,
|
|
9145
|
+
action: "progress",
|
|
9146
|
+
lane_id: updated.lane_id,
|
|
9147
|
+
state: updated.state,
|
|
9148
|
+
...(line !== undefined ? { line } : {}),
|
|
9149
|
+
...(envelopeRef !== undefined ? { envelope_ref: envelopeRef } : {}),
|
|
9150
|
+
...(updated.terminal_reason ? { terminal_reason: updated.terminal_reason } : {}),
|
|
9151
|
+
});
|
|
9152
|
+
}
|
|
8813
9153
|
async function runStop(ctx, laneId, reason, quiet = false) {
|
|
8814
9154
|
const operatorReason = reason?.trim();
|
|
8815
9155
|
if (reason !== undefined && !operatorReason) {
|
|
@@ -9914,7 +10254,11 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
9914
10254
|
registerLaneAutopilotReportCommand(lanes, {
|
|
9915
10255
|
hqRoot: ctx.hqRoot,
|
|
9916
10256
|
now: ctx.now,
|
|
9917
|
-
currentHost: () => resolveAdmissionHost({
|
|
10257
|
+
currentHost: () => resolveAdmissionHost({
|
|
10258
|
+
hostname: ctx.hostname,
|
|
10259
|
+
homedir: ctx.homedir,
|
|
10260
|
+
readOutpostIdentity: ctx.readOutpostIdentity,
|
|
10261
|
+
}),
|
|
9918
10262
|
}, wrap);
|
|
9919
10263
|
const noStoryOption = new Option("--no-story <reason>", "Create a lane without a story and record why");
|
|
9920
10264
|
noStoryOption.attributeName = () => "noStory";
|
|
@@ -9933,6 +10277,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
9933
10277
|
.option("--fresh-thread", "Skip worker continuity and start a cold provider session")
|
|
9934
10278
|
.option("--loop", "Keep one worker lane registered to process queued phase envelopes")
|
|
9935
10279
|
.option("--provider <name>", "claude | codex | grok")
|
|
10280
|
+
.option("--runner <kind>", "process (default) | subagent: register a lane an in-session Claude subagent runs; skips admission and starts no process")
|
|
9936
10281
|
.option("--remote-control", "Open a Claude lane in Remote Control using account a for login and the selected account for inference")
|
|
9937
10282
|
.option("--title <text>", "Human-readable Claude Remote Control session title; required with --remote-control")
|
|
9938
10283
|
.option("--account <id>", "Provider account id, or 'auto' to pick the account with the most room (all accounts are one pool across companies)")
|
|
@@ -9950,6 +10295,16 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
9950
10295
|
.action(wrap(async (opts) => {
|
|
9951
10296
|
await withCliFeatureUsed("lane_create", () => runCreate(ctx, opts));
|
|
9952
10297
|
}));
|
|
10298
|
+
lanes
|
|
10299
|
+
.command("progress")
|
|
10300
|
+
.description("Report on a subagent lane: a progress line, the final envelope, or a failure")
|
|
10301
|
+
.argument("<lane-id>", "Lane id")
|
|
10302
|
+
.option("--line <text>", "Progress line shown as the lane's last line")
|
|
10303
|
+
.option("--envelope <path>", "Final hq-lane-envelope.v1 file; the lane state follows its decision")
|
|
10304
|
+
.option("--failed <reason>", "Mark the lane failed when the subagent ended without an envelope")
|
|
10305
|
+
.action(wrap(async (laneId, opts) => {
|
|
10306
|
+
await runProgress(ctx, laneId, opts);
|
|
10307
|
+
}));
|
|
9953
10308
|
lanes
|
|
9954
10309
|
.command("approve-target", { hidden: true })
|
|
9955
10310
|
.argument("<lane-id>", "Lane id")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Readable } from "node:stream";
|
|
2
|
-
import { type CloudAssetMediaKind } from "@indigoai-us/hq-cloud";
|
|
2
|
+
import { type CloudAssetMediaKind, type PullScope, type PullScopeClient } from "@indigoai-us/hq-cloud";
|
|
3
3
|
import type { LibraryCaptionNote, LibraryCaptionRequest } from "./library-caption.js";
|
|
4
4
|
export interface LibraryCompany {
|
|
5
5
|
uid: string;
|
|
@@ -37,6 +37,13 @@ export interface LibraryAddDeps {
|
|
|
37
37
|
* a caption failure never fails the upload. Absent = no captioning.
|
|
38
38
|
*/
|
|
39
39
|
caption?: (request: LibraryCaptionRequest) => Promise<LibraryCaptionNote>;
|
|
40
|
+
/**
|
|
41
|
+
* The membership's sync scope for the company. Throws when it cannot be
|
|
42
|
+
* read. Absent = the scope is not checked and nothing is pinned.
|
|
43
|
+
*/
|
|
44
|
+
syncScope?: (company: LibraryCompany) => Promise<Pick<PullScope, "syncMode" | "prefixSet">>;
|
|
45
|
+
/** `hq files get`'s pin writer (`.hq/pins.json`). */
|
|
46
|
+
addPin?: (hqRoot: string, companySlug: string, prefix: string) => void;
|
|
40
47
|
}
|
|
41
48
|
export interface LibraryAddOptions {
|
|
42
49
|
company?: string;
|
|
@@ -60,9 +67,19 @@ export interface LibraryAddItem {
|
|
|
60
67
|
/** AI caption outcome, set when captioning ran for this item. */
|
|
61
68
|
caption?: LibraryCaptionNote;
|
|
62
69
|
}
|
|
70
|
+
export interface LibrarySyncScopeNote {
|
|
71
|
+
status: "in-scope" | "pinned" | "not-pinned";
|
|
72
|
+
/** Company-relative pins written (`pinned`) or still needed (`not-pinned`). */
|
|
73
|
+
pins: string[];
|
|
74
|
+
/** Set when not pinned and `hq files get` can pin by hand (the library folder). */
|
|
75
|
+
command?: string;
|
|
76
|
+
reason?: string;
|
|
77
|
+
}
|
|
63
78
|
export interface LibraryAddResult {
|
|
64
79
|
company: string;
|
|
65
80
|
items: LibraryAddItem[];
|
|
81
|
+
/** Set when at least one placeholder was written and the scope was checked. */
|
|
82
|
+
syncScope?: LibrarySyncScopeNote;
|
|
66
83
|
}
|
|
67
84
|
export declare function contentTypeFor(name: string, served?: string): string;
|
|
68
85
|
export declare function mediaKindFor(contentType: string): CloudAssetMediaKind;
|
|
@@ -89,6 +106,19 @@ export declare function offloadTimestamp(now: Date): string;
|
|
|
89
106
|
* only that source.
|
|
90
107
|
*/
|
|
91
108
|
export declare function runLibraryAdd(inputs: readonly string[], options: LibraryAddOptions, deps: LibraryAddDeps): Promise<LibraryAddResult>;
|
|
109
|
+
/**
|
|
110
|
+
* A `shared` or `custom` sync pushes only paths inside the membership's
|
|
111
|
+
* prefix set (explicit grants plus pins), so a placeholder written outside it
|
|
112
|
+
* never reaches the vault. Pin it with `hq files get`'s pin writer: the
|
|
113
|
+
* default library folder as a whole, and for any other `--to` folder only the
|
|
114
|
+
* written placeholders, so the rest of that folder stays out of sync.
|
|
115
|
+
*/
|
|
116
|
+
export declare function ensureLibraryInSyncScope(company: LibraryCompany, dirRel: string, placeholderPaths: readonly string[], deps: Pick<LibraryAddDeps, "hqRoot" | "syncScope" | "addPin">): Promise<LibrarySyncScopeNote | undefined>;
|
|
117
|
+
/**
|
|
118
|
+
* {@link resolvePullScope} answers `all` when any call fails. Report the
|
|
119
|
+
* failure instead, so a failed lookup is not mistaken for full sync.
|
|
120
|
+
*/
|
|
121
|
+
export declare function resolveLibrarySyncScope(client: Required<PullScopeClient>, company: LibraryCompany, hqRoot: string): Promise<PullScope>;
|
|
92
122
|
export interface LibraryTagResult {
|
|
93
123
|
placeholderPath: string;
|
|
94
124
|
tags: string[];
|