@indigoai-us/hq-cli 5.345.8 → 5.345.9
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 +4 -0
- package/dist/commands/lanes-message.js +100 -75
- package/dist/lib/daemon/heartbeat.d.ts +0 -4
- package/dist/lib/daemon/heartbeat.js +3 -3
- package/dist/lib/daemon/run.d.ts +1 -1
- package/dist/lib/daemon/run.js +11 -2
- package/dist/lib/doctor/checks/anywhere.d.ts +3 -4
- package/dist/lib/doctor/checks/anywhere.js +2 -2
- package/dist/lib/install/claude-targets.d.ts +0 -4
- package/dist/lib/install/claude-targets.js +3 -3
- package/dist/lib/install/codex-targets.d.ts +0 -9
- package/dist/lib/install/codex-targets.js +4 -4
- package/dist/lib/mesh/live/report-summary.d.ts +0 -10
- package/dist/lib/mesh/live/report-summary.js +5 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.9] — 2026-10-05
|
|
6
|
+
- Lane messages to exited sessions now queue durably when provider admission is busy, so delivery succeeds and reconcile can resume them later.
|
|
7
|
+
- Keep the current sync runner until an hq-cloud version matching the configured pin is available.
|
|
8
|
+
|
|
5
9
|
## [5.345.8] — 2026-10-05
|
|
6
10
|
- Prioritize due lead keep-alive retries and honor due next-check times ahead of later backoff deadlines behind an hq-flags gate.
|
|
7
11
|
- Drain bot turns before replacing a running bot after a daemon plan change.
|
|
@@ -754,6 +754,59 @@ export function createLaneMessageHandlers(deps) {
|
|
|
754
754
|
...(keepCurrentAccount && opts.model !== undefined ? { cliModel: opts.model } : {}),
|
|
755
755
|
});
|
|
756
756
|
}
|
|
757
|
+
const queueResumeMessage = (queuedReason, archiveBeforeWrite) => {
|
|
758
|
+
const sessionId = lane.provider_session_id;
|
|
759
|
+
if (!sessionId) {
|
|
760
|
+
throw new LanesError("no_provider_session", `Lane ${laneId} has no provider session id to queue this message`, {
|
|
761
|
+
lane_id: laneId,
|
|
762
|
+
provider: lane.provider,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
if (archiveBeforeWrite)
|
|
766
|
+
archiveResumeMessage(hqRoot, laneId, dropboxMessage);
|
|
767
|
+
const queued = casUpdateLane(hqRoot, laneId, (current) => {
|
|
768
|
+
if (current.version !== lane.version ||
|
|
769
|
+
current.state !== lane.state ||
|
|
770
|
+
current.provider_session_id !== sessionId) {
|
|
771
|
+
throw new LanesError("lane_admission_conflict", `Lane ${laneId} changed while its message was queued`, {
|
|
772
|
+
lane_id: laneId,
|
|
773
|
+
prepared_version: lane.version,
|
|
774
|
+
version: current.version,
|
|
775
|
+
state: current.state,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
return {
|
|
779
|
+
...current,
|
|
780
|
+
state: "queued",
|
|
781
|
+
queued_reason: queuedReason,
|
|
782
|
+
queued_details: undefined,
|
|
783
|
+
queued_at: at,
|
|
784
|
+
admission_reserved_at: undefined,
|
|
785
|
+
pending_resume: {
|
|
786
|
+
session_id: sessionId,
|
|
787
|
+
trigger: messageHistoryTrigger,
|
|
788
|
+
caller: ctx.env.HQ_LANE_ID?.trim() || "user",
|
|
789
|
+
text: deliveryText,
|
|
790
|
+
...(isMonitorEvent ? { monitor_event: true } : {}),
|
|
791
|
+
message_id: messageId,
|
|
792
|
+
compact: opts.compact !== false,
|
|
793
|
+
at,
|
|
794
|
+
},
|
|
795
|
+
};
|
|
796
|
+
});
|
|
797
|
+
queuedOnAdmission = true;
|
|
798
|
+
storySwitchDeliveryStarted = true;
|
|
799
|
+
deps.onLaneMessageQueued?.(laneId, "admission");
|
|
800
|
+
if (!archiveBeforeWrite) {
|
|
801
|
+
try {
|
|
802
|
+
archiveResumeMessage(hqRoot, laneId, dropboxMessage);
|
|
803
|
+
}
|
|
804
|
+
catch (archiveError) {
|
|
805
|
+
process.stderr.write(`Could not write the resume message audit copy for ${laneId}: ${archiveError instanceof Error ? archiveError.message : String(archiveError)}\n`);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return queued;
|
|
809
|
+
};
|
|
757
810
|
let questionCancellation = null;
|
|
758
811
|
if (move?.queued || queuedOnQuotaHold) {
|
|
759
812
|
// Parked at the target's cap: reconcile resumes the session there.
|
|
@@ -777,84 +830,56 @@ export function createLaneMessageHandlers(deps) {
|
|
|
777
830
|
// Exited worker with a recorded session: queue the message when capacity
|
|
778
831
|
// is occupied, otherwise start its next turn under the shared admission lock.
|
|
779
832
|
assertLaneRunsHereForAdmission(ctx, lane);
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
throw new LanesError("no_provider_session", `Lane ${laneId} has no provider session id to queue this message resume`, {
|
|
789
|
-
lane_id: laneId,
|
|
790
|
-
provider: lane.provider,
|
|
791
|
-
});
|
|
833
|
+
try {
|
|
834
|
+
return await withLaneAdmissionLock(hqRoot, lane.provider, async () => {
|
|
835
|
+
const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
|
|
836
|
+
if (admission.action === "refuse" && admission.error)
|
|
837
|
+
throw admission.error;
|
|
838
|
+
return withResumeHireAdmission(ctx, lane, () => {
|
|
839
|
+
if (admission.action === "queue") {
|
|
840
|
+
return queueResumeMessage(admission.reason ?? "throttle", true);
|
|
792
841
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
at,
|
|
819
|
-
},
|
|
820
|
-
};
|
|
842
|
+
const started = spawnLaneWorker(hqRoot, lane, {
|
|
843
|
+
env: ctx.env,
|
|
844
|
+
modelDeps: {
|
|
845
|
+
readFileSync: ctx.readFileSync,
|
|
846
|
+
probe: ctx.probeProvider,
|
|
847
|
+
env: ctx.env,
|
|
848
|
+
},
|
|
849
|
+
existsSync: ctx.existsSync,
|
|
850
|
+
spawnProcess: ctx.spawnProcess,
|
|
851
|
+
proveDetached: ctx.proveDetached,
|
|
852
|
+
killProcess: ctx.killProcess,
|
|
853
|
+
now: ctx.now,
|
|
854
|
+
resolveMachineIdentity: ctx.resolveMachineIdentity,
|
|
855
|
+
runCompaction: ctx.runCompaction,
|
|
856
|
+
locateTranscript: ctx.locateTranscript,
|
|
857
|
+
}, {
|
|
858
|
+
trigger: {
|
|
859
|
+
kind: "message",
|
|
860
|
+
text: deliveryText,
|
|
861
|
+
message_id: messageId,
|
|
862
|
+
...(isMonitorEvent ? { monitor_event: true } : {}),
|
|
863
|
+
...(isSystemNotice ? { system_notice: true } : {}),
|
|
864
|
+
},
|
|
865
|
+
compact: opts.compact !== false,
|
|
866
|
+
effort: resumeEffort ?? recordedResumeEffort(lane),
|
|
821
867
|
});
|
|
822
868
|
storySwitchDeliveryStarted = true;
|
|
823
|
-
deps.
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
now: ctx.now,
|
|
838
|
-
resolveMachineIdentity: ctx.resolveMachineIdentity,
|
|
839
|
-
runCompaction: ctx.runCompaction,
|
|
840
|
-
locateTranscript: ctx.locateTranscript,
|
|
841
|
-
}, {
|
|
842
|
-
trigger: {
|
|
843
|
-
kind: "message",
|
|
844
|
-
text: deliveryText,
|
|
845
|
-
message_id: messageId,
|
|
846
|
-
...(isMonitorEvent ? { monitor_event: true } : {}),
|
|
847
|
-
...(isSystemNotice ? { system_notice: true } : {}),
|
|
848
|
-
},
|
|
849
|
-
compact: opts.compact !== false,
|
|
850
|
-
effort: resumeEffort ?? recordedResumeEffort(lane),
|
|
851
|
-
});
|
|
852
|
-
storySwitchDeliveryStarted = true;
|
|
853
|
-
deps.onLaneWorkerStarted?.(laneId, "message");
|
|
854
|
-
archiveResumeMessage(hqRoot, laneId, dropboxMessage);
|
|
855
|
-
return started;
|
|
856
|
-
}, hireGrantContext);
|
|
857
|
-
}, ctx.sleep);
|
|
869
|
+
deps.onLaneWorkerStarted?.(laneId, "message");
|
|
870
|
+
archiveResumeMessage(hqRoot, laneId, dropboxMessage);
|
|
871
|
+
return started;
|
|
872
|
+
}, hireGrantContext);
|
|
873
|
+
}, ctx.sleep);
|
|
874
|
+
}
|
|
875
|
+
catch (error) {
|
|
876
|
+
const expectedLockPath = path.join(hqRoot, "workspace", "lanes-runs", ".admission-locks", `${lane.provider}-provider.lock`);
|
|
877
|
+
if (!(error instanceof LanesError) ||
|
|
878
|
+
error.error !== "store_lock_timeout" ||
|
|
879
|
+
error.details.path !== expectedLockPath)
|
|
880
|
+
throw error;
|
|
881
|
+
return queueResumeMessage(undefined, false);
|
|
882
|
+
}
|
|
858
883
|
},
|
|
859
884
|
}, mechanism);
|
|
860
885
|
if (dispatched.mechanism === "resume")
|
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
* from a stale one offline. A device with no beat for 5 minutes is
|
|
6
6
|
* "daemon stale".
|
|
7
7
|
*/
|
|
8
|
-
export declare const HEARTBEAT_INTERVAL_MS = 60000;
|
|
9
8
|
export declare const HEARTBEAT_STALE_MS: number;
|
|
10
|
-
export declare const HEARTBEAT_FILE = "hqd-heartbeat.json";
|
|
11
9
|
export declare const DAEMON_STALE_LABEL = "daemon stale";
|
|
12
10
|
export interface HeartbeatState {
|
|
13
11
|
at: string;
|
|
@@ -16,8 +14,6 @@ export interface HeartbeatState {
|
|
|
16
14
|
device: string;
|
|
17
15
|
}
|
|
18
16
|
export declare function heartbeatFile(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
19
|
-
/** Mesh session id for this device's daemon; only characters the spool accepts. */
|
|
20
|
-
export declare function heartbeatSessionId(hostname?: string): string;
|
|
21
17
|
export declare function readHeartbeat(file: string): HeartbeatState | null;
|
|
22
18
|
/** Presence label for a device given its last beat: "daemon stale" after 5 minutes of silence. */
|
|
23
19
|
export declare function daemonPresenceLabel(state: HeartbeatState | null, now?: Date): "daemon live" | typeof DAEMON_STALE_LABEL;
|
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
import * as fs from "node:fs";
|
|
9
9
|
import * as os from "node:os";
|
|
10
10
|
import * as path from "node:path";
|
|
11
|
-
|
|
11
|
+
const HEARTBEAT_INTERVAL_MS = 60_000;
|
|
12
12
|
export const HEARTBEAT_STALE_MS = 5 * 60_000;
|
|
13
|
-
|
|
13
|
+
const HEARTBEAT_FILE = "hqd-heartbeat.json";
|
|
14
14
|
export const DAEMON_STALE_LABEL = "daemon stale";
|
|
15
15
|
export function heartbeatFile(home = os.homedir(), env = process.env) {
|
|
16
16
|
const dir = env.HQ_REGISTRY_DIR?.trim() ? path.resolve(env.HQ_REGISTRY_DIR.trim()) : path.join(home, ".hq");
|
|
17
17
|
return path.join(dir, HEARTBEAT_FILE);
|
|
18
18
|
}
|
|
19
19
|
/** Mesh session id for this device's daemon; only characters the spool accepts. */
|
|
20
|
-
|
|
20
|
+
function heartbeatSessionId(hostname = os.hostname()) {
|
|
21
21
|
return `hqd-${hostname.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").slice(0, 48) || "device"}`;
|
|
22
22
|
}
|
|
23
23
|
export function readHeartbeat(file) {
|
package/dist/lib/daemon/run.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ export type SyncRunnerSource = {
|
|
|
62
62
|
export declare function syncRunnerSourceForPlan(stagedRunnerPath: string | null, hqCloud: PickedHqCloud | null, legacyBundledRunnerPath?: string | null, staged?: {
|
|
63
63
|
version: string | null;
|
|
64
64
|
pinnedVersion?: string | null;
|
|
65
|
-
}): SyncRunnerSource;
|
|
65
|
+
}, currentSyncRunnerSource?: SyncRunnerSource | null): SyncRunnerSource;
|
|
66
66
|
/** Whether every executable in the first service plan remains usable after the CLI package is replaced. */
|
|
67
67
|
export declare function daemonPlanCanReleasePackageUseLease(plans: readonly UnitPlan[], replaceablePackageRoots: readonly string[], stagingFailed: boolean, hqBootstrapReady?: boolean): boolean;
|
|
68
68
|
export declare function daemonHqChildCommand(nodeBinary: string, hqBinary: string, args: readonly string[], bootstrapPath: string | null, packagePrefix: string | null): [string, string[]];
|
package/dist/lib/daemon/run.js
CHANGED
|
@@ -76,13 +76,20 @@ export function handoffForPackageUpdateRequest(requested, restartDaemon) {
|
|
|
76
76
|
const LEGACY_PROBE_TTL_MS = 60_000;
|
|
77
77
|
/** A flag read that has not answered by then counts as off. */
|
|
78
78
|
const HANDOFF_FLAG_TIMEOUT_MS = 10_000;
|
|
79
|
-
export function syncRunnerSourceForPlan(stagedRunnerPath, hqCloud, legacyBundledRunnerPath = null, staged = { version: null }) {
|
|
79
|
+
export function syncRunnerSourceForPlan(stagedRunnerPath, hqCloud, legacyBundledRunnerPath = null, staged = { version: null }, currentSyncRunnerSource = null) {
|
|
80
80
|
// A managed copy lives in the daemon directory, so it survives a CLI
|
|
81
81
|
// replacement. Before lease release pickHqCloud returns it only when it is
|
|
82
82
|
// pinned or newer than the bundled copy; after release it is the only
|
|
83
83
|
// eligible package, so it must not displace a newer staged bundle unless it
|
|
84
84
|
// is the pinned version. The staged bundle is built from the bundled copy and
|
|
85
85
|
// otherwise only stands in for that copy (hq-cli#1547).
|
|
86
|
+
const pin = staged.pinnedVersion;
|
|
87
|
+
const hasRunnerAtOrAbovePin = pin != null &&
|
|
88
|
+
((staged.version !== null && !hqCloudVersionOlder(staged.version, pin)) ||
|
|
89
|
+
(hqCloud !== null && !hqCloudVersionOlder(hqCloud.version, pin)));
|
|
90
|
+
if (pin && !hasRunnerAtOrAbovePin && currentSyncRunnerSource?.runnerPath) {
|
|
91
|
+
return currentSyncRunnerSource;
|
|
92
|
+
}
|
|
86
93
|
const managedYieldsToStaged = stagedRunnerPath !== null &&
|
|
87
94
|
hqCloud?.source === "managed" &&
|
|
88
95
|
hqCloud.version !== staged.pinnedVersion &&
|
|
@@ -373,6 +380,7 @@ export async function runDaemon(opts) {
|
|
|
373
380
|
let hqChildBootstrapStageFailed = false;
|
|
374
381
|
let legacySyncRunnerPathChecked = false;
|
|
375
382
|
let legacySyncRunnerPath = null;
|
|
383
|
+
let lastPlannedSyncRunnerSource = null;
|
|
376
384
|
let packageUseLeaseReleased = false;
|
|
377
385
|
let hqCloudVersionBeforeRelease = null;
|
|
378
386
|
let startupPlanOpen = true;
|
|
@@ -570,7 +578,8 @@ export async function runDaemon(opts) {
|
|
|
570
578
|
legacySyncRunnerPath = resolveSyncRunnerPath();
|
|
571
579
|
legacySyncRunnerPathChecked = true;
|
|
572
580
|
}
|
|
573
|
-
const syncSource = syncRunnerSourceForPlan(staged?.runnerPath ?? null, hqCloud, packageUseLeaseReleased ? null : legacySyncRunnerPath, { version: staged?.metadata.version ?? null, pinnedVersion: config.hqCloudVersion ?? null });
|
|
581
|
+
const syncSource = syncRunnerSourceForPlan(staged?.runnerPath ?? null, hqCloud, packageUseLeaseReleased ? null : legacySyncRunnerPath, { version: staged?.metadata.version ?? null, pinnedVersion: config.hqCloudVersion ?? null }, lastPlannedSyncRunnerSource);
|
|
582
|
+
lastPlannedSyncRunnerSource = syncSource.runnerPath ? syncSource : null;
|
|
574
583
|
if (!syncSource.runnerPath && !warnedMissingSyncRunner) {
|
|
575
584
|
warnedMissingSyncRunner = true;
|
|
576
585
|
log("warn", "the hq-cloud sync runner was not found in this CLI install; sync is unavailable");
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
* on this machine the family reports a single NA line.
|
|
10
10
|
*/
|
|
11
11
|
import { type HqdHealthResult } from "../../daemon/protocol.js";
|
|
12
|
-
import type {
|
|
13
|
-
export declare const ANYWHERE_FAMILY_ID = "anywhere";
|
|
12
|
+
import type { CheckFamily } from "../types.js";
|
|
14
13
|
export declare const REMEDIATION: {
|
|
15
14
|
readonly installClaude: "hq install --global --runtime claude";
|
|
16
15
|
readonly installCodex: "hq install --global --runtime codex";
|
|
@@ -18,7 +17,7 @@ export declare const REMEDIATION: {
|
|
|
18
17
|
readonly daemonRestart: "hq daemon restart --daemon";
|
|
19
18
|
readonly link: "hq link <repo-dir> <company>";
|
|
20
19
|
};
|
|
21
|
-
|
|
20
|
+
interface DaemonServiceState {
|
|
22
21
|
installed: boolean;
|
|
23
22
|
loaded: boolean;
|
|
24
23
|
detail: string;
|
|
@@ -39,5 +38,5 @@ export interface AnywhereDeps {
|
|
|
39
38
|
}
|
|
40
39
|
export declare function createAnywhereFamily(deps?: AnywhereDeps): CheckFamily;
|
|
41
40
|
export declare const anywhereFamily: CheckFamily;
|
|
42
|
-
export
|
|
41
|
+
export {};
|
|
43
42
|
//# sourceMappingURL=anywhere.d.ts.map
|
|
@@ -24,7 +24,7 @@ import { DAEMON_LAUNCHD_LABEL, DAEMON_SYSTEMD_UNIT, DAEMON_WINDOWS_TASK } from "
|
|
|
24
24
|
import { hqdSocketPath } from "../../daemon/protocol.js";
|
|
25
25
|
import { readRegistry, registryDir, registryFile } from "../../registry/registry.js";
|
|
26
26
|
import { anywhereRuntimeEnabled } from "../../../commands/anywhere-runtime-flag.js";
|
|
27
|
-
|
|
27
|
+
const ANYWHERE_FAMILY_ID = "anywhere";
|
|
28
28
|
export const REMEDIATION = {
|
|
29
29
|
installClaude: "hq install --global --runtime claude",
|
|
30
30
|
installCodex: "hq install --global --runtime codex",
|
|
@@ -39,7 +39,7 @@ export const anywhereFamily = createAnywhereFamily();
|
|
|
39
39
|
function result(status, checkId, message, remediation, target) {
|
|
40
40
|
return { status, checkId: `anywhere.${checkId}`, message, ...(remediation ? { remediation } : {}), ...(target ? { target } : {}) };
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
async function runAnywhereChecks(context, deps = {}) {
|
|
43
43
|
const home = deps.home ?? os.homedir();
|
|
44
44
|
const env = deps.env ?? process.env;
|
|
45
45
|
const socketPath = hqdSocketPath(home, env);
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/** Events master-hook.sh is registered for at user scope. */
|
|
2
|
-
export declare const GLOBAL_HOOK_EVENTS: readonly ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"];
|
|
3
1
|
/** Name of the HQ MCP server entry in ~/.claude.json. */
|
|
4
2
|
export declare const HQ_MCP_SERVER_NAME = "hq";
|
|
5
3
|
/** `_hqPack` provenance stamped on the HQ MCP server entry. */
|
|
@@ -9,7 +7,6 @@ export declare const MCP_SUBPROCESS_PATH = "/opt/homebrew/bin:/usr/local/bin:/us
|
|
|
9
7
|
/** Prefix for skill symlinks under ~/.claude/skills. */
|
|
10
8
|
export declare const SKILL_LINK_PREFIX = "hq-";
|
|
11
9
|
export declare const CLAUDE_MD_BEGIN = "<!-- hq-anywhere:begin (managed by `hq install --global`; remove with `hq uninstall --global`) -->";
|
|
12
|
-
export declare const CLAUDE_MD_END = "<!-- hq-anywhere:end -->";
|
|
13
10
|
export interface ClaudeTargets {
|
|
14
11
|
hqPointer: string;
|
|
15
12
|
claudeMd: string;
|
|
@@ -29,7 +26,6 @@ export declare function charterPath(hqRoot: string): string;
|
|
|
29
26
|
export declare function hqSkillsSource(hqRoot: string): string;
|
|
30
27
|
/** The exact hook command registered for one event (absolute path, quoted). */
|
|
31
28
|
export declare function hookCommand(hqRoot: string, event: string, target?: HookTarget): string;
|
|
32
|
-
export declare function claudeMdBlock(hqRoot: string): string;
|
|
33
29
|
/** Append the block (replacing any prior HQ block). `null` input = file absent. */
|
|
34
30
|
export declare function addClaudeMdBlock(current: string | null, hqRoot: string): string;
|
|
35
31
|
/** Strip the HQ block, plus the separator `addClaudeMdBlock` put before it. */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
/** Events master-hook.sh is registered for at user scope. */
|
|
9
|
-
|
|
9
|
+
const GLOBAL_HOOK_EVENTS = [
|
|
10
10
|
'SessionStart',
|
|
11
11
|
'UserPromptSubmit',
|
|
12
12
|
'PreToolUse',
|
|
@@ -22,7 +22,7 @@ export const MCP_SUBPROCESS_PATH = '/opt/homebrew/bin:/usr/local/bin:/usr/bin:/b
|
|
|
22
22
|
/** Prefix for skill symlinks under ~/.claude/skills. */
|
|
23
23
|
export const SKILL_LINK_PREFIX = 'hq-';
|
|
24
24
|
export const CLAUDE_MD_BEGIN = '<!-- hq-anywhere:begin (managed by `hq install --global`; remove with `hq uninstall --global`) -->';
|
|
25
|
-
|
|
25
|
+
const CLAUDE_MD_END = '<!-- hq-anywhere:end -->';
|
|
26
26
|
export function claudeTargets(home) {
|
|
27
27
|
return {
|
|
28
28
|
hqPointer: path.join(home, '.hq', 'root'),
|
|
@@ -61,7 +61,7 @@ function shellQuote(value) {
|
|
|
61
61
|
// ---------------------------------------------------------------------------
|
|
62
62
|
// CLAUDE.md block.
|
|
63
63
|
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
64
|
+
function claudeMdBlock(hqRoot) {
|
|
65
65
|
return `${CLAUDE_MD_BEGIN}\n@${charterPath(hqRoot)}\n${CLAUDE_MD_END}\n`;
|
|
66
66
|
}
|
|
67
67
|
/** Append the block (replacing any prior HQ block). `null` input = file absent. */
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { type HookTarget } from './claude-targets.js';
|
|
2
|
-
/** Events the Codex hook adapter is registered for at user scope. */
|
|
3
|
-
export declare const CODEX_HOOK_EVENTS: readonly ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop", "SessionEnd"];
|
|
4
2
|
export declare const AGENTS_MD_BEGIN = "<!-- hq-anywhere:begin (managed by `hq install --global --runtime codex`; remove with `hq uninstall --global --runtime codex`) -->";
|
|
5
|
-
export declare const AGENTS_MD_END = "<!-- hq-anywhere:end -->";
|
|
6
3
|
/** Name of the legacy ad-hoc skills mirror (~/.codex/skills/hq). */
|
|
7
4
|
export declare const LEGACY_SKILLS_NAME = "hq";
|
|
8
5
|
/** A directory carrying this file is HQ-owned and may be replaced. */
|
|
@@ -21,12 +18,6 @@ export declare function codexTargets(home: string): CodexTargets;
|
|
|
21
18
|
export declare function codexAdapterPath(hqRoot: string): string;
|
|
22
19
|
/** HQ's Codex charter: AGENTS.md at the HQ root, else the Claude charter. */
|
|
23
20
|
export declare function codexCharterSource(hqRoot: string): string;
|
|
24
|
-
/**
|
|
25
|
-
* Hook command for every event. The adapter reads the event from
|
|
26
|
-
* `hook_event_name` and falls back to its own $PWD when Codex sends no `cwd`.
|
|
27
|
-
*/
|
|
28
|
-
export declare function codexHookCommand(hqRoot: string, target?: HookTarget): string;
|
|
29
|
-
export declare function agentsMdBlock(charter: string): string;
|
|
30
21
|
/** Append the block (replacing any prior HQ block). `null` input = file absent. */
|
|
31
22
|
export declare function addAgentsMdBlock(current: string | null, charter: string): string;
|
|
32
23
|
/** Strip the HQ block, plus the separator `addAgentsMdBlock` put before it. */
|
|
@@ -13,7 +13,7 @@ import * as fs from 'fs';
|
|
|
13
13
|
import * as path from 'path';
|
|
14
14
|
import { shimPath } from './claude-targets.js';
|
|
15
15
|
/** Events the Codex hook adapter is registered for at user scope. */
|
|
16
|
-
|
|
16
|
+
const CODEX_HOOK_EVENTS = [
|
|
17
17
|
'SessionStart',
|
|
18
18
|
'UserPromptSubmit',
|
|
19
19
|
'PreToolUse',
|
|
@@ -22,7 +22,7 @@ export const CODEX_HOOK_EVENTS = [
|
|
|
22
22
|
'SessionEnd',
|
|
23
23
|
];
|
|
24
24
|
export const AGENTS_MD_BEGIN = '<!-- hq-anywhere:begin (managed by `hq install --global --runtime codex`; remove with `hq uninstall --global --runtime codex`) -->';
|
|
25
|
-
|
|
25
|
+
const AGENTS_MD_END = '<!-- hq-anywhere:end -->';
|
|
26
26
|
/** Name of the legacy ad-hoc skills mirror (~/.codex/skills/hq). */
|
|
27
27
|
export const LEGACY_SKILLS_NAME = 'hq';
|
|
28
28
|
/** A directory carrying this file is HQ-owned and may be replaced. */
|
|
@@ -52,7 +52,7 @@ export function codexCharterSource(hqRoot) {
|
|
|
52
52
|
* Hook command for every event. The adapter reads the event from
|
|
53
53
|
* `hook_event_name` and falls back to its own $PWD when Codex sends no `cwd`.
|
|
54
54
|
*/
|
|
55
|
-
|
|
55
|
+
function codexHookCommand(hqRoot, target = 'master') {
|
|
56
56
|
if (target === 'shim')
|
|
57
57
|
return `/bin/sh ${JSON.stringify(shimPath(hqRoot))} --runtime codex`;
|
|
58
58
|
return `/bin/bash ${JSON.stringify(codexAdapterPath(hqRoot))}`;
|
|
@@ -60,7 +60,7 @@ export function codexHookCommand(hqRoot, target = 'master') {
|
|
|
60
60
|
// ---------------------------------------------------------------------------
|
|
61
61
|
// AGENTS.md block.
|
|
62
62
|
// ---------------------------------------------------------------------------
|
|
63
|
-
|
|
63
|
+
function agentsMdBlock(charter) {
|
|
64
64
|
const body = charter.endsWith('\n') ? charter : `${charter}\n`;
|
|
65
65
|
return `${AGENTS_MD_BEGIN}\n${body}${AGENTS_MD_END}\n`;
|
|
66
66
|
}
|
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
* Built locally at the quiet trigger / SessionEnd flush. Never includes file
|
|
5
5
|
* contents. Tokens, secret-looking strings, and secrets-file paths are redacted.
|
|
6
6
|
*/
|
|
7
|
-
export declare const REPORT_SUMMARY_MAX_CHARS = 140;
|
|
8
|
-
export declare const REPORT_SUMMARY_SCHEMA_MAX = 280;
|
|
9
|
-
export declare const REPORT_TRANSCRIPT_TAIL_BYTES: number;
|
|
10
7
|
export interface SessionEndReportFields {
|
|
11
8
|
summary?: string;
|
|
12
9
|
repo?: string;
|
|
@@ -26,19 +23,12 @@ export interface BuildSessionEndReportFieldsInput {
|
|
|
26
23
|
export declare function redactReportSummary(text: string): string;
|
|
27
24
|
export declare function firstSentence(text: string, max?: number): string;
|
|
28
25
|
export declare function extractLastAssistantText(jsonl: string): string | undefined;
|
|
29
|
-
export declare function readTranscriptTail(transcriptPath: string, maxBytes?: number): string | undefined;
|
|
30
26
|
export declare function collectGitContext(cwd: string, git?: (cwd: string, args: string[]) => string | undefined): {
|
|
31
27
|
repo?: string;
|
|
32
28
|
gitBranch?: string;
|
|
33
29
|
};
|
|
34
30
|
export declare function extractPrUrls(text: string, max?: number): string[];
|
|
35
31
|
export declare function claudeTranscriptPathFor(sessionId: string, cwd: string, home?: string): string;
|
|
36
|
-
export declare function resolveTranscriptPath(input: {
|
|
37
|
-
transcriptPath?: string;
|
|
38
|
-
sessionId?: string;
|
|
39
|
-
cwd?: string;
|
|
40
|
-
home?: string;
|
|
41
|
-
}): string | undefined;
|
|
42
32
|
export declare function buildSessionEndReportFields(input: BuildSessionEndReportFieldsInput): SessionEndReportFields;
|
|
43
33
|
/** Merge report fields onto a session_end event object (no-op for other kinds). */
|
|
44
34
|
export declare function enrichSessionEndEvent(event: Record<string, unknown>, extra?: Pick<BuildSessionEndReportFieldsInput, "transcriptPath" | "home" | "git" | "readTranscript">): Record<string, unknown>;
|
|
@@ -9,9 +9,9 @@ import * as fs from "node:fs";
|
|
|
9
9
|
import * as os from "node:os";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { SECRET_RE, SECRETS_PATH_RE } from "../../lanes/text-scan.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const REPORT_SUMMARY_MAX_CHARS = 140;
|
|
13
|
+
const REPORT_SUMMARY_SCHEMA_MAX = 280;
|
|
14
|
+
const REPORT_TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
15
15
|
const PR_URL_RE = /https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/\d+/g;
|
|
16
16
|
export function redactReportSummary(text) {
|
|
17
17
|
let out = text.replace(SECRET_RE, "[redacted]");
|
|
@@ -101,7 +101,7 @@ export function extractLastAssistantText(jsonl) {
|
|
|
101
101
|
}
|
|
102
102
|
return last;
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
function readTranscriptTail(transcriptPath, maxBytes = REPORT_TRANSCRIPT_TAIL_BYTES) {
|
|
105
105
|
try {
|
|
106
106
|
const st = fs.statSync(transcriptPath);
|
|
107
107
|
if (!st.isFile() || st.size <= 0)
|
|
@@ -171,7 +171,7 @@ export function claudeTranscriptPathFor(sessionId, cwd, home = os.homedir()) {
|
|
|
171
171
|
const encoded = cwd.startsWith("/") ? cwd.replace(/\//g, "-") : `-${cwd.replace(/\//g, "-")}`;
|
|
172
172
|
return path.join(home, ".claude", "projects", encoded, `${sessionId}.jsonl`);
|
|
173
173
|
}
|
|
174
|
-
|
|
174
|
+
function resolveTranscriptPath(input) {
|
|
175
175
|
const explicit = input.transcriptPath?.trim();
|
|
176
176
|
if (explicit)
|
|
177
177
|
return explicit;
|