@indigoai-us/hq-cli 5.299.0 → 5.301.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/assets/scaffold/core/scripts/token-usage-report.sh +9 -0
- package/dist/command-catalog.generated.d.ts +32 -6
- package/dist/command-catalog.generated.js +38 -6
- package/dist/commands/__fixtures__/access-vault.d.ts +2 -0
- package/dist/commands/__fixtures__/access-vault.js +1 -1
- package/dist/commands/access.d.ts +2 -1
- package/dist/commands/access.js +58 -49
- package/dist/commands/cloud-provision.js +51 -2
- package/dist/commands/cloud.js +12 -7
- package/dist/commands/core-company-index-scope-flag.d.ts +3 -0
- package/dist/commands/core-company-index-scope-flag.js +6 -0
- package/dist/commands/core-job-native-utils.d.ts +6 -0
- package/dist/commands/core-job-native-utils.js +80 -0
- package/dist/commands/core-job-native.d.ts +2 -4
- package/dist/commands/core-job-native.js +6 -10
- package/dist/commands/core-job-notify-native.d.ts +36 -0
- package/dist/commands/core-job-notify-native.js +359 -0
- package/dist/commands/core-job-remediate-native.d.ts +26 -0
- package/dist/commands/core-job-remediate-native.js +374 -0
- package/dist/commands/core.js +26 -8
- package/dist/commands/files.d.ts +1 -0
- package/dist/commands/files.js +90 -14
- package/dist/commands/integrations-api.d.ts +6 -6
- package/dist/commands/integrations-api.js +4 -13
- package/dist/commands/integrations-manage.js +1 -4
- package/dist/commands/lanes-message.js +78 -37
- package/dist/commands/lanes-resume.js +247 -210
- package/dist/commands/lanes-shared.d.ts +5 -1
- package/dist/commands/lanes.d.ts +4 -0
- package/dist/commands/lanes.js +7 -4
- package/dist/commands/meetings.js +35 -16
- package/dist/commands/safe-extract.d.ts +4 -4
- package/dist/commands/safe-extract.js +6 -7
- package/dist/lib/core-utils/token-usage-report.js +17 -3
- package/dist/lib/index-render/companies.d.ts +4 -1
- package/dist/lib/index-render/companies.js +66 -3
- package/dist/lib/index-render/index.d.ts +1 -0
- package/dist/lib/index-render/index.js +2 -1
- package/dist/lib/index-render/shared.d.ts +1 -1
- package/dist/lib/index-render/shared.js +4 -2
- package/dist/lib/lanes/reconcile.js +22 -4
- package/dist/lib/lanes/story-switch.d.ts +21 -0
- package/dist/lib/lanes/story-switch.js +75 -0
- package/dist/lib/lanes/types.d.ts +25 -0
- package/dist/lib/lanes/types.js +1 -1
- package/dist/lib/lanes/watcher.js +15 -4
- package/dist/lib/mesh/live/daemon/mqtt-events.d.ts +7 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.js +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.js +10 -0
- package/dist/lib/mesh/live/daemon/presence.d.ts +2 -2
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +2 -2
- package/dist/utils/self-update.js +51 -4
- package/dist/utils/staged-npm-install.d.ts +16 -0
- package/dist/utils/staged-npm-install.js +264 -0
- package/dist/utils/update-command-supervisor.cjs +53 -1
- package/dist/utils/version-gate.d.ts +8 -0
- package/dist/utils/version-gate.js +82 -15
- package/package.json +2 -2
- package/dist/lib/install-path-compatibility-flag.d.ts +0 -9
- package/dist/lib/install-path-compatibility-flag.js +0 -12
|
@@ -3,6 +3,25 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
4
4
|
import { vaultApiFetch, getCompanyUid, resolveCallerPersonUid, } from "../utils/vault-api.js";
|
|
5
5
|
import { isPlanGateError, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
|
|
6
|
+
// Two minutes gives a large transcript time to arrive over a slow connection,
|
|
7
|
+
// while keeping the interactive command from waiting indefinitely.
|
|
8
|
+
const MEETING_DOCUMENT_DOWNLOAD_TIMEOUT_MS = 120_000;
|
|
9
|
+
async function downloadMeetingDocument(url, readBody) {
|
|
10
|
+
const signal = AbortSignal.timeout(MEETING_DOCUMENT_DOWNLOAD_TIMEOUT_MS);
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(url, { signal });
|
|
13
|
+
if (!response.ok)
|
|
14
|
+
return { response, ok: false };
|
|
15
|
+
return { response, ok: true, body: await readBody(response) };
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
if ((signal.aborted && signal.reason instanceof DOMException && signal.reason.name === "TimeoutError") ||
|
|
19
|
+
(err instanceof DOMException && err.name === "TimeoutError")) {
|
|
20
|
+
throw new Error(`Meeting document download timed out after ${MEETING_DOCUMENT_DOWNLOAD_TIMEOUT_MS / 1000} seconds. Check your network connection and try again.`);
|
|
21
|
+
}
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
6
25
|
function formatDuration(seconds) {
|
|
7
26
|
const h = Math.floor(seconds / 3600);
|
|
8
27
|
const m = Math.floor((seconds % 3600) / 60);
|
|
@@ -495,12 +514,12 @@ export function registerMeetingsCommand(program) {
|
|
|
495
514
|
console.error(chalk.red("No document available for this meeting."));
|
|
496
515
|
process.exit(1);
|
|
497
516
|
}
|
|
498
|
-
const
|
|
499
|
-
if (!
|
|
500
|
-
console.error(chalk.red(`Failed to download meeting document (${
|
|
517
|
+
const download = await downloadMeetingDocument(documentUrl, (response) => response.text());
|
|
518
|
+
if (!download.ok) {
|
|
519
|
+
console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
|
|
501
520
|
process.exit(1);
|
|
502
521
|
}
|
|
503
|
-
const markdown =
|
|
522
|
+
const markdown = download.body;
|
|
504
523
|
if (meetings.opts().json) {
|
|
505
524
|
console.log(JSON.stringify({ meetingId: detail.meetingId, sourceShape: "markdown", markdown }, null, 2));
|
|
506
525
|
return;
|
|
@@ -513,12 +532,12 @@ export function registerMeetingsCommand(program) {
|
|
|
513
532
|
console.error(chalk.red("No document URL available for this meeting."));
|
|
514
533
|
process.exit(1);
|
|
515
534
|
}
|
|
516
|
-
const
|
|
517
|
-
if (!
|
|
518
|
-
console.error(chalk.red(`Failed to download meeting document (${
|
|
535
|
+
const download = await downloadMeetingDocument(detail.documentUrl, (response) => response.json());
|
|
536
|
+
if (!download.ok) {
|
|
537
|
+
console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
|
|
519
538
|
process.exit(1);
|
|
520
539
|
}
|
|
521
|
-
const doc =
|
|
540
|
+
const doc = download.body;
|
|
522
541
|
if (meetings.opts().json) {
|
|
523
542
|
console.log(JSON.stringify(doc.transcript, null, 2));
|
|
524
543
|
return;
|
|
@@ -573,12 +592,12 @@ export function registerMeetingsCommand(program) {
|
|
|
573
592
|
console.log(chalk.yellow("No notes available for this meeting."));
|
|
574
593
|
return;
|
|
575
594
|
}
|
|
576
|
-
const
|
|
577
|
-
if (!
|
|
578
|
-
console.error(chalk.red(`Failed to download meeting document (${
|
|
595
|
+
const download = await downloadMeetingDocument(documentUrl, (response) => response.text());
|
|
596
|
+
if (!download.ok) {
|
|
597
|
+
console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
|
|
579
598
|
process.exit(1);
|
|
580
599
|
}
|
|
581
|
-
const markdown =
|
|
600
|
+
const markdown = download.body;
|
|
582
601
|
if (meetings.opts().json) {
|
|
583
602
|
console.log(JSON.stringify({ meetingId: detail.meetingId, sourceShape: "markdown", markdown }, null, 2));
|
|
584
603
|
return;
|
|
@@ -591,12 +610,12 @@ export function registerMeetingsCommand(program) {
|
|
|
591
610
|
console.error(chalk.red("No document URL available for this meeting."));
|
|
592
611
|
process.exit(1);
|
|
593
612
|
}
|
|
594
|
-
const
|
|
595
|
-
if (!
|
|
596
|
-
console.error(chalk.red(`Failed to download meeting document (${
|
|
613
|
+
const download = await downloadMeetingDocument(detail.documentUrl, (response) => response.json());
|
|
614
|
+
if (!download.ok) {
|
|
615
|
+
console.error(chalk.red(`Failed to download meeting document (${download.response.status})`));
|
|
597
616
|
process.exit(1);
|
|
598
617
|
}
|
|
599
|
-
const doc =
|
|
618
|
+
const doc = download.body;
|
|
600
619
|
if (meetings.opts().json) {
|
|
601
620
|
console.log(JSON.stringify(doc.notes, null, 2));
|
|
602
621
|
return;
|
|
@@ -111,10 +111,10 @@ export declare function assertLinkTargetContained(packDir: string, entryAbs: str
|
|
|
111
111
|
/**
|
|
112
112
|
* List a gzip-compressed tarball's entries via `tar -tvf` (no extraction).
|
|
113
113
|
* argv form, never a shell string, keeps hostile filenames out of shell syntax.
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
114
|
+
* Windows drive paths use a basename relative to the archive directory by
|
|
115
|
+
* default. GNU tar cannot parse `C:\\...` as remote `host:path`, and Windows'
|
|
116
|
+
* bundled bsdtar accepts the same relative argv without GNU-only `--force-local`.
|
|
117
|
+
* Callers can still explicitly opt out with `useRelativeWindowsDrivePath: false`.
|
|
118
118
|
*/
|
|
119
119
|
export declare function listTarball(tarballPath: string, opts?: Pick<SafeExtractOptions, 'platform' | 'useRelativeWindowsDrivePath' | 'execFileSync'>): TarEntry[];
|
|
120
120
|
/**
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
import { execFileSync } from 'child_process';
|
|
36
36
|
import * as fs from 'fs';
|
|
37
37
|
import * as path from 'path';
|
|
38
|
-
import { installPathCompatibilityEnabled } from '../lib/install-path-compatibility-flag.js';
|
|
39
38
|
export const DEFAULT_CAPS = {
|
|
40
39
|
// A content pack is docs/skills/policies/small scripts. 256 MiB is already
|
|
41
40
|
// generous; anything larger is almost certainly a bomb or a mistake.
|
|
@@ -218,14 +217,14 @@ function tarballArgvLocation(tarballPath, platform, useRelativeWindowsDrivePath)
|
|
|
218
217
|
/**
|
|
219
218
|
* List a gzip-compressed tarball's entries via `tar -tvf` (no extraction).
|
|
220
219
|
* argv form, never a shell string, keeps hostile filenames out of shell syntax.
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
220
|
+
* Windows drive paths use a basename relative to the archive directory by
|
|
221
|
+
* default. GNU tar cannot parse `C:\\...` as remote `host:path`, and Windows'
|
|
222
|
+
* bundled bsdtar accepts the same relative argv without GNU-only `--force-local`.
|
|
223
|
+
* Callers can still explicitly opt out with `useRelativeWindowsDrivePath: false`.
|
|
225
224
|
*/
|
|
226
225
|
export function listTarball(tarballPath, opts = {}) {
|
|
227
226
|
const platform = opts.platform ?? process.platform;
|
|
228
|
-
const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ??
|
|
227
|
+
const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ?? true;
|
|
229
228
|
const location = tarballArgvLocation(tarballPath, platform, useRelativeWindowsDrivePath);
|
|
230
229
|
const out = (opts.execFileSync ?? execFileSync)('tar', ['-tvf', location.archiveArg], {
|
|
231
230
|
encoding: 'utf-8',
|
|
@@ -330,7 +329,7 @@ export function safeExtractTarball(tarballPath, finalDir, opts = {}) {
|
|
|
330
329
|
}
|
|
331
330
|
// Resolve once so listing and extraction use the same archive location.
|
|
332
331
|
const platform = opts.platform ?? process.platform;
|
|
333
|
-
const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ??
|
|
332
|
+
const useRelativeWindowsDrivePath = opts.useRelativeWindowsDrivePath ?? true;
|
|
334
333
|
const exec = opts.execFileSync ?? execFileSync;
|
|
335
334
|
const location = tarballArgvLocation(tarballPath, platform, useRelativeWindowsDrivePath);
|
|
336
335
|
// 1. Pre-flight against headers (no bytes written yet).
|
|
@@ -120,7 +120,7 @@ function comparison(projectDirs, before, after, minHours, json, stdout) {
|
|
|
120
120
|
/** Print token usage totals from Claude session JSONL files. */
|
|
121
121
|
export function tokenUsageReport(args = [], options = {}) {
|
|
122
122
|
const { stdout, stderr } = ioFor(options);
|
|
123
|
-
let lastDays = 7, since = "", json = false, before = "", after = "", minHours = 0;
|
|
123
|
+
let lastDays = 7, since = "", json = false, before = "", after = "", minHours = 0, projectDir = options.projectDir, projectDirSeen = false;
|
|
124
124
|
for (let index = 0; index < args.length;) {
|
|
125
125
|
const arg = args[index];
|
|
126
126
|
if (arg === "--last") {
|
|
@@ -149,8 +149,22 @@ export function tokenUsageReport(args = [], options = {}) {
|
|
|
149
149
|
minHours = Number.parseFloat(args[index + 1] ?? "");
|
|
150
150
|
index += 2;
|
|
151
151
|
}
|
|
152
|
+
else if (arg === "--project-dir") {
|
|
153
|
+
const value = args[index + 1];
|
|
154
|
+
if (!value || value.startsWith("--")) {
|
|
155
|
+
line(stderr, "--project-dir requires a path");
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
if (projectDirSeen) {
|
|
159
|
+
line(stderr, "--project-dir may be specified only once");
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
projectDir = value;
|
|
163
|
+
projectDirSeen = true;
|
|
164
|
+
index += 2;
|
|
165
|
+
}
|
|
152
166
|
else if (arg === "-h" || arg === "--help") {
|
|
153
|
-
stdout("token-usage-report.sh — Daily effective-token totals + top burners from Claude Code session JSONLs.\nUsage:\n scripts/token-usage-report.sh # last 7 days, table\n scripts/token-usage-report.sh --last 14 # last 14 days\n scripts/token-usage-report.sh --since 2026-05-01\n scripts/token-usage-report.sh --json # machine-readable\nEffective tokens weighting (matches weekly rate-limit volume):\n input + 5×output + 1.25×cache_creation + 0.1×cache_read\n--- Compare-windows mode (US-011) ---\nFormat: --before YYYY-MM-DD:YYYY-MM-DD --after YYYY-MM-DD:YYYY-MM-DD\n");
|
|
167
|
+
stdout("token-usage-report.sh — Daily effective-token totals + top burners from Claude Code session JSONLs.\nUsage:\n scripts/token-usage-report.sh # last 7 days, table\n scripts/token-usage-report.sh --last 14 # last 14 days\n scripts/token-usage-report.sh --since 2026-05-01\n scripts/token-usage-report.sh --json # machine-readable\n scripts/token-usage-report.sh --project-dir <path> # use one Claude activity folder\nEffective tokens weighting (matches weekly rate-limit volume):\n input + 5×output + 1.25×cache_creation + 0.1×cache_read\n--- Compare-windows mode (US-011) ---\nFormat: --before YYYY-MM-DD:YYYY-MM-DD --after YYYY-MM-DD:YYYY-MM-DD\n");
|
|
154
168
|
return 0;
|
|
155
169
|
}
|
|
156
170
|
else {
|
|
@@ -158,7 +172,7 @@ export function tokenUsageReport(args = [], options = {}) {
|
|
|
158
172
|
return 1;
|
|
159
173
|
}
|
|
160
174
|
}
|
|
161
|
-
const projectDirs = resolveProjectDirs(options).filter((dir) => fs.existsSync(dir) && fs.statSync(dir).isDirectory());
|
|
175
|
+
const projectDirs = resolveProjectDirs({ ...options, projectDir }).filter((dir) => fs.existsSync(dir) && fs.statSync(dir).isDirectory());
|
|
162
176
|
if (before && after) {
|
|
163
177
|
comparison(projectDirs, before, after, minHours, json, stdout);
|
|
164
178
|
return 0;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { RenderContext, RenderResult } from "./shared.js";
|
|
2
|
-
export declare
|
|
2
|
+
export declare class CompanyIndexUserError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function renderCompanies(context: RenderContext, args?: string[]): RenderResult;
|
|
3
6
|
//# sourceMappingURL=companies.d.ts.map
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { basename, countImmediate, date, exists, immediateEntries, isHidden, log, projectStatus, readJson, sanitize, titleize, truncate, write } from "./shared.js";
|
|
4
|
+
export class CompanyIndexUserError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "CompanyIndexUserError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
4
10
|
function description(root, company, name) {
|
|
5
11
|
const base = `companies/${company}/${name}`;
|
|
6
12
|
const nonHidden = (child) => !isHidden(child);
|
|
@@ -17,9 +23,66 @@ function description(root, company, name) {
|
|
|
17
23
|
default: return "—";
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
|
-
export function renderCompanies(context) {
|
|
26
|
+
export function renderCompanies(context, args = []) {
|
|
27
|
+
if (args.some((arg) => arg === "--help" || arg === "-h")) {
|
|
28
|
+
(context.output ?? ((message) => process.stdout.write(message)))("Usage: hq core rebuild-index companies [--company <slug>]\n\n" +
|
|
29
|
+
"Regenerate company root INDEX.md files. Without --company, all companies are regenerated.\n" +
|
|
30
|
+
"--company <slug> regenerates only that company's index.\n" +
|
|
31
|
+
"Company-scoped writes require the cli.rebuild-index-company-scope flag.\n");
|
|
32
|
+
return { written: [] };
|
|
33
|
+
}
|
|
34
|
+
let selectedCompany;
|
|
35
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
36
|
+
const arg = args[index];
|
|
37
|
+
if (arg === "--company") {
|
|
38
|
+
if (selectedCompany !== undefined)
|
|
39
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company may be supplied once");
|
|
40
|
+
const value = args[index + 1];
|
|
41
|
+
if (!value || value.startsWith("--"))
|
|
42
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company requires a company slug");
|
|
43
|
+
selectedCompany = value;
|
|
44
|
+
index += 1;
|
|
45
|
+
}
|
|
46
|
+
else if (arg.startsWith("--company=")) {
|
|
47
|
+
if (selectedCompany !== undefined)
|
|
48
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company may be supplied once");
|
|
49
|
+
selectedCompany = arg.slice("--company=".length);
|
|
50
|
+
if (!selectedCompany)
|
|
51
|
+
throw new CompanyIndexUserError("rebuild-index companies: --company requires a company slug");
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
throw new CompanyIndexUserError(`rebuild-index companies: unknown option ${arg}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
21
57
|
const written = [];
|
|
22
|
-
|
|
58
|
+
let scopedDirectories;
|
|
59
|
+
if (selectedCompany === undefined) {
|
|
60
|
+
scopedDirectories = immediateEntries(context.root, "companies", "dir");
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
if (selectedCompany === "." || selectedCompany === ".." ||
|
|
64
|
+
selectedCompany.includes("/") || selectedCompany.includes("\\") ||
|
|
65
|
+
path.basename(selectedCompany) !== selectedCompany ||
|
|
66
|
+
selectedCompany.startsWith("_") || isHidden(selectedCompany)) {
|
|
67
|
+
throw new CompanyIndexUserError(`rebuild-index companies: invalid company slug "${selectedCompany}"`);
|
|
68
|
+
}
|
|
69
|
+
const companiesRoot = path.resolve(context.root, "companies");
|
|
70
|
+
const selectedDirectory = path.resolve(companiesRoot, selectedCompany);
|
|
71
|
+
let selectedStat;
|
|
72
|
+
try {
|
|
73
|
+
selectedStat = fs.lstatSync(selectedDirectory);
|
|
74
|
+
}
|
|
75
|
+
catch (cause) {
|
|
76
|
+
if (cause.code !== "ENOENT")
|
|
77
|
+
throw cause;
|
|
78
|
+
throw new CompanyIndexUserError(`rebuild-index companies: no company directory named "${selectedCompany}"`);
|
|
79
|
+
}
|
|
80
|
+
if (!selectedStat.isDirectory()) {
|
|
81
|
+
throw new CompanyIndexUserError(`rebuild-index companies: no company directory named "${selectedCompany}"`);
|
|
82
|
+
}
|
|
83
|
+
scopedDirectories = [selectedDirectory];
|
|
84
|
+
}
|
|
85
|
+
for (const directory of scopedDirectories) {
|
|
23
86
|
const company = basename(directory);
|
|
24
87
|
if (company.startsWith("_") || isHidden(company))
|
|
25
88
|
continue;
|
|
@@ -40,7 +103,7 @@ export function renderCompanies(context) {
|
|
|
40
103
|
const parsed = readJson(prd);
|
|
41
104
|
let text = typeof parsed?.description === "string" ? parsed.description : "";
|
|
42
105
|
text = truncate(sanitize(text), 110) || "—";
|
|
43
|
-
lines.push(`| \`${project}\` | ${projectStatus(context.root, project, prd, "active")} | ${text} |`);
|
|
106
|
+
lines.push(`| \`${project}\` | ${projectStatus(context.root, project, prd, "active", prd)} | ${text} |`);
|
|
44
107
|
}
|
|
45
108
|
}
|
|
46
109
|
else
|
|
@@ -2,4 +2,5 @@ import { RenderContext, RenderResult } from "./shared.js";
|
|
|
2
2
|
export type IndexTarget = "all" | "companies" | "company-knowledge" | "orchestrator" | "projects" | "public-knowledge" | "reports" | "social-drafts" | "threads" | "workers";
|
|
3
3
|
export declare function renderIndexTarget(target: IndexTarget, context: RenderContext, args?: string[]): RenderResult;
|
|
4
4
|
export { atomicWrite, comparePath, immediateEntries, type RenderContext, type RenderResult } from "./shared.js";
|
|
5
|
+
export { CompanyIndexUserError } from "./companies.js";
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -10,7 +10,7 @@ import { renderWorkers } from "./workers.js";
|
|
|
10
10
|
const allOrder = ["threads", "orchestrator", "companies", "projects", "company-knowledge", "public-knowledge", "workers", "reports", "social-drafts"];
|
|
11
11
|
export function renderIndexTarget(target, context, args = []) {
|
|
12
12
|
switch (target) {
|
|
13
|
-
case "companies": return renderCompanies(context);
|
|
13
|
+
case "companies": return renderCompanies(context, args);
|
|
14
14
|
case "company-knowledge": return renderCompanyKnowledge(context);
|
|
15
15
|
case "orchestrator": return renderOrchestrator(context);
|
|
16
16
|
case "projects": return renderProjects(context);
|
|
@@ -40,4 +40,5 @@ export function renderIndexTarget(target, context, args = []) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
export { atomicWrite, comparePath, immediateEntries } from "./shared.js";
|
|
43
|
+
export { CompanyIndexUserError } from "./companies.js";
|
|
43
44
|
//# sourceMappingURL=index.js.map
|
|
@@ -39,7 +39,7 @@ export declare function hasUnclosedKeepMarker(content: string): boolean;
|
|
|
39
39
|
export declare function looksAutoGenerated(content: string): boolean;
|
|
40
40
|
export declare function generatedStamp(content: string): string | undefined;
|
|
41
41
|
export declare function log(context: RenderContext, message: string): void;
|
|
42
|
-
export declare function projectStatus(root: string, project: string, prdPath: string, fallback: string): string;
|
|
42
|
+
export declare function projectStatus(root: string, project: string, prdPath: string, fallback: string, expectedStatePrdPath?: string): string;
|
|
43
43
|
export declare function basename(file: string): string;
|
|
44
44
|
export declare function isHidden(name: string): boolean;
|
|
45
45
|
export declare function mtime(file: string): number;
|
|
@@ -134,9 +134,11 @@ export function generatedStamp(content) {
|
|
|
134
134
|
return content.match(/^<!-- hq-generated-sha256: ([a-f0-9]{64}) -->/m)?.[1];
|
|
135
135
|
}
|
|
136
136
|
export function log(context, message) { context.log?.(message); }
|
|
137
|
-
export function projectStatus(root, project, prdPath, fallback) {
|
|
137
|
+
export function projectStatus(root, project, prdPath, fallback, expectedStatePrdPath) {
|
|
138
138
|
const state = readJson(at(root, `workspace/orchestrator/${project}/state.json`));
|
|
139
|
-
|
|
139
|
+
const stateMatchesPrd = expectedStatePrdPath === undefined || (typeof state?.prd_path === "string" &&
|
|
140
|
+
path.resolve(root, state.prd_path) === path.resolve(root, expectedStatePrdPath));
|
|
141
|
+
if (state && stateMatchesPrd) {
|
|
140
142
|
const raw = typeof state.status === "string" ? state.status : "active";
|
|
141
143
|
if (["completed", "complete"].includes(raw))
|
|
142
144
|
return "complete";
|
|
@@ -32,6 +32,7 @@ import { answeredQuestionEntries, questionRecordRefusalCode, raiseEnvelopeQuesti
|
|
|
32
32
|
import { SILENCE_MS, appendWatcherEvent, classifyLaneExit, isPidAlive, missingExpectedEnvelopeFile, readLatestEnvelopeFile, readLatestEnvelopeRecord, watcherEventsPath, watcherOutLogPath, writeWatcherDecision, } from "./watcher.js";
|
|
33
33
|
import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshCompany, } from "./mesh-events.js";
|
|
34
34
|
import { activeLaneStories, laneStories, terminalEnvelopeStoryStatus } from "./stories.js";
|
|
35
|
+
import { applyStorySwitchEnvelope } from "./story-switch.js";
|
|
35
36
|
import { decideEnvelopeRepair, envelopeQuestionIds, routeProviderFailure } from "./watcher.js";
|
|
36
37
|
import { PROVIDER_REJECTED_REASON, providerRejectionDetails, readProviderRejection, } from "./provider-rejection.js";
|
|
37
38
|
/** Spec §9: unanswered at the current hop for 60 minutes → one hop. */
|
|
@@ -1271,11 +1272,28 @@ async function heartbeatAndDeadHosts(hqRoot, lanes, summary, deps, now) {
|
|
|
1271
1272
|
}
|
|
1272
1273
|
if (terminalEnvelope && latest) {
|
|
1273
1274
|
const envelope = terminalEnvelope;
|
|
1274
|
-
|
|
1275
|
+
next.last_envelope = {
|
|
1276
|
+
ref: latest.ref,
|
|
1277
|
+
decision: envelope.decision,
|
|
1278
|
+
at: nowIso,
|
|
1279
|
+
artifacts: envelope.artifacts,
|
|
1280
|
+
...(envelope.blocked_reason !== undefined ? { blocked_reason: envelope.blocked_reason } : {}),
|
|
1281
|
+
};
|
|
1282
|
+
const storySwitch = applyStorySwitchEnvelope(current, envelope, nowIso, latest.ref);
|
|
1283
|
+
if (storySwitch.clearPending) {
|
|
1284
|
+
next.story_switch_pending = undefined;
|
|
1285
|
+
}
|
|
1286
|
+
if (storySwitch.storySwitchUnacknowledged !== undefined) {
|
|
1287
|
+
next.story_switch_unacknowledged = storySwitch.storySwitchUnacknowledged;
|
|
1288
|
+
next.attribution_history = storySwitch.attributionHistory ?? current.attribution_history;
|
|
1289
|
+
}
|
|
1290
|
+
const storyStatus = storySwitch.suppressCompletion
|
|
1291
|
+
? "in_progress"
|
|
1292
|
+
: terminalEnvelopeStoryStatus(current, envelope.decision, envelope.story_status);
|
|
1275
1293
|
if (storyStatus !== undefined) {
|
|
1276
|
-
next.story_status = envelope.story_status;
|
|
1277
|
-
next.story_complete = envelope.story_complete;
|
|
1278
|
-
next.story_complete_reason = envelope.story_complete_reason;
|
|
1294
|
+
next.story_status = storySwitch.suppressCompletion ? "in_progress" : envelope.story_status;
|
|
1295
|
+
next.story_complete = storySwitch.suppressCompletion ? false : envelope.story_complete;
|
|
1296
|
+
next.story_complete_reason = storySwitch.suppressCompletion ? undefined : envelope.story_complete_reason;
|
|
1279
1297
|
next.stories = laneStories(current).map((story) => story.project_id === current.project_id && story.story_id === current.story_id
|
|
1280
1298
|
? {
|
|
1281
1299
|
...story,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AttributionHistoryEntry, LaneEnvelope, LaneRecord, StorySwitchUnacknowledgedNote } from "./types.js";
|
|
2
|
+
export declare function storySwitchNotice(previousProjectId: string, previousStoryId: string, projectId: string, storyId: string): string | undefined;
|
|
3
|
+
export declare function artifactIdentityKeys(artifacts: readonly unknown[] | undefined): string[];
|
|
4
|
+
export interface StorySwitchRollbackSnapshot {
|
|
5
|
+
message_id: string;
|
|
6
|
+
project_id: string;
|
|
7
|
+
story_id: string;
|
|
8
|
+
no_story_reason?: string;
|
|
9
|
+
added_story?: NonNullable<LaneRecord["stories"]>[number];
|
|
10
|
+
story_switch_unacknowledged: StorySwitchUnacknowledgedNote | undefined;
|
|
11
|
+
}
|
|
12
|
+
/** Restore only the attribution write owned by this failed command attempt. */
|
|
13
|
+
export declare function rollbackStorySwitchAttribution(current: LaneRecord, snapshot: StorySwitchRollbackSnapshot): LaneRecord;
|
|
14
|
+
/** Apply and consume one attribution-switch marker when the next envelope arrives. */
|
|
15
|
+
export declare function applyStorySwitchEnvelope(lane: LaneRecord, envelope: LaneEnvelope, at: string, envelopeRef: string): {
|
|
16
|
+
suppressCompletion: boolean;
|
|
17
|
+
clearPending: boolean;
|
|
18
|
+
storySwitchUnacknowledged?: StorySwitchUnacknowledgedNote;
|
|
19
|
+
attributionHistory?: AttributionHistoryEntry[];
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=story-switch.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export function storySwitchNotice(previousProjectId, previousStoryId, projectId, storyId) {
|
|
2
|
+
if (previousProjectId === projectId && previousStoryId === storyId)
|
|
3
|
+
return undefined;
|
|
4
|
+
const from = `${previousProjectId}/${previousStoryId || "(no story)"}`;
|
|
5
|
+
const to = `${projectId}/${storyId || "(no story)"}`;
|
|
6
|
+
return `Story changed: ${from} -> ${to}. The previous story is closed; report only on the new story in this turn's envelope.`;
|
|
7
|
+
}
|
|
8
|
+
export function artifactIdentityKeys(artifacts) {
|
|
9
|
+
if (!Array.isArray(artifacts))
|
|
10
|
+
return [];
|
|
11
|
+
return artifacts.flatMap((artifact) => {
|
|
12
|
+
if (typeof artifact === "string" && artifact.trim())
|
|
13
|
+
return [`value:${artifact.trim()}`];
|
|
14
|
+
if (artifact === null || typeof artifact !== "object" || Array.isArray(artifact))
|
|
15
|
+
return [];
|
|
16
|
+
const record = artifact;
|
|
17
|
+
const url = typeof record.url === "string" ? record.url.trim() : "";
|
|
18
|
+
const path = typeof record.path === "string" ? record.path.trim() : "";
|
|
19
|
+
const values = [url && `url:${url}`, path && `path:${path}`].filter((value) => Boolean(value));
|
|
20
|
+
return values;
|
|
21
|
+
}).sort();
|
|
22
|
+
}
|
|
23
|
+
/** Restore only the attribution write owned by this failed command attempt. */
|
|
24
|
+
export function rollbackStorySwitchAttribution(current, snapshot) {
|
|
25
|
+
if (current.story_switch_pending?.message_id !== snapshot.message_id)
|
|
26
|
+
return current;
|
|
27
|
+
const addedStory = snapshot.added_story;
|
|
28
|
+
const stories = (current.stories ?? []).filter((story) => !(addedStory && story.project_id === addedStory.project_id && story.story_id === addedStory.story_id &&
|
|
29
|
+
story.added_at === addedStory.added_at));
|
|
30
|
+
return {
|
|
31
|
+
...current,
|
|
32
|
+
project_id: snapshot.project_id,
|
|
33
|
+
story_id: snapshot.story_id,
|
|
34
|
+
no_story_reason: snapshot.no_story_reason,
|
|
35
|
+
stories,
|
|
36
|
+
attribution_history: current.attribution_history.filter((entry) => entry.message_id !== snapshot.message_id),
|
|
37
|
+
story_switch_pending: undefined,
|
|
38
|
+
story_switch_unacknowledged: snapshot.story_switch_unacknowledged,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function sameKeys(left, right) {
|
|
42
|
+
return left.length > 0 && left.length === right.length && left.every((key, index) => key === right[index]);
|
|
43
|
+
}
|
|
44
|
+
/** Apply and consume one attribution-switch marker when the next envelope arrives. */
|
|
45
|
+
export function applyStorySwitchEnvelope(lane, envelope, at, envelopeRef) {
|
|
46
|
+
const pending = lane.story_switch_pending;
|
|
47
|
+
if (!pending)
|
|
48
|
+
return { suppressCompletion: false, clearPending: false };
|
|
49
|
+
if (envelope.story_complete !== true)
|
|
50
|
+
return { suppressCompletion: false, clearPending: false };
|
|
51
|
+
const currentKeys = artifactIdentityKeys(envelope.artifacts);
|
|
52
|
+
const suppressCompletion = sameKeys(pending.previous_artifact_keys, currentKeys);
|
|
53
|
+
if (!suppressCompletion)
|
|
54
|
+
return { suppressCompletion: false, clearPending: true };
|
|
55
|
+
const note = {
|
|
56
|
+
event: "story_switch_unacknowledged",
|
|
57
|
+
previous_project_id: pending.previous_project_id,
|
|
58
|
+
previous_story_id: pending.previous_story_id,
|
|
59
|
+
project_id: pending.project_id,
|
|
60
|
+
story_id: pending.story_id,
|
|
61
|
+
message_id: pending.message_id,
|
|
62
|
+
envelope_ref: envelopeRef,
|
|
63
|
+
at,
|
|
64
|
+
artifact_keys: currentKeys,
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
suppressCompletion: true,
|
|
68
|
+
clearPending: true,
|
|
69
|
+
storySwitchUnacknowledged: note,
|
|
70
|
+
attributionHistory: lane.attribution_history.map((entry) => entry.message_id === pending.message_id
|
|
71
|
+
? { ...entry, story_switch_unacknowledged: note }
|
|
72
|
+
: entry),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=story-switch.js.map
|
|
@@ -351,6 +351,27 @@ export interface AttributionHistoryEntry {
|
|
|
351
351
|
story_id: string;
|
|
352
352
|
message_id: string;
|
|
353
353
|
at: string;
|
|
354
|
+
story_switch_unacknowledged?: StorySwitchUnacknowledgedNote;
|
|
355
|
+
}
|
|
356
|
+
export interface StorySwitchPending {
|
|
357
|
+
previous_project_id: string;
|
|
358
|
+
previous_story_id: string;
|
|
359
|
+
project_id: string;
|
|
360
|
+
story_id: string;
|
|
361
|
+
message_id: string;
|
|
362
|
+
at: string;
|
|
363
|
+
previous_artifact_keys: string[];
|
|
364
|
+
}
|
|
365
|
+
export interface StorySwitchUnacknowledgedNote {
|
|
366
|
+
event: "story_switch_unacknowledged";
|
|
367
|
+
previous_project_id: string;
|
|
368
|
+
previous_story_id: string;
|
|
369
|
+
project_id: string;
|
|
370
|
+
story_id: string;
|
|
371
|
+
message_id: string;
|
|
372
|
+
envelope_ref: string;
|
|
373
|
+
at: string;
|
|
374
|
+
artifact_keys: string[];
|
|
354
375
|
}
|
|
355
376
|
export interface LastEnvelopeRef {
|
|
356
377
|
ref: string;
|
|
@@ -573,6 +594,10 @@ export interface LaneRecord {
|
|
|
573
594
|
envelope_summary?: string;
|
|
574
595
|
attribution_history: AttributionHistoryEntry[];
|
|
575
596
|
last_envelope?: LastEnvelopeRef;
|
|
597
|
+
/** Pending attribution change, consumed by the next terminal envelope. */
|
|
598
|
+
story_switch_pending?: StorySwitchPending;
|
|
599
|
+
/** Last completion suppressed because the new story repeated prior evidence. */
|
|
600
|
+
story_switch_unacknowledged?: StorySwitchUnacknowledgedNote;
|
|
576
601
|
/** Effective model id recorded at create/spawn. Omitted when the provider chooses. */
|
|
577
602
|
model?: string;
|
|
578
603
|
/** Where the effective model came from; repaired models stay pinned across the account move. */
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -227,7 +227,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
227
227
|
priority: ["command"], worker: ["creator", "command"], worker_path: ["creator", "command"], worker_pack: ["creator", "command"], worker_profile: ["creator", "command"], worker_profile_hash: ["creator", "command", "spawn"], registry_worker: ["creator", "command", "spawn"], registry_worker_approval_history: ["command", "watcher", "reconcile"], worker_profile_repin_history: ["command"], worker_profile_ignored: ["creator", "command", "spawn"], worker_policy_delivery: ["command", "spawn"],
|
|
228
228
|
keep_alive: ["creator", "command"], keep_alive_standing: ["command"], keep_alive_opted_in_at: ["creator", "command"], keep_alive_blocked_reason: ["command", "reconcile"], keep_alive_resume_count: ["command", "reconcile"], keep_alive_history: ["reconcile"], keep_alive_blocked_resume_streak: ["reconcile", "spawn", "watcher"], keep_alive_last_resume_at: ["command", "reconcile"], keep_alive_next_eligible_at: ["command", "reconcile"], keep_alive_exhausted_at: ["command", "reconcile"],
|
|
229
229
|
provider: ["creator"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
230
|
-
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], ended_at: ["reconcile"], envelope_summary: ["reconcile"], attribution_history: ["command"],
|
|
230
|
+
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: ["watcher", "reconcile"],
|
|
231
231
|
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"],
|
|
232
232
|
last_message_at: ["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"],
|
|
233
233
|
overdue: ["reconcile"], overdue_at: ["reconcile"], last_host_report_at: ["watcher", "spawn", "reconcile"], stalled: ["watcher"], stalled_at: ["watcher"], stalled_reason: ["watcher"],
|
|
@@ -37,6 +37,7 @@ import { LanesError } from "./errors.js";
|
|
|
37
37
|
import { questionRecordRefusalCode, raiseEnvelopeQuestions, resolveQuestionCompanyWithFallback, } from "./questions.js";
|
|
38
38
|
import { isTerminalLaneState, ENVELOPE_INVALID_REASON, parseLaneEnvelope, STORY_STATUSES, LANE_PROVIDERS, } from "./types.js";
|
|
39
39
|
import { laneStories, terminalEnvelopeStoryStatus } from "./stories.js";
|
|
40
|
+
import { applyStorySwitchEnvelope } from "./story-switch.js";
|
|
40
41
|
import { appendCappedHistory, listQuestionIds, readLane, readQuestion, retryUpdateLane, withLaneHireLocks } from "./store.js";
|
|
41
42
|
import { enforceRegistryApprovalGate } from "./approval-gate.js";
|
|
42
43
|
import { readWorkerRunResult } from "./delivery.js";
|
|
@@ -414,6 +415,7 @@ export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, op
|
|
|
414
415
|
}), current.registry_worker_approval_history ?? []);
|
|
415
416
|
}
|
|
416
417
|
if (envelope) {
|
|
418
|
+
const storySwitch = applyStorySwitchEnvelope(current, envelope, at, envelopeRef);
|
|
417
419
|
if (envelope.decision !== "blocked")
|
|
418
420
|
next.keep_alive_blocked_resume_streak = 0;
|
|
419
421
|
next.last_envelope = {
|
|
@@ -423,11 +425,20 @@ export function applyTerminalToLaneWithResult(hqRoot, laneId, classification, op
|
|
|
423
425
|
artifacts: envelope.artifacts,
|
|
424
426
|
...(envelope.blocked_reason !== undefined ? { blocked_reason: envelope.blocked_reason } : {}),
|
|
425
427
|
};
|
|
426
|
-
|
|
428
|
+
if (storySwitch.clearPending) {
|
|
429
|
+
next.story_switch_pending = undefined;
|
|
430
|
+
}
|
|
431
|
+
if (storySwitch.storySwitchUnacknowledged !== undefined) {
|
|
432
|
+
next.story_switch_unacknowledged = storySwitch.storySwitchUnacknowledged;
|
|
433
|
+
next.attribution_history = storySwitch.attributionHistory ?? current.attribution_history;
|
|
434
|
+
}
|
|
435
|
+
const storyStatus = storySwitch.suppressCompletion
|
|
436
|
+
? "in_progress"
|
|
437
|
+
: terminalEnvelopeStoryStatus(current, envelope.decision, envelope.story_status);
|
|
427
438
|
if (storyStatus !== undefined) {
|
|
428
|
-
next.story_status = envelope.story_status;
|
|
429
|
-
next.story_complete = envelope.story_complete;
|
|
430
|
-
next.story_complete_reason = envelope.story_complete_reason;
|
|
439
|
+
next.story_status = storySwitch.suppressCompletion ? "in_progress" : envelope.story_status;
|
|
440
|
+
next.story_complete = storySwitch.suppressCompletion ? false : envelope.story_complete;
|
|
441
|
+
next.story_complete_reason = storySwitch.suppressCompletion ? undefined : envelope.story_complete_reason;
|
|
431
442
|
const currentProject = current.project_id;
|
|
432
443
|
const currentStory = current.story_id;
|
|
433
444
|
const updatedStories = laneStories(current).map((story) => story.project_id === currentProject && story.story_id === currentStory
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MqttClientEventCallbacks } from "mqtt";
|
|
2
|
+
/** Event callbacks shared by the daemon's two MQTT client seams. */
|
|
3
|
+
export type MeshMqttEventMap = Pick<MqttClientEventCallbacks, "connect" | "error" | "close" | "message">;
|
|
4
|
+
export interface MeshMqttEventClient {
|
|
5
|
+
on<Event extends keyof MeshMqttEventMap>(event: Event, handler: MeshMqttEventMap[Event]): unknown;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=mqtt-events.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// @ts-expect-error unsupported event names are not part of mqtt's event map
|
|
2
|
+
presence.on("conect", () => { });
|
|
3
|
+
// @ts-expect-error MQTT message handlers receive a string topic
|
|
4
|
+
presence.on("message", (topic) => void topic);
|
|
5
|
+
// @ts-expect-error unsupported event names are not part of mqtt's event map
|
|
6
|
+
workCache.on("conect", () => { });
|
|
7
|
+
// @ts-expect-error MQTT message handlers receive a string topic
|
|
8
|
+
workCache.on("message", (topic) => void topic);
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=mqtt-events.test-d.js.map
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* - Server credential refusals use a long retry (default 10m ±20%)
|
|
13
13
|
*/
|
|
14
14
|
import { type IClientOptions, type MqttClient } from "mqtt";
|
|
15
|
+
import type { MeshMqttEventClient } from "./mqtt-events.js";
|
|
15
16
|
import { type Contract3Bundle, type CredentialsFetcher, type TimerHost } from "./credentials.js";
|
|
16
17
|
export type MqttConnectionState = "idle" | "connecting" | "connected" | "reconnecting" | "closed";
|
|
17
18
|
/**
|
|
@@ -49,8 +50,7 @@ export interface PresencePayload {
|
|
|
49
50
|
at: string;
|
|
50
51
|
}
|
|
51
52
|
/** Structural mqtt client surface used by PresenceClient (test seam). */
|
|
52
|
-
export interface MeshMqttClientLike {
|
|
53
|
-
on(event: string, handler: (...args: any[]) => void): unknown;
|
|
53
|
+
export interface MeshMqttClientLike extends MeshMqttEventClient {
|
|
54
54
|
publish(topic: string, message: string | Buffer, opts: {
|
|
55
55
|
qos: 0 | 1 | 2;
|
|
56
56
|
retain?: boolean;
|