@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.
Files changed (118) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/dist/command-catalog.generated.d.ts +393 -12
  3. package/dist/command-catalog.generated.js +482 -12
  4. package/dist/commands/bot-events.d.ts +19 -0
  5. package/dist/commands/bot-events.js +46 -0
  6. package/dist/commands/bot-jobs.d.ts +2 -0
  7. package/dist/commands/bot-jobs.js +43 -2
  8. package/dist/commands/bot.d.ts +5 -0
  9. package/dist/commands/bot.js +33 -1
  10. package/dist/commands/core-resolve-company-native.d.ts +2 -1
  11. package/dist/commands/core-resolve-company-native.js +2 -24
  12. package/dist/commands/deploy-domains.d.ts +14 -2
  13. package/dist/commands/deploy-domains.js +726 -42
  14. package/dist/commands/integrations-connect.js +41 -3
  15. package/dist/commands/lanes-report.d.ts +2 -1
  16. package/dist/commands/lanes-report.js +44 -1
  17. package/dist/commands/lanes.d.ts +9 -0
  18. package/dist/commands/lanes.js +95 -9
  19. package/dist/commands/meetings.js +1 -21
  20. package/dist/commands/secrets.js +27 -6
  21. package/dist/commands/signals-run.d.ts +2 -0
  22. package/dist/commands/signals-run.js +6 -1
  23. package/dist/lanes-drain-hook.js +9 -0
  24. package/dist/lib/bot/api.d.ts +5 -0
  25. package/dist/lib/bot/claude-login.d.ts +31 -0
  26. package/dist/lib/bot/claude-login.js +160 -0
  27. package/dist/lib/bot/company-bind.d.ts +34 -0
  28. package/dist/lib/bot/company-bind.js +112 -0
  29. package/dist/lib/bot/config.js +3 -1
  30. package/dist/lib/bot/events.d.ts +18 -0
  31. package/dist/lib/bot/events.js +160 -0
  32. package/dist/lib/bot/executive-settings.d.ts +10 -0
  33. package/dist/lib/bot/executive-settings.js +43 -0
  34. package/dist/lib/bot/fleet-cron.js +4 -0
  35. package/dist/lib/bot/fleet-local.d.ts +12 -0
  36. package/dist/lib/bot/fleet-local.js +50 -20
  37. package/dist/lib/bot/job-run.js +1 -1
  38. package/dist/lib/bot/jobs/hot-signal-sync.d.ts +8 -0
  39. package/dist/lib/bot/jobs/hot-signal-sync.js +2 -0
  40. package/dist/lib/bot/jobs/meeting-detect.d.ts +28 -0
  41. package/dist/lib/bot/jobs/meeting-detect.js +176 -0
  42. package/dist/lib/bot/jobs/meeting-rider.d.ts +53 -0
  43. package/dist/lib/bot/jobs/meeting-rider.js +284 -0
  44. package/dist/lib/bot/jobs/signal-to-lane.d.ts +32 -0
  45. package/dist/lib/bot/jobs/signal-to-lane.js +169 -0
  46. package/dist/lib/bot/local-jobs.d.ts +15 -2
  47. package/dist/lib/bot/local-jobs.js +24 -7
  48. package/dist/lib/bot/run.d.ts +7 -49
  49. package/dist/lib/bot/run.js +135 -12
  50. package/dist/lib/bot/session.d.ts +19 -1
  51. package/dist/lib/bot/session.js +75 -28
  52. package/dist/lib/bot/status.d.ts +21 -0
  53. package/dist/lib/core-utils/manifest-slugs.d.ts +2 -0
  54. package/dist/lib/core-utils/manifest-slugs.js +26 -0
  55. package/dist/lib/doctor/checks/bots.d.ts +6 -0
  56. package/dist/lib/doctor/checks/bots.js +47 -1
  57. package/dist/lib/hot-channels/adapters/meetings.d.ts +1 -0
  58. package/dist/lib/hot-channels/adapters/meetings.js +3 -1
  59. package/dist/lib/hot-channels/meeting-source-ref.d.ts +3 -0
  60. package/dist/lib/hot-channels/meeting-source-ref.js +7 -0
  61. package/dist/lib/hot-channels/schedule.js +2 -2
  62. package/dist/lib/hot-channels/signals-pass.d.ts +9 -32
  63. package/dist/lib/hot-channels/signals-pass.js +51 -17
  64. package/dist/lib/lanes/admission.d.ts +2 -0
  65. package/dist/lib/lanes/admission.js +4 -0
  66. package/dist/lib/lanes/approval-gate.js +4 -1
  67. package/dist/lib/lanes/links.d.ts +8 -1
  68. package/dist/lib/lanes/links.js +84 -3
  69. package/dist/lib/lanes/outcome.d.ts +154 -0
  70. package/dist/lib/lanes/outcome.js +151 -0
  71. package/dist/lib/lanes/reconcile.js +1 -0
  72. package/dist/lib/lanes/signal-brief-template.d.ts +7 -0
  73. package/dist/lib/lanes/signal-brief-template.js +40 -0
  74. package/dist/lib/lanes/signal-lane.d.ts +24 -0
  75. package/dist/lib/lanes/signal-lane.js +99 -0
  76. package/dist/lib/lanes/source-report.d.ts +37 -0
  77. package/dist/lib/lanes/source-report.js +66 -0
  78. package/dist/lib/lanes/spawn.d.ts +6 -0
  79. package/dist/lib/lanes/spawn.js +21 -0
  80. package/dist/lib/lanes/store.d.ts +3 -0
  81. package/dist/lib/lanes/store.js +64 -0
  82. package/dist/lib/lanes/story-ledger.d.ts +3 -0
  83. package/dist/lib/lanes/story-ledger.js +1 -0
  84. package/dist/lib/lanes/types.d.ts +137 -0
  85. package/dist/lib/lanes/types.js +17 -2
  86. package/dist/lib/lanes/watcher.js +4 -0
  87. package/dist/lib/live-meetings.d.ts +19 -0
  88. package/dist/lib/live-meetings.js +23 -0
  89. package/dist/lib/mesh/live/daemon/doctor.js +6 -7
  90. package/dist/lib/mesh/live/spool.d.ts +8 -0
  91. package/dist/lib/mesh/live/spool.js +59 -40
  92. package/dist/lib/plan-limit-nag.d.ts +30 -4
  93. package/dist/lib/plan-limit-nag.js +68 -17
  94. package/dist/lib/signals/engine.d.ts +4 -0
  95. package/dist/lib/signals/engine.js +25 -5
  96. package/dist/lib/signals/incremental.d.ts +42 -0
  97. package/dist/lib/signals/incremental.js +191 -0
  98. package/dist/lib/signals/prior-signals.d.ts +11 -0
  99. package/dist/lib/signals/prior-signals.js +44 -0
  100. package/dist/lib/signals/reply.js +6 -1
  101. package/dist/lib/signals/settings.d.ts +5 -0
  102. package/dist/lib/signals/settings.js +17 -0
  103. package/dist/lib/signals/steps/extract.d.ts +30 -2
  104. package/dist/lib/signals/steps/extract.js +153 -11
  105. package/dist/lib/signals/steps/types.d.ts +1 -0
  106. package/dist/lib/signals/write-guard.js +7 -0
  107. package/dist/secrets-env-action.d.ts +70 -0
  108. package/dist/secrets-env-action.js +220 -2
  109. package/dist/utils/cognito-callback-port.d.ts +15 -0
  110. package/dist/utils/cognito-callback-port.js +51 -0
  111. package/dist/utils/cognito-config.js +2 -3
  112. package/dist/utils/cognito-session.d.ts +7 -6
  113. package/dist/utils/cognito-session.js +14 -14
  114. package/dist/utils/interactive-caller.d.ts +15 -0
  115. package/dist/utils/interactive-caller.js +70 -0
  116. package/dist/utils/login-browser.js +3 -1
  117. package/dist/utils/resident-package-preload.js +4 -0
  118. package/package.json +9 -7
