@indigoai-us/hq-cli 5.188.1 → 5.190.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 +31 -0
- package/dist/command-catalog.generated.d.ts +31 -1
- package/dist/command-catalog.generated.js +40 -1
- package/dist/commands/lanes.d.ts +3 -1
- package/dist/commands/lanes.js +269 -50
- package/dist/lib/bot/promotion-upload.js +17 -2
- package/dist/lib/lanes/index.d.ts +2 -0
- package/dist/lib/lanes/index.js +1 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +4 -0
- package/dist/lib/lanes/outpost-dispatch.js +8 -0
- package/dist/lib/lanes/spawn.d.ts +3 -0
- package/dist/lib/lanes/spawn.js +8 -2
- package/dist/lib/lanes/store.js +129 -6
- package/dist/lib/lanes/stories.js +3 -0
- package/dist/lib/lanes/story-ledger.d.ts +78 -0
- package/dist/lib/lanes/story-ledger.js +762 -0
- package/dist/lib/lanes/types.d.ts +10 -1
- package/dist/lib/lanes/types.js +3 -3
- package/dist/lib/lanes/watcher.js +2 -0
- package/dist/utils/vault-api.js +1 -1
- package/package.json +1 -1
package/dist/commands/lanes.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `hq lanes` — worker lane management and decision queue (spec §5–§7, §10–§11).
|
|
3
3
|
*
|
|
4
|
-
* Existing commands print JSON on stdout and take no interactive input, except
|
|
4
|
+
* Existing commands print JSON on stdout by default and take no interactive input, except
|
|
5
5
|
* `accounts login`, which hands the terminal to the vendor's own sign-in.
|
|
6
6
|
* `watch` and the monitor reminder are the line-oriented text surfaces.
|
|
7
7
|
*/
|
|
@@ -10,6 +10,7 @@ import { randomBytes } from "node:crypto";
|
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
11
|
import * as os from "node:os";
|
|
12
12
|
import * as path from "node:path";
|
|
13
|
+
import { Option } from "commander";
|
|
13
14
|
import { registerOutpostPrunerCommand } from "./outpost-pruner.js";
|
|
14
15
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
15
16
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
@@ -20,7 +21,7 @@ import { createOutpostHireGrantContext, parseOutpostHireGrantContext, sameHireGr
|
|
|
20
21
|
import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
|
|
21
22
|
export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
22
23
|
export { LANE_HIRE_GRANTS_FLAG };
|
|
23
|
-
import { buildOutpostCapacityCommand, buildOutpostBalanceMoveCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostMoveTargetCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, parseOutpostPickupPayload, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
|
|
24
|
+
import { buildOutpostCapacityCommand, buildOutpostBalanceMoveCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostMoveTargetCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, outpostRejectedEffortOption, parseOutpostPickupPayload, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
|
|
24
25
|
import { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "../lib/lanes/host-capacity.js";
|
|
25
26
|
import { BALANCE_MOVABLE_STATES, CLAUDE_ACCOUNT_BALANCE_FLAG, accountLoadsForReport, assertBalanceApplyEnabled, planClaudeBalanceMoves, selectClaudeAccount, } from "../lib/lanes/claude-account-balance.js";
|
|
26
27
|
import { parseUserYaml } from "../utils/user-yaml-error.js";
|
|
@@ -29,12 +30,12 @@ import { vaultApiFetch } from "../utils/vault-api.js";
|
|
|
29
30
|
import { openMeshTransport } from "../lib/mesh/client.js";
|
|
30
31
|
import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshEventIdentity, } from "../lib/lanes/mesh-events.js";
|
|
31
32
|
import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
|
|
32
|
-
import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, LANE_STATES, PROVIDER_HOME_ENV, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installDrainHook, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, withHireGrantAdmission, listAccountStatuses, listLaneIds, listQuestionIds, listSessionLiveness, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, MAX_WATCHED_LANES, monitorReminderText, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseLivenessState, parseQuestionStatus, parseSeniorKind, readDaemonPresenceConnected, readLane, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, runReconcilePass, runLaneWatch, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, writeLanesConfig, appendDropboxMessage, answerDropboxMessage, deliveryRefusalReason, resumeCommandRefusal, dropboxDir, dropboxInboxPath, laneAcceptsDropboxDelivery, laneAcceptsOperatorMessage, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, withLaneAdmissionLock, withLaneHireLocks, laneIdForAnswerDelivery, operatorMessageRefusalReason, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "../lib/lanes/index.js";
|
|
33
|
+
import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, LANE_STATES, PROVIDER_HOME_ENV, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installDrainHook, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listAccountStatuses, listLaneIds, listQuestionIds, listSessionLiveness, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, MAX_WATCHED_LANES, monitorReminderText, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseLivenessState, parseQuestionStatus, parseSeniorKind, readDaemonPresenceConnected, readLane, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, runReconcilePass, runLaneWatch, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, writeLanesConfig, appendDropboxMessage, answerDropboxMessage, deliveryRefusalReason, resumeCommandRefusal, dropboxDir, dropboxInboxPath, laneAcceptsDropboxDelivery, laneAcceptsOperatorMessage, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, withLaneAdmissionLock, withLaneHireLocks, laneIdForAnswerDelivery, operatorMessageRefusalReason, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "../lib/lanes/index.js";
|
|
33
34
|
import { activeLaneStories, laneStories, laneStory, storyWarning, } from "../lib/lanes/stories.js";
|
|
34
35
|
import { questionBodyAbsPath } from "../lib/lanes/questions.js";
|
|
35
36
|
import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
|
|
36
37
|
import { providerCompactAfterMs } from "../lib/lanes/config.js";
|
|
37
|
-
import { parseCliEffort, resolveLaneEffort } from "../lib/lanes/effort.js";
|
|
38
|
+
import { parseCliEffort, resolveLaneEffort, } from "../lib/lanes/effort.js";
|
|
38
39
|
import { LanesError } from "../lib/lanes/errors.js";
|
|
39
40
|
import { accountQuotaView, loadQuotaState, quotaBlockFor } from "../lib/lanes/quota.js";
|
|
40
41
|
import { SESSION_MOVE_REASON } from "../lib/lanes/move-account.js";
|
|
@@ -257,6 +258,7 @@ function resolveDeps(deps = {}) {
|
|
|
257
258
|
fetchText: deps.fetchText ?? defaultFetchText,
|
|
258
259
|
lanePlacementEnabled: deps.lanePlacementEnabled ?? defaultLanePlacementEnabled,
|
|
259
260
|
laneHireGrantsEnabled: deps.laneHireGrantsEnabled ?? defaultLaneHireGrantsEnabled,
|
|
261
|
+
storyLedgerEnabled: deps.storyLedgerEnabled ?? laneStoryLedgerEnabled,
|
|
260
262
|
isSsmOwnerDispatch: deps.isSsmOwnerDispatch ?? (() => isSsmRunCommandProcess()),
|
|
261
263
|
withGrantHireLocks: deps.withGrantHireLocks ?? withLaneHireLocks,
|
|
262
264
|
laneClaudeAccountBalanceEnabled: deps.laneClaudeAccountBalanceEnabled ?? defaultLaneClaudeAccountBalanceEnabled,
|
|
@@ -2112,18 +2114,28 @@ async function runCreate(ctx, opts) {
|
|
|
2112
2114
|
const meshCompanyFlag = opts.meshCompany?.trim();
|
|
2113
2115
|
const project = opts.project?.trim();
|
|
2114
2116
|
const story = opts.story?.trim();
|
|
2117
|
+
const noStoryReason = opts.noStory?.trim();
|
|
2115
2118
|
const additionalStories = (opts.additionalStory ?? []).map((value) => value.trim());
|
|
2116
2119
|
const briefFile = opts.briefFile?.trim();
|
|
2117
2120
|
if (!project) {
|
|
2118
2121
|
throw new LanesError("missing_project", "--project is required", {});
|
|
2119
2122
|
}
|
|
2120
|
-
if (
|
|
2123
|
+
if (opts.story !== undefined && opts.noStory !== undefined) {
|
|
2124
|
+
throw new LanesError("story_selection_conflict", "Pass either --story or --no-story, not both", {});
|
|
2125
|
+
}
|
|
2126
|
+
if (opts.noStory !== undefined && !noStoryReason) {
|
|
2127
|
+
throw new LanesError("missing_no_story_reason", "--no-story requires a non-empty reason", {});
|
|
2128
|
+
}
|
|
2129
|
+
if (!story && !noStoryReason) {
|
|
2121
2130
|
throw new LanesError("missing_story", "--story is required", {});
|
|
2122
2131
|
}
|
|
2132
|
+
if (noStoryReason && additionalStories.length > 0) {
|
|
2133
|
+
throw new LanesError("story_selection_conflict", "--additional-story requires --story and cannot be used with --no-story", {});
|
|
2134
|
+
}
|
|
2123
2135
|
if (additionalStories.some((value) => value.length === 0)) {
|
|
2124
2136
|
throw new LanesError("missing_story", "--additional-story values must be non-empty", {});
|
|
2125
2137
|
}
|
|
2126
|
-
const storyIds = [story, ...additionalStories];
|
|
2138
|
+
const storyIds = story ? [story, ...additionalStories] : [];
|
|
2127
2139
|
if (new Set(storyIds).size !== storyIds.length) {
|
|
2128
2140
|
throw new LanesError("duplicate_story", "A lane cannot carry the same project/story more than once", {
|
|
2129
2141
|
project_id: project,
|
|
@@ -2174,6 +2186,9 @@ async function runCreate(ctx, opts) {
|
|
|
2174
2186
|
throw new LanesError("max_lane_depth", `Lane depth ${laneDepth} exceeds cap ${MAX_LANE_DEPTH}`, { depth: laneDepth, max: MAX_LANE_DEPTH });
|
|
2175
2187
|
}
|
|
2176
2188
|
// A no-company lane must not call company resolution (openMeshTransport).
|
|
2189
|
+
if (storyIds.length > 0 && ctx.storyLedgerEnabled()) {
|
|
2190
|
+
validateProjectStories(hqRoot, project, storyIds, company?.slug);
|
|
2191
|
+
}
|
|
2177
2192
|
let meshCompany = company;
|
|
2178
2193
|
if (meshCompanyFlag) {
|
|
2179
2194
|
const resolvedMeshCompany = await ctx.resolveCompany(meshCompanyFlag);
|
|
@@ -2189,7 +2204,7 @@ async function runCreate(ctx, opts) {
|
|
|
2189
2204
|
const seniorCompany = companyOfSenior(hqRoot, senior, ctx);
|
|
2190
2205
|
assertSameTenant(company, senior, seniorCompany);
|
|
2191
2206
|
const createStoryWarnings = [];
|
|
2192
|
-
if (meshCompany) {
|
|
2207
|
+
if (meshCompany && storyIds.length > 0) {
|
|
2193
2208
|
const warning = await storyBoardWarning(ctx, meshCompany, project, storyIds);
|
|
2194
2209
|
if (warning !== undefined)
|
|
2195
2210
|
createStoryWarnings.push(warning);
|
|
@@ -2630,7 +2645,8 @@ async function runCreate(ctx, opts) {
|
|
|
2630
2645
|
? { mesh_company: meshCompany }
|
|
2631
2646
|
: {}),
|
|
2632
2647
|
project_id: project,
|
|
2633
|
-
story_id: story,
|
|
2648
|
+
story_id: story ?? "",
|
|
2649
|
+
...(noStoryReason ? { no_story_reason: noStoryReason } : {}),
|
|
2634
2650
|
stories: storyIds.map((storyId) => ({
|
|
2635
2651
|
project_id: project,
|
|
2636
2652
|
story_id: storyId,
|
|
@@ -3100,6 +3116,8 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3100
3116
|
story: options.story,
|
|
3101
3117
|
account: options.account,
|
|
3102
3118
|
model: options.model,
|
|
3119
|
+
effort: options.effort,
|
|
3120
|
+
worker: options.worker,
|
|
3103
3121
|
hire: options.hire,
|
|
3104
3122
|
grantContext,
|
|
3105
3123
|
compact: options.compact,
|
|
@@ -3110,6 +3128,18 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
|
|
|
3110
3128
|
throw new LanesError("outpost_dispatch_failed", `Could not dispatch ${action} for lane ${lane.lane_id} to Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, { lane_id: lane.lane_id, outpost_id: outpostId });
|
|
3111
3129
|
}
|
|
3112
3130
|
const ownerError = ownerErrorFromOutput(dispatched.stdout);
|
|
3131
|
+
if (options.effort !== undefined && outpostRejectedEffortOption(dispatched)) {
|
|
3132
|
+
throw new LanesError("outpost_effort_unsupported", `Outpost ${outpostId} rejected --effort; update hq-cli on that Outpost before changing a lane's effort remotely`, {
|
|
3133
|
+
lane_id: lane.lane_id,
|
|
3134
|
+
outpost_id: outpostId,
|
|
3135
|
+
command_id: dispatched.commandId,
|
|
3136
|
+
effort: options.effort,
|
|
3137
|
+
status: dispatched.status,
|
|
3138
|
+
done: dispatched.done ?? false,
|
|
3139
|
+
exit_code: dispatched.exitCode,
|
|
3140
|
+
owner_error: ownerError,
|
|
3141
|
+
});
|
|
3142
|
+
}
|
|
3113
3143
|
if (ownerError) {
|
|
3114
3144
|
throw new LanesError("outpost_dispatch_failed", `Outpost ${outpostId} rejected the ${action} command: ${ownerError.message}`, {
|
|
3115
3145
|
lane_id: lane.lane_id,
|
|
@@ -3402,6 +3432,9 @@ async function runPickup(ctx, opts) {
|
|
|
3402
3432
|
});
|
|
3403
3433
|
}
|
|
3404
3434
|
async function runList(ctx, opts) {
|
|
3435
|
+
if (opts.json && opts.human) {
|
|
3436
|
+
throw new LanesError("conflicting_output_format", "Use either --json or --human for hq lanes list", { json: true, human: true });
|
|
3437
|
+
}
|
|
3405
3438
|
const hqRoot = ctx.hqRoot();
|
|
3406
3439
|
const state = opts.state ? parseLaneState(opts.state) : undefined;
|
|
3407
3440
|
const senior = opts.senior ? parseSeniorRef(opts.senior) : undefined;
|
|
@@ -3416,9 +3449,11 @@ async function runList(ctx, opts) {
|
|
|
3416
3449
|
.filter((lane) => {
|
|
3417
3450
|
if (state && lane.state !== state)
|
|
3418
3451
|
return false;
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3452
|
+
const carriesFilter = laneStories(lane).some((story) => (!opts.project || story.project_id === opts.project) &&
|
|
3453
|
+
(!opts.story || story.story_id === opts.story));
|
|
3454
|
+
const matchingNoStory = !opts.story && opts.project !== undefined &&
|
|
3455
|
+
lane.no_story_reason !== undefined && lane.project_id === opts.project;
|
|
3456
|
+
if ((opts.project || opts.story) && !carriesFilter && !matchingNoStory) {
|
|
3422
3457
|
return false;
|
|
3423
3458
|
}
|
|
3424
3459
|
if (company && !recordMatchesCompany(lane, company)) {
|
|
@@ -3451,6 +3486,9 @@ async function runList(ctx, opts) {
|
|
|
3451
3486
|
return {
|
|
3452
3487
|
...lane,
|
|
3453
3488
|
...hireByLane.get(lane.lane_id),
|
|
3489
|
+
stories: laneStories(lane),
|
|
3490
|
+
story_ids: laneStories(lane).map((story) => story.story_id),
|
|
3491
|
+
no_story_reason: lane.no_story_reason ?? null,
|
|
3454
3492
|
...(remoteQueued
|
|
3455
3493
|
? { state: "remote", remote_state: lane.state, queued_reason: undefined }
|
|
3456
3494
|
: {}),
|
|
@@ -3472,12 +3510,74 @@ async function runList(ctx, opts) {
|
|
|
3472
3510
|
const host_capacity = ctx.lanePlacementEnabled()
|
|
3473
3511
|
? (await loadHostCapacitySnapshots(ctx, hqRoot)).entries
|
|
3474
3512
|
: undefined;
|
|
3475
|
-
|
|
3513
|
+
const result = {
|
|
3476
3514
|
ok: true,
|
|
3477
3515
|
action: "list",
|
|
3478
3516
|
lanes,
|
|
3479
3517
|
...(host_capacity !== undefined ? { host_capacity } : {}),
|
|
3518
|
+
};
|
|
3519
|
+
if (opts.human)
|
|
3520
|
+
console.log(formatLaneListHuman(lanes));
|
|
3521
|
+
else
|
|
3522
|
+
printJson(result);
|
|
3523
|
+
}
|
|
3524
|
+
function formatLaneListHuman(lanes) {
|
|
3525
|
+
if (lanes.length === 0)
|
|
3526
|
+
return "No lanes.";
|
|
3527
|
+
return lanes.map((lane) => {
|
|
3528
|
+
const ids = lane.story_ids;
|
|
3529
|
+
const storyLabel = ids.length > 0
|
|
3530
|
+
? "stories: " + ids.join(", ")
|
|
3531
|
+
: "no story: " + (lane.no_story_reason ?? "reason not recorded");
|
|
3532
|
+
return lane.lane_id + " " + lane.state + " " + lane.provider + " " + lane.project_id +
|
|
3533
|
+
" " + storyLabel;
|
|
3534
|
+
}).join("\n");
|
|
3535
|
+
}
|
|
3536
|
+
async function runStatus(ctx, opts) {
|
|
3537
|
+
if (!ctx.storyLedgerEnabled()) {
|
|
3538
|
+
throw new LanesError("story_ledger_disabled", "hq lanes status requires the lanes.story-ledger flag", {
|
|
3539
|
+
flag: "lanes.story-ledger",
|
|
3540
|
+
});
|
|
3541
|
+
}
|
|
3542
|
+
const hqRoot = ctx.hqRoot();
|
|
3543
|
+
const company = opts.company ? await ctx.resolveCompany(opts.company) : undefined;
|
|
3544
|
+
const lanes = loadAllLanes(hqRoot).filter((lane) => lane.company?.slug === company?.slug);
|
|
3545
|
+
const pending = new Map();
|
|
3546
|
+
for (const lane of lanes) {
|
|
3547
|
+
const refs = [];
|
|
3548
|
+
for (const questionId of lane.pending_question_ids ?? []) {
|
|
3549
|
+
const question = readQuestion(hqRoot, questionId);
|
|
3550
|
+
const sameCompany = question.company === null
|
|
3551
|
+
? lane.company === null
|
|
3552
|
+
: lane.company !== null &&
|
|
3553
|
+
question.company.uid === lane.company.uid &&
|
|
3554
|
+
question.company.slug === lane.company.slug;
|
|
3555
|
+
if (question.lane_id !== lane.lane_id || !sameCompany) {
|
|
3556
|
+
throw new LanesError("story_ledger_question_mismatch", `Pending question ${questionId} does not belong to lane ${lane.lane_id}`, { lane_id: lane.lane_id, question_id: questionId, question_lane_id: question.lane_id });
|
|
3557
|
+
}
|
|
3558
|
+
if (question.status === "pending") {
|
|
3559
|
+
refs.push({
|
|
3560
|
+
question_id: question.question_id,
|
|
3561
|
+
project_id: question.project_id,
|
|
3562
|
+
story_id: question.story_id,
|
|
3563
|
+
});
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
pending.set(lane.lane_id, refs);
|
|
3567
|
+
}
|
|
3568
|
+
const snapshot = buildLaneStatusSnapshot({
|
|
3569
|
+
hqRoot,
|
|
3570
|
+
projectId: opts.project,
|
|
3571
|
+
...(company ? { companySlug: company.slug } : {}),
|
|
3572
|
+
...(company ? { companyUid: company.uid } : {}),
|
|
3573
|
+
lanes,
|
|
3574
|
+
generatedAt: ctx.now().toISOString(),
|
|
3575
|
+
pendingQuestions: pending,
|
|
3480
3576
|
});
|
|
3577
|
+
if (opts.json)
|
|
3578
|
+
printJson(snapshot);
|
|
3579
|
+
else
|
|
3580
|
+
console.log(formatLaneStatusHuman(snapshot));
|
|
3481
3581
|
}
|
|
3482
3582
|
async function runCapacity(ctx) {
|
|
3483
3583
|
const hqRoot = ctx.hqRoot();
|
|
@@ -4336,34 +4436,43 @@ function validateResumeProfileModel(ctx, lane, profile) {
|
|
|
4336
4436
|
env: ctx.env,
|
|
4337
4437
|
});
|
|
4338
4438
|
}
|
|
4439
|
+
function applyResumeProfileAndEffort(current, next, input) {
|
|
4440
|
+
if (input.profile !== undefined) {
|
|
4441
|
+
const profile = input.profile.profile;
|
|
4442
|
+
next.worker = input.profile.id;
|
|
4443
|
+
next.worker_path = input.profile.path;
|
|
4444
|
+
next.worker_pack = input.profile.pack;
|
|
4445
|
+
next.worker_profile = profile.id;
|
|
4446
|
+
next.worker_profile_hash = profile.contentHash;
|
|
4447
|
+
next.worker_profile_ignored = ignoredWorkerProfileFields(current.provider);
|
|
4448
|
+
next.worker_policy_delivery = undefined;
|
|
4449
|
+
if (next.model_source !== "cli") {
|
|
4450
|
+
if (profile.meta.provider === current.provider && profile.meta.model !== undefined) {
|
|
4451
|
+
next.model = profile.meta.model;
|
|
4452
|
+
next.model_source = "profile";
|
|
4453
|
+
}
|
|
4454
|
+
else if (profile.meta.provider === current.provider) {
|
|
4455
|
+
next.model = undefined;
|
|
4456
|
+
next.model_source = "none";
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
if (input.effort !== undefined) {
|
|
4461
|
+
next.effort = input.effort.effort;
|
|
4462
|
+
next.effort_source = input.effort.source;
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4339
4465
|
function recordResumeSetup(ctx, lane, input) {
|
|
4340
|
-
if (input.profile === undefined &&
|
|
4466
|
+
if (input.profile === undefined &&
|
|
4467
|
+
input.effort === undefined &&
|
|
4468
|
+
input.meshCompany === undefined)
|
|
4341
4469
|
return lane;
|
|
4342
4470
|
return casUpdateLane(ctx.hqRoot(), lane.lane_id, (current) => {
|
|
4343
4471
|
if (current.version !== lane.version) {
|
|
4344
4472
|
throw new LanesError("lane_resume_setup_conflict", `Lane ${lane.lane_id} changed while resume settings were prepared; run the resume again`, { lane_id: lane.lane_id, prepared_version: lane.version, version: current.version });
|
|
4345
4473
|
}
|
|
4346
4474
|
const next = { ...current };
|
|
4347
|
-
|
|
4348
|
-
const profile = input.profile.profile;
|
|
4349
|
-
next.worker = input.profile.id;
|
|
4350
|
-
next.worker_path = input.profile.path;
|
|
4351
|
-
next.worker_pack = input.profile.pack;
|
|
4352
|
-
next.worker_profile = profile.id;
|
|
4353
|
-
next.worker_profile_hash = profile.contentHash;
|
|
4354
|
-
next.worker_profile_ignored = ignoredWorkerProfileFields(current.provider);
|
|
4355
|
-
next.worker_policy_delivery = undefined;
|
|
4356
|
-
if (current.model_source !== "cli") {
|
|
4357
|
-
if (profile.meta.provider === current.provider && profile.meta.model !== undefined) {
|
|
4358
|
-
next.model = profile.meta.model;
|
|
4359
|
-
next.model_source = "profile";
|
|
4360
|
-
}
|
|
4361
|
-
else if (profile.meta.provider === current.provider) {
|
|
4362
|
-
next.model = undefined;
|
|
4363
|
-
next.model_source = "none";
|
|
4364
|
-
}
|
|
4365
|
-
}
|
|
4366
|
-
}
|
|
4475
|
+
applyResumeProfileAndEffort(current, next, input);
|
|
4367
4476
|
if (input.meshCompany !== undefined) {
|
|
4368
4477
|
if (current.company === null)
|
|
4369
4478
|
next.mesh_company = input.meshCompany;
|
|
@@ -4373,6 +4482,40 @@ function recordResumeSetup(ctx, lane, input) {
|
|
|
4373
4482
|
return next;
|
|
4374
4483
|
});
|
|
4375
4484
|
}
|
|
4485
|
+
/** Resolve an explicit resume override or newly attached profile like create does. */
|
|
4486
|
+
function resolveResumeEffort(ctx, lane, input) {
|
|
4487
|
+
if (input.cliEffort === undefined && input.profile === undefined)
|
|
4488
|
+
return undefined;
|
|
4489
|
+
const config = loadLanesConfig(ctx.hqRoot());
|
|
4490
|
+
const providerConfig = config.providers[lane.provider];
|
|
4491
|
+
const accountId = input.account?.trim() || lane.account;
|
|
4492
|
+
const accountConfig = accountId === undefined
|
|
4493
|
+
? undefined
|
|
4494
|
+
: providerConfig.accounts[accountId];
|
|
4495
|
+
const profileDefaultsApply = input.profile?.profile.meta.provider === lane.provider;
|
|
4496
|
+
const pinnedModel = lane.model_source === undefined || lane.model_source === "cli"
|
|
4497
|
+
? lane.model
|
|
4498
|
+
: undefined;
|
|
4499
|
+
const model = resolveLaneModel({
|
|
4500
|
+
cliModel: input.model?.trim() || pinnedModel,
|
|
4501
|
+
profileModel: profileDefaultsApply ? input.profile?.profile.meta.model : undefined,
|
|
4502
|
+
accountModel: accountConfig?.model,
|
|
4503
|
+
providerModel: providerConfig.model,
|
|
4504
|
+
});
|
|
4505
|
+
return resolveLaneEffort({
|
|
4506
|
+
cliEffort: input.cliEffort,
|
|
4507
|
+
profileEffort: profileDefaultsApply ? input.profile?.profile.meta.effort : undefined,
|
|
4508
|
+
accountEffort: accountConfig?.effort,
|
|
4509
|
+
providerEffort: providerConfig.effort,
|
|
4510
|
+
model,
|
|
4511
|
+
});
|
|
4512
|
+
}
|
|
4513
|
+
function recordedResumeEffort(lane) {
|
|
4514
|
+
return {
|
|
4515
|
+
...(lane.effort !== undefined ? { effort: lane.effort } : {}),
|
|
4516
|
+
source: lane.effort_source ?? "none",
|
|
4517
|
+
};
|
|
4518
|
+
}
|
|
4376
4519
|
/**
|
|
4377
4520
|
* US-023: move the lane's provider session to `--account` (same provider)
|
|
4378
4521
|
* before a resume or a message resume. Every check runs before anything is
|
|
@@ -4440,7 +4583,7 @@ async function prepareAccountMove(ctx, lane, input) {
|
|
|
4440
4583
|
}).logged_in !== true) {
|
|
4441
4584
|
throw new LanesError("account_not_logged_in", `Account ${target} for ${lane.provider} is not logged in (no credential file)`, { provider: lane.provider, account: target, lane_id: laneId });
|
|
4442
4585
|
}
|
|
4443
|
-
const profile = input.workerProfile ?? (lane.worker_profile !== undefined
|
|
4586
|
+
const profile = input.workerProfile?.profile ?? (lane.worker_profile !== undefined
|
|
4444
4587
|
? loadWorkerProfile(hqRoot, lane.worker_profile)
|
|
4445
4588
|
: undefined);
|
|
4446
4589
|
const profileDefaultsApply = profile !== undefined && profile.meta.provider === lane.provider;
|
|
@@ -4542,6 +4685,10 @@ async function prepareAccountMove(ctx, lane, input) {
|
|
|
4542
4685
|
? undefined
|
|
4543
4686
|
: { session_id: sessionId, at, ...input.pending },
|
|
4544
4687
|
};
|
|
4688
|
+
applyResumeProfileAndEffort(current, next, {
|
|
4689
|
+
...(input.workerProfile !== undefined ? { profile: input.workerProfile } : {}),
|
|
4690
|
+
...(input.effort !== undefined ? { effort: input.effort } : {}),
|
|
4691
|
+
});
|
|
4545
4692
|
if (input.deferTurnStart) {
|
|
4546
4693
|
next.state = current.state;
|
|
4547
4694
|
}
|
|
@@ -4618,6 +4765,10 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
4618
4765
|
return;
|
|
4619
4766
|
}
|
|
4620
4767
|
assertLaneRunsHereForOperatorMutation(ctx, lane, "message");
|
|
4768
|
+
const cliEffort = parseCliEffort(lane.provider, opts.effort);
|
|
4769
|
+
if (cliEffort !== undefined && !providerSupportsSessionResume(lane.provider)) {
|
|
4770
|
+
throw new LanesError("effort_resume_unsupported", `Provider ${lane.provider} cannot change effort on a resumed session`, { lane_id: laneId, provider: lane.provider });
|
|
4771
|
+
}
|
|
4621
4772
|
// Check before transcript recovery, account probing, or message writes.
|
|
4622
4773
|
// The final state transition repeats this check under the senior lock.
|
|
4623
4774
|
await withResumeHireAdmission(ctx, lane, () => undefined);
|
|
@@ -4649,16 +4800,34 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
4649
4800
|
if (opts.worker !== undefined && mechanism === "dropbox") {
|
|
4650
4801
|
throw new LanesError("worker_profile_requires_resume", `Lane ${laneId} is still running; --worker can only be applied when message starts a resumed provider turn`, { lane_id: laneId, state: lane.state });
|
|
4651
4802
|
}
|
|
4803
|
+
if (cliEffort !== undefined && mechanism === "dropbox") {
|
|
4804
|
+
throw new LanesError("lane_effort_requires_resume", `Lane ${laneId} is still running; --effort can only be applied when message starts a resumed provider turn`, { lane_id: laneId, state: lane.state });
|
|
4805
|
+
}
|
|
4652
4806
|
const resumeProfile = opts.worker !== undefined
|
|
4653
4807
|
? resolveResumeProfile(ctx, lane, opts.worker)
|
|
4654
4808
|
: undefined;
|
|
4655
4809
|
if (opts.model !== undefined && opts.account === undefined) {
|
|
4656
4810
|
throw new LanesError("model_requires_account", "--model on hq lanes message is only accepted together with --account", {});
|
|
4657
4811
|
}
|
|
4812
|
+
const automaticAccountSelection = opts.account === undefined && mechanism === "resume"
|
|
4813
|
+
? await selectAutomaticClaudeResumeAccount(ctx, lane)
|
|
4814
|
+
: undefined;
|
|
4815
|
+
const resumeEffort = resolveResumeEffort(ctx, lane, {
|
|
4816
|
+
cliEffort,
|
|
4817
|
+
profile: resumeProfile,
|
|
4818
|
+
account: opts.account ?? automaticAccountSelection?.account,
|
|
4819
|
+
model: opts.model,
|
|
4820
|
+
});
|
|
4658
4821
|
const messageId = mintMessageId();
|
|
4659
4822
|
const at = ctx.now().toISOString();
|
|
4660
4823
|
const projectId = opts.project?.trim() || lane.project_id;
|
|
4661
4824
|
const storyId = opts.story?.trim() || lane.story_id;
|
|
4825
|
+
if (opts.story !== undefined && !opts.story.trim()) {
|
|
4826
|
+
throw new LanesError("missing_story", "--story must be non-empty", {});
|
|
4827
|
+
}
|
|
4828
|
+
if ((opts.project !== undefined || opts.story !== undefined) && ctx.storyLedgerEnabled()) {
|
|
4829
|
+
validateProjectStories(hqRoot, projectId, [storyId], lane.company?.slug);
|
|
4830
|
+
}
|
|
4662
4831
|
const dropboxMessage = {
|
|
4663
4832
|
at,
|
|
4664
4833
|
message_id: messageId,
|
|
@@ -4675,7 +4844,8 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
4675
4844
|
move = await prepareAccountMove(ctx, lane, {
|
|
4676
4845
|
account: opts.account,
|
|
4677
4846
|
cliModel: opts.model?.trim() || undefined,
|
|
4678
|
-
...(resumeProfile !== undefined ? { workerProfile: resumeProfile
|
|
4847
|
+
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
4848
|
+
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
4679
4849
|
pending: { trigger: "message", text, message_id: messageId, compact: opts.compact !== false },
|
|
4680
4850
|
});
|
|
4681
4851
|
lane = move.lane;
|
|
@@ -4684,13 +4854,14 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
4684
4854
|
}
|
|
4685
4855
|
}
|
|
4686
4856
|
else if (mechanism === "resume") {
|
|
4687
|
-
const selection =
|
|
4857
|
+
const selection = automaticAccountSelection;
|
|
4688
4858
|
if (selection !== undefined) {
|
|
4689
4859
|
if (selection.account !== lane.account) {
|
|
4690
4860
|
move = await prepareAccountMove(ctx, lane, {
|
|
4691
4861
|
account: selection.account,
|
|
4692
4862
|
cliModel: undefined,
|
|
4693
|
-
...(resumeProfile !== undefined ? { workerProfile: resumeProfile
|
|
4863
|
+
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
4864
|
+
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
4694
4865
|
pending: {
|
|
4695
4866
|
trigger: "message",
|
|
4696
4867
|
text,
|
|
@@ -4708,10 +4879,14 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
4708
4879
|
}
|
|
4709
4880
|
}
|
|
4710
4881
|
}
|
|
4711
|
-
if (resumeProfile !== undefined) {
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4882
|
+
if (resumeProfile !== undefined && opts.account === undefined) {
|
|
4883
|
+
validateResumeProfileModel(ctx, lane, resumeProfile.profile);
|
|
4884
|
+
}
|
|
4885
|
+
if (move === null && (resumeProfile !== undefined || resumeEffort !== undefined)) {
|
|
4886
|
+
lane = recordResumeSetup(ctx, lane, {
|
|
4887
|
+
...(resumeProfile !== undefined ? { profile: resumeProfile } : {}),
|
|
4888
|
+
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
4889
|
+
});
|
|
4715
4890
|
}
|
|
4716
4891
|
if (move?.queued) {
|
|
4717
4892
|
// Parked at the target's cap: reconcile resumes the session there.
|
|
@@ -4785,6 +4960,7 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
4785
4960
|
}, {
|
|
4786
4961
|
trigger: { kind: "message", text, message_id: messageId },
|
|
4787
4962
|
compact: opts.compact !== false,
|
|
4963
|
+
effort: resumeEffort ?? recordedResumeEffort(lane),
|
|
4788
4964
|
});
|
|
4789
4965
|
archiveResumeMessage(hqRoot, laneId, dropboxMessage);
|
|
4790
4966
|
return started;
|
|
@@ -4805,6 +4981,7 @@ async function runMessage(ctx, laneId, opts) {
|
|
|
4805
4981
|
...current,
|
|
4806
4982
|
project_id: projectId,
|
|
4807
4983
|
story_id: storyId,
|
|
4984
|
+
no_story_reason: storyId ? undefined : current.no_story_reason,
|
|
4808
4985
|
stories: laneStory(current, projectId, storyId)
|
|
4809
4986
|
? laneStories(current)
|
|
4810
4987
|
: [
|
|
@@ -5071,6 +5248,10 @@ async function runResume(ctx, laneId, opts) {
|
|
|
5071
5248
|
// slot that its senior has already assigned elsewhere.
|
|
5072
5249
|
await withResumeHireAdmission(ctx, lane, () => undefined);
|
|
5073
5250
|
assertLaneRunsHereForOperatorMutation(ctx, lane, "resume");
|
|
5251
|
+
const cliEffort = parseCliEffort(lane.provider, opts.effort);
|
|
5252
|
+
if (cliEffort !== undefined && !providerSupportsSessionResume(lane.provider)) {
|
|
5253
|
+
throw new LanesError("effort_resume_unsupported", `Provider ${lane.provider} cannot change effort on a resumed session`, { lane_id: laneId, provider: lane.provider });
|
|
5254
|
+
}
|
|
5074
5255
|
lane = laneWithRecoveredProviderSession(hqRoot, lane);
|
|
5075
5256
|
if (opts.model !== undefined && opts.account === undefined) {
|
|
5076
5257
|
throw new LanesError("model_requires_account", "--model on hq lanes resume is only accepted together with --account", {});
|
|
@@ -5102,6 +5283,12 @@ async function runResume(ctx, laneId, opts) {
|
|
|
5102
5283
|
const at = ctx.now().toISOString();
|
|
5103
5284
|
const projectId = opts.project?.trim() || lane.project_id;
|
|
5104
5285
|
const storyId = opts.story?.trim() || lane.story_id;
|
|
5286
|
+
if (opts.story !== undefined && !opts.story.trim()) {
|
|
5287
|
+
throw new LanesError("missing_story", "--story must be non-empty", {});
|
|
5288
|
+
}
|
|
5289
|
+
if ((opts.project !== undefined || opts.story !== undefined) && ctx.storyLedgerEnabled()) {
|
|
5290
|
+
validateProjectStories(hqRoot, projectId, [storyId], lane.company?.slug);
|
|
5291
|
+
}
|
|
5105
5292
|
const attributionChanged = projectId !== lane.project_id || storyId !== lane.story_id;
|
|
5106
5293
|
const messageId = text !== undefined || attributionChanged ? mintMessageId() : undefined;
|
|
5107
5294
|
let requestedMeshCompany;
|
|
@@ -5127,12 +5314,19 @@ async function runResume(ctx, laneId, opts) {
|
|
|
5127
5314
|
const automaticAccountSelection = opts.account === undefined
|
|
5128
5315
|
? await selectAutomaticClaudeResumeAccount(ctx, lane)
|
|
5129
5316
|
: undefined;
|
|
5317
|
+
const resumeEffort = resolveResumeEffort(ctx, lane, {
|
|
5318
|
+
cliEffort,
|
|
5319
|
+
profile: resumeProfile,
|
|
5320
|
+
account: opts.account ?? automaticAccountSelection?.account,
|
|
5321
|
+
model: opts.model,
|
|
5322
|
+
});
|
|
5130
5323
|
let move = null;
|
|
5131
5324
|
if (opts.account !== undefined) {
|
|
5132
5325
|
move = await prepareAccountMove(ctx, lane, {
|
|
5133
5326
|
account: opts.account,
|
|
5134
5327
|
cliModel: opts.model?.trim() || undefined,
|
|
5135
|
-
...(resumeProfile !== undefined ? { workerProfile: resumeProfile
|
|
5328
|
+
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
5329
|
+
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
5136
5330
|
pending: {
|
|
5137
5331
|
trigger: "resume",
|
|
5138
5332
|
resumed_from: resumedFrom,
|
|
@@ -5148,7 +5342,8 @@ async function runResume(ctx, laneId, opts) {
|
|
|
5148
5342
|
move = await prepareAccountMove(ctx, lane, {
|
|
5149
5343
|
account: automaticAccountSelection.account,
|
|
5150
5344
|
cliModel: undefined,
|
|
5151
|
-
...(resumeProfile !== undefined ? { workerProfile: resumeProfile
|
|
5345
|
+
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
5346
|
+
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
5152
5347
|
pending: {
|
|
5153
5348
|
trigger: "resume",
|
|
5154
5349
|
resumed_from: resumedFrom,
|
|
@@ -5165,7 +5360,8 @@ async function runResume(ctx, laneId, opts) {
|
|
|
5165
5360
|
}
|
|
5166
5361
|
}
|
|
5167
5362
|
lane = recordResumeSetup(ctx, lane, {
|
|
5168
|
-
...(resumeProfile !== undefined ? { profile: resumeProfile } : {}),
|
|
5363
|
+
...(move === null && resumeProfile !== undefined ? { profile: resumeProfile } : {}),
|
|
5364
|
+
...(move === null && resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
5169
5365
|
...(requestedMeshCompany !== undefined
|
|
5170
5366
|
? { meshCompany: requestedMeshCompany, threadId: meshThreadId }
|
|
5171
5367
|
: {}),
|
|
@@ -5239,6 +5435,7 @@ async function runResume(ctx, laneId, opts) {
|
|
|
5239
5435
|
...(messageId !== undefined ? { message_id: messageId } : {}),
|
|
5240
5436
|
},
|
|
5241
5437
|
compact: opts.compact !== false,
|
|
5438
|
+
effort: resumeEffort ?? recordedResumeEffort(lane),
|
|
5242
5439
|
});
|
|
5243
5440
|
});
|
|
5244
5441
|
}, ctx.sleep);
|
|
@@ -5272,6 +5469,7 @@ async function runResume(ctx, laneId, opts) {
|
|
|
5272
5469
|
...current,
|
|
5273
5470
|
project_id: projectId,
|
|
5274
5471
|
story_id: storyId,
|
|
5472
|
+
no_story_reason: storyId ? undefined : current.no_story_reason,
|
|
5275
5473
|
stories: laneStory(current, projectId, storyId)
|
|
5276
5474
|
? laneStories(current)
|
|
5277
5475
|
: [
|
|
@@ -5362,8 +5560,7 @@ function addCarriedStory(lane, projectId, storyId, at) {
|
|
|
5362
5560
|
}
|
|
5363
5561
|
return {
|
|
5364
5562
|
...lane,
|
|
5365
|
-
project_id: projectId,
|
|
5366
|
-
story_id: storyId,
|
|
5563
|
+
...(lane.story_id.trim() ? {} : { project_id: projectId, story_id: storyId }),
|
|
5367
5564
|
stories: [
|
|
5368
5565
|
...laneStories(lane),
|
|
5369
5566
|
{ project_id: projectId, story_id: storyId, status: "in_progress", added_at: at, updated_at: at },
|
|
@@ -5423,7 +5620,7 @@ async function emitStoryTransition(ctx, lane, projectId, storyId, event, status,
|
|
|
5423
5620
|
const company = lane.mesh_company ?? lane.company;
|
|
5424
5621
|
if (!company)
|
|
5425
5622
|
return;
|
|
5426
|
-
const ready = await ensureStoryThread(ctx, lane, projectId);
|
|
5623
|
+
const ready = await ensureStoryThread(ctx, lane, projectId, storyId);
|
|
5427
5624
|
if (!ready.thread_id)
|
|
5428
5625
|
return;
|
|
5429
5626
|
await emitLaneMeshEvent(ctx, company, ready.thread_id, event, {
|
|
@@ -5495,6 +5692,9 @@ async function runStoryAdd(ctx, laneId, storyId, opts) {
|
|
|
5495
5692
|
const normalizedStoryId = storyId.trim();
|
|
5496
5693
|
if (!normalizedStoryId)
|
|
5497
5694
|
throw new LanesError("missing_story", "<story-id> is required", {});
|
|
5695
|
+
if (ctx.storyLedgerEnabled()) {
|
|
5696
|
+
validateProjectStories(hqRoot, projectId, [normalizedStoryId], lane.company?.slug);
|
|
5697
|
+
}
|
|
5498
5698
|
const company = lane.mesh_company ?? lane.company;
|
|
5499
5699
|
const warnings = [];
|
|
5500
5700
|
if (company) {
|
|
@@ -5510,7 +5710,11 @@ async function runStoryAdd(ctx, laneId, storyId, opts) {
|
|
|
5510
5710
|
const before = lane;
|
|
5511
5711
|
const next = casUpdateLane(hqRoot, laneId, (current) => {
|
|
5512
5712
|
assertLaneRunsHereForOperatorMutation(ctx, current, "update story status for");
|
|
5513
|
-
|
|
5713
|
+
const added = addCarriedStory(current, projectId, normalizedStoryId, at);
|
|
5714
|
+
return {
|
|
5715
|
+
...added,
|
|
5716
|
+
no_story_reason: undefined,
|
|
5717
|
+
};
|
|
5514
5718
|
});
|
|
5515
5719
|
const transitionWarning = await commitStoryTransition(ctx, before, next, projectId, normalizedStoryId, "lane_started", "in_progress");
|
|
5516
5720
|
if (transitionWarning !== undefined)
|
|
@@ -6855,13 +7059,16 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
6855
7059
|
.command("lanes", { hidden: true })
|
|
6856
7060
|
.description("Create, supervise, and steer worker lanes; routed decision queue");
|
|
6857
7061
|
registerOutpostPrunerCommand(lanes);
|
|
7062
|
+
const noStoryOption = new Option("--no-story <reason>", "Create a lane without a story and record why");
|
|
7063
|
+
noStoryOption.attributeName = () => "noStory";
|
|
6858
7064
|
lanes
|
|
6859
7065
|
.command("create")
|
|
6860
7066
|
.description("Register a lane, run admission, and return immediately")
|
|
6861
7067
|
.option("--company <slug|uid>", "Company slug or cloud uid; omit for a core/personal lane")
|
|
6862
7068
|
.option("--mesh-company <slug|uid>", "Explicit Work Mesh company for a personal lane; membership is verified")
|
|
6863
7069
|
.requiredOption("--project <slug>", "HQ project slug")
|
|
6864
|
-
.
|
|
7070
|
+
.option("--story <id>", "Story id from the project's prd.json")
|
|
7071
|
+
.addOption(noStoryOption)
|
|
6865
7072
|
.option("--additional-story <id>", "Additional story carried by this lane; repeatable and discouraged when active", collectRepeatable)
|
|
6866
7073
|
.requiredOption("--brief-file <path>", "Path to the lane brief")
|
|
6867
7074
|
.option("--worker <name>", "HQ worker or reusable profile id")
|
|
@@ -6911,10 +7118,20 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
6911
7118
|
.option("--story <id>", "Filter by story")
|
|
6912
7119
|
.option("--state <state>", `Filter by state (${LANE_STATES.join("|")})`)
|
|
6913
7120
|
.option("--senior <ref>", "Filter by senior ref")
|
|
6914
|
-
.option("--json", "JSON on stdout (
|
|
7121
|
+
.option("--json", "Emit JSON on stdout (default)")
|
|
7122
|
+
.option("--human", "Emit human-readable output")
|
|
6915
7123
|
.action(wrap(async (opts) => {
|
|
6916
7124
|
await runList(ctx, opts);
|
|
6917
7125
|
}));
|
|
7126
|
+
lanes
|
|
7127
|
+
.command("status")
|
|
7128
|
+
.description("Summarize project stories, lanes, PRs, blockers, and pending questions")
|
|
7129
|
+
.requiredOption("--project <slug>", "HQ project slug")
|
|
7130
|
+
.option("--company <slug|uid>", "Company project scope; omit for a personal project")
|
|
7131
|
+
.option("--json", "Emit JSON on stdout")
|
|
7132
|
+
.action(wrap(async (opts) => {
|
|
7133
|
+
await runStatus(ctx, opts);
|
|
7134
|
+
}));
|
|
6918
7135
|
lanes
|
|
6919
7136
|
.command("capacity")
|
|
6920
7137
|
.description("Report this host's lane capacity by provider and account")
|
|
@@ -7049,6 +7266,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7049
7266
|
.option("--story <id>", "Re-point story attribution from this message")
|
|
7050
7267
|
.option("--account <id>", "Move the lane's session to this account of its provider before the message resumes it")
|
|
7051
7268
|
.option("--model <id>", "Model for the moved lane (with --account only)")
|
|
7269
|
+
.option("--effort <level>", "Reasoning effort for the resumed provider turn")
|
|
7052
7270
|
.option("--worker <profile>", "Attach a personal worker profile when this message resumes the lane")
|
|
7053
7271
|
.option("--no-compact", "Skip the resume-time compaction check for this message")
|
|
7054
7272
|
.action(wrap(async (laneId, opts) => {
|
|
@@ -7063,6 +7281,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7063
7281
|
.option("--story <id>", "Re-point story attribution from this resume")
|
|
7064
7282
|
.option("--account <id>", "Move the lane's session to this account of its provider before resuming")
|
|
7065
7283
|
.option("--model <id>", "Model for the moved lane (with --account only)")
|
|
7284
|
+
.option("--effort <level>", "Reasoning effort for the resumed provider turn")
|
|
7066
7285
|
.option("--worker <profile>", "Attach a personal worker profile for the resumed turn")
|
|
7067
7286
|
.option("--mesh-company <slug>", "Attach Work Mesh attribution after verifying company membership")
|
|
7068
7287
|
.option("--no-compact", "Skip the resume-time compaction check for this resume")
|