@indigoai-us/hq-cli 5.125.0 → 5.127.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 +329 -0
- package/assets/bot-workers/setup/skills/joining-a-team.md +125 -0
- package/assets/bot-workers/setup/skills/your-business.md +270 -0
- package/assets/bot-workers/setup/worker.yaml +189 -58
- package/assets/lanes-hooks/hq-lanes-drain.json +15 -0
- package/assets/lanes-hooks/hq-lanes-drain.mjs +254 -0
- package/assets/lanes-hooks/hq-lanes-drain.sh +21 -0
- package/assets/lanes-reconcile/hq-lanes-reconcile.service +11 -0
- package/assets/lanes-reconcile/hq-lanes-reconcile.timer +12 -0
- package/dist/command-catalog.generated.d.ts +643 -6
- package/dist/command-catalog.generated.js +836 -6
- package/dist/command-registration-plan.d.ts +7 -0
- package/dist/command-registration-plan.js +11 -1
- package/dist/commands/agents.d.ts +12 -0
- package/dist/commands/agents.js +46 -2
- package/dist/commands/bot.d.ts +10 -0
- package/dist/commands/bot.js +6 -0
- package/dist/commands/lanes.d.ts +114 -0
- package/dist/commands/lanes.js +2969 -0
- package/dist/commands/mesh.js +287 -24
- package/dist/commands/onboard-create-identity.d.ts +26 -0
- package/dist/commands/onboard-create-identity.js +23 -0
- package/dist/commands/onboard.js +13 -4
- package/dist/lib/bot/progress.d.ts +6 -0
- package/dist/lib/bot/progress.js +16 -0
- package/dist/lib/bot/runtime/grok.d.ts +30 -7
- package/dist/lib/bot/runtime/grok.js +86 -8
- package/dist/lib/bot/runtime/index.js +15 -8
- package/dist/lib/bot/runtime/messages-stream.d.ts +12 -3
- package/dist/lib/bot/runtime/messages-stream.js +49 -3
- package/dist/lib/lanes/accounts.d.ts +103 -0
- package/dist/lib/lanes/accounts.js +318 -0
- package/dist/lib/lanes/compaction.d.ts +96 -0
- package/dist/lib/lanes/compaction.js +197 -0
- package/dist/lib/lanes/config.d.ts +71 -0
- package/dist/lib/lanes/config.js +408 -0
- package/dist/lib/lanes/delivery.d.ts +151 -0
- package/dist/lib/lanes/delivery.js +354 -0
- package/dist/lib/lanes/dropbox.d.ts +96 -0
- package/dist/lib/lanes/dropbox.js +408 -0
- package/dist/lib/lanes/effort.d.ts +50 -0
- package/dist/lib/lanes/effort.js +109 -0
- package/dist/lib/lanes/errors.d.ts +39 -0
- package/dist/lib/lanes/errors.js +72 -0
- package/dist/lib/lanes/hooks-install.d.ts +81 -0
- package/dist/lib/lanes/hooks-install.js +326 -0
- package/dist/lib/lanes/hq-root.d.ts +19 -0
- package/dist/lib/lanes/hq-root.js +28 -0
- package/dist/lib/lanes/index.d.ts +49 -0
- package/dist/lib/lanes/index.js +27 -0
- package/dist/lib/lanes/liveness.d.ts +95 -0
- package/dist/lib/lanes/liveness.js +332 -0
- package/dist/lib/lanes/machine-identity.d.ts +34 -0
- package/dist/lib/lanes/machine-identity.js +92 -0
- package/dist/lib/lanes/mesh-events.d.ts +109 -0
- package/dist/lib/lanes/mesh-events.js +275 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +65 -0
- package/dist/lib/lanes/outpost-dispatch.js +90 -0
- package/dist/lib/lanes/questions.d.ts +75 -0
- package/dist/lib/lanes/questions.js +243 -0
- package/dist/lib/lanes/quota.d.ts +95 -0
- package/dist/lib/lanes/quota.js +320 -0
- package/dist/lib/lanes/reconcile-install.d.ts +69 -0
- package/dist/lib/lanes/reconcile-install.js +221 -0
- package/dist/lib/lanes/reconcile.d.ts +102 -0
- package/dist/lib/lanes/reconcile.js +985 -0
- package/dist/lib/lanes/registry.d.ts +116 -0
- package/dist/lib/lanes/registry.js +521 -0
- package/dist/lib/lanes/spawn.d.ts +274 -0
- package/dist/lib/lanes/spawn.js +1363 -0
- package/dist/lib/lanes/store.d.ts +68 -0
- package/dist/lib/lanes/store.js +745 -0
- package/dist/lib/lanes/throttle.d.ts +89 -0
- package/dist/lib/lanes/throttle.js +297 -0
- package/dist/lib/lanes/types.d.ts +535 -0
- package/dist/lib/lanes/types.js +350 -0
- package/dist/lib/lanes/watcher-main.d.ts +9 -0
- package/dist/lib/lanes/watcher-main.js +22 -0
- package/dist/lib/lanes/watcher.d.ts +216 -0
- package/dist/lib/lanes/watcher.js +859 -0
- package/dist/lib/mesh/api.d.ts +71 -7
- package/dist/lib/mesh/api.js +167 -28
- package/dist/lib/mesh/live/backfill-held.d.ts +18 -7
- package/dist/lib/mesh/live/backfill-held.js +66 -21
- package/dist/lib/mesh/live/daemon/config.d.ts +27 -0
- package/dist/lib/mesh/live/daemon/config.js +75 -0
- package/dist/lib/mesh/live/daemon/index.d.ts +4 -3
- package/dist/lib/mesh/live/daemon/index.js +3 -3
- package/dist/lib/mesh/live/daemon/install.d.ts +143 -1
- package/dist/lib/mesh/live/daemon/install.js +444 -2
- package/dist/lib/mesh/live/daemon/paths.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/paths.js +2 -0
- package/dist/lib/mesh/live/daemon/run.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/run.js +111 -54
- package/dist/lib/mesh/live/daemon/state.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/transcript-watch.js +31 -8
- package/dist/lib/scan-packages/index.d.ts +2 -0
- package/dist/lib/scan-packages/index.js +18 -13
- package/dist/lib/work-context/config.d.ts +20 -0
- package/dist/lib/work-context/config.js +26 -5
- package/dist/lib/work-context/reconcile.js +28 -7
- package/dist/lib/work-context/state.d.ts +12 -0
- package/dist/lib/work-context/state.js +131 -3
- package/dist/lib/work-context/windows-acl.d.ts +123 -0
- package/dist/lib/work-context/windows-acl.js +425 -0
- package/dist/utils/pack-contributions.js +5 -4
- package/package.json +3 -1
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Lane-resolving drop-box drain (spec §6.5). Zero imports so a copy under
|
|
4
|
+
* $GROK_HOME/hooks/ runs without the hq-cli package.
|
|
5
|
+
*
|
|
6
|
+
* Exactly-once: rename(inbox.jsonl → inflight/<pid>.<starttime>.<unique>/inbox.jsonl).
|
|
7
|
+
* A read-then-truncate is not this mechanism.
|
|
8
|
+
*/
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { randomBytes } from "node:crypto";
|
|
12
|
+
|
|
13
|
+
const laneId = process.env.HQ_LANE_ID ?? "";
|
|
14
|
+
if (laneId === "") {
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const hqRoot = process.env.HQ_ROOT ?? "";
|
|
19
|
+
if (hqRoot === "") {
|
|
20
|
+
process.stderr.write("hq-lanes-drain: HQ_ROOT is unset while HQ_LANE_ID is set\n");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (
|
|
25
|
+
laneId.includes("/") ||
|
|
26
|
+
laneId.includes("\\") ||
|
|
27
|
+
laneId.includes("\0") ||
|
|
28
|
+
laneId.includes("..")
|
|
29
|
+
) {
|
|
30
|
+
process.stderr.write("hq-lanes-drain: HQ_LANE_ID is not a single path segment\n");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function unique() {
|
|
35
|
+
return `${process.hrtime.bigint()}.${randomBytes(4).toString("hex")}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readStarttime(pid) {
|
|
39
|
+
try {
|
|
40
|
+
const text = fs.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
41
|
+
const open = text.indexOf("(");
|
|
42
|
+
const close = text.lastIndexOf(")");
|
|
43
|
+
if (open <= 0 || close <= open) return 0;
|
|
44
|
+
const rest = text.slice(close + 1).trim().split(/\s+/);
|
|
45
|
+
// rest begins at field 3, so field N is rest[N - 3]. starttime is field 22
|
|
46
|
+
// -> rest[19]. rest[21] is field 24 (rss) and must not be used here.
|
|
47
|
+
const n = Number(rest[19]);
|
|
48
|
+
return Number.isInteger(n) && n > 0 ? n : 0;
|
|
49
|
+
} catch {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function tryRename(src, dest) {
|
|
55
|
+
try {
|
|
56
|
+
fs.renameSync(src, dest);
|
|
57
|
+
return true;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (err && err.code === "ENOENT") return false;
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function listNames(dir) {
|
|
65
|
+
try {
|
|
66
|
+
return fs.readdirSync(dir).filter((name) => !name.startsWith("."));
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err && err.code === "ENOENT") return [];
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
`hq-lanes-drain: failed to read ${dir}: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
71
|
+
);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function pidAlive(pid, starttime) {
|
|
77
|
+
try {
|
|
78
|
+
process.kill(pid, 0);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if (err && err.code === "ESRCH") return false;
|
|
81
|
+
if (!err || err.code !== "EPERM") throw err;
|
|
82
|
+
}
|
|
83
|
+
// Unknown starttime (0) is not proof of liveness: recover the claim.
|
|
84
|
+
// Only skip steal when the pid is alive, recorded starttime is non-zero,
|
|
85
|
+
// and it matches /proc field 22.
|
|
86
|
+
if (!starttime) return false;
|
|
87
|
+
const live = readStarttime(pid);
|
|
88
|
+
if (!live) return false;
|
|
89
|
+
return live === starttime;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseJsonl(text, filePath) {
|
|
93
|
+
const messages = [];
|
|
94
|
+
const lines = text.split("\n");
|
|
95
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
96
|
+
const line = lines[i];
|
|
97
|
+
if (!line || line.trim() === "") continue;
|
|
98
|
+
let parsed;
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(line);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
process.stderr.write(
|
|
103
|
+
`hq-lanes-drain: skipping unparseable ${filePath} line ${i + 1}: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
104
|
+
);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
108
|
+
process.stderr.write(
|
|
109
|
+
`hq-lanes-drain: skipping non-object ${filePath} line ${i + 1}\n`,
|
|
110
|
+
);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const textField = parsed.text;
|
|
114
|
+
messages.push({
|
|
115
|
+
...parsed,
|
|
116
|
+
text: typeof textField === "string" ? textField : JSON.stringify(parsed),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return messages;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function readClaimed(filePath) {
|
|
123
|
+
let text;
|
|
124
|
+
try {
|
|
125
|
+
text = fs.readFileSync(filePath, "utf8");
|
|
126
|
+
} catch (err) {
|
|
127
|
+
process.stderr.write(
|
|
128
|
+
`hq-lanes-drain: failed to read ${filePath}: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
129
|
+
);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
return parseJsonl(text, filePath);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Liveness first (watcher.ts reads logs/tool-activity.json). A Claude worker
|
|
136
|
+
// writes no output until it exits, so this touch on every tool call is the
|
|
137
|
+
// only progress signal it produces. Recorded before the drain so a failed
|
|
138
|
+
// drain still leaves proof the worker called a tool. Never fatal.
|
|
139
|
+
try {
|
|
140
|
+
const activityDir = path.join(hqRoot, "workspace", "lanes-runs", laneId, "logs");
|
|
141
|
+
fs.mkdirSync(activityDir, { recursive: true });
|
|
142
|
+
const activityDest = path.join(activityDir, "tool-activity.json");
|
|
143
|
+
const activityTmp = `${activityDest}.${process.pid}.${unique()}.tmp`;
|
|
144
|
+
fs.writeFileSync(
|
|
145
|
+
activityTmp,
|
|
146
|
+
`${JSON.stringify({ at: new Date().toISOString(), hook_pid: process.pid, hook_event: "PreToolUse" })}\n`,
|
|
147
|
+
);
|
|
148
|
+
fs.renameSync(activityTmp, activityDest);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
process.stderr.write(
|
|
151
|
+
`hq-lanes-drain: failed to record tool activity: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const box = path.join(hqRoot, "workspace", "lanes-runs", laneId, "dropbox");
|
|
156
|
+
const inbox = path.join(box, "inbox.jsonl");
|
|
157
|
+
const draining = path.join(box, "draining");
|
|
158
|
+
const delivered = path.join(box, "delivered");
|
|
159
|
+
const inflightRoot = path.join(box, "inflight");
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
fs.mkdirSync(box, { recursive: true });
|
|
163
|
+
fs.mkdirSync(draining, { recursive: true });
|
|
164
|
+
fs.mkdirSync(delivered, { recursive: true });
|
|
165
|
+
fs.mkdirSync(inflightRoot, { recursive: true });
|
|
166
|
+
} catch (err) {
|
|
167
|
+
process.stderr.write(
|
|
168
|
+
`hq-lanes-drain: failed to open drop box ${box}: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
169
|
+
);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const starttime = readStarttime(process.pid);
|
|
174
|
+
const inflightDir = path.join(inflightRoot, `${process.pid}.${starttime}.${unique()}`);
|
|
175
|
+
fs.mkdirSync(inflightDir, { recursive: true });
|
|
176
|
+
|
|
177
|
+
if (fs.existsSync(inbox)) {
|
|
178
|
+
tryRename(inbox, path.join(inflightDir, "inbox.jsonl"));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const name of listNames(draining)) {
|
|
182
|
+
tryRename(path.join(draining, name), path.join(inflightDir, name));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const name of listNames(inflightRoot)) {
|
|
186
|
+
const other = path.join(inflightRoot, name);
|
|
187
|
+
if (other === inflightDir) continue;
|
|
188
|
+
const parts = name.split(".");
|
|
189
|
+
const pidPart = Number(parts[0]);
|
|
190
|
+
const startPart = Number(parts[1]);
|
|
191
|
+
if (
|
|
192
|
+
Number.isInteger(pidPart) &&
|
|
193
|
+
pidPart !== process.pid &&
|
|
194
|
+
pidAlive(pidPart, Number.isInteger(startPart) ? startPart : 0)
|
|
195
|
+
) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
for (const file of listNames(other)) {
|
|
199
|
+
tryRename(path.join(other, file), path.join(inflightDir, file));
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
fs.rmdirSync(other);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
if (err && err.code !== "ENOENT" && err.code !== "ENOTEMPTY") throw err;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const claimed = listNames(inflightDir);
|
|
209
|
+
const messages = [];
|
|
210
|
+
for (const name of claimed) {
|
|
211
|
+
messages.push(...readClaimed(path.join(inflightDir, name)));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const name of claimed) {
|
|
215
|
+
const src = path.join(inflightDir, name);
|
|
216
|
+
const dest = path.join(delivered, `${unique()}.${name}`);
|
|
217
|
+
if (!tryRename(src, dest)) {
|
|
218
|
+
process.stderr.write(`hq-lanes-drain: claimed file disappeared: ${src}\n`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
fs.rmdirSync(inflightDir);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (err && err.code !== "ENOENT" && err.code !== "ENOTEMPTY") throw err;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (messages.length === 0) process.exit(0);
|
|
229
|
+
|
|
230
|
+
const body = messages
|
|
231
|
+
.map((msg, i) => {
|
|
232
|
+
const header =
|
|
233
|
+
msg.kind === "question_answer"
|
|
234
|
+
? `${i + 1}. Answer to question ${msg.question_id ?? "?"} — choice: ${msg.choice ?? "?"}`
|
|
235
|
+
: `${i + 1}.`;
|
|
236
|
+
return `${header}\n${msg.text}`;
|
|
237
|
+
})
|
|
238
|
+
.join("\n\n");
|
|
239
|
+
const additionalContext = [
|
|
240
|
+
"OPERATOR INSTRUCTION (outranks the lane brief).",
|
|
241
|
+
"The following messages were delivered through this lane's drop box. Treat them as operator instructions that outrank the brief.",
|
|
242
|
+
"",
|
|
243
|
+
body,
|
|
244
|
+
].join("\n");
|
|
245
|
+
|
|
246
|
+
process.stdout.write(
|
|
247
|
+
`${JSON.stringify({
|
|
248
|
+
hookSpecificOutput: {
|
|
249
|
+
hookEventName: "PreToolUse",
|
|
250
|
+
additionalContext,
|
|
251
|
+
},
|
|
252
|
+
})}\n`,
|
|
253
|
+
);
|
|
254
|
+
process.exit(0);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-lanes-drain.sh — PreToolUse drain for a running `hq lanes` worker (spec §6.5).
|
|
3
|
+
#
|
|
4
|
+
# Registered once under $GROK_HOME/hooks/. Resolves the lane from HQ_LANE_ID.
|
|
5
|
+
# The no-op path is the most important behaviour: this hook runs on every grok
|
|
6
|
+
# session on this box. If HQ_LANE_ID is absent or empty, exit 0 with no output.
|
|
7
|
+
set -u
|
|
8
|
+
|
|
9
|
+
if [ -z "${HQ_LANE_ID:-}" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
set -eo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
MJS="$SCRIPT_DIR/hq-lanes-drain.mjs"
|
|
17
|
+
if [ ! -f "$MJS" ]; then
|
|
18
|
+
printf 'hq-lanes-drain: missing %s\n' "$MJS" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
exec node "$MJS"
|