@indigoai-us/hq-cli 5.291.0 → 5.293.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 +53 -8
- package/assets/scaffold/core/scripts/handoff-finalize.sh +18 -0
- package/assets/scaffold/core/scripts/handoff-post.sh +71 -6
- package/assets/scaffold/core/scripts/hq-delegate-bundle.sh +307 -0
- package/assets/scaffold/core/scripts/hq-delegate-grant.sh +226 -0
- package/assets/scaffold/core/scripts/hq-delegate-publish.sh +91 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +259 -0
- package/assets/scaffold/core/scripts/hq-delegate-verify.sh +213 -0
- package/assets/scaffold/core/scripts/hq-job-notify.sh +389 -0
- package/assets/scaffold/core/scripts/hq-job-probe.sh +835 -0
- package/assets/scaffold/core/scripts/hq-job-remediate.sh +359 -0
- package/assets/scaffold/core/scripts/hq-job-run.sh +1025 -0
- package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +830 -0
- package/dist/command-catalog.generated.d.ts +109 -1
- package/dist/command-catalog.generated.js +136 -1
- package/dist/commands/core-delegate-w1b.d.ts +8 -0
- package/dist/commands/core-delegate-w1b.js +1049 -0
- package/dist/commands/core-delegate.d.ts +28 -0
- package/dist/commands/core-delegate.js +15 -13
- package/dist/commands/core-handoff.js +152 -23
- package/dist/commands/core-jobs-native.d.ts +15 -0
- package/dist/commands/core-jobs-native.js +709 -2
- package/dist/commands/core-ontology.d.ts +22 -4
- package/dist/commands/core-ontology.js +90 -8
- package/dist/commands/core.js +10 -0
- package/dist/commands/lanes-move.js +54 -1
- package/dist/commands/lanes-questions.js +4 -0
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +119 -5
- package/dist/lib/checkpoint-ontology/drop-aliases.d.ts +41 -0
- package/dist/lib/checkpoint-ontology/drop-aliases.js +76 -0
- package/dist/lib/checkpoint-ontology/entity-file.d.ts +6 -3
- package/dist/lib/checkpoint-ontology/entity-file.js +12 -6
- package/dist/lib/checkpoint-ontology/merge-copies.d.ts +27 -8
- package/dist/lib/checkpoint-ontology/merge-copies.js +64 -97
- package/dist/lib/core-utils/sentry-report.js +2 -2
- package/dist/lib/daemon/flag.d.ts +2 -0
- package/dist/lib/daemon/flag.js +2 -0
- package/dist/lib/daemon/run.d.ts +11 -0
- package/dist/lib/daemon/run.js +17 -1
- package/dist/lib/lanes/autopilot.d.ts +13 -1
- package/dist/lib/lanes/autopilot.js +271 -28
- package/dist/lib/lanes/grants.d.ts +1 -1
- package/dist/lib/lanes/grants.js +7 -5
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +5 -0
- package/dist/lib/lanes/keep-alive.js +9 -4
- package/dist/lib/lanes/questions.d.ts +2 -1
- package/dist/lib/lanes/questions.js +2 -0
- package/dist/lib/lanes/reconcile.js +186 -9
- package/dist/lib/lanes/spawn.js +4 -0
- package/dist/lib/lanes/store.js +21 -1
- package/dist/lib/lanes/types.d.ts +13 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.js +3 -1
- package/dist/utils/scaffold-sibling-references.js +4 -4
- package/dist/utils/self-update.d.ts +13 -0
- package/dist/utils/self-update.js +33 -7
- package/dist/utils/staged-install.d.ts +34 -0
- package/dist/utils/staged-install.js +296 -0
- package/dist/utils/version-gate.d.ts +15 -0
- package/dist/utils/version-gate.js +46 -2
- package/package.json +1 -1
- package/scripts/mesh-daemon-if-stale.mjs +4 -0
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
/** Native implementations of the five `hq core delegate` shell companions. */
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
export declare const CORE_NATIVE_DELEGATE_FLAG = "core-native-delegate";
|
|
4
|
+
export type DelegateContext = {
|
|
5
|
+
cwd: string;
|
|
6
|
+
root?: string;
|
|
7
|
+
};
|
|
8
|
+
type CommandResult = {
|
|
9
|
+
code: number;
|
|
10
|
+
stdout?: string;
|
|
11
|
+
stderr?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function subprocess(command: string, args: string[], cwd: string, options?: {
|
|
14
|
+
input?: Buffer | string;
|
|
15
|
+
stdinFd?: number;
|
|
16
|
+
stderr?: "inherit" | "ignore" | "pipe";
|
|
17
|
+
stdout?: "capture" | "inherit" | "ignore";
|
|
18
|
+
}): CommandResult;
|
|
19
|
+
type JsonRecord = Record<string, unknown>;
|
|
20
|
+
export declare function isRecord(value: unknown): value is JsonRecord;
|
|
21
|
+
export declare function readJsonText(text: string): unknown;
|
|
22
|
+
export declare function readJsonFile(file: string): unknown;
|
|
23
|
+
export declare function jqFallback(value: unknown, fallback?: unknown): unknown;
|
|
24
|
+
export declare function jsonAt(value: unknown, dottedPath: string): unknown;
|
|
25
|
+
export declare function rawJsonValue(value: unknown): string;
|
|
26
|
+
export declare function jqRawPath(value: unknown, dottedPath: string, fallback?: unknown): string;
|
|
27
|
+
export declare function jqRawPaths(value: unknown, dottedPaths: string[], fallback?: unknown): string;
|
|
28
|
+
export declare function requireRecord(value: unknown, property: string): JsonRecord;
|
|
29
|
+
export declare function moveFile(source: string, target: string): void;
|
|
30
|
+
export declare function writeJsonAtomically(target: string, value: unknown, siblingTemp?: boolean): void;
|
|
4
31
|
/** Register the Wave 1a delegate commands and preserve the shell rollback path. */
|
|
5
32
|
export declare function registerCoreDelegateCommand(core: Command): void;
|
|
33
|
+
export {};
|
|
6
34
|
//# sourceMappingURL=core-delegate.d.ts.map
|
|
@@ -8,6 +8,7 @@ import { resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
|
8
8
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
9
9
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
10
10
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
11
|
+
import { registerCoreDelegateW1b } from "./core-delegate-w1b.js";
|
|
11
12
|
export const CORE_NATIVE_DELEGATE_FLAG = "core-native-delegate";
|
|
12
13
|
const SCRIPT_ASSETS = {
|
|
13
14
|
resolve: "core/scripts/hq-delegate-resolve.sh",
|
|
@@ -60,13 +61,13 @@ function commandPath(name, cwd = process.cwd()) {
|
|
|
60
61
|
function commandExists(name, cwd = process.cwd()) {
|
|
61
62
|
return commandPath(name, cwd) !== undefined;
|
|
62
63
|
}
|
|
63
|
-
function subprocess(command, args, cwd, options = {}) {
|
|
64
|
+
export function subprocess(command, args, cwd, options = {}) {
|
|
64
65
|
const result = spawnSync(command, args, {
|
|
65
66
|
cwd,
|
|
66
67
|
env: process.env,
|
|
67
68
|
input: options.input,
|
|
68
69
|
encoding: "utf8",
|
|
69
|
-
stdio: [options.stdinFd ?? "inherit", options.stdout === "inherit" ? "inherit" : options.stdout === "ignore" ? "ignore" : "pipe", options.stderr ?? "inherit"],
|
|
70
|
+
stdio: [options.stdinFd ?? "inherit", options.stdout === "inherit" ? "inherit" : options.stdout === "ignore" ? "ignore" : "pipe", options.stderr === "pipe" ? "pipe" : options.stderr ?? "inherit"],
|
|
70
71
|
});
|
|
71
72
|
if (result.error) {
|
|
72
73
|
return { code: 127, stderr: result.error.message, stdout: "" };
|
|
@@ -77,10 +78,10 @@ function subprocess(command, args, cwd, options = {}) {
|
|
|
77
78
|
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
|
-
function isRecord(value) {
|
|
81
|
+
export function isRecord(value) {
|
|
81
82
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
82
83
|
}
|
|
83
|
-
function readJsonText(text) {
|
|
84
|
+
export function readJsonText(text) {
|
|
84
85
|
return JSON.parse(text);
|
|
85
86
|
}
|
|
86
87
|
function jqParseDiagnostic(text) {
|
|
@@ -92,7 +93,7 @@ function jqParseDiagnostic(text) {
|
|
|
92
93
|
}
|
|
93
94
|
return "jq: parse error: Invalid JSON text\n";
|
|
94
95
|
}
|
|
95
|
-
function readJsonFile(file) {
|
|
96
|
+
export function readJsonFile(file) {
|
|
96
97
|
return readJsonText(fs.readFileSync(file, "utf8"));
|
|
97
98
|
}
|
|
98
99
|
function jqValid(file) {
|
|
@@ -108,24 +109,24 @@ function jqValid(file) {
|
|
|
108
109
|
function jqTruthy(value) {
|
|
109
110
|
return value !== null && value !== undefined && value !== false;
|
|
110
111
|
}
|
|
111
|
-
function jqFallback(value, fallback = "") {
|
|
112
|
+
export function jqFallback(value, fallback = "") {
|
|
112
113
|
return jqTruthy(value) ? value : fallback;
|
|
113
114
|
}
|
|
114
|
-
function jsonAt(value, dottedPath) {
|
|
115
|
+
export function jsonAt(value, dottedPath) {
|
|
115
116
|
let current = value;
|
|
116
117
|
for (const part of dottedPath.split(".")) {
|
|
117
118
|
current = isRecord(current) ? current[part] : undefined;
|
|
118
119
|
}
|
|
119
120
|
return current;
|
|
120
121
|
}
|
|
121
|
-
function rawJsonValue(value) {
|
|
122
|
+
export function rawJsonValue(value) {
|
|
122
123
|
return serializeJqJson(value, "raw").replace(/\n+$/, "");
|
|
123
124
|
}
|
|
124
|
-
function jqRawPath(value, dottedPath, fallback = "") {
|
|
125
|
+
export function jqRawPath(value, dottedPath, fallback = "") {
|
|
125
126
|
const found = jsonAt(value, dottedPath);
|
|
126
127
|
return found === undefined && fallback === "" ? "" : rawJsonValue(jqFallback(found, fallback));
|
|
127
128
|
}
|
|
128
|
-
function jqRawPaths(value, dottedPaths, fallback = "") {
|
|
129
|
+
export function jqRawPaths(value, dottedPaths, fallback = "") {
|
|
129
130
|
for (const dottedPath of dottedPaths) {
|
|
130
131
|
const found = jsonAt(value, dottedPath);
|
|
131
132
|
if (jqTruthy(found))
|
|
@@ -133,13 +134,13 @@ function jqRawPaths(value, dottedPaths, fallback = "") {
|
|
|
133
134
|
}
|
|
134
135
|
return rawJsonValue(fallback);
|
|
135
136
|
}
|
|
136
|
-
function requireRecord(value, property) {
|
|
137
|
+
export function requireRecord(value, property) {
|
|
137
138
|
const result = isRecord(value) ? value[property] : undefined;
|
|
138
139
|
if (!isRecord(result))
|
|
139
140
|
throw new TypeError(`Cannot update non-object field ${property}`);
|
|
140
141
|
return result;
|
|
141
142
|
}
|
|
142
|
-
function moveFile(source, target) {
|
|
143
|
+
export function moveFile(source, target) {
|
|
143
144
|
try {
|
|
144
145
|
fs.renameSync(source, target);
|
|
145
146
|
}
|
|
@@ -151,7 +152,7 @@ function moveFile(source, target) {
|
|
|
151
152
|
fs.unlinkSync(source);
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
|
-
function writeJsonAtomically(target, value, siblingTemp = false) {
|
|
155
|
+
export function writeJsonAtomically(target, value, siblingTemp = false) {
|
|
155
156
|
const serialized = serializeJqJson(value, "pretty");
|
|
156
157
|
if (siblingTemp) {
|
|
157
158
|
const temporary = `${target}.tmp`;
|
|
@@ -983,5 +984,6 @@ export function registerCoreDelegateCommand(core) {
|
|
|
983
984
|
});
|
|
984
985
|
describeCoreCommand(command, { execution: "native", asset: SCRIPT_ASSETS[name], root: rootMode });
|
|
985
986
|
}
|
|
987
|
+
registerCoreDelegateW1b(delegate, core);
|
|
986
988
|
}
|
|
987
989
|
//# sourceMappingURL=core-delegate.js.map
|
|
@@ -46,6 +46,66 @@ const OPEN_STEPS_UNKNOWN_USAGE = `# hq-core: public
|
|
|
46
46
|
set -uo pipefail
|
|
47
47
|
HQ_ROOT="\${HQ_ROOT:-\${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "\${BASH_SOURCE[0]}")/../.." && pwd)}}"
|
|
48
48
|
`;
|
|
49
|
+
const SESSION_ID_ENV_KEYS = [
|
|
50
|
+
"HQ_SESSION_ID",
|
|
51
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
52
|
+
"CLAUDE_SESSION_ID",
|
|
53
|
+
"CODEX_SESSION_ID",
|
|
54
|
+
"CODEX_THREAD_ID",
|
|
55
|
+
"GROK_SESSION_ID",
|
|
56
|
+
];
|
|
57
|
+
function validSessionId(value) {
|
|
58
|
+
return value !== "" && value !== "." && value !== ".." && /^[A-Za-z0-9._-]+$/.test(value);
|
|
59
|
+
}
|
|
60
|
+
function resolveSessionId(root) {
|
|
61
|
+
for (const key of SESSION_ID_ENV_KEYS) {
|
|
62
|
+
const value = (process.env[key] ?? "").replace(/\s/g, "");
|
|
63
|
+
if (validSessionId(value))
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
const current = path.join(root, "workspace/sessions/.current");
|
|
67
|
+
try {
|
|
68
|
+
const value = fs.readFileSync(current, "utf8").replace(/\s/g, "");
|
|
69
|
+
return validSessionId(value) ? value : "";
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function boundSessionCompany(root) {
|
|
76
|
+
const sessionId = resolveSessionId(root);
|
|
77
|
+
if (!sessionId)
|
|
78
|
+
return "";
|
|
79
|
+
const metadata = path.join(root, "workspace/sessions", sessionId, "meta.yaml");
|
|
80
|
+
let contents;
|
|
81
|
+
try {
|
|
82
|
+
contents = fs.readFileSync(metadata, "utf8");
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return "";
|
|
86
|
+
}
|
|
87
|
+
const line = contents.split(/\r?\n/).find((entry) => /^\s*company_slug:/.test(entry));
|
|
88
|
+
if (!line)
|
|
89
|
+
return "";
|
|
90
|
+
const company = (line.replace(/^\s*company_slug:\s*/, "").replace(/^"|"$/g, ""));
|
|
91
|
+
return /^[a-z][a-z0-9_-]*$/.test(company) ? company : "";
|
|
92
|
+
}
|
|
93
|
+
class HandoffJsonOptionError extends Error {
|
|
94
|
+
flag;
|
|
95
|
+
constructor(flag, cause) {
|
|
96
|
+
super(cause instanceof Error ? cause.message : String(cause));
|
|
97
|
+
this.flag = flag;
|
|
98
|
+
this.name = "HandoffJsonOptionError";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function parseHandoffJson(value, flag) {
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(value);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
throw new HandoffJsonOptionError(flag, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
49
109
|
function nowFor(context) {
|
|
50
110
|
return context.now?.() ?? new Date();
|
|
51
111
|
}
|
|
@@ -259,7 +319,7 @@ export function runHandoffOpenSteps(args, context) {
|
|
|
259
319
|
title: (isRecord(thread.metadata) ? thread.metadata.title : undefined) ?? "",
|
|
260
320
|
created: thread.created_at,
|
|
261
321
|
id: step.id,
|
|
262
|
-
step: step.step,
|
|
322
|
+
step: step.step ?? null,
|
|
263
323
|
});
|
|
264
324
|
}
|
|
265
325
|
}
|
|
@@ -540,7 +600,7 @@ function resolveFinalizeOptions(args) {
|
|
|
540
600
|
return { values, error: { code: 2, stdout: "", stderr: `Unknown arg: ${key}\n` } };
|
|
541
601
|
if (index + 1 >= args.length) {
|
|
542
602
|
const shellAsset = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../assets/scaffold", ASSETS.finalize);
|
|
543
|
-
return { values, error: { code: 1, stdout: "", stderr: `${shellAsset}: line
|
|
603
|
+
return { values, error: { code: 1, stdout: "", stderr: `${shellAsset}: line 93: $2: unbound variable\n` } };
|
|
544
604
|
}
|
|
545
605
|
values[target] = args[index + 1] ?? "";
|
|
546
606
|
index += 2;
|
|
@@ -605,10 +665,10 @@ export function runHandoffFinalize(args, context) {
|
|
|
605
665
|
const file = path.isAbsolute(values.filesTouchedFile) ? values.filesTouchedFile : path.join(root, values.filesTouchedFile);
|
|
606
666
|
if (!fs.existsSync(file))
|
|
607
667
|
return { code: 3, stdout: "", stderr: `handoff-finalize: --files-touched-json-file not found: ${values.filesTouchedFile}\n` };
|
|
608
|
-
touchedValue =
|
|
668
|
+
touchedValue = parseHandoffJson(fs.readFileSync(file, "utf8"), "--files-touched-json-file");
|
|
609
669
|
}
|
|
610
670
|
else {
|
|
611
|
-
touchedValue =
|
|
671
|
+
touchedValue = parseHandoffJson(values.filesTouched, "--files-touched-json");
|
|
612
672
|
}
|
|
613
673
|
const touchedFile = values.filesTouchedFile
|
|
614
674
|
? (path.isAbsolute(values.filesTouchedFile) ? values.filesTouchedFile : path.join(root, values.filesTouchedFile))
|
|
@@ -636,14 +696,21 @@ export function runHandoffFinalize(args, context) {
|
|
|
636
696
|
}
|
|
637
697
|
safePaths.push(relative);
|
|
638
698
|
}
|
|
639
|
-
const
|
|
699
|
+
const sessionCompany = boundSessionCompany(root);
|
|
700
|
+
const companyDirectory = sessionCompany
|
|
701
|
+
? fs.statSync(path.join(root, "companies", sessionCompany), { throwIfNoEntry: false })
|
|
702
|
+
: undefined;
|
|
703
|
+
const companies = [...new Set([
|
|
704
|
+
...touchedCompanyPaths(safePaths),
|
|
705
|
+
...(companyDirectory?.isDirectory() ? [sessionCompany] : []),
|
|
706
|
+
])].sort();
|
|
640
707
|
const dirty = probe.hasGit && printGitStatus(root) !== "";
|
|
641
708
|
const branchResult = probe.hasGit ? git(["symbolic-ref", "--short", "HEAD"], root) : { code: 1, stdout: "", stderr: "" };
|
|
642
709
|
const commitResult = probe.hasGit ? git(["rev-parse", "--short", "HEAD"], root) : { code: 1, stdout: "", stderr: "" };
|
|
643
710
|
const branch = branchResult.code === 0 ? branchResult.stdout.trim() : "DETACHED";
|
|
644
711
|
const startCommit = commitResult.code === 0 ? commitResult.stdout.trim() : "unknown";
|
|
645
712
|
const status = statusSummary(root, touchedFile);
|
|
646
|
-
const stepsInput =
|
|
713
|
+
const stepsInput = parseHandoffJson(values.nextSteps, "--next-steps-json");
|
|
647
714
|
const nextSteps = Array.isArray(stepsInput) ? stepsInput.map((raw, index) => {
|
|
648
715
|
const step = typeof raw === "string" ? { step: raw } : isRecord(raw) ? { ...raw } : {};
|
|
649
716
|
if (!jqTruthy(step.id))
|
|
@@ -666,8 +733,8 @@ export function runHandoffFinalize(args, context) {
|
|
|
666
733
|
conversation_summary: values.summary,
|
|
667
734
|
next_steps: nextSteps,
|
|
668
735
|
files_touched: touchedValue,
|
|
669
|
-
learnings:
|
|
670
|
-
metadata: { title: values.title, tags:
|
|
736
|
+
learnings: parseHandoffJson(values.learnings, "--learnings-json"),
|
|
737
|
+
metadata: { title: values.title, tags: parseHandoffJson(values.tags, "--tags-json"), company: companies },
|
|
671
738
|
};
|
|
672
739
|
fs.mkdirSync(threadsDir, { recursive: true });
|
|
673
740
|
fs.writeFileSync(path.join(root, threadPath), prettyJson(thread));
|
|
@@ -823,6 +890,9 @@ export function runHandoffFinalize(args, context) {
|
|
|
823
890
|
return { code: 0, stdout, stderr: stderrParts.join("") };
|
|
824
891
|
}
|
|
825
892
|
catch (error) {
|
|
893
|
+
if (error instanceof HandoffJsonOptionError) {
|
|
894
|
+
return { code: 2, stdout: "", stderr: `handoff-finalize: invalid JSON for ${error.flag}: ${error.message}\n` };
|
|
895
|
+
}
|
|
826
896
|
const message = error instanceof Error ? error.message : String(error);
|
|
827
897
|
return { code: 1, stdout: "", stderr: `${message}\n` };
|
|
828
898
|
}
|
|
@@ -891,7 +961,7 @@ function readThreadCompanyNames(file) {
|
|
|
891
961
|
if (!jqTruthy(value))
|
|
892
962
|
return [];
|
|
893
963
|
const values = Array.isArray(value) ? value : [value];
|
|
894
|
-
return values.
|
|
964
|
+
return [...new Set(values.filter((item) => typeof item === "string" && /^[a-z][a-z0-9_-]*$/.test(item)))].sort();
|
|
895
965
|
}
|
|
896
966
|
catch {
|
|
897
967
|
return [];
|
|
@@ -927,17 +997,7 @@ function workMeshSessionEnd(root) {
|
|
|
927
997
|
return "work-mesh-session-end: skipped (hook disabled)";
|
|
928
998
|
if (/(^|,)(work-mesh|work-mesh-live|\*)(,|$)/.test(`,${process.env.HQ_DISABLED_HOOKS ?? ""},`))
|
|
929
999
|
return "work-mesh-session-end: skipped (hook disabled)";
|
|
930
|
-
|
|
931
|
-
const current = path.join(root, "workspace/sessions/.current");
|
|
932
|
-
if (fs.existsSync(current)) {
|
|
933
|
-
try {
|
|
934
|
-
sessionId = fs.readFileSync(current, "utf8").replace(/\s/g, "");
|
|
935
|
-
}
|
|
936
|
-
catch {
|
|
937
|
-
sessionId = "";
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
sessionId = sessionId || process.env.CLAUDE_SESSION_ID || process.env.CODEX_SESSION_ID || process.env.CODEX_THREAD_ID || "";
|
|
1000
|
+
const sessionId = resolveSessionId(root);
|
|
941
1001
|
if (!sessionId)
|
|
942
1002
|
return undefined;
|
|
943
1003
|
let harness = process.env.HQ_HARNESS || process.env.HQ_WORK_MESH_HARNESS || process.env.HQ_CHECKPOINT_RUNTIME || "claude-code";
|
|
@@ -1112,11 +1172,80 @@ export function runHandoffPostWorker(args, context) {
|
|
|
1112
1172
|
else
|
|
1113
1173
|
log("document-release: skipped (no thread path)");
|
|
1114
1174
|
if (threadFile && fs.existsSync(threadFile) && commandAvailable("hq")) {
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1175
|
+
let syncCompanies = readThreadCompanyNames(threadFile);
|
|
1176
|
+
const sessionCompany = boundSessionCompany(root);
|
|
1177
|
+
if (sessionCompany) {
|
|
1178
|
+
const updatedCompanies = [...new Set([...syncCompanies, sessionCompany])].sort();
|
|
1179
|
+
const companyListChanged = JSON.stringify(syncCompanies) !== JSON.stringify(updatedCompanies);
|
|
1180
|
+
syncCompanies = updatedCompanies;
|
|
1181
|
+
if (companyListChanged) {
|
|
1182
|
+
try {
|
|
1183
|
+
const thread = JSON.parse(fs.readFileSync(threadFile, "utf8"));
|
|
1184
|
+
if (!isRecord(thread))
|
|
1185
|
+
throw new Error("thread is not an object");
|
|
1186
|
+
let metadata;
|
|
1187
|
+
if (thread.metadata === null || thread.metadata === undefined)
|
|
1188
|
+
metadata = {};
|
|
1189
|
+
else if (isRecord(thread.metadata))
|
|
1190
|
+
metadata = thread.metadata;
|
|
1191
|
+
else
|
|
1192
|
+
throw new Error("thread metadata is not an object");
|
|
1193
|
+
metadata.company = syncCompanies;
|
|
1194
|
+
thread.metadata = metadata;
|
|
1195
|
+
const temporaryThread = path.join(path.dirname(threadFile), `.handoff-post-${process.pid}-${Math.random().toString(36).slice(2)}`);
|
|
1196
|
+
let temporaryFd;
|
|
1197
|
+
try {
|
|
1198
|
+
temporaryFd = fs.openSync(temporaryThread, "wx", 0o600);
|
|
1199
|
+
fs.writeFileSync(temporaryFd, `${compactJson(thread).trimEnd()}\n`);
|
|
1200
|
+
fs.closeSync(temporaryFd);
|
|
1201
|
+
temporaryFd = undefined;
|
|
1202
|
+
fs.chmodSync(temporaryThread, 0o600);
|
|
1203
|
+
fs.renameSync(temporaryThread, threadFile);
|
|
1204
|
+
}
|
|
1205
|
+
catch (error) {
|
|
1206
|
+
if (temporaryFd !== undefined) {
|
|
1207
|
+
try {
|
|
1208
|
+
fs.closeSync(temporaryFd);
|
|
1209
|
+
}
|
|
1210
|
+
catch { /* temporary output cleanup remains best effort */ }
|
|
1211
|
+
}
|
|
1212
|
+
try {
|
|
1213
|
+
fs.rmSync(temporaryThread, { force: true });
|
|
1214
|
+
}
|
|
1215
|
+
catch { /* jq/mv cleanup is fail-soft */ }
|
|
1216
|
+
throw error;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
catch {
|
|
1220
|
+
log("workspace-sync: skipped (could not add bound company to handoff thread)");
|
|
1221
|
+
syncCompanies = [];
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
let threadId = "";
|
|
1226
|
+
let threadMirrorable = false;
|
|
1227
|
+
try {
|
|
1228
|
+
const thread = JSON.parse(fs.readFileSync(threadFile, "utf8"));
|
|
1229
|
+
threadId = isRecord(thread) && typeof thread.thread_id === "string" ? thread.thread_id : "";
|
|
1230
|
+
const relative = path.relative(root, threadFile);
|
|
1231
|
+
threadMirrorable = /^workspace[\\/]threads[\\/]T-[A-Za-z0-9._-]+\.json$/.test(relative)
|
|
1232
|
+
&& /^T-[A-Za-z0-9._-]+$/.test(threadId);
|
|
1233
|
+
}
|
|
1234
|
+
catch { /* shell helper treats an invalid thread as non-mirrorable */ }
|
|
1235
|
+
if (threadMirrorable && syncCompanies.length > 0) {
|
|
1236
|
+
const mirrorHook = path.join(root, ".claude/hooks/mirror-thread-to-company.sh");
|
|
1237
|
+
if (fs.existsSync(mirrorHook))
|
|
1238
|
+
mirrorThread(root, threadFile);
|
|
1239
|
+
else
|
|
1240
|
+
log("workspace-sync: mirror hook unavailable for handoff thread");
|
|
1241
|
+
}
|
|
1242
|
+
for (const company of syncCompanies) {
|
|
1118
1243
|
if (!fs.existsSync(path.join(root, "companies", company, "workspace")))
|
|
1119
1244
|
continue;
|
|
1245
|
+
if (!threadMirrorable || !fs.existsSync(path.join(root, "companies", company, "workspace/sessions", `${threadId}.json`))) {
|
|
1246
|
+
log(`workspace-sync: skipped (thread mirror missing for ${company})`);
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1120
1249
|
const pushed = spawnSync("hq", ["sync", "push", "--company", company, `companies/${company}/workspace`], {
|
|
1121
1250
|
cwd: root,
|
|
1122
1251
|
env: process.env,
|
|
@@ -3,6 +3,21 @@ import { Command } from "commander";
|
|
|
3
3
|
export declare function detectHostTimezone(override: string, resolveLocaltime?: (file: string) => string, readTimedatectl?: () => string): string;
|
|
4
4
|
export declare function runScheduleParse(argv: string[]): number;
|
|
5
5
|
export declare function validateJobs(args: string[]): number;
|
|
6
|
+
type ReconcileCommandResult = {
|
|
7
|
+
status: number;
|
|
8
|
+
stdout: string;
|
|
9
|
+
stderr: string;
|
|
10
|
+
};
|
|
11
|
+
export type ReconcileRunner = (command: string, args: string[]) => ReconcileCommandResult;
|
|
12
|
+
export type ReconcileOptions = {
|
|
13
|
+
schedulerRunner?: ReconcileRunner;
|
|
14
|
+
commandRunner?: ReconcileRunner;
|
|
15
|
+
identityFileReader?: (file: string) => string | undefined;
|
|
16
|
+
fetcher?: typeof fetch;
|
|
17
|
+
now?: () => Date;
|
|
18
|
+
};
|
|
19
|
+
export declare function runReconcile(argv: string[], rootArg?: string, options?: ReconcileOptions): Promise<number>;
|
|
6
20
|
export declare function registerCoreJobsNativeCommand(core: Command): void;
|
|
7
21
|
export declare function rawArgumentsForCommand(command: Command, fallback: string[], raw?: string[]): string[];
|
|
22
|
+
export {};
|
|
8
23
|
//# sourceMappingURL=core-jobs-native.d.ts.map
|