@indigoai-us/hq-cli 5.345.197 → 5.345.199
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 +111 -0
- package/dist/command-catalog.generated.d.ts +393 -12
- package/dist/command-catalog.generated.js +482 -12
- package/dist/commands/bot-events.d.ts +19 -0
- package/dist/commands/bot-events.js +46 -0
- package/dist/commands/bot-jobs.d.ts +2 -0
- package/dist/commands/bot-jobs.js +43 -2
- package/dist/commands/bot.d.ts +5 -0
- package/dist/commands/bot.js +33 -1
- package/dist/commands/core-resolve-company-native.d.ts +2 -1
- package/dist/commands/core-resolve-company-native.js +2 -24
- package/dist/commands/deploy-domains.d.ts +14 -2
- package/dist/commands/deploy-domains.js +726 -42
- package/dist/commands/integrations-connect.js +41 -3
- package/dist/commands/lanes-report.d.ts +2 -1
- package/dist/commands/lanes-report.js +44 -1
- package/dist/commands/lanes.d.ts +9 -0
- package/dist/commands/lanes.js +95 -9
- package/dist/commands/meetings.js +1 -21
- package/dist/commands/secrets.js +27 -6
- package/dist/commands/signals-run.d.ts +2 -0
- package/dist/commands/signals-run.js +6 -1
- package/dist/lanes-drain-hook.js +9 -0
- package/dist/lib/bot/api.d.ts +5 -0
- package/dist/lib/bot/claude-login.d.ts +31 -0
- package/dist/lib/bot/claude-login.js +160 -0
- package/dist/lib/bot/company-bind.d.ts +34 -0
- package/dist/lib/bot/company-bind.js +112 -0
- package/dist/lib/bot/config.js +3 -1
- package/dist/lib/bot/events.d.ts +18 -0
- package/dist/lib/bot/events.js +160 -0
- package/dist/lib/bot/executive-settings.d.ts +10 -0
- package/dist/lib/bot/executive-settings.js +43 -0
- package/dist/lib/bot/fleet-cron.js +4 -0
- package/dist/lib/bot/fleet-local.d.ts +12 -0
- package/dist/lib/bot/fleet-local.js +50 -20
- package/dist/lib/bot/job-run.js +1 -1
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +8 -0
- package/dist/lib/bot/jobs/hot-signal-sync.js +2 -0
- package/dist/lib/bot/jobs/meeting-detect.d.ts +28 -0
- package/dist/lib/bot/jobs/meeting-detect.js +176 -0
- package/dist/lib/bot/jobs/meeting-rider.d.ts +53 -0
- package/dist/lib/bot/jobs/meeting-rider.js +284 -0
- package/dist/lib/bot/jobs/signal-to-lane.d.ts +32 -0
- package/dist/lib/bot/jobs/signal-to-lane.js +169 -0
- package/dist/lib/bot/local-jobs.d.ts +15 -2
- package/dist/lib/bot/local-jobs.js +24 -7
- package/dist/lib/bot/run.d.ts +7 -49
- package/dist/lib/bot/run.js +135 -12
- package/dist/lib/bot/session.d.ts +19 -1
- package/dist/lib/bot/session.js +75 -28
- package/dist/lib/bot/status.d.ts +21 -0
- package/dist/lib/core-utils/manifest-slugs.d.ts +2 -0
- package/dist/lib/core-utils/manifest-slugs.js +26 -0
- package/dist/lib/doctor/checks/bots.d.ts +6 -0
- package/dist/lib/doctor/checks/bots.js +47 -1
- package/dist/lib/hot-channels/adapters/meetings.d.ts +1 -0
- package/dist/lib/hot-channels/adapters/meetings.js +3 -1
- package/dist/lib/hot-channels/meeting-source-ref.d.ts +3 -0
- package/dist/lib/hot-channels/meeting-source-ref.js +7 -0
- package/dist/lib/hot-channels/schedule.js +2 -2
- package/dist/lib/hot-channels/signals-pass.d.ts +9 -32
- package/dist/lib/hot-channels/signals-pass.js +51 -17
- package/dist/lib/lanes/admission.d.ts +2 -0
- package/dist/lib/lanes/admission.js +4 -0
- package/dist/lib/lanes/approval-gate.js +4 -1
- package/dist/lib/lanes/links.d.ts +8 -1
- package/dist/lib/lanes/links.js +84 -3
- package/dist/lib/lanes/outcome.d.ts +154 -0
- package/dist/lib/lanes/outcome.js +151 -0
- package/dist/lib/lanes/reconcile.js +1 -0
- package/dist/lib/lanes/signal-brief-template.d.ts +7 -0
- package/dist/lib/lanes/signal-brief-template.js +40 -0
- package/dist/lib/lanes/signal-lane.d.ts +24 -0
- package/dist/lib/lanes/signal-lane.js +99 -0
- package/dist/lib/lanes/source-report.d.ts +37 -0
- package/dist/lib/lanes/source-report.js +66 -0
- package/dist/lib/lanes/spawn.d.ts +6 -0
- package/dist/lib/lanes/spawn.js +21 -0
- package/dist/lib/lanes/store.d.ts +3 -0
- package/dist/lib/lanes/store.js +64 -0
- package/dist/lib/lanes/story-ledger.d.ts +3 -0
- package/dist/lib/lanes/story-ledger.js +1 -0
- package/dist/lib/lanes/types.d.ts +137 -0
- package/dist/lib/lanes/types.js +17 -2
- package/dist/lib/lanes/watcher.js +4 -0
- package/dist/lib/live-meetings.d.ts +19 -0
- package/dist/lib/live-meetings.js +23 -0
- package/dist/lib/mesh/live/daemon/doctor.js +6 -7
- package/dist/lib/mesh/live/spool.d.ts +8 -0
- package/dist/lib/mesh/live/spool.js +59 -40
- package/dist/lib/plan-limit-nag.d.ts +30 -4
- package/dist/lib/plan-limit-nag.js +68 -17
- package/dist/lib/signals/engine.d.ts +4 -0
- package/dist/lib/signals/engine.js +25 -5
- package/dist/lib/signals/incremental.d.ts +42 -0
- package/dist/lib/signals/incremental.js +191 -0
- package/dist/lib/signals/prior-signals.d.ts +11 -0
- package/dist/lib/signals/prior-signals.js +44 -0
- package/dist/lib/signals/reply.js +6 -1
- package/dist/lib/signals/settings.d.ts +5 -0
- package/dist/lib/signals/settings.js +17 -0
- package/dist/lib/signals/steps/extract.d.ts +30 -2
- package/dist/lib/signals/steps/extract.js +153 -11
- package/dist/lib/signals/steps/types.d.ts +1 -0
- package/dist/lib/signals/write-guard.js +7 -0
- package/dist/secrets-env-action.d.ts +70 -0
- package/dist/secrets-env-action.js +220 -2
- package/dist/utils/cognito-callback-port.d.ts +15 -0
- package/dist/utils/cognito-callback-port.js +51 -0
- package/dist/utils/cognito-config.js +2 -3
- package/dist/utils/cognito-session.d.ts +7 -6
- package/dist/utils/cognito-session.js +14 -14
- package/dist/utils/interactive-caller.d.ts +15 -0
- package/dist/utils/interactive-caller.js +70 -0
- package/dist/utils/login-browser.js +3 -1
- package/dist/utils/resident-package-preload.js +4 -0
- package/package.json +9 -7
package/dist/lib/bot/run.d.ts
CHANGED
|
@@ -1,52 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The resident bot process: `hq bot run <name>` (local-bots US-003/US-004).
|
|
3
|
-
*
|
|
4
|
-
* - pid lock in ~/.hq/bots/<name>/pid; status.json is the observable state
|
|
5
|
-
* - refuses to start unless the creds file names this bot's agt_ AND the
|
|
6
|
-
* server-side record is owned by the configured owner
|
|
7
|
-
* - heartbeat every 30s (POST /v1/agents/{uid}/heartbeat, agent JWT)
|
|
8
|
-
* - inbox poll every 2s: DMs go to ONE headless model turn (resumed by
|
|
9
|
-
* session id); a new session (restart, or a resume id the runtime no longer
|
|
10
|
-
* has) is given the recent DM thread as a "Recent messages:" block so a
|
|
11
|
-
* one-word reply still has the question it answers; the reply is sent as
|
|
12
|
-
* the bot; the message is acked only after the reply is sent; processed ids
|
|
13
|
-
* persist so a crash between reply and ack never answers twice
|
|
14
|
-
* - any correspondent can message the bot; each sender has a separate session
|
|
15
|
-
* - first start only: the intro DM, then (when bot.json has `kickoff`) ONE
|
|
16
|
-
* model turn on that prompt as if the owner sent it, answered in the DM, so
|
|
17
|
-
* a guided bot starts working without waiting for the owner to type
|
|
18
|
-
* - job items (channel "job"): a scheduled run, fired by the bot's own loop
|
|
19
|
-
* from its jobs.json (lib/bot/local-jobs.ts) or, for a hosted agent, by
|
|
20
|
-
* the cloud. No system scheduler is used on this computer. The run is one model turn
|
|
21
|
-
* in its own session (one per jobId), reported in the owner's DM with a
|
|
22
|
-
* "scheduled job" header through the same split/recovery path as every other
|
|
23
|
-
* reply, and reported back to the cloud EXACTLY ONCE — nothing else clears
|
|
24
|
-
* the job's in-progress flag. A run whose staleAfter has passed still runs,
|
|
25
|
-
* once, labelled a catch-up; older pending runs of the same job are reported
|
|
26
|
-
* skipped-precondition instead of piling up.
|
|
27
|
-
* - delivered room items (channels / group chats) are eligible for replies; claimed
|
|
28
|
-
* first (first-claim-wins), one session per room, reply posted in the room
|
|
29
|
-
* - conversations run side by side: up to MAX_PARALLEL_TURNS turns at once,
|
|
30
|
-
* one lane per model session (one DM correspondent, or one room). Messages
|
|
31
|
-
* in the same lane run in order, so a session is never resumed by two turns
|
|
32
|
-
* at once and a thread's answers never arrive out of sequence.
|
|
33
|
-
* - while a turn runs, each finished assistant message is posted as its own
|
|
34
|
-
* message (progress.ts); turns have no time limit
|
|
35
|
-
* - every received message gets a reply: a turn that fails after the model
|
|
36
|
-
* started (error, crash, owner stop, restart) posts one "did not finish"
|
|
37
|
-
* message and is NOT run again — it may already have done outward things.
|
|
38
|
-
* inflight.json marks the turn in progress so a restart can say so.
|
|
39
|
-
* - a turn that fails because the coding tool's sign-in no longer works
|
|
40
|
-
* (runtime-sign-in.ts) is not a failure of the message: the bot says once
|
|
41
|
-
* per conversation that it needs a sign-in, keeps the message un-acked,
|
|
42
|
-
* holds new turns, and tries again every SIGN_IN_RETRY_MS (and at once
|
|
43
|
-
* after a restart). A turn that works clears it.
|
|
44
|
-
* - failures before the model starts (missing binary) retry with backoff;
|
|
45
|
-
* 5 model failures in 10 minutes → state `failed` and a clean exit (launchd
|
|
46
|
-
* does not respawn a clean exit). Owner stops never count.
|
|
47
|
-
*
|
|
48
|
-
* Everything with a side effect is injectable so the loop is unit-testable.
|
|
49
|
-
*/
|
|
50
1
|
import { type OwnerContext } from "./owner-context.js";
|
|
51
2
|
import { type BotApi, type InboxItem } from "./api.js";
|
|
52
3
|
import type { BotConfig, BotKind } from "./config.js";
|
|
@@ -57,6 +8,7 @@ import { type BotRuntime, type RuntimeTurnInput, type RuntimeTurnResult } from "
|
|
|
57
8
|
import { type BotSessionScope } from "./paths.js";
|
|
58
9
|
import { type DecisionDmClient } from "./decisions.js";
|
|
59
10
|
import type { VaultObjectDownloader } from "../agent-kit/attachment-download.js";
|
|
11
|
+
import type { JobRunCompletion } from "./api.js";
|
|
60
12
|
import { type RuntimeSignInCheck } from "./runtime-sign-in.js";
|
|
61
13
|
import { type HeartbeatAgentUidSource, type HeartbeatCredsSource, type HeartbeatIdentityMismatchReporter } from "../heartbeat-identity.js";
|
|
62
14
|
import { type BotState, type BotStatusFile } from "./status.js";
|
|
@@ -325,6 +277,12 @@ export declare function inboxLaneKey(item: InboxItem, config: Pick<BotConfig, "o
|
|
|
325
277
|
*/
|
|
326
278
|
export declare function dmTurnSessionScope(item: Pick<InboxItem, "fromPersonUid" | "text">, ownerUid: string): BotSessionScope;
|
|
327
279
|
export declare function preflightCreds(dir: string, config: BotConfig): string | null;
|
|
280
|
+
/** Register source-independent in-process jobs by their context.jobKind. */
|
|
281
|
+
export declare const builtinEventHandlers: Record<string, (input: {
|
|
282
|
+
dir: string;
|
|
283
|
+
config: BotConfig;
|
|
284
|
+
item: InboxItem;
|
|
285
|
+
}) => Promise<JobRunCompletion>>;
|
|
328
286
|
export declare function runBot(deps: BotRunDeps): Promise<BotHandle>;
|
|
329
287
|
/** True for a setup note the desktop app sent, not words the person typed. */
|
|
330
288
|
export declare function isAppSetupNote(text: string): boolean;
|
package/dist/lib/bot/run.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ensureSignalToLaneJobs, runSignalToLane, SIGNAL_TO_LANE_JOB_KIND } from "./jobs/signal-to-lane.js";
|
|
1
2
|
/**
|
|
2
3
|
* The resident bot process: `hq bot run <name>` (local-bots US-003/US-004).
|
|
3
4
|
*
|
|
@@ -47,6 +48,9 @@
|
|
|
47
48
|
*
|
|
48
49
|
* Everything with a side effect is injectable so the loop is unit-testable.
|
|
49
50
|
*/
|
|
51
|
+
import { createMeetingRider, defaultMeetingRiderDeps, ensureMeetingRiderJob, MEETING_RIDER_JOB_KIND } from "./jobs/meeting-rider.js";
|
|
52
|
+
import { executiveRiderEnabled, isExecutiveBot } from "./executive-settings.js";
|
|
53
|
+
import { createMeetingDetect, defaultMeetingDetectDeps, resolveDetectedMeeting } from "./jobs/meeting-detect.js";
|
|
50
54
|
import { botAuthEnv, describeBotAuth, formatBotAuth } from "./runtime-auth.js";
|
|
51
55
|
import { ownerContextBlock } from "./owner-context.js";
|
|
52
56
|
import { randomUUID } from "node:crypto";
|
|
@@ -58,7 +62,7 @@ import { fullJitterDelayMs } from "../mesh/live/backoff.js";
|
|
|
58
62
|
import { acquirePidLock, defaultPidLockDeps, releasePidLock } from "../mesh/live/daemon/pid-lock.js";
|
|
59
63
|
import { BotApiError } from "./api.js";
|
|
60
64
|
import { effectiveBotCompanies, effectiveBotKind, patchBotConfig, readBotConfig, resolveBotMemoryDir, SETUP_BOT_WORKER_ID } from "./config.js";
|
|
61
|
-
import { ensureBotSessionMeta } from "./company-bind.js";
|
|
65
|
+
import { botCompanyScopeKey, ensureBotSessionMeta, legacyPersonalEntryScopeKey, readManifestCompanies, resolveBotCompanyBinding } from "./company-bind.js";
|
|
62
66
|
import { readBotCredsIdentity } from "./creds.js";
|
|
63
67
|
import { isProcessed, markProcessed, readInboxState } from "./inbox-state.js";
|
|
64
68
|
import { clearInflight, patchInflight, readInflight, readInflightTurns, writeInflight } from "./inflight.js";
|
|
@@ -86,6 +90,7 @@ import { buildJobTurnPrompt, isCatchUpRun, isJobItem, jobReportHeader, jobRunOut
|
|
|
86
90
|
import { botJobQuietOptionEnabled } from "./job-quiet-flag.js";
|
|
87
91
|
import { currentHotSignalSyncCompany, hotSignalSyncJobCompany, readActiveCompanySlug, removeStaleHotSignalSyncJobs } from "./jobs/hot-signal-sync.js";
|
|
88
92
|
import { isLocalJobMessage, isLocalJobRetryMessage, localJobRetryMessageId, readLocalJobs } from "./local-jobs.js";
|
|
93
|
+
import { drainBotEvents } from "./events.js";
|
|
89
94
|
import { clearPendingJobCompletion, isCompletionDue, isCompletionExpired, isCompletionForOtherAgent, readPendingJobCompletions, recordPendingJobCompletion } from "./job-completions.js";
|
|
90
95
|
import { SIGN_IN_CHECK_FRESH_MS, SIGN_IN_CHECK_MS, SIGN_IN_RETRY_MS, signInNeededText, signedOutExcerpt, } from "./runtime-sign-in.js";
|
|
91
96
|
import { checkRuntimeSignIn } from "./runtime-sign-in-check.js";
|
|
@@ -407,6 +412,8 @@ export function preflightCreds(dir, config) {
|
|
|
407
412
|
}
|
|
408
413
|
return null;
|
|
409
414
|
}
|
|
415
|
+
/** Register source-independent in-process jobs by their context.jobKind. */
|
|
416
|
+
export const builtinEventHandlers = Object.create(null);
|
|
410
417
|
export async function runBot(deps) {
|
|
411
418
|
const now = deps.now ?? (() => new Date());
|
|
412
419
|
const sleep = deps.sleep ?? defaultSleep;
|
|
@@ -414,6 +421,23 @@ export async function runBot(deps) {
|
|
|
414
421
|
const log = deps.log ?? createBotLogger(deps.dir, now);
|
|
415
422
|
const pid = deps.pid ?? process.pid;
|
|
416
423
|
const { dir, config, api, runtime } = deps;
|
|
424
|
+
const meetingRider = createMeetingRider(defaultMeetingRiderDeps(config, dir, (message) => log("info", message)));
|
|
425
|
+
const meetingDetectDeps = defaultMeetingDetectDeps(config, dir, (message) => log("info", message));
|
|
426
|
+
const meetingDetect = createMeetingDetect(meetingDetectDeps);
|
|
427
|
+
const eventHandlers = Object.create(builtinEventHandlers);
|
|
428
|
+
eventHandlers[MEETING_RIDER_JOB_KIND] = async ({ item }) => {
|
|
429
|
+
if (!isExecutiveBot(config))
|
|
430
|
+
return { status: "skipped-precondition", output: "Not the Executive" };
|
|
431
|
+
if (!executiveRiderEnabled())
|
|
432
|
+
return { status: "skipped-precondition", output: "rider disabled" };
|
|
433
|
+
meetingRider.start(await resolveDetectedMeeting(config.hqRoot, typeof item.context === "object" ? item.context?.payload : undefined, meetingDetectDeps.list));
|
|
434
|
+
return { status: "succeeded", output: "Meeting rider accepted" };
|
|
435
|
+
};
|
|
436
|
+
eventHandlers[SIGNAL_TO_LANE_JOB_KIND] = async ({ item }) => runSignalToLane({
|
|
437
|
+
dir, config, payload: typeof item.context === "object" ? item.context?.payload : undefined,
|
|
438
|
+
company: typeof item.context === "object" ? item.context?.company : undefined,
|
|
439
|
+
log: message => log("info", message),
|
|
440
|
+
});
|
|
417
441
|
const runTurn = deps.runTurn ?? ((rt, input) => runRuntimeTurn(rt, input));
|
|
418
442
|
const inboxIntervalMs = deps.inboxIntervalMs ?? INBOX_POLL_INTERVAL_MS;
|
|
419
443
|
const inboxPollBackoffEnabled = deps.inboxPollBackoffEnabled ?? createInboxPollBackoffFlagReader(config);
|
|
@@ -531,18 +555,36 @@ export async function runBot(deps) {
|
|
|
531
555
|
const kind = effectiveBotKind(config);
|
|
532
556
|
const companies = effectiveBotCompanies(config);
|
|
533
557
|
// A single membership is unambiguous; personal and multi-company bots stay unbound.
|
|
534
|
-
const
|
|
535
|
-
const
|
|
536
|
-
|
|
558
|
+
const binding = resolveBotCompanyBinding(config);
|
|
559
|
+
const companySlug = binding.slug;
|
|
560
|
+
// What bot.json alone says; a lock inferred from the manifest is not part of it.
|
|
561
|
+
const startExplicitSlug = resolveBotCompanyBinding(config, () => []).slug;
|
|
562
|
+
// The explicit scope: bot.json's kind, memberships and company. A lock inferred
|
|
563
|
+
// from the HQ's company list is not part of it.
|
|
564
|
+
const companyScopeKey = kind === "company" || (companySlug !== undefined && !binding.inferred)
|
|
565
|
+
? botCompanyScopeKey(kind, companies, companySlug)
|
|
566
|
+
: undefined;
|
|
567
|
+
// A personal bot with no explicit company records the company it inferred (or
|
|
568
|
+
// null when unbound) beside that key. When the HQ's company list changes, its
|
|
569
|
+
// conversations are archived instead of resumed under a lock they were not
|
|
570
|
+
// created under. The first inferred lock keeps existing history.
|
|
571
|
+
const inferredScopeSlug = companyScopeKey === undefined && kind === "personal" && config.workerId !== SETUP_BOT_WORKER_ID
|
|
572
|
+
? (binding.inferred ? binding.slug ?? null : null)
|
|
537
573
|
: undefined;
|
|
574
|
+
// The key an older CLI wrote when bot.json lists "personal": the same scope.
|
|
575
|
+
const legacyScopeKey = legacyPersonalEntryScopeKey(config);
|
|
538
576
|
const lockDeps = defaultPidLockDeps({
|
|
539
577
|
pid,
|
|
540
578
|
...(deps.isProcessAlive ? { isProcessAlive: deps.isProcessAlive } : {}),
|
|
541
579
|
});
|
|
542
580
|
const finish = async (final, code, reason, beforeExit) => {
|
|
543
|
-
if (state === "stopped" || state === "failed")
|
|
581
|
+
if (state === "stopped" || state === "failed") {
|
|
582
|
+
await done;
|
|
544
583
|
return;
|
|
584
|
+
}
|
|
545
585
|
state = final;
|
|
586
|
+
await meetingDetect.stop();
|
|
587
|
+
await meetingRider.stop();
|
|
546
588
|
closeInboxWakeWatcher();
|
|
547
589
|
setStatus({ state: final, ...(final === "failed" ? { lastError: reason.slice(0, 400) } : {}) });
|
|
548
590
|
log(final === "failed" ? "error" : "info", `bot ${final}: ${reason}`);
|
|
@@ -679,7 +721,10 @@ export async function runBot(deps) {
|
|
|
679
721
|
return { stop: async () => { }, done, status: () => null, idle: () => true };
|
|
680
722
|
}
|
|
681
723
|
try {
|
|
682
|
-
const scope = ensureBotCompanyScope(dir, companyScopeKey
|
|
724
|
+
const scope = ensureBotCompanyScope(dir, companyScopeKey, {
|
|
725
|
+
...(inferredScopeSlug !== undefined ? { inferredSlug: inferredScopeSlug } : {}),
|
|
726
|
+
...(legacyScopeKey !== undefined && legacyScopeKey !== companyScopeKey ? { equivalentKeys: [legacyScopeKey] } : {}),
|
|
727
|
+
});
|
|
683
728
|
if (scope.reset)
|
|
684
729
|
log("info", "company scope changed or was unverified; archived prior conversations before starting fresh");
|
|
685
730
|
}
|
|
@@ -1014,6 +1059,8 @@ export async function runBot(deps) {
|
|
|
1014
1059
|
activeInboxWait?.abort();
|
|
1015
1060
|
};
|
|
1016
1061
|
const waitForInbox = async (delayMs, adaptive, useMqttWake = false) => {
|
|
1062
|
+
if (readLocalJobs(dir).some((job) => job.trigger))
|
|
1063
|
+
delayMs = Math.min(delayMs, inboxIntervalMs);
|
|
1017
1064
|
if (!adaptive) {
|
|
1018
1065
|
await sleep(delayMs);
|
|
1019
1066
|
return;
|
|
@@ -1023,9 +1070,14 @@ export async function runBot(deps) {
|
|
|
1023
1070
|
while (remainingMs > 0 && !stopping && state === "running") {
|
|
1024
1071
|
const controller = new AbortController();
|
|
1025
1072
|
activeInboxWait = controller;
|
|
1026
|
-
const chunkMs =
|
|
1073
|
+
const chunkMs = Math.min(inboxIntervalMs, remainingMs);
|
|
1027
1074
|
if (useMqttWake) {
|
|
1028
|
-
await getInboxWakeWatcher().wait(chunkMs, controller.signal);
|
|
1075
|
+
const wake = await getInboxWakeWatcher().wait(chunkMs, controller.signal);
|
|
1076
|
+
if (wake === "wake") {
|
|
1077
|
+
if (activeInboxWait === controller)
|
|
1078
|
+
activeInboxWait = null;
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1029
1081
|
}
|
|
1030
1082
|
else {
|
|
1031
1083
|
await sleep(chunkMs, controller.signal);
|
|
@@ -1036,6 +1088,9 @@ export async function runBot(deps) {
|
|
|
1036
1088
|
break;
|
|
1037
1089
|
if (hasPromotionHold(dir))
|
|
1038
1090
|
break;
|
|
1091
|
+
// The first event job can be added while an otherwise idle bot waits.
|
|
1092
|
+
if (readLocalJobs(dir).some((job) => job.trigger))
|
|
1093
|
+
break;
|
|
1039
1094
|
if (observedGeneration !== inboxWaitGeneration || controller.signal.aborted) {
|
|
1040
1095
|
observedGeneration = inboxWaitGeneration;
|
|
1041
1096
|
remainingMs = inboxIntervalMs;
|
|
@@ -1102,14 +1157,24 @@ export async function runBot(deps) {
|
|
|
1102
1157
|
* prepended so a one-word reply still has the question it answers.
|
|
1103
1158
|
*/
|
|
1104
1159
|
const bindingNotices = new Set();
|
|
1160
|
+
let bindingNoticeLogged = false;
|
|
1105
1161
|
const executeTurn = async (prompt, sessionScope, tReceived, stream = {}, historyEventId) => {
|
|
1106
1162
|
const fresh = readBotConfig(dir);
|
|
1107
1163
|
if (!fresh)
|
|
1108
1164
|
throw new CompanyBindingError(`I couldn't read my company settings. Please restart ${config.name} after restoring its configuration; this work is still pending.`);
|
|
1109
1165
|
const live = fresh;
|
|
1110
1166
|
const liveCompanies = effectiveBotCompanies(live);
|
|
1111
|
-
|
|
1112
|
-
|
|
1167
|
+
// Explicit settings only: a lock inferred from the HQ's company list is
|
|
1168
|
+
// fixed at start, so an owner who creates or joins a company never fails a turn.
|
|
1169
|
+
const liveCompanySlug = resolveBotCompanyBinding(live, () => []).slug;
|
|
1170
|
+
if (binding.inferred && !bindingNoticeLogged) {
|
|
1171
|
+
const hqNow = readManifestCompanies(config.hqRoot);
|
|
1172
|
+
if (!(hqNow.length === 1 && hqNow[0] === companySlug)) {
|
|
1173
|
+
bindingNoticeLogged = true;
|
|
1174
|
+
log("warn", `${config.name}: this HQ's company list changed since start; keeping the start-time lock to ${companySlug} until the next restart.`);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
if (liveCompanySlug !== startExplicitSlug || effectiveBotKind(live) !== kind ||
|
|
1113
1178
|
JSON.stringify([...liveCompanies].sort()) !== JSON.stringify([...companies].sort())) {
|
|
1114
1179
|
throw new CompanyBindingError(`My company settings changed. Please restart ${config.name} to load them before I continue; this work is still pending.`);
|
|
1115
1180
|
}
|
|
@@ -1171,7 +1236,7 @@ export async function runBot(deps) {
|
|
|
1171
1236
|
let bind = { env };
|
|
1172
1237
|
if (companySlug) {
|
|
1173
1238
|
try {
|
|
1174
|
-
ensureBotSessionMeta(config.hqRoot, sid,
|
|
1239
|
+
ensureBotSessionMeta(config.hqRoot, sid, companySlug, undefined, now);
|
|
1175
1240
|
}
|
|
1176
1241
|
catch {
|
|
1177
1242
|
throw new CompanyBindingError(`I couldn't bind this session to ${companySlug}. Please check the bot's workspace permissions and retry; this work is still pending.`);
|
|
@@ -1990,6 +2055,31 @@ export async function runBot(deps) {
|
|
|
1990
2055
|
const id = item.messageId;
|
|
1991
2056
|
const { job, jobId } = target;
|
|
1992
2057
|
const peerUid = target.peerUid;
|
|
2058
|
+
const eventHandler = item.messageId.startsWith("localjob-event-") && typeof job.context === "object" && typeof job.context?.jobKind === "string"
|
|
2059
|
+
? eventHandlers[job.context.jobKind] : undefined;
|
|
2060
|
+
if (eventHandler) {
|
|
2061
|
+
// Recoverable built-ins persist intent before acknowledging the event.
|
|
2062
|
+
// Signal lane creation is idempotent, so a pre-ack crash can replay.
|
|
2063
|
+
const durableEvent = typeof job.context === "object" && (job.context?.jobKind === MEETING_RIDER_JOB_KIND || job.context?.jobKind === SIGNAL_TO_LANE_JOB_KIND);
|
|
2064
|
+
if (!durableEvent)
|
|
2065
|
+
markProcessed(dir, id, now().toISOString());
|
|
2066
|
+
let completion;
|
|
2067
|
+
try {
|
|
2068
|
+
completion = await eventHandler({ dir, config, item });
|
|
2069
|
+
// Durable intent is stored before acknowledging; a pre-effect crash replays.
|
|
2070
|
+
if (durableEvent)
|
|
2071
|
+
markProcessed(dir, id, now().toISOString());
|
|
2072
|
+
}
|
|
2073
|
+
catch (error) {
|
|
2074
|
+
if (durableEvent)
|
|
2075
|
+
throw error;
|
|
2076
|
+
completion = { status: "failed", output: error instanceof Error ? error.message : String(error) };
|
|
2077
|
+
}
|
|
2078
|
+
await completeJobRun(jobId, id, completion);
|
|
2079
|
+
await api.ackInbox(config.agentUid, id);
|
|
2080
|
+
log("info", `event job ${jobId} run ${id} ${completion.status}`);
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
1993
2083
|
// An older pending run of this job: a newer one runs instead. Reported so
|
|
1994
2084
|
// the cloud releases the slot and the history says what happened.
|
|
1995
2085
|
if (supersededJobRuns.has(id)) {
|
|
@@ -2794,6 +2884,21 @@ export async function runBot(deps) {
|
|
|
2794
2884
|
// up below: end here, before the kickoff can send a "needs a sign-in" DM.
|
|
2795
2885
|
if (stopping)
|
|
2796
2886
|
return await stoppedDuringStartup();
|
|
2887
|
+
try {
|
|
2888
|
+
ensureSignalToLaneJobs(dir, config);
|
|
2889
|
+
}
|
|
2890
|
+
catch {
|
|
2891
|
+
log("warn", "Signal-to-lane registration failed");
|
|
2892
|
+
}
|
|
2893
|
+
if (isExecutiveBot(config)) {
|
|
2894
|
+
try {
|
|
2895
|
+
if (ensureMeetingRiderJob(dir) && executiveRiderEnabled())
|
|
2896
|
+
meetingRider.resume();
|
|
2897
|
+
}
|
|
2898
|
+
catch {
|
|
2899
|
+
log("warn", "Meeting rider registration or recovery failed");
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2797
2902
|
// The kickoff runs alongside the heartbeat, before the first inbox poll, so
|
|
2798
2903
|
// an owner message sent meanwhile is answered after it in the same session.
|
|
2799
2904
|
const kickoff = kickoffAfterIntro
|
|
@@ -2910,6 +3015,14 @@ export async function runBot(deps) {
|
|
|
2910
3015
|
kickCompletionRetries();
|
|
2911
3016
|
// A failed signals run's automatic retry, once due (no model turn).
|
|
2912
3017
|
enqueueDueSignalsRetries();
|
|
3018
|
+
if (isExecutiveBot(config)) {
|
|
3019
|
+
try {
|
|
3020
|
+
meetingDetect.tick();
|
|
3021
|
+
}
|
|
3022
|
+
catch {
|
|
3023
|
+
log("warn", "meeting detection settings unreadable");
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
2913
3026
|
// Holding after repeated model failures: no inbox pulls until the
|
|
2914
3027
|
// cooldown is over (the update watcher above keeps running meanwhile).
|
|
2915
3028
|
if (modelHoldUntil > now().getTime()) {
|
|
@@ -2918,6 +3031,15 @@ export async function runBot(deps) {
|
|
|
2918
3031
|
}
|
|
2919
3032
|
if (modelHoldUntil)
|
|
2920
3033
|
liftModelHold();
|
|
3034
|
+
// Local events are available even when the cloud inbox is unavailable.
|
|
3035
|
+
try {
|
|
3036
|
+
for (const item of drainBotEvents(dir))
|
|
3037
|
+
enqueue(item);
|
|
3038
|
+
pump();
|
|
3039
|
+
}
|
|
3040
|
+
catch (error) {
|
|
3041
|
+
log("warn", `event drain failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3042
|
+
}
|
|
2921
3043
|
// While the tool says it is signed out, ask it again on a timer (cheap,
|
|
2922
3044
|
// no turn): the moment it is signed in, the held messages run.
|
|
2923
3045
|
if (signInIssue && busyLanes.size === 0 && now().getTime() >= nextSignInCheckAt) {
|
|
@@ -2975,7 +3097,8 @@ export async function runBot(deps) {
|
|
|
2975
3097
|
log("warn", `inbox poll failed: ${message}`);
|
|
2976
3098
|
if (stopping || state !== "running")
|
|
2977
3099
|
break;
|
|
2978
|
-
|
|
3100
|
+
const retryDelay = apiPollDelayMs({ err, attempt: inboxFails - 1, intervalMs: inboxIntervalMs, random });
|
|
3101
|
+
await sleep(readLocalJobs(dir).some((job) => job.trigger) ? Math.min(retryDelay, inboxIntervalMs) : retryDelay);
|
|
2979
3102
|
continue;
|
|
2980
3103
|
}
|
|
2981
3104
|
if (stopping || state !== "running")
|
|
@@ -18,6 +18,24 @@ export interface BotSessionFile {
|
|
|
18
18
|
export declare function readBotSession(dir: string, runtime: BotRuntimeId, scope?: BotSessionScope): BotSessionFile | null;
|
|
19
19
|
export declare function writeBotSession(dir: string, session: BotSessionFile, scope?: BotSessionScope): void;
|
|
20
20
|
export declare function clearBotSession(dir: string, scope?: BotSessionScope): void;
|
|
21
|
+
export interface BotCompanyScopeOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Keys an earlier CLI wrote for this same scope. A marker holding one is
|
|
24
|
+
* rewritten to the current key in place: nothing is archived and pending
|
|
25
|
+
* work is kept. Used for a company bot whose bot.json lists "personal",
|
|
26
|
+
* which no longer counts as a membership.
|
|
27
|
+
*/
|
|
28
|
+
equivalentKeys?: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* A personal bot with no explicit company (companyScopeKey undefined): the
|
|
31
|
+
* company its sessions are locked to because the HQ lists only that one, or
|
|
32
|
+
* null when it is unbound. A change from the recorded value archives the
|
|
33
|
+
* conversations, like an explicit scope change, so a session is never
|
|
34
|
+
* resumed under a lock it was not created under. Ignored when
|
|
35
|
+
* companyScopeKey is set.
|
|
36
|
+
*/
|
|
37
|
+
inferredSlug?: string | null;
|
|
38
|
+
}
|
|
21
39
|
/**
|
|
22
40
|
* Run once at startup, before reading or appending conversation state. A scope
|
|
23
41
|
* change must never resume a prior company's model session or replay its DM
|
|
@@ -26,7 +44,7 @@ export declare function clearBotSession(dir: string, scope?: BotSessionScope): v
|
|
|
26
44
|
* Legacy personal bots retain their existing history; company bots require
|
|
27
45
|
* positive scope evidence and archive legacy, unproven conversations once.
|
|
28
46
|
*/
|
|
29
|
-
export declare function ensureBotCompanyScope(dir: string, companyScopeKey: string | undefined): {
|
|
47
|
+
export declare function ensureBotCompanyScope(dir: string, companyScopeKey: string | undefined, options?: BotCompanyScopeOptions): {
|
|
30
48
|
reset: boolean;
|
|
31
49
|
archiveDir?: string;
|
|
32
50
|
};
|
package/dist/lib/bot/session.js
CHANGED
|
@@ -38,6 +38,31 @@ export function clearBotSession(dir, scope = "dm") {
|
|
|
38
38
|
/* already gone */
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
const isScopeValue = (value) => typeof value === "string" || value === null;
|
|
42
|
+
function writeCompanyScopeMarker(dir, marker) {
|
|
43
|
+
const tmp = path.join(dir, `.company-scope.${randomUUID()}.tmp`);
|
|
44
|
+
try {
|
|
45
|
+
fs.writeFileSync(tmp, `${JSON.stringify(marker)}\n`, { mode: 0o600 });
|
|
46
|
+
fs.renameSync(tmp, path.join(dir, "company-scope.json"));
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
fs.rmSync(tmp, { force: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Move every session and DM history file into a fresh private archive folder. */
|
|
53
|
+
function archiveConversations(dir) {
|
|
54
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
55
|
+
const names = fs.readdirSync(dir).filter((name) => /^(?:session|dm-history)(?:-[A-Za-z0-9_-]+)?\.json$/.test(name));
|
|
56
|
+
if (!names.length)
|
|
57
|
+
return { names };
|
|
58
|
+
const archiveRoot = path.join(dir, "company-scope-archives");
|
|
59
|
+
fs.mkdirSync(archiveRoot, { recursive: true, mode: 0o700 });
|
|
60
|
+
const archiveDir = path.join(archiveRoot, randomUUID());
|
|
61
|
+
fs.mkdirSync(archiveDir, { mode: 0o700 });
|
|
62
|
+
for (const name of names)
|
|
63
|
+
fs.renameSync(path.join(dir, name), path.join(archiveDir, name));
|
|
64
|
+
return { names, archiveDir };
|
|
65
|
+
}
|
|
41
66
|
/**
|
|
42
67
|
* Run once at startup, before reading or appending conversation state. A scope
|
|
43
68
|
* change must never resume a prior company's model session or replay its DM
|
|
@@ -46,13 +71,14 @@ export function clearBotSession(dir, scope = "dm") {
|
|
|
46
71
|
* Legacy personal bots retain their existing history; company bots require
|
|
47
72
|
* positive scope evidence and archive legacy, unproven conversations once.
|
|
48
73
|
*/
|
|
49
|
-
export function ensureBotCompanyScope(dir, companyScopeKey) {
|
|
74
|
+
export function ensureBotCompanyScope(dir, companyScopeKey, options = {}) {
|
|
50
75
|
const marker = path.join(dir, "company-scope.json");
|
|
51
76
|
const hasMarker = fs.existsSync(marker);
|
|
52
|
-
|
|
77
|
+
const inferred = companyScopeKey === undefined ? options.inferredSlug : undefined;
|
|
78
|
+
if (!hasMarker && companyScopeKey === undefined && inferred === undefined)
|
|
53
79
|
return { reset: false };
|
|
54
80
|
const expected = companyScopeKey ?? null;
|
|
55
|
-
let
|
|
81
|
+
let previous = null;
|
|
56
82
|
if (hasMarker) {
|
|
57
83
|
// Read errors fail closed. Invalid JSON is unproven provenance and resets.
|
|
58
84
|
const body = fs.readFileSync(marker, "utf8");
|
|
@@ -63,16 +89,22 @@ export function ensureBotCompanyScope(dir, companyScopeKey) {
|
|
|
63
89
|
catch {
|
|
64
90
|
parsed = null;
|
|
65
91
|
}
|
|
66
|
-
|
|
67
|
-
parsed
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
92
|
+
previous = parsed && typeof parsed === "object" && parsed.v === 1
|
|
93
|
+
? parsed
|
|
94
|
+
: null;
|
|
95
|
+
if (inferred === undefined && previous !== null) {
|
|
96
|
+
if (previous.companyScopeKey === expected)
|
|
97
|
+
return { reset: false };
|
|
98
|
+
if (typeof previous.companyScopeKey === "string" && options.equivalentKeys?.includes(previous.companyScopeKey)) {
|
|
99
|
+
// The same scope under an older key: record the current key and keep everything.
|
|
100
|
+
writeCompanyScopeMarker(dir, { v: 1, companyScopeKey: expected });
|
|
101
|
+
return { reset: false };
|
|
102
|
+
}
|
|
74
103
|
}
|
|
75
104
|
}
|
|
105
|
+
const provenPreviousScope = previous !== null && isScopeValue(previous.companyScopeKey);
|
|
106
|
+
if (inferred !== undefined)
|
|
107
|
+
return ensureInferredScope(dir, hasMarker, previous, inferred);
|
|
76
108
|
// A known company switch must settle recovery under its previous scope.
|
|
77
109
|
// Legacy bots have no scope evidence: isolate their model context below,
|
|
78
110
|
// but leave delivery/job markers intact for the existing recovery path.
|
|
@@ -84,26 +116,41 @@ export function ensureBotCompanyScope(dir, companyScopeKey) {
|
|
|
84
116
|
}
|
|
85
117
|
if (!provenPreviousScope)
|
|
86
118
|
omitInflightRecoveryHistory(dir, pending);
|
|
87
|
-
|
|
88
|
-
const names = fs.readdirSync(dir).filter((name) => /^(?:session|dm-history)(?:-[A-Za-z0-9_-]+)?\.json$/.test(name));
|
|
89
|
-
let archiveDir;
|
|
90
|
-
if (names.length) {
|
|
91
|
-
const archiveRoot = path.join(dir, "company-scope-archives");
|
|
92
|
-
fs.mkdirSync(archiveRoot, { recursive: true, mode: 0o700 });
|
|
93
|
-
archiveDir = path.join(archiveRoot, randomUUID());
|
|
94
|
-
fs.mkdirSync(archiveDir, { mode: 0o700 });
|
|
95
|
-
for (const name of names)
|
|
96
|
-
fs.renameSync(path.join(dir, name), path.join(archiveDir, name));
|
|
97
|
-
}
|
|
119
|
+
const { names, archiveDir } = archiveConversations(dir);
|
|
98
120
|
// Commit the new provenance only after every old conversation was isolated.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
121
|
+
writeCompanyScopeMarker(dir, { v: 1, companyScopeKey: expected });
|
|
122
|
+
return { reset: names.length > 0, ...(archiveDir ? { archiveDir } : {}) };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The personal-bot path of ensureBotCompanyScope: the recorded inferred slug
|
|
126
|
+
* must match, or the conversations are archived. The explicit companyScopeKey
|
|
127
|
+
* field is carried over exactly as the explicit rule would leave it (absent,
|
|
128
|
+
* or null once the bot has had a marker), so a later explicit bind behaves as
|
|
129
|
+
* it did before inferred locks existed.
|
|
130
|
+
*/
|
|
131
|
+
function ensureInferredScope(dir, hasMarker, previous, inferred) {
|
|
132
|
+
const previousInferred = previous !== null && isScopeValue(previous.inferredCompanySlug) ? previous.inferredCompanySlug : undefined;
|
|
133
|
+
const previousExplicit = previous !== null && isScopeValue(previous.companyScopeKey) ? previous.companyScopeKey : undefined;
|
|
134
|
+
const recordsNull = hasMarker && !(previousExplicit === undefined && previousInferred !== undefined);
|
|
135
|
+
const next = { v: 1, ...(recordsNull ? { companyScopeKey: null } : {}), inferredCompanySlug: inferred };
|
|
136
|
+
// No inferred record yet: a legacy personal bot (no marker, or an
|
|
137
|
+
// explicit-unbound one) keeps its history, as it did before inferred locks.
|
|
138
|
+
if (!hasMarker || (previousExplicit === null && previousInferred === undefined)) {
|
|
139
|
+
writeCompanyScopeMarker(dir, next);
|
|
140
|
+
return { reset: false };
|
|
103
141
|
}
|
|
104
|
-
|
|
105
|
-
|
|
142
|
+
if (previousInferred === inferred && typeof previousExplicit !== "string")
|
|
143
|
+
return { reset: false };
|
|
144
|
+
const pending = readInflightTurns(dir, { strictIo: true });
|
|
145
|
+
// Leaving an explicit company keeps the explicit rule: settle its work first.
|
|
146
|
+
// A change in the HQ's company list cannot be undone from bot.json, so it
|
|
147
|
+
// never blocks the start; saved work is settled without the old history.
|
|
148
|
+
if (typeof previousExplicit === "string" && pending.length > 0) {
|
|
149
|
+
throw new Error("Bot company settings changed with pending work. Restore the previous company settings and settle pending work before switching companies.");
|
|
106
150
|
}
|
|
151
|
+
omitInflightRecoveryHistory(dir, pending);
|
|
152
|
+
const { names, archiveDir } = archiveConversations(dir);
|
|
153
|
+
writeCompanyScopeMarker(dir, next);
|
|
107
154
|
return { reset: names.length > 0, ...(archiveDir ? { archiveDir } : {}) };
|
|
108
155
|
}
|
|
109
156
|
//# sourceMappingURL=session.js.map
|
package/dist/lib/bot/status.d.ts
CHANGED
|
@@ -85,8 +85,29 @@ export interface BotStatusFile {
|
|
|
85
85
|
} | null;
|
|
86
86
|
/** hq-cli version this process loaded (a bot keeps it until it restarts). */
|
|
87
87
|
cliVersion?: string;
|
|
88
|
+
/**
|
|
89
|
+
* HQ Fleet Runtime bots: the last turn that ended in the canned away line,
|
|
90
|
+
* as the bot reported it to HQ and to the owner (written by the HQ DM
|
|
91
|
+
* adapter in hq-fleet-local).
|
|
92
|
+
*/
|
|
93
|
+
lastFailure?: BotLastFailure;
|
|
88
94
|
updatedAt: string;
|
|
89
95
|
}
|
|
96
|
+
export interface BotLastFailure {
|
|
97
|
+
at: string;
|
|
98
|
+
/** auth | billing | rate_limit | outage | runtime_error | generic */
|
|
99
|
+
class: string;
|
|
100
|
+
site?: string;
|
|
101
|
+
provider?: string | null;
|
|
102
|
+
/** Plain-language cause, as the owner was told. */
|
|
103
|
+
cause: string;
|
|
104
|
+
/** Plain-language fix, as the owner was told. */
|
|
105
|
+
fix: string;
|
|
106
|
+
/** The HQ failure report id, when the upload succeeded. */
|
|
107
|
+
reportId?: string | null;
|
|
108
|
+
uploaded?: boolean;
|
|
109
|
+
ownerNotified?: boolean;
|
|
110
|
+
}
|
|
90
111
|
export declare function defaultBotStatus(pid: number, runtime: BotRuntimeId, now?: () => Date): BotStatusFile;
|
|
91
112
|
export declare function readBotStatus(dir: string): BotStatusFile | null;
|
|
92
113
|
export declare function writeBotStatus(dir: string, status: BotStatusFile): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Leaf module: company slugs from companies/manifest.yaml, shared by the resolver and the bot runtime. */
|
|
2
|
+
const keepSlug = (slug) => slug !== "" && slug !== "_template" && slug !== "companies" && slug !== "unaffiliated_repos";
|
|
3
|
+
export function parseManifestSlugs(text) {
|
|
4
|
+
const slugs = new Set();
|
|
5
|
+
let wrapped = false;
|
|
6
|
+
for (const rawLine of text.split("\n")) {
|
|
7
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
8
|
+
if (/^companies:[ \t]*$/.test(line)) {
|
|
9
|
+
wrapped = true;
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
if (wrapped && /^[^ \t][^:]*:[ \t]*$/.test(line))
|
|
13
|
+
wrapped = false;
|
|
14
|
+
if (wrapped) {
|
|
15
|
+
const match = line.match(/^ {2}([A-Za-z][A-Za-z0-9_-]*):/);
|
|
16
|
+
if (match && keepSlug(match[1]))
|
|
17
|
+
slugs.add(match[1]);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const match = line.match(/^([A-Za-z][A-Za-z0-9_-]*):/);
|
|
21
|
+
if (match && keepSlug(match[1]))
|
|
22
|
+
slugs.add(match[1]);
|
|
23
|
+
}
|
|
24
|
+
return [...slugs];
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=manifest-slugs.js.map
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type ClaudeLoginCheck } from "../../bot/claude-login.js";
|
|
2
|
+
import { type BotStatusFile } from "../../bot/status.js";
|
|
1
3
|
import { type NodeBinaryIo } from "../../node-binary.js";
|
|
2
4
|
import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
|
|
3
5
|
export declare const BOTS_FAMILY_ID = "bots";
|
|
@@ -7,7 +9,11 @@ export interface BotsDoctorDeps {
|
|
|
7
9
|
root: (home?: string) => string;
|
|
8
10
|
/** Filesystem view used to judge the startup agent's node path (tests). */
|
|
9
11
|
nodeIo?: NodeBinaryIo;
|
|
12
|
+
/** The Claude Code login read the way the HQ Fleet Runtime reads it (tests). */
|
|
13
|
+
claudeLogin?: () => ClaudeLoginCheck;
|
|
10
14
|
}
|
|
15
|
+
/** The last reported failure, unless a later turn worked. */
|
|
16
|
+
export declare function unresolvedLastFailure(status: BotStatusFile | null): BotStatusFile["lastFailure"] | null;
|
|
11
17
|
export declare const botsFamily: CheckFamily;
|
|
12
18
|
export declare function checkBots(_context: CheckContext, deps?: BotsDoctorDeps): CheckResult[];
|
|
13
19
|
//# sourceMappingURL=bots.d.ts.map
|