@@ -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. */
@@ -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
@@ -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
- for (const line of text.split("\n")) {
35
+ forEachJsonlLine(filePath, (line) => {
37
36
  if (!line.trim())
38
- continue;
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
- continue;
45
+ return;
47
46
  const t = Date.parse(at);
48
47
  if (!Number.isFinite(t))
49
- continue;
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 {
@@ -44,6 +44,14 @@ export declare function recoverOrphanClaims(root: string): {
44
44
  };
45
45
  export declare function removeClaimFile(claimPath: string | null): void;
46
46
  export declare function deadLetterNonEmpty(root?: string): boolean;
47
+ /**
48
+ * Visit each line of a JSONL file without loading the whole file into one
49
+ * string. Queue files (dead-letter especially) can grow past V8's ~512 MB
50
+ * string ceiling, where readFileSync(..., "utf8") throws. Splits on the raw
51
+ * newline byte so multi-byte UTF-8 sequences are never cut. Returns false when
52
+ * the file cannot be opened.
53
+ */
54
+ export declare function forEachJsonlLine(filePath: string, visit: (line: string) => void): boolean;
47
55
  export declare function countJsonlLines(filePath: string): number;
48
56
  /** Count held eventIds once; legacy rows without an eventId remain line-counted. */
49
57
  export declare function countDistinctHeldEvents(filePath: string): number;
@@ -334,40 +334,71 @@ export function deadLetterNonEmpty(root) {
334
334
  return false;
335
335
  }
336
336
  }
337
+ /** Chunk size for streaming JSONL reads. */
338
+ const JSONL_READ_CHUNK_BYTES = 1024 * 1024;
339
+ /**
340
+ * Visit each line of a JSONL file without loading the whole file into one
341
+ * string. Queue files (dead-letter especially) can grow past V8's ~512 MB
342
+ * string ceiling, where readFileSync(..., "utf8") throws. Splits on the raw
343
+ * newline byte so multi-byte UTF-8 sequences are never cut. Returns false when
344
+ * the file cannot be opened.
345
+ */
346
+ export function forEachJsonlLine(filePath, visit) {
347
+ let fd;
348
+ try {
349
+ fd = fs.openSync(filePath, "r");
350
+ }
351
+ catch {
352
+ return false;
353
+ }
354
+ try {
355
+ const chunk = Buffer.alloc(JSONL_READ_CHUNK_BYTES);
356
+ let carry = Buffer.alloc(0);
357
+ for (;;) {
358
+ const n = fs.readSync(fd, chunk, 0, chunk.length, null);
359
+ if (n <= 0)
360
+ break;
361
+ let data = carry.length > 0 ? Buffer.concat([carry, chunk.subarray(0, n)]) : chunk.subarray(0, n);
362
+ let start = 0;
363
+ for (let nl = data.indexOf(0x0a, start); nl !== -1; nl = data.indexOf(0x0a, start)) {
364
+ visit(data.toString("utf8", start, nl));
365
+ start = nl + 1;
366
+ }
367
+ carry = Buffer.from(data.subarray(start));
368
+ data = Buffer.alloc(0);
369
+ }
370
+ if (carry.length > 0)
371
+ visit(carry.toString("utf8"));
372
+ }
373
+ finally {
374
+ fs.closeSync(fd);
375
+ }
376
+ return true;
377
+ }
337
378
  export function countJsonlLines(filePath) {
338
- if (!fs.existsSync(filePath))
339
- return 0;
340
- const raw = fs.readFileSync(filePath, "utf8");
341
379
  let n = 0;
342
- for (const line of raw.split("\n")) {
380
+ forEachJsonlLine(filePath, (line) => {
343
381
  if (line.trim())
344
382
  n += 1;
345
- }
383
+ });
346
384
  return n;
347
385
  }
348
386
  /** Count held eventIds once; legacy rows without an eventId remain line-counted. */
349
387
  export function countDistinctHeldEvents(filePath) {
350
- let raw;
351
- try {
352
- raw = fs.readFileSync(filePath, "utf8");
353
- }
354
- catch {
355
- // A concurrent claim can rename held.jsonl between existence and read checks.
356
- return 0;
357
- }
358
388
  const seen = new Set();
359
389
  let count = 0;
360
- for (const line of raw.split("\n")) {
390
+ // A concurrent claim can rename held.jsonl away; a missing file counts as 0.
391
+ forEachJsonlLine(filePath, (line) => {
361
392
  if (!line.trim())
362
- continue;
393
+ return;
363
394
  const eventId = heldEventId(line);
364
395
  if (eventId) {
365
396
  if (seen.has(eventId))
366
- continue;
397
+ return;
367
398
  seen.add(eventId);
368
399
  }
369
400
  count += 1;
370
- }
401
+ });
371
402
  return count;
372
403
  }
373
404
  function record(value) {
@@ -413,17 +444,12 @@ function isUnattributedLine(line) {
413
444
  }
414
445
  }
415
446
  function countUnattributedJsonlLines(filePath) {
416
- if (!fs.existsSync(filePath))
417
- return 0;
418
- try {
419
- return fs
420
- .readFileSync(filePath, "utf8")
421
- .split("\n")
422
- .filter((line) => line.trim() && isUnattributedLine(line)).length;
423
- }
424
- catch {
425
- return 0;
426
- }
447
+ let n = 0;
448
+ forEachJsonlLine(filePath, (line) => {
449
+ if (line.trim() && isUnattributedLine(line))
450
+ n += 1;
451
+ });
452
+ return n;
427
453
  }
428
454
  /**
429
455
  * Count the actionable, unfiled events across all local delivery queues.
@@ -438,27 +464,20 @@ export function countUnattributedEvents(root) {
438
464
  return { spool, held, deadLetter, total: spool + held + deadLetter };
439
465
  }
440
466
  function countUnattributedHeldEvents(filePath) {
441
- let raw;
442
- try {
443
- raw = fs.readFileSync(filePath, "utf8");
444
- }
445
- catch {
446
- // A concurrent held claim may rename this file between the read and open.
447
- return 0;
448
- }
449
467
  const seen = new Set();
450
468
  let count = 0;
451
- for (const line of raw.split("\n")) {
469
+ // A concurrent held claim may rename this file away; a missing file counts as 0.
470
+ forEachJsonlLine(filePath, (line) => {
452
471
  if (!line.trim() || !isUnattributedLine(line))
453
- continue;
472
+ return;
454
473
  const eventId = heldEventId(line);
455
474
  if (eventId) {
456
475
  if (seen.has(eventId))
457
- continue;
476
+ return;
458
477
  seen.add(eventId);
459
478
  }
460
479
  count += 1;
461
- }
480
+ });
462
481
  return count;
463
482
  }
464
483
  //# sourceMappingURL=spool.js.map
@@ -49,6 +49,28 @@ export declare const OVER_NAG_INTERVAL_MS: number;
49
49
  * as the bare key rather than a guess.
50
50
  */
51
51
  export declare const PLAN_LIMIT_DIMENSION_LABELS: Record<string, string>;
52
+ /**
53
+ * The account a nag payload is about, as hq-pro labels it (`account` beside
54
+ * `planLimits`). One command can touch a personal vault and a company vault,
55
+ * so the line names which one is over (feedback_b1867fe6).
56
+ */
57
+ export type PlanLimitAccount = {
58
+ kind: "person";
59
+ } | {
60
+ kind: "company";
61
+ slug: string | null;
62
+ };
63
+ /**
64
+ * Render one amount of a resource. Storage arrives in bytes; printed raw it
65
+ * reads as "47012700666 of 10737418240". It is shown in GB with one decimal,
66
+ * so a 10 GB ceiling reads "10.0 GB", matching the plan's own wording.
67
+ */
68
+ export declare function formatPlanLimitAmount(key: string, n: number): string;
69
+ /**
70
+ * The account as a noun phrase: "your personal account", "the acme company",
71
+ * or "this account" when an older hq-pro sent no label.
72
+ */
73
+ export declare function planLimitAccountPhrase(account: PlanLimitAccount | null | undefined): string;
52
74
  export interface PlanLimitEntry {
53
75
  used: number;
54
76
  limit: number;
@@ -75,10 +97,13 @@ export declare function recordPlanLimitStatus(body: unknown, opts?: {
75
97
  /**
76
98
  * The over-limit per-turn line (US-036 §2.2). One line, not the old box: it
77
99
  * now repeats up to four times a day, and a multi-line box at that cadence
78
- * reads as breakage rather than as a nag. It states the two paused things and
79
- * never claims the workspace has stopped working.
100
+ * reads as breakage rather than as a nag. It names the account and states the
101
+ * facts. It claims no pause: being over is not being refused. A refusal comes
102
+ * back from hq-pro as its own error with its own sentence, and that is the
103
+ * only place a pause is reported (feedback_b1867fe6, where this line told a
104
+ * person their uploads were paused right after two uploads succeeded).
80
105
  */
81
- export declare function buildOverLine(overEntries: Array<[string, PlanLimitEntry]>, upgradeUrl: string): string;
106
+ export declare function buildOverLine(overEntries: Array<[string, PlanLimitEntry]>, upgradeUrl: string, account?: PlanLimitAccount | null): string;
82
107
  /**
83
108
  * The notice a personal feature refusal prints. `upgradeUrl` is the server's
84
109
  * own checkout target; an older hq-pro sends none, and the line then names the
@@ -95,7 +120,7 @@ export declare function renderPersonalPlanFeatureNotice(feature: string, upgrade
95
120
  * their own story, and a notice that announces a pause nobody is experiencing
96
121
  * reads as a bug.
97
122
  */
98
- export declare function renderThresholdNotice(crossing: PlanLimitThresholdCrossing, upgradeUrl: string): string;
123
+ export declare function renderThresholdNotice(crossing: PlanLimitThresholdCrossing, upgradeUrl: string, account?: PlanLimitAccount | null): string;
99
124
  /**
100
125
  * Print every crossing this machine has not already printed for this episode.
101
126
  *
@@ -106,6 +131,7 @@ export declare function renderThresholdNotice(crossing: PlanLimitThresholdCrossi
106
131
  export declare function emitPlanLimitThresholdNotice(crossings: readonly PlanLimitThresholdCrossing[], upgradeUrl: string | null, opts?: {
107
132
  write?: (s: string) => void;
108
133
  statePath?: string;
134
+ account?: PlanLimitAccount | null;
109
135
  }): void;
110
136
  /**
111
137
  * Emit a plan-limit nag to stderr at command completion.