@indigoai-us/hq-cli 5.118.1 → 5.119.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 +52 -0
- package/dist/command-catalog.generated.d.ts +11 -2
- package/dist/command-catalog.generated.js +14 -2
- package/dist/commands/agent-kit.d.ts +7 -0
- package/dist/commands/agent-kit.js +32 -10
- package/dist/commands/agent-mcp.js +2 -0
- package/dist/commands/agent-probe.d.ts +6 -2
- package/dist/commands/agent-probe.js +48 -20
- package/dist/commands/agents.d.ts +1 -0
- package/dist/commands/agents.js +10 -2
- package/dist/lib/agent-kit/kit-config.d.ts +26 -1
- package/dist/lib/agent-kit/kit-config.js +12 -0
- package/dist/lib/agent-kit/mcp/tools.d.ts +7 -0
- package/dist/lib/agent-kit/mcp/tools.js +11 -1
- package/dist/lib/agent-kit/run/heartbeat.d.ts +13 -3
- package/dist/lib/agent-kit/run/heartbeat.js +6 -4
- package/dist/lib/agent-kit/services.d.ts +12 -1
- package/dist/lib/agent-kit/services.js +12 -2
- package/dist/lib/agent-kit/skills.js +30 -14
- package/dist/lib/billing/plan-lock.js +3 -1
- package/dist/lib/plan-limit-nag.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.119.0] — 2026-09-17
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- External agents no longer copy the company vault onto their own machine.
|
|
10
|
+
`hq agent kit install` used to install a sync loop that mirrored the whole
|
|
11
|
+
vault to disk; a bot reads and writes vault files on demand through the `hq`
|
|
12
|
+
MCP server and the `hq files` commands, so the copy bought nothing for the
|
|
13
|
+
common case and cost a full download of the company's assets. On a team with
|
|
14
|
+
a large brand library that is hundreds of megabytes, and on a sandboxed bot
|
|
15
|
+
host whose egress truncates large responses it could never finish at all —
|
|
16
|
+
every file retried three times and failed with `ERR_HQ_TRUNCATED_RESPONSE`,
|
|
17
|
+
which left the probe stuck and the agent reporting a sync error while it was
|
|
18
|
+
otherwise a healthy team member.
|
|
19
|
+
|
|
20
|
+
Pass `--sync-files` if the bot genuinely needs files on disk for shell-based
|
|
21
|
+
work; a reinstall without it removes a sync loop an earlier install left
|
|
22
|
+
behind. An existing install is unchanged until it is reinstalled.
|
|
23
|
+
|
|
24
|
+
- `hq agent probe` reports a `files` check in place of `team-sync`. Without a
|
|
25
|
+
local copy it proves the read path the bot actually uses — a vault listing
|
|
26
|
+
answers — instead of downloading the vault to prove a folder exists. With
|
|
27
|
+
`--sync-files` it checks the pull exactly as before.
|
|
28
|
+
|
|
29
|
+
- The heartbeat reports only the components the kit runs. A component that was
|
|
30
|
+
never installed is omitted rather than reported as an error, so a bot with no
|
|
31
|
+
sync loop no longer looks broken.
|
|
32
|
+
|
|
33
|
+
- `hq_search` no longer runs a local index search on an agent that has no local
|
|
34
|
+
copy, where it could only ever return nothing. It runs the server-side vault
|
|
35
|
+
search instead, and says plainly that content and semantic search need a kit
|
|
36
|
+
installed with `--sync-files`.
|
|
37
|
+
|
|
38
|
+
## [5.118.2] — 2026-09-17
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- The locked-workspace notice now says what you can still do. A locked
|
|
43
|
+
workspace is read-only for new things, but deleting still works — which
|
|
44
|
+
matters, because every fix the notice lists (remove members, disconnect
|
|
45
|
+
integrations, delete secrets, deprovision agents) is a delete. The notice
|
|
46
|
+
states that outright instead of leaving "read-only" to be guessed at.
|
|
47
|
+
|
|
48
|
+
- The plan-limit box names the plan: "HQ Starter plan limit exceeded" rather
|
|
49
|
+
than "HQ plan limit exceeded".
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- `hq agents provision --model` now sets the brain model during creation. A
|
|
54
|
+
model such as `gpt-5.5` or `grok-4.6` selects the agents-v2 runtime unless
|
|
55
|
+
`--provider` is specified, avoiding a later box recreation to change brains.
|
|
56
|
+
|
|
5
57
|
## [5.118.1] — 2026-09-17
|
|
6
58
|
|
|
7
59
|
### Fixed
|
|
@@ -3812,7 +3812,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3812
3812
|
readonly description: "Agent slug (defaults to a slug of <name>)";
|
|
3813
3813
|
}, {
|
|
3814
3814
|
readonly flags: "--provider <provider>";
|
|
3815
|
-
readonly description: "Runtime:
|
|
3815
|
+
readonly description: "Runtime: agents-v2 (recommended) | codex | grok | claude (legacy). A --model selects agents-v2 by default";
|
|
3816
|
+
}, {
|
|
3817
|
+
readonly flags: "--model <model>";
|
|
3818
|
+
readonly description: "Brain model, for example gpt-5.5 or grok-4.6";
|
|
3816
3819
|
}, {
|
|
3817
3820
|
readonly flags: "--auth-mode <mode>";
|
|
3818
3821
|
readonly description: "Auth: subscription | apiKey (default subscription)";
|
|
@@ -4178,7 +4181,7 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
4178
4181
|
readonly options: readonly [];
|
|
4179
4182
|
readonly subcommands: readonly [{
|
|
4180
4183
|
readonly name: "install";
|
|
4181
|
-
readonly description: "Install
|
|
4184
|
+
readonly description: "Install the work-mesh, inbox and heartbeat services plus the skills directory";
|
|
4182
4185
|
readonly aliases: readonly [];
|
|
4183
4186
|
readonly hidden: false;
|
|
4184
4187
|
readonly usage: "[options]";
|
|
@@ -4186,6 +4189,12 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
4186
4189
|
readonly options: readonly [{
|
|
4187
4190
|
readonly flags: "--hq-root <path>";
|
|
4188
4191
|
readonly description: "Local HQ tree to sync the company vault into (default: ~/.hq-agent/hq)";
|
|
4192
|
+
}, {
|
|
4193
|
+
readonly flags: "--sync-files";
|
|
4194
|
+
readonly description: "Also mirror the company vault to disk. Off by default: files are read on demand through the hq MCP server";
|
|
4195
|
+
}, {
|
|
4196
|
+
readonly flags: "--no-sync-files";
|
|
4197
|
+
readonly description: "Do not mirror the vault to disk (default; removes the loop if an earlier install added it)";
|
|
4189
4198
|
}, {
|
|
4190
4199
|
readonly flags: "--inbox-ack";
|
|
4191
4200
|
readonly description: "Ack mirrored inbox items on the server (default: leave acking to the bot)";
|
|
@@ -4926,7 +4926,11 @@ export const COMMAND_CATALOG = [
|
|
|
4926
4926
|
},
|
|
4927
4927
|
{
|
|
4928
4928
|
"flags": "--provider <provider>",
|
|
4929
|
-
"description": "Runtime:
|
|
4929
|
+
"description": "Runtime: agents-v2 (recommended) | codex | grok | claude (legacy). A --model selects agents-v2 by default"
|
|
4930
|
+
},
|
|
4931
|
+
{
|
|
4932
|
+
"flags": "--model <model>",
|
|
4933
|
+
"description": "Brain model, for example gpt-5.5 or grok-4.6"
|
|
4930
4934
|
},
|
|
4931
4935
|
{
|
|
4932
4936
|
"flags": "--auth-mode <mode>",
|
|
@@ -5406,7 +5410,7 @@ export const COMMAND_CATALOG = [
|
|
|
5406
5410
|
"subcommands": [
|
|
5407
5411
|
{
|
|
5408
5412
|
"name": "install",
|
|
5409
|
-
"description": "Install
|
|
5413
|
+
"description": "Install the work-mesh, inbox and heartbeat services plus the skills directory",
|
|
5410
5414
|
"aliases": [],
|
|
5411
5415
|
"hidden": false,
|
|
5412
5416
|
"usage": "[options]",
|
|
@@ -5416,6 +5420,14 @@ export const COMMAND_CATALOG = [
|
|
|
5416
5420
|
"flags": "--hq-root <path>",
|
|
5417
5421
|
"description": "Local HQ tree to sync the company vault into (default: ~/.hq-agent/hq)"
|
|
5418
5422
|
},
|
|
5423
|
+
{
|
|
5424
|
+
"flags": "--sync-files",
|
|
5425
|
+
"description": "Also mirror the company vault to disk. Off by default: files are read on demand through the hq MCP server"
|
|
5426
|
+
},
|
|
5427
|
+
{
|
|
5428
|
+
"flags": "--no-sync-files",
|
|
5429
|
+
"description": "Do not mirror the vault to disk (default; removes the loop if an earlier install added it)"
|
|
5430
|
+
},
|
|
5419
5431
|
{
|
|
5420
5432
|
"flags": "--inbox-ack",
|
|
5421
5433
|
"description": "Ack mirrored inbox items on the server (default: leave acking to the bot)"
|
|
@@ -35,6 +35,13 @@ export interface KitInstallOptions {
|
|
|
35
35
|
hqRoot?: string;
|
|
36
36
|
inboxAck?: boolean;
|
|
37
37
|
activate?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Mirror the company vault onto local disk. Off unless asked for: a bot
|
|
40
|
+
* reads and writes vault files on demand through the `hq` MCP server, so the
|
|
41
|
+
* sync loop is not installed at all rather than installed and left failing
|
|
42
|
+
* on a host that cannot finish a full-copy download.
|
|
43
|
+
*/
|
|
44
|
+
syncFiles?: boolean;
|
|
38
45
|
}
|
|
39
46
|
export interface KitFallbackInfo {
|
|
40
47
|
mode: "fallback";
|
|
@@ -22,10 +22,10 @@ import * as os from "node:os";
|
|
|
22
22
|
import { CLI_VERSION } from "../cli-version.js";
|
|
23
23
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
24
24
|
import { readExternalMachineCreds, } from "../lib/agent-kit/creds.js";
|
|
25
|
-
import { defaultKitConfig, readKitConfig, writeKitConfig, } from "../lib/agent-kit/kit-config.js";
|
|
25
|
+
import { defaultKitConfig, enabledKitComponents, readKitConfig, writeKitConfig, } from "../lib/agent-kit/kit-config.js";
|
|
26
26
|
import { createKitLogger } from "../lib/agent-kit/log.js";
|
|
27
27
|
import { agentKitPaths } from "../lib/agent-kit/paths.js";
|
|
28
|
-
import { isKitService, KIT_SERVICES, kitServiceSpecs, } from "../lib/agent-kit/services.js";
|
|
28
|
+
import { isKitService, KIT_SERVICES, enabledKitServices, kitServiceSpec, kitServiceSpecs, } from "../lib/agent-kit/services.js";
|
|
29
29
|
import { writeKitSkills } from "../lib/agent-kit/skills.js";
|
|
30
30
|
import { runHeartbeatLoop } from "../lib/agent-kit/run/heartbeat.js";
|
|
31
31
|
import { pollInboxOnce, runInboxLoop } from "../lib/agent-kit/run/inbox.js";
|
|
@@ -76,13 +76,24 @@ export function installKit(paths, opts, host, deps = {}, fallbackDeps = {}) {
|
|
|
76
76
|
...existing,
|
|
77
77
|
...(opts.hqRoot ? { hqRoot: opts.hqRoot } : {}),
|
|
78
78
|
...(opts.inboxAck !== undefined ? { inboxAck: opts.inboxAck } : {}),
|
|
79
|
+
...(opts.syncFiles !== undefined ? { syncFiles: opts.syncFiles } : {}),
|
|
79
80
|
cliVersion: CLI_VERSION,
|
|
80
81
|
};
|
|
81
82
|
fs.mkdirSync(config.hqRoot, { recursive: true, mode: 0o700 });
|
|
82
83
|
writeKitConfig(paths, config);
|
|
83
84
|
const skills = writeKitSkills(paths);
|
|
84
85
|
const activate = opts.activate ?? deps.activate ?? true;
|
|
85
|
-
|
|
86
|
+
// A reinstall that turns file sync off must also remove the unit an earlier
|
|
87
|
+
// install left behind, or the loop keeps running and keeps failing.
|
|
88
|
+
if (!config.syncFiles) {
|
|
89
|
+
try {
|
|
90
|
+
uninstallServices([kitServiceSpec("sync", paths)], host, deps);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
/* best effort: an absent unit is the desired end state either way */
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const services = installServices(kitServiceSpecs(paths, { syncFiles: config.syncFiles }), host, {
|
|
86
97
|
...deps,
|
|
87
98
|
activate,
|
|
88
99
|
});
|
|
@@ -149,6 +160,7 @@ export async function runKitService(service, rt) {
|
|
|
149
160
|
intervalMs: rt.config.heartbeatIntervalMs,
|
|
150
161
|
getToken: rt.getToken,
|
|
151
162
|
log: rt.log,
|
|
163
|
+
components: enabledKitComponents(rt.config),
|
|
152
164
|
});
|
|
153
165
|
return;
|
|
154
166
|
case "inbox":
|
|
@@ -210,9 +222,10 @@ export async function runKitService(service, rt) {
|
|
|
210
222
|
}
|
|
211
223
|
/** `kit run all`: supervise the four services as child processes with restart backoff. */
|
|
212
224
|
export async function runAllKitServices(rt, host) {
|
|
213
|
-
|
|
225
|
+
const services = enabledKitServices({ syncFiles: rt.config.syncFiles });
|
|
226
|
+
rt.log("info", `supervisor starting ${services.join(",")} agent=${rt.creds.entityUid} cli=${CLI_VERSION}`);
|
|
214
227
|
const handle = superviseKitServices({
|
|
215
|
-
services
|
|
228
|
+
services,
|
|
216
229
|
log: rt.log,
|
|
217
230
|
spawnService: (service) => {
|
|
218
231
|
const fd = fs.openSync(serviceLogPath(rt.paths, service), "a", 0o600);
|
|
@@ -265,21 +278,30 @@ export function registerAgentKitCommand(agent) {
|
|
|
265
278
|
.description("Install, inspect, or run the external-agent background services");
|
|
266
279
|
kit
|
|
267
280
|
.command("install")
|
|
268
|
-
.description("Install
|
|
281
|
+
.description("Install the work-mesh, inbox and heartbeat services plus the skills directory")
|
|
269
282
|
.option("--hq-root <path>", "Local HQ tree to sync the company vault into (default: ~/.hq-agent/hq)")
|
|
283
|
+
.option("--sync-files", "Also mirror the company vault to disk. Off by default: files are read on demand through the hq MCP server")
|
|
284
|
+
.option("--no-sync-files", "Do not mirror the vault to disk (default; removes the loop if an earlier install added it)")
|
|
270
285
|
.option("--inbox-ack", "Ack mirrored inbox items on the server (default: leave acking to the bot)")
|
|
271
286
|
.option("--no-activate", "Write unit files but do not load them")
|
|
272
287
|
.option("--json", "Print machine-readable JSON")
|
|
273
288
|
.action((opts) => {
|
|
274
289
|
try {
|
|
275
290
|
const paths = agentKitPaths(os.homedir(), process.env);
|
|
276
|
-
const result = installKit(paths, {
|
|
291
|
+
const result = installKit(paths, {
|
|
292
|
+
hqRoot: opts.hqRoot,
|
|
293
|
+
inboxAck: opts.inboxAck,
|
|
294
|
+
syncFiles: opts.syncFiles,
|
|
295
|
+
activate: opts.activate,
|
|
296
|
+
}, resolveServiceHost(paths));
|
|
277
297
|
if (opts.json) {
|
|
278
298
|
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
279
299
|
return;
|
|
280
300
|
}
|
|
281
301
|
console.log(chalk.green("HQ agent kit installed."));
|
|
282
|
-
console.log(
|
|
302
|
+
console.log(result.config.syncFiles
|
|
303
|
+
? ` files: mirrored to ${result.config.hqRoot}`
|
|
304
|
+
: " files: read on demand through the hq MCP server (no local copy)");
|
|
283
305
|
console.log(` logs: ${paths.logsDir}`);
|
|
284
306
|
console.log(` skills: ${paths.skillsDir} (${result.skills.length} skills)`);
|
|
285
307
|
if (result.fallback) {
|
|
@@ -310,7 +332,7 @@ export function registerAgentKitCommand(agent) {
|
|
|
310
332
|
try {
|
|
311
333
|
const paths = agentKitPaths(os.homedir(), process.env);
|
|
312
334
|
const fallback = fallbackStatus(paths);
|
|
313
|
-
const result = servicesStatus(kitServiceSpecs(paths), resolveServiceHost(paths));
|
|
335
|
+
const result = servicesStatus(kitServiceSpecs(paths, { syncFiles: true }), resolveServiceHost(paths));
|
|
314
336
|
if (opts.json) {
|
|
315
337
|
console.log(JSON.stringify({ ok: true, ...result, ...(fallback ? { fallback } : {}) }, null, 2));
|
|
316
338
|
return;
|
|
@@ -335,7 +357,7 @@ export function registerAgentKitCommand(agent) {
|
|
|
335
357
|
try {
|
|
336
358
|
const paths = agentKitPaths(os.homedir(), process.env);
|
|
337
359
|
const fallback = stopFallback(paths);
|
|
338
|
-
const result = uninstallServices(kitServiceSpecs(paths), resolveServiceHost(paths));
|
|
360
|
+
const result = uninstallServices(kitServiceSpecs(paths, { syncFiles: true }), resolveServiceHost(paths));
|
|
339
361
|
if (opts.json) {
|
|
340
362
|
console.log(JSON.stringify({ ok: true, ...result, fallback }, null, 2));
|
|
341
363
|
return;
|
|
@@ -15,6 +15,7 @@ import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
|
15
15
|
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
16
16
|
import { McpServer } from "../lib/agent-kit/mcp/jsonrpc.js";
|
|
17
17
|
import { buildAgentMcpTools } from "../lib/agent-kit/mcp/tools.js";
|
|
18
|
+
import { readKitConfig } from "../lib/agent-kit/kit-config.js";
|
|
18
19
|
import { agentKitPaths } from "../lib/agent-kit/paths.js";
|
|
19
20
|
import { requireExternalCreds } from "./agent-kit.js";
|
|
20
21
|
export const AGENT_MCP_SERVER_NAME = "hq-agent";
|
|
@@ -56,6 +57,7 @@ export function defaultMcpClients(creds, credsPath, paths = agentKitPaths(os.hom
|
|
|
56
57
|
return {
|
|
57
58
|
creds,
|
|
58
59
|
paths,
|
|
60
|
+
syncFiles: readKitConfig(agentKitPaths(os.homedir(), process.env), CLI_VERSION).syncFiles,
|
|
59
61
|
runHq: defaultRunHq(process.execPath, process.argv[1], env),
|
|
60
62
|
getToken: () => ensureCognitoToken({ tokenSource: "machine", interactive: false }),
|
|
61
63
|
apiJson: async (token, path, init) => {
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* team member, one line per check, non-zero exit on any failure:
|
|
4
4
|
*
|
|
5
5
|
* whoami mint as the machine identity; token names this agent
|
|
6
|
-
*
|
|
6
|
+
* files a vault listing answers on demand (or, with --sync-files, a
|
|
7
|
+
* pull succeeds and the company folder exists)
|
|
7
8
|
* work-mesh the personal (contract-2) realtime vend names this agent, the
|
|
8
9
|
* agent-authorized inbox route answers 200, and this host's own
|
|
9
10
|
* presence is live: a fresh last-heartbeat.json plus an ok
|
|
@@ -46,7 +47,10 @@ export interface ProbeDeps {
|
|
|
46
47
|
hqRoot: string;
|
|
47
48
|
getToken: () => Promise<string>;
|
|
48
49
|
resolveCompanyUid: (token: string, slug: string) => Promise<string>;
|
|
49
|
-
|
|
50
|
+
/** Runs the `hq` CLI with these args as a child; resolves with its exit code. */
|
|
51
|
+
runHq: (args: string[]) => Promise<number>;
|
|
52
|
+
/** Mirror the vault to disk. Off by default: files are read through the API. */
|
|
53
|
+
syncFiles?: boolean;
|
|
50
54
|
vendRealtime: (token: string) => Promise<{
|
|
51
55
|
actorUid: string;
|
|
52
56
|
}>;
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* team member, one line per check, non-zero exit on any failure:
|
|
4
4
|
*
|
|
5
5
|
* whoami mint as the machine identity; token names this agent
|
|
6
|
-
*
|
|
6
|
+
* files a vault listing answers on demand (or, with --sync-files, a
|
|
7
|
+
* pull succeeds and the company folder exists)
|
|
7
8
|
* work-mesh the personal (contract-2) realtime vend names this agent, the
|
|
8
9
|
* agent-authorized inbox route answers 200, and this host's own
|
|
9
10
|
* presence is live: a fresh last-heartbeat.json plus an ok
|
|
@@ -91,29 +92,55 @@ export async function runProbe(deps) {
|
|
|
91
92
|
checks.push({ name: "company", ok: false, detail: `cannot resolve ${deps.creds.companySlug}: ${errText(err)}` });
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
|
-
// 2.
|
|
95
|
+
// 2. files
|
|
96
|
+
//
|
|
97
|
+
// Two shapes, because the kit has two. With file sync off (the default) the
|
|
98
|
+
// bot reads and writes vault files on demand, so what has to be proven is
|
|
99
|
+
// that a vault listing answers — not that a full local copy exists. Pulling
|
|
100
|
+
// the whole vault here would also make the probe fail for a reason that does
|
|
101
|
+
// not affect the bot: a team with a large asset library is hundreds of
|
|
102
|
+
// megabytes, and a sandboxed bot host whose egress truncates large responses
|
|
103
|
+
// can never finish it.
|
|
95
104
|
if (token) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
if (deps.syncFiles) {
|
|
106
|
+
try {
|
|
107
|
+
const code = await deps.runHq(syncPullArgs(deps.hqRoot));
|
|
108
|
+
const companyDir = path.join(deps.hqRoot, "companies", deps.creds.companySlug);
|
|
109
|
+
const present = fs.existsSync(companyDir);
|
|
110
|
+
const ok = code === 0 && present;
|
|
111
|
+
checks.push({
|
|
112
|
+
name: "files",
|
|
113
|
+
ok,
|
|
114
|
+
detail: ok
|
|
115
|
+
? `synced into ${companyDir}`
|
|
116
|
+
: code !== 0
|
|
117
|
+
? `hq sync pull exited ${code}`
|
|
118
|
+
: `sync exited 0 but ${companyDir} is missing`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
checks.push({ name: "files", ok: false, detail: errText(err) });
|
|
123
|
+
}
|
|
110
124
|
}
|
|
111
|
-
|
|
112
|
-
|
|
125
|
+
else {
|
|
126
|
+
const target = `companies/${deps.creds.companySlug}`;
|
|
127
|
+
try {
|
|
128
|
+
const code = await deps.runHq(["files", "browse", target]);
|
|
129
|
+
checks.push({
|
|
130
|
+
name: "files",
|
|
131
|
+
ok: code === 0,
|
|
132
|
+
detail: code === 0
|
|
133
|
+
? `${target} is readable on demand (no local copy)`
|
|
134
|
+
: `hq files browse ${target} exited ${code}`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
checks.push({ name: "files", ok: false, detail: errText(err) });
|
|
139
|
+
}
|
|
113
140
|
}
|
|
114
141
|
}
|
|
115
142
|
else {
|
|
116
|
-
checks.push({ name: "
|
|
143
|
+
checks.push({ name: "files", ok: false, detail: "skipped: no token" });
|
|
117
144
|
}
|
|
118
145
|
// 3. work-mesh: vend + agent-authorized reachability + self presence
|
|
119
146
|
if (token && companyUid) {
|
|
@@ -258,9 +285,10 @@ export function defaultProbeDeps(paths, creds) {
|
|
|
258
285
|
paths,
|
|
259
286
|
creds,
|
|
260
287
|
hqRoot: config.hqRoot,
|
|
288
|
+
syncFiles: config.syncFiles,
|
|
261
289
|
getToken: () => ensureCognitoToken({ tokenSource: "machine", interactive: false }),
|
|
262
290
|
resolveCompanyUid: (token, slug) => getCompanyUid(token, slug),
|
|
263
|
-
|
|
291
|
+
runHq: defaultRunPull(process.execPath, process.argv[1], process.env),
|
|
264
292
|
vendRealtime: async (token) => {
|
|
265
293
|
const bundle = await createPersonalRealtimeFetcher({ token, baseUrl: base })();
|
|
266
294
|
return { actorUid: bundle.actorUid };
|
|
@@ -171,6 +171,7 @@ export interface ProvisionAgentInput {
|
|
|
171
171
|
slug: string;
|
|
172
172
|
codexAuthMode: "subscription" | "apiKey";
|
|
173
173
|
provider?: "codex" | "grok" | "claude" | "agents-v2";
|
|
174
|
+
codexModel?: string;
|
|
174
175
|
codexApiKey?: string;
|
|
175
176
|
idempotencyKey: string;
|
|
176
177
|
title?: string;
|
package/dist/commands/agents.js
CHANGED
|
@@ -1074,7 +1074,8 @@ export function registerAgentsCommand(program) {
|
|
|
1074
1074
|
.description("Provision a new cloud agent (company-specific monthly price shown before creation)")
|
|
1075
1075
|
.option("--company <slug>", "Company slug (resolves to companyUid)")
|
|
1076
1076
|
.option("--slug <slug>", "Agent slug (defaults to a slug of <name>)")
|
|
1077
|
-
.option("--provider <provider>", "Runtime:
|
|
1077
|
+
.option("--provider <provider>", "Runtime: agents-v2 (recommended) | codex | grok | claude (legacy). A --model selects agents-v2 by default")
|
|
1078
|
+
.option("--model <model>", "Brain model, for example gpt-5.5 or grok-4.6")
|
|
1078
1079
|
.option("--auth-mode <mode>", "Auth: subscription | apiKey (default subscription)", "subscription")
|
|
1079
1080
|
.option("--api-key-env <VAR>", "Env var holding the API key for --auth-mode apiKey (never pass the key as a flag)")
|
|
1080
1081
|
.option("--title <title>", "Org-chart job title")
|
|
@@ -1088,7 +1089,13 @@ export function registerAgentsCommand(program) {
|
|
|
1088
1089
|
// unrecognised value, silently provisioning a billable box the operator
|
|
1089
1090
|
// did not ask for. They now exit(1) instead — see parseEnumOption.
|
|
1090
1091
|
const authMode = parseEnumOption(opts.authMode, VALID_AUTH_MODES, "--auth-mode") ?? "subscription";
|
|
1091
|
-
const
|
|
1092
|
+
const explicitProvider = parseEnumOption(opts.provider, VALID_PROVIDERS, "--provider");
|
|
1093
|
+
if (opts.model !== undefined &&
|
|
1094
|
+
!/^[A-Za-z0-9._-]{1,64}$/.test(opts.model)) {
|
|
1095
|
+
console.error(chalk.red("--model must be a model identifier (letters, digits, dot, dash, underscore; max 64 chars)."));
|
|
1096
|
+
process.exit(1);
|
|
1097
|
+
}
|
|
1098
|
+
const provider = explicitProvider ?? (opts.model ? "agents-v2" : undefined);
|
|
1092
1099
|
// claude is subscription-only on hq-pro (rejectIncompatibleProviderAuthMode
|
|
1093
1100
|
// returns AGENT_PROVIDER_INCOMPATIBLE_WITH_AUTH_MODE). Catch it here so the
|
|
1094
1101
|
// operator gets a direct message instead of a 400 from the control plane
|
|
@@ -1139,6 +1146,7 @@ export function registerAgentsCommand(program) {
|
|
|
1139
1146
|
slug,
|
|
1140
1147
|
codexAuthMode: authMode,
|
|
1141
1148
|
...(provider ? { provider } : {}),
|
|
1149
|
+
...(opts.model ? { codexModel: opts.model } : {}),
|
|
1142
1150
|
...(codexApiKey ? { codexApiKey } : {}),
|
|
1143
1151
|
idempotencyKey,
|
|
1144
1152
|
...(opts.title ? { title: opts.title } : {}),
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* sync and how often. Kept separate from machine-creds.json, which is the
|
|
5
5
|
* contract-shaped credential file and nothing else.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type AgentKitPaths, type KitComponent } from "./paths.js";
|
|
8
8
|
export declare const DEFAULT_SYNC_INTERVAL_MS: number;
|
|
9
9
|
export declare const DEFAULT_INBOX_POLL_MS = 15000;
|
|
10
10
|
export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 60000;
|
|
@@ -18,9 +18,34 @@ export interface KitConfig {
|
|
|
18
18
|
meshRefreshMs: number;
|
|
19
19
|
/** Ack mirrored inbox items on the server after writing them locally. */
|
|
20
20
|
inboxAck: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Mirror the company vault onto local disk.
|
|
23
|
+
*
|
|
24
|
+
* Off by default. An external bot reads and writes vault files on demand
|
|
25
|
+
* through the `hq` MCP server (hq_files_read / hq_files_list /
|
|
26
|
+
* hq_files_write) and the `hq files` CLI, so a local mirror buys nothing
|
|
27
|
+
* for the common case and costs a full copy of the company's assets — on a
|
|
28
|
+
* team with a large brand library that is hundreds of megabytes, and on a
|
|
29
|
+
* sandboxed bot host whose egress proxy truncates large responses it can
|
|
30
|
+
* never finish at all (every file retried three times, all failing with
|
|
31
|
+
* ERR_HQ_TRUNCATED_RESPONSE, leaving the probe stuck and component-sync in
|
|
32
|
+
* error while the bot was otherwise a healthy team member).
|
|
33
|
+
*
|
|
34
|
+
* Turn it on with `hq agent kit install --sync-files` when the bot genuinely
|
|
35
|
+
* needs files on disk for shell-based work (grep, build tools, a local
|
|
36
|
+
* index). A kit.json written before this field existed is read as `true`,
|
|
37
|
+
* because every install of that vintage ran the sync loop.
|
|
38
|
+
*/
|
|
39
|
+
syncFiles: boolean;
|
|
21
40
|
installedAt: string;
|
|
22
41
|
cliVersion: string;
|
|
23
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Kit components whose health this install actually reports. `sync` drops out
|
|
45
|
+
* when the vault is not mirrored to disk — there is no loop to be healthy or
|
|
46
|
+
* unhealthy, so reporting it either way would be a lie.
|
|
47
|
+
*/
|
|
48
|
+
export declare function enabledKitComponents(config: Pick<KitConfig, "syncFiles">): readonly KitComponent[];
|
|
24
49
|
export declare function defaultHqRoot(paths: Pick<AgentKitPaths, "agentDir">): string;
|
|
25
50
|
export declare function defaultKitConfig(paths: Pick<AgentKitPaths, "agentDir">, cliVersion: string, now?: () => Date): KitConfig;
|
|
26
51
|
export declare function writeKitConfig(paths: Pick<AgentKitPaths, "kitConfigPath">, config: KitConfig): void;
|
|
@@ -6,10 +6,19 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
+
import { KIT_COMPONENTS } from "./paths.js";
|
|
9
10
|
export const DEFAULT_SYNC_INTERVAL_MS = 5 * 60_000;
|
|
10
11
|
export const DEFAULT_INBOX_POLL_MS = 15_000;
|
|
11
12
|
export const DEFAULT_HEARTBEAT_INTERVAL_MS = 60_000;
|
|
12
13
|
export const DEFAULT_MESH_REFRESH_MS = 5 * 60_000;
|
|
14
|
+
/**
|
|
15
|
+
* Kit components whose health this install actually reports. `sync` drops out
|
|
16
|
+
* when the vault is not mirrored to disk — there is no loop to be healthy or
|
|
17
|
+
* unhealthy, so reporting it either way would be a lie.
|
|
18
|
+
*/
|
|
19
|
+
export function enabledKitComponents(config) {
|
|
20
|
+
return KIT_COMPONENTS.filter((c) => c !== "sync" || config.syncFiles);
|
|
21
|
+
}
|
|
13
22
|
export function defaultHqRoot(paths) {
|
|
14
23
|
return path.join(paths.agentDir, "hq");
|
|
15
24
|
}
|
|
@@ -21,6 +30,7 @@ export function defaultKitConfig(paths, cliVersion, now = () => new Date()) {
|
|
|
21
30
|
heartbeatIntervalMs: DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
22
31
|
meshRefreshMs: DEFAULT_MESH_REFRESH_MS,
|
|
23
32
|
inboxAck: false,
|
|
33
|
+
syncFiles: false,
|
|
24
34
|
installedAt: now().toISOString(),
|
|
25
35
|
cliVersion,
|
|
26
36
|
};
|
|
@@ -47,6 +57,8 @@ export function readKitConfig(paths, cliVersion) {
|
|
|
47
57
|
heartbeatIntervalMs: num("heartbeatIntervalMs"),
|
|
48
58
|
meshRefreshMs: num("meshRefreshMs"),
|
|
49
59
|
inboxAck: raw.inboxAck === true,
|
|
60
|
+
// Absent means a pre-`syncFiles` install, which always synced.
|
|
61
|
+
syncFiles: typeof raw.syncFiles === "boolean" ? raw.syncFiles : true,
|
|
50
62
|
installedAt: typeof raw.installedAt === "string" ? raw.installedAt : base.installedAt,
|
|
51
63
|
cliVersion: typeof raw.cliVersion === "string" ? raw.cliVersion : base.cliVersion,
|
|
52
64
|
};
|
|
@@ -30,6 +30,13 @@ export interface McpToolClients {
|
|
|
30
30
|
/** Run `hq <args…>` as the machine identity; never throws. */
|
|
31
31
|
runHq: (args: string[]) => Promise<HqRunResult>;
|
|
32
32
|
getToken: () => Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Whether this kit mirrors the vault to disk. Off by default, and it decides
|
|
35
|
+
* what `hq_search` can honestly do: the local qmd index only exists when
|
|
36
|
+
* there is a local copy to index, so without one the search is a server-side
|
|
37
|
+
* match on file names and paths, not on contents.
|
|
38
|
+
*/
|
|
39
|
+
syncFiles?: boolean;
|
|
33
40
|
/** Authenticated JSON call against the control plane. */
|
|
34
41
|
apiJson: (token: string, path: string, init?: {
|
|
35
42
|
method?: string;
|
|
@@ -99,7 +99,9 @@ export function buildAgentMcpTools(clients) {
|
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
name: "hq_search",
|
|
102
|
-
description:
|
|
102
|
+
description: clients.syncFiles
|
|
103
|
+
? "Search the synced company vault (local qmd index). Modes: keyword (default), semantic, hybrid."
|
|
104
|
+
: "Search the company vault by file name and path, server-side (no local copy is kept). Content and semantic search need a kit installed with --sync-files.",
|
|
103
105
|
inputSchema: {
|
|
104
106
|
type: "object",
|
|
105
107
|
properties: {
|
|
@@ -117,6 +119,14 @@ export function buildAgentMcpTools(clients) {
|
|
|
117
119
|
throw new McpToolInputError('"mode" must be keyword, semantic or hybrid');
|
|
118
120
|
}
|
|
119
121
|
const count = int(args, "count", { min: 1, max: 50, fallback: 10 });
|
|
122
|
+
if (!clients.syncFiles) {
|
|
123
|
+
// No local copy means no local index. Say so plainly instead of
|
|
124
|
+
// running a local search that can only ever return nothing.
|
|
125
|
+
if (mode && mode !== "keyword") {
|
|
126
|
+
throw new McpToolInputError(`"${mode}" search needs a local copy of the vault; this agent reads files on demand. Use keyword, or reinstall the kit with \`hq agent kit install --sync-files\`.`);
|
|
127
|
+
}
|
|
128
|
+
return fromRun(await clients.runHq(["files", "search", query, "--company", company]), "hq files search");
|
|
129
|
+
}
|
|
120
130
|
const hqArgs = ["search", query, "--mode", mode ?? "keyword", "-n", String(count), "--json"];
|
|
121
131
|
return fromRun(await clients.runHq(hqArgs), "hq search");
|
|
122
132
|
},
|
|
@@ -17,7 +17,15 @@ import { type AgentKitPaths, type KitComponent } from "../paths.js";
|
|
|
17
17
|
/** A component stamp older than this reads as error. */
|
|
18
18
|
export declare const COMPONENT_STALE_AFTER_MS: Record<KitComponent, number>;
|
|
19
19
|
export interface HeartbeatBody {
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Only the components this kit actually runs. A component that was never
|
|
22
|
+
* installed is OMITTED rather than reported `error`: with file sync off (the
|
|
23
|
+
* default — the bot reads vault files on demand through the MCP server)
|
|
24
|
+
* there is no sync loop to be unhealthy, and stamping it `error` would make
|
|
25
|
+
* every healthy bot look broken. hq-pro records the components it is sent
|
|
26
|
+
* and ignores absent names.
|
|
27
|
+
*/
|
|
28
|
+
components: Partial<Record<KitComponent, ComponentStatus>>;
|
|
21
29
|
cliVersion: string;
|
|
22
30
|
hostInfo: {
|
|
23
31
|
os: string;
|
|
@@ -26,10 +34,10 @@ export interface HeartbeatBody {
|
|
|
26
34
|
cliVersion: string;
|
|
27
35
|
};
|
|
28
36
|
}
|
|
29
|
-
export declare function buildHeartbeatBody(paths: Pick<AgentKitPaths, "stateDir">, now?: () => Date, hostInfo?: HeartbeatBody["hostInfo"]): HeartbeatBody;
|
|
37
|
+
export declare function buildHeartbeatBody(paths: Pick<AgentKitPaths, "stateDir">, now?: () => Date, hostInfo?: HeartbeatBody["hostInfo"], components_?: readonly KitComponent[]): HeartbeatBody;
|
|
30
38
|
export interface LastHeartbeat {
|
|
31
39
|
at: string;
|
|
32
|
-
components: Record<KitComponent, ComponentStatus
|
|
40
|
+
components: Partial<Record<KitComponent, ComponentStatus>>;
|
|
33
41
|
status: number;
|
|
34
42
|
}
|
|
35
43
|
export declare function writeLastHeartbeat(paths: Pick<AgentKitPaths, "stateDir" | "lastHeartbeatPath">, record: LastHeartbeat): void;
|
|
@@ -41,6 +49,8 @@ export interface HeartbeatDeps {
|
|
|
41
49
|
intervalMs: number;
|
|
42
50
|
getToken: () => Promise<string>;
|
|
43
51
|
log: KitLogger;
|
|
52
|
+
/** Components this kit runs; defaults to all of them. */
|
|
53
|
+
components?: readonly KitComponent[];
|
|
44
54
|
post?: (token: string, body: HeartbeatBody) => Promise<number>;
|
|
45
55
|
now?: () => Date;
|
|
46
56
|
sleep?: (ms: number) => Promise<void>;
|
|
@@ -23,9 +23,9 @@ export const COMPONENT_STALE_AFTER_MS = {
|
|
|
23
23
|
mesh: 3 * 60_000,
|
|
24
24
|
inbox: 3 * 60_000,
|
|
25
25
|
};
|
|
26
|
-
export function buildHeartbeatBody(paths, now = () => new Date(), hostInfo) {
|
|
26
|
+
export function buildHeartbeatBody(paths, now = () => new Date(), hostInfo, components_ = KIT_COMPONENTS) {
|
|
27
27
|
const components = {};
|
|
28
|
-
for (const c of
|
|
28
|
+
for (const c of components_) {
|
|
29
29
|
components[c] = readComponentStatus(paths, c, COMPONENT_STALE_AFTER_MS[c], now).status;
|
|
30
30
|
}
|
|
31
31
|
return {
|
|
@@ -67,7 +67,7 @@ export function defaultHeartbeatPost(agentUid, apiBaseUrl) {
|
|
|
67
67
|
export async function beatOnce(deps) {
|
|
68
68
|
const now = deps.now ?? (() => new Date());
|
|
69
69
|
const post = deps.post ?? defaultHeartbeatPost(deps.agentUid, deps.apiBaseUrl);
|
|
70
|
-
const body = buildHeartbeatBody(deps.paths, now);
|
|
70
|
+
const body = buildHeartbeatBody(deps.paths, now, undefined, deps.components ?? KIT_COMPONENTS);
|
|
71
71
|
try {
|
|
72
72
|
const token = await deps.getToken();
|
|
73
73
|
const status = await post(token, body);
|
|
@@ -75,7 +75,9 @@ export async function beatOnce(deps) {
|
|
|
75
75
|
if (ok) {
|
|
76
76
|
writeLastHeartbeat(deps.paths, { at: now().toISOString(), components: body.components, status });
|
|
77
77
|
}
|
|
78
|
-
deps.log(ok ? "info" : "warn", `heartbeat ${status}
|
|
78
|
+
deps.log(ok ? "info" : "warn", `heartbeat ${status} ${Object.entries(body.components)
|
|
79
|
+
.map(([name, health]) => `${name}=${health}`)
|
|
80
|
+
.join(" ")}`);
|
|
79
81
|
return ok;
|
|
80
82
|
}
|
|
81
83
|
catch (err) {
|
|
@@ -17,5 +17,16 @@ export declare function isKitService(value: string): value is KitService;
|
|
|
17
17
|
*/
|
|
18
18
|
export declare function kitServiceEnv(paths: AgentKitPaths): Record<string, string>;
|
|
19
19
|
export declare function kitServiceSpec(service: KitService, paths: AgentKitPaths): ServiceSpec;
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Services to install. `sync` is included only when the kit is configured to
|
|
22
|
+
* mirror the vault to disk; by default a bot reads files on demand through the
|
|
23
|
+
* `hq` MCP server, so the loop (and its full-copy download) is not installed at
|
|
24
|
+
* all rather than installed and left failing.
|
|
25
|
+
*/
|
|
26
|
+
export declare function kitServiceSpecs(paths: AgentKitPaths, options?: {
|
|
27
|
+
syncFiles?: boolean;
|
|
28
|
+
}): ServiceSpec[];
|
|
29
|
+
export declare function enabledKitServices(options?: {
|
|
30
|
+
syncFiles?: boolean;
|
|
31
|
+
}): readonly KitService[];
|
|
21
32
|
//# sourceMappingURL=services.d.ts.map
|
|
@@ -40,7 +40,17 @@ export function kitServiceSpec(service, paths) {
|
|
|
40
40
|
restartSec: 10,
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Services to install. `sync` is included only when the kit is configured to
|
|
45
|
+
* mirror the vault to disk; by default a bot reads files on demand through the
|
|
46
|
+
* `hq` MCP server, so the loop (and its full-copy download) is not installed at
|
|
47
|
+
* all rather than installed and left failing.
|
|
48
|
+
*/
|
|
49
|
+
export function kitServiceSpecs(paths, options = {}) {
|
|
50
|
+
return enabledKitServices(options).map((s) => kitServiceSpec(s, paths));
|
|
51
|
+
}
|
|
52
|
+
export function enabledKitServices(options = {}) {
|
|
53
|
+
const syncFiles = options.syncFiles === true;
|
|
54
|
+
return KIT_SERVICES.filter((s) => s !== "sync" || syncFiles);
|
|
45
55
|
}
|
|
46
56
|
//# sourceMappingURL=services.js.map
|
|
@@ -50,21 +50,29 @@ mark each item done.
|
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
name: "search",
|
|
53
|
-
description: "
|
|
53
|
+
description: "Find things in the company vault (documents, knowledge, meeting notes) with the hq CLI.",
|
|
54
54
|
body: `# HQ search
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
By default this agent keeps no local copy of the vault, so search runs
|
|
57
|
+
server-side and matches file NAMES and paths, not file contents:
|
|
58
58
|
|
|
59
59
|
\`\`\`bash
|
|
60
|
-
hq search "<query>"
|
|
61
|
-
hq
|
|
62
|
-
hq
|
|
63
|
-
hq files search "<query>" --company <slug> # server-side vault search (no local index needed)
|
|
60
|
+
hq files search "<query>" --company <slug> # find files by name/path
|
|
61
|
+
hq files browse companies/<slug>/<folder> # list a folder
|
|
62
|
+
hq files cat companies/<slug>/<file> # read one file
|
|
64
63
|
\`\`\`
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
A good pattern for a question you cannot answer from names alone: search or
|
|
66
|
+
browse to narrow to a few likely files, then read them with \`hq files cat\`.
|
|
67
|
+
|
|
68
|
+
Content and semantic search need a local copy and its index, which only exists
|
|
69
|
+
if the kit was installed with \`--sync-files\`. In that case (check
|
|
70
|
+
\`~/.hq-agent/kit.json\` → \`syncFiles\`) you also have:
|
|
71
|
+
|
|
72
|
+
\`\`\`bash
|
|
73
|
+
hq search "<query>" # ranked full-text over the local copy
|
|
74
|
+
hq search "<query>" --mode hybrid # keyword + semantic
|
|
75
|
+
\`\`\`
|
|
68
76
|
|
|
69
77
|
Cite the file path of anything you quote so a teammate can open it.
|
|
70
78
|
`,
|
|
@@ -82,11 +90,19 @@ hq files share companies/<slug>/<path> # share a path with a teammate
|
|
|
82
90
|
hq files versions companies/<slug>/<file> # version history
|
|
83
91
|
\`\`\`
|
|
84
92
|
|
|
85
|
-
Paths are vault-relative and start with \`companies/<slug>/\`.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
Paths are vault-relative and start with \`companies/<slug>/\`. Read them on
|
|
94
|
+
demand with the commands above — this agent keeps no local copy of the vault
|
|
95
|
+
unless the kit was installed with \`--sync-files\` (check \`~/.hq-agent/kit.json\`
|
|
96
|
+
→ \`syncFiles\`), so never assume a file is already on disk.
|
|
97
|
+
|
|
98
|
+
To publish a change you need that local copy: with \`syncFiles\` on, edit under
|
|
99
|
+
the kit's HQ root (\`kit.json\` → \`hqRoot\`) and run
|
|
100
|
+
\`hq sync push --hq-root <hqRoot>\`. Without it, \`hq files get\` a file, and if
|
|
101
|
+
you need to write one back, say so and ask — do not improvise an upload.
|
|
102
|
+
|
|
103
|
+
Writes are audited under this agent's identity — only write where you were
|
|
104
|
+
asked to, and prefer creating a new file over overwriting one you did not
|
|
105
|
+
author.
|
|
90
106
|
`,
|
|
91
107
|
},
|
|
92
108
|
{
|
|
@@ -281,7 +281,9 @@ export function renderPlanLockNotice(status) {
|
|
|
281
281
|
if (lock.reasons.includes("agents")) {
|
|
282
282
|
lines.push(` Agents allowed on Starter: ${agentTarget(lock)}.`);
|
|
283
283
|
}
|
|
284
|
-
lines.push(" This workspace is read-only until it is fixed
|
|
284
|
+
lines.push(" Nothing has been deleted. This workspace is read-only until it is fixed:");
|
|
285
|
+
lines.push(" you can still delete things, but you cannot create, invite or upload.");
|
|
286
|
+
lines.push(" Two ways to fix it:");
|
|
285
287
|
const remedy = lock.reasons.length
|
|
286
288
|
? lock.reasons.map((reason) => reasonRemedy(reason, lock)).join(", and ")
|
|
287
289
|
: "come back under the Starter plan limits";
|
|
@@ -190,7 +190,7 @@ function withinDayWindow(shownAt, nowMs) {
|
|
|
190
190
|
return nowMs - shownAt < DAY_MS;
|
|
191
191
|
}
|
|
192
192
|
function buildOverBox(overEntries, upgradeUrl) {
|
|
193
|
-
const title = "⚠ HQ plan limit exceeded";
|
|
193
|
+
const title = "⚠ HQ Starter plan limit exceeded";
|
|
194
194
|
const upgrade = `Upgrade: ${upgradeUrl}`;
|
|
195
195
|
const resourceLines = overEntries.map(([key, entry]) => ` ${key}: ${entry.used}/${entry.limit}${dimensionSuffix(key)}`);
|
|
196
196
|
const contentLines = [title, "", ...resourceLines, "", upgrade];
|