@indigoai-us/hq-cli 5.301.0 → 5.303.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 +26 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +99 -12
- package/assets/scaffold/core/scripts/token-usage-report.sh +72 -20
- package/dist/command-catalog.generated.d.ts +4 -1
- package/dist/command-catalog.generated.js +5 -1
- package/dist/commands/bot.js +1 -0
- package/dist/commands/core-checkpoint.js +1 -0
- package/dist/commands/core-handoff-fast.js +1 -0
- package/dist/commands/core-handoff.js +2 -0
- package/dist/commands/deploy-comments.js +5 -2
- package/dist/commands/integrations-connect.d.ts +3 -0
- package/dist/commands/integrations-connect.js +21 -2
- package/dist/commands/lanes-message.js +33 -3
- package/dist/commands/lanes-questions.d.ts +4 -2
- package/dist/commands/lanes-questions.js +123 -2
- package/dist/commands/lanes-resume.js +25 -4
- package/dist/commands/lanes-shared.d.ts +13 -1
- package/dist/commands/lanes-watch.js +5 -1
- package/dist/commands/lanes.js +91 -9
- package/dist/commands/monitor.js +17 -2
- package/dist/commands/report.js +5 -2
- package/dist/commands/rescue.d.ts +4 -0
- package/dist/commands/rescue.js +27 -17
- package/dist/lib/agent-kit/fallback.d.ts +1 -0
- package/dist/lib/agent-kit/fallback.js +1 -0
- package/dist/lib/bot/cli-update.d.ts +0 -8
- package/dist/lib/bot/cli-update.js +5 -5
- package/dist/lib/bot/daemon.d.ts +5 -1
- package/dist/lib/bot/daemon.js +4 -4
- package/dist/lib/bot/local-jobs.d.ts +1 -21
- package/dist/lib/bot/local-jobs.js +12 -12
- package/dist/lib/core-utils/soft-timeout.js +1 -1
- package/dist/lib/daemon/config.d.ts +0 -1
- package/dist/lib/daemon/config.js +1 -1
- package/dist/lib/deploy-apps.d.ts +13 -0
- package/dist/lib/deploy-apps.js +24 -0
- package/dist/lib/index-render/projects.js +82 -5
- package/dist/lib/lanes/delivery.d.ts +7 -3
- package/dist/lib/lanes/delivery.js +18 -8
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +13 -3
- package/dist/lib/lanes/keep-alive.js +29 -5
- package/dist/lib/lanes/outpost-dispatch.d.ts +5 -0
- package/dist/lib/lanes/outpost-dispatch.js +12 -0
- package/dist/lib/lanes/reconcile.d.ts +1 -0
- package/dist/lib/lanes/reconcile.js +358 -38
- package/dist/lib/lanes/spawn.d.ts +1 -0
- package/dist/lib/lanes/spawn.js +20 -10
- package/dist/lib/lanes/store.js +24 -0
- package/dist/lib/lanes/story-ledger.d.ts +2 -0
- package/dist/lib/lanes/story-ledger.js +8 -0
- package/dist/lib/lanes/types.d.ts +26 -4
- package/dist/lib/lanes/types.js +33 -1
- package/dist/lib/lanes/watch.d.ts +1 -0
- package/dist/lib/lanes/watch.js +19 -6
- package/dist/lib/lanes/watcher.js +139 -9
- package/dist/lib/mesh/live/daemon/run.js +36 -2
- package/dist/lib/report-snapshot.d.ts +0 -2
- package/dist/lib/report-snapshot.js +2 -2
- package/dist/lib/search-index/background.d.ts +1 -0
- package/dist/lib/search-index/background.js +1 -0
- package/dist/utils/run-bundled-script.js +103 -1
- package/dist/utils/update-command-supervisor.cjs +1 -0
- package/dist/utils/version-gate.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildOutpostQuestionAnswerCommand, buildOutpostQuestionShowCommand, dispatchErrorMessage } from "../lib/lanes/outpost-dispatch.js";
|
|
1
|
+
import { buildOutpostQuestionAnswerCommand, buildOutpostQuestionCancelCommand, buildOutpostQuestionShowCommand, dispatchErrorMessage } from "../lib/lanes/outpost-dispatch.js";
|
|
2
2
|
import { assertNoSecretText } from "../lib/lanes/text-scan.js";
|
|
3
3
|
import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
|
|
4
4
|
import { appendCappedHistory, createQuestion, isLanesError, listSessionLiveness, parseLivenessState, parseQuestionRecord, parseQuestionStatus, readDaemonPresenceConnected, readLane, readQuestion, wakeAnsweredLaneNow, sessionLiveness, appendDropboxMessage, answerDropboxMessage, laneAcceptsDropboxDelivery, laneIdForAnswerDelivery, writeQuestionBody } from "../lib/lanes/index.js";
|
|
@@ -20,7 +20,7 @@ export function resolveAnswerChoice(choice, options, freeform) {
|
|
|
20
20
|
throw new LanesError("off_menu_choice", `Choice ${JSON.stringify(choice)} is not on the menu. Pass --freeform to allow it.`, { choice, options });
|
|
21
21
|
}
|
|
22
22
|
export function createLaneQuestionsHandlers(deps) {
|
|
23
|
-
const { CAPACITY_PROBE_TIMEOUT_MS, MAX_PLACEMENT_OUTPOSTS, OUTPOST_QUESTION_LOOKUP_CONCURRENCY, assertLaneRunsHereForOperatorMutation, assertQuestionAnswerAuthorized, assertQuestionCompanyMatchesLane, casUpdateQuestion, effectiveLaneQuestionCompany, emitLaneMeshEvent, groupQuestionLanesByCompany, isQuestionAnswer, laneIsHostedHere, livenessDepsFromCtx, loadAllLanes, loadAllQuestions, mapWithConcurrency, mintMessageId, parseOutpostQuestionAudience, parseOutpostQuestionBody, parseRemoteJson, parseSeniorRef, questionCompanyForLane, questionCompanyRefMatches, questionListCompanyArg, readOutpostQuestionRecords, readQuestionBody, printJson, readSessionMeta, reconcileHostFromCtx, resolveActiveQuestionCompany, resolveAudience, sameQuestionAnswer, sameQuestionCompany, seniorOfSession, writeAnswerDeliveryFailed } = deps;
|
|
23
|
+
const { CAPACITY_PROBE_TIMEOUT_MS, MAX_PLACEMENT_OUTPOSTS, OUTPOST_QUESTION_LOOKUP_CONCURRENCY, assertLaneRunsHereForOperatorMutation, assertQuestionAnswerAuthorized, assertQuestionCompanyMatchesLane, casUpdateQuestion, casUpdateLane, effectiveLaneQuestionCompany, emitLaneMeshEvent, groupQuestionLanesByCompany, isQuestionAnswer, laneIsHostedHere, livenessDepsFromCtx, loadAllLanes, loadAllQuestions, mapWithConcurrency, mintMessageId, parseOutpostQuestionAudience, parseOutpostQuestionBody, parseRemoteJson, parseSeniorRef, questionCompanyForLane, questionCompanyRefMatches, questionListCompanyArg, readOutpostQuestionRecords, readQuestionBody, printJson, readSessionMeta, reconcileHostFromCtx, resolveActiveQuestionCompany, resolveAudience, sameQuestionAnswer, sameQuestionCompany, seniorOfSession, writeAnswerDeliveryFailed } = deps;
|
|
24
24
|
function requireQuestionBody(hqRoot, question) {
|
|
25
25
|
const body = readQuestionBody(hqRoot, question);
|
|
26
26
|
if (!body) {
|
|
@@ -52,6 +52,16 @@ export function createLaneQuestionsHandlers(deps) {
|
|
|
52
52
|
return audience.kind === "user";
|
|
53
53
|
return audience.kind === filter.kind && audience.id === filter.id;
|
|
54
54
|
}
|
|
55
|
+
function questionAudienceMatchesSenior(audience, lane, ctx) {
|
|
56
|
+
if (audienceMatches(audience, lane.senior))
|
|
57
|
+
return true;
|
|
58
|
+
const sessionId = sessionIdFromEnv(ctx.env);
|
|
59
|
+
if (sessionId && audienceMatches(audience, { kind: "session", id: sessionId }))
|
|
60
|
+
return true;
|
|
61
|
+
const callerLaneId = ctx.env.HQ_LANE_ID?.trim();
|
|
62
|
+
return callerLaneId !== undefined && callerLaneId.length > 0 &&
|
|
63
|
+
audienceMatches(audience, { kind: "lane", id: callerLaneId });
|
|
64
|
+
}
|
|
55
65
|
function parseForFilter(raw, ctx) {
|
|
56
66
|
const trimmed = raw.trim();
|
|
57
67
|
if (trimmed === "me") {
|
|
@@ -384,6 +394,115 @@ export function createLaneQuestionsHandlers(deps) {
|
|
|
384
394
|
errors: [...new Map(errors.map((entry) => [entry.host, entry])).values()],
|
|
385
395
|
};
|
|
386
396
|
}
|
|
397
|
+
async function cancelFallbackQuestionsForLane(ctx, lane, trigger, at) {
|
|
398
|
+
const hqRoot = ctx.hqRoot();
|
|
399
|
+
const caller = ctx.env.HQ_LANE_ID?.trim() || sessionIdFromEnv(ctx.env) || "user";
|
|
400
|
+
const reason = `Fallback ${trigger} by ${caller} at ${at}`;
|
|
401
|
+
const failures = [];
|
|
402
|
+
const cancelledQuestionIds = [];
|
|
403
|
+
const remoteOwner = lane.host.kind === "outpost" && !laneIsHostedHere(ctx, lane);
|
|
404
|
+
let pending;
|
|
405
|
+
try {
|
|
406
|
+
pending = remoteOwner
|
|
407
|
+
? (await readOutpostQuestionRecords(ctx, lane.host.id, effectiveLaneQuestionCompany(lane, hqRoot)))
|
|
408
|
+
.map(({ question }) => question)
|
|
409
|
+
.filter((question) => question.origin.id === lane.lane_id && question.status === "pending")
|
|
410
|
+
: loadAllQuestions(hqRoot).filter((question) => question.origin.id === lane.lane_id && question.status === "pending");
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
failures.push({ error: dispatchErrorMessage(err) });
|
|
414
|
+
pending = [];
|
|
415
|
+
}
|
|
416
|
+
for (const question of pending) {
|
|
417
|
+
try {
|
|
418
|
+
if (remoteOwner) {
|
|
419
|
+
const outpostId = lane.host.id;
|
|
420
|
+
const dispatched = await ctx.dispatchOutpostExec({
|
|
421
|
+
outpostId,
|
|
422
|
+
command: buildOutpostQuestionCancelCommand({ questionId: question.question_id, reason }),
|
|
423
|
+
timeoutSeconds: Math.ceil(CAPACITY_PROBE_TIMEOUT_MS / 1000),
|
|
424
|
+
waitMs: CAPACITY_PROBE_TIMEOUT_MS,
|
|
425
|
+
});
|
|
426
|
+
const remote = parseRemoteJson(dispatched.stdout, "Outpost question cancel");
|
|
427
|
+
const remoteQuestion = remote?.question !== null && typeof remote?.question === "object"
|
|
428
|
+
? remote.question
|
|
429
|
+
: null;
|
|
430
|
+
if (dispatched.done !== true || dispatched.status !== "Success" || dispatched.exitCode !== 0 ||
|
|
431
|
+
remote?.ok !== true || remote.action !== "questions.cancel" ||
|
|
432
|
+
remoteQuestion?.question_id !== question.question_id || remoteQuestion.status !== "cancelled") {
|
|
433
|
+
throw new LanesError("outpost_question_cancel_unconfirmed", `Outpost ${outpostId} did not confirm cancellation of question ${question.question_id}`, { lane_id: lane.lane_id, question_id: question.question_id, outpost_id: outpostId });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
const cancellationEntry = {
|
|
438
|
+
hop: question.hop,
|
|
439
|
+
audience: resolveAudience(hqRoot, question, ctx),
|
|
440
|
+
at,
|
|
441
|
+
reason: "cancelled",
|
|
442
|
+
detail: reason,
|
|
443
|
+
};
|
|
444
|
+
const updated = casUpdateQuestion(hqRoot, question.question_id, (current) => {
|
|
445
|
+
if (current.status !== "pending")
|
|
446
|
+
return current;
|
|
447
|
+
return {
|
|
448
|
+
...current,
|
|
449
|
+
status: "cancelled",
|
|
450
|
+
routing_history: appendCappedHistory(current.routing_history, cancellationEntry),
|
|
451
|
+
};
|
|
452
|
+
});
|
|
453
|
+
if (updated.status !== "cancelled")
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
cancelledQuestionIds.push(question.question_id);
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
failures.push({ question_id: question.question_id, error: dispatchErrorMessage(err) });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (failures.length > 0) {
|
|
463
|
+
const failure = {
|
|
464
|
+
at,
|
|
465
|
+
caller,
|
|
466
|
+
trigger,
|
|
467
|
+
question_ids: failures.flatMap((entry) => entry.question_id ? [entry.question_id] : []),
|
|
468
|
+
error: failures.map((entry) => entry.error).join("; "),
|
|
469
|
+
};
|
|
470
|
+
try {
|
|
471
|
+
casUpdateLane(hqRoot, lane.lane_id, (current) => {
|
|
472
|
+
const history = current.fallback_question_cancellation_failures ?? [];
|
|
473
|
+
const duplicate = history.some((entry) => entry.at === failure.at && entry.caller === caller && entry.trigger === trigger);
|
|
474
|
+
return duplicate
|
|
475
|
+
? current
|
|
476
|
+
: {
|
|
477
|
+
...current,
|
|
478
|
+
fallback_question_cancellation_failures: [...history, failure].slice(-20),
|
|
479
|
+
};
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
const message = `Could not record fallback question cancellation failure for lane ${lane.lane_id}: ${dispatchErrorMessage(err)}\n`;
|
|
484
|
+
process.stderr.write(message);
|
|
485
|
+
failures.push({ error: `Failure record could not be persisted: ${dispatchErrorMessage(err)}` });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return { cancelled_question_ids: cancelledQuestionIds, failures };
|
|
489
|
+
}
|
|
490
|
+
async function hasPendingQuestionForLane(ctx, lane) {
|
|
491
|
+
const hqRoot = ctx.hqRoot();
|
|
492
|
+
const local = loadAllQuestions(hqRoot).some((question) => question.lane_id === lane.lane_id &&
|
|
493
|
+
question.status === "pending" &&
|
|
494
|
+
questionAudienceMatchesSenior(resolveAudience(hqRoot, question, ctx), lane, ctx));
|
|
495
|
+
if (local)
|
|
496
|
+
return true;
|
|
497
|
+
if (lane.host.kind !== "outpost" || laneIsHostedHere(ctx, lane))
|
|
498
|
+
return false;
|
|
499
|
+
// Reuse the owner lookup used by questions list/answer. An unreachable
|
|
500
|
+
// owner leaves no answerable question, so the message/resume path can recover.
|
|
501
|
+
const lookup = await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot);
|
|
502
|
+
return lookup.questions.some(({ question, lane: ownerLane, audience }) => ownerLane.lane_id === lane.lane_id &&
|
|
503
|
+
question.status === "pending" &&
|
|
504
|
+
questionAudienceMatchesSenior(audience, ownerLane, ctx));
|
|
505
|
+
}
|
|
387
506
|
function syncPrimaryQuestionDelivery(hqRoot, owner) {
|
|
388
507
|
const deliveredAt = owner.question.delivered_at;
|
|
389
508
|
if (!deliveredAt)
|
|
@@ -870,6 +989,8 @@ export function createLaneQuestionsHandlers(deps) {
|
|
|
870
989
|
});
|
|
871
990
|
}
|
|
872
991
|
return {
|
|
992
|
+
hasPendingQuestionForLane,
|
|
993
|
+
cancelFallbackQuestionsForLane,
|
|
873
994
|
runQuestionsList,
|
|
874
995
|
runQuestionsShow,
|
|
875
996
|
runQuestionsAnswer,
|
|
@@ -75,7 +75,8 @@ export function createLaneResumeHandlers(deps) {
|
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* `hq lanes resume <lane-id> [--text <msg>]`: the explicit opt-in that
|
|
78
|
-
* continues a lane in `interrupted`, `done` or `blocked`,
|
|
78
|
+
* continues a lane in `interrupted`, `done` or `blocked`, an `awaiting_input`
|
|
79
|
+
* lane with no answerable pending question, or a failed lane
|
|
79
80
|
* whose only terminal reason is an invalid envelope after its repair turn,
|
|
80
81
|
* on its recorded
|
|
81
82
|
* provider session. `hq lanes message` keeps refusing `interrupted`; this
|
|
@@ -124,12 +125,21 @@ export function createLaneResumeHandlers(deps) {
|
|
|
124
125
|
throw new LanesError("worker_profile_hash_mismatch", "Profile re-pin context is only valid on an Outpost owner dispatch", { lane_id: laneId });
|
|
125
126
|
}
|
|
126
127
|
if (resolution.remoteOnly || (lane.host.kind === "outpost" && !laneIsHostedHere(ctx, lane))) {
|
|
128
|
+
const fallbackAt = lane.state === "awaiting_input" ? ctx.now().toISOString() : undefined;
|
|
127
129
|
if (!resolution.remoteOnly) {
|
|
128
130
|
await withResumeHireAdmission(ctx, lane, () => undefined);
|
|
129
131
|
}
|
|
130
132
|
await dispatchOutpostLaneCommand(ctx, lane, "resume", opts, resolution.remoteOnly
|
|
131
|
-
? {
|
|
132
|
-
|
|
133
|
+
? {
|
|
134
|
+
syncPrimary: false,
|
|
135
|
+
expectedOwnerHost: resolution.ownerHost,
|
|
136
|
+
...(fallbackAt === undefined ? {} : {
|
|
137
|
+
afterFallbackResume: () => deps.cancelFallbackQuestionsForLane(ctx, lane, "resume", fallbackAt),
|
|
138
|
+
}),
|
|
139
|
+
}
|
|
140
|
+
: fallbackAt === undefined
|
|
141
|
+
? undefined
|
|
142
|
+
: { afterFallbackResume: () => deps.cancelFallbackQuestionsForLane(ctx, lane, "resume", fallbackAt) });
|
|
133
143
|
return;
|
|
134
144
|
}
|
|
135
145
|
// Fail before provider/session work if a terminal child would consume a
|
|
@@ -171,7 +181,10 @@ export function createLaneResumeHandlers(deps) {
|
|
|
171
181
|
opts.compact === false)) {
|
|
172
182
|
throw new LanesError("worker_profile_recovery_options_invalid", "Queued worker-profile recovery accepts only --worker; rerun hq lanes resume after reconcile admits the lane", { lane_id: laneId });
|
|
173
183
|
}
|
|
174
|
-
const
|
|
184
|
+
const pendingQuestion = lane.state === "awaiting_input"
|
|
185
|
+
? await deps.hasPendingQuestionForLane(ctx, lane)
|
|
186
|
+
: false;
|
|
187
|
+
const refusal = queuedProfileRecovery || (lane.state === "awaiting_input" && !pendingQuestion)
|
|
175
188
|
? null
|
|
176
189
|
: resumeCommandRefusal(lane, { allowFailed: opts.fromFailed === true });
|
|
177
190
|
if (refusal !== null) {
|
|
@@ -444,6 +457,7 @@ export function createLaneResumeHandlers(deps) {
|
|
|
444
457
|
trigger: {
|
|
445
458
|
kind: "resume",
|
|
446
459
|
from: resumedFrom,
|
|
460
|
+
...(resumedFrom === "awaiting_input" && !pendingQuestion ? { allowAwaitingInput: true } : {}),
|
|
447
461
|
...(opts.fromFailed === true ? { fromFailed: true } : {}),
|
|
448
462
|
...(failedResumeExpectedSenior !== undefined
|
|
449
463
|
? { expectedSenior: failedResumeExpectedSenior }
|
|
@@ -460,6 +474,9 @@ export function createLaneResumeHandlers(deps) {
|
|
|
460
474
|
}, hireGrantContext);
|
|
461
475
|
}, ctx.sleep);
|
|
462
476
|
}
|
|
477
|
+
const questionCancellation = resumedFrom === "awaiting_input" && !pendingQuestion
|
|
478
|
+
? await deps.cancelFallbackQuestionsForLane(ctx, lane, "resume", at)
|
|
479
|
+
: null;
|
|
463
480
|
if (deliveryText !== undefined && messageId !== undefined) {
|
|
464
481
|
// Same audit copy a message resume leaves; nothing drains it. Archive only
|
|
465
482
|
// after a successful spawn so a concurrent resume cannot leave a phantom.
|
|
@@ -519,6 +536,10 @@ export function createLaneResumeHandlers(deps) {
|
|
|
519
536
|
story_id: next.story_id,
|
|
520
537
|
stories: laneStories(next),
|
|
521
538
|
story_warning: storyWarning(next) ?? null,
|
|
539
|
+
...(questionCancellation === null ? {} : {
|
|
540
|
+
cancelled_questions: questionCancellation.cancelled_question_ids,
|
|
541
|
+
question_cancellation_failures: questionCancellation.failures,
|
|
542
|
+
}),
|
|
522
543
|
lane: next,
|
|
523
544
|
});
|
|
524
545
|
}
|
|
@@ -137,11 +137,20 @@ export type CommandLaneResolution = {
|
|
|
137
137
|
outpostId: string;
|
|
138
138
|
ownerHost: LaneRecord["host"];
|
|
139
139
|
};
|
|
140
|
+
export interface FallbackQuestionCancellationResult {
|
|
141
|
+
cancelled_question_ids: string[];
|
|
142
|
+
failures: Array<{
|
|
143
|
+
question_id?: string;
|
|
144
|
+
error: string;
|
|
145
|
+
}>;
|
|
146
|
+
}
|
|
140
147
|
export interface OutpostLaneDispatchBehavior {
|
|
141
148
|
/** Keep remote-only lanes out of the primary lane store. */
|
|
142
149
|
syncPrimary?: boolean;
|
|
143
150
|
/** Host identity returned by the remote record lookup for a remote-only lane. */
|
|
144
151
|
expectedOwnerHost?: LaneRecord["host"];
|
|
152
|
+
/** Run after a successful remote awaiting_input fallback, before printing. */
|
|
153
|
+
afterFallbackResume?: () => Promise<FallbackQuestionCancellationResult>;
|
|
145
154
|
}
|
|
146
155
|
export interface LaneMessageHandlerDeps {
|
|
147
156
|
/** Test seam invoked after a provider worker has been started. */
|
|
@@ -177,6 +186,8 @@ export interface LaneMessageHandlerDeps {
|
|
|
177
186
|
recordResumeSetup(ctx: Ctx, lane: LaneRecord, input: RecordResumeSetupInput): LaneRecord;
|
|
178
187
|
resolveCommandLane(ctx: Ctx, laneId: string): Promise<CommandLaneResolution>;
|
|
179
188
|
resolveActiveQuestionCompany(ctx: Ctx, message?: string): Promise<LaneCompanyRef>;
|
|
189
|
+
hasPendingQuestionForLane(ctx: Ctx, lane: LaneRecord): Promise<boolean>;
|
|
190
|
+
cancelFallbackQuestionsForLane(ctx: Ctx, lane: LaneRecord, trigger: "message" | "resume", at: string): Promise<FallbackQuestionCancellationResult>;
|
|
180
191
|
resolveResumeProfile(ctx: Ctx, lane: LaneRecord, workerId: string): ResolvedResumeProfile;
|
|
181
192
|
selectAutomaticClaudeResumeAccount(ctx: Ctx, lane: LaneRecord, workerProfile?: ResolvedResumeProfile, options?: {
|
|
182
193
|
repairUnavailableAccount?: boolean;
|
|
@@ -188,7 +199,7 @@ export interface LaneMessageHandlerDeps {
|
|
|
188
199
|
validateResumeProfileModel(ctx: Ctx, lane: LaneRecord, profile: WorkerProfile): void;
|
|
189
200
|
withResumeHireAdmission<T>(ctx: Ctx, lane: LaneRecord, fn: () => T, grantContext?: OutpostHireGrantContext): Promise<T>;
|
|
190
201
|
}
|
|
191
|
-
export interface LaneResumeHandlerDeps extends Pick<LaneMessageHandlerDeps, "onLaneWorkerStarted" | "archiveResumeMessage" | "assertLaneRunsHereForAdmission" | "assertLaneRunsHereForOperatorMutation" | "assessLaneAdmission" | "casUpdateLane" | "laneIsHostedHere" | "laneWithRecoveredProviderSession" | "mintMessageId" | "prepareAccountMove" | "persistClaudeAccountSelection" | "printJson" | "recordResumeSetup" | "resolveCommandLane" | "resolveResumeProfile" | "selectAutomaticClaudeResumeAccount" | "validateResumeProfileModel" | "withResumeHireAdmission"> {
|
|
202
|
+
export interface LaneResumeHandlerDeps extends Pick<LaneMessageHandlerDeps, "onLaneWorkerStarted" | "archiveResumeMessage" | "assertLaneRunsHereForAdmission" | "assertLaneRunsHereForOperatorMutation" | "assessLaneAdmission" | "casUpdateLane" | "laneIsHostedHere" | "laneWithRecoveredProviderSession" | "mintMessageId" | "prepareAccountMove" | "persistClaudeAccountSelection" | "printJson" | "recordResumeSetup" | "resolveCommandLane" | "hasPendingQuestionForLane" | "cancelFallbackQuestionsForLane" | "resolveResumeProfile" | "selectAutomaticClaudeResumeAccount" | "validateResumeProfileModel" | "withResumeHireAdmission"> {
|
|
192
203
|
dispatchOutpostLaneCommand(ctx: Ctx, lane: LaneRecord, action: "resume", options: LaneResumeOptions, behavior?: OutpostLaneDispatchBehavior): Promise<void>;
|
|
193
204
|
authorizeFailedResume(ctx: Ctx, lane: LaneRecord, encodedContext?: string): string;
|
|
194
205
|
authorizeResumeProfileRepin(ctx: Ctx, lane: LaneRecord, profile: ResolvedResumeProfile | undefined, encodedContext: string | undefined): ResumeProfileRepinAuthorization | undefined;
|
|
@@ -382,6 +393,7 @@ export interface LaneQuestionsHandlerDeps {
|
|
|
382
393
|
assertQuestionAnswerAuthorized(ctx: Ctx, lane: LaneRecord): void;
|
|
383
394
|
assertQuestionCompanyMatchesLane(question: QuestionRecord, lane: Pick<LaneRecord, "lane_id" | "company" | "mesh_company" | "project_id">, code: string, hqRoot?: string): void;
|
|
384
395
|
casUpdateQuestion(hqRoot: string, questionId: string, updater: (current: QuestionRecord) => QuestionRecord): QuestionRecord;
|
|
396
|
+
casUpdateLane(hqRoot: string, laneId: string, updater: (current: LaneRecord) => LaneRecord): LaneRecord;
|
|
385
397
|
effectiveLaneQuestionCompany(lane: Pick<LaneRecord, "company" | "mesh_company" | "project_id" | "lane_id">, hqRoot?: string): LaneCompanyRef | null;
|
|
386
398
|
emitLaneMeshEvent(ctx: Ctx, company: LaneCompanyRef | null | undefined, threadId: string | undefined, event: LaneMeshEvent, payload: Record<string, unknown>): Promise<void>;
|
|
387
399
|
groupQuestionLanesByCompany(hqRoot: string, lanes: LaneRecord[], fallbackCompany?: LaneCompanyRef): QuestionCompanyGroup[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { accountStatus, coveredLaneIds, loadLanesConfig, MAX_WATCHED_LANES, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, monitorReminderText, runLaneWatch, } from "../lib/lanes/index.js";
|
|
2
|
-
import { LEAD_LANE_KEEP_ALIVE_FLAG } from "../lib/lanes/keep-alive.js";
|
|
2
|
+
import { LEAD_LANE_KEEP_ALIVE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG } from "../lib/lanes/keep-alive.js";
|
|
3
3
|
import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
|
|
4
4
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
5
5
|
import { isLanesError, LanesError } from "../lib/lanes/errors.js";
|
|
@@ -50,6 +50,10 @@ export function createLaneWatchHandlers(deps) {
|
|
|
50
50
|
company: resolveLaneCompany(hqRoot, lane).company ?? undefined,
|
|
51
51
|
env: ctx.env,
|
|
52
52
|
}),
|
|
53
|
+
keepAliveNextCheckAtEnabled: (lane) => readLanesRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, {
|
|
54
|
+
company: resolveLaneCompany(hqRoot, lane).company ?? undefined,
|
|
55
|
+
env: ctx.env,
|
|
56
|
+
}),
|
|
53
57
|
readAccountStatuses: async (lanes) => {
|
|
54
58
|
if (lanes.length === 0)
|
|
55
59
|
return [];
|
package/dist/commands/lanes.js
CHANGED
|
@@ -55,11 +55,11 @@ import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, lan
|
|
|
55
55
|
import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
|
|
56
56
|
import { listLaneReviews } from "../lib/lanes/reviews.js";
|
|
57
57
|
import { effectiveAccountHome } from "../lib/lanes/account-home.js";
|
|
58
|
-
import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STATES, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listLaneIds, listQuestionIds, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseQuestionRecord, parseSeniorKind, readAllLanes, readLane, sanitizeHostComponent, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, dropboxDir, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, readWorkerRunResult, resumeCommandRefusal, withLaneHireLocks, raiseLaneQuestion, sha256Text, } from "../lib/lanes/index.js";
|
|
58
|
+
import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STATES, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listLaneIds, listQuestionIds, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseQuestionRecord, parseSeniorKind, readAllLanes, readLane, sanitizeHostComponent, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, DEAD_HOST_MS, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, dropboxDir, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, readWorkerRunResult, resumeCommandRefusal, withLaneHireLocks, raiseLaneQuestion, sha256Text, } from "../lib/lanes/index.js";
|
|
59
59
|
import { laneStories, storyWarning, } from "../lib/lanes/stories.js";
|
|
60
60
|
import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
|
|
61
61
|
import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
|
|
62
|
-
import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
|
|
62
|
+
import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
|
|
63
63
|
import { questionBodyAbsPath, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
|
|
64
64
|
import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
|
|
65
65
|
import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
|
|
@@ -1260,6 +1260,8 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
|
|
|
1260
1260
|
unverifiedRemote: new Map(),
|
|
1261
1261
|
missingOnOwner: new Map(),
|
|
1262
1262
|
};
|
|
1263
|
+
const now = ctx.now();
|
|
1264
|
+
const nowMs = now.getTime();
|
|
1263
1265
|
const host = reconcileHostFromCtx(ctx);
|
|
1264
1266
|
if (ctx.env.HQ_LANES_REMOTE_READ === "1" || parentLaneIds?.size === 0)
|
|
1265
1267
|
return result;
|
|
@@ -1282,6 +1284,9 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
|
|
|
1282
1284
|
((parentLaneIds === undefined || (lane.senior.kind === "lane" &&
|
|
1283
1285
|
lane.senior.id !== undefined &&
|
|
1284
1286
|
parentLaneIds.has(lane.senior.id))) || options.laneIds?.has(lane.lane_id) === true) &&
|
|
1287
|
+
!(isTerminalLaneState(lane.state) &&
|
|
1288
|
+
lane.terminal_reason === DISPATCH_TERMINAL_REASON &&
|
|
1289
|
+
lane.last_host_report_at === undefined) &&
|
|
1285
1290
|
(options.includeTerminalMirrors === true || !isTerminalLaneState(lane.state)))
|
|
1286
1291
|
.sort((a, b) => {
|
|
1287
1292
|
const terminalOrder = Number(isTerminalLaneState(a.state)) - Number(isTerminalLaneState(b.state));
|
|
@@ -1389,11 +1394,20 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
|
|
|
1389
1394
|
result.updated.set(mirror.lane_id, before);
|
|
1390
1395
|
continue;
|
|
1391
1396
|
}
|
|
1397
|
+
const ownerReportAtMs = Date.parse(owner.last_host_report_at ?? "");
|
|
1398
|
+
const ownerReportIsFresh = owner.state === "running" &&
|
|
1399
|
+
Number.isFinite(ownerReportAtMs) &&
|
|
1400
|
+
ownerReportAtMs <= nowMs + 60_000 &&
|
|
1401
|
+
nowMs - ownerReportAtMs < DEAD_HOST_MS;
|
|
1402
|
+
const mirroredReportAt = ownerReportIsFresh
|
|
1403
|
+
? new Date(Math.min(ownerReportAtMs, nowMs)).toISOString()
|
|
1404
|
+
: undefined;
|
|
1392
1405
|
const needsUpdate = before.state !== owner.state ||
|
|
1393
1406
|
before.queued_reason !== (owner.state === "queued" ? owner.queued_reason : undefined) ||
|
|
1394
1407
|
JSON.stringify(before.queued_details) !== JSON.stringify(owner.state === "queued" ? owner.queued_details : undefined) ||
|
|
1395
1408
|
before.ended_at !== endedAt ||
|
|
1396
1409
|
before.envelope_summary !== envelopeSummary ||
|
|
1410
|
+
(mirroredReportAt !== undefined && before.last_host_report_at !== mirroredReportAt) ||
|
|
1397
1411
|
(isTerminalLaneState(owner.state) && (JSON.stringify(before.last_envelope) !== JSON.stringify(owner.last_envelope) ||
|
|
1398
1412
|
before.story_complete !== owner.story_complete ||
|
|
1399
1413
|
before.story_complete_reason !== owner.story_complete_reason ||
|
|
@@ -1420,6 +1434,8 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
|
|
|
1420
1434
|
queued_details: owner.state === "queued" ? owner.queued_details : undefined,
|
|
1421
1435
|
ended_at: endedAt,
|
|
1422
1436
|
envelope_summary: envelopeSummary,
|
|
1437
|
+
// Preserve a fresh owner report; clamp small clock skew to the primary's time.
|
|
1438
|
+
...(mirroredReportAt !== undefined ? { last_host_report_at: mirroredReportAt } : {}),
|
|
1423
1439
|
...(isTerminalLaneState(owner.state) ? {
|
|
1424
1440
|
last_envelope: owner.last_envelope,
|
|
1425
1441
|
story_complete: owner.story_complete,
|
|
@@ -4261,10 +4277,29 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
4261
4277
|
remote.delivery_status === "queued_on_target_account" ||
|
|
4262
4278
|
remote.delivery_status === "queued_on_admission")) {
|
|
4263
4279
|
const ownerLane = dispatchedOwnerLane(ctx, lane.lane_id, outpostId, remote.lane, behavior);
|
|
4280
|
+
const questionCancellation = lane.state === "awaiting_input" && behavior.afterFallbackResume
|
|
4281
|
+
? await behavior.afterFallbackResume()
|
|
4282
|
+
: undefined;
|
|
4283
|
+
const ownerCancelledQuestions = Array.isArray(remote.cancelled_questions)
|
|
4284
|
+
? remote.cancelled_questions.filter((id) => typeof id === "string")
|
|
4285
|
+
: [];
|
|
4286
|
+
const ownerCancellationFailures = Array.isArray(remote.question_cancellation_failures)
|
|
4287
|
+
? remote.question_cancellation_failures
|
|
4288
|
+
: [];
|
|
4264
4289
|
printJson({
|
|
4265
4290
|
...remote,
|
|
4266
4291
|
lane_id: ownerLane.lane_id,
|
|
4267
4292
|
lane: ownerLane,
|
|
4293
|
+
...(questionCancellation === undefined ? {} : {
|
|
4294
|
+
cancelled_questions: [...new Set([
|
|
4295
|
+
...ownerCancelledQuestions,
|
|
4296
|
+
...questionCancellation.cancelled_question_ids,
|
|
4297
|
+
])],
|
|
4298
|
+
question_cancellation_failures: [
|
|
4299
|
+
...ownerCancellationFailures,
|
|
4300
|
+
...questionCancellation.failures,
|
|
4301
|
+
],
|
|
4302
|
+
}),
|
|
4268
4303
|
outpost_id: outpostId,
|
|
4269
4304
|
outpost_command_id: dispatched.commandId,
|
|
4270
4305
|
outpost_transport: "outposts_exec",
|
|
@@ -4281,10 +4316,29 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
4281
4316
|
descendants.push(syncLaneRecordFromOwner(ctx.hqRoot(), descendantId, outpostId, ownerDescendant, undefined, { allowNonTerminal: true }));
|
|
4282
4317
|
}
|
|
4283
4318
|
}
|
|
4319
|
+
const questionCancellation = action === "resume" && lane.state === "awaiting_input" && behavior.afterFallbackResume
|
|
4320
|
+
? await behavior.afterFallbackResume()
|
|
4321
|
+
: undefined;
|
|
4322
|
+
const ownerCancelledQuestions = Array.isArray(remote.cancelled_questions)
|
|
4323
|
+
? remote.cancelled_questions.filter((id) => typeof id === "string")
|
|
4324
|
+
: [];
|
|
4325
|
+
const ownerCancellationFailures = Array.isArray(remote.question_cancellation_failures)
|
|
4326
|
+
? remote.question_cancellation_failures
|
|
4327
|
+
: [];
|
|
4284
4328
|
printJson({
|
|
4285
4329
|
...remote,
|
|
4286
4330
|
lane_id: ownerLane.lane_id,
|
|
4287
4331
|
lane: ownerLane,
|
|
4332
|
+
...(questionCancellation === undefined ? {} : {
|
|
4333
|
+
cancelled_questions: [...new Set([
|
|
4334
|
+
...ownerCancelledQuestions,
|
|
4335
|
+
...questionCancellation.cancelled_question_ids,
|
|
4336
|
+
])],
|
|
4337
|
+
question_cancellation_failures: [
|
|
4338
|
+
...ownerCancellationFailures,
|
|
4339
|
+
...questionCancellation.failures,
|
|
4340
|
+
],
|
|
4341
|
+
}),
|
|
4288
4342
|
...(action === "reparent"
|
|
4289
4343
|
? { changed_descendant_ids: changedDescendantIds, descendants }
|
|
4290
4344
|
: {}),
|
|
@@ -5009,7 +5063,7 @@ async function refreshOutpostLaneCopiesForShow(ctx, lane, localLanes) {
|
|
|
5009
5063
|
const refreshedLane = refreshed[0] ?? null;
|
|
5010
5064
|
return refreshedLane ?? lane;
|
|
5011
5065
|
}
|
|
5012
|
-
async function runShow(ctx, laneId) {
|
|
5066
|
+
async function runShow(ctx, laneId, hasPendingQuestionForLane) {
|
|
5013
5067
|
const hqRoot = ctx.hqRoot();
|
|
5014
5068
|
const initialLane = readLane(hqRoot, laneId);
|
|
5015
5069
|
const initialLanes = loadAllLanes(hqRoot);
|
|
@@ -5023,10 +5077,24 @@ async function runShow(ctx, laneId) {
|
|
|
5023
5077
|
? { ...storedLane, state: "remote", remote_state: storedLane.state, queued_reason: undefined, queued_details: undefined }
|
|
5024
5078
|
: storedLane;
|
|
5025
5079
|
const askDeliveryFailed = readJsonFile(askDeliveryFailedPath(hqRoot, laneId));
|
|
5080
|
+
const delivery = storedLane.state === "awaiting_input" &&
|
|
5081
|
+
await hasPendingQuestionForLane(ctx, storedLane)
|
|
5082
|
+
? "questions"
|
|
5083
|
+
: laneDeliveryMechanism(storedLane);
|
|
5026
5084
|
const keepAliveState = resolveLaneKeepAlive(hqRoot, storedLane);
|
|
5027
5085
|
const keepAliveCompany = resolveLaneCompany(hqRoot, storedLane);
|
|
5086
|
+
const now = ctx.now().getTime();
|
|
5087
|
+
const nextCheckAtEnabled = storedLane.state === "done" && storedLane.keep_alive === true
|
|
5088
|
+
? await readLanesRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, {
|
|
5089
|
+
company: keepAliveCompany.company ?? undefined,
|
|
5090
|
+
env: ctx.env,
|
|
5091
|
+
})
|
|
5092
|
+
: false;
|
|
5093
|
+
const hasPendingWork = storedLane.keep_alive_standing === true || nextCheckAtEnabled
|
|
5094
|
+
? hasPendingKeepAliveWork(hqRoot, storedLane)
|
|
5095
|
+
: false;
|
|
5028
5096
|
const doneStateVerdict = storedLane.state === "done" && storedLane.keep_alive === true
|
|
5029
|
-
? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete }, storedLane.keep_alive_standing === true &&
|
|
5097
|
+
? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete, next_check_at: storedLane.last_envelope?.next_check_at }, storedLane.keep_alive_standing === true && hasPendingWork, { nextCheckAtEnabled, hasPendingWork, nowMs: now })
|
|
5030
5098
|
: null;
|
|
5031
5099
|
let managerStatusTarget;
|
|
5032
5100
|
let managerStatusGateError;
|
|
@@ -5048,7 +5116,15 @@ async function runShow(ctx, laneId) {
|
|
|
5048
5116
|
now: ctx.now().getTime(),
|
|
5049
5117
|
});
|
|
5050
5118
|
const laneIsSenior = allLanes.some((item) => item.senior.kind === "lane" && item.senior.id === laneId);
|
|
5051
|
-
const
|
|
5119
|
+
const requestedNextCheckMs = nextCheckAtEnabled && !hasPendingWork
|
|
5120
|
+
? keepAliveNextCheckAtMs(storedLane.last_envelope?.next_check_at, now, Date.parse(storedLane.last_envelope?.at ?? storedLane.updated_at))
|
|
5121
|
+
: undefined;
|
|
5122
|
+
const storedNextEligibleMs = Date.parse(storedLane.keep_alive_next_eligible_at ?? "");
|
|
5123
|
+
const nextEligibleAt = Number.isFinite(storedNextEligibleMs) && requestedNextCheckMs !== undefined
|
|
5124
|
+
? new Date(Math.max(storedNextEligibleMs, requestedNextCheckMs)).toISOString()
|
|
5125
|
+
: requestedNextCheckMs !== undefined
|
|
5126
|
+
? new Date(requestedNextCheckMs).toISOString()
|
|
5127
|
+
: storedLane.keep_alive_next_eligible_at ?? null;
|
|
5052
5128
|
const reviewListing = laneIsSenior
|
|
5053
5129
|
? listLaneReviews(hqRoot, laneId)
|
|
5054
5130
|
: { reviews: [], skipped: 0 };
|
|
@@ -5075,7 +5151,7 @@ async function runShow(ctx, laneId) {
|
|
|
5075
5151
|
stalled_at: lane.stalled_at ?? null,
|
|
5076
5152
|
stalled_reason: lane.stalled_reason ?? null,
|
|
5077
5153
|
ask_delivery_failed: askDeliveryFailed,
|
|
5078
|
-
delivery
|
|
5154
|
+
delivery,
|
|
5079
5155
|
provider_session_id: lane.provider_session_id ?? null,
|
|
5080
5156
|
provider_session_id_absent: lane.provider_session_id_absent ?? null,
|
|
5081
5157
|
envelope_repair_mode: lane.envelope_repair?.mode ?? null,
|
|
@@ -5108,7 +5184,7 @@ async function runShow(ctx, laneId) {
|
|
|
5108
5184
|
consecutive_auto_resumes: lane.keep_alive_resume_count ?? 0,
|
|
5109
5185
|
last_auto_resume_at: lane.keep_alive_last_resume_at ?? null,
|
|
5110
5186
|
keep_alive_history: lane.keep_alive_history ?? [],
|
|
5111
|
-
next_eligible_at:
|
|
5187
|
+
next_eligible_at: nextEligibleAt,
|
|
5112
5188
|
exhausted_at: lane.keep_alive_exhausted_at ?? null,
|
|
5113
5189
|
},
|
|
5114
5190
|
manager_status_gate: managerStatusGateError !== undefined
|
|
@@ -5134,6 +5210,7 @@ async function runShow(ctx, laneId) {
|
|
|
5134
5210
|
escalated: blockerFreshness.stale.length > 0 &&
|
|
5135
5211
|
storedLane.blocker_freshness_state?.escalated_fingerprint ===
|
|
5136
5212
|
blockerFreshnessFingerprint(blockerFreshness.stale),
|
|
5213
|
+
undeliverable_reason: storedLane.blocker_freshness_state?.undeliverable_reason ?? null,
|
|
5137
5214
|
}
|
|
5138
5215
|
: null,
|
|
5139
5216
|
kr_freshness: krFreshness,
|
|
@@ -7807,6 +7884,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7807
7884
|
assertQuestionAnswerAuthorized,
|
|
7808
7885
|
assertQuestionCompanyMatchesLane,
|
|
7809
7886
|
casUpdateQuestion,
|
|
7887
|
+
casUpdateLane,
|
|
7810
7888
|
effectiveLaneQuestionCompany,
|
|
7811
7889
|
emitLaneMeshEvent,
|
|
7812
7890
|
groupQuestionLanesByCompany,
|
|
@@ -7857,6 +7935,8 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7857
7935
|
recordResumeSetup,
|
|
7858
7936
|
resolveCommandLane: (commandCtx, laneId) => resolveCommandLaneTarget(commandCtx, laneId),
|
|
7859
7937
|
resolveActiveQuestionCompany,
|
|
7938
|
+
hasPendingQuestionForLane: questionHandlers.hasPendingQuestionForLane,
|
|
7939
|
+
cancelFallbackQuestionsForLane: questionHandlers.cancelFallbackQuestionsForLane,
|
|
7860
7940
|
resolveResumeProfile,
|
|
7861
7941
|
selectAutomaticClaudeResumeAccount,
|
|
7862
7942
|
validateResumeProfileModel,
|
|
@@ -7882,6 +7962,8 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7882
7962
|
printJson,
|
|
7883
7963
|
recordResumeSetup,
|
|
7884
7964
|
resolveCommandLane: (commandCtx, laneId) => resolveCommandLaneTarget(commandCtx, laneId),
|
|
7965
|
+
hasPendingQuestionForLane: questionHandlers.hasPendingQuestionForLane,
|
|
7966
|
+
cancelFallbackQuestionsForLane: questionHandlers.cancelFallbackQuestionsForLane,
|
|
7885
7967
|
resolveResumeProfile,
|
|
7886
7968
|
selectAutomaticClaudeResumeAccount,
|
|
7887
7969
|
validateResumeProfileModel,
|
|
@@ -8153,7 +8235,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8153
8235
|
.description("Show one lane")
|
|
8154
8236
|
.argument("<lane-id>", "Lane id")
|
|
8155
8237
|
.action(wrap(async (laneId) => {
|
|
8156
|
-
await runShow(ctx, laneId);
|
|
8238
|
+
await runShow(ctx, laneId, questionHandlers.hasPendingQuestionForLane);
|
|
8157
8239
|
}));
|
|
8158
8240
|
const stories = lanes
|
|
8159
8241
|
.command("story")
|
|
@@ -8290,7 +8372,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8290
8372
|
}));
|
|
8291
8373
|
lanes
|
|
8292
8374
|
.command("resume")
|
|
8293
|
-
.description("Resume an interrupted, done or blocked lane
|
|
8375
|
+
.description("Resume an interrupted, done or blocked lane, or an awaiting_input lane with no answerable question; recover failed lanes with the senior-only --from-failed option")
|
|
8294
8376
|
.argument("<lane-id>", "Lane id")
|
|
8295
8377
|
.option("--text <msg>", "Operator message for the resumed turn")
|
|
8296
8378
|
.option("--from-failed", "Explicitly resume a failed lane as its senior")
|
package/dist/commands/monitor.js
CHANGED
|
@@ -278,6 +278,7 @@ async function startMonitor(options, argv = []) {
|
|
|
278
278
|
cwd: meta.cwd,
|
|
279
279
|
env: { ...env, HQ_ROOT: hqRoot },
|
|
280
280
|
detached: true,
|
|
281
|
+
windowsHide: true,
|
|
281
282
|
stdio: "ignore",
|
|
282
283
|
});
|
|
283
284
|
child.once("error", (err) => {
|
|
@@ -347,7 +348,21 @@ async function showMonitor(id, json) {
|
|
|
347
348
|
const state = stateForDisplay(hqRoot, id, readJson(statePath(hqRoot, id)));
|
|
348
349
|
if (!meta || !state)
|
|
349
350
|
throw new LanesError("monitor_not_found", `Monitor ${id} was not found.`, { monitor_id: id });
|
|
350
|
-
const events =
|
|
351
|
+
const events = [];
|
|
352
|
+
for (const line of fs.readFileSync(path.join(dir, "events.jsonl"), "utf8").split("\n")) {
|
|
353
|
+
if (!line.trim())
|
|
354
|
+
continue;
|
|
355
|
+
let parsed;
|
|
356
|
+
try {
|
|
357
|
+
parsed = JSON.parse(line);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
363
|
+
continue;
|
|
364
|
+
events.push(parsed);
|
|
365
|
+
}
|
|
351
366
|
if (json)
|
|
352
367
|
printJson({ meta, state, events });
|
|
353
368
|
else
|
|
@@ -712,7 +727,7 @@ export async function runSupervisor(id, deps = {}) {
|
|
|
712
727
|
const args = meta.command !== null ? childArgs : childArgs.slice(1);
|
|
713
728
|
if (!executable)
|
|
714
729
|
throw new LanesError("monitor_command_required", "No command was recorded for this monitor.");
|
|
715
|
-
commandChild = (deps.spawn ?? spawn)(executable, args, { cwd: meta.cwd, env: process.env, detached: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
730
|
+
commandChild = (deps.spawn ?? spawn)(executable, args, { cwd: meta.cwd, env: process.env, detached: true, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
716
731
|
if (commandChild.pid) {
|
|
717
732
|
state = { ...state, child_pid: commandChild.pid, child_starttime: readProcStarttime(commandChild.pid) };
|
|
718
733
|
writeJsonAtomic(statePath(hqRoot, id), state);
|
package/dist/commands/report.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as path from "node:path";
|
|
|
6
6
|
import { ensureCognitoToken, } from "../utils/cognito-session.js";
|
|
7
7
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
8
8
|
import { DEPLOY_API_URL, deployApiRequest, deployHeaders } from "../lib/deploy-api.js";
|
|
9
|
+
import { listDeployApps } from "../lib/deploy-apps.js";
|
|
9
10
|
import { listReportTemplates } from "../lib/report-templates.js";
|
|
10
11
|
import { adoptReport, buildReportTarball, conventionReportDir, deployOrgContext, ReportPublishError, plainFailureReason, publishReport, } from "../lib/report-publish.js";
|
|
11
12
|
const REPORT_CHANNEL_STATE_FILE = "report-channels.json";
|
|
@@ -259,8 +260,10 @@ async function orgFor(token, report) {
|
|
|
259
260
|
return deployOrgContext(report, slug);
|
|
260
261
|
}
|
|
261
262
|
export async function findOrCreateApp(token, slug, org) {
|
|
262
|
-
const
|
|
263
|
-
|
|
263
|
+
const apps = await listDeployApps((cursor) => {
|
|
264
|
+
const route = cursor ? `/api/apps?cursor=${encodeURIComponent(cursor)}` : "/api/apps";
|
|
265
|
+
return deployJson(token, route, "GET", undefined, org.headers);
|
|
266
|
+
});
|
|
264
267
|
const mine = (name) => apps.find((app) => !!app && typeof app === "object" && (app.subdomain === name || app.name === name));
|
|
265
268
|
let app;
|
|
266
269
|
for (const name of candidateSlugs(slug)) {
|
|
@@ -45,5 +45,9 @@ export declare function shouldAttemptSelfUpdate(opts: {
|
|
|
45
45
|
check?: boolean;
|
|
46
46
|
selfUpdate?: boolean;
|
|
47
47
|
}): boolean;
|
|
48
|
+
/** Latest release tag for a repo, e.g. `v12.3.0`. Throws on failure. */
|
|
49
|
+
export declare function getLatestReleaseTag(repo: string, token?: string): Promise<string>;
|
|
50
|
+
/** Resolve a tag to its commit SHA, dereferencing annotated tags. */
|
|
51
|
+
export declare function resolveTagSha(repo: string, tag: string, token?: string): Promise<string>;
|
|
48
52
|
export declare function registerRescueCommand(program: Command): void;
|
|
49
53
|
//# sourceMappingURL=rescue.d.ts.map
|