@indigoai-us/hq-cli 5.309.1 → 5.311.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 +18 -0
- package/assets/scaffold/core/scripts/hq-dm-bind.sh +585 -0
- package/assets/scaffold/core/scripts/jobs-validate.sh +1 -1
- package/assets/scaffold/core/scripts/work-mesh-live-bind-trusted.sh +152 -0
- package/dist/command-catalog.generated.d.ts +28 -2
- package/dist/command-catalog.generated.js +34 -2
- package/dist/commands/channels.d.ts +1 -9
- package/dist/commands/channels.js +2 -2
- package/dist/commands/company-transfer.d.ts +4 -28
- package/dist/commands/company-transfer.js +5 -49
- package/dist/commands/core-checkpoint-stop-gate-native.d.ts +34 -0
- package/dist/commands/core-checkpoint-stop-gate-native.js +713 -0
- package/dist/commands/core-checkpoint.d.ts +0 -24
- package/dist/commands/core-checkpoint.js +4 -4
- package/dist/commands/core-command-catalog.js +1 -1
- package/dist/commands/core-dm-bind-native.d.ts +32 -0
- package/dist/commands/core-dm-bind-native.js +668 -0
- package/dist/commands/core-hq-session-native.d.ts +39 -0
- package/dist/commands/core-hq-session-native.js +579 -0
- package/dist/commands/core-jobs-native.js +1 -4
- package/dist/commands/core-native-session-flag.d.ts +4 -0
- package/dist/commands/core-native-session-flag.js +7 -0
- package/dist/commands/core-native-utilities.d.ts +2 -1
- package/dist/commands/core-native-utilities.js +4 -2
- package/dist/commands/core-native-worker-skills-flag.d.ts +4 -0
- package/dist/commands/core-native-worker-skills-flag.js +7 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +32 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.js +190 -0
- package/dist/commands/core-worker-lint-native.d.ts +4 -0
- package/dist/commands/core-worker-lint-native.js +160 -0
- package/dist/commands/core-worker-share-native.d.ts +5 -0
- package/dist/commands/core-worker-share-native.js +260 -0
- package/dist/commands/core-worker-skills-command.d.ts +8 -0
- package/dist/commands/core-worker-skills-command.js +33 -0
- package/dist/commands/core-worker-skills-runtime.d.ts +33 -0
- package/dist/commands/core-worker-skills-runtime.js +86 -0
- package/dist/commands/core.js +10 -2
- package/dist/commands/dm.d.ts +3 -7
- package/dist/commands/dm.js +3 -3
- package/dist/commands/integrations-import.d.ts +1 -1
- package/dist/commands/integrations-import.js +34 -19
- package/dist/commands/lanes-accounts.js +3 -1
- package/dist/commands/lanes-message.js +84 -10
- package/dist/commands/lanes-shared.d.ts +4 -1
- package/dist/commands/lanes.d.ts +4 -1
- package/dist/commands/lanes.js +48 -7
- package/dist/commands/personal-connector-settings-flag.d.ts +4 -0
- package/dist/commands/personal-connector-settings-flag.js +7 -0
- package/dist/commands/report.d.ts +1 -26
- package/dist/commands/report.js +6 -6
- package/dist/fast-core.js +15 -2
- package/dist/fast-scaffold.js +25 -2
- package/dist/index.js +8 -0
- package/dist/lib/bot/continuity.d.ts +2 -1
- package/dist/lib/bot/job-completions.d.ts +0 -2
- package/dist/lib/bot/job-completions.js +2 -2
- package/dist/lib/bot/local-config.d.ts +0 -10
- package/dist/lib/bot/local-config.js +3 -9
- package/dist/lib/bot/owner-context.d.ts +2 -6
- package/dist/lib/bot/owner-context.js +3 -3
- package/dist/lib/bot/runtime/windows.d.ts +0 -2
- package/dist/lib/bot/runtime/windows.js +1 -1
- package/dist/lib/bot/setup-explain.d.ts +0 -4
- package/dist/lib/bot/setup-explain.js +2 -2
- package/dist/lib/daemon/catalog.d.ts +3 -2
- package/dist/lib/daemon/hq-cloud.d.ts +2 -2
- package/dist/lib/daemon/hq-cloud.js +1 -1
- package/dist/lib/daemon/machine-type.d.ts +3 -4
- package/dist/lib/daemon/machine-type.js +2 -2
- package/dist/lib/daemon/remote-control.d.ts +0 -1
- package/dist/lib/daemon/remote-control.js +1 -1
- package/dist/lib/daemon/supervisor.d.ts +4 -9
- package/dist/lib/daemon/supervisor.js +5 -5
- package/dist/lib/lanes/admission-host.d.ts +3 -0
- package/dist/lib/lanes/admission-host.js +4 -0
- package/dist/lib/lanes/autopilot-registry.d.ts +62 -0
- package/dist/lib/lanes/autopilot-registry.js +192 -0
- package/dist/lib/lanes/autopilot.d.ts +2 -0
- package/dist/lib/lanes/autopilot.js +18 -5
- package/dist/lib/lanes/claude-balance.d.ts +6 -0
- package/dist/lib/lanes/claude-balance.js +17 -2
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/reconcile.js +14 -6
- package/dist/lib/lanes/spawn.js +2 -0
- package/dist/lib/mesh/live/daemon/credentials.d.ts +2 -17
- package/dist/lib/mesh/live/daemon/credentials.js +9 -9
- package/dist/lib/mesh/live/daemon/index.d.ts +5 -5
- package/dist/lib/mesh/live/daemon/index.js +2 -2
- package/dist/lib/mesh/live/daemon/state.d.ts +1 -1
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +4 -11
- package/dist/lib/mesh/live/daemon/transcript-watch.js +5 -5
- package/dist/lib/work-context/company.d.ts +2 -25
- package/dist/lib/work-context/company.js +3 -3
- package/dist/lib/work-context/config.d.ts +3 -8
- package/dist/lib/work-context/config.js +2 -13
- package/dist/lib/work-context/state.d.ts +4 -13
- package/dist/lib/work-context/state.js +2 -30
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const HQ_SESSION_ASSET = "core/scripts/hq-session.sh";
|
|
2
|
+
export declare const HQ_SESSION_HELP: string;
|
|
3
|
+
export type HqSessionResult = {
|
|
4
|
+
code: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
};
|
|
8
|
+
export type HqSessionDependencies = {
|
|
9
|
+
env: NodeJS.ProcessEnv;
|
|
10
|
+
now: () => Date;
|
|
11
|
+
readFile: (filePath: string, encoding: BufferEncoding) => string;
|
|
12
|
+
exists: (filePath: string) => boolean;
|
|
13
|
+
isFile: (filePath: string) => boolean;
|
|
14
|
+
isDirectory: (filePath: string) => boolean;
|
|
15
|
+
isSymlink: (filePath: string) => boolean;
|
|
16
|
+
listDirectory: (directory: string) => string[];
|
|
17
|
+
mkdir: (directory: string) => void;
|
|
18
|
+
writeFile: (filePath: string, contents: string, mode?: number) => void;
|
|
19
|
+
rename: (from: string, to: string) => void;
|
|
20
|
+
remove: (filePath: string) => void;
|
|
21
|
+
makeTempPath: () => string;
|
|
22
|
+
umask: () => number;
|
|
23
|
+
findExecutable: (name: string, env?: NodeJS.ProcessEnv) => string | undefined;
|
|
24
|
+
spawnDetached: (command: string, args: string[], cwd: string, env: NodeJS.ProcessEnv, logFile: string) => void;
|
|
25
|
+
runCommand: (command: string, args: string[], cwd: string, env: NodeJS.ProcessEnv) => {
|
|
26
|
+
status: number | null;
|
|
27
|
+
stdout: string;
|
|
28
|
+
stderr: string;
|
|
29
|
+
error?: Error;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/** In-process command body, with injectable file access for parity tests. */
|
|
33
|
+
export declare function executeHqSession(argv: readonly string[], context: {
|
|
34
|
+
cwd: string;
|
|
35
|
+
hqRoot?: string;
|
|
36
|
+
}, overrides?: Partial<HqSessionDependencies>): HqSessionResult;
|
|
37
|
+
/** Signature consumed by the lightweight fast-core dispatcher. */
|
|
38
|
+
export declare function runHqSessionFast(argv: readonly string[], _stdin: string, cwd: string, hqRoot: string | undefined): Promise<number>;
|
|
39
|
+
//# sourceMappingURL=core-hq-session-native.d.ts.map
|
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
/** Native fast-route implementation of the bundled hq-session.sh contract. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
6
|
+
export const HQ_SESSION_ASSET = "core/scripts/hq-session.sh";
|
|
7
|
+
export const HQ_SESSION_HELP = [
|
|
8
|
+
"hq-core: public",
|
|
9
|
+
"hq-session.sh — read/write the current session's metadata.",
|
|
10
|
+
"",
|
|
11
|
+
"Usage:",
|
|
12
|
+
" core/scripts/hq-session.sh current # print current session_id (or empty)",
|
|
13
|
+
" core/scripts/hq-session.sh path # print path to current meta.yaml",
|
|
14
|
+
" core/scripts/hq-session.sh get <key> # read a key from meta.yaml",
|
|
15
|
+
" core/scripts/hq-session.sh set <key> <value> # set/replace a top-level key",
|
|
16
|
+
"",
|
|
17
|
+
"Reserved value:",
|
|
18
|
+
" set company_slug personal # bind the session to no-company (personal) work",
|
|
19
|
+
"",
|
|
20
|
+
"Global option (before the subcommand):",
|
|
21
|
+
" --session-id <id> operate on this session instead of the resolved one",
|
|
22
|
+
].join("\n") + "\n";
|
|
23
|
+
const defaultDependencies = () => ({
|
|
24
|
+
env: process.env,
|
|
25
|
+
now: () => new Date(),
|
|
26
|
+
readFile: (filePath) => fs.readFileSync(filePath, "utf8"),
|
|
27
|
+
exists: (filePath) => fs.existsSync(filePath),
|
|
28
|
+
isFile: (filePath) => {
|
|
29
|
+
try {
|
|
30
|
+
return fs.statSync(filePath).isFile();
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (isExpectedFsMiss(error))
|
|
34
|
+
return false;
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
isDirectory: (filePath) => {
|
|
39
|
+
try {
|
|
40
|
+
return fs.statSync(filePath).isDirectory();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (isExpectedFsMiss(error))
|
|
44
|
+
return false;
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
isSymlink: (filePath) => {
|
|
49
|
+
try {
|
|
50
|
+
return fs.lstatSync(filePath).isSymbolicLink();
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (isExpectedFsMiss(error))
|
|
54
|
+
return false;
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
listDirectory: (directory) => fs.readdirSync(directory),
|
|
59
|
+
mkdir: (directory) => fs.mkdirSync(directory, { recursive: true }),
|
|
60
|
+
writeFile: (filePath, contents, mode) => fs.writeFileSync(filePath, contents, mode === undefined ? undefined : { mode }),
|
|
61
|
+
rename: (from, to) => fs.renameSync(from, to),
|
|
62
|
+
remove: (filePath) => { try {
|
|
63
|
+
fs.unlinkSync(filePath);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (!isExpectedFsMiss(error))
|
|
67
|
+
throw error;
|
|
68
|
+
} },
|
|
69
|
+
makeTempPath: () => path.join(os.tmpdir(), `tmp.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`),
|
|
70
|
+
umask: () => process.umask(),
|
|
71
|
+
findExecutable: (name, env = process.env) => {
|
|
72
|
+
for (const directory of (env.PATH ?? "").split(path.delimiter)) {
|
|
73
|
+
if (!directory)
|
|
74
|
+
continue;
|
|
75
|
+
const candidate = path.join(directory, name);
|
|
76
|
+
try {
|
|
77
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
78
|
+
return candidate;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const code = error?.code;
|
|
82
|
+
if (code !== "ENOENT" && code !== "ENOTDIR" && code !== "EACCES" && code !== "ELOOP")
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
},
|
|
88
|
+
spawnDetached: (command, args, cwd, env, logFile) => {
|
|
89
|
+
const output = fs.openSync(logFile, "a");
|
|
90
|
+
const child = spawn(command, args, { cwd, env, detached: true, stdio: ["ignore", output, output], windowsHide: true });
|
|
91
|
+
fs.closeSync(output);
|
|
92
|
+
child.once("error", (error) => {
|
|
93
|
+
fs.appendFileSync(logFile, `${error.message}\n`, { encoding: "utf8" });
|
|
94
|
+
});
|
|
95
|
+
child.unref();
|
|
96
|
+
},
|
|
97
|
+
runCommand: (command, args, cwd, env) => {
|
|
98
|
+
const result = spawnSync(command, args, { cwd, env, encoding: "utf8" });
|
|
99
|
+
return {
|
|
100
|
+
status: result.status,
|
|
101
|
+
stdout: result.stdout ?? "",
|
|
102
|
+
stderr: result.stderr ?? "",
|
|
103
|
+
...(result.error ? { error: result.error } : {}),
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
function isExpectedFsMiss(error) {
|
|
108
|
+
const code = error?.code;
|
|
109
|
+
return code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "ELOOP";
|
|
110
|
+
}
|
|
111
|
+
function asciiWhitespace(value) {
|
|
112
|
+
return value.replace(/[\t\n\v\f\r ]/g, "");
|
|
113
|
+
}
|
|
114
|
+
function validSessionId(value) {
|
|
115
|
+
return value.length > 0 && value !== "." && value !== ".." && /^[A-Za-z0-9._-]+$/.test(value);
|
|
116
|
+
}
|
|
117
|
+
function sessionIdFromEnv(env) {
|
|
118
|
+
for (const name of [
|
|
119
|
+
"HQ_SESSION_ID", "CLAUDE_CODE_SESSION_ID", "CLAUDE_SESSION_ID",
|
|
120
|
+
"CODEX_SESSION_ID", "CODEX_THREAD_ID", "GROK_SESSION_ID",
|
|
121
|
+
]) {
|
|
122
|
+
const value = asciiWhitespace(env[name] ?? "");
|
|
123
|
+
if (validSessionId(value))
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
return "";
|
|
127
|
+
}
|
|
128
|
+
function sessionIdFromCurrentFile(root, deps) {
|
|
129
|
+
const filePath = path.join(root, "workspace", "sessions", ".current");
|
|
130
|
+
if (!deps.isFile(filePath))
|
|
131
|
+
return "";
|
|
132
|
+
try {
|
|
133
|
+
const value = asciiWhitespace(deps.readFile(filePath, "utf8"));
|
|
134
|
+
return validSessionId(value) ? value : "";
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (isExpectedFsMiss(error))
|
|
138
|
+
return "";
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function resolveSessionId(root, deps) {
|
|
143
|
+
return sessionIdFromEnv(deps.env) || sessionIdFromCurrentFile(root, deps);
|
|
144
|
+
}
|
|
145
|
+
function findMetaValue(contents, key) {
|
|
146
|
+
for (const line of contents.split("\n")) {
|
|
147
|
+
const first = line.match(/^\s*(\S+)/)?.[1] ?? "";
|
|
148
|
+
if (first !== `${key}:`)
|
|
149
|
+
continue;
|
|
150
|
+
return { found: true, value: line.replace(/^[^:]+:[\t ]*/, "").replace(/^"|"$/g, "") };
|
|
151
|
+
}
|
|
152
|
+
return { found: false, value: "" };
|
|
153
|
+
}
|
|
154
|
+
function getMetaValue(contents, key) { return findMetaValue(contents, key).value; }
|
|
155
|
+
const WORK_MESH_SESSION_START_COMMAND = 'printf "%s" "$1" | bash "$2" SessionStart';
|
|
156
|
+
function recordWorkMeshSessionStart(input) {
|
|
157
|
+
const hook = path.join(input.root, "core", "hooks", "SessionStart", "35-work-mesh-session-start.sh");
|
|
158
|
+
// Keep the shell hook as the single owner of SessionStart's Work Mesh behavior.
|
|
159
|
+
// The bundled hq-session helper skips this path if either dependency is absent.
|
|
160
|
+
if (!input.isFile(hook) || !input.findExecutable("jq", input.env))
|
|
161
|
+
return "";
|
|
162
|
+
const event = JSON.stringify({ session_id: input.sessionId, cwd: input.env.PWD || input.cwd });
|
|
163
|
+
if (!event)
|
|
164
|
+
return "";
|
|
165
|
+
const logFile = path.join(input.root, "workspace", "logs", "work-mesh-hook.log");
|
|
166
|
+
try {
|
|
167
|
+
input.mkdir(path.dirname(logFile));
|
|
168
|
+
}
|
|
169
|
+
catch { /* The shell also suppresses mkdir failure and attempts the redirected launch. */ }
|
|
170
|
+
try {
|
|
171
|
+
input.spawnDetached("nohup", ["bash", "-c", WORK_MESH_SESSION_START_COMMAND, "_", event, hook], input.cwd, {
|
|
172
|
+
...input.env,
|
|
173
|
+
HQ_ROOT: input.root,
|
|
174
|
+
CLAUDE_CODE_SESSION_ID: input.sessionId,
|
|
175
|
+
}, logFile);
|
|
176
|
+
return "";
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
+
return `${message}\n`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function runWorkMeshProjectRebind(input) {
|
|
184
|
+
const library = path.join(input.root, "core", "scripts", "lib");
|
|
185
|
+
if (!input.isFile(path.join(library, "work-mesh-enqueue.sh")) ||
|
|
186
|
+
!input.isFile(path.join(library, "work-mesh-live-rebind.sh")))
|
|
187
|
+
return { stdout: "", stderr: "" };
|
|
188
|
+
const script = [
|
|
189
|
+
'. "$1/work-mesh-enqueue.sh" 2>/dev/null || true',
|
|
190
|
+
'. "$1/work-mesh-live-hook.sh" 2>/dev/null || true',
|
|
191
|
+
'. "$1/work-mesh-live-rebind.sh" 2>/dev/null || true',
|
|
192
|
+
'if [ -n "$4" ] && command -v work_mesh_live_rebind >/dev/null 2>&1; then',
|
|
193
|
+
' work_mesh_live_rebind "$2" "$3" "$4" "$5" "$3" "$6" "$5" || true',
|
|
194
|
+
'elif command -v work_mesh_live_write_binding_marker >/dev/null 2>&1; then',
|
|
195
|
+
' work_mesh_live_write_binding_marker "$2" "$3" "$6" "$5" || true',
|
|
196
|
+
'fi',
|
|
197
|
+
].join("\n");
|
|
198
|
+
const childEnv = { ...input.env, HQ_ROOT: input.root, PWD: input.env.PWD || input.cwd };
|
|
199
|
+
const result = input.runCommand("bash", ["-c", script, "_", library, input.sessionId, input.company,
|
|
200
|
+
input.previousProject, input.task, input.project], input.cwd, childEnv);
|
|
201
|
+
if (result.error) {
|
|
202
|
+
const code = result.error.code;
|
|
203
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
204
|
+
return { stdout: "", stderr: "" };
|
|
205
|
+
throw result.error;
|
|
206
|
+
}
|
|
207
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
208
|
+
}
|
|
209
|
+
function recordHqSessionWorkMeshEffects(input, deps) {
|
|
210
|
+
if (input.key === "company_slug" && input.value && input.value !== input.previous && input.value !== "personal") {
|
|
211
|
+
const stderr = recordWorkMeshSessionStart({
|
|
212
|
+
sessionId: input.sessionId,
|
|
213
|
+
root: input.root,
|
|
214
|
+
cwd: input.cwd,
|
|
215
|
+
env: deps.env,
|
|
216
|
+
isFile: deps.isFile,
|
|
217
|
+
findExecutable: deps.findExecutable,
|
|
218
|
+
mkdir: deps.mkdir,
|
|
219
|
+
spawnDetached: deps.spawnDetached,
|
|
220
|
+
});
|
|
221
|
+
return { stdout: "", stderr };
|
|
222
|
+
}
|
|
223
|
+
if (input.key === "project" && input.value && input.value !== input.previous) {
|
|
224
|
+
return runWorkMeshProjectRebind({
|
|
225
|
+
sessionId: input.sessionId,
|
|
226
|
+
previousProject: input.previous,
|
|
227
|
+
project: input.value,
|
|
228
|
+
root: input.root,
|
|
229
|
+
cwd: input.cwd,
|
|
230
|
+
company: getMetaValue(input.metadata, "company_slug"),
|
|
231
|
+
task: getMetaValue(input.metadata, "task"),
|
|
232
|
+
env: deps.env,
|
|
233
|
+
isFile: deps.isFile,
|
|
234
|
+
runCommand: deps.runCommand,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return { stdout: "", stderr: "" };
|
|
238
|
+
}
|
|
239
|
+
function timestamp(date) {
|
|
240
|
+
return date.toISOString().slice(0, 19) + "Z";
|
|
241
|
+
}
|
|
242
|
+
function currentMetaPath(root, sessionOverride, deps) {
|
|
243
|
+
const id = sessionOverride || resolveSessionId(root, deps);
|
|
244
|
+
return id ? path.join(root, "workspace", "sessions", id, "meta.yaml") : "";
|
|
245
|
+
}
|
|
246
|
+
function exactCompanyDir(root, slug, deps) {
|
|
247
|
+
if (!root || !slug)
|
|
248
|
+
return false;
|
|
249
|
+
const companies = path.join(root, "companies");
|
|
250
|
+
try {
|
|
251
|
+
for (const entry of deps.listDirectory(companies)) {
|
|
252
|
+
const candidate = path.join(companies, entry);
|
|
253
|
+
if (!deps.isDirectory(candidate) || deps.isSymlink(candidate))
|
|
254
|
+
continue;
|
|
255
|
+
if (entry === slug)
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (!isExpectedFsMiss(error))
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
function hasCompanyCaseAlias(root, slug, deps) {
|
|
266
|
+
if (!root || !slug)
|
|
267
|
+
return false;
|
|
268
|
+
const companies = path.join(root, "companies");
|
|
269
|
+
let entries;
|
|
270
|
+
try {
|
|
271
|
+
entries = deps.listDirectory(companies);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
if (isExpectedFsMiss(error))
|
|
275
|
+
return false;
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
const realEntries = entries.filter((entry) => {
|
|
279
|
+
const candidate = path.join(companies, entry);
|
|
280
|
+
return deps.isDirectory(candidate) && !deps.isSymlink(candidate);
|
|
281
|
+
});
|
|
282
|
+
if (realEntries.some((entry) => entry === slug))
|
|
283
|
+
return false;
|
|
284
|
+
const folded = slug.toLowerCase();
|
|
285
|
+
return realEntries.some((entry) => entry !== slug && entry.toLowerCase() === folded);
|
|
286
|
+
}
|
|
287
|
+
function hardPolicyLines(root, company, deps) {
|
|
288
|
+
if (!exactCompanyDir(root, company, deps))
|
|
289
|
+
return [];
|
|
290
|
+
const dir = path.join(root, "companies", company, "policies");
|
|
291
|
+
if (!deps.isDirectory(dir))
|
|
292
|
+
return [];
|
|
293
|
+
let files;
|
|
294
|
+
try {
|
|
295
|
+
files = deps.listDirectory(dir).sort();
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
if (isExpectedFsMiss(error))
|
|
299
|
+
return [];
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
const result = [];
|
|
303
|
+
for (const basename of files) {
|
|
304
|
+
if (!basename.endsWith(".md") || /^_digest.*\.md$/.test(basename) || basename === "README.md" ||
|
|
305
|
+
basename === "readme.md" || basename === "example-policy.md" || basename.includes(".conflict-") || basename.includes(" "))
|
|
306
|
+
continue;
|
|
307
|
+
const filePath = path.join(dir, basename);
|
|
308
|
+
let body;
|
|
309
|
+
try {
|
|
310
|
+
body = deps.readFile(filePath, "utf8");
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
if (isExpectedFsMiss(error))
|
|
314
|
+
continue;
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
const lines = body.split("\n");
|
|
318
|
+
let delimiters = 0;
|
|
319
|
+
let id = "";
|
|
320
|
+
let enforcement = "";
|
|
321
|
+
let ruleSection = false;
|
|
322
|
+
let rule = "";
|
|
323
|
+
for (const line of lines) {
|
|
324
|
+
if (/^---[\t ]*$/.test(line)) {
|
|
325
|
+
delimiters += 1;
|
|
326
|
+
if (delimiters >= 2 && ruleSection)
|
|
327
|
+
ruleSection = false;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (delimiters === 1) {
|
|
331
|
+
const idMatch = line.match(/^id:[\t ]*(.*)$/);
|
|
332
|
+
if (idMatch)
|
|
333
|
+
id = (idMatch[1] ?? "").replace(/^["']|["']$/g, "");
|
|
334
|
+
const enforceMatch = line.match(/^enforcement:[\t ]*(.*)$/);
|
|
335
|
+
if (enforceMatch)
|
|
336
|
+
enforcement = (enforceMatch[1] ?? "").replace(/[\t ]/g, "");
|
|
337
|
+
if (/^status:[\t ]*retired/.test(line))
|
|
338
|
+
enforcement = "";
|
|
339
|
+
}
|
|
340
|
+
if (delimiters >= 2 && line === "## Rule") {
|
|
341
|
+
ruleSection = true;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (delimiters >= 2 && ruleSection && line.startsWith("## ")) {
|
|
345
|
+
ruleSection = false;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (delimiters >= 2 && ruleSection && !rule && line.trim()) {
|
|
349
|
+
rule = line.replaceAll("**", "");
|
|
350
|
+
if (rule.length > 160)
|
|
351
|
+
rule = `${rule.slice(0, 157)}...`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (enforcement !== "hard" || !rule)
|
|
355
|
+
continue;
|
|
356
|
+
const policyId = id || basename.replace(/\.md$/, "");
|
|
357
|
+
result.push(`- [hard] **${policyId}**: ${rule} Full text: \`companies/${company}/policies/${basename}\`.`);
|
|
358
|
+
}
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
function awkNumericValue(value, fallback) {
|
|
362
|
+
if (!value)
|
|
363
|
+
return fallback;
|
|
364
|
+
const match = value.match(/^\s*([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/);
|
|
365
|
+
return match ? Number(match[1]) : 0;
|
|
366
|
+
}
|
|
367
|
+
function emitHardPolicies(root, company, deps) {
|
|
368
|
+
const lines = hardPolicyLines(root, company, deps);
|
|
369
|
+
if (!lines.length)
|
|
370
|
+
return "";
|
|
371
|
+
const cap = awkNumericValue(deps.env.HQ_COMPANY_BIND_POLICY_CAP, 32);
|
|
372
|
+
const maxBytes = awkNumericValue(deps.env.HQ_COMPANY_BIND_POLICY_BYTES, 7168);
|
|
373
|
+
let bytes = 0;
|
|
374
|
+
let kept = 0;
|
|
375
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
376
|
+
const next = Buffer.byteLength(lines[index], "utf8") + 1;
|
|
377
|
+
if (index + 1 > cap || bytes + next > maxBytes)
|
|
378
|
+
break;
|
|
379
|
+
bytes += next;
|
|
380
|
+
kept += 1;
|
|
381
|
+
}
|
|
382
|
+
const dropped = lines.length - kept;
|
|
383
|
+
const output = [
|
|
384
|
+
"",
|
|
385
|
+
`<company-policy-digest co="${company}">`,
|
|
386
|
+
`# ${company} hard-enforcement policies (auto-surfaced on company bind)`,
|
|
387
|
+
"> Company context just bound mid-session. These HARD rules now apply.",
|
|
388
|
+
`> Full text: open the file path shown on each policy line. Browse \`companies/${company}/policies/\` for the full set.`,
|
|
389
|
+
"",
|
|
390
|
+
...lines.slice(0, kept),
|
|
391
|
+
];
|
|
392
|
+
if (dropped > 0) {
|
|
393
|
+
output.push("", `> ${dropped} more hard ${dropped === 1 ? "policy" : "policies"} not shown (bind budget: ${Math.trunc(cap)} policies / ${Math.trunc(maxBytes)} bytes). Browse \`companies/${company}/policies/\` for the full set; matching rules re-surface via the policy trigger hook when they apply.`);
|
|
394
|
+
}
|
|
395
|
+
output.push("</company-policy-digest>");
|
|
396
|
+
return `${output.join("\n")}\n`;
|
|
397
|
+
}
|
|
398
|
+
function validateSenior(value) {
|
|
399
|
+
if (value === "user")
|
|
400
|
+
return true;
|
|
401
|
+
const match = value.match(/^(session|lane):(.*)$/);
|
|
402
|
+
if (!match)
|
|
403
|
+
return false;
|
|
404
|
+
const id = match[2];
|
|
405
|
+
return id !== "" && id !== "." && id !== ".." && /^[A-Za-z0-9._-]+$/.test(id);
|
|
406
|
+
}
|
|
407
|
+
function atomicWrite(filePath, contents, deps) {
|
|
408
|
+
const temp = deps.makeTempPath();
|
|
409
|
+
try {
|
|
410
|
+
deps.writeFile(temp, contents, 0o600);
|
|
411
|
+
deps.rename(temp, filePath);
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
deps.remove(temp);
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function mintScopeCapability(root, sessionId, company, deps) {
|
|
419
|
+
if (!root || !sessionId || !company || !/^[A-Za-z0-9_-]+$/.test(company))
|
|
420
|
+
return false;
|
|
421
|
+
const directory = path.join(root, "workspace", "sessions", sessionId);
|
|
422
|
+
const filePath = path.join(directory, "scope-capability.json");
|
|
423
|
+
try {
|
|
424
|
+
deps.mkdir(directory);
|
|
425
|
+
const value = { session_id: sessionId, company_slug: company, minted_at: timestamp(deps.now()) };
|
|
426
|
+
atomicWrite(filePath, `${JSON.stringify(value, null, 2)}\n`, deps);
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
if (isExpectedFsMiss(error))
|
|
431
|
+
return false;
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/** In-process command body, with injectable file access for parity tests. */
|
|
436
|
+
export function executeHqSession(argv, context, overrides = {}) {
|
|
437
|
+
const deps = { ...defaultDependencies(), ...overrides };
|
|
438
|
+
const root = context.hqRoot || context.cwd;
|
|
439
|
+
let sessionOverride = "";
|
|
440
|
+
let index = 0;
|
|
441
|
+
while (index < argv.length) {
|
|
442
|
+
const value = argv[index];
|
|
443
|
+
if (value === "--session-id") {
|
|
444
|
+
sessionOverride = argv[index + 1] ?? "";
|
|
445
|
+
if (!validSessionId(sessionOverride))
|
|
446
|
+
return { code: 1, stdout: "", stderr: `hq-session: invalid --session-id: '${sessionOverride}'\n` };
|
|
447
|
+
index += 2;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (value?.startsWith("--session-id=")) {
|
|
451
|
+
sessionOverride = value.slice("--session-id=".length);
|
|
452
|
+
if (!validSessionId(sessionOverride))
|
|
453
|
+
return { code: 1, stdout: "", stderr: `hq-session: invalid --session-id: '${sessionOverride}'\n` };
|
|
454
|
+
index += 1;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
const subcommand = argv[index] ?? "";
|
|
460
|
+
const args = argv.slice(index + 1);
|
|
461
|
+
if (!subcommand || subcommand === "-h" || subcommand === "--help" || subcommand === "help") {
|
|
462
|
+
return { code: 0, stdout: HQ_SESSION_HELP, stderr: "" };
|
|
463
|
+
}
|
|
464
|
+
if (!["current", "path", "get", "set"].includes(subcommand)) {
|
|
465
|
+
return { code: 1, stdout: "", stderr: `hq-session: unknown subcommand '${subcommand}'\n${HQ_SESSION_HELP}` };
|
|
466
|
+
}
|
|
467
|
+
if (subcommand === "current") {
|
|
468
|
+
return { code: 0, stdout: sessionOverride || resolveSessionId(root, deps), stderr: "" };
|
|
469
|
+
}
|
|
470
|
+
const metaPath = currentMetaPath(root, sessionOverride, deps);
|
|
471
|
+
if (subcommand === "path")
|
|
472
|
+
return { code: 0, stdout: metaPath, stderr: "" };
|
|
473
|
+
if (subcommand === "get") {
|
|
474
|
+
const key = args[0] ?? "";
|
|
475
|
+
if (!key)
|
|
476
|
+
return { code: 1, stdout: "", stderr: "usage: hq-session.sh get <key>\n" };
|
|
477
|
+
if (!metaPath || !deps.isFile(metaPath))
|
|
478
|
+
return { code: 0, stdout: "", stderr: "" };
|
|
479
|
+
try {
|
|
480
|
+
const result = findMetaValue(deps.readFile(metaPath, "utf8"), key);
|
|
481
|
+
return { code: 0, stdout: result.found ? `${result.value}\n` : "", stderr: "" };
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
if (error?.code === "EACCES" || error?.code === "EPERM") {
|
|
485
|
+
return { code: 2, stdout: "", stderr: `awk: fatal: cannot open file \`${metaPath}' for reading: Permission denied\n` };
|
|
486
|
+
}
|
|
487
|
+
if (isExpectedFsMiss(error))
|
|
488
|
+
return { code: 1, stdout: "", stderr: `awk: cannot open ${metaPath} (Permission denied)\n` };
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const key = args[0] ?? "";
|
|
493
|
+
const value = args[1] ?? "";
|
|
494
|
+
if (!key || args.length < 2)
|
|
495
|
+
return { code: 1, stdout: "", stderr: "usage: hq-session.sh set <key> <value>\n" };
|
|
496
|
+
if (key === "company_slug" && value) {
|
|
497
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
498
|
+
return { code: 1, stdout: "", stderr: `hq-session: invalid company_slug: '${value}' (allowed characters: A-Z a-z 0-9 - _)\n` };
|
|
499
|
+
}
|
|
500
|
+
const dir = path.join(root, "companies", value);
|
|
501
|
+
if (value !== "personal" && (deps.isSymlink(dir) || hasCompanyCaseAlias(root, value, deps))) {
|
|
502
|
+
return { code: 1, stdout: "", stderr: `hq-session: company_slug must use the exact real directory name: '${value}'\n` };
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (key === "senior" && !validateSenior(value)) {
|
|
506
|
+
return { code: 1, stdout: "", stderr: `hq-session: invalid senior: '${value}' (accepted forms: user, session:<id>, lane:<id>)\n` };
|
|
507
|
+
}
|
|
508
|
+
const sessionId = sessionOverride || resolveSessionId(root, deps);
|
|
509
|
+
if (!sessionId) {
|
|
510
|
+
return {
|
|
511
|
+
code: 1,
|
|
512
|
+
stdout: "",
|
|
513
|
+
stderr: "hq-session: no current session — no session id in the environment (HQ_SESSION_ID, CLAUDE_CODE_SESSION_ID, CLAUDE_SESSION_ID, CODEX_SESSION_ID, CODEX_THREAD_ID) and workspace/sessions/.current is missing or invalid. Is master-hook installed? Pass --session-id <id> to target a session explicitly.\n",
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
const target = path.join(root, "workspace", "sessions", sessionId, "meta.yaml");
|
|
517
|
+
try {
|
|
518
|
+
deps.mkdir(path.dirname(target));
|
|
519
|
+
if (!deps.isFile(target)) {
|
|
520
|
+
const initial = `session_id: ${sessionId}\nstarted_at: "${timestamp(deps.now())}"\nsenior: user\n`;
|
|
521
|
+
deps.writeFile(target, initial, 0o666 & ~deps.umask());
|
|
522
|
+
}
|
|
523
|
+
let previous = "";
|
|
524
|
+
try {
|
|
525
|
+
previous = getMetaValue(deps.readFile(target, "utf8"), key);
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
previous = "";
|
|
529
|
+
}
|
|
530
|
+
const oldText = deps.readFile(target, "utf8");
|
|
531
|
+
const records = oldText.split("\n");
|
|
532
|
+
if (records.at(-1) === "")
|
|
533
|
+
records.pop();
|
|
534
|
+
let found = false;
|
|
535
|
+
const replaced = records.map((line) => {
|
|
536
|
+
const first = line.match(/^\s*(\S+)/)?.[1] ?? "";
|
|
537
|
+
if (first !== `${key}:`)
|
|
538
|
+
return line;
|
|
539
|
+
found = true;
|
|
540
|
+
return `${key}: ${value}`;
|
|
541
|
+
});
|
|
542
|
+
if (!found)
|
|
543
|
+
replaced.push(`${key}: ${value}`);
|
|
544
|
+
const updatedText = `${replaced.join("\n")}\n`;
|
|
545
|
+
atomicWrite(target, updatedText, deps);
|
|
546
|
+
let stdout = "";
|
|
547
|
+
if (key === "company_slug" && value && value !== previous) {
|
|
548
|
+
if (!mintScopeCapability(root, sessionId, value, deps)) {
|
|
549
|
+
return { code: 1, stdout: "", stderr: `hq-session: failed to mint scope-capability for session ${sessionId}\n` };
|
|
550
|
+
}
|
|
551
|
+
if (value !== "personal")
|
|
552
|
+
stdout = emitHardPolicies(root, value, deps);
|
|
553
|
+
}
|
|
554
|
+
const workMeshOutput = recordHqSessionWorkMeshEffects({
|
|
555
|
+
key,
|
|
556
|
+
value,
|
|
557
|
+
previous,
|
|
558
|
+
sessionId,
|
|
559
|
+
root,
|
|
560
|
+
cwd: deps.env.PWD || context.cwd,
|
|
561
|
+
metadata: updatedText,
|
|
562
|
+
}, deps);
|
|
563
|
+
return { code: 0, stdout: stdout + workMeshOutput.stdout, stderr: workMeshOutput.stderr };
|
|
564
|
+
}
|
|
565
|
+
catch (error) {
|
|
566
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
567
|
+
return { code: 1, stdout: "", stderr: `${message}\n` };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/** Signature consumed by the lightweight fast-core dispatcher. */
|
|
571
|
+
export async function runHqSessionFast(argv, _stdin, cwd, hqRoot) {
|
|
572
|
+
const result = executeHqSession(argv, { cwd, hqRoot });
|
|
573
|
+
if (result.stdout)
|
|
574
|
+
process.stdout.write(result.stdout);
|
|
575
|
+
if (result.stderr)
|
|
576
|
+
process.stderr.write(result.stderr);
|
|
577
|
+
return result.code;
|
|
578
|
+
}
|
|
579
|
+
//# sourceMappingURL=core-hq-session-native.js.map
|
|
@@ -291,10 +291,7 @@ function asText(value) {
|
|
|
291
291
|
return String(value);
|
|
292
292
|
}
|
|
293
293
|
function matchingJobSecretPatterns(value) {
|
|
294
|
-
|
|
295
|
-
// Preserve that shell behavior: grep interprets this class as an option.
|
|
296
|
-
return matchingSecretPatterns(value, { requireBoundary: false })
|
|
297
|
-
.filter((name) => name !== "Private key");
|
|
294
|
+
return matchingSecretPatterns(value, { requireBoundary: false });
|
|
298
295
|
}
|
|
299
296
|
function validateCwd(file, field, cwd, emit) {
|
|
300
297
|
if (cwd.startsWith("/")) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Default-off rollout gate for native session and Stop-hook plumbing. */
|
|
2
|
+
export declare const CORE_NATIVE_SESSION_FLAG = "core-native-session";
|
|
3
|
+
export declare function coreNativeSessionEnabled(): Promise<boolean>;
|
|
4
|
+
//# sourceMappingURL=core-native-session-flag.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Default-off rollout gate for native session and Stop-hook plumbing. */
|
|
2
|
+
export const CORE_NATIVE_SESSION_FLAG = "core-native-session";
|
|
3
|
+
export async function coreNativeSessionEnabled() {
|
|
4
|
+
const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
|
|
5
|
+
return resolveProcessFlagGate(CORE_NATIVE_SESSION_FLAG, { fallback: false }, () => false);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=core-native-session-flag.js.map
|
|
@@ -5,6 +5,7 @@ export type UtilityOutput = {
|
|
|
5
5
|
code: number;
|
|
6
6
|
stdout: string;
|
|
7
7
|
stderr: string;
|
|
8
|
+
stderrBytes?: Uint8Array;
|
|
8
9
|
};
|
|
9
10
|
export type UtilityContext = {
|
|
10
11
|
root: string;
|
|
@@ -18,7 +19,7 @@ export type NativeUtilityDefinition = {
|
|
|
18
19
|
root: CoreCommandRoot;
|
|
19
20
|
summary: string;
|
|
20
21
|
resolveRoot: (cwd: string, hqRoot?: string) => string;
|
|
21
|
-
run: (args: string[], context: UtilityContext) => UtilityOutput
|
|
22
|
+
run: (args: string[], context: UtilityContext) => UtilityOutput | Promise<UtilityOutput>;
|
|
22
23
|
};
|
|
23
24
|
/** Register one raw-argv command and retain the original shell as rollback. */
|
|
24
25
|
export declare function registerNativeUtility(core: Command, definition: NativeUtilityDefinition): void;
|
|
@@ -31,10 +31,12 @@ export function registerNativeUtility(core, definition) {
|
|
|
31
31
|
process.exitCode = result.code;
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
-
const result = definition.run(operands, { root, cwd, env: process.env, now: () => new Date() });
|
|
34
|
+
const result = await definition.run(operands, { root, cwd, env: process.env, now: () => new Date() });
|
|
35
35
|
if (result.stdout)
|
|
36
36
|
process.stdout.write(result.stdout);
|
|
37
|
-
if (result.
|
|
37
|
+
if (result.stderrBytes)
|
|
38
|
+
process.stderr.write(result.stderrBytes);
|
|
39
|
+
else if (result.stderr)
|
|
38
40
|
process.stderr.write(result.stderr);
|
|
39
41
|
if (result.code !== 0)
|
|
40
42
|
process.exitCode = result.code;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Default-off rollout gate for native worker-scoped skill commands. */
|
|
2
|
+
export declare const CORE_NATIVE_WORKER_SKILLS_FLAG = "core-native-worker-skills";
|
|
3
|
+
export declare function coreNativeWorkerSkillsEnabled(): Promise<boolean>;
|
|
4
|
+
//# sourceMappingURL=core-native-worker-skills-flag.d.ts.map
|