@indigoai-us/hq-cli 5.234.1 → 5.235.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.
@@ -632,14 +632,6 @@ export async function runPassBody(hqRoot, deps) {
632
632
  const livenessDeps = deps.livenessDeps ?? defaultLivenessDeps();
633
633
  let lanes;
634
634
  let questions;
635
- if (!deps.dryRun) {
636
- try {
637
- await deps.syncTrackerQuestions?.(hqRoot);
638
- }
639
- catch (err) {
640
- markError(summary, err);
641
- }
642
- }
643
635
  try {
644
636
  lanes = loadLaneRecords(hqRoot, summary);
645
637
  questions = loadQuestionRecords(hqRoot, summary);
@@ -843,14 +835,6 @@ export async function runPassBody(hqRoot, deps) {
843
835
  await admitQueued(hqRoot, drainLanes, summary, deps, now);
844
836
  await resumeAwaiting(hqRoot, drainLanes, freshQuestions, summary, deps);
845
837
  });
846
- if (!deps.dryRun && deps.syncTrackerQuestions) {
847
- try {
848
- await deps.syncTrackerQuestions(hqRoot);
849
- }
850
- catch (err) {
851
- markError(summary, err);
852
- }
853
- }
854
838
  if (!deps.dryRun && summary.errors.length === 0) {
855
839
  try {
856
840
  writeHeartbeatFile(hqRoot, summary, nowIso);
@@ -738,15 +738,7 @@ export function parseQuestionRecord(raw, filePath) {
738
738
  value: delivered_at,
739
739
  });
740
740
  }
741
- const mesh_question_id = raw.mesh_question_id;
742
- if (mesh_question_id !== undefined && (typeof mesh_question_id !== "string" || !mesh_question_id.trim())) {
743
- throw new LaneSchemaError("mesh_question_id must be a non-empty string when present", { path: filePath, value: mesh_question_id });
744
- }
745
- const mesh_delivered_at = raw.mesh_delivered_at === undefined ? null : raw.mesh_delivered_at;
746
- if (mesh_delivered_at !== null && (typeof mesh_delivered_at !== "string" || !Number.isFinite(Date.parse(mesh_delivered_at)))) {
747
- throw new LaneSchemaError("mesh_delivered_at must be a valid timestamp or null", { path: filePath, value: mesh_delivered_at });
748
- }
749
- return { ...raw, delivered_at, mesh_delivered_at };
741
+ return { ...raw, delivered_at };
750
742
  }
751
743
  /**
752
744
  * HQ Sync sibling-twin grammar from `@indigoai-us/hq-cloud` `buildConflictPath`:
@@ -22,17 +22,13 @@ export interface TrackerStore {
22
22
  read(kind: TrackerKind, id: string): StoreValue<TrackerRecord | null>;
23
23
  list(kind: TrackerKind): StoreValue<TrackerRecord[]>;
24
24
  create(record: TrackerRecordInput): StoreValue<TrackerRecord>;
25
- update(kind: TrackerKind, id: string, expectedVersion: number, change: (current: TrackerRecord) => TrackerRecord, metadata?: {
26
- approvalQuestionId?: string;
27
- }): StoreValue<TrackerRecord>;
25
+ update(kind: TrackerKind, id: string, expectedVersion: number, change: (current: TrackerRecord) => TrackerRecord): StoreValue<TrackerRecord>;
28
26
  }
29
27
  export interface LocalTrackerStore extends TrackerStore {
30
28
  read(kind: TrackerKind, id: string): TrackerRecord | null;
31
29
  list(kind: TrackerKind): TrackerRecord[];
32
30
  create(record: TrackerRecordInput): TrackerRecord;
33
- update(kind: TrackerKind, id: string, expectedVersion: number, change: (current: TrackerRecord) => TrackerRecord, metadata?: {
34
- approvalQuestionId?: string;
35
- }): TrackerRecord;
31
+ update(kind: TrackerKind, id: string, expectedVersion: number, change: (current: TrackerRecord) => TrackerRecord): TrackerRecord;
36
32
  }
37
33
  export declare function createLocalTrackerStore(projectDirectory: string, now?: () => Date): LocalTrackerStore;
38
34
  //# sourceMappingURL=tracker-store.d.ts.map
@@ -238,7 +238,7 @@ export function createLocalTrackerStore(projectDirectory, now = () => new Date()
238
238
  writeExclusive(path.join(directory, `${id}.json`), `${JSON.stringify(record, null, 2)}\n`);
239
239
  return record;
240
240
  },
241
- update(kind, id, expectedVersion, change, _metadata) {
241
+ update(kind, id, expectedVersion, change) {
242
242
  const file = recordPath(root, kind, id);
243
243
  const directory = ensureKindDirectory(root, kind);
244
244
  return withRecordLock(file, () => {
@@ -662,10 +662,6 @@ export interface QuestionRecord {
662
662
  version: number;
663
663
  /** Null until the owner starts a resume carrying this answer; absent on old records. */
