@indigoai-us/hq-cli 5.345.191 → 5.345.193
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 +398 -0
- package/assets/bot-workers/setup/worker.yaml +91 -34
- package/dist/command-catalog.generated.d.ts +96 -19
- package/dist/command-catalog.generated.js +124 -21
- package/dist/commands/bot-jobs.d.ts +21 -18
- package/dist/commands/bot-jobs.js +127 -39
- package/dist/commands/bot.d.ts +4 -3
- package/dist/commands/bot.js +265 -140
- package/dist/commands/core-jobs-schedule.js +10 -0
- package/dist/commands/daemon.js +37 -0
- package/dist/commands/files.d.ts +41 -6
- package/dist/commands/files.js +108 -27
- package/dist/commands/pack-start-bot.d.ts +4 -6
- package/dist/commands/pack-start-bot.js +4 -8
- package/dist/commands/signals-run.d.ts +45 -0
- package/dist/commands/signals-run.js +175 -0
- package/dist/commands/signals.d.ts +1 -0
- package/dist/commands/signals.js +3 -0
- package/dist/commands/sources.d.ts +11 -0
- package/dist/commands/sources.js +137 -25
- package/dist/commands/staff/reset-test-account.d.ts +10 -0
- package/dist/commands/staff/reset-test-account.js +18 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +31171 -52512
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/bot/api.d.ts +3 -2
- package/dist/lib/bot/config.d.ts +2 -0
- package/dist/lib/bot/fleet-cron.d.ts +121 -0
- package/dist/lib/bot/fleet-cron.js +319 -0
- package/dist/lib/bot/fleet-local.d.ts +93 -1
- package/dist/lib/bot/fleet-local.js +546 -34
- package/dist/lib/bot/fleet-owner-companies.d.ts +12 -0
- package/dist/lib/bot/fleet-owner-companies.js +30 -0
- package/dist/lib/bot/fleet-watch.d.ts +25 -0
- package/dist/lib/bot/fleet-watch.js +51 -0
- package/dist/lib/bot/index.d.ts +1 -1
- package/dist/lib/bot/index.js +1 -1
- package/dist/lib/bot/job-run.d.ts +5 -4
- package/dist/lib/bot/job-run.js +5 -4
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +57 -3
- package/dist/lib/bot/jobs/hot-signal-sync.js +163 -4
- package/dist/lib/bot/local-jobs.d.ts +13 -2
- package/dist/lib/bot/local-jobs.js +17 -4
- package/dist/lib/bot/progress.d.ts +14 -0
- package/dist/lib/bot/progress.js +47 -1
- package/dist/lib/bot/run.d.ts +55 -2
- package/dist/lib/bot/run.js +192 -10
- package/dist/lib/bot/setup-tool-offer.d.ts +1 -0
- package/dist/lib/bot/setup-tool-offer.js +16 -0
- package/dist/lib/bot/signals-engine-flag.d.ts +18 -0
- package/dist/lib/bot/signals-engine-flag.js +30 -0
- package/dist/lib/daemon/catalog.d.ts +4 -0
- package/dist/lib/daemon/catalog.js +29 -9
- package/dist/lib/daemon/legacy.d.ts +6 -0
- package/dist/lib/daemon/legacy.js +16 -10
- package/dist/lib/daemon/run.d.ts +1 -0
- package/dist/lib/daemon/run.js +10 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/flag-registry.d.ts +13 -1
- package/dist/lib/flag-registry.js +46 -0
- package/dist/lib/hot-channels/adapters/attio.d.ts +75 -2
- package/dist/lib/hot-channels/adapters/attio.js +367 -29
- package/dist/lib/hot-channels/adapters/gmail.d.ts +14 -0
- package/dist/lib/hot-channels/adapters/gmail.js +59 -2
- package/dist/lib/hot-channels/adapters/linear.d.ts +24 -0
- package/dist/lib/hot-channels/adapters/linear.js +71 -1
- package/dist/lib/hot-channels/adapters/meetings.js +4 -1
- package/dist/lib/hot-channels/adapters/slack.js +7 -3
- package/dist/lib/hot-channels/alert.js +1 -1
- package/dist/lib/hot-channels/contract.d.ts +11 -0
- package/dist/lib/hot-channels/contract.js +1 -0
- package/dist/lib/hot-channels/cursors.js +1 -1
- package/dist/lib/hot-channels/landing.d.ts +6 -2
- package/dist/lib/hot-channels/landing.js +13 -6
- package/dist/lib/hot-channels/ledger.js +2 -0
- package/dist/lib/hot-channels/loop.js +18 -4
- package/dist/lib/hot-channels/registry.js +9 -1
- package/dist/lib/hot-channels/run.d.ts +18 -0
- package/dist/lib/hot-channels/run.js +15 -4
- package/dist/lib/hot-channels/signals-pass.d.ts +1 -0
- package/dist/lib/hot-channels/signals-pass.js +7 -5
- package/dist/lib/hot-channels/sync-lock.js +22 -6
- package/dist/lib/mesh/live/daemon/install.d.ts +22 -0
- package/dist/lib/mesh/live/daemon/install.js +117 -30
- package/dist/lib/signals/adapters-catalog.d.ts +77 -0
- package/dist/lib/signals/adapters-catalog.js +194 -0
- package/dist/lib/signals/engine.d.ts +196 -0
- package/dist/lib/signals/engine.js +495 -0
- package/dist/lib/signals/failure-alert.d.ts +89 -0
- package/dist/lib/signals/failure-alert.js +175 -0
- package/dist/lib/signals/local-date.d.ts +3 -0
- package/dist/lib/signals/local-date.js +6 -0
- package/dist/lib/signals/projection.d.ts +42 -0
- package/dist/lib/signals/projection.js +67 -0
- package/dist/lib/signals/reply.d.ts +112 -0
- package/dist/lib/signals/reply.js +368 -0
- package/dist/lib/signals/run-lock.d.ts +36 -0
- package/dist/lib/signals/run-lock.js +148 -0
- package/dist/lib/signals/run-record.d.ts +24 -0
- package/dist/lib/signals/run-record.js +82 -0
- package/dist/lib/signals/sanitize.d.ts +105 -0
- package/dist/lib/signals/sanitize.js +439 -0
- package/dist/lib/signals/settings.d.ts +10 -0
- package/dist/lib/signals/settings.js +48 -0
- package/dist/lib/signals/steps/adapters.d.ts +14 -0
- package/dist/lib/signals/steps/adapters.js +156 -0
- package/dist/lib/signals/steps/entity-similarity.d.ts +10 -0
- package/dist/lib/signals/steps/entity-similarity.js +47 -0
- package/dist/lib/signals/steps/extract.d.ts +113 -0
- package/dist/lib/signals/steps/extract.js +765 -0
- package/dist/lib/signals/steps/garden.d.ts +24 -0
- package/dist/lib/signals/steps/garden.js +250 -0
- package/dist/lib/signals/steps/types.d.ts +112 -0
- package/dist/lib/signals/steps/types.js +4 -0
- package/dist/lib/signals/steps/upload.d.ts +43 -0
- package/dist/lib/signals/steps/upload.js +324 -0
- package/dist/lib/signals/write-guard.d.ts +63 -0
- package/dist/lib/signals/write-guard.js +287 -0
- package/dist/utils/resident-package-preload.js +16 -0
- package/package.json +9 -9
- package/dist/lib/bot/fleet-default-flag.d.ts +0 -31
- package/dist/lib/bot/fleet-default-flag.js +0 -127
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SignalsStep, StepResult } from "./types.js";
|
|
2
|
+
interface GardenReport {
|
|
3
|
+
candidates_processed?: number;
|
|
4
|
+
entities_created?: number;
|
|
5
|
+
entities_updated?: number;
|
|
6
|
+
signals_promoted?: number;
|
|
7
|
+
facts_written?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Book manuscript paths the gardener must never touch. */
|
|
10
|
+
export declare const BYA_BOOK_PATTERNS: RegExp[];
|
|
11
|
+
/** itemsOut = promoted entities + promoted signals + facts written. */
|
|
12
|
+
export declare function mapGardenReport(r: GardenReport): Pick<StepResult, "itemsIn" | "itemsOut">;
|
|
13
|
+
export declare function isByaBook(company: string, ref: string): boolean;
|
|
14
|
+
type Fm = Record<string, string | string[]>;
|
|
15
|
+
/** Same flat key: value reader as ontology-garden.mjs. */
|
|
16
|
+
export declare function readFrontmatter(text: string): {
|
|
17
|
+
fm: Fm;
|
|
18
|
+
body: string;
|
|
19
|
+
};
|
|
20
|
+
/** Pending candidate files (relative to hqRoot) under one _candidates dir. */
|
|
21
|
+
export declare function listPending(hqRoot: string, candRel: string): string[];
|
|
22
|
+
export declare const gardenStep: SignalsStep;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=garden.d.ts.map
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* garden step: runs core/scripts/ontology-garden.mjs for one company.
|
|
3
|
+
*
|
|
4
|
+
* Around the script it:
|
|
5
|
+
* - counts pending candidates (itemsIn) before the run,
|
|
6
|
+
* - holds back bya-book candidates (policy indigo-bya-book-skip-ontology-gardening)
|
|
7
|
+
* so they are not promoted, then puts them back where they were (skipped),
|
|
8
|
+
* - moves entity candidates whose name is 0.8..0.95 similar to an existing
|
|
9
|
+
* entity of the same type, or to an earlier pending candidate of the same
|
|
10
|
+
* type that this run keeps, to ontology/_candidates/_review/{date}/ ({date}
|
|
11
|
+
* is the engine's local date) and appends a line to
|
|
12
|
+
* ontology/review/merges.jsonl (review),
|
|
13
|
+
* - first puts back any candidates an earlier crashed run left in _held/.
|
|
14
|
+
* Only exact slug or alias matches are merged, by ontology-garden.mjs itself;
|
|
15
|
+
* this step adds no merging of its own for the >= 0.95 band.
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from "node:fs";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { localDate } from "../local-date.js";
|
|
20
|
+
import { assertAllowedPath, assertNoEscapingLinks, guardedRemove, guardedRmdir } from "../write-guard.js";
|
|
21
|
+
import { nameSimilarity, similarityBand } from "./entity-similarity.js";
|
|
22
|
+
/** Book manuscript paths the gardener must never touch. */
|
|
23
|
+
export const BYA_BOOK_PATTERNS = [
|
|
24
|
+
/(^|[\s/:"'])companies\/indigo\/knowledge\/book(\/|$)/,
|
|
25
|
+
/(^|[\s:"'])knowledge\/book\/build-your-own-agi\.md/,
|
|
26
|
+
];
|
|
27
|
+
const HOLD_DIR = "_held";
|
|
28
|
+
const REVIEW_DIR = "_review";
|
|
29
|
+
const SKIP_DIRS = new Set(["_done", HOLD_DIR, REVIEW_DIR]);
|
|
30
|
+
/** itemsOut = promoted entities + promoted signals + facts written. */
|
|
31
|
+
export function mapGardenReport(r) {
|
|
32
|
+
const n = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
33
|
+
return {
|
|
34
|
+
itemsIn: n(r.candidates_processed),
|
|
35
|
+
itemsOut: n(r.entities_created) + n(r.signals_promoted) + n(r.facts_written),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function isByaBook(company, ref) {
|
|
39
|
+
if (!ref)
|
|
40
|
+
return false;
|
|
41
|
+
if (BYA_BOOK_PATTERNS.some((re) => re.test(ref)))
|
|
42
|
+
return true;
|
|
43
|
+
return company === "indigo" && /^["']?knowledge\/book(\/|$)/.test(ref);
|
|
44
|
+
}
|
|
45
|
+
/** Same flat key: value reader as ontology-garden.mjs. */
|
|
46
|
+
export function readFrontmatter(text) {
|
|
47
|
+
const m = text.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
48
|
+
if (!m)
|
|
49
|
+
return { fm: {}, body: text.trim() };
|
|
50
|
+
const fm = {};
|
|
51
|
+
for (const line of m[1].split("\n")) {
|
|
52
|
+
const mm = line.match(/^([A-Za-z_]+):\s*(.*)$/);
|
|
53
|
+
if (!mm)
|
|
54
|
+
continue;
|
|
55
|
+
const v = mm[2].trim();
|
|
56
|
+
fm[mm[1]] = v.startsWith("[") && v.endsWith("]")
|
|
57
|
+
? v.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean)
|
|
58
|
+
: v.replace(/^["']|["']$/g, "");
|
|
59
|
+
}
|
|
60
|
+
return { fm, body: m[2].trim() };
|
|
61
|
+
}
|
|
62
|
+
const str = (v) => (Array.isArray(v) ? v.join(",") : v ?? "");
|
|
63
|
+
/** Pending candidate files (relative to hqRoot) under one _candidates dir. */
|
|
64
|
+
export function listPending(hqRoot, candRel) {
|
|
65
|
+
const dir = path.join(hqRoot, candRel);
|
|
66
|
+
if (!fs.existsSync(dir))
|
|
67
|
+
return [];
|
|
68
|
+
const out = [];
|
|
69
|
+
for (const d of fs.readdirSync(dir).sort()) {
|
|
70
|
+
if (SKIP_DIRS.has(d))
|
|
71
|
+
continue;
|
|
72
|
+
const full = path.join(dir, d);
|
|
73
|
+
if (!fs.statSync(full).isDirectory())
|
|
74
|
+
continue;
|
|
75
|
+
for (const f of fs.readdirSync(full).sort())
|
|
76
|
+
if (f.endsWith(".md"))
|
|
77
|
+
out.push(`${candRel}/${d}/${f}`);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Move a file through the guarded writer, then remove the source through the
|
|
83
|
+
* guard. The source is checked first, scoped to this company, so a link into
|
|
84
|
+
* another company's folder is refused before anything is read or written.
|
|
85
|
+
*/
|
|
86
|
+
function guardedMove(ctx, fromRel, toRel) {
|
|
87
|
+
const scope = { company: ctx.company };
|
|
88
|
+
const fromAbs = assertAllowedPath(ctx.hqRoot, fromRel, scope);
|
|
89
|
+
const data = fs.readFileSync(fromAbs, "utf8");
|
|
90
|
+
ctx.write(toRel, data);
|
|
91
|
+
guardedRemove(ctx.hqRoot, fromRel, scope);
|
|
92
|
+
}
|
|
93
|
+
/** Remove rel and its subdirs when they hold no files. Links are neither followed nor removed. */
|
|
94
|
+
function removeEmptyDirs(ctx, rel) {
|
|
95
|
+
const abs = path.join(ctx.hqRoot, rel);
|
|
96
|
+
let st;
|
|
97
|
+
try {
|
|
98
|
+
st = fs.lstatSync(abs);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!st.isDirectory())
|
|
104
|
+
return;
|
|
105
|
+
for (const e of fs.readdirSync(abs))
|
|
106
|
+
removeEmptyDirs(ctx, `${rel}/${e}`);
|
|
107
|
+
if (fs.readdirSync(abs).length === 0)
|
|
108
|
+
guardedRmdir(ctx.hqRoot, rel, { company: ctx.company });
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Move files left under {candRoot}/_held/ back to {candRoot}/, unless a file
|
|
112
|
+
* already sits at the original path (then the held copy is left for a person).
|
|
113
|
+
*/
|
|
114
|
+
function restoreStranded(ctx, candRoot) {
|
|
115
|
+
const holdRoot = `${candRoot}/${HOLD_DIR}`;
|
|
116
|
+
const abs = path.join(ctx.hqRoot, holdRoot);
|
|
117
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory())
|
|
118
|
+
return;
|
|
119
|
+
for (const d of fs.readdirSync(abs).sort()) {
|
|
120
|
+
const dir = path.join(abs, d);
|
|
121
|
+
if (!fs.statSync(dir).isDirectory())
|
|
122
|
+
continue;
|
|
123
|
+
for (const f of fs.readdirSync(dir).sort()) {
|
|
124
|
+
const back = `${candRoot}/${d}/${f}`;
|
|
125
|
+
if (!fs.existsSync(path.join(ctx.hqRoot, back)))
|
|
126
|
+
guardedMove(ctx, `${holdRoot}/${d}/${f}`, back);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
removeEmptyDirs(ctx, holdRoot);
|
|
130
|
+
}
|
|
131
|
+
function loadEntities(hqRoot, ontRel) {
|
|
132
|
+
const dir = path.join(hqRoot, ontRel, "entities");
|
|
133
|
+
if (!fs.existsSync(dir))
|
|
134
|
+
return [];
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const t of fs.readdirSync(dir)) {
|
|
137
|
+
const td = path.join(dir, t);
|
|
138
|
+
if (!fs.statSync(td).isDirectory())
|
|
139
|
+
continue;
|
|
140
|
+
for (const f of fs.readdirSync(td)) {
|
|
141
|
+
if (!f.endsWith(".md"))
|
|
142
|
+
continue;
|
|
143
|
+
const { fm } = readFrontmatter(fs.readFileSync(path.join(td, f), "utf8"));
|
|
144
|
+
const aliases = Array.isArray(fm.aliases) ? fm.aliases : [];
|
|
145
|
+
const names = [str(fm.canonical_name), ...aliases].filter(Boolean);
|
|
146
|
+
out.push({ type: str(fm.type) || t, names });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
export const gardenStep = {
|
|
152
|
+
name: "garden",
|
|
153
|
+
async run(ctx) {
|
|
154
|
+
const script = path.join(ctx.hqRoot, "core", "scripts", "ontology-garden.mjs");
|
|
155
|
+
if (!fs.existsSync(script)) {
|
|
156
|
+
return { status: "skipped", itemsIn: 0, itemsOut: 0, skippedReason: "garden-script-missing" };
|
|
157
|
+
}
|
|
158
|
+
const coRel = `companies/${ctx.company}`;
|
|
159
|
+
const ontRel = `${coRel}/ontology`;
|
|
160
|
+
const entCand = `${ontRel}/_candidates`;
|
|
161
|
+
const sigCand = `${coRel}/signals/_candidates`;
|
|
162
|
+
const date = localDate(ctx.now());
|
|
163
|
+
// A run that crashed between holding and restoring left candidates in
|
|
164
|
+
// _held/. Put them back first; the scan below holds them again if needed.
|
|
165
|
+
// Fail closed before reading anything: a link in either candidate tree
|
|
166
|
+
// that leaves this company's folder stops the step.
|
|
167
|
+
for (const root of [entCand, sigCand])
|
|
168
|
+
assertNoEscapingLinks(ctx.hqRoot, root, { company: ctx.company });
|
|
169
|
+
for (const root of [entCand, sigCand])
|
|
170
|
+
restoreStranded(ctx, root);
|
|
171
|
+
const pending = [...listPending(ctx.hqRoot, entCand), ...listPending(ctx.hqRoot, sigCand)];
|
|
172
|
+
const itemsIn = pending.length;
|
|
173
|
+
const held = [];
|
|
174
|
+
// Names each candidate is compared against: existing entities, plus every
|
|
175
|
+
// entity candidate this scan keeps, so two pending near-duplicates of each
|
|
176
|
+
// other are caught too (the later one is queued, the earlier one goes on).
|
|
177
|
+
const known = loadEntities(ctx.hqRoot, ontRel).map((e) => ({ ...e, against: "entity" }));
|
|
178
|
+
const mergeLines = [];
|
|
179
|
+
for (const rel of pending) {
|
|
180
|
+
const { fm, body } = readFrontmatter(fs.readFileSync(path.join(ctx.hqRoot, rel), "utf8"));
|
|
181
|
+
if (isByaBook(ctx.company, str(fm.source_ref)) || isByaBook(ctx.company, str(fm.path))) {
|
|
182
|
+
const candRoot = rel.startsWith(`${entCand}/`) ? entCand : sigCand;
|
|
183
|
+
held.push({ from: rel, to: `${candRoot}/${HOLD_DIR}/${rel.slice(candRoot.length + 1)}` });
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (!rel.startsWith(`${entCand}/`))
|
|
187
|
+
continue;
|
|
188
|
+
const name = body.split("\n")[0].trim();
|
|
189
|
+
const type = str(fm.type);
|
|
190
|
+
if (!name || !type)
|
|
191
|
+
continue;
|
|
192
|
+
let best = null;
|
|
193
|
+
for (const e of known) {
|
|
194
|
+
if (e.type !== type)
|
|
195
|
+
continue;
|
|
196
|
+
for (const n of e.names) {
|
|
197
|
+
const sim = nameSimilarity(name, n);
|
|
198
|
+
if (!best || sim > best.sim)
|
|
199
|
+
best = { sim, existing: n, against: e.against };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (best && similarityBand(best.sim) === "uncertain") {
|
|
203
|
+
mergeLines.push(JSON.stringify({
|
|
204
|
+
candidate: name,
|
|
205
|
+
existing: best.existing,
|
|
206
|
+
similarity: Math.round(best.sim * 1000) / 1000,
|
|
207
|
+
type,
|
|
208
|
+
against: best.against,
|
|
209
|
+
at: ctx.now().toISOString(),
|
|
210
|
+
}));
|
|
211
|
+
guardedMove(ctx, rel, `${entCand}/${REVIEW_DIR}/${date}/${path.basename(rel)}`);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
known.push({ type, names: [name], against: "candidate" });
|
|
215
|
+
}
|
|
216
|
+
if (mergeLines.length) {
|
|
217
|
+
const mRel = `${ontRel}/review/merges.jsonl`;
|
|
218
|
+
const mAbs = path.join(ctx.hqRoot, mRel);
|
|
219
|
+
const prev = fs.existsSync(mAbs) ? fs.readFileSync(mAbs, "utf8") : "";
|
|
220
|
+
const sep = prev && !prev.endsWith("\n") ? "\n" : "";
|
|
221
|
+
ctx.write(mRel, `${prev}${sep}${mergeLines.join("\n")}\n`);
|
|
222
|
+
}
|
|
223
|
+
const base = { itemsIn, skipped: held.length, review: mergeLines.length };
|
|
224
|
+
// Hold bya-book candidates outside the script's view, restore afterwards.
|
|
225
|
+
for (const h of held)
|
|
226
|
+
guardedMove(ctx, h.from, h.to);
|
|
227
|
+
try {
|
|
228
|
+
const res = ctx.spawn(process.execPath, [script, "--company", ctx.company, "--hq-root", ctx.hqRoot, "--json"], { cwd: ctx.hqRoot });
|
|
229
|
+
if (res.error || res.status !== 0) {
|
|
230
|
+
const msg = res.error?.message ?? (res.stderr.trim() || `exit ${String(res.status)}`);
|
|
231
|
+
return { status: "failed", ...base, itemsOut: 0, error: msg.slice(0, 500), errorKind: "other" };
|
|
232
|
+
}
|
|
233
|
+
const line = res.stdout.trim().split("\n").filter(Boolean).pop() ?? "{}";
|
|
234
|
+
try {
|
|
235
|
+
return { status: "ok", ...base, itemsOut: mapGardenReport(JSON.parse(line)).itemsOut };
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return { status: "failed", ...base, itemsOut: 0, error: "garden output was not JSON", errorKind: "other" };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
for (const h of held)
|
|
243
|
+
if (fs.existsSync(path.join(ctx.hqRoot, h.to)))
|
|
244
|
+
guardedMove(ctx, h.to, h.from);
|
|
245
|
+
for (const root of [entCand, sigCand])
|
|
246
|
+
removeEmptyDirs(ctx, `${root}/${HOLD_DIR}`);
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
//# sourceMappingURL=garden.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Pluggable step interface for the `hq signals run` engine. */
|
|
2
|
+
import type { OnDemandSyncReport } from "../../hot-channels/run.js";
|
|
3
|
+
import type { RunTrigger } from "../../hot-channels/ledger.js";
|
|
4
|
+
import type { UploadDeps } from "./upload.js";
|
|
5
|
+
import type { AdapterFlagReader } from "../adapters-catalog.js";
|
|
6
|
+
/** Same input shape as runCompanyHotChannelSync in src/commands/sources.ts. */
|
|
7
|
+
export type HotChannelSyncFn = (input: {
|
|
8
|
+
hqRoot: string;
|
|
9
|
+
company: string;
|
|
10
|
+
/** Run only this adapter (catalog provider or adapter id); all when unset. */
|
|
11
|
+
adapter?: string;
|
|
12
|
+
/** Run only these registered adapter ids; ids the catalog lacks are left out, not errors. */
|
|
13
|
+
onlyAdapters?: readonly string[];
|
|
14
|
+
/** Company flag reader for the adapter gate; the sync defaults to the hq-flags evaluator. */
|
|
15
|
+
isFlagOn?: AdapterFlagReader;
|
|
16
|
+
now?: () => Date;
|
|
17
|
+
trigger?: RunTrigger;
|
|
18
|
+
catchUp?: boolean;
|
|
19
|
+
}) => Promise<OnDemandSyncReport>;
|
|
20
|
+
export type StepName = "garden" | "adapters" | "extract" | "upload";
|
|
21
|
+
export type StepStatus = "ok" | "skipped" | "failed" | "partial";
|
|
22
|
+
export type ErrorKind = "reconnect" | "signed-out" | "transient" | "config" | "other";
|
|
23
|
+
export interface SpawnResult {
|
|
24
|
+
status: number | null;
|
|
25
|
+
stdout: string;
|
|
26
|
+
stderr: string;
|
|
27
|
+
error?: Error;
|
|
28
|
+
}
|
|
29
|
+
export type SpawnSyncFn = (cmd: string, args: string[], opts: {
|
|
30
|
+
cwd?: string;
|
|
31
|
+
env?: NodeJS.ProcessEnv;
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
}) => SpawnResult;
|
|
34
|
+
export interface StepContext {
|
|
35
|
+
hqRoot: string;
|
|
36
|
+
company: string;
|
|
37
|
+
dryRun: boolean;
|
|
38
|
+
trigger: string;
|
|
39
|
+
catchUp: boolean;
|
|
40
|
+
maxTurns: number;
|
|
41
|
+
maxBudgetUsd: number;
|
|
42
|
+
now: () => Date;
|
|
43
|
+
spawn: SpawnSyncFn;
|
|
44
|
+
/** Guarded writer bound to hqRoot. Every engine write goes through it. */
|
|
45
|
+
write: (relPath: string, data: string) => void;
|
|
46
|
+
/** Hot-channel sync used by the adapters step; defaults to runCompanyHotChannelSync. */
|
|
47
|
+
hotChannelSync?: HotChannelSyncFn;
|
|
48
|
+
/** Overrides for the upload step's session, fetch and vault reader (tests). */
|
|
49
|
+
uploadDeps?: Partial<UploadDeps>;
|
|
50
|
+
/** Registered hot-channel adapter ids; defaults to the builtin registry. */
|
|
51
|
+
registeredAdapterIds?: readonly string[];
|
|
52
|
+
/**
|
|
53
|
+
* Company flag reader for the adapter gate (hq-flags keys and their
|
|
54
|
+
* registered defaults). When set, the step gates on it and hands it to the
|
|
55
|
+
* sync. When unset, the sync reads the flags through the shared hq-flags
|
|
56
|
+
* evaluator with the signed-in session.
|
|
57
|
+
*/
|
|
58
|
+
isFlagOn?: AdapterFlagReader;
|
|
59
|
+
}
|
|
60
|
+
export interface StepResult {
|
|
61
|
+
status: StepStatus;
|
|
62
|
+
itemsIn: number;
|
|
63
|
+
itemsOut: number;
|
|
64
|
+
skippedReason?: string;
|
|
65
|
+
error?: string;
|
|
66
|
+
modelTurns?: number;
|
|
67
|
+
estimatedSpendUsd?: number;
|
|
68
|
+
byAdapter?: Record<string, number>;
|
|
69
|
+
/** Adapter id -> items the adapter read this run (landed + duplicates). */
|
|
70
|
+
itemsInByAdapter?: Record<string, number>;
|
|
71
|
+
/** Adapter id -> token level used (`user` or `workspace`); recorded for attio and gmail. */
|
|
72
|
+
tokenLevels?: Record<string, string>;
|
|
73
|
+
/** Adapter id -> reason it did not run (`flag-off`). */
|
|
74
|
+
adapterSkips?: Record<string, string>;
|
|
75
|
+
uploaded?: {
|
|
76
|
+
count: number;
|
|
77
|
+
bytes: number;
|
|
78
|
+
};
|
|
79
|
+
/** Dry-run only: human-readable lines describing what the step would do. */
|
|
80
|
+
plan?: string[];
|
|
81
|
+
/** Step-specific detail kept in the run record (for example projection state). */
|
|
82
|
+
detail?: Record<string, unknown>;
|
|
83
|
+
errorKind?: ErrorKind;
|
|
84
|
+
/** Candidates left in place on purpose (garden: bya-book). */
|
|
85
|
+
skipped?: number;
|
|
86
|
+
/** Candidates queued for human review (garden: uncertain merges). */
|
|
87
|
+
review?: number;
|
|
88
|
+
/** Items whose step work failed; they are left unprocessed for the next run. */
|
|
89
|
+
failures?: StepFailure[];
|
|
90
|
+
/** Items the source worklist skipped (for example no-audience). */
|
|
91
|
+
skippedItems?: {
|
|
92
|
+
file: string;
|
|
93
|
+
reason: string;
|
|
94
|
+
}[];
|
|
95
|
+
/** Candidates deleted after a turn because they lacked source_ref/audience_key or had a bad type. */
|
|
96
|
+
rejectedCandidates?: number;
|
|
97
|
+
}
|
|
98
|
+
export interface StepFailure {
|
|
99
|
+
file: string;
|
|
100
|
+
error: string;
|
|
101
|
+
}
|
|
102
|
+
export interface SignalsStep {
|
|
103
|
+
name: StepName;
|
|
104
|
+
run(ctx: StepContext): Promise<StepResult>;
|
|
105
|
+
/**
|
|
106
|
+
* Optional read-only planner called instead of run() under --dry-run. It must
|
|
107
|
+
* not write files or make network calls that change state.
|
|
108
|
+
*/
|
|
109
|
+
dryRun?(ctx: StepContext): Promise<StepResult>;
|
|
110
|
+
}
|
|
111
|
+
export declare function notImplemented(): StepResult;
|
|
112
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SignalsStep } from "./types.js";
|
|
2
|
+
export interface UploadSession {
|
|
3
|
+
token: string;
|
|
4
|
+
memberUid: string;
|
|
5
|
+
companyUid: string;
|
|
6
|
+
/** Control-plane origin, e.g. DEFAULT_VAULT_API_URL. */
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
}
|
|
9
|
+
export type FetchFn = (url: string, init: {
|
|
10
|
+
method: string;
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
body?: string;
|
|
13
|
+
}) => Promise<{
|
|
14
|
+
status: number;
|
|
15
|
+
text(): Promise<string>;
|
|
16
|
+
}>;
|
|
17
|
+
export interface UploadDeps {
|
|
18
|
+
/** Resolves the signed-in member for this company; null when there is no HQ login. */
|
|
19
|
+
getSession(company: string): Promise<UploadSession | null>;
|
|
20
|
+
fetch: FetchFn;
|
|
21
|
+
/** Reads one bucket-relative key from the company vault; null when absent. */
|
|
22
|
+
readVaultObject(session: UploadSession, company: string, key: string): Promise<string | null>;
|
|
23
|
+
}
|
|
24
|
+
export interface UploadWatermark {
|
|
25
|
+
lastUploadedAt: string | null;
|
|
26
|
+
uploaded: string[];
|
|
27
|
+
rejected: Record<string, string>;
|
|
28
|
+
}
|
|
29
|
+
export declare function contributionRoute(company: string): string;
|
|
30
|
+
export declare function watermarkRel(company: string, memberUid: string): string;
|
|
31
|
+
export declare function readWatermark(hqRoot: string, company: string, memberUid: string): UploadWatermark;
|
|
32
|
+
export interface SignalCandidate {
|
|
33
|
+
/** Company-relative, `signals/...`. */
|
|
34
|
+
rel: string;
|
|
35
|
+
sha: string;
|
|
36
|
+
mtimeMs: number;
|
|
37
|
+
}
|
|
38
|
+
/** Promoted signals for a company not yet uploaded or rejected, newer than the watermark. */
|
|
39
|
+
export declare function listSignalCandidates(hqRoot: string, company: string, wm: UploadWatermark): SignalCandidate[];
|
|
40
|
+
export declare const defaultUploadDeps: UploadDeps;
|
|
41
|
+
export declare function createUploadStep(overrides?: Partial<UploadDeps>): SignalsStep;
|
|
42
|
+
export declare const uploadStep: SignalsStep;
|
|
43
|
+
//# sourceMappingURL=upload.d.ts.map
|