@indigoai-us/hq-cli 5.298.0 → 5.300.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 +11 -0
- package/dist/command-catalog.generated.d.ts +27 -4
- package/dist/command-catalog.generated.js +34 -4
- package/dist/commands/cloud.js +12 -7
- package/dist/commands/daemon.js +1 -0
- package/dist/commands/files.d.ts +1 -0
- package/dist/commands/files.js +59 -0
- package/dist/commands/lanes-accounts.d.ts +3 -0
- package/dist/commands/lanes-accounts.js +25 -1
- package/dist/commands/lanes-message.js +68 -35
- package/dist/commands/lanes-resume.js +247 -210
- package/dist/commands/lanes-shared.d.ts +3 -1
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +16 -4
- package/dist/lib/daemon/legacy.d.ts +5 -2
- package/dist/lib/daemon/legacy.js +6 -4
- package/dist/lib/daemon/migrate.d.ts +10 -1
- package/dist/lib/daemon/migrate.js +22 -7
- package/dist/lib/daemon/run.js +19 -7
- package/dist/lib/daemon/supervisor.js +9 -1
- package/dist/lib/lanes/quota.d.ts +19 -0
- package/dist/lib/lanes/quota.js +106 -29
- package/dist/lib/lanes/reconcile.js +34 -5
- package/dist/lib/lanes/store.d.ts +2 -0
- package/dist/lib/lanes/store.js +4 -0
- package/dist/lib/lanes/story-switch.d.ts +21 -0
- package/dist/lib/lanes/story-switch.js +75 -0
- package/dist/lib/lanes/types.d.ts +25 -0
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/lanes/watcher.js +25 -5
- package/dist/lib/mesh/live/daemon/install.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/install.js +13 -2
- package/dist/utils/self-update.js +249 -2
- package/dist/utils/staged-install.js +71 -21
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import { parseCliEffort } from "../lib/lanes/effort.js";
|
|
|
5
5
|
import { parseOutpostHireGrantContext } from "../lib/lanes/hire-grant-dispatch.js";
|
|
6
6
|
import { assertNoSecretText } from "../lib/lanes/text-scan.js";
|
|
7
7
|
import { laneStories, laneStory, storyWarning } from "../lib/lanes/stories.js";
|
|
8
|
+
import { artifactIdentityKeys, rollbackStorySwitchAttribution, storySwitchNotice } from "../lib/lanes/story-switch.js";
|
|
8
9
|
import { spawnLaneWorker } from "../lib/lanes/spawn.js";
|
|
9
10
|
import { accountMoveSummary, accountRequestKeepsCurrentLane, assertCurrentResumeAccountAvailable, hasKeepAliveProgress, recordedResumeEffort, resetKeepAliveProgress, resolveResumeEffort } from "./lanes-shared.js";
|
|
10
11
|
export function createLaneResumeHandlers(deps) {
|
|
@@ -195,6 +196,8 @@ export function createLaneResumeHandlers(deps) {
|
|
|
195
196
|
}
|
|
196
197
|
const attributionChanged = projectId !== lane.project_id || storyId !== lane.story_id;
|
|
197
198
|
const messageId = text !== undefined || attributionChanged ? mintMessageId() : undefined;
|
|
199
|
+
const notice = storySwitchNotice(lane.project_id, lane.story_id, projectId, storyId);
|
|
200
|
+
const deliveryText = notice === undefined ? text : `${notice}${text === undefined ? "" : `\n\n${text}`}`;
|
|
198
201
|
let requestedMeshCompany;
|
|
199
202
|
let meshThreadId;
|
|
200
203
|
if (opts.meshCompany !== undefined) {
|
|
@@ -253,41 +256,60 @@ export function createLaneResumeHandlers(deps) {
|
|
|
253
256
|
account: keepCurrentAccount ? lane.account : opts.account ?? automaticAccountSelection?.selection.account,
|
|
254
257
|
model: opts.model,
|
|
255
258
|
});
|
|
256
|
-
let
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
...(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
259
|
+
let storySwitchRollback;
|
|
260
|
+
let storySwitchWorkerStarted = false;
|
|
261
|
+
if (messageId !== undefined && attributionChanged) {
|
|
262
|
+
lane = casUpdateLane(hqRoot, laneId, (current) => {
|
|
263
|
+
const already = current.attribution_history.some((entry) => entry.message_id === messageId);
|
|
264
|
+
if (already)
|
|
265
|
+
return current;
|
|
266
|
+
const existingStory = laneStory(current, projectId, storyId);
|
|
267
|
+
storySwitchRollback = {
|
|
268
|
+
message_id: messageId,
|
|
269
|
+
project_id: current.project_id,
|
|
270
|
+
story_id: current.story_id,
|
|
271
|
+
no_story_reason: current.no_story_reason,
|
|
272
|
+
...(!existingStory ? {
|
|
273
|
+
added_story: { project_id: projectId, story_id: storyId, status: "in_progress", added_at: at, updated_at: at },
|
|
274
|
+
} : {}),
|
|
275
|
+
story_switch_unacknowledged: current.story_switch_unacknowledged,
|
|
276
|
+
};
|
|
277
|
+
return {
|
|
278
|
+
...current,
|
|
279
|
+
project_id: projectId,
|
|
280
|
+
story_id: storyId,
|
|
281
|
+
no_story_reason: storyId ? undefined : current.no_story_reason,
|
|
282
|
+
stories: laneStory(current, projectId, storyId)
|
|
283
|
+
? laneStories(current)
|
|
284
|
+
: [
|
|
285
|
+
...laneStories(current),
|
|
286
|
+
{ project_id: projectId, story_id: storyId, status: "in_progress", added_at: at, updated_at: at },
|
|
287
|
+
],
|
|
288
|
+
attribution_history: appendCappedHistory(current.attribution_history, {
|
|
289
|
+
project_id: projectId,
|
|
290
|
+
story_id: storyId,
|
|
291
|
+
message_id: messageId,
|
|
292
|
+
at,
|
|
293
|
+
}),
|
|
294
|
+
story_switch_pending: {
|
|
295
|
+
previous_project_id: current.project_id,
|
|
296
|
+
previous_story_id: current.story_id,
|
|
297
|
+
project_id: projectId,
|
|
298
|
+
story_id: storyId,
|
|
299
|
+
message_id: messageId,
|
|
300
|
+
at,
|
|
301
|
+
previous_artifact_keys: artifactIdentityKeys(current.last_envelope?.artifacts),
|
|
302
|
+
},
|
|
303
|
+
story_switch_unacknowledged: undefined,
|
|
304
|
+
};
|
|
276
305
|
});
|
|
277
|
-
lane = move.lane;
|
|
278
306
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (
|
|
307
|
+
try {
|
|
308
|
+
let move = null;
|
|
309
|
+
if (opts.account !== undefined && !keepCurrentAccount) {
|
|
282
310
|
move = await prepareAccountMove(ctx, lane, {
|
|
283
|
-
account:
|
|
284
|
-
cliModel: undefined,
|
|
285
|
-
...(automaticAccountSelection.historyReason !== undefined
|
|
286
|
-
? { accountHistoryReason: automaticAccountSelection.historyReason }
|
|
287
|
-
: {}),
|
|
288
|
-
...(automaticAccountSelection.preservedModel !== undefined
|
|
289
|
-
? { preservedModel: automaticAccountSelection.preservedModel }
|
|
290
|
-
: {}),
|
|
311
|
+
account: opts.account,
|
|
312
|
+
cliModel: opts.model?.trim() || undefined,
|
|
291
313
|
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
292
314
|
...(profileRepin !== undefined ? { profileRepin } : {}),
|
|
293
315
|
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
@@ -299,204 +321,219 @@ export function createLaneResumeHandlers(deps) {
|
|
|
299
321
|
...(failedResumeExpectedSenior !== undefined
|
|
300
322
|
? { expected_senior: failedResumeExpectedSenior }
|
|
301
323
|
: {}),
|
|
302
|
-
...(
|
|
324
|
+
...(deliveryText !== undefined ? { text: deliveryText } : {}),
|
|
303
325
|
...(messageId !== undefined ? { message_id: messageId } : {}),
|
|
304
326
|
compact: opts.compact !== false,
|
|
305
327
|
},
|
|
306
|
-
accountSelection: selection,
|
|
307
328
|
});
|
|
308
329
|
lane = move.lane;
|
|
309
330
|
}
|
|
310
|
-
else {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
resumed = await withLaneAdmissionLock(hqRoot, lane.provider, async () => {
|
|
331
|
-
const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
|
|
332
|
-
if (admission.action === "refuse" && admission.error)
|
|
333
|
-
throw admission.error;
|
|
334
|
-
return withResumeHireAdmission(ctx, lane, () => {
|
|
335
|
-
if (admission.action === "queue") {
|
|
336
|
-
const sessionId = lane.provider_session_id;
|
|
337
|
-
if (!sessionId) {
|
|
338
|
-
throw new LanesError("no_provider_session", `Lane ${laneId} has no provider session id to queue this resume`, {
|
|
339
|
-
lane_id: laneId,
|
|
340
|
-
provider: lane.provider,
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
queuedOnAdmission = true;
|
|
344
|
-
return casUpdateLane(hqRoot, laneId, (current) => {
|
|
345
|
-
if (current.version !== lane.version || current.state !== lane.state) {
|
|
346
|
-
throw new LanesError("lane_admission_conflict", `Lane ${laneId} changed while resume admission was checked`, {
|
|
347
|
-
lane_id: laneId,
|
|
348
|
-
prepared_version: lane.version,
|
|
349
|
-
version: current.version,
|
|
350
|
-
state: current.state,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
return {
|
|
354
|
-
...current,
|
|
355
|
-
state: "queued",
|
|
356
|
-
queued_reason: admission.reason ?? "throttle",
|
|
357
|
-
queued_details: undefined,
|
|
358
|
-
queued_at: at,
|
|
359
|
-
pending_resume: {
|
|
360
|
-
session_id: sessionId,
|
|
361
|
-
trigger: "resume",
|
|
362
|
-
caller: ctx.env.HQ_LANE_ID?.trim() || "user",
|
|
363
|
-
resumed_from: resumedFrom,
|
|
364
|
-
...(opts.fromFailed === true ? { from_failed: true } : {}),
|
|
365
|
-
...(failedResumeExpectedSenior !== undefined
|
|
366
|
-
? { expected_senior: failedResumeExpectedSenior }
|
|
367
|
-
: {}),
|
|
368
|
-
...(text !== undefined ? { text } : {}),
|
|
369
|
-
...(messageId !== undefined ? { message_id: messageId } : {}),
|
|
370
|
-
compact: opts.compact !== false,
|
|
371
|
-
at,
|
|
372
|
-
},
|
|
373
|
-
};
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
return spawnLaneWorker(hqRoot, lane, {
|
|
377
|
-
env: ctx.env,
|
|
378
|
-
modelDeps: {
|
|
379
|
-
readFileSync: ctx.readFileSync,
|
|
380
|
-
probe: ctx.probeProvider,
|
|
381
|
-
env: ctx.env,
|
|
382
|
-
},
|
|
383
|
-
existsSync: ctx.existsSync,
|
|
384
|
-
spawnProcess: ctx.spawnProcess,
|
|
385
|
-
proveDetached: ctx.proveDetached,
|
|
386
|
-
killProcess: ctx.killProcess,
|
|
387
|
-
now: ctx.now,
|
|
388
|
-
resolveMachineIdentity: ctx.resolveMachineIdentity,
|
|
389
|
-
runCompaction: ctx.runCompaction,
|
|
390
|
-
locateTranscript: ctx.locateTranscript,
|
|
391
|
-
}, {
|
|
392
|
-
trigger: {
|
|
393
|
-
kind: "resume",
|
|
394
|
-
from: resumedFrom,
|
|
395
|
-
...(opts.fromFailed === true ? { fromFailed: true } : {}),
|
|
331
|
+
else if (automaticAccountSelection !== undefined) {
|
|
332
|
+
const selection = automaticAccountSelection.selection;
|
|
333
|
+
if (selection.account !== lane.account) {
|
|
334
|
+
move = await prepareAccountMove(ctx, lane, {
|
|
335
|
+
account: selection.account,
|
|
336
|
+
cliModel: undefined,
|
|
337
|
+
...(automaticAccountSelection.historyReason !== undefined
|
|
338
|
+
? { accountHistoryReason: automaticAccountSelection.historyReason }
|
|
339
|
+
: {}),
|
|
340
|
+
...(automaticAccountSelection.preservedModel !== undefined
|
|
341
|
+
? { preservedModel: automaticAccountSelection.preservedModel }
|
|
342
|
+
: {}),
|
|
343
|
+
...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
|
|
344
|
+
...(profileRepin !== undefined ? { profileRepin } : {}),
|
|
345
|
+
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
346
|
+
...(hireGrantContext !== undefined ? { grantContext: hireGrantContext } : {}),
|
|
347
|
+
pending: {
|
|
348
|
+
trigger: "resume",
|
|
349
|
+
resumed_from: resumedFrom,
|
|
350
|
+
...(opts.fromFailed === true ? { from_failed: true } : {}),
|
|
396
351
|
...(failedResumeExpectedSenior !== undefined
|
|
397
|
-
? {
|
|
352
|
+
? { expected_senior: failedResumeExpectedSenior }
|
|
398
353
|
: {}),
|
|
399
|
-
...(
|
|
354
|
+
...(deliveryText !== undefined ? { text: deliveryText } : {}),
|
|
400
355
|
...(messageId !== undefined ? { message_id: messageId } : {}),
|
|
356
|
+
compact: opts.compact !== false,
|
|
401
357
|
},
|
|
402
|
-
|
|
403
|
-
effort: resumeEffort ?? recordedResumeEffort(lane),
|
|
358
|
+
accountSelection: selection,
|
|
404
359
|
});
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
360
|
+
lane = move.lane;
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
lane = persistClaudeAccountSelection(hqRoot, lane, selection);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
lane = recordResumeSetup(ctx, lane, {
|
|
367
|
+
...(move === null && resumeProfile !== undefined ? { profile: resumeProfile } : {}),
|
|
368
|
+
...(move === null && profileRepin !== undefined ? { profileRepin } : {}),
|
|
369
|
+
...(move === null && resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
370
|
+
...(keepAlive !== undefined ? { keepAlive } : {}),
|
|
371
|
+
...(move === null && keepCurrentAccount && opts.model !== undefined ? { cliModel: opts.model } : {}),
|
|
372
|
+
...(requestedMeshCompany !== undefined
|
|
373
|
+
? { meshCompany: requestedMeshCompany, threadId: meshThreadId }
|
|
374
|
+
: {}),
|
|
418
375
|
});
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
376
|
+
let queuedOnAdmission = false;
|
|
377
|
+
let resumed;
|
|
378
|
+
if (move?.queued) {
|
|
379
|
+
resumed = lane;
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
resumed = await withLaneAdmissionLock(hqRoot, lane.provider, async () => {
|
|
383
|
+
const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
|
|
384
|
+
if (admission.action === "refuse" && admission.error)
|
|
385
|
+
throw admission.error;
|
|
386
|
+
return withResumeHireAdmission(ctx, lane, () => {
|
|
387
|
+
if (admission.action === "queue") {
|
|
388
|
+
const sessionId = lane.provider_session_id;
|
|
389
|
+
if (!sessionId) {
|
|
390
|
+
throw new LanesError("no_provider_session", `Lane ${laneId} has no provider session id to queue this resume`, {
|
|
391
|
+
lane_id: laneId,
|
|
392
|
+
provider: lane.provider,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
queuedOnAdmission = true;
|
|
396
|
+
return casUpdateLane(hqRoot, laneId, (current) => {
|
|
397
|
+
if (current.version !== lane.version || current.state !== lane.state) {
|
|
398
|
+
throw new LanesError("lane_admission_conflict", `Lane ${laneId} changed while resume admission was checked`, {
|
|
399
|
+
lane_id: laneId,
|
|
400
|
+
prepared_version: lane.version,
|
|
401
|
+
version: current.version,
|
|
402
|
+
state: current.state,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
...current,
|
|
407
|
+
state: "queued",
|
|
408
|
+
queued_reason: admission.reason ?? "throttle",
|
|
409
|
+
queued_details: undefined,
|
|
410
|
+
queued_at: at,
|
|
411
|
+
pending_resume: {
|
|
412
|
+
session_id: sessionId,
|
|
413
|
+
trigger: "resume",
|
|
414
|
+
caller: ctx.env.HQ_LANE_ID?.trim() || "user",
|
|
415
|
+
resumed_from: resumedFrom,
|
|
416
|
+
...(opts.fromFailed === true ? { from_failed: true } : {}),
|
|
417
|
+
...(failedResumeExpectedSenior !== undefined
|
|
418
|
+
? { expected_senior: failedResumeExpectedSenior }
|
|
419
|
+
: {}),
|
|
420
|
+
...(deliveryText !== undefined ? { text: deliveryText } : {}),
|
|
421
|
+
...(messageId !== undefined ? { message_id: messageId } : {}),
|
|
422
|
+
compact: opts.compact !== false,
|
|
423
|
+
at,
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const started = spawnLaneWorker(hqRoot, lane, {
|
|
429
|
+
env: ctx.env,
|
|
430
|
+
modelDeps: {
|
|
431
|
+
readFileSync: ctx.readFileSync,
|
|
432
|
+
probe: ctx.probeProvider,
|
|
433
|
+
env: ctx.env,
|
|
434
|
+
},
|
|
435
|
+
existsSync: ctx.existsSync,
|
|
436
|
+
spawnProcess: ctx.spawnProcess,
|
|
437
|
+
proveDetached: ctx.proveDetached,
|
|
438
|
+
killProcess: ctx.killProcess,
|
|
439
|
+
now: ctx.now,
|
|
440
|
+
resolveMachineIdentity: ctx.resolveMachineIdentity,
|
|
441
|
+
runCompaction: ctx.runCompaction,
|
|
442
|
+
locateTranscript: ctx.locateTranscript,
|
|
443
|
+
}, {
|
|
444
|
+
trigger: {
|
|
445
|
+
kind: "resume",
|
|
446
|
+
from: resumedFrom,
|
|
447
|
+
...(opts.fromFailed === true ? { fromFailed: true } : {}),
|
|
448
|
+
...(failedResumeExpectedSenior !== undefined
|
|
449
|
+
? { expectedSenior: failedResumeExpectedSenior }
|
|
450
|
+
: {}),
|
|
451
|
+
...(deliveryText !== undefined ? { text: deliveryText } : {}),
|
|
452
|
+
...(messageId !== undefined ? { message_id: messageId } : {}),
|
|
453
|
+
},
|
|
454
|
+
compact: opts.compact !== false,
|
|
455
|
+
effort: resumeEffort ?? recordedResumeEffort(lane),
|
|
456
|
+
});
|
|
457
|
+
storySwitchWorkerStarted = true;
|
|
458
|
+
deps.onLaneWorkerStarted?.(laneId, "resume");
|
|
459
|
+
return started;
|
|
460
|
+
}, hireGrantContext);
|
|
461
|
+
}, ctx.sleep);
|
|
462
|
+
}
|
|
463
|
+
if (deliveryText !== undefined && messageId !== undefined) {
|
|
464
|
+
// Same audit copy a message resume leaves; nothing drains it. Archive only
|
|
465
|
+
// after a successful spawn so a concurrent resume cannot leave a phantom.
|
|
466
|
+
archiveResumeMessage(hqRoot, laneId, {
|
|
427
467
|
at,
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
failure: recoveredFailure,
|
|
431
|
-
}),
|
|
432
|
-
}));
|
|
433
|
-
}
|
|
434
|
-
if (text !== undefined) {
|
|
435
|
-
// An operator text moves the compaction reference forward, as a message does.
|
|
436
|
-
next = casUpdateLane(hqRoot, laneId, (current) => ({
|
|
437
|
-
...resetKeepAliveProgress(current),
|
|
438
|
-
last_message_at: at,
|
|
439
|
-
}));
|
|
440
|
-
}
|
|
441
|
-
if (messageId !== undefined && attributionChanged) {
|
|
442
|
-
next = casUpdateLane(hqRoot, laneId, (current) => {
|
|
443
|
-
const already = current.attribution_history.some((entry) => entry.message_id === messageId);
|
|
444
|
-
if (already)
|
|
445
|
-
return current;
|
|
446
|
-
return {
|
|
447
|
-
...current,
|
|
468
|
+
message_id: messageId,
|
|
469
|
+
text: deliveryText,
|
|
448
470
|
project_id: projectId,
|
|
449
471
|
story_id: storyId,
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
updated_at: at,
|
|
461
|
-
},
|
|
462
|
-
],
|
|
463
|
-
attribution_history: appendCappedHistory(current.attribution_history, {
|
|
464
|
-
project_id: projectId,
|
|
465
|
-
story_id: storyId,
|
|
466
|
-
message_id: messageId,
|
|
472
|
+
resumed_from: resumedFrom,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
let next = resumed;
|
|
476
|
+
if (failedResumeActor !== undefined &&
|
|
477
|
+
failedResumeReason !== undefined &&
|
|
478
|
+
recoveredFailure !== undefined) {
|
|
479
|
+
next = casUpdateLane(hqRoot, laneId, (current) => ({
|
|
480
|
+
...current,
|
|
481
|
+
failed_resume_history: appendCappedHistory(current.failed_resume_history ?? [], {
|
|
467
482
|
at,
|
|
483
|
+
actor: failedResumeActor,
|
|
484
|
+
reason: failedResumeReason,
|
|
485
|
+
failure: recoveredFailure,
|
|
468
486
|
}),
|
|
469
|
-
};
|
|
487
|
+
}));
|
|
488
|
+
}
|
|
489
|
+
if (deliveryText !== undefined) {
|
|
490
|
+
// An operator text moves the compaction reference forward, as a message does.
|
|
491
|
+
next = casUpdateLane(hqRoot, laneId, (current) => ({
|
|
492
|
+
...resetKeepAliveProgress(current),
|
|
493
|
+
last_message_at: at,
|
|
494
|
+
}));
|
|
495
|
+
}
|
|
496
|
+
if (text === undefined && hasKeepAliveProgress(next)) {
|
|
497
|
+
next = casUpdateLane(hqRoot, laneId, (current) => resetKeepAliveProgress(current));
|
|
498
|
+
}
|
|
499
|
+
printJson({
|
|
500
|
+
ok: true,
|
|
501
|
+
action: "resume",
|
|
502
|
+
lane_id: laneId,
|
|
503
|
+
resumed_from: resumedFrom,
|
|
504
|
+
message_id: messageId ?? null,
|
|
505
|
+
text: text ?? null,
|
|
506
|
+
delivery: "resume",
|
|
507
|
+
delivery_status: move?.queued
|
|
508
|
+
? "queued_on_target_account"
|
|
509
|
+
: queuedOnAdmission
|
|
510
|
+
? "queued_on_admission"
|
|
511
|
+
: "worker_spawned",
|
|
512
|
+
account_move: move ? accountMoveSummary(move) : null,
|
|
513
|
+
resumed_session_id: resumed.last_resume?.session_id ?? null,
|
|
514
|
+
last_resume: next.last_resume ?? null,
|
|
515
|
+
compaction: resumed.last_compaction && resumed.last_compaction.resume_at === resumed.last_resume?.at
|
|
516
|
+
? resumed.last_compaction
|
|
517
|
+
: null,
|
|
518
|
+
project_id: next.project_id,
|
|
519
|
+
story_id: next.story_id,
|
|
520
|
+
stories: laneStories(next),
|
|
521
|
+
story_warning: storyWarning(next) ?? null,
|
|
522
|
+
lane: next,
|
|
470
523
|
});
|
|
471
524
|
}
|
|
472
|
-
|
|
473
|
-
|
|
525
|
+
catch (err) {
|
|
526
|
+
if (storySwitchRollback !== undefined && !storySwitchWorkerStarted) {
|
|
527
|
+
try {
|
|
528
|
+
casUpdateLane(hqRoot, laneId, (current) => rollbackStorySwitchAttribution(current, storySwitchRollback));
|
|
529
|
+
}
|
|
530
|
+
catch (rollbackErr) {
|
|
531
|
+
const rollbackMessage = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
|
|
532
|
+
process.stderr.write(`Could not roll back story attribution after resume failure: ${rollbackMessage}\n`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
throw err;
|
|
474
536
|
}
|
|
475
|
-
printJson({
|
|
476
|
-
ok: true,
|
|
477
|
-
action: "resume",
|
|
478
|
-
lane_id: laneId,
|
|
479
|
-
resumed_from: resumedFrom,
|
|
480
|
-
message_id: messageId ?? null,
|
|
481
|
-
text: text ?? null,
|
|
482
|
-
delivery: "resume",
|
|
483
|
-
delivery_status: move?.queued
|
|
484
|
-
? "queued_on_target_account"
|
|
485
|
-
: queuedOnAdmission
|
|
486
|
-
? "queued_on_admission"
|
|
487
|
-
: "worker_spawned",
|
|
488
|
-
account_move: move ? accountMoveSummary(move) : null,
|
|
489
|
-
resumed_session_id: resumed.last_resume?.session_id ?? null,
|
|
490
|
-
last_resume: next.last_resume ?? null,
|
|
491
|
-
compaction: resumed.last_compaction && resumed.last_compaction.resume_at === resumed.last_resume?.at
|
|
492
|
-
? resumed.last_compaction
|
|
493
|
-
: null,
|
|
494
|
-
project_id: next.project_id,
|
|
495
|
-
story_id: next.story_id,
|
|
496
|
-
stories: laneStories(next),
|
|
497
|
-
story_warning: storyWarning(next) ?? null,
|
|
498
|
-
lane: next,
|
|
499
|
-
});
|
|
500
537
|
}
|
|
501
538
|
return { runClaudeBalanceMoveTarget, runResume };
|
|
502
539
|
}
|
|
@@ -144,6 +144,8 @@ export interface OutpostLaneDispatchBehavior {
|
|
|
144
144
|
expectedOwnerHost?: LaneRecord["host"];
|
|
145
145
|
}
|
|
146
146
|
export interface LaneMessageHandlerDeps {
|
|
147
|
+
/** Test seam invoked after a provider worker has been started. */
|
|
148
|
+
onLaneWorkerStarted?: (laneId: string, command: "message" | "resume") => void;
|
|
147
149
|
archiveResumeMessage(hqRoot: string, laneId: string, message: {
|
|
148
150
|
message_id: string;
|
|
149
151
|
[key: string]: unknown;
|
|
@@ -184,7 +186,7 @@ export interface LaneMessageHandlerDeps {
|
|
|
184
186
|
validateResumeProfileModel(ctx: Ctx, lane: LaneRecord, profile: WorkerProfile): void;
|
|
185
187
|
withResumeHireAdmission<T>(ctx: Ctx, lane: LaneRecord, fn: () => T, grantContext?: OutpostHireGrantContext): Promise<T>;
|
|
186
188
|
}
|
|
187
|
-
export interface LaneResumeHandlerDeps extends Pick<LaneMessageHandlerDeps, "archiveResumeMessage" | "assertLaneRunsHereForAdmission" | "assertLaneRunsHereForOperatorMutation" | "assessLaneAdmission" | "casUpdateLane" | "laneIsHostedHere" | "laneWithRecoveredProviderSession" | "mintMessageId" | "prepareAccountMove" | "persistClaudeAccountSelection" | "printJson" | "recordResumeSetup" | "resolveCommandLane" | "resolveResumeProfile" | "selectAutomaticClaudeResumeAccount" | "validateResumeProfileModel" | "withResumeHireAdmission"> {
|
|
189
|
+
export interface LaneResumeHandlerDeps extends Pick<LaneMessageHandlerDeps, "onLaneWorkerStarted" | "archiveResumeMessage" | "assertLaneRunsHereForAdmission" | "assertLaneRunsHereForOperatorMutation" | "assessLaneAdmission" | "casUpdateLane" | "laneIsHostedHere" | "laneWithRecoveredProviderSession" | "mintMessageId" | "prepareAccountMove" | "persistClaudeAccountSelection" | "printJson" | "recordResumeSetup" | "resolveCommandLane" | "resolveResumeProfile" | "selectAutomaticClaudeResumeAccount" | "validateResumeProfileModel" | "withResumeHireAdmission"> {
|
|
188
190
|
dispatchOutpostLaneCommand(ctx: Ctx, lane: LaneRecord, action: "resume", options: LaneResumeOptions, behavior?: OutpostLaneDispatchBehavior): Promise<void>;
|
|
189
191
|
authorizeFailedResume(ctx: Ctx, lane: LaneRecord, encodedContext?: string): string;
|
|
190
192
|
authorizeResumeProfileRepin(ctx: Ctx, lane: LaneRecord, profile: ResolvedResumeProfile | undefined, encodedContext: string | undefined): ResumeProfileRepinAuthorization | undefined;
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export declare const LANE_REMOTE_MIRROR_SYNC_FLAG = "lanes.remote-done-mirror";
|
|
|
29
29
|
/** The lane → mesh seam. Defined in lib/lanes/mesh-events.ts; the watcher shares it. */
|
|
30
30
|
export { emitLaneDecisionEvents };
|
|
31
31
|
export interface LanesCommandDeps {
|
|
32
|
+
/** Test seam for failures after a lane provider worker is started. */
|
|
33
|
+
onLaneWorkerStarted?: (laneId: string, command: "message" | "resume") => void;
|
|
32
34
|
hqRoot?: () => string;
|
|
33
35
|
/** Start HQ-root walk here instead of process.cwd(). Tests only. */
|
|
34
36
|
cwd?: () => string;
|
package/dist/commands/lanes.js
CHANGED
|
@@ -7837,6 +7837,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7837
7837
|
writeAnswerDeliveryFailed,
|
|
7838
7838
|
});
|
|
7839
7839
|
const messageHandlerDeps = {
|
|
7840
|
+
onLaneWorkerStarted: deps.onLaneWorkerStarted,
|
|
7840
7841
|
archiveResumeMessage,
|
|
7841
7842
|
assertLaneRunsHereForAdmission,
|
|
7842
7843
|
assertLaneRunsHereForOperatorMutation,
|
|
@@ -7862,6 +7863,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7862
7863
|
};
|
|
7863
7864
|
const messageHandlers = createLaneMessageHandlers(messageHandlerDeps);
|
|
7864
7865
|
const resumeHandlers = createLaneResumeHandlers({
|
|
7866
|
+
onLaneWorkerStarted: deps.onLaneWorkerStarted,
|
|
7865
7867
|
archiveResumeMessage,
|
|
7866
7868
|
assertLaneRunsHereForAdmission,
|
|
7867
7869
|
assertLaneRunsHereForOperatorMutation,
|
|
@@ -8214,8 +8216,8 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8214
8216
|
.description("Send a follow-up message to a lane")
|
|
8215
8217
|
.argument("<lane-id>", "Lane id")
|
|
8216
8218
|
.requiredOption("--text <msg>", "Message text")
|
|
8217
|
-
.option("--project <slug>", "Re-point project attribution
|
|
8218
|
-
.option("--story <id>", "Re-point story attribution
|
|
8219
|
+
.option("--project <slug>", "Re-point project attribution; a changed story is named in the resumed prompt")
|
|
8220
|
+
.option("--story <id>", "Re-point story attribution; a changed story is named in the resumed prompt")
|
|
8219
8221
|
.option("--account <id>", "Move the lane's session to this account of its provider before the message resumes it")
|
|
8220
8222
|
.option("--model <id>", "Model for the moved lane (with --account only)")
|
|
8221
8223
|
.option("--effort <level>", "Reasoning effort for the resumed provider turn")
|
|
@@ -8292,8 +8294,8 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8292
8294
|
.option("--text <msg>", "Operator message for the resumed turn")
|
|
8293
8295
|
.option("--from-failed", "Explicitly resume a failed lane as its senior")
|
|
8294
8296
|
.option("--reason <text>", "Required explanation for resuming from failed")
|
|
8295
|
-
.option("--project <slug>", "Re-point project attribution
|
|
8296
|
-
.option("--story <id>", "Re-point story attribution
|
|
8297
|
+
.option("--project <slug>", "Re-point project attribution; a changed story is named in the resumed prompt")
|
|
8298
|
+
.option("--story <id>", "Re-point story attribution; a changed story is named in the resumed prompt")
|
|
8297
8299
|
.option("--account <id>", "Move the lane's session to this account of its provider before resuming")
|
|
8298
8300
|
.option("--model <id>", "Model for the moved lane (with --account only)")
|
|
8299
8301
|
.option("--effort <level>", "Reasoning effort for the resumed provider turn")
|
|
@@ -8453,6 +8455,16 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8453
8455
|
.action(wrap(async (provider, accountId, opts) => {
|
|
8454
8456
|
await accountHandlers.runAccountsLogin(ctx, provider, accountId, opts);
|
|
8455
8457
|
}));
|
|
8458
|
+
accounts
|
|
8459
|
+
.command("clear-quota")
|
|
8460
|
+
.description("Clear quota reset marks for a provider account")
|
|
8461
|
+
.argument("<provider>", "claude | codex | grok")
|
|
8462
|
+
.argument("<account-id>", "Account id")
|
|
8463
|
+
.option("--model <model>", "Clear only this model's reset")
|
|
8464
|
+
.option("--json", "JSON on stdout (always)")
|
|
8465
|
+
.action(wrap(async (provider, accountId, opts) => {
|
|
8466
|
+
await accountHandlers.runAccountsClearQuota(ctx, provider, accountId, opts);
|
|
8467
|
+
}));
|
|
8456
8468
|
accounts
|
|
8457
8469
|
.command("enable")
|
|
8458
8470
|
.description("Enable an account without touching credentials")
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* still runs a service, the daemon leaves that service alone instead of
|
|
4
4
|
* starting a second copy, and `hq daemon migrate` lists it.
|
|
5
5
|
*
|
|
6
|
-
* Detection
|
|
7
|
-
*
|
|
6
|
+
* Detection reads unit files and checks for an orphaned loaded macOS mesh job
|
|
7
|
+
* whose plist was removed. Windows scheduled tasks are probed through
|
|
8
|
+
* schtasks. Nothing here stops or removes a unit.
|
|
8
9
|
*/
|
|
9
10
|
import type { ServiceName } from "./catalog.js";
|
|
10
11
|
export interface LegacyUnit {
|
|
@@ -22,6 +23,8 @@ export interface LegacyProbeDeps {
|
|
|
22
23
|
home: string;
|
|
23
24
|
existsSync: (p: string) => boolean;
|
|
24
25
|
listDir: (dir: string) => string[];
|
|
26
|
+
/** True when launchctl can print the GUI-domain job despite a missing plist. */
|
|
27
|
+
launchdJobLoaded: (label: string) => boolean;
|
|
25
28
|
windowsTaskExists: (taskName: string) => boolean;
|
|
26
29
|
}
|
|
27
30
|
export declare function describeLegacyUnit(unit: LegacyUnit): string;
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* still runs a service, the daemon leaves that service alone instead of
|
|
4
4
|
* starting a second copy, and `hq daemon migrate` lists it.
|
|
5
5
|
*
|
|
6
|
-
* Detection
|
|
7
|
-
*
|
|
6
|
+
* Detection reads unit files and checks for an orphaned loaded macOS mesh job
|
|
7
|
+
* whose plist was removed. Windows scheduled tasks are probed through
|
|
8
|
+
* schtasks. Nothing here stops or removes a unit.
|
|
8
9
|
*/
|
|
9
10
|
import * as path from "node:path";
|
|
10
11
|
const KIT_SERVICES = ["sync", "inbox", "heartbeat"];
|
|
@@ -52,8 +53,9 @@ function darwinUnits(deps) {
|
|
|
52
53
|
const units = [];
|
|
53
54
|
const at = (label) => path.join(dir, `${label}.plist`);
|
|
54
55
|
const add = (unitId, service, label, migratable = true) => units.push({ unitId, service, kind: "launchd", name: label, path: at(label), migratable });
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
const meshLabel = "ai.getindigo.hq-mesh-daemon";
|
|
57
|
+
if (deps.existsSync(at(meshLabel)) || deps.launchdJobLoaded(meshLabel))
|
|
58
|
+
add("mesh", "mesh", meshLabel);
|
|
57
59
|
for (const file of safeList(deps, dir).sort()) {
|
|
58
60
|
const m = /^ai\.getindigo\.hq-bot\.(.+)\.plist$/.exec(file);
|
|
59
61
|
if (m)
|