664
664
  delivered_at?: string | null;
665
- /** Work Mesh's server-issued id; absent for pre-TRK-9 local questions. */
666
- mesh_question_id?: string;
667
- /** Local checkpoint that the server accepted this question's delivery stamp. */
668
- mesh_delivered_at?: string | null;
669
665
  body: QuestionBodyRef;
670
666
  routing_history: RoutingHistoryEntry[];
671
667
  answer?: QuestionAnswer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.234.1",
3
+ "version": "5.235.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,20 +0,0 @@
1
- export declare const FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG = "cli.flag-worker-self-contained";
2
- interface CachedWorkerMode {
3
- version: number;
4
- value: boolean;
5
- }
6
- export type FlagRegistryWorkerModeCacheOperation = "read" | "write";
7
- export type FlagRegistryWorkerModeCacheStep = "mkdir" | "write_temp" | "link" | "unlink" | "prune" | "readdir" | "read";
8
- /** Build bounded filesystem diagnostics without exposing the state directory. */
9
- export declare function flagRegistryWorkerModeCacheFailureContext(error: unknown, operation: FlagRegistryWorkerModeCacheOperation, filePath: string, env?: NodeJS.ProcessEnv): {
10
- exception: unknown;
11
- tags: Record<string, string>;
12
- };
13
- /** Keep the last server snapshot decision for this one startup-mode flag. */
14
- export declare function flagRegistryWorkerModeCachePath(stateDir: string, scopeIdentifiers: readonly string[]): string;
15
- /** A missing, invalid, or inaccessible cache never enables the rollout. */
16
- export declare function readCachedFlagRegistryWorkerMode(filePath: string): CachedWorkerMode | null;
17
- export declare function writeCachedFlagRegistryWorkerMode(filePath: string, snapshot: CachedWorkerMode): void;
18
- export declare function flagRegistryStateDir(env?: NodeJS.ProcessEnv): string;
19
- export {};
20
- //# sourceMappingURL=flag-registry-worker-mode-cache.d.ts.map
@@ -1,254 +0,0 @@
1
- import { execFileSync } from "node:child_process";
2
- import { createHash, randomUUID } from "node:crypto";
3
- import * as fs from "node:fs";
4
- import * as os from "node:os";
5
- import * as path from "node:path";
6
- export const FLAG_REGISTRY_SELF_CONTAINED_WORKER_FLAG = "cli.flag-worker-self-contained";
7
- class FlagRegistryWorkerModeCacheStepError extends Error {
8
- cacheStep;
9
- originalError;
10
- constructor(cacheStep, originalError) {
11
- super(`Flag registry worker-mode cache failed at ${cacheStep}`);
12
- this.cacheStep = cacheStep;
13
- this.originalError = originalError;
14
- this.name = "FlagRegistryWorkerModeCacheStepError";
15
- }
16
- }
17
- const KNOWN_ERRNOS = new Set([
18
- "EACCES",
19
- "EBUSY",
20
- "EEXIST",
21
- "EIO",
22
- "EISDIR",
23
- "EMFILE",
24
- "ENFILE",
25
- "ENOENT",
26
- "ENOSPC",
27
- "ENOTDIR",
28
- "EPERM",
29
- "EROFS",
30
- ]);
31
- function withCacheFsStep(cacheStep, operation) {
32
- try {
33
- return operation();
34
- }
35
- catch (error) {
36
- if (error instanceof FlagRegistryWorkerModeCacheStepError)
37
- throw error;
38
- throw new FlagRegistryWorkerModeCacheStepError(cacheStep, error);
39
- }
40
- }
41
- function originalCacheError(error) {
42
- return error instanceof FlagRegistryWorkerModeCacheStepError
43
- ? error.originalError
44
- : error;
45
- }
46
- function cacheErrorCode(error) {
47
- const original = originalCacheError(error);
48
- return typeof original === "object" && original !== null && "code" in original
49
- ? original.code
50
- : undefined;
51
- }
52
- function directoryHasUchg(directory) {
53
- if (process.platform !== "darwin")
54
- return "unknown";
55
- try {
56
- const rawFlags = execFileSync("/usr/bin/stat", ["-f", "%f", directory], { timeout: 1000 }).toString("utf8").trim();
57
- if (!/^\d+$/.test(rawFlags))
58
- return "unknown";
59
- const flags = Number(rawFlags);
60
- if (!Number.isSafeInteger(flags))
61
- return "unknown";
62
- return String((flags & 0x00000002) !== 0 || (flags & 0x00020000) !== 0);
63
- }
64
- catch {
65
- // This bounded diagnostic is best-effort; preserve unknown and the original cache error.
66
- return "unknown";
67
- }
68
- }
69
- /** Build bounded filesystem diagnostics without exposing the state directory. */
70
- export function flagRegistryWorkerModeCacheFailureContext(error, operation, filePath, env = process.env) {
71
- const cacheError = error instanceof FlagRegistryWorkerModeCacheStepError
72
- ? error
73
- : undefined;
74
- const directory = path.dirname(filePath);
75
- let dirExists = "unknown";
76
- let stats;
77
- try {
78
- stats = fs.statSync(directory);
79
- dirExists = "true";
80
- }
81
- catch (metadataError) {
82
- if (cacheErrorCode(metadataError) === "ENOENT")
83
- dirExists = "false";
84
- // Keep inaccessible metadata unknown; diagnostics must not hide the cache error.
85
- }
86
- const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
87
- const ownerMatches = typeof uid === "number" && typeof stats?.uid === "number"
88
- ? String(stats.uid === uid)
89
- : "unknown";
90
- const dirMode = typeof stats?.mode === "number"
91
- ? (stats.mode & 0o777).toString(8).padStart(3, "0")
92
- : "unknown";
93
- const dirHasUchg = directoryHasUchg(directory);
94
- const rawErrno = cacheErrorCode(error);
95
- const errno = typeof rawErrno === "string" && KNOWN_ERRNOS.has(rawErrno)
96
- ? rawErrno
97
- : "UNKNOWN";
98
- const stateDirSource = env.HQ_STATE_DIR?.trim() ? "env" : "home";
99
- return {
100
- exception: originalCacheError(error),
101
- tags: {
102
- cache_op: operation,
103
- cache_step: cacheError?.cacheStep ?? (operation === "read" ? "read" : "write_temp"),
104
- errno,
105
- state_dir_source: stateDirSource,
106
- dir_exists: dirExists,
107
- dir_owner_matches_uid: ownerMatches,
108
- dir_mode: dirMode,
109
- dir_has_uchg: dirHasUchg,
110
- cache_path_kind: "<stateDir>/flags/<basename>",
111
- },
112
- };
113
- }
114
- /** Keep the last server snapshot decision for this one startup-mode flag. */
115
- export function flagRegistryWorkerModeCachePath(stateDir, scopeIdentifiers) {
116
- const basename = scopeIdentifiers.length === 0
117
- ? "worker-mode-default.json"
118
- : `worker-mode-${createHash("sha256")
119
- .update([...scopeIdentifiers].sort().join("\0"))
120
- .digest("hex")
121
- .slice(0, 24)}.json`;
122
- return path.join(stateDir, "flags", basename);
123
- }
124
- /** A missing, invalid, or inaccessible cache never enables the rollout. */
125
- export function readCachedFlagRegistryWorkerMode(filePath) {
126
- let latest = readCacheRecordIfPresent(filePath);
127
- const directory = path.dirname(filePath);
128
- if (!withCacheFsStep("read", () => fs.existsSync(directory)))
129
- return latest;
130
- const basename = path.basename(filePath);
131
- const versionedPrefix = `${basename}.v`;
132
- for (const entry of withCacheFsStep("readdir", () => fs.readdirSync(directory))) {
133
- if (!entry.startsWith(versionedPrefix) || !entry.endsWith(".json"))
134
- continue;
135
- const versionedPath = path.join(directory, entry);
136
- const record = readCacheRecordIfPresent(versionedPath);
137
- if (record)
138
- latest = newerCacheRecord(latest, record);
139
- }
140
- return latest;
141
- }
142
- export function writeCachedFlagRegistryWorkerMode(filePath, snapshot) {
143
- withCacheFsStep("mkdir", () => fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 }));
144
- const existing = readCachedFlagRegistryWorkerMode(filePath);
145
- if (existing && snapshot.version < existing.version)
146
- return;
147
- // Each registry version gets its own immutable destination. Concurrent
148
- // processes can publish v7 and v8 in either order without an older value
149
- // replacing the newer record.
150
- const versionedPath = `${filePath}.v${snapshot.version}.json`;
151
- const tempPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
152
- try {
153
- withCacheFsStep("write_temp", () => fs.writeFileSync(tempPath, `${JSON.stringify(snapshot)}\n`, {
154
- encoding: "utf8",
155
- mode: 0o600,
156
- flag: "wx",
157
- }));
158
- try {
159
- withCacheFsStep("link", () => fs.linkSync(tempPath, versionedPath));
160
- }
161
- catch (error) {
162
- if (!isAlreadyExistsError(error))
163
- throw error;
164
- const existingVersion = readCacheRecord(versionedPath);
165
- if (existingVersion.value !== snapshot.value) {
166
- throw new FlagRegistryWorkerModeCacheStepError("link", new Error("Conflicting flag-registry worker cache values share a version"));
167
- }
168
- }
169
- }
170
- finally {
171
- if (withCacheFsStep("unlink", () => fs.existsSync(tempPath))) {
172
- withCacheFsStep("unlink", () => fs.unlinkSync(tempPath));
173
- }
174
- }
175
- pruneOlderCacheRecords(filePath);
176
- }
177
- export function flagRegistryStateDir(env = process.env) {
178
- const override = env.HQ_STATE_DIR?.trim();
179
- return override ? override : path.join(os.homedir(), ".hq");
180
- }
181
- function isCachedWorkerMode(value) {
182
- return typeof value === "object" && value !== null &&
183
- "version" in value && typeof value.version === "number" &&
184
- Number.isSafeInteger(value.version) && value.version >= 0 &&
185
- "value" in value && typeof value.value === "boolean";
186
- }
187
- function readCacheRecord(filePath) {
188
- const contents = withCacheFsStep("read", () => fs.readFileSync(filePath, "utf8"));
189
- const value = JSON.parse(contents);
190
- if (!isCachedWorkerMode(value)) {
191
- throw new Error("Cached flag-registry worker mode has an invalid shape");
192
- }
193
- return value;
194
- }
195
- function newerCacheRecord(current, candidate) {
196
- if (!current || candidate.version > current.version)
197
- return candidate;
198
- if (candidate.version === current.version && candidate.value !== current.value) {
199
- throw new Error("Conflicting flag-registry worker cache values share a version");
200
- }
201
- return current;
202
- }
203
- function pruneOlderCacheRecords(filePath) {
204
- const latest = readCachedFlagRegistryWorkerMode(filePath);
205
- if (!latest)
206
- return;
207
- const directory = path.dirname(filePath);
208
- const basename = path.basename(filePath);
209
- const versionedPrefix = `${basename}.v`;
210
- for (const entry of withCacheFsStep("readdir", () => fs.readdirSync(directory))) {
211
- if (!entry.startsWith(versionedPrefix) || !entry.endsWith(".json"))
212
- continue;
213
- const recordPath = path.join(directory, entry);
214
- const record = readCacheRecordIfPresent(recordPath);
215
- if (record && record.version < latest.version) {
216
- try {
217
- withCacheFsStep("prune", () => fs.unlinkSync(recordPath));
218
- }
219
- catch (error) {
220
- if (!isNotFoundError(error))
221
- throw error;
222
- }
223
- }
224
- }
225
- if (withCacheFsStep("prune", () => fs.existsSync(filePath))) {
226
- const legacyRecord = readCacheRecordIfPresent(filePath);
227
- if (legacyRecord && legacyRecord.version < latest.version) {
228
- try {
229
- withCacheFsStep("prune", () => fs.unlinkSync(filePath));
230
- }
231
- catch (error) {
232
- if (!isNotFoundError(error))
233
- throw error;
234
- }
235
- }
236
- }
237
- }
238
- function readCacheRecordIfPresent(filePath) {
239
- try {
240
- return readCacheRecord(filePath);
241
- }
242
- catch (error) {
243
- if (isNotFoundError(error))
244
- return null;
245
- throw error;
246
- }
247
- }
248
- function isAlreadyExistsError(error) {
249
- return cacheErrorCode(error) === "EEXIST";
250
- }
251
- function isNotFoundError(error) {
252
- return cacheErrorCode(error) === "ENOENT";
253
- }
254
- //# sourceMappingURL=flag-registry-worker-mode-cache.js.map
@@ -1,54 +0,0 @@
1
- import type { LaneCompanyRef, QuestionRecord } from "./types.js";
2
- import { type TrackerMeshRequest } from "./tracker-mesh.js";
3
- export type TrackerMeshQuestionAudience = "owner" | "operator" | "lane";
4
- export type TrackerMeshQuestionStatus = "pending" | "answered" | "cancelled";
5
- export interface TrackerMeshQuestionRow {
6
- questionId: string;
7
- threadId: string;
8
- companyUid: string;
9
- projectId: string;
10
- laneId: string;
11
- host: {
12
- kind: string;
13
- id: string;
14
- };
15
- audience: TrackerMeshQuestionAudience;
16
- audienceChain: string[];
17
- routingHistory: Array<Record<string, string>>;
18
- question: string;
19
- context?: string;
20
- options: string[];
21
- recommended?: string;
22
- askedAt: string;
23
- status: TrackerMeshQuestionStatus;
24
- choice?: string;
25
- notes?: string;
26
- answeredBy?: string;
27
- answeredAt?: string;
28
- deliveredAt?: string;
29
- }
30
- export interface TrackerMeshQuestionInput {
31
- laneId: string;
32
- threadId?: string;
33
- projectId: string;
34
- audience: TrackerMeshQuestionAudience;
35
- audienceChain: string[];
36
- routingHistory: Array<Record<string, string>>;
37
- question: string;
38
- context: string;
39
- options: string[];
40
- recommended?: string;
41
- }
42
- export interface TrackerMeshQuestionClient {
43
- create(input: TrackerMeshQuestionInput): Promise<TrackerMeshQuestionRow>;
44
- list(statuses?: readonly TrackerMeshQuestionStatus[]): Promise<TrackerMeshQuestionRow[]>;
45
- answer(questionId: string, choice: string, notes?: string): Promise<TrackerMeshQuestionRow>;
46
- delivered(questionId: string): Promise<TrackerMeshQuestionRow>;
47
- }
48
- /** Persist a local delivery checkpoint only after the Work Mesh stamp succeeds. */
49
- export declare function deliverTrackerMeshQuestionOnce(question: QuestionRecord, company: LaneCompanyRef, client: Pick<TrackerMeshQuestionClient, "delivered">, persist: (deliveredAt: string) => QuestionRecord): Promise<QuestionRecord>;
50
- export declare function createTrackerMeshQuestionClient(options: {
51
- company: LaneCompanyRef;
52
- request?: TrackerMeshRequest;
53
- }): TrackerMeshQuestionClient;
54
- //# sourceMappingURL=tracker-mesh-questions.d.ts.map
@@ -1,138 +0,0 @@
1
- import { meshJson, requireToken } from "../mesh/client.js";
2
- import { LanesError } from "./errors.js";
3
- import { mapWorkMeshTrackerError } from "./tracker-mesh.js";
4
- /** Persist a local delivery checkpoint only after the Work Mesh stamp succeeds. */
5
- export async function deliverTrackerMeshQuestionOnce(question, company, client, persist) {
6
- if (!question.mesh_question_id || question.status !== "answered" ||
7
- !question.delivered_at || question.mesh_delivered_at)
8
- return question;
9
- const delivered = await client.delivered(question.mesh_question_id);
10
- if (delivered.questionId !== question.mesh_question_id ||
11
- delivered.companyUid !== company.uid ||
12
- delivered.laneId !== question.lane_id ||
13
- delivered.projectId !== question.project_id ||
14
- !delivered.deliveredAt) {
15
- throw new LanesError("tracker_mesh_response_invalid", "Work Mesh did not stamp the expected question as delivered", {
16
- mesh_question_id: question.mesh_question_id,
17
- question_id: question.question_id,
18
- });
19
- }
20
- return persist(delivered.deliveredAt);
21
- }
22
- const ROOT = "/v1/work-mesh/tracker/questions";
23
- const AUDIENCES = ["owner", "operator", "lane"];
24
- const STATUSES = ["pending", "answered", "cancelled"];
25
- const MAX_PAGES_PER_FILTER = 10;
26
- function invalidResponse(field) {
27
- throw new LanesError("tracker_mesh_response_invalid", `Work Mesh returned an invalid tracker question ${field}`, { field });
28
- }
29
- function parseQuestion(raw, company) {
30
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
31
- return invalidResponse("row");
32
- const row = raw;
33
- if (typeof row.questionId !== "string" || !row.questionId ||
34
- typeof row.threadId !== "string" || !row.threadId ||
35
- row.companyUid !== company.uid ||
36
- typeof row.projectId !== "string" || !row.projectId ||
37
- typeof row.laneId !== "string" || !row.laneId ||
38
- !AUDIENCES.includes(row.audience) ||
39
- !STATUSES.includes(row.status) ||
40
- typeof row.question !== "string" ||
41
- !Array.isArray(row.options) || row.options.some((entry) => typeof entry !== "string") ||
42
- !Array.isArray(row.audienceChain) || row.audienceChain.some((entry) => typeof entry !== "string") ||
43
- !Array.isArray(row.routingHistory) ||
44
- typeof row.askedAt !== "string" || !Number.isFinite(Date.parse(row.askedAt)))
45
- return invalidResponse("row");
46
- if (row.context !== undefined && typeof row.context !== "string")
47
- return invalidResponse("context");
48
- if (row.recommended !== undefined && typeof row.recommended !== "string")
49
- return invalidResponse("recommended");
50
- if (row.choice !== undefined && typeof row.choice !== "string")
51
- return invalidResponse("choice");
52
- if (row.notes !== undefined && typeof row.notes !== "string")
53
- return invalidResponse("notes");
54
- if (row.answeredBy !== undefined && typeof row.answeredBy !== "string")
55
- return invalidResponse("answeredBy");
56
- if (row.answeredAt !== undefined && (typeof row.answeredAt !== "string" || !Number.isFinite(Date.parse(row.answeredAt))))
57
- return invalidResponse("answeredAt");
58
- if (row.deliveredAt !== undefined && (typeof row.deliveredAt !== "string" || !Number.isFinite(Date.parse(row.deliveredAt))))
59
- return invalidResponse("deliveredAt");
60
- if (!row.host || typeof row.host !== "object" || Array.isArray(row.host) || typeof row.host.kind !== "string" || typeof row.host.id !== "string")
61
- return invalidResponse("host");
62
- return row;
63
- }
64
- export function createTrackerMeshQuestionClient(options) {
65
- const { company } = options;
66
- if (!company.slug.trim() || !company.uid.trim()) {
67
- throw new LanesError("tracker_company_unresolved", "A resolved company slug and uid are required for Work Mesh tracker questions", {});
68
- }
69
- const request = options.request ?? (async (endpoint, init) => {
70
- const token = await requireToken({ interactive: false });
71
- return meshJson(token, endpoint, init);
72
- });
73
- const call = async (endpoint, init) => {
74
- try {
75
- const result = await request(endpoint, init);
76
- if (!result || typeof result !== "object" || Array.isArray(result))
77
- return invalidResponse("response");
78
- return result;
79
- }
80
- catch (err) {
81
- throw mapWorkMeshTrackerError(err);
82
- }
83
- };
84
- return {
85
- async create(input) {
86
- const result = await call(ROOT, { method: "POST", body: { companyUid: company.uid, ...input } });
87
- return parseQuestion(result.question, company);
88
- },
89
- async list(requestedStatuses = STATUSES) {
90
- const uniqueStatuses = [...new Set(requestedStatuses)];
91
- if (uniqueStatuses.some((status) => !STATUSES.includes(status))) {
92
- throw new LanesError("tracker_mesh_query_invalid", "Unsupported Work Mesh tracker question status filter", { statuses: uniqueStatuses });
93
- }
94
- const rows = new Map();
95
- for (const audience of AUDIENCES) {
96
- for (const status of uniqueStatuses) {
97
- let cursor;
98
- for (let page = 0; page < MAX_PAGES_PER_FILTER; page += 1) {
99
- const query = { companyUid: company.uid, audience, status, limit: "100", ...(cursor ? { cursor } : {}) };
100
- const result = await call(ROOT, { method: "GET", query });
101
- if (!Array.isArray(result.questions))
102
- return invalidResponse("questions list");
103
- for (const raw of result.questions) {
104
- const row = parseQuestion(raw, company);
105
- if (row.audience !== audience || row.status !== status)
106
- return invalidResponse("filter scope");
107
- rows.set(row.questionId, row);
108
- }
109
- if (result.nextCursor === undefined)
110
- break;
111
- if (typeof result.nextCursor !== "string" || !result.nextCursor || result.nextCursor === cursor)
112
- return invalidResponse("cursor");
113
- if (page === MAX_PAGES_PER_FILTER - 1) {
114
- throw new LanesError("tracker_mesh_response_incomplete", "Work Mesh tracker question list exceeded its bounded page limit", { audience, status, max_pages: MAX_PAGES_PER_FILTER });
115
- }
116
- cursor = result.nextCursor;
117
- }
118
- }
119
- }
120
- return [...rows.values()].sort((a, b) => b.askedAt.localeCompare(a.askedAt) || a.questionId.localeCompare(b.questionId));
121
- },
122
- async answer(questionId, choice, notes) {
123
- const result = await call(`${ROOT}/${encodeURIComponent(questionId)}/answer`, {
124
- method: "POST",
125
- body: { companyUid: company.uid, choice, ...(notes === undefined ? {} : { notes }) },
126
- });
127
- return parseQuestion(result.question, company);
128
- },
129
- async delivered(questionId) {
130
- const result = await call(`${ROOT}/${encodeURIComponent(questionId)}/delivered`, {
131
- method: "POST",
132
- body: { companyUid: company.uid },
133
- });
134
- return parseQuestion(result.question, company);
135
- },
136
- };
137
- }
138
- //# sourceMappingURL=tracker-mesh-questions.js.map
@@ -1,19 +0,0 @@
1
- import { LanesError } from "./errors.js";
2
- import type { LaneCompanyRef } from "./types.js";
3
- import type { TrackerStore } from "./tracker-store.js";
4
- export interface TrackerMeshRequestInit {
5
- method?: string;
6
- body?: Record<string, unknown>;
7
- query?: Record<string, string>;
8
- }
9
- export type TrackerMeshRequest = (path: string, init: TrackerMeshRequestInit) => Promise<unknown>;
10
- export interface WorkMeshTrackerStoreOptions {
11
- company: LaneCompanyRef;
12
- projectId: string;
13
- request?: TrackerMeshRequest;
14
- now?: () => Date;
15
- clientRecordId?: () => string;
16
- }
17
- export declare function mapWorkMeshTrackerError(err: unknown): LanesError;
18
- export declare function createWorkMeshTrackerStore(options: WorkMeshTrackerStoreOptions): TrackerStore;
19
- //# sourceMappingURL=tracker-mesh.d.ts.map