@indigoai-us/hq-cli 5.345.198 → 5.345.200
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 +71 -0
- package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +6 -4
- package/dist/command-catalog.generated.d.ts +150 -6
- package/dist/command-catalog.generated.js +182 -6
- 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-runtime.d.ts +35 -0
- package/dist/commands/bot-runtime.js +115 -0
- package/dist/commands/bot.d.ts +11 -0
- package/dist/commands/bot.js +72 -11
- package/dist/commands/creators.js +2 -1
- 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/library-remove.js +2 -1
- package/dist/commands/library.js +5 -4
- package/dist/commands/meetings.js +1 -21
- package/dist/commands/mesh.d.ts +0 -7
- package/dist/commands/mesh.js +5 -1
- package/dist/commands/signals-run.d.ts +2 -0
- package/dist/commands/signals-run.js +6 -1
- package/dist/commands/sync-held.js +2 -2
- 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/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-download.d.ts +27 -0
- package/dist/lib/bot/fleet-download.js +162 -0
- package/dist/lib/bot/fleet-local.d.ts +51 -2
- package/dist/lib/bot/fleet-local.js +179 -76
- package/dist/lib/bot/fleet-python.d.ts +2 -0
- package/dist/lib/bot/fleet-python.js +29 -0
- 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 +100 -4
- package/dist/lib/bot/status.d.ts +21 -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/mesh-events.js +3 -1
- 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/api.js +2 -1
- package/dist/lib/mesh/live/daemon/doctor.js +6 -7
- package/dist/lib/mesh/live/daemon/run.d.ts +0 -10
- package/dist/lib/mesh/live/daemon/run.js +3 -0
- package/dist/lib/mesh/live/emit.d.ts +2 -11
- package/dist/lib/mesh/live/emit.js +20 -2
- package/dist/lib/mesh/live/flush.d.ts +2 -0
- package/dist/lib/mesh/live/flush.js +34 -3
- package/dist/lib/mesh/live/reporting-privacy.d.ts +7 -0
- package/dist/lib/mesh/live/reporting-privacy.js +47 -0
- 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 +23 -2
- package/dist/lib/signals/steps/extract.js +142 -9
- package/dist/lib/signals/steps/types.d.ts +1 -0
- package/dist/lib/signals/write-guard.js +7 -0
- package/dist/lib/work-context/company.d.ts +2 -3
- package/dist/lib/work-context/company.js +4 -22
- package/dist/secrets-env-action.js +5 -2
- package/dist/utils/cognito-callback-port.d.ts +17 -0
- package/dist/utils/cognito-callback-port.js +56 -0
- package/dist/utils/cognito-config.js +7 -3
- package/dist/utils/cognito-session.d.ts +7 -6
- package/dist/utils/cognito-session.js +19 -14
- package/dist/utils/interactive-caller.d.ts +23 -0
- package/dist/utils/interactive-caller.js +80 -0
- package/dist/utils/login-browser.js +5 -2
- package/dist/utils/resident-package-preload.js +4 -0
- package/package.json +7 -7
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -1541,6 +1541,16 @@ function recoverRawJsonArrayField(rawText, field) {
|
|
|
1541
1541
|
}
|
|
1542
1542
|
return undefined;
|
|
1543
1543
|
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Repair-turn guidance for the optional outcome block. Every repair prompt
|
|
1546
|
+
* names the block, its three states, and the consistency rules so a model
|
|
1547
|
+
* that wrote a malformed outcome can fix it in the single repair turn.
|
|
1548
|
+
*/
|
|
1549
|
+
export const LANE_OUTCOME_REPAIR_GUIDANCE = "Optional outcome block: if you include \"outcome\", it is an object with exactly the keys state, outcome, gathered, next_steps, and optional decision_id. " +
|
|
1550
|
+
"state is one of done, needs_decision, partial. outcome is a non-empty string; gathered is an array of {label, text, ref?}; next_steps is an array of non-empty strings. " +
|
|
1551
|
+
"state needs_decision requires a non-empty outcome.decision_id or a non-empty questions[] (with decision ask). " +
|
|
1552
|
+
"state partial requires at least one next_steps entry. state done must not be combined with decision blocked. " +
|
|
1553
|
+
"If you cannot satisfy these rules, remove the outcome block; the envelope is valid without it.";
|
|
1544
1554
|
export function envelopeRepairPrompt(input) {
|
|
1545
1555
|
const maxRawTextChars = 24_000;
|
|
1546
1556
|
const rawText = input.rawText.length <= maxRawTextChars
|
|
@@ -1553,6 +1563,7 @@ export function envelopeRepairPrompt(input) {
|
|
|
1553
1563
|
"Printing an envelope in the conversation or writing it to last-message.txt is not enough; write the JSON object directly to the expected file.",
|
|
1554
1564
|
"If the task is unfinished, write decision: done, story_complete: false, and set story_status and story_complete_reason to describe the work that remains. The repair turn itself is never a reason for blocked.",
|
|
1555
1565
|
"This repair turn only writes the missing envelope; do not resume the task.",
|
|
1566
|
+
LANE_OUTCOME_REPAIR_GUIDANCE,
|
|
1556
1567
|
"Schema:",
|
|
1557
1568
|
JSON.stringify(LANE_ENVELOPE_JSON_SCHEMA, null, 2),
|
|
1558
1569
|
`Write the corrected JSON object to ${input.envelopePath}, overwriting it. ` +
|
|
@@ -1564,6 +1575,7 @@ export function envelopeRepairPrompt(input) {
|
|
|
1564
1575
|
`The file you wrote at ${input.envelopePath} is not a valid hq-lane-envelope.v1. This is the single repair turn: rewrite that file so it validates, and do nothing else.`,
|
|
1565
1576
|
`Parser error: ${input.error}`,
|
|
1566
1577
|
"For an ask question, include non-empty id, question, and context fields.",
|
|
1578
|
+
LANE_OUTCOME_REPAIR_GUIDANCE,
|
|
1567
1579
|
"Schema:", JSON.stringify(LANE_ENVELOPE_JSON_SCHEMA, null, 2),
|
|
1568
1580
|
"Your previous output was:", rawText,
|
|
1569
1581
|
`Write the corrected JSON object to ${input.envelopePath}, overwriting it. Output only that JSON. Do not add keys the schema does not name, and do not resume the task.`,
|
|
@@ -1645,6 +1657,7 @@ export function envelopeRepairPrompt(input) {
|
|
|
1645
1657
|
? "This is a JSON syntax error. Fix the exact reported line and column and remove the offending token or delimiter. Do not add trailing commas."
|
|
1646
1658
|
: "This is a schema validation error. Correct the named field path and use only the allowed value(s) stated in the error.",
|
|
1647
1659
|
"Keep every supplied value that is already valid. Do not invent next_check_at; it is optional. For decision=ask include non-empty id, question, and context fields; for decision=blocked include blocked_reason.",
|
|
1660
|
+
LANE_OUTCOME_REPAIR_GUIDANCE,
|
|
1648
1661
|
"Minimal valid envelope shape (retain your original decision and values when valid):",
|
|
1649
1662
|
JSON.stringify(minimalEnvelope, null, 2),
|
|
1650
1663
|
"Schema:",
|
|
@@ -2675,6 +2688,10 @@ export function spawnLaneWorker(hqRoot, lane, deps = {}, launch = {}) {
|
|
|
2675
2688
|
: undefined;
|
|
2676
2689
|
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: remoteControlHome ?? resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined, workerPolicyDir, lane.registry_worker, undefined, laneWorkerSessionEnv(existing, plan.mode === "resume" ? plan.session_id : remoteSessionId));
|
|
2677
2690
|
const laneTurnId = randomUUID();
|
|
2691
|
+
if (lane.signal_link)
|
|
2692
|
+
childEnv.HQ_LANE_SIGNAL_LINK = lane.signal_link;
|
|
2693
|
+
else
|
|
2694
|
+
delete childEnv.HQ_LANE_SIGNAL_LINK;
|
|
2678
2695
|
childEnv.HQ_LANE_TURN_ID = laneTurnId;
|
|
2679
2696
|
const fingerprint = (deps.buildFingerprint ?? laneStartFingerprint)(hqRoot, childEnv);
|
|
2680
2697
|
const model = resolveLaneModel({
|
|
@@ -3322,6 +3339,10 @@ export function spawnEnvelopeRepair(hqRoot, lane, input, deps = {}) {
|
|
|
3322
3339
|
? providerAutoCompactTokens(config.providers.claude)
|
|
3323
3340
|
: undefined;
|
|
3324
3341
|
const childEnv = workerEnv(env, { envVar: resolved.envVar, home: resolved.home }, lane.lane_id, hqRoot, autoCompactTokens, lane.mesh_company ?? lane.company ?? undefined, workerPolicyDir, lane.registry_worker, undefined, laneWorkerSessionEnv(lane, mode === "resume" && sessionId !== null ? sessionId : undefined));
|
|
3342
|
+
if (lane.signal_link)
|
|
3343
|
+
childEnv.HQ_LANE_SIGNAL_LINK = lane.signal_link;
|
|
3344
|
+
else
|
|
3345
|
+
delete childEnv.HQ_LANE_SIGNAL_LINK;
|
|
3325
3346
|
const accountCfg = config.providers[lane.provider].accounts[resolved.id];
|
|
3326
3347
|
const model = resolveLaneModel({
|
|
3327
3348
|
cliModel: pinnedModel,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { type OutpostIdentity } from "./admission-host.js";
|
|
3
3
|
import { type AttributionHistoryEntry, type LaneRecord, type LaneWriter, type QuestionRecord, type RoutingHistoryEntry, type SeniorHistoryEntry } from "./types.js";
|
|
4
|
+
/** Signal identity is company-scoped and survives terminal lane states. */
|
|
5
|
+
export declare function findLaneBySignal(company: string, signalId: string, hqRoot?: string): LaneRecord | undefined;
|
|
6
|
+
export declare function listLanesBySource(company: string, sourceRef: string, hqRoot?: string): LaneRecord[];
|
|
4
7
|
export declare const LANES_STORE_REL: string;
|
|
5
8
|
export declare const LANE_RECORDS_DIR = "lanes";
|
|
6
9
|
export declare const QUESTION_RECORDS_DIR = "questions";
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -19,8 +19,52 @@ import { readOutpostIdentity } from "./admission-host.js";
|
|
|
19
19
|
import { LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesError, isLaneVersionConflict, } from "./errors.js";
|
|
20
20
|
import { HISTORY_CAP, HOST_KINDS, LANE_STORY_STATUSES, LANE_SCHEMA_VERSION, QUESTION_SCHEMA_VERSION, SENIOR_KINDS, isHostKind, isLaneProvider, isQueuedReason, isLaneState, isQuestionStatus, isSeniorKind, isTerminalLaneState, parseMajorSchema, REQUIRED_LANE_RECORD_FIELDS, LANE_FIELD_WRITERS, LANE_RUNNERS, LANE_WRITERS, } from "./types.js";
|
|
21
21
|
import { laneStories } from "./stories.js";
|
|
22
|
+
import { parseLaneOutcomeShape } from "./outcome.js";
|
|
22
23
|
import { laneStoryLedgerEnabled, syncLaneToProjectStoryLedger, validateProjectStories, } from "./story-ledger.js";
|
|
23
24
|
import { liveOwnerMaxMs, reclaimStaleLock } from "./stale-lock.js";
|
|
25
|
+
/** Signal identity is company-scoped and survives terminal lane states. */
|
|
26
|
+
export function findLaneBySignal(company, signalId, hqRoot = resolveStoreHqRoot()) {
|
|
27
|
+
return readAllLanes(hqRoot).find(lane => lane.company?.slug === company && lane.signal?.signal_id === signalId);
|
|
28
|
+
}
|
|
29
|
+
export function listLanesBySource(company, sourceRef, hqRoot = resolveStoreHqRoot()) {
|
|
30
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(company))
|
|
31
|
+
throw new LaneSchemaError("Invalid company slug");
|
|
32
|
+
const lanes = readAllLanes(hqRoot).filter(lane => lane.company?.slug === company && lane.signal?.source_ref === sourceRef);
|
|
33
|
+
if (!lanes.length)
|
|
34
|
+
return lanes;
|
|
35
|
+
// Daily indexes preserve extraction order, which can differ from dispatch order.
|
|
36
|
+
const index = path.join(hqRoot, "companies", company, "signals", "_index");
|
|
37
|
+
const order = new Map();
|
|
38
|
+
let files;
|
|
39
|
+
try {
|
|
40
|
+
files = fs.readdirSync(index).filter(name => name.endsWith(".json")).sort();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code !== "ENOENT")
|
|
44
|
+
throw error;
|
|
45
|
+
files = [];
|
|
46
|
+
}
|
|
47
|
+
if (files.length) {
|
|
48
|
+
const expected = path.join(fs.realpathSync(hqRoot), "companies", company, "signals", "_index");
|
|
49
|
+
if (fs.realpathSync(index) !== expected)
|
|
50
|
+
throw new LaneSchemaError("Signal index must stay within this company");
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
const filePath = path.join(index, file);
|
|
53
|
+
if (fs.realpathSync(filePath) !== path.join(expected, file))
|
|
54
|
+
throw new LaneSchemaError("Signal index file must stay within this company");
|
|
55
|
+
const rows = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
56
|
+
if (!Array.isArray(rows))
|
|
57
|
+
throw new LaneSchemaError("Signal index must contain an array");
|
|
58
|
+
for (const row of rows) {
|
|
59
|
+
if (row && typeof row === "object" && row.source_ref === sourceRef && typeof row.signal_id === "string" && !order.has(row.signal_id)) {
|
|
60
|
+
order.set(row.signal_id, order.size);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return lanes.sort((a, b) => (order.get(a.signal.signal_id) ?? Number.MAX_SAFE_INTEGER) - (order.get(b.signal.signal_id) ?? Number.MAX_SAFE_INTEGER)
|
|
66
|
+
|| a.created_at.localeCompare(b.created_at) || a.lane_id.localeCompare(b.lane_id));
|
|
67
|
+
}
|
|
24
68
|
export const LANES_STORE_REL = path.join("workspace", "lanes");
|
|
25
69
|
export const LANE_RECORDS_DIR = "lanes";
|
|
26
70
|
export const QUESTION_RECORDS_DIR = "questions";
|
|
@@ -451,6 +495,17 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
451
495
|
const schema_version = requireString(raw.schema_version, "schema_version");
|
|
452
496
|
parseMajorSchema(schema_version, "hq-lane", 1);
|
|
453
497
|
requireString(raw.lane_id, "lane_id");
|
|
498
|
+
if (raw.signal !== undefined) {
|
|
499
|
+
if (!raw.signal || typeof raw.signal !== "object" || Array.isArray(raw.signal)) {
|
|
500
|
+
throw new LaneSchemaError("signal must be an object", { field: "signal" });
|
|
501
|
+
}
|
|
502
|
+
const signal = raw.signal;
|
|
503
|
+
for (const field of ["signal_id", "type", "source_ref", "title"])
|
|
504
|
+
requireString(signal[field], "signal." + field);
|
|
505
|
+
}
|
|
506
|
+
if (raw.signal_link !== undefined && (typeof raw.signal_link !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(raw.signal_link) || !raw.signal)) {
|
|
507
|
+
throw new LaneSchemaError("signal_link requires a signal and a valid link id", { field: "signal_link" });
|
|
508
|
+
}
|
|
454
509
|
if (raw.remote_control !== undefined && (raw.remote_control !== true || raw.provider !== "claude")) {
|
|
455
510
|
throw new LaneSchemaError("remote_control requires a Claude lane", {
|
|
456
511
|
path: filePath,
|
|
@@ -666,6 +721,15 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
666
721
|
raw.envelope_validation_errors.some((error) => typeof error !== "string" || error.trim() === ""))) {
|
|
667
722
|
throw new LaneSchemaError("envelope_validation_errors must be a non-empty array of non-empty strings", { path: filePath, field: "envelope_validation_errors" });
|
|
668
723
|
}
|
|
724
|
+
if (raw.outcome !== undefined) {
|
|
725
|
+
try {
|
|
726
|
+
parseLaneOutcomeShape(raw.outcome);
|
|
727
|
+
}
|
|
728
|
+
catch (error) {
|
|
729
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
730
|
+
throw new LaneSchemaError(`outcome is malformed: ${message}`, { path: filePath, field: "outcome" });
|
|
731
|
+
}
|
|
732
|
+
}
|
|
669
733
|
if (raw.fallback_question_cancellation_failures !== undefined) {
|
|
670
734
|
if (!Array.isArray(raw.fallback_question_cancellation_failures)) {
|
|
671
735
|
throw new LaneSchemaError("fallback_question_cancellation_failures must be an array", {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LaneOutcome } from "./outcome.js";
|
|
1
2
|
import { type LaneRecord, type LaneState } from "./types.js";
|
|
2
3
|
export declare const LANE_STORY_LEDGER_FLAG = "lanes.story-ledger";
|
|
3
4
|
declare const STORY_STATES: readonly ["todo", "in_progress", "review", "waiting", "blocked", "done", "dropped"];
|
|
@@ -55,6 +56,8 @@ export interface LaneStatusSnapshot {
|
|
|
55
56
|
state: LaneState;
|
|
56
57
|
queued_reason: string | null;
|
|
57
58
|
fingerprint: string | null;
|
|
59
|
+
/** The last envelope's outcome block, or null when it carried none. */
|
|
60
|
+
outcome: LaneOutcome | null;
|
|
58
61
|
}>;
|
|
59
62
|
prs: Array<{
|
|
60
63
|
story_id: string;
|
|
@@ -813,6 +813,7 @@ export function buildLaneStatusSnapshot(input) {
|
|
|
813
813
|
state: lane.state,
|
|
814
814
|
queued_reason: lane.queued_reason ?? null,
|
|
815
815
|
fingerprint: lane.fingerprint ?? null,
|
|
816
|
+
outcome: lane.outcome ?? null,
|
|
816
817
|
}));
|
|
817
818
|
const projectPrs = [];
|
|
818
819
|
const blockers = [];
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* §4, §5.5, §6.2). Finite sets are string-literal unions plus `const` arrays
|
|
4
4
|
* so the command layer can validate flags explicitly.
|
|
5
5
|
*/
|
|
6
|
+
import { type LaneOutcome } from "./outcome.js";
|
|
6
7
|
export declare const LANE_SCHEMA_VERSION: "hq-lane.v1";
|
|
7
8
|
export declare const QUESTION_SCHEMA_VERSION: "hq-lane-question.v1";
|
|
8
9
|
export declare const ENVELOPE_SCHEMA_VERSION: "hq-lane-envelope.v1";
|
|
@@ -85,6 +86,76 @@ export declare const LANE_ENVELOPE_JSON_SCHEMA: {
|
|
|
85
86
|
readonly type: readonly ["string", "null"];
|
|
86
87
|
readonly description: "Optional worker fingerprint (hq fingerprint self); null when it could not be built. Older readers ignore it.";
|
|
87
88
|
};
|
|
89
|
+
readonly outcome: {
|
|
90
|
+
readonly type: "object";
|
|
91
|
+
readonly additionalProperties: false;
|
|
92
|
+
readonly required: readonly ["state", "outcome", "gathered", "next_steps"];
|
|
93
|
+
readonly properties: {
|
|
94
|
+
readonly state: {
|
|
95
|
+
readonly enum: readonly ["done", "needs_decision", "partial"];
|
|
96
|
+
};
|
|
97
|
+
readonly outcome: {
|
|
98
|
+
readonly type: "string";
|
|
99
|
+
readonly minLength: 1;
|
|
100
|
+
readonly pattern: "\\S";
|
|
101
|
+
};
|
|
102
|
+
readonly gathered: {
|
|
103
|
+
readonly type: "array";
|
|
104
|
+
readonly items: {
|
|
105
|
+
readonly type: "object";
|
|
106
|
+
readonly additionalProperties: false;
|
|
107
|
+
readonly required: readonly ["label", "text"];
|
|
108
|
+
readonly properties: {
|
|
109
|
+
readonly label: {
|
|
110
|
+
readonly type: "string";
|
|
111
|
+
readonly minLength: 1;
|
|
112
|
+
readonly pattern: "\\S";
|
|
113
|
+
};
|
|
114
|
+
readonly text: {
|
|
115
|
+
readonly type: "string";
|
|
116
|
+
readonly minLength: 1;
|
|
117
|
+
readonly pattern: "\\S";
|
|
118
|
+
};
|
|
119
|
+
readonly ref: {
|
|
120
|
+
readonly type: "string";
|
|
121
|
+
readonly minLength: 1;
|
|
122
|
+
readonly pattern: "\\S";
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
readonly next_steps: {
|
|
128
|
+
readonly type: "array";
|
|
129
|
+
readonly items: {
|
|
130
|
+
readonly type: "string";
|
|
131
|
+
readonly minLength: 1;
|
|
132
|
+
readonly pattern: "\\S";
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
readonly decision_id: {
|
|
136
|
+
readonly type: "string";
|
|
137
|
+
readonly minLength: 1;
|
|
138
|
+
readonly pattern: "\\S";
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
readonly allOf: readonly [{
|
|
142
|
+
readonly if: {
|
|
143
|
+
readonly properties: {
|
|
144
|
+
readonly state: {
|
|
145
|
+
readonly const: "partial";
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
readonly then: {
|
|
150
|
+
readonly properties: {
|
|
151
|
+
readonly next_steps: {
|
|
152
|
+
readonly minItems: 1;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
}];
|
|
157
|
+
readonly description: string;
|
|
158
|
+
};
|
|
88
159
|
};
|
|
89
160
|
readonly allOf: readonly [{
|
|
90
161
|
readonly if: {
|
|
@@ -171,6 +242,57 @@ export declare const LANE_ENVELOPE_JSON_SCHEMA: {
|
|
|
171
242
|
};
|
|
172
243
|
};
|
|
173
244
|
};
|
|
245
|
+
}, {
|
|
246
|
+
readonly if: {
|
|
247
|
+
readonly required: readonly ["outcome"];
|
|
248
|
+
readonly properties: {
|
|
249
|
+
readonly outcome: {
|
|
250
|
+
readonly properties: {
|
|
251
|
+
readonly state: {
|
|
252
|
+
readonly const: "needs_decision";
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
readonly then: {
|
|
259
|
+
readonly anyOf: readonly [{
|
|
260
|
+
readonly properties: {
|
|
261
|
+
readonly outcome: {
|
|
262
|
+
readonly required: readonly ["decision_id"];
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
}, {
|
|
266
|
+
readonly required: readonly ["questions"];
|
|
267
|
+
readonly properties: {
|
|
268
|
+
readonly questions: {
|
|
269
|
+
readonly minItems: 1;
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
}];
|
|
273
|
+
};
|
|
274
|
+
}, {
|
|
275
|
+
readonly if: {
|
|
276
|
+
readonly required: readonly ["outcome"];
|
|
277
|
+
readonly properties: {
|
|
278
|
+
readonly decision: {
|
|
279
|
+
readonly const: "blocked";
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
readonly then: {
|
|
284
|
+
readonly properties: {
|
|
285
|
+
readonly outcome: {
|
|
286
|
+
readonly properties: {
|
|
287
|
+
readonly state: {
|
|
288
|
+
readonly not: {
|
|
289
|
+
readonly const: "done";
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
};
|
|
174
296
|
}];
|
|
175
297
|
readonly description: string;
|
|
176
298
|
};
|
|
@@ -523,7 +645,15 @@ export interface LaneStory {
|
|
|
523
645
|
verified_at?: string;
|
|
524
646
|
verified_by?: SeniorRef;
|
|
525
647
|
}
|
|
648
|
+
export interface LaneSignal {
|
|
649
|
+
signal_id: string;
|
|
650
|
+
type: string;
|
|
651
|
+
source_ref: string;
|
|
652
|
+
title: string;
|
|
653
|
+
}
|
|
526
654
|
export interface LaneRecord {
|
|
655
|
+
signal?: LaneSignal;
|
|
656
|
+
signal_link?: string;
|
|
527
657
|
schema_version: typeof LANE_SCHEMA_VERSION | string;
|
|
528
658
|
lane_id: string;
|
|
529
659
|
/** CLI release that most recently wrote this lane record. Absent on legacy rows. */
|
|
@@ -729,6 +859,11 @@ export interface LaneRecord {
|
|
|
729
859
|
story_status?: StoryStatus;
|
|
730
860
|
/** Envelope parser failures retained when a usable ask is recovered. */
|
|
731
861
|
envelope_validation_errors?: string[];
|
|
862
|
+
/**
|
|
863
|
+
* Copied from the last ingested envelope's optional `outcome` block.
|
|
864
|
+
* Cleared when a later envelope carries no outcome.
|
|
865
|
+
*/
|
|
866
|
+
outcome?: LaneOutcome;
|
|
732
867
|
/** Set by the reconcile pass when `--timeout` has passed (spec §5.7). */
|
|
733
868
|
overdue?: boolean;
|
|
734
869
|
overdue_at?: string;
|
|
@@ -1027,6 +1162,8 @@ export interface LaneEnvelope {
|
|
|
1027
1162
|
story_complete_reason?: string;
|
|
1028
1163
|
/** The worker's fingerprint, or null when it could not be built. Optional. */
|
|
1029
1164
|
fingerprint?: string | null;
|
|
1165
|
+
/** Optional structured result block (see outcome.ts). */
|
|
1166
|
+
outcome?: LaneOutcome;
|
|
1030
1167
|
}
|
|
1031
1168
|
export interface AccountConfig {
|
|
1032
1169
|
/** Company slug or UID owning this account. Untagged accounts are personal. */
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* so the command layer can validate flags explicitly.
|
|
5
5
|
*/
|
|
6
6
|
import { LanesError, LaneSchemaError } from "./errors.js";
|
|
7
|
+
import { LANE_OUTCOME_ENVELOPE_RULES, LANE_OUTCOME_JSON_SCHEMA, parseLaneOutcome, } from "./outcome.js";
|
|
7
8
|
export const LANE_SCHEMA_VERSION = "hq-lane.v1";
|
|
8
9
|
export const QUESTION_SCHEMA_VERSION = "hq-lane-question.v1";
|
|
9
10
|
export const ENVELOPE_SCHEMA_VERSION = "hq-lane-envelope.v1";
|
|
@@ -53,6 +54,7 @@ export const LANE_ENVELOPE_JSON_SCHEMA = {
|
|
|
53
54
|
type: ["string", "null"],
|
|
54
55
|
description: "Optional worker fingerprint (hq fingerprint self); null when it could not be built. Older readers ignore it.",
|
|
55
56
|
},
|
|
57
|
+
outcome: LANE_OUTCOME_JSON_SCHEMA,
|
|
56
58
|
},
|
|
57
59
|
allOf: [
|
|
58
60
|
{
|
|
@@ -80,11 +82,16 @@ export const LANE_ENVELOPE_JSON_SCHEMA = {
|
|
|
80
82
|
else: { not: { required: ["story_complete_reason"] } },
|
|
81
83
|
},
|
|
82
84
|
},
|
|
85
|
+
...LANE_OUTCOME_ENVELOPE_RULES,
|
|
83
86
|
],
|
|
84
87
|
description: "questions is present and non-empty exactly when decision is ask. " +
|
|
85
88
|
"blocked_reason is present exactly when decision is blocked. " +
|
|
86
89
|
"story_complete true requires story_status done; story_complete false requires story_status other than done; " +
|
|
87
|
-
"story_complete_reason is required whenever story_complete is present."
|
|
90
|
+
"story_complete_reason is required whenever story_complete is present. " +
|
|
91
|
+
"outcome is optional; outcome.state is done, needs_decision or partial. " +
|
|
92
|
+
"needs_decision requires outcome.decision_id or a non-empty questions[]; " +
|
|
93
|
+
"partial requires at least one outcome.next_steps entry; " +
|
|
94
|
+
"outcome.state done is rejected when decision is blocked.",
|
|
88
95
|
};
|
|
89
96
|
export const LANE_STATES = [
|
|
90
97
|
"queued",
|
|
@@ -247,6 +254,7 @@ export const REQUIRED_LANE_RECORD_FIELDS = [
|
|
|
247
254
|
export const LANE_WRITERS = ["creator", "command", "watcher", "reconcile", "spawn", "story-ledger"];
|
|
248
255
|
/** Field ownership at the durable-record mutation boundary (spec §10.1). */
|
|
249
256
|
export const LANE_FIELD_WRITERS = {
|
|
257
|
+
signal: ["creator"], signal_link: ["creator"],
|
|
250
258
|
schema_version: ["creator"], lane_id: ["creator"], written_by_version: [], owner_written_by_version: [], company: ["creator", "command"], company_history: ["command"],
|
|
251
259
|
project_id: ["command"], story_id: ["command"], no_story_reason: ["creator", "command"], focus_overrides: ["command"], ledger_sync_error: ["story-ledger"], pending_question_ids: ["story-ledger"], stories: ["creator", "command", "watcher", "reconcile", "spawn"], thread_id: ["creator", "command", "spawn"], mesh_company: ["creator", "command"],
|
|
252
260
|
depth: ["command"], senior: ["command"], senior_history: ["command"],
|
|
@@ -260,7 +268,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
260
268
|
provider: ["creator", "command"], runner: ["creator"], remote_control: ["creator"], remote_control_title: ["creator"], provider_source: ["creator", "command"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_source: ["creator", "command"], profile_settings_pinned: ["command"], account_selection: ["creator", "command"], account_pick: ["creator"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], fallback_question_cancellation_failures: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
261
269
|
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], ended_at: ["reconcile"], envelope_summary: ["reconcile"], attribution_history: ["command", "watcher", "reconcile"], story_switch_pending: ["command", "watcher", "reconcile"], story_switch_unacknowledged: ["command", "watcher", "reconcile"],
|
|
262
270
|
last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn", "command"], effort_source: ["creator", "spawn", "command"], fast_mode: ["spawn"],
|
|
263
|
-
last_message_at: ["creator", "command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher", "reconcile"], blocker_freshness_state: ["watcher"], kr_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
|
|
271
|
+
last_message_at: ["creator", "command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher", "reconcile"], blocker_freshness_state: ["watcher"], kr_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"], outcome: ["watcher", "reconcile"],
|
|
264
272
|
overdue: ["reconcile"], overdue_at: ["reconcile"], last_host_report_at: ["watcher", "spawn", "reconcile"], stalled: ["watcher"], stalled_at: ["watcher"], stalled_reason: ["watcher"],
|
|
265
273
|
admitted_reason: ["command", "reconcile"], terminal_reason: ["command", "watcher", "spawn", "reconcile"], stop_history: ["command", "reconcile"], settings_history: ["command"], pending_settings: ["command", "spawn"], interrupted_at: ["command", "watcher", "reconcile", "spawn"], terminal_details: ["command", "spawn", "watcher", "reconcile"],
|
|
266
274
|
quota_reset_at: ["watcher", "reconcile", "spawn", "command"], model_quota_marker: ["watcher", "reconcile"], outpost_command_id: ["command"],
|
|
@@ -322,6 +330,7 @@ export function parseLaneEnvelope(raw, options = {}) {
|
|
|
322
330
|
"story_complete",
|
|
323
331
|
"story_complete_reason",
|
|
324
332
|
"fingerprint",
|
|
333
|
+
"outcome",
|
|
325
334
|
].includes(key));
|
|
326
335
|
if (extra.length > 0) {
|
|
327
336
|
throw new LaneSchemaError(`Lane envelope rejects unknown properties: ${extra.join(", ")}`, { extra });
|
|
@@ -453,6 +462,12 @@ export function parseLaneEnvelope(raw, options = {}) {
|
|
|
453
462
|
if (typeof raw.blocked_reason === "string") {
|
|
454
463
|
envelope.blocked_reason = raw.blocked_reason;
|
|
455
464
|
}
|
|
465
|
+
if (raw.outcome !== undefined) {
|
|
466
|
+
envelope.outcome = parseLaneOutcome(raw.outcome, {
|
|
467
|
+
decision: envelope.decision,
|
|
468
|
+
questionCount: envelope.questions?.length ?? 0,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
456
471
|
return envelope;
|
|
457
472
|
}
|
|
458
473
|
//# sourceMappingURL=types.js.map
|
|
@@ -425,6 +425,8 @@ function applyTerminalToLaneWithResult(hqRoot, laneId, classification, opts = {}
|
|
|
425
425
|
},
|
|
426
426
|
...(envelope.decision !== "blocked" ? { keep_alive_blocked_resume_streak: 0 } : {}),
|
|
427
427
|
envelope_validation_errors: opts.envelopeValidationErrors,
|
|
428
|
+
// The last envelope's outcome block; undefined clears a stale one.
|
|
429
|
+
outcome: envelope.outcome,
|
|
428
430
|
};
|
|
429
431
|
if (!approvalGate.violation && approvalGate.approvedIdsToConsume.length > 0) {
|
|
430
432
|
next.registry_worker_approval_history = approvalGate.approvedIdsToConsume.reduce((history, approvalId) => appendCappedHistory(history, {
|
|
@@ -509,6 +511,8 @@ function applyTerminalToLaneWithResult(hqRoot, laneId, classification, opts = {}
|
|
|
509
511
|
const storySwitch = applyStorySwitchEnvelope(current, envelope, at, envelopeRef);
|
|
510
512
|
if (envelope.decision !== "blocked")
|
|
511
513
|
next.keep_alive_blocked_resume_streak = 0;
|
|
514
|
+
// The last envelope's outcome block; undefined clears a stale one.
|
|
515
|
+
next.outcome = envelope.outcome;
|
|
512
516
|
next.last_envelope = {
|
|
513
517
|
ref: envelopeRef,
|
|
514
518
|
decision: envelope.decision,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface LiveMeetingBot {
|
|
2
|
+
meetingId?: string;
|
|
3
|
+
meetingUrl?: string;
|
|
4
|
+
recallBotId?: string;
|
|
5
|
+
botId?: string;
|
|
6
|
+
companyId?: string;
|
|
7
|
+
meetingTitle?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
status?: string;
|
|
10
|
+
scheduledStartTime?: string;
|
|
11
|
+
createdAt?: string;
|
|
12
|
+
linesSoFar?: number;
|
|
13
|
+
transcriptLineCount?: number;
|
|
14
|
+
sourceLanded?: boolean;
|
|
15
|
+
savedTranscriptPath?: string;
|
|
16
|
+
}
|
|
17
|
+
/** The attended list used by meetings live and the Executive's poll producer. */
|
|
18
|
+
export declare function fetchAttendedLiveBots(token: string, companyId: string, strict?: boolean, signal?: AbortSignal): Promise<LiveMeetingBot[]>;
|
|
19
|
+
//# sourceMappingURL=live-meetings.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
2
|
+
/** The attended list used by meetings live and the Executive's poll producer. */
|
|
3
|
+
export async function fetchAttendedLiveBots(token, companyId, strict = false, signal) {
|
|
4
|
+
try {
|
|
5
|
+
const res = await vaultApiFetch({ token, path: "/v1/meetings", query: { companyId, view: "live" }, ...(signal ? { signal } : {}) });
|
|
6
|
+
if (!res.ok) {
|
|
7
|
+
if (strict)
|
|
8
|
+
throw new Error("Could not list attended meetings");
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
const data = (await res.json());
|
|
12
|
+
if (strict && !Array.isArray(data.bots))
|
|
13
|
+
throw new Error("Invalid attended meetings response");
|
|
14
|
+
return Array.isArray(data.bots) ? data.bots : [];
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
if (strict)
|
|
18
|
+
throw err;
|
|
19
|
+
// Older servers and offline callers retain the CLI's own-notetaker list.
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=live-meetings.js.map
|
package/dist/lib/mesh/api.js
CHANGED
|
@@ -9,6 +9,7 @@ import { ensureCognitoToken, isMachineIdentity } from "../../utils/cognito-sessi
|
|
|
9
9
|
import { peekIdToken } from "../../utils/id-token.js";
|
|
10
10
|
import { getCompanyUid, vaultApiFetch } from "../../utils/vault-api.js";
|
|
11
11
|
import { meshCacheRoot, removeSessionFallbackFiles, writeMeshCacheFile, } from "./cache.js";
|
|
12
|
+
import { gatedInteractiveLogin } from "../../utils/interactive-caller.js";
|
|
12
13
|
const PRINCIPAL_UID = /^(prs|agt)_/;
|
|
13
14
|
export const STORY_STATUSES = new Set([
|
|
14
15
|
"queued",
|
|
@@ -183,7 +184,7 @@ export async function requireToken(opts = {}) {
|
|
|
183
184
|
// hard policy indigo-agent-caller-resolution-id-token, agent claims ride
|
|
184
185
|
// the ID token only; the server is correct to require it. Humans keep the
|
|
185
186
|
// person access token (unchanged).
|
|
186
|
-
const interactive = opts.interactive ?? true;
|
|
187
|
+
const interactive = gatedInteractiveLogin(opts.interactive ?? true);
|
|
187
188
|
if (isMachineIdentity()) {
|
|
188
189
|
return ensureCognitoToken({ tokenSource: "machine", interactive });
|
|
189
190
|
}
|
|
@@ -12,7 +12,7 @@ import { workContextRoot } from "../../../work-context/paths.js";
|
|
|
12
12
|
import { outboxQuarantineStatusEnabled } from "../../../work-context/outbox-quarantine-status-flag.js";
|
|
13
13
|
import { formatDroppedPresenceCompany } from "./credentials.js";
|
|
14
14
|
import { formatOwnerPushStatus, ownerPushStatusForAccount, readOwnerInboxPushState, } from "./owner-inbox-push.js";
|
|
15
|
-
import { countDistinctHeldEvents, countJsonlLines, countUnattributedEvents, } from "../spool.js";
|
|
15
|
+
import { countDistinctHeldEvents, countJsonlLines, countUnattributedEvents, forEachJsonlLine, } from "../spool.js";
|
|
16
16
|
import { workMeshDeadLetterPath, workMeshHeldPath, workMeshRoot, workMeshSpoolPath, } from "../paths.js";
|
|
17
17
|
import { daemonDir, daemonPidPath } from "./paths.js";
|
|
18
18
|
import { readEmitState } from "../emit.js";
|
|
@@ -31,11 +31,10 @@ function oldestJsonlAgeMs(filePath, nowMs) {
|
|
|
31
31
|
if (!fs.existsSync(filePath))
|
|
32
32
|
return undefined;
|
|
33
33
|
try {
|
|
34
|
-
const text = fs.readFileSync(filePath, "utf8");
|
|
35
34
|
let oldest;
|
|
36
|
-
|
|
35
|
+
forEachJsonlLine(filePath, (line) => {
|
|
37
36
|
if (!line.trim())
|
|
38
|
-
|
|
37
|
+
return;
|
|
39
38
|
try {
|
|
40
39
|
const obj = JSON.parse(line);
|
|
41
40
|
const at = (typeof obj.at === "string" && obj.at) ||
|
|
@@ -43,10 +42,10 @@ function oldestJsonlAgeMs(filePath, nowMs) {
|
|
|
43
42
|
(obj.event && typeof obj.event.at === "string" && obj.event.at) ||
|
|
44
43
|
undefined;
|
|
45
44
|
if (!at)
|
|
46
|
-
|
|
45
|
+
return;
|
|
47
46
|
const t = Date.parse(at);
|
|
48
47
|
if (!Number.isFinite(t))
|
|
49
|
-
|
|
48
|
+
return;
|
|
50
49
|
const age = nowMs - t;
|
|
51
50
|
if (oldest === undefined || age > oldest)
|
|
52
51
|
oldest = age;
|
|
@@ -54,7 +53,7 @@ function oldestJsonlAgeMs(filePath, nowMs) {
|
|
|
54
53
|
catch {
|
|
55
54
|
/* skip */
|
|
56
55
|
}
|
|
57
|
-
}
|
|
56
|
+
});
|
|
58
57
|
return oldest;
|
|
59
58
|
}
|
|
60
59
|
catch {
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hq mesh daemon run — single-instance resident process.
|
|
3
|
-
*
|
|
4
|
-
* - pid lock at ~/.hq/work-mesh/daemon/pid
|
|
5
|
-
* - fs.watch spool + 2s debounce; flush at least every 10s (US-008 path)
|
|
6
|
-
* - replay context outbox
|
|
7
|
-
* - board.md refresh for bound sessions ≤ every 60s
|
|
8
|
-
* - MQTT presence (contract-3)
|
|
9
|
-
* - SIGTERM/SIGINT: flush once more, MQTT DISCONNECT (server derives offline), exit
|
|
10
|
-
*/
|
|
11
1
|
import { type DeliverFn } from "../../../work-context/outbox.js";
|
|
12
2
|
import { type SessionEventsPoster } from "../session-events-client.js";
|
|
13
3
|
import { type FlushSummary } from "../flush.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportingRuntimeAllowed } from "../reporting-privacy.js";
|
|
1
2
|
/**
|
|
2
3
|
* hq mesh daemon run — single-instance resident process.
|
|
3
4
|
*
|
|
@@ -380,6 +381,8 @@ export async function runMeshDaemon(deps = {}) {
|
|
|
380
381
|
baseUrl: resolveVaultApiBase(env),
|
|
381
382
|
});
|
|
382
383
|
return flushSessionEvents({
|
|
384
|
+
env,
|
|
385
|
+
reportingAllowed: () => reportingRuntimeAllowed(machineIdentity, env),
|
|
383
386
|
workMeshRoot: meshRoot,
|
|
384
387
|
workContextRoot: ctxRoot,
|
|
385
388
|
poster,
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Direct-emit orchestration (owner decision 2026-09-08). One invocation:
|
|
3
|
-
* 1. drains the local retry file (prior failures),
|
|
4
|
-
* 2. appends the new event(s),
|
|
5
|
-
* 3. POSTs /v1/mesh/events in batches with a short bounded retry,
|
|
6
|
-
* 4. keeps only network/5xx/429-failed or unaccounted events for next time,
|
|
7
|
-
* 5. records lastPostAt + per-event status counts for `hq mesh doctor`.
|
|
8
|
-
*
|
|
9
|
-
* accepted / unassigned / rejected are all terminal (rejected is dropped with a
|
|
10
|
-
* count — never re-posted). No long-lived process, spool, or held queue.
|
|
11
|
-
*/
|
|
12
1
|
import { type EmitPoster, type EmitEventStatus } from "./emit-client.js";
|
|
13
2
|
import { type RandomFn, type SleepFn } from "./backoff.js";
|
|
14
3
|
export interface EmitStateFile {
|
|
@@ -23,6 +12,8 @@ export interface EmitStateFile {
|
|
|
23
12
|
export declare function emitStatePath(workMeshRoot: string): string;
|
|
24
13
|
export declare function readEmitState(workMeshRoot: string): EmitStateFile | null;
|
|
25
14
|
export interface EmitEventsDeps {
|
|
15
|
+
env?: NodeJS.ProcessEnv;
|
|
16
|
+
reportingAllowed?: () => Promise<boolean>;
|
|
26
17
|
workMeshRoot: string;
|
|
27
18
|
poster: EmitPoster;
|
|
28
19
|
/** New events to emit this invocation (may be empty — just drain retry). */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportingLocallyDisabled, mayEmitConversation } from "./reporting-privacy.js";
|
|
1
2
|
/**
|
|
2
3
|
* Direct-emit orchestration (owner decision 2026-09-08). One invocation:
|
|
3
4
|
* 1. drains the local retry file (prior failures),
|
|
@@ -90,10 +91,27 @@ export async function emitEvents(deps) {
|
|
|
90
91
|
statuses: { accepted: 0, unassigned: 0, rejected: 0 },
|
|
91
92
|
rejections: [],
|
|
92
93
|
};
|
|
94
|
+
const env = deps.env ?? process.env;
|
|
95
|
+
if (reportingLocallyDisabled(env) || (deps.reportingAllowed && !(await deps.reportingAllowed()))) {
|
|
96
|
+
const written = writeEmitRetry(deps.workMeshRoot, combined, retryMax);
|
|
97
|
+
return { ...summary, attempted: 0, retained: written.written,
|
|
98
|
+
retryDepth: written.written, droppedOverflow: written.dropped };
|
|
99
|
+
}
|
|
100
|
+
const allowed = combined.filter(event => {
|
|
101
|
+
if (mayEmitConversation(event, env))
|
|
102
|
+
return true;
|
|
103
|
+
summary.rejected += 1;
|
|
104
|
+
summary.statuses.rejected += 1;
|
|
105
|
+
const eventId = eventIdOf(event);
|
|
106
|
+
if (eventId)
|
|
107
|
+
summary.rejections.push({ eventId, code: "PERSONAL_OR_UNATTRIBUTED" });
|
|
108
|
+
return false;
|
|
109
|
+
});
|
|
110
|
+
summary.attempted = allowed.length;
|
|
93
111
|
const keep = [];
|
|
94
112
|
let anyPosted = false;
|
|
95
|
-
for (let i = 0; i <
|
|
96
|
-
const chunk =
|
|
113
|
+
for (let i = 0; i < allowed.length; i += MESH_EVENTS_BATCH_MAX) {
|
|
114
|
+
const chunk = allowed.slice(i, i + MESH_EVENTS_BATCH_MAX);
|
|
97
115
|
summary.batches += 1;
|
|
98
116
|
let result = await deps.poster(chunk);
|
|
99
117
|
for (let attempt = 1; attempt < maxAttempts && !result.ok && result.retryable; attempt += 1) {
|