@useorgx/wizard 0.1.55 → 0.1.57
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/README.md +7 -0
- package/dist/cli.js +2328 -387
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
|
|
5
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
5
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="8eca99e4-958f-5704-a1cb-bd96bbd10293")}catch(e){}}();
|
|
6
6
|
import * as clack from "@clack/prompts";
|
|
7
7
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
8
|
-
import { readFileSync as
|
|
8
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
9
9
|
import { hostname } from "os";
|
|
10
|
-
import { resolve as
|
|
10
|
+
import { resolve as resolve4 } from "path";
|
|
11
11
|
import { Command } from "commander";
|
|
12
12
|
import pc3 from "picocolors";
|
|
13
13
|
|
|
@@ -187,6 +187,9 @@ import {
|
|
|
187
187
|
writeFileSync
|
|
188
188
|
} from "fs";
|
|
189
189
|
import { dirname } from "path";
|
|
190
|
+
function fileExists(path) {
|
|
191
|
+
return existsSync(path);
|
|
192
|
+
}
|
|
190
193
|
function readTextIfExists(path) {
|
|
191
194
|
if (!existsSync(path)) return null;
|
|
192
195
|
try {
|
|
@@ -831,7 +834,7 @@ function parsePairingPollResult(value) {
|
|
|
831
834
|
};
|
|
832
835
|
}
|
|
833
836
|
function sleep(ms) {
|
|
834
|
-
return new Promise((
|
|
837
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
835
838
|
}
|
|
836
839
|
async function startBrowserPairing(options, fetchImpl) {
|
|
837
840
|
const data = await fetchJson({
|
|
@@ -944,12 +947,12 @@ h1{font-size:1.5rem;color:#b91c1c}p{color:#555;margin-top:.5rem}</style>
|
|
|
944
947
|
<p>Return to your terminal and try again.</p>
|
|
945
948
|
</div></body></html>`;
|
|
946
949
|
function tryListen(port, hostname2) {
|
|
947
|
-
return new Promise((
|
|
950
|
+
return new Promise((resolve5, reject) => {
|
|
948
951
|
const server = createServer();
|
|
949
952
|
server.once("error", reject);
|
|
950
953
|
server.listen(port, hostname2, () => {
|
|
951
954
|
server.removeListener("error", reject);
|
|
952
|
-
|
|
955
|
+
resolve5(server);
|
|
953
956
|
});
|
|
954
957
|
});
|
|
955
958
|
}
|
|
@@ -978,7 +981,7 @@ async function startLocalAuthServer(options) {
|
|
|
978
981
|
const successHtml = options.successHtml ?? DEFAULT_SUCCESS_HTML;
|
|
979
982
|
const errorHtml = options.errorHtml ?? DEFAULT_ERROR_HTML;
|
|
980
983
|
const { server, port } = await bindServer(options.preferredPort, hostname2);
|
|
981
|
-
const result = new Promise((
|
|
984
|
+
const result = new Promise((resolve5, reject) => {
|
|
982
985
|
const timer = setTimeout(() => {
|
|
983
986
|
server.close();
|
|
984
987
|
reject(new Error("Timed out waiting for browser authorization."));
|
|
@@ -1021,7 +1024,7 @@ async function startLocalAuthServer(options) {
|
|
|
1021
1024
|
res.writeHead(200, { "Content-Type": "text/html" }).end(successHtml);
|
|
1022
1025
|
clearTimeout(timer);
|
|
1023
1026
|
server.close();
|
|
1024
|
-
|
|
1027
|
+
resolve5({ code, state });
|
|
1025
1028
|
});
|
|
1026
1029
|
});
|
|
1027
1030
|
return { port, result };
|
|
@@ -3604,8 +3607,8 @@ function encodeRepoPath2(value) {
|
|
|
3604
3607
|
return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
|
|
3605
3608
|
}
|
|
3606
3609
|
function isLikelyRepoFilePath(path) {
|
|
3607
|
-
const
|
|
3608
|
-
return
|
|
3610
|
+
const basename6 = path.split("/").pop() ?? path;
|
|
3611
|
+
return basename6.includes(".") && !/^\.[^./]+$/.test(basename6);
|
|
3609
3612
|
}
|
|
3610
3613
|
function buildContentsUrl2(spec, path) {
|
|
3611
3614
|
const encodedPath = encodeRepoPath2(path);
|
|
@@ -3881,7 +3884,7 @@ function formatCommandFailure(command, args, result) {
|
|
|
3881
3884
|
return `${command} ${args.join(" ")} failed${result.exitCode >= 0 ? ` with exit code ${result.exitCode}` : ""}${detail ? `: ${detail}` : "."}`;
|
|
3882
3885
|
}
|
|
3883
3886
|
async function defaultCommandRunner(command, args) {
|
|
3884
|
-
return await new Promise((
|
|
3887
|
+
return await new Promise((resolve5) => {
|
|
3885
3888
|
const child = spawn(command, [...args], {
|
|
3886
3889
|
env: process.env,
|
|
3887
3890
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -3895,16 +3898,16 @@ async function defaultCommandRunner(command, args) {
|
|
|
3895
3898
|
stderr += chunk.toString();
|
|
3896
3899
|
});
|
|
3897
3900
|
child.on("error", (error) => {
|
|
3898
|
-
const
|
|
3899
|
-
|
|
3901
|
+
const errorCode2 = typeof error === "object" && error && "code" in error ? String(error.code) : void 0;
|
|
3902
|
+
resolve5({
|
|
3900
3903
|
exitCode: -1,
|
|
3901
3904
|
stdout,
|
|
3902
3905
|
stderr,
|
|
3903
|
-
...
|
|
3906
|
+
...errorCode2 ? { errorCode: errorCode2 } : {}
|
|
3904
3907
|
});
|
|
3905
3908
|
});
|
|
3906
3909
|
child.on("close", (code) => {
|
|
3907
|
-
|
|
3910
|
+
resolve5({
|
|
3908
3911
|
exitCode: code ?? -1,
|
|
3909
3912
|
stdout,
|
|
3910
3913
|
stderr
|
|
@@ -6211,7 +6214,7 @@ function initializeWizardSentry() {
|
|
|
6211
6214
|
Sentry.init({
|
|
6212
6215
|
dsn,
|
|
6213
6216
|
environment: process.env.ORGX_SENTRY_ENVIRONMENT || "production",
|
|
6214
|
-
release: "useorgx-wizard@0.1.
|
|
6217
|
+
release: "useorgx-wizard@0.1.57",
|
|
6215
6218
|
tracesSampleRate: sampleRate(process.env.ORGX_SENTRY_TRACES_SAMPLE_RATE),
|
|
6216
6219
|
enableLogs: true,
|
|
6217
6220
|
sendDefaultPii: false,
|
|
@@ -8592,11 +8595,138 @@ Rollback: ${plan.recommended_follow_up.rollback}`,
|
|
|
8592
8595
|
);
|
|
8593
8596
|
}
|
|
8594
8597
|
|
|
8598
|
+
// src/lib/operating-map.ts
|
|
8599
|
+
import { createHash as createHash6 } from "crypto";
|
|
8600
|
+
function parseResponseBody6(text2) {
|
|
8601
|
+
if (!text2) return null;
|
|
8602
|
+
try {
|
|
8603
|
+
return JSON.parse(text2);
|
|
8604
|
+
} catch {
|
|
8605
|
+
return text2;
|
|
8606
|
+
}
|
|
8607
|
+
}
|
|
8608
|
+
function formatHttpError5(status, body) {
|
|
8609
|
+
if (typeof body === "string" && body.trim()) return `HTTP ${status}: ${body}`;
|
|
8610
|
+
if (isRecord(body) && isRecord(body.error)) {
|
|
8611
|
+
const code = typeof body.error.code === "string" ? body.error.code : "api_error";
|
|
8612
|
+
const message = typeof body.error.message === "string" ? body.error.message : `HTTP ${status}`;
|
|
8613
|
+
return `HTTP ${status} ${code}: ${message}`;
|
|
8614
|
+
}
|
|
8615
|
+
return `HTTP ${status}`;
|
|
8616
|
+
}
|
|
8617
|
+
function extractData(payload) {
|
|
8618
|
+
return isRecord(payload) && "data" in payload ? payload.data : payload;
|
|
8619
|
+
}
|
|
8620
|
+
function parseDiscoveryResult(payload) {
|
|
8621
|
+
const data = extractData(payload);
|
|
8622
|
+
if (!isRecord(data) || !isRecord(data.run) || !Array.isArray(data.processCards)) {
|
|
8623
|
+
throw new Error("OrgX returned an incomplete operating-map discovery payload.");
|
|
8624
|
+
}
|
|
8625
|
+
const run = data.run;
|
|
8626
|
+
if (typeof run.id !== "string" || typeof run.mode !== "string" || typeof run.status !== "string") {
|
|
8627
|
+
throw new Error("OrgX returned an invalid operating-map discovery run.");
|
|
8628
|
+
}
|
|
8629
|
+
return {
|
|
8630
|
+
run: {
|
|
8631
|
+
id: run.id,
|
|
8632
|
+
mode: run.mode,
|
|
8633
|
+
status: run.status,
|
|
8634
|
+
query: typeof run.query === "string" ? run.query : null,
|
|
8635
|
+
observationCount: typeof run.observationCount === "number" ? run.observationCount : 0,
|
|
8636
|
+
candidateProcessCardCount: typeof run.candidateProcessCardCount === "number" ? run.candidateProcessCardCount : data.processCards.length,
|
|
8637
|
+
citationCount: typeof run.citationCount === "number" ? run.citationCount : 0,
|
|
8638
|
+
sourceHealth: Array.isArray(run.sourceHealth) ? run.sourceHealth : [],
|
|
8639
|
+
limitations: Array.isArray(run.limitations) ? run.limitations.filter((item) => typeof item === "string") : []
|
|
8640
|
+
},
|
|
8641
|
+
observations: Array.isArray(data.observations) ? data.observations.filter(isRecord) : [],
|
|
8642
|
+
processCards: data.processCards.filter(isRecord).map((card) => {
|
|
8643
|
+
const ref = isRecord(card.processCandidateRef) ? card.processCandidateRef : {};
|
|
8644
|
+
return {
|
|
8645
|
+
processCandidateRef: {
|
|
8646
|
+
id: typeof ref.id === "string" ? ref.id : "",
|
|
8647
|
+
workspaceId: typeof ref.workspaceId === "string" ? ref.workspaceId : ""
|
|
8648
|
+
},
|
|
8649
|
+
displayName: typeof card.displayName === "string" ? card.displayName : "Unnamed workflow",
|
|
8650
|
+
confidence: typeof card.confidence === "number" ? card.confidence : 0,
|
|
8651
|
+
nextConfirmationQuestion: typeof card.nextConfirmationQuestion === "string" ? card.nextConfirmationQuestion : null,
|
|
8652
|
+
candidateTrigger: isRecord(card.candidateTrigger) ? card.candidateTrigger : {},
|
|
8653
|
+
handoffDelays: Array.isArray(card.handoffDelays) ? card.handoffDelays.filter(isRecord).map((handoff) => ({
|
|
8654
|
+
from: typeof handoff.from === "string" ? handoff.from : "unknown",
|
|
8655
|
+
to: typeof handoff.to === "string" ? handoff.to : "unknown",
|
|
8656
|
+
delayMinutes: typeof handoff.delayMinutes === "number" ? handoff.delayMinutes : null
|
|
8657
|
+
})) : [],
|
|
8658
|
+
riskFlags: Array.isArray(card.riskFlags) ? card.riskFlags.filter((item) => typeof item === "string") : []
|
|
8659
|
+
};
|
|
8660
|
+
}),
|
|
8661
|
+
confirmedProcessRefs: Array.isArray(data.confirmedProcessRefs) ? data.confirmedProcessRefs.filter(isRecord).flatMap(
|
|
8662
|
+
(ref) => typeof ref.id === "string" && typeof ref.workspaceId === "string" ? [{ id: ref.id, workspaceId: ref.workspaceId }] : []
|
|
8663
|
+
) : []
|
|
8664
|
+
};
|
|
8665
|
+
}
|
|
8666
|
+
async function requireOrgxAuth4(options = {}) {
|
|
8667
|
+
const auth = await resolveOrgxAuth(options);
|
|
8668
|
+
if (!auth) {
|
|
8669
|
+
throw new Error("No OrgX API key configured. Run `wizard auth login` or set ORGX_API_KEY first.");
|
|
8670
|
+
}
|
|
8671
|
+
return auth;
|
|
8672
|
+
}
|
|
8673
|
+
function defaultOperatingMapIdempotencyKey(input) {
|
|
8674
|
+
const digest = createHash6("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 32);
|
|
8675
|
+
return `wizard:operating-map:${input.workspaceId}:${digest}`;
|
|
8676
|
+
}
|
|
8677
|
+
async function startOperatingMapDiscovery(input, options = {}) {
|
|
8678
|
+
const auth = await requireOrgxAuth4(options);
|
|
8679
|
+
const response = await fetchWithRetry(buildOrgxApiUrl("/v1/discovery-runs", auth.baseUrl), {
|
|
8680
|
+
method: "POST",
|
|
8681
|
+
headers: {
|
|
8682
|
+
Authorization: `Bearer ${auth.apiKey}`,
|
|
8683
|
+
"Content-Type": "application/json",
|
|
8684
|
+
"Idempotency-Key": input.idempotencyKey
|
|
8685
|
+
},
|
|
8686
|
+
body: JSON.stringify({
|
|
8687
|
+
workspace_id: input.workspaceId,
|
|
8688
|
+
mode: input.mode ?? "bounded_sync",
|
|
8689
|
+
query: input.query ?? null,
|
|
8690
|
+
source_kinds: input.sourceKinds ?? []
|
|
8691
|
+
})
|
|
8692
|
+
});
|
|
8693
|
+
const body = parseResponseBody6(await response.text());
|
|
8694
|
+
if (!response.ok) {
|
|
8695
|
+
throw new Error(`Unable to start the operating-map discovery run. ${formatHttpError5(response.status, body)}`);
|
|
8696
|
+
}
|
|
8697
|
+
const result = parseDiscoveryResult(body);
|
|
8698
|
+
const meta = isRecord(body) && isRecord(body.meta) ? body.meta : {};
|
|
8699
|
+
return { result, duplicate: meta.duplicate === true };
|
|
8700
|
+
}
|
|
8701
|
+
async function proposeOperatingProcessFromMap(input, options = {}) {
|
|
8702
|
+
const auth = await requireOrgxAuth4(options);
|
|
8703
|
+
const response = await fetchWithRetry(
|
|
8704
|
+
buildOrgxApiUrl(`/v1/discovery-runs/${encodeURIComponent(input.discoveryRunId)}/propose`, auth.baseUrl),
|
|
8705
|
+
{
|
|
8706
|
+
method: "POST",
|
|
8707
|
+
headers: {
|
|
8708
|
+
Authorization: `Bearer ${auth.apiKey}`,
|
|
8709
|
+
"Content-Type": "application/json",
|
|
8710
|
+
"Idempotency-Key": input.idempotencyKey
|
|
8711
|
+
},
|
|
8712
|
+
body: JSON.stringify({
|
|
8713
|
+
workspace_id: input.workspaceId,
|
|
8714
|
+
process_candidate_id: input.processCandidateId
|
|
8715
|
+
})
|
|
8716
|
+
}
|
|
8717
|
+
);
|
|
8718
|
+
const body = parseResponseBody6(await response.text());
|
|
8719
|
+
if (!response.ok) {
|
|
8720
|
+
throw new Error(`Unable to propose the OperatingProcess. ${formatHttpError5(response.status, body)}`);
|
|
8721
|
+
}
|
|
8722
|
+
return extractData(body);
|
|
8723
|
+
}
|
|
8724
|
+
|
|
8595
8725
|
// src/lib/work-graph.ts
|
|
8596
|
-
import { createHash as
|
|
8726
|
+
import { createHash as createHash8 } from "crypto";
|
|
8597
8727
|
|
|
8598
8728
|
// src/lib/work-graph-investigation.ts
|
|
8599
|
-
import { createHash as
|
|
8729
|
+
import { createHash as createHash7 } from "crypto";
|
|
8600
8730
|
var WORK_GRAPH_INVESTIGATION_SCHEMA_VERSION = "2.0.0";
|
|
8601
8731
|
var WORK_GRAPH_INVESTIGATION_CLIENTS = [
|
|
8602
8732
|
"claude_code",
|
|
@@ -8729,7 +8859,7 @@ var CAPABILITY_CEILINGS = {
|
|
|
8729
8859
|
}
|
|
8730
8860
|
};
|
|
8731
8861
|
function hash3(value, length = 16) {
|
|
8732
|
-
return
|
|
8862
|
+
return createHash7("sha256").update(JSON.stringify(value)).digest("hex").slice(0, length);
|
|
8733
8863
|
}
|
|
8734
8864
|
function clamp(value, min = 0, max = 1) {
|
|
8735
8865
|
return Math.max(min, Math.min(max, value));
|
|
@@ -9831,7 +9961,7 @@ function clampScore2(value) {
|
|
|
9831
9961
|
return Math.max(0, Math.min(100, Math.round(value)));
|
|
9832
9962
|
}
|
|
9833
9963
|
function hashJson(value) {
|
|
9834
|
-
return
|
|
9964
|
+
return createHash8("sha256").update(JSON.stringify(value)).digest("hex");
|
|
9835
9965
|
}
|
|
9836
9966
|
function normalizeFingerprintText(value) {
|
|
9837
9967
|
return value.toLowerCase().replace(/https?:\/\/\S+/g, "url").replace(/[0-9a-f]{12,}/g, "hash").replace(/\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/g, "uuid").replace(/\s+/g, " ").trim().slice(0, 600);
|
|
@@ -13043,14 +13173,14 @@ function renderAqAgentBrief(report, links = {}) {
|
|
|
13043
13173
|
}
|
|
13044
13174
|
|
|
13045
13175
|
// src/lib/work-graph-publish.ts
|
|
13046
|
-
import { createHash as
|
|
13176
|
+
import { createHash as createHash9, randomUUID as randomUUID2 } from "crypto";
|
|
13047
13177
|
import { gzipSync } from "zlib";
|
|
13048
13178
|
var WORK_GRAPH_REPORT_GZIP_THRESHOLD_BYTES = 4e6;
|
|
13049
13179
|
var WORK_GRAPH_REPORT_CHUNK_UPLOAD_THRESHOLD_BYTES = 5e5;
|
|
13050
13180
|
var WORK_GRAPH_REPORT_CHUNK_CHARS = 3e4;
|
|
13051
13181
|
var WORK_GRAPH_REPORT_CHUNK_UPLOAD_TIMEOUT_MS = 3e5;
|
|
13052
13182
|
function hashText(value) {
|
|
13053
|
-
return
|
|
13183
|
+
return createHash9("sha256").update(value).digest("hex");
|
|
13054
13184
|
}
|
|
13055
13185
|
function buildWorkGraphReportPostPayload(report, options = {}) {
|
|
13056
13186
|
return {
|
|
@@ -13257,7 +13387,7 @@ async function publishWorkGraphEvents(fingerprint, patch, options = {}) {
|
|
|
13257
13387
|
}
|
|
13258
13388
|
|
|
13259
13389
|
// src/lib/work-graph-hook-events.ts
|
|
13260
|
-
import { createHash as
|
|
13390
|
+
import { createHash as createHash10 } from "crypto";
|
|
13261
13391
|
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
13262
13392
|
var SOURCE_CLIENTS = [
|
|
13263
13393
|
"codex",
|
|
@@ -13292,7 +13422,7 @@ function asStringArray(value) {
|
|
|
13292
13422
|
return value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
13293
13423
|
}
|
|
13294
13424
|
function stableHash(value) {
|
|
13295
|
-
return
|
|
13425
|
+
return createHash10("sha256").update(value).digest("hex").slice(0, 20);
|
|
13296
13426
|
}
|
|
13297
13427
|
function normalizeSourceClient2(value) {
|
|
13298
13428
|
const raw = asString2(value)?.toLowerCase();
|
|
@@ -13496,213 +13626,992 @@ function buildWorkGraphHookReplayPatch(readResult) {
|
|
|
13496
13626
|
}
|
|
13497
13627
|
|
|
13498
13628
|
// src/lib/runtime-hooks.ts
|
|
13499
|
-
import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
13629
|
+
import { copyFileSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync6 } from "fs";
|
|
13500
13630
|
import { homedir as homedir3 } from "os";
|
|
13501
|
-
import { dirname as dirname5, join as join8 } from "path";
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
var
|
|
13505
|
-
var
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
codexConfigPath: options.codexConfigPath ?? CODEX_CONFIG_PATH ?? join8(CODEX_DIR, "config.toml"),
|
|
13511
|
-
codexHooksPath: options.codexHooksPath ?? join8(CODEX_DIR, "hooks.json"),
|
|
13512
|
-
hookScriptPath: options.hookScriptPath ?? join8(hookDir, HOOK_MARKER),
|
|
13513
|
-
emitHookScriptPath: options.emitHookScriptPath ?? join8(hookDir, EMIT_HOOK_MARKER),
|
|
13514
|
-
outboxPath: options.outboxPath ?? join8(hookDir, "events.jsonl")
|
|
13515
|
-
};
|
|
13516
|
-
}
|
|
13517
|
-
function countJsonlLines(path) {
|
|
13518
|
-
const raw = readTextIfExists(path);
|
|
13519
|
-
if (!raw) return 0;
|
|
13520
|
-
return raw.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
|
|
13521
|
-
}
|
|
13522
|
-
function backupPath(path, now) {
|
|
13523
|
-
const timestamp = now.toISOString().replace(/[:.]/g, "-");
|
|
13524
|
-
return `${path}.bak.${timestamp}`;
|
|
13525
|
-
}
|
|
13526
|
-
function backupExisting(path, now) {
|
|
13527
|
-
if (!existsSync9(path)) return null;
|
|
13528
|
-
const backup = backupPath(path, now);
|
|
13529
|
-
copyFileSync(path, backup);
|
|
13530
|
-
return backup;
|
|
13531
|
-
}
|
|
13532
|
-
function hasOrgxHook(raw) {
|
|
13533
|
-
return Boolean(raw?.includes(HOOK_MARKER));
|
|
13534
|
-
}
|
|
13535
|
-
function codexHooksEnabled(raw) {
|
|
13536
|
-
return Boolean(raw && /^\s*codex_hooks\s*=\s*true\s*$/m.test(raw));
|
|
13537
|
-
}
|
|
13538
|
-
function codexHasNotify(raw) {
|
|
13539
|
-
return Boolean(raw && /^\s*notify\s*=/m.test(raw));
|
|
13540
|
-
}
|
|
13541
|
-
function buildRuntimeHookScriptContent() {
|
|
13631
|
+
import { dirname as dirname5, join as join8, resolve as resolve2 } from "path";
|
|
13632
|
+
|
|
13633
|
+
// src/lib/session-summary-hook.ts
|
|
13634
|
+
var SESSION_SUMMARY_HOOK_MARKER = "orgx-session-summary.mjs";
|
|
13635
|
+
var SESSION_SUMMARY_ENDPOINT_PATH = "/api/v1/sessions/summary";
|
|
13636
|
+
var SESSION_SUMMARY_FALLBACK_ENDPOINT_PATH = "/api/v1/work-graph/reports";
|
|
13637
|
+
var SESSION_SUMMARY_SCHEMA_VERSION = "2026-08-07";
|
|
13638
|
+
var HOOK_OUTBOX_MAX_BYTES = 8 * 1024 * 1024;
|
|
13639
|
+
function buildSessionSummaryHookScriptContent() {
|
|
13542
13640
|
return `#!/usr/bin/env node
|
|
13543
|
-
|
|
13544
|
-
|
|
13641
|
+
// OrgX lean session-summary hook. Generated by @useorgx/wizard.
|
|
13642
|
+
//
|
|
13643
|
+
// Hot path (PostToolUse and friends): ONE small read-modify-write. No network.
|
|
13644
|
+
// Terminal (Stop / SessionEnd): ONE compact cumulative summary queue write.
|
|
13645
|
+
// A separate orgx-wizard hooks flush process owns all network delivery.
|
|
13646
|
+
//
|
|
13647
|
+
// NOTE ON Stop SEMANTICS: in Claude Code, Stop fires at every assistant turn
|
|
13648
|
+
// boundary, not only once at session end. Stop therefore writes a cumulative
|
|
13649
|
+
// snapshot and retains state. The ingest endpoint merges repeated snapshots by
|
|
13650
|
+
// session_id; SessionEnd, when the client sends it, queues the final snapshot
|
|
13651
|
+
// and clears local state.
|
|
13652
|
+
|
|
13653
|
+
import { spawn } from "node:child_process";
|
|
13654
|
+
import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, writeFileSync, writeSync, rmSync, renameSync } from "node:fs";
|
|
13545
13655
|
import { homedir } from "node:os";
|
|
13656
|
+
import { join, dirname, basename } from "node:path";
|
|
13546
13657
|
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13658
|
+
var SCHEMA_VERSION = "${SESSION_SUMMARY_SCHEMA_VERSION}";
|
|
13659
|
+
|
|
13660
|
+
export function parseArgs(argv) {
|
|
13661
|
+
var args = {};
|
|
13662
|
+
for (var i = 0; i < argv.length; i++) {
|
|
13663
|
+
var arg = argv[i];
|
|
13664
|
+
if (arg.indexOf("--") !== 0) continue;
|
|
13665
|
+
var eq = arg.indexOf("=");
|
|
13666
|
+
if (eq < 0) args[arg.slice(2)] = "true";
|
|
13667
|
+
else args[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
13553
13668
|
}
|
|
13554
13669
|
return args;
|
|
13555
13670
|
}
|
|
13556
13671
|
|
|
13557
|
-
function pickString(
|
|
13558
|
-
for (
|
|
13672
|
+
export function pickString() {
|
|
13673
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
13674
|
+
var value = arguments[i];
|
|
13559
13675
|
if (typeof value !== "string") continue;
|
|
13560
|
-
|
|
13676
|
+
var trimmed = value.trim();
|
|
13561
13677
|
if (trimmed) return trimmed;
|
|
13562
13678
|
}
|
|
13563
13679
|
return undefined;
|
|
13564
13680
|
}
|
|
13565
13681
|
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
13682
|
+
function configHome() {
|
|
13683
|
+
var xdg = pickString(process.env.XDG_CONFIG_HOME);
|
|
13684
|
+
return xdg ? xdg : join(homedir(), ".config");
|
|
13570
13685
|
}
|
|
13571
13686
|
|
|
13572
|
-
function
|
|
13687
|
+
function defaultStateDir() {
|
|
13688
|
+
var configured = pickString(process.env.ORGX_WIZARD_CONFIG_HOME);
|
|
13689
|
+
var root = configured ? configured : join(configHome(), "useorgx", "wizard");
|
|
13690
|
+
return join(root, "sessions");
|
|
13691
|
+
}
|
|
13692
|
+
|
|
13693
|
+
function defaultQueueDir() {
|
|
13694
|
+
var configured = pickString(process.env.ORGX_WIZARD_CONFIG_HOME);
|
|
13695
|
+
var root = configured ? configured : join(configHome(), "useorgx", "wizard");
|
|
13696
|
+
return join(root, "session-summary-captures");
|
|
13697
|
+
}
|
|
13698
|
+
|
|
13699
|
+
// Mirrors the backfill distiller so live and historical records agree on repo.
|
|
13700
|
+
export function repoOf(cwd) {
|
|
13701
|
+
if (!cwd) return "unknown";
|
|
13702
|
+
var marker = "/Code/";
|
|
13703
|
+
var at = cwd.indexOf(marker);
|
|
13704
|
+
if (at >= 0) {
|
|
13705
|
+
var rest = cwd.slice(at + marker.length);
|
|
13706
|
+
var slash = rest.indexOf("/");
|
|
13707
|
+
var name = slash < 0 ? rest : rest.slice(0, slash);
|
|
13708
|
+
if (name) return name;
|
|
13709
|
+
}
|
|
13710
|
+
return basename(cwd) || "unknown";
|
|
13711
|
+
}
|
|
13712
|
+
|
|
13713
|
+
export function statePath(sessionId, dir) {
|
|
13714
|
+
var root = dir ? dir : defaultStateDir();
|
|
13715
|
+
// Session ids come from the harness; keep them filesystem-safe regardless.
|
|
13716
|
+
var safe = String(sessionId).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 128);
|
|
13717
|
+
return join(root, safe + ".json");
|
|
13718
|
+
}
|
|
13719
|
+
|
|
13720
|
+
function safeFilePart(value) {
|
|
13721
|
+
return String(value).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 128);
|
|
13722
|
+
}
|
|
13723
|
+
|
|
13724
|
+
export function enqueueSummaryCapture(queueDir, summary, initiativeId, nowIso, captureKind) {
|
|
13725
|
+
var root = queueDir ? queueDir : defaultQueueDir();
|
|
13726
|
+
var stamp = String(nowIso).replace(/[^A-Za-z0-9]/g, "_");
|
|
13727
|
+
var captureId = [summary.source_client, summary.session_id, summary.ended_at, summary.events].join(":");
|
|
13728
|
+
var nonce = Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
|
|
13729
|
+
var name = "summary-" + safeFilePart(summary.source_client) + "-" + safeFilePart(summary.session_id) + "-" + stamp + "-" + process.pid + "-" + nonce + ".capture.json";
|
|
13730
|
+
var target = join(root, name);
|
|
13731
|
+
var temporary = target + ".tmp";
|
|
13732
|
+
var capture = {
|
|
13733
|
+
schema_version: "orgx-session-summary-capture/v1",
|
|
13734
|
+
capture_id: captureId,
|
|
13735
|
+
capture_kind: captureKind,
|
|
13736
|
+
queued_at: nowIso,
|
|
13737
|
+
session: summary,
|
|
13738
|
+
};
|
|
13739
|
+
if (initiativeId) capture.initiative_id = initiativeId;
|
|
13573
13740
|
try {
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13741
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
13742
|
+
var descriptor = openSync(temporary, "w", 0o600);
|
|
13743
|
+
try {
|
|
13744
|
+
writeSync(descriptor, JSON.stringify(capture), undefined, "utf8");
|
|
13745
|
+
fsyncSync(descriptor);
|
|
13746
|
+
} finally {
|
|
13747
|
+
closeSync(descriptor);
|
|
13748
|
+
}
|
|
13749
|
+
renameSync(temporary, target);
|
|
13750
|
+
return { path: target, capture: capture };
|
|
13751
|
+
} catch (e) {
|
|
13752
|
+
try { rmSync(temporary, { force: true }); } catch (ignored) {}
|
|
13753
|
+
return null;
|
|
13578
13754
|
}
|
|
13579
13755
|
}
|
|
13580
13756
|
|
|
13581
|
-
function
|
|
13582
|
-
const toolName = pickString(payload.tool_name, payload.toolName, payload.tool?.name, payload.name);
|
|
13583
|
-
const prompt = pickString(payload.prompt);
|
|
13757
|
+
export function emptyState(sessionId, sourceClient, cwd, nowIso) {
|
|
13584
13758
|
return {
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
13759
|
+
session_id: sessionId,
|
|
13760
|
+
source_client: sourceClient,
|
|
13761
|
+
cwd: cwd === undefined ? null : cwd,
|
|
13762
|
+
repo: repoOf(cwd),
|
|
13763
|
+
first_ts: nowIso,
|
|
13764
|
+
last_ts: nowIso,
|
|
13765
|
+
events: 0,
|
|
13766
|
+
prompts: 0,
|
|
13767
|
+
tool_calls: 0,
|
|
13768
|
+
tools: {},
|
|
13769
|
+
edits: 0,
|
|
13770
|
+
bash: 0,
|
|
13771
|
+
action_seq: 0,
|
|
13772
|
+
actions: [],
|
|
13773
|
+
actions_omitted: 0,
|
|
13774
|
+
pending_actions: [],
|
|
13775
|
+
pending_starts_omitted: 0,
|
|
13776
|
+
permission_requests: 0,
|
|
13777
|
+
permission_modes: [],
|
|
13778
|
+
work_context: null,
|
|
13588
13779
|
};
|
|
13589
13780
|
}
|
|
13590
13781
|
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
args.outbox,
|
|
13597
|
-
join(homedir(), ".config", "useorgx", "wizard", "hooks", "events.jsonl")
|
|
13598
|
-
);
|
|
13599
|
-
const event = pickString(args.event, payload.hook_event_name, payload.hookEventName, payload.event, payload.eventName, "unknown");
|
|
13600
|
-
const sourceClient = pickString(args.source_client, args["source-client"], "unknown");
|
|
13782
|
+
// One bounded read-modify-write. No spool, no network, no unbounded growth:
|
|
13783
|
+
// the tool map is capped so a pathological session cannot inflate the file.
|
|
13784
|
+
var MAX_TOOL_KEYS = 40;
|
|
13785
|
+
var MAX_CAPTURE_ACTIONS = 32;
|
|
13786
|
+
var MAX_PENDING_ACTIONS = 32;
|
|
13601
13787
|
|
|
13602
|
-
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
source_client: sourceClient,
|
|
13606
|
-
event,
|
|
13607
|
-
session_id: pickString(payload.session_id, payload.sessionId, payload.conversation_id, payload.conversationId),
|
|
13608
|
-
turn_id: pickString(payload.turn_id, payload.turnId),
|
|
13609
|
-
cwd: pickString(payload.cwd, payload.working_directory, payload.workspace, process.cwd()),
|
|
13610
|
-
transcript_path: pickString(payload.transcript_path, payload.transcriptPath),
|
|
13611
|
-
timestamp: new Date().toISOString(),
|
|
13612
|
-
summary: summarize(payload),
|
|
13613
|
-
};
|
|
13788
|
+
function isPromptEvent(event) {
|
|
13789
|
+
return event === "UserPromptSubmit" || String(event).indexOf("user_prompt") >= 0;
|
|
13790
|
+
}
|
|
13614
13791
|
|
|
13615
|
-
|
|
13616
|
-
|
|
13617
|
-
|
|
13618
|
-
|
|
13619
|
-
|
|
13792
|
+
function isToolCompletionEvent(event) {
|
|
13793
|
+
var normalized = String(event).toLowerCase();
|
|
13794
|
+
return event === "PostToolUse" ||
|
|
13795
|
+
event === "PostToolUseFailure" ||
|
|
13796
|
+
normalized.indexOf("post_tool_use") >= 0;
|
|
13620
13797
|
}
|
|
13621
13798
|
|
|
13622
|
-
|
|
13623
|
-
|
|
13799
|
+
function ensureExecutionState(state) {
|
|
13800
|
+
if (!Number.isFinite(state.action_seq)) state.action_seq = 0;
|
|
13801
|
+
if (!Array.isArray(state.actions)) state.actions = [];
|
|
13802
|
+
if (!Number.isFinite(state.actions_omitted)) state.actions_omitted = 0;
|
|
13803
|
+
if (!Array.isArray(state.pending_actions)) state.pending_actions = [];
|
|
13804
|
+
if (!Number.isFinite(state.pending_starts_omitted)) state.pending_starts_omitted = 0;
|
|
13805
|
+
if (!Number.isFinite(state.permission_requests)) state.permission_requests = 0;
|
|
13806
|
+
if (!Array.isArray(state.permission_modes)) state.permission_modes = [];
|
|
13807
|
+
return state;
|
|
13624
13808
|
}
|
|
13625
|
-
|
|
13626
|
-
|
|
13627
|
-
|
|
13628
|
-
|
|
13629
|
-
`--event=${params.event}`,
|
|
13630
|
-
`--source_client=${params.sourceClient}`,
|
|
13631
|
-
`--outbox=${params.outboxPath}`
|
|
13632
|
-
].join(" ");
|
|
13809
|
+
|
|
13810
|
+
function nextActionId(state) {
|
|
13811
|
+
state.action_seq += 1;
|
|
13812
|
+
return "action-" + String(state.action_seq).padStart(6, "0");
|
|
13633
13813
|
}
|
|
13634
|
-
function buildExecutionGraphEmitScriptContent() {
|
|
13635
|
-
return `#!/usr/bin/env node
|
|
13636
|
-
import { readFileSync } from "node:fs";
|
|
13637
13814
|
|
|
13638
|
-
function
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
if (!arg.startsWith("--")) continue;
|
|
13642
|
-
const i = arg.indexOf("=");
|
|
13643
|
-
if (i < 0) args[arg.slice(2)] = "true";
|
|
13644
|
-
else args[arg.slice(2, i)] = arg.slice(i + 1);
|
|
13645
|
-
}
|
|
13646
|
-
return args;
|
|
13815
|
+
function boundedString(value, max) {
|
|
13816
|
+
var text = pickString(value);
|
|
13817
|
+
return text ? text.slice(0, max) : undefined;
|
|
13647
13818
|
}
|
|
13648
|
-
|
|
13649
|
-
|
|
13819
|
+
|
|
13820
|
+
function safeDuration(value) {
|
|
13821
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return undefined;
|
|
13822
|
+
return Math.min(Math.round(value), 86400000);
|
|
13650
13823
|
}
|
|
13651
|
-
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
|
|
13824
|
+
|
|
13825
|
+
function actionResult(event, sourceClient) {
|
|
13826
|
+
if (event === "PostToolUseFailure" || String(event).toLowerCase().indexOf("failure") >= 0) {
|
|
13827
|
+
return "failed";
|
|
13655
13828
|
}
|
|
13656
|
-
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
return
|
|
13829
|
+
// Claude's PostToolUse contract fires only after success. Codex documents
|
|
13830
|
+
// PostToolUse for completed tools, including non-zero Bash exits, so its
|
|
13831
|
+
// outcome must remain explicitly unclassified without reading tool output.
|
|
13832
|
+
return sourceClient === "claude-code" ? "succeeded" : "completed_unknown";
|
|
13660
13833
|
}
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
} catch (e) {
|
|
13667
|
-
return "";
|
|
13834
|
+
|
|
13835
|
+
function pendingIndex(state, toolUseId) {
|
|
13836
|
+
if (!toolUseId) return -1;
|
|
13837
|
+
for (var i = 0; i < state.pending_actions.length; i++) {
|
|
13838
|
+
if (state.pending_actions[i].correlation_key === toolUseId) return i;
|
|
13668
13839
|
}
|
|
13840
|
+
return -1;
|
|
13669
13841
|
}
|
|
13670
|
-
|
|
13671
|
-
|
|
13672
|
-
|
|
13673
|
-
|
|
13674
|
-
|
|
13675
|
-
|
|
13676
|
-
|
|
13842
|
+
|
|
13843
|
+
function observeAction(state, input) {
|
|
13844
|
+
var event = input.event;
|
|
13845
|
+
var toolName = boundedString(input.toolName, 120);
|
|
13846
|
+
if (!toolName) return;
|
|
13847
|
+
var toolUseId = boundedString(input.toolUseId, 200);
|
|
13848
|
+
|
|
13849
|
+
if (event === "PreToolUse" || String(event).toLowerCase().indexOf("pre_tool_use") >= 0) {
|
|
13850
|
+
if (pendingIndex(state, toolUseId) >= 0) return;
|
|
13851
|
+
if (state.pending_actions.length >= MAX_PENDING_ACTIONS) {
|
|
13852
|
+
state.pending_starts_omitted += 1;
|
|
13853
|
+
return;
|
|
13854
|
+
}
|
|
13855
|
+
state.pending_actions.push({
|
|
13856
|
+
id: nextActionId(state),
|
|
13857
|
+
correlation_key: toolUseId,
|
|
13858
|
+
tool_name: normalizeToolName(toolName),
|
|
13859
|
+
started_at: input.nowIso,
|
|
13860
|
+
turn_id: boundedString(input.turnId, 120),
|
|
13861
|
+
});
|
|
13862
|
+
return;
|
|
13677
13863
|
}
|
|
13678
|
-
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13864
|
+
|
|
13865
|
+
if (!isToolCompletionEvent(event)) return;
|
|
13866
|
+
var index = pendingIndex(state, toolUseId);
|
|
13867
|
+
var pending = index >= 0 ? state.pending_actions.splice(index, 1)[0] : null;
|
|
13868
|
+
if (!pending) state.pending_starts_omitted += 1;
|
|
13869
|
+
var action = {
|
|
13870
|
+
id: pending ? pending.id : nextActionId(state),
|
|
13871
|
+
type: "tool.invoke",
|
|
13872
|
+
tool_name: normalizeToolName(toolName),
|
|
13873
|
+
status: actionResult(event, state.source_client),
|
|
13874
|
+
started_at: pending ? pending.started_at : undefined,
|
|
13875
|
+
completed_at: input.nowIso,
|
|
13876
|
+
duration_ms: safeDuration(input.durationMs),
|
|
13877
|
+
turn_id: boundedString(input.turnId, 120) || (pending ? pending.turn_id : undefined),
|
|
13878
|
+
provenance: "runtime_observed",
|
|
13879
|
+
};
|
|
13880
|
+
if (state.actions.length < MAX_CAPTURE_ACTIONS) state.actions.push(action);
|
|
13881
|
+
else state.actions_omitted += 1;
|
|
13684
13882
|
}
|
|
13685
|
-
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13883
|
+
|
|
13884
|
+
export function applyEvent(state, input) {
|
|
13885
|
+
ensureExecutionState(state);
|
|
13886
|
+
var event = input.event;
|
|
13887
|
+
var toolName = input.toolName;
|
|
13888
|
+
state.events += 1;
|
|
13889
|
+
state.last_ts = input.nowIso;
|
|
13890
|
+
|
|
13891
|
+
if (isPromptEvent(event)) state.prompts += 1;
|
|
13892
|
+
|
|
13893
|
+
if (event === "PermissionRequest") state.permission_requests += 1;
|
|
13894
|
+
var permissionMode = boundedString(input.permissionMode, 40);
|
|
13895
|
+
if (
|
|
13896
|
+
permissionMode &&
|
|
13897
|
+
state.permission_modes.indexOf(permissionMode) < 0 &&
|
|
13898
|
+
state.permission_modes.length < 8
|
|
13899
|
+
) {
|
|
13900
|
+
state.permission_modes.push(permissionMode);
|
|
13693
13901
|
}
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13902
|
+
|
|
13903
|
+
observeAction(state, input);
|
|
13904
|
+
|
|
13905
|
+
if (toolName && isToolCompletionEvent(event)) {
|
|
13906
|
+
state.tool_calls += 1;
|
|
13907
|
+
var known = state.tools[toolName] !== undefined;
|
|
13908
|
+
if (known || Object.keys(state.tools).length < MAX_TOOL_KEYS) {
|
|
13909
|
+
state.tools[toolName] = (state.tools[toolName] || 0) + 1;
|
|
13910
|
+
}
|
|
13911
|
+
if (toolName === "Write" || toolName === "Edit" || toolName === "NotebookEdit") state.edits += 1;
|
|
13912
|
+
if (toolName === "Bash") state.bash += 1;
|
|
13699
13913
|
}
|
|
13700
|
-
return
|
|
13914
|
+
return state;
|
|
13701
13915
|
}
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
13705
|
-
|
|
13916
|
+
|
|
13917
|
+
// Long MCP tool names ("mcp__<uuid>__<tool>") can make a raw tool map dominate
|
|
13918
|
+
// the payload, so keep the heaviest tools and bucket the tail. This bounds the
|
|
13919
|
+
// summary regardless of how varied or long a session gets.
|
|
13920
|
+
var MAX_REPORTED_TOOLS = 12;
|
|
13921
|
+
var MAX_TOOL_NAME = 48;
|
|
13922
|
+
|
|
13923
|
+
// "mcp__c2ed4428-8c64-4586-9c2a-52eac05637b8__search-contacts" is mostly an
|
|
13924
|
+
// opaque server uuid. The tool suffix is the part with analytic value, so
|
|
13925
|
+
// collapse the uuid rather than truncating the informative end.
|
|
13926
|
+
export function normalizeToolName(name) {
|
|
13927
|
+
var mcp = /^mcp__[0-9a-fA-F-]{16,}__(.+)$/.exec(name);
|
|
13928
|
+
var base = mcp ? "mcp__" + mcp[1] : name;
|
|
13929
|
+
return base.length > MAX_TOOL_NAME ? base.slice(0, MAX_TOOL_NAME) : base;
|
|
13930
|
+
}
|
|
13931
|
+
|
|
13932
|
+
export function boundTools(tools) {
|
|
13933
|
+
// Collapse first so two variants of the same tool merge before ranking.
|
|
13934
|
+
var merged = {};
|
|
13935
|
+
var names = Object.keys(tools);
|
|
13936
|
+
for (var i = 0; i < names.length; i++) {
|
|
13937
|
+
var key = normalizeToolName(names[i]);
|
|
13938
|
+
merged[key] = (merged[key] || 0) + tools[names[i]];
|
|
13939
|
+
}
|
|
13940
|
+
var entries = Object.keys(merged).map(function (k) { return [k, merged[k]]; });
|
|
13941
|
+
entries.sort(function (a, b) { return b[1] - a[1]; });
|
|
13942
|
+
var out = {};
|
|
13943
|
+
if (entries.length <= MAX_REPORTED_TOOLS) {
|
|
13944
|
+
for (var j = 0; j < entries.length; j++) out[entries[j][0]] = entries[j][1];
|
|
13945
|
+
return out;
|
|
13946
|
+
}
|
|
13947
|
+
for (var k = 0; k < MAX_REPORTED_TOOLS; k++) out[entries[k][0]] = entries[k][1];
|
|
13948
|
+
var other = 0;
|
|
13949
|
+
for (var m = MAX_REPORTED_TOOLS; m < entries.length; m++) other += entries[m][1];
|
|
13950
|
+
out.__other__ = other;
|
|
13951
|
+
out.__other_tools__ = entries.length - MAX_REPORTED_TOOLS;
|
|
13952
|
+
return out;
|
|
13953
|
+
}
|
|
13954
|
+
|
|
13955
|
+
function buildExecutionObservation(state, captureKind) {
|
|
13956
|
+
ensureExecutionState(state);
|
|
13957
|
+
var remaining = Math.max(0, MAX_CAPTURE_ACTIONS - state.actions.length);
|
|
13958
|
+
var includedPending = state.pending_actions.slice(0, remaining).map(function (pending) {
|
|
13959
|
+
return {
|
|
13960
|
+
id: pending.id,
|
|
13961
|
+
type: "tool.invoke",
|
|
13962
|
+
tool_name: pending.tool_name,
|
|
13963
|
+
status: "running",
|
|
13964
|
+
started_at: pending.started_at,
|
|
13965
|
+
turn_id: pending.turn_id,
|
|
13966
|
+
provenance: "runtime_observed",
|
|
13967
|
+
};
|
|
13968
|
+
});
|
|
13969
|
+
var pendingOmitted = Math.max(0, state.pending_actions.length - includedPending.length);
|
|
13970
|
+
var actions = state.actions.concat(includedPending).map(function (action) {
|
|
13971
|
+
var out = {
|
|
13972
|
+
id: boundedString(action.id, 80),
|
|
13973
|
+
type: "tool.invoke",
|
|
13974
|
+
tool_name: boundedString(action.tool_name, 48),
|
|
13975
|
+
status: action.status,
|
|
13976
|
+
provenance: "runtime_observed",
|
|
13977
|
+
};
|
|
13978
|
+
if (action.started_at) out.started_at = action.started_at;
|
|
13979
|
+
if (action.completed_at) out.completed_at = action.completed_at;
|
|
13980
|
+
if (action.duration_ms !== undefined) out.duration_ms = action.duration_ms;
|
|
13981
|
+
if (action.turn_id) out.turn_id = action.turn_id;
|
|
13982
|
+
return out;
|
|
13983
|
+
});
|
|
13984
|
+
var omitted = state.actions_omitted + pendingOmitted;
|
|
13985
|
+
var workContext = state.work_context && typeof state.work_context === "object"
|
|
13986
|
+
? state.work_context
|
|
13987
|
+
: null;
|
|
13988
|
+
var unavailable = ["verification"];
|
|
13989
|
+
if (!workContext || !workContext.intent) unavailable.push("intent");
|
|
13990
|
+
if (!workContext || !workContext.authority) unavailable.push("authority_decision");
|
|
13991
|
+
if (!workContext || !workContext.cost) unavailable.push("cost");
|
|
13992
|
+
if (!workContext || !Array.isArray(workContext.artifact_refs) || workContext.artifact_refs.length === 0) {
|
|
13993
|
+
unavailable.push("artifact_refs");
|
|
13994
|
+
}
|
|
13995
|
+
return {
|
|
13996
|
+
schema_version: "orgx-session-execution-observation/v1",
|
|
13997
|
+
boundary: captureKind || "legacy_unspecified",
|
|
13998
|
+
terminal_observed: captureKind === "session_end",
|
|
13999
|
+
actions: actions,
|
|
14000
|
+
actions_completed_observed: state.tool_calls,
|
|
14001
|
+
actions_omitted: omitted,
|
|
14002
|
+
start_times_omitted: state.pending_starts_omitted,
|
|
14003
|
+
pending_actions: state.pending_actions.length,
|
|
14004
|
+
permission_requests_observed: state.permission_requests,
|
|
14005
|
+
permission_modes_observed: state.permission_modes.slice(0, 8),
|
|
14006
|
+
action_capture_complete:
|
|
14007
|
+
omitted === 0 &&
|
|
14008
|
+
state.pending_starts_omitted === 0 &&
|
|
14009
|
+
state.pending_actions.length === 0,
|
|
14010
|
+
unavailable_fields: unavailable,
|
|
14011
|
+
};
|
|
14012
|
+
}
|
|
14013
|
+
|
|
14014
|
+
export function buildSummary(state, nowIso, captureKind) {
|
|
14015
|
+
var first = Date.parse(state.first_ts);
|
|
14016
|
+
var last = Date.parse(state.last_ts || nowIso);
|
|
14017
|
+
var spanOk = isFinite(first) && isFinite(last);
|
|
14018
|
+
var metadata = { execution: buildExecutionObservation(state, captureKind) };
|
|
14019
|
+
if (state.work_context && typeof state.work_context === "object") {
|
|
14020
|
+
metadata.work_context = state.work_context;
|
|
14021
|
+
}
|
|
14022
|
+
return {
|
|
14023
|
+
schema_version: SCHEMA_VERSION,
|
|
14024
|
+
source: "orgx_session_summary",
|
|
14025
|
+
session_id: state.session_id,
|
|
14026
|
+
source_client: state.source_client,
|
|
14027
|
+
repo: state.repo,
|
|
14028
|
+
cwd: state.cwd,
|
|
14029
|
+
day: isFinite(first) ? new Date(first).toISOString().slice(0, 10) : "unknown",
|
|
14030
|
+
started_at: state.first_ts,
|
|
14031
|
+
ended_at: state.last_ts,
|
|
14032
|
+
duration_min: spanOk ? Math.round((last - first) / 60000) : 0,
|
|
14033
|
+
events: state.events,
|
|
14034
|
+
prompts: state.prompts,
|
|
14035
|
+
tool_calls: state.tool_calls,
|
|
14036
|
+
tools: boundTools(state.tools),
|
|
14037
|
+
edits: state.edits,
|
|
14038
|
+
bash: state.bash,
|
|
14039
|
+
metadata: metadata,
|
|
14040
|
+
};
|
|
14041
|
+
}
|
|
14042
|
+
|
|
14043
|
+
export function readState(path) {
|
|
14044
|
+
try {
|
|
14045
|
+
var parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
14046
|
+
var ok = parsed && typeof parsed === "object" && !Array.isArray(parsed);
|
|
14047
|
+
return ok ? parsed : null;
|
|
14048
|
+
} catch (e) {
|
|
14049
|
+
return null;
|
|
14050
|
+
}
|
|
14051
|
+
}
|
|
14052
|
+
|
|
14053
|
+
export function writeState(path, state) {
|
|
14054
|
+
try {
|
|
14055
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
14056
|
+
writeFileSync(path, JSON.stringify(state), { encoding: "utf8", mode: 0o600 });
|
|
14057
|
+
return true;
|
|
14058
|
+
} catch (e) {
|
|
14059
|
+
return false;
|
|
14060
|
+
}
|
|
14061
|
+
}
|
|
14062
|
+
|
|
14063
|
+
async function readStdin() {
|
|
14064
|
+
try {
|
|
14065
|
+
var chunks = [];
|
|
14066
|
+
for await (var chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
14067
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
14068
|
+
} catch (e) {
|
|
14069
|
+
return "";
|
|
14070
|
+
}
|
|
14071
|
+
}
|
|
14072
|
+
|
|
14073
|
+
export function parseJson(value) {
|
|
14074
|
+
try {
|
|
14075
|
+
var parsed = JSON.parse(value || "{}");
|
|
14076
|
+
var ok = parsed && typeof parsed === "object" && !Array.isArray(parsed);
|
|
14077
|
+
return ok ? parsed : {};
|
|
14078
|
+
} catch (e) {
|
|
14079
|
+
return {};
|
|
14080
|
+
}
|
|
14081
|
+
}
|
|
14082
|
+
|
|
14083
|
+
function workString(value, max) {
|
|
14084
|
+
if (typeof value !== "string") return null;
|
|
14085
|
+
var trimmed = value.trim();
|
|
14086
|
+
return trimmed && trimmed.length <= max ? trimmed : null;
|
|
14087
|
+
}
|
|
14088
|
+
|
|
14089
|
+
function workObject(value) {
|
|
14090
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
14091
|
+
}
|
|
14092
|
+
|
|
14093
|
+
function workStrings(value, max) {
|
|
14094
|
+
if (value === undefined) return [];
|
|
14095
|
+
if (!Array.isArray(value) || value.length > 20) return null;
|
|
14096
|
+
var output = [];
|
|
14097
|
+
for (var i = 0; i < value.length; i++) {
|
|
14098
|
+
var item = workString(value[i], max);
|
|
14099
|
+
if (!item) return null;
|
|
14100
|
+
if (output.indexOf(item) < 0) output.push(item);
|
|
14101
|
+
}
|
|
14102
|
+
return output;
|
|
14103
|
+
}
|
|
14104
|
+
|
|
14105
|
+
function workRef(value) {
|
|
14106
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
14107
|
+
var system = workString(value.system, 120);
|
|
14108
|
+
var type = workString(value.type, 120);
|
|
14109
|
+
var id = workString(value.id, 500);
|
|
14110
|
+
var uri = value.uri === undefined ? undefined : workString(value.uri, 1000);
|
|
14111
|
+
var version = value.version === undefined ? undefined : workString(value.version, 120);
|
|
14112
|
+
if (!system || !type || !id || uri === null || version === null) return null;
|
|
14113
|
+
if (uri && /[\0- ]/.test(uri)) return null;
|
|
14114
|
+
var ref = { system: system, type: type, id: id };
|
|
14115
|
+
if (uri) ref.uri = uri;
|
|
14116
|
+
if (version) ref.version = version;
|
|
14117
|
+
return ref;
|
|
14118
|
+
}
|
|
14119
|
+
|
|
14120
|
+
function workRefs(value) {
|
|
14121
|
+
if (value === undefined) return [];
|
|
14122
|
+
if (!Array.isArray(value) || value.length > 20) return null;
|
|
14123
|
+
var refs = [];
|
|
14124
|
+
var seen = {};
|
|
14125
|
+
for (var i = 0; i < value.length; i++) {
|
|
14126
|
+
var ref = workRef(value[i]);
|
|
14127
|
+
if (!ref) return null;
|
|
14128
|
+
var key = ref.system + "\0" + ref.type + "\0" + ref.id;
|
|
14129
|
+
if (!seen[key]) {
|
|
14130
|
+
seen[key] = true;
|
|
14131
|
+
refs.push(ref);
|
|
14132
|
+
}
|
|
14133
|
+
}
|
|
14134
|
+
refs.sort(function (a, b) {
|
|
14135
|
+
var left = a.system + "\0" + a.type + "\0" + a.id;
|
|
14136
|
+
var right = b.system + "\0" + b.type + "\0" + b.id;
|
|
14137
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
14138
|
+
});
|
|
14139
|
+
return refs;
|
|
14140
|
+
}
|
|
14141
|
+
|
|
14142
|
+
function workIso(value) {
|
|
14143
|
+
var text = workString(value, 100);
|
|
14144
|
+
return text && /^d{4}-d{2}-d{2}Td{2}:d{2}:d{2}(?:.d+)?(?:Z|[+-]d{2}:d{2})$/.test(text) && !Number.isNaN(Date.parse(text))
|
|
14145
|
+
? text
|
|
14146
|
+
: null;
|
|
14147
|
+
}
|
|
14148
|
+
|
|
14149
|
+
export function parseExplicitWorkContext(value) {
|
|
14150
|
+
if (!value || Buffer.byteLength(String(value), "utf8") > 4096) return null;
|
|
14151
|
+
var parsed = parseJson(value);
|
|
14152
|
+
if (parsed.schema_version !== "orgx-session-work-context/v1") return null;
|
|
14153
|
+
var intent = workObject(parsed.intent);
|
|
14154
|
+
var authority = workObject(parsed.authority);
|
|
14155
|
+
var scope = workObject(authority && authority.scope);
|
|
14156
|
+
var cost = workObject(parsed.cost);
|
|
14157
|
+
if (!intent || !authority || !scope || !cost) return null;
|
|
14158
|
+
var summary = workString(intent.summary, 2000);
|
|
14159
|
+
var objective = intent.objective === undefined ? undefined : workString(intent.objective, 4000);
|
|
14160
|
+
var acceptance = workStrings(intent.acceptance_criteria, 1000);
|
|
14161
|
+
var intentConstraints = workStrings(intent.constraints, 1000);
|
|
14162
|
+
var requestRef = intent.request_ref === undefined ? undefined : workRef(intent.request_ref);
|
|
14163
|
+
var modes = ["explicit", "delegated", "inherited", "policy", "none", "unknown"];
|
|
14164
|
+
var statuses = ["granted", "restricted", "denied", "expired", "unknown"];
|
|
14165
|
+
var mode = workString(authority.mode, 40);
|
|
14166
|
+
var status = workString(authority.status, 40);
|
|
14167
|
+
var actions = workStrings(scope.actions, 120);
|
|
14168
|
+
var resources = workRefs(scope.resources);
|
|
14169
|
+
var systems = workStrings(scope.systems, 120);
|
|
14170
|
+
var spend = scope.spend_limit === undefined ? undefined : workObject(scope.spend_limit);
|
|
14171
|
+
var spendCurrency = spend === undefined ? undefined : workString(spend && spend.currency, 12);
|
|
14172
|
+
var spendAmount = spend && spend.amount;
|
|
14173
|
+
var authorityConstraints = workStrings(authority.constraints, 1000);
|
|
14174
|
+
var authorizationRef = authority.authorization_ref === undefined ? undefined : workRef(authority.authorization_ref);
|
|
14175
|
+
var validFrom = authority.valid_from === undefined ? undefined : workIso(authority.valid_from);
|
|
14176
|
+
var validUntil = authority.valid_until === undefined ? undefined : workIso(authority.valid_until);
|
|
14177
|
+
var artifactRefs = workRefs(parsed.artifact_refs);
|
|
14178
|
+
var evidenceRefs = workRefs(parsed.evidence_refs);
|
|
14179
|
+
var currency = workString(cost.currency, 12);
|
|
14180
|
+
var source = cost.source === undefined ? undefined : workString(cost.source, 120);
|
|
14181
|
+
if (!summary || objective === null || acceptance === null || intentConstraints === null || requestRef === null) return null;
|
|
14182
|
+
if (!mode || modes.indexOf(mode) < 0 || !status || statuses.indexOf(status) < 0) return null;
|
|
14183
|
+
if (actions === null || resources === null || systems === null || authorityConstraints === null || authorizationRef === null) return null;
|
|
14184
|
+
if (spend !== undefined && (!spend || !spendCurrency || !/^[A-Z][A-Z0-9_-]{1,11}$/.test(spendCurrency) || typeof spendAmount !== "number" || !Number.isFinite(spendAmount) || spendAmount < 0 || spendAmount > 100000)) return null;
|
|
14185
|
+
if (validFrom === null || validUntil === null || (validFrom && validUntil && Date.parse(validUntil) < Date.parse(validFrom))) return null;
|
|
14186
|
+
if (!currency || !/^[A-Z][A-Z0-9_-]{1,11}$/.test(currency) || typeof cost.total !== "number" || !Number.isFinite(cost.total) || cost.total < 0 || cost.total > 100000 || source === null) return null;
|
|
14187
|
+
if (cost.estimated !== undefined && typeof cost.estimated !== "boolean") return null;
|
|
14188
|
+
if (artifactRefs === null || evidenceRefs === null) return null;
|
|
14189
|
+
var normalizedIntent = { summary: summary, acceptance_criteria: acceptance, constraints: intentConstraints };
|
|
14190
|
+
if (objective) normalizedIntent.objective = objective;
|
|
14191
|
+
if (requestRef) normalizedIntent.request_ref = requestRef;
|
|
14192
|
+
var normalizedAuthority = {
|
|
14193
|
+
mode: mode,
|
|
14194
|
+
status: status,
|
|
14195
|
+
scope: { actions: actions, resources: resources, systems: systems },
|
|
14196
|
+
constraints: authorityConstraints,
|
|
14197
|
+
};
|
|
14198
|
+
if (spend !== undefined) normalizedAuthority.scope.spend_limit = { currency: spendCurrency, amount: spendAmount };
|
|
14199
|
+
if (authorizationRef) normalizedAuthority.authorization_ref = authorizationRef;
|
|
14200
|
+
if (validFrom) normalizedAuthority.valid_from = validFrom;
|
|
14201
|
+
if (validUntil) normalizedAuthority.valid_until = validUntil;
|
|
14202
|
+
var normalizedCost = { currency: currency, total: cost.total, estimated: cost.estimated !== false };
|
|
14203
|
+
if (source) normalizedCost.source = source;
|
|
14204
|
+
return {
|
|
14205
|
+
schema_version: "orgx-session-work-context/v1",
|
|
14206
|
+
provenance: "producer_asserted",
|
|
14207
|
+
intent: normalizedIntent,
|
|
14208
|
+
authority: normalizedAuthority,
|
|
14209
|
+
cost: normalizedCost,
|
|
14210
|
+
artifact_refs: artifactRefs,
|
|
14211
|
+
evidence_refs: evidenceRefs,
|
|
14212
|
+
};
|
|
14213
|
+
}
|
|
14214
|
+
|
|
14215
|
+
function autoFlushDisabled(value) {
|
|
14216
|
+
var normalized = String(value || "").trim().toLowerCase();
|
|
14217
|
+
return normalized === "off" || normalized === "false" || normalized === "0";
|
|
14218
|
+
}
|
|
14219
|
+
|
|
14220
|
+
/**
|
|
14221
|
+
* Ask a detached Wizard process to deliver the durable queue. The hook itself
|
|
14222
|
+
* performs no network or credential work and never waits for delivery.
|
|
14223
|
+
*/
|
|
14224
|
+
export function triggerQueueDelivery(args, env, queueDir, spawnImpl) {
|
|
14225
|
+
if (autoFlushDisabled(env.ORGX_SESSION_SUMMARY_AUTO_FLUSH)) return false;
|
|
14226
|
+
var nodePath = pickString(args.delivery_node);
|
|
14227
|
+
var cliPath = pickString(args.delivery_cli);
|
|
14228
|
+
if (!nodePath || !cliPath || !queueDir) return false;
|
|
14229
|
+
var configuredLimit = parseInt(
|
|
14230
|
+
pickString(args.auto_flush_limit, env.ORGX_SESSION_SUMMARY_AUTO_FLUSH_LIMIT) || "",
|
|
14231
|
+
10
|
|
14232
|
+
);
|
|
14233
|
+
var limit = Number.isFinite(configuredLimit)
|
|
14234
|
+
? Math.max(1, Math.min(configuredLimit, 100))
|
|
14235
|
+
: 25;
|
|
14236
|
+
try {
|
|
14237
|
+
var child = spawnImpl(
|
|
14238
|
+
nodePath,
|
|
14239
|
+
[
|
|
14240
|
+
cliPath,
|
|
14241
|
+
"hooks",
|
|
14242
|
+
"flush",
|
|
14243
|
+
"--background",
|
|
14244
|
+
"--limit=" + String(limit),
|
|
14245
|
+
"--queue=" + queueDir,
|
|
14246
|
+
],
|
|
14247
|
+
{ detached: true, stdio: "ignore" }
|
|
14248
|
+
);
|
|
14249
|
+
if (child && typeof child.on === "function") {
|
|
14250
|
+
child.on("error", function () {});
|
|
14251
|
+
}
|
|
14252
|
+
if (child && typeof child.unref === "function") child.unref();
|
|
14253
|
+
return true;
|
|
14254
|
+
} catch (e) {
|
|
14255
|
+
return false;
|
|
14256
|
+
}
|
|
14257
|
+
}
|
|
14258
|
+
|
|
14259
|
+
export async function main(options) {
|
|
14260
|
+
var opts = options || {};
|
|
14261
|
+
var argv = opts.argv ? opts.argv : process.argv.slice(2);
|
|
14262
|
+
var env = opts.env ? opts.env : process.env;
|
|
14263
|
+
var stdinText = opts.stdinText === undefined ? "" : opts.stdinText;
|
|
14264
|
+
var now = opts.now ? opts.now : function () { return new Date().toISOString(); };
|
|
14265
|
+
var dir = opts.dir;
|
|
14266
|
+
var spawnImpl = opts.spawnImpl ? opts.spawnImpl : spawn;
|
|
14267
|
+
|
|
14268
|
+
var args = parseArgs(argv);
|
|
14269
|
+
var payload = parseJson(stdinText);
|
|
14270
|
+
var nowIso = now();
|
|
14271
|
+
var explicitWorkContext = parseExplicitWorkContext(env.ORGX_SESSION_WORK_CONTEXT);
|
|
14272
|
+
|
|
14273
|
+
var event = pickString(args.event, payload.hook_event_name, payload.hookEventName, "unknown");
|
|
14274
|
+
var sessionId = pickString(payload.session_id, payload.sessionId, args.session_id);
|
|
14275
|
+
if (!sessionId) return { ok: true, skipped: "missing_session_id" };
|
|
14276
|
+
|
|
14277
|
+
var sourceClient = pickString(args.source_client, env.ORGX_SOURCE_CLIENT, "claude-code");
|
|
14278
|
+
var cwd = pickString(payload.cwd, args.cwd);
|
|
14279
|
+
var stateDir = dir ? dir : pickString(args.state_dir, env.ORGX_SESSION_STATE_DIR);
|
|
14280
|
+
var path = statePath(sessionId, stateDir);
|
|
14281
|
+
|
|
14282
|
+
var terminal = event === "Stop" || event === "SessionEnd";
|
|
14283
|
+
|
|
14284
|
+
if (!terminal) {
|
|
14285
|
+
// Hot path: one small file read + write. Never a network call.
|
|
14286
|
+
var existing = readState(path);
|
|
14287
|
+
var state = existing ? existing : emptyState(sessionId, sourceClient, cwd, nowIso);
|
|
14288
|
+
if (!state.cwd && cwd) {
|
|
14289
|
+
state.cwd = cwd;
|
|
14290
|
+
state.repo = repoOf(cwd);
|
|
14291
|
+
}
|
|
14292
|
+
if (explicitWorkContext) state.work_context = explicitWorkContext;
|
|
14293
|
+
var toolName = pickString(
|
|
14294
|
+
payload.tool_name,
|
|
14295
|
+
payload.toolName,
|
|
14296
|
+
payload.tool ? payload.tool.name : undefined
|
|
14297
|
+
);
|
|
14298
|
+
applyEvent(state, {
|
|
14299
|
+
event: event,
|
|
14300
|
+
toolName: toolName,
|
|
14301
|
+
toolUseId: pickString(payload.tool_use_id, payload.toolUseId),
|
|
14302
|
+
turnId: pickString(payload.turn_id, payload.turnId),
|
|
14303
|
+
durationMs: payload.duration_ms,
|
|
14304
|
+
permissionMode: pickString(payload.permission_mode, payload.permissionMode),
|
|
14305
|
+
nowIso: nowIso,
|
|
14306
|
+
});
|
|
14307
|
+
writeState(path, state);
|
|
14308
|
+
return { ok: true, counted: true, event: event };
|
|
14309
|
+
}
|
|
14310
|
+
|
|
14311
|
+
// Terminal: persist one cumulative snapshot, then ask a detached Wizard
|
|
14312
|
+
// worker to flush. The hook never owns credentials, retries, or network I/O.
|
|
14313
|
+
var finalState = readState(path);
|
|
14314
|
+
if (!finalState) return { ok: true, skipped: "no_session_state" };
|
|
14315
|
+
if (explicitWorkContext) finalState.work_context = explicitWorkContext;
|
|
14316
|
+
var initiativeId = pickString(env.ORGX_INITIATIVE_ID, args.initiative);
|
|
14317
|
+
var queueDir = pickString(args.queue_dir, env.ORGX_SESSION_SUMMARY_QUEUE_DIR) || defaultQueueDir();
|
|
14318
|
+
var captureKind = event === "SessionEnd" ? "session_end" : "turn_boundary";
|
|
14319
|
+
var summary = buildSummary(finalState, nowIso, captureKind);
|
|
14320
|
+
var queued = enqueueSummaryCapture(queueDir, summary, initiativeId, nowIso, captureKind);
|
|
14321
|
+
if (!queued) return { ok: true, skipped: "queue_write_failed", summary: summary };
|
|
14322
|
+
var deliveryTriggered = triggerQueueDelivery(args, env, queueDir, spawnImpl);
|
|
14323
|
+
|
|
14324
|
+
if (event === "SessionEnd") {
|
|
14325
|
+
try {
|
|
14326
|
+
rmSync(path, { force: true });
|
|
14327
|
+
} catch (e) {
|
|
14328
|
+
// keep going
|
|
14329
|
+
}
|
|
14330
|
+
}
|
|
14331
|
+
|
|
14332
|
+
return {
|
|
14333
|
+
ok: true,
|
|
14334
|
+
queued: true,
|
|
14335
|
+
queue_path: queued.path,
|
|
14336
|
+
delivery_triggered: deliveryTriggered,
|
|
14337
|
+
final: event === "SessionEnd",
|
|
14338
|
+
summary: summary,
|
|
14339
|
+
};
|
|
14340
|
+
}
|
|
14341
|
+
|
|
14342
|
+
var invokedDirectly = process.argv[1] && import.meta.url === "file://" + process.argv[1];
|
|
14343
|
+
if (invokedDirectly) {
|
|
14344
|
+
readStdin()
|
|
14345
|
+
.then(function (text) { return main({ stdinText: text }); })
|
|
14346
|
+
.catch(function () {})
|
|
14347
|
+
.finally(function () { process.exit(0); });
|
|
14348
|
+
}
|
|
14349
|
+
`;
|
|
14350
|
+
}
|
|
14351
|
+
|
|
14352
|
+
// src/lib/runtime-hooks.ts
|
|
14353
|
+
var HOOK_MARKER = "orgx-session-hook.mjs";
|
|
14354
|
+
var EMIT_HOOK_MARKER = "orgx-emit-execution-graph.mjs";
|
|
14355
|
+
var SUMMARY_HOOK_MARKER = SESSION_SUMMARY_HOOK_MARKER;
|
|
14356
|
+
var HOOK_EVENTS = [
|
|
14357
|
+
"SessionStart",
|
|
14358
|
+
"UserPromptSubmit",
|
|
14359
|
+
"PreToolUse",
|
|
14360
|
+
"PostToolUse",
|
|
14361
|
+
"PermissionRequest",
|
|
14362
|
+
"Stop",
|
|
14363
|
+
"SessionEnd"
|
|
14364
|
+
];
|
|
14365
|
+
var CLAUDE_HOOK_EVENTS = [
|
|
14366
|
+
"SessionStart",
|
|
14367
|
+
"UserPromptSubmit",
|
|
14368
|
+
"PreToolUse",
|
|
14369
|
+
"PostToolUse",
|
|
14370
|
+
"PostToolUseFailure",
|
|
14371
|
+
"PermissionRequest",
|
|
14372
|
+
"SubagentStop",
|
|
14373
|
+
"Stop",
|
|
14374
|
+
"SessionEnd"
|
|
14375
|
+
];
|
|
14376
|
+
function currentPackagedCliPath() {
|
|
14377
|
+
if (!process.argv[1]) return "";
|
|
14378
|
+
const candidate = resolve2(process.argv[1]);
|
|
14379
|
+
return /\.(?:cts|mts|ts|tsx)$/i.test(candidate) ? "" : candidate;
|
|
14380
|
+
}
|
|
14381
|
+
function defaultPaths(options = {}) {
|
|
14382
|
+
const hookDir = join8(ORGX_WIZARD_CONFIG_HOME, "hooks");
|
|
14383
|
+
return {
|
|
14384
|
+
claudeSettingsPath: options.claudeSettingsPath ?? join8(CLAUDE_DIR, "settings.json"),
|
|
14385
|
+
codexConfigPath: options.codexConfigPath ?? CODEX_CONFIG_PATH ?? join8(CODEX_DIR, "config.toml"),
|
|
14386
|
+
codexHooksPath: options.codexHooksPath ?? join8(CODEX_DIR, "hooks.json"),
|
|
14387
|
+
hookScriptPath: options.hookScriptPath ?? join8(hookDir, HOOK_MARKER),
|
|
14388
|
+
emitHookScriptPath: options.emitHookScriptPath ?? join8(hookDir, EMIT_HOOK_MARKER),
|
|
14389
|
+
summaryHookScriptPath: options.summaryHookScriptPath ?? join8(hookDir, SUMMARY_HOOK_MARKER),
|
|
14390
|
+
sessionStateDir: options.sessionStateDir ?? join8(ORGX_WIZARD_CONFIG_HOME, "sessions"),
|
|
14391
|
+
sessionSummaryQueueDir: options.sessionSummaryQueueDir ?? join8(ORGX_WIZARD_CONFIG_HOME, "session-summary-captures"),
|
|
14392
|
+
deliveryNodePath: options.deliveryNodePath ?? process.execPath,
|
|
14393
|
+
deliveryCliPath: options.deliveryCliPath ?? currentPackagedCliPath(),
|
|
14394
|
+
outboxPath: options.outboxPath ?? join8(hookDir, "events.jsonl")
|
|
14395
|
+
};
|
|
14396
|
+
}
|
|
14397
|
+
function countJsonlLines(path) {
|
|
14398
|
+
const raw = readTextIfExists(path);
|
|
14399
|
+
if (!raw) return 0;
|
|
14400
|
+
return raw.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
|
|
14401
|
+
}
|
|
14402
|
+
function countSessionSummaryCaptures(path) {
|
|
14403
|
+
try {
|
|
14404
|
+
return readdirSync6(path).filter((name) => name.endsWith(".capture.json")).length;
|
|
14405
|
+
} catch {
|
|
14406
|
+
return 0;
|
|
14407
|
+
}
|
|
14408
|
+
}
|
|
14409
|
+
function backupPath(path, now) {
|
|
14410
|
+
const timestamp = now.toISOString().replace(/[:.]/g, "-");
|
|
14411
|
+
return `${path}.bak.${timestamp}`;
|
|
14412
|
+
}
|
|
14413
|
+
function backupExisting(path, now) {
|
|
14414
|
+
if (!existsSync9(path)) return null;
|
|
14415
|
+
const backup = backupPath(path, now);
|
|
14416
|
+
copyFileSync(path, backup);
|
|
14417
|
+
return backup;
|
|
14418
|
+
}
|
|
14419
|
+
function hasOrgxHook(raw) {
|
|
14420
|
+
return Boolean(raw?.includes(HOOK_MARKER));
|
|
14421
|
+
}
|
|
14422
|
+
function codexHooksEnabled(raw) {
|
|
14423
|
+
return Boolean(raw && /^\s*codex_hooks\s*=\s*true\s*$/m.test(raw));
|
|
14424
|
+
}
|
|
14425
|
+
function codexHasNotify(raw) {
|
|
14426
|
+
return Boolean(raw && /^\s*notify\s*=/m.test(raw));
|
|
14427
|
+
}
|
|
14428
|
+
function buildRuntimeHookScriptContent() {
|
|
14429
|
+
return `#!/usr/bin/env node
|
|
14430
|
+
import { appendFileSync, mkdirSync, renameSync, statSync } from "node:fs";
|
|
14431
|
+
import { dirname, join } from "node:path";
|
|
14432
|
+
import { homedir } from "node:os";
|
|
14433
|
+
|
|
14434
|
+
const MAX_OUTBOX_BYTES = ${HOOK_OUTBOX_MAX_BYTES};
|
|
14435
|
+
|
|
14436
|
+
function outboxLimit() {
|
|
14437
|
+
const raw = parseInt(process.env.ORGX_WIZARD_HOOK_OUTBOX_MAX_BYTES || "", 10);
|
|
14438
|
+
return Number.isFinite(raw) && raw > 0 ? raw : MAX_OUTBOX_BYTES;
|
|
14439
|
+
}
|
|
14440
|
+
|
|
14441
|
+
function spoolDisabled() {
|
|
14442
|
+
return String(process.env.ORGX_WIZARD_HOOK_SPOOL || "").trim().toLowerCase() === "off";
|
|
14443
|
+
}
|
|
14444
|
+
|
|
14445
|
+
function rotateIfOversized(path) {
|
|
14446
|
+
try {
|
|
14447
|
+
if (statSync(path).size > outboxLimit()) renameSync(path, path + ".1");
|
|
14448
|
+
} catch (error) {
|
|
14449
|
+
// No spool yet, or rotation is not possible. Either way, keep going.
|
|
14450
|
+
}
|
|
14451
|
+
}
|
|
14452
|
+
|
|
14453
|
+
function parseArgs(argv) {
|
|
14454
|
+
const args = {};
|
|
14455
|
+
for (const arg of argv) {
|
|
14456
|
+
if (!arg.startsWith("--")) continue;
|
|
14457
|
+
const [key, ...rest] = arg.slice(2).split("=");
|
|
14458
|
+
args[key] = rest.length > 0 ? rest.join("=") : "true";
|
|
14459
|
+
}
|
|
14460
|
+
return args;
|
|
14461
|
+
}
|
|
14462
|
+
|
|
14463
|
+
function pickString(...values) {
|
|
14464
|
+
for (const value of values) {
|
|
14465
|
+
if (typeof value !== "string") continue;
|
|
14466
|
+
const trimmed = value.trim();
|
|
14467
|
+
if (trimmed) return trimmed;
|
|
14468
|
+
}
|
|
14469
|
+
return undefined;
|
|
14470
|
+
}
|
|
14471
|
+
|
|
14472
|
+
async function readStdin() {
|
|
14473
|
+
const chunks = [];
|
|
14474
|
+
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
14475
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
14476
|
+
}
|
|
14477
|
+
|
|
14478
|
+
function parseJson(value) {
|
|
14479
|
+
try {
|
|
14480
|
+
const parsed = JSON.parse(value || "{}");
|
|
14481
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
14482
|
+
} catch {
|
|
14483
|
+
return {};
|
|
14484
|
+
}
|
|
14485
|
+
}
|
|
14486
|
+
|
|
14487
|
+
function summarize(payload) {
|
|
14488
|
+
const toolName = pickString(payload.tool_name, payload.toolName, payload.tool?.name, payload.name);
|
|
14489
|
+
const prompt = pickString(payload.prompt);
|
|
14490
|
+
return {
|
|
14491
|
+
tool_name: toolName,
|
|
14492
|
+
prompt_chars: prompt ? prompt.length : undefined,
|
|
14493
|
+
payload_keys: Object.keys(payload).slice(0, 40),
|
|
14494
|
+
};
|
|
14495
|
+
}
|
|
14496
|
+
|
|
14497
|
+
const args = parseArgs(process.argv.slice(2));
|
|
14498
|
+
const raw = await readStdin();
|
|
14499
|
+
const payload = parseJson(raw);
|
|
14500
|
+
const outbox = pickString(
|
|
14501
|
+
process.env.ORGX_WIZARD_HOOK_OUTBOX,
|
|
14502
|
+
args.outbox,
|
|
14503
|
+
join(homedir(), ".config", "useorgx", "wizard", "hooks", "events.jsonl")
|
|
14504
|
+
);
|
|
14505
|
+
const event = pickString(args.event, payload.hook_event_name, payload.hookEventName, payload.event, payload.eventName, "unknown");
|
|
14506
|
+
const sourceClient = pickString(args.source_client, args["source-client"], "unknown");
|
|
14507
|
+
|
|
14508
|
+
const record = {
|
|
14509
|
+
schema_version: "2026-05-07",
|
|
14510
|
+
source: "orgx_wizard_runtime_hook",
|
|
14511
|
+
source_client: sourceClient,
|
|
14512
|
+
event,
|
|
14513
|
+
session_id: pickString(payload.session_id, payload.sessionId, payload.conversation_id, payload.conversationId),
|
|
14514
|
+
turn_id: pickString(payload.turn_id, payload.turnId),
|
|
14515
|
+
cwd: pickString(payload.cwd, payload.working_directory, payload.workspace, process.cwd()),
|
|
14516
|
+
transcript_path: pickString(payload.transcript_path, payload.transcriptPath),
|
|
14517
|
+
timestamp: new Date().toISOString(),
|
|
14518
|
+
summary: summarize(payload),
|
|
14519
|
+
};
|
|
14520
|
+
|
|
14521
|
+
try {
|
|
14522
|
+
if (!spoolDisabled()) {
|
|
14523
|
+
mkdirSync(dirname(outbox), { recursive: true, mode: 0o700 });
|
|
14524
|
+
rotateIfOversized(outbox);
|
|
14525
|
+
appendFileSync(outbox, JSON.stringify(record) + "\\n", { encoding: "utf8", mode: 0o600 });
|
|
14526
|
+
}
|
|
14527
|
+
} catch {
|
|
14528
|
+
// Hooks must never break the user's agent runtime.
|
|
14529
|
+
}
|
|
14530
|
+
|
|
14531
|
+
process.exit(0);
|
|
14532
|
+
`;
|
|
14533
|
+
}
|
|
14534
|
+
function buildHookCommand(params) {
|
|
14535
|
+
return [
|
|
14536
|
+
"node",
|
|
14537
|
+
JSON.stringify(params.hookScriptPath),
|
|
14538
|
+
`--event=${params.event}`,
|
|
14539
|
+
`--source_client=${params.sourceClient}`,
|
|
14540
|
+
`--outbox=${params.outboxPath}`
|
|
14541
|
+
].join(" ");
|
|
14542
|
+
}
|
|
14543
|
+
function buildExecutionGraphEmitScriptContent() {
|
|
14544
|
+
return `#!/usr/bin/env node
|
|
14545
|
+
import { readFileSync } from "node:fs";
|
|
14546
|
+
|
|
14547
|
+
function parseArgs(argv) {
|
|
14548
|
+
const args = {};
|
|
14549
|
+
for (const arg of argv) {
|
|
14550
|
+
if (!arg.startsWith("--")) continue;
|
|
14551
|
+
const i = arg.indexOf("=");
|
|
14552
|
+
if (i < 0) args[arg.slice(2)] = "true";
|
|
14553
|
+
else args[arg.slice(2, i)] = arg.slice(i + 1);
|
|
14554
|
+
}
|
|
14555
|
+
return args;
|
|
14556
|
+
}
|
|
14557
|
+
function truthy(v) {
|
|
14558
|
+
return typeof v === "string" && ["1", "true", "yes", "on"].includes(v.toLowerCase());
|
|
14559
|
+
}
|
|
14560
|
+
function pick() {
|
|
14561
|
+
for (let i = 0; i < arguments.length; i++) {
|
|
14562
|
+
const v = arguments[i];
|
|
14563
|
+
if (typeof v === "string" && v.trim()) return v.trim();
|
|
14564
|
+
}
|
|
14565
|
+
return undefined;
|
|
14566
|
+
}
|
|
14567
|
+
function clamp(s, m) {
|
|
14568
|
+
return typeof s === "string" ? s.slice(0, m) : undefined;
|
|
14569
|
+
}
|
|
14570
|
+
async function readStdin() {
|
|
14571
|
+
try {
|
|
14572
|
+
const c = [];
|
|
14573
|
+
for await (const ch of process.stdin) c.push(Buffer.from(ch));
|
|
14574
|
+
return Buffer.concat(c).toString("utf8");
|
|
14575
|
+
} catch (e) {
|
|
14576
|
+
return "";
|
|
14577
|
+
}
|
|
14578
|
+
}
|
|
14579
|
+
function jsonl(raw) {
|
|
14580
|
+
const out = [];
|
|
14581
|
+
if (typeof raw !== "string") return out;
|
|
14582
|
+
for (const line of raw.split(String.fromCharCode(10))) {
|
|
14583
|
+
const t = line.trim();
|
|
14584
|
+
if (!t) continue;
|
|
14585
|
+
try { out.push(JSON.parse(t)); } catch (e) {}
|
|
14586
|
+
}
|
|
14587
|
+
return out;
|
|
14588
|
+
}
|
|
14589
|
+
function blocks(entry) {
|
|
14590
|
+
const m = entry && (entry.message || entry);
|
|
14591
|
+
const c = m && m.content;
|
|
14592
|
+
return Array.isArray(c) ? c : [];
|
|
14593
|
+
}
|
|
14594
|
+
function derive(entries, max) {
|
|
14595
|
+
const errs = new Map();
|
|
14596
|
+
for (const e of entries) for (const b of blocks(e)) {
|
|
14597
|
+
if (b && b.type === "tool_result" && b.tool_use_id) errs.set(b.tool_use_id, !!b.is_error);
|
|
14598
|
+
}
|
|
14599
|
+
const steps = [];
|
|
14600
|
+
for (const e of entries) for (const b of blocks(e)) {
|
|
14601
|
+
if (b && b.type === "tool_use") steps.push({ id: b.id, name: typeof b.name === "string" ? b.name : "tool" });
|
|
14602
|
+
}
|
|
14603
|
+
const nodes = [{ id: "session", type: "task", title: "Claude Code session", status: "completed", requires_evidence: false }];
|
|
14604
|
+
const capped = steps.slice(-(max - 1));
|
|
14605
|
+
for (let i = 0; i < capped.length; i++) {
|
|
14606
|
+
const s = capped[i];
|
|
14607
|
+
nodes.push({ id: "step-" + (i + 1), type: "step", title: clamp(s.name, 500), status: errs.get(s.id) === true ? "failed" : "completed", requires_evidence: false });
|
|
14608
|
+
}
|
|
14609
|
+
return nodes;
|
|
14610
|
+
}
|
|
14611
|
+
function authHeaders(env) {
|
|
14612
|
+
if (env.ORGX_CLIENT_KEY) return { Authorization: "Bearer " + env.ORGX_CLIENT_KEY };
|
|
14613
|
+
if (env.ORGX_API_KEY) {
|
|
14614
|
+
const h = { Authorization: "Bearer " + env.ORGX_API_KEY };
|
|
13706
14615
|
if (env.ORGX_USER_ID) h["X-Orgx-User-Id"] = env.ORGX_USER_ID;
|
|
13707
14616
|
return h;
|
|
13708
14617
|
}
|
|
@@ -13713,7 +14622,7 @@ function authHeaders(env) {
|
|
|
13713
14622
|
try {
|
|
13714
14623
|
const args = parseArgs(process.argv.slice(2));
|
|
13715
14624
|
const env = process.env;
|
|
13716
|
-
if (!
|
|
14625
|
+
if (!truthy(env.ORGX_EMIT_EXECUTION_GRAPH)) return;
|
|
13717
14626
|
const initiative = pick(env.ORGX_INITIATIVE_ID, args.initiative);
|
|
13718
14627
|
if (!initiative) return;
|
|
13719
14628
|
const auth = authHeaders(env);
|
|
@@ -13745,7 +14654,7 @@ function authHeaders(env) {
|
|
|
13745
14654
|
const ctrl = new AbortController();
|
|
13746
14655
|
const timer = setTimeout(() => ctrl.abort(), parseInt(env.ORGX_EMIT_TIMEOUT_MS || "", 10) || 4000);
|
|
13747
14656
|
try {
|
|
13748
|
-
await fetch(base + "/api/
|
|
14657
|
+
await fetch(base + "/api/v1/live/execution-graph", {
|
|
13749
14658
|
method: "POST",
|
|
13750
14659
|
headers: Object.assign({ "Content-Type": "application/json" }, auth),
|
|
13751
14660
|
body: JSON.stringify(event),
|
|
@@ -13761,10 +14670,69 @@ function buildEmitHookCommand(params) {
|
|
|
13761
14670
|
return [
|
|
13762
14671
|
"node",
|
|
13763
14672
|
JSON.stringify(params.emitHookScriptPath),
|
|
13764
|
-
"--enabled=true",
|
|
13765
14673
|
`--source_client=${params.sourceClient}`
|
|
13766
14674
|
].join(" ");
|
|
13767
14675
|
}
|
|
14676
|
+
function buildSummaryHookCommand(params) {
|
|
14677
|
+
const terminal = params.event === "Stop" || params.event === "SessionEnd";
|
|
14678
|
+
return [
|
|
14679
|
+
"node",
|
|
14680
|
+
JSON.stringify(params.summaryHookScriptPath),
|
|
14681
|
+
`--event=${params.event}`,
|
|
14682
|
+
`--source_client=${params.sourceClient}`,
|
|
14683
|
+
`--state_dir=${JSON.stringify(params.sessionStateDir)}`,
|
|
14684
|
+
`--queue_dir=${JSON.stringify(params.sessionSummaryQueueDir)}`,
|
|
14685
|
+
...terminal && params.deliveryNodePath && params.deliveryCliPath ? [
|
|
14686
|
+
`--delivery_node=${JSON.stringify(params.deliveryNodePath)}`,
|
|
14687
|
+
`--delivery_cli=${JSON.stringify(params.deliveryCliPath)}`
|
|
14688
|
+
] : []
|
|
14689
|
+
].join(" ");
|
|
14690
|
+
}
|
|
14691
|
+
function ensureClaudeSummaryHook(rules, event, paths) {
|
|
14692
|
+
let changed = false;
|
|
14693
|
+
let universal = rules.find((entry) => isRecord(entry) && entry.matcher === "");
|
|
14694
|
+
if (!universal) {
|
|
14695
|
+
universal = { matcher: "", hooks: [] };
|
|
14696
|
+
rules.push(universal);
|
|
14697
|
+
changed = true;
|
|
14698
|
+
}
|
|
14699
|
+
const desiredCommand = buildSummaryHookCommand({
|
|
14700
|
+
deliveryCliPath: paths.deliveryCliPath,
|
|
14701
|
+
deliveryNodePath: paths.deliveryNodePath,
|
|
14702
|
+
event,
|
|
14703
|
+
sessionStateDir: paths.sessionStateDir,
|
|
14704
|
+
sessionSummaryQueueDir: paths.sessionSummaryQueueDir,
|
|
14705
|
+
sourceClient: "claude-code",
|
|
14706
|
+
summaryHookScriptPath: paths.summaryHookScriptPath
|
|
14707
|
+
});
|
|
14708
|
+
let desiredKept = false;
|
|
14709
|
+
for (const rule of rules) {
|
|
14710
|
+
if (!isRecord(rule)) continue;
|
|
14711
|
+
const hooks = Array.isArray(rule.hooks) ? rule.hooks : [];
|
|
14712
|
+
const next = [];
|
|
14713
|
+
for (const hook of hooks) {
|
|
14714
|
+
const isSummary = isRecord(hook) && typeof hook.command === "string" && hook.command.includes(SUMMARY_HOOK_MARKER);
|
|
14715
|
+
if (!isSummary) {
|
|
14716
|
+
next.push(hook);
|
|
14717
|
+
continue;
|
|
14718
|
+
}
|
|
14719
|
+
if (rule === universal && hook.type === "command" && hook.command === desiredCommand && !desiredKept) {
|
|
14720
|
+
next.push(hook);
|
|
14721
|
+
desiredKept = true;
|
|
14722
|
+
} else {
|
|
14723
|
+
changed = true;
|
|
14724
|
+
}
|
|
14725
|
+
}
|
|
14726
|
+
if (next.length !== hooks.length) rule.hooks = next;
|
|
14727
|
+
}
|
|
14728
|
+
if (!desiredKept) {
|
|
14729
|
+
const hooks = Array.isArray(universal.hooks) ? universal.hooks : [];
|
|
14730
|
+
hooks.push({ type: "command", command: desiredCommand });
|
|
14731
|
+
universal.hooks = hooks;
|
|
14732
|
+
changed = true;
|
|
14733
|
+
}
|
|
14734
|
+
return changed;
|
|
14735
|
+
}
|
|
13768
14736
|
function mergeCodexHooks(raw, paths) {
|
|
13769
14737
|
const value = parseJsonObject(raw);
|
|
13770
14738
|
const hooks = isRecord(value.hooks) ? value.hooks : {};
|
|
@@ -13780,10 +14748,31 @@ function mergeCodexHooks(raw, paths) {
|
|
|
13780
14748
|
const already = existing.some(
|
|
13781
14749
|
(entry) => isRecord(entry) && typeof entry.command === "string" && entry.command.includes(HOOK_MARKER)
|
|
13782
14750
|
);
|
|
13783
|
-
|
|
13784
|
-
|
|
14751
|
+
const next = already ? [...existing] : [...existing, { command }];
|
|
14752
|
+
if (!already) changed = true;
|
|
14753
|
+
const desiredSummaryCommand = buildSummaryHookCommand({
|
|
14754
|
+
deliveryCliPath: paths.deliveryCliPath,
|
|
14755
|
+
deliveryNodePath: paths.deliveryNodePath,
|
|
14756
|
+
event,
|
|
14757
|
+
sessionStateDir: paths.sessionStateDir,
|
|
14758
|
+
sessionSummaryQueueDir: paths.sessionSummaryQueueDir,
|
|
14759
|
+
sourceClient: "codex",
|
|
14760
|
+
summaryHookScriptPath: paths.summaryHookScriptPath
|
|
14761
|
+
});
|
|
14762
|
+
const summaryEntries = next.filter(
|
|
14763
|
+
(entry) => isRecord(entry) && typeof entry.command === "string" && entry.command.includes(SUMMARY_HOOK_MARKER)
|
|
14764
|
+
);
|
|
14765
|
+
const summaryCurrent = summaryEntries.length === 1 && isRecord(summaryEntries[0]) && summaryEntries[0].command === desiredSummaryCommand;
|
|
14766
|
+
if (!summaryCurrent) {
|
|
14767
|
+
const withoutStaleSummary = next.filter(
|
|
14768
|
+
(entry) => !(isRecord(entry) && typeof entry.command === "string" && entry.command.includes(SUMMARY_HOOK_MARKER))
|
|
14769
|
+
);
|
|
14770
|
+
withoutStaleSummary.push({ command: desiredSummaryCommand });
|
|
14771
|
+
hooks[event] = withoutStaleSummary;
|
|
13785
14772
|
changed = true;
|
|
14773
|
+
continue;
|
|
13786
14774
|
}
|
|
14775
|
+
hooks[event] = next;
|
|
13787
14776
|
}
|
|
13788
14777
|
value.hooks = hooks;
|
|
13789
14778
|
return { changed: changed || !raw, value };
|
|
@@ -13794,7 +14783,7 @@ function mergeClaudeHooks(raw, paths) {
|
|
|
13794
14783
|
let changed = false;
|
|
13795
14784
|
for (const event of CLAUDE_HOOK_EVENTS) {
|
|
13796
14785
|
const list = Array.isArray(hooksRoot[event]) ? hooksRoot[event] : [];
|
|
13797
|
-
const matcher = event === "PreToolUse" || event === "PostToolUse" ? "Bash|Write|Edit|MultiEdit|mcp__.*" : "";
|
|
14786
|
+
const matcher = event === "PreToolUse" || event === "PostToolUse" || event === "PostToolUseFailure" ? "Bash|Write|Edit|MultiEdit|mcp__.*" : "";
|
|
13798
14787
|
const command = buildHookCommand({
|
|
13799
14788
|
event,
|
|
13800
14789
|
hookScriptPath: paths.hookScriptPath,
|
|
@@ -13807,7 +14796,7 @@ function mergeClaudeHooks(raw, paths) {
|
|
|
13807
14796
|
list.push(rule);
|
|
13808
14797
|
changed = true;
|
|
13809
14798
|
}
|
|
13810
|
-
|
|
14799
|
+
let hooks = Array.isArray(rule.hooks) ? rule.hooks : [];
|
|
13811
14800
|
const already = hooks.some(
|
|
13812
14801
|
(entry) => isRecord(entry) && entry.type === "command" && typeof entry.command === "string" && entry.command.includes(HOOK_MARKER)
|
|
13813
14802
|
);
|
|
@@ -13816,6 +14805,10 @@ function mergeClaudeHooks(raw, paths) {
|
|
|
13816
14805
|
rule.hooks = hooks;
|
|
13817
14806
|
changed = true;
|
|
13818
14807
|
}
|
|
14808
|
+
if (ensureClaudeSummaryHook(list, event, paths)) {
|
|
14809
|
+
changed = true;
|
|
14810
|
+
}
|
|
14811
|
+
hooks = Array.isArray(rule.hooks) ? rule.hooks : [];
|
|
13819
14812
|
if (event === "Stop") {
|
|
13820
14813
|
const emitCommand = buildEmitHookCommand({
|
|
13821
14814
|
emitHookScriptPath: paths.emitHookScriptPath,
|
|
@@ -13840,122 +14833,812 @@ function ensureCodexHooksFeature(raw) {
|
|
|
13840
14833
|
if (codexHooksEnabled(current)) {
|
|
13841
14834
|
return { changed: false, value: current };
|
|
13842
14835
|
}
|
|
13843
|
-
if (/^\s*codex_hooks\s*=\s*false\s*$/m.test(current)) {
|
|
14836
|
+
if (/^\s*codex_hooks\s*=\s*false\s*$/m.test(current)) {
|
|
14837
|
+
return {
|
|
14838
|
+
changed: true,
|
|
14839
|
+
value: current.replace(/^\s*codex_hooks\s*=\s*false\s*$/m, "codex_hooks = true")
|
|
14840
|
+
};
|
|
14841
|
+
}
|
|
14842
|
+
if (/^\s*\[features\]\s*$/m.test(current)) {
|
|
14843
|
+
return {
|
|
14844
|
+
changed: true,
|
|
14845
|
+
value: current.replace(/^(\s*\[features\]\s*)$/m, "$1\ncodex_hooks = true")
|
|
14846
|
+
};
|
|
14847
|
+
}
|
|
14848
|
+
const suffix = current.trimEnd().length > 0 ? "\n\n" : "";
|
|
14849
|
+
return {
|
|
14850
|
+
changed: true,
|
|
14851
|
+
value: `${current.trimEnd()}${suffix}[features]
|
|
14852
|
+
codex_hooks = true
|
|
14853
|
+
`
|
|
14854
|
+
};
|
|
14855
|
+
}
|
|
14856
|
+
function inspectRuntimeHooks(options = {}) {
|
|
14857
|
+
const paths = defaultPaths(options);
|
|
14858
|
+
const codexConfigRaw = readTextIfExists(paths.codexConfigPath);
|
|
14859
|
+
const codexHooksRaw = readTextIfExists(paths.codexHooksPath);
|
|
14860
|
+
const claudeSettingsRaw = readTextIfExists(paths.claudeSettingsPath);
|
|
14861
|
+
const hasAutomaticDelivery = (raw) => Boolean(
|
|
14862
|
+
raw && raw.includes("--delivery_node=") && raw.includes("--delivery_cli=") && raw.includes(paths.deliveryNodePath) && raw.includes(paths.deliveryCliPath) && existsSync9(paths.deliveryNodePath) && existsSync9(paths.deliveryCliPath)
|
|
14863
|
+
);
|
|
14864
|
+
return {
|
|
14865
|
+
paths,
|
|
14866
|
+
installed: {
|
|
14867
|
+
claudeCode: hasOrgxHook(claudeSettingsRaw),
|
|
14868
|
+
codex: hasOrgxHook(codexHooksRaw),
|
|
14869
|
+
hookScript: existsSync9(paths.hookScriptPath),
|
|
14870
|
+
emitHookScript: existsSync9(paths.emitHookScriptPath),
|
|
14871
|
+
summaryHookScript: existsSync9(paths.summaryHookScriptPath),
|
|
14872
|
+
automaticDelivery: hasAutomaticDelivery(codexHooksRaw) || hasAutomaticDelivery(claudeSettingsRaw)
|
|
14873
|
+
},
|
|
14874
|
+
codex: {
|
|
14875
|
+
configExists: Boolean(codexConfigRaw),
|
|
14876
|
+
hooksEnabled: codexHooksEnabled(codexConfigRaw),
|
|
14877
|
+
hasNotify: codexHasNotify(codexConfigRaw),
|
|
14878
|
+
notifyPreserved: !codexHasNotify(codexConfigRaw) || !hasOrgxHook(codexConfigRaw)
|
|
14879
|
+
},
|
|
14880
|
+
outboxEvents: countJsonlLines(paths.outboxPath),
|
|
14881
|
+
sessionSummaryCaptures: countSessionSummaryCaptures(paths.sessionSummaryQueueDir)
|
|
14882
|
+
};
|
|
14883
|
+
}
|
|
14884
|
+
function installRuntimeHooks(targets, options = {}) {
|
|
14885
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
14886
|
+
const paths = defaultPaths(options);
|
|
14887
|
+
const backups = [];
|
|
14888
|
+
const changed = {
|
|
14889
|
+
claudeCode: false,
|
|
14890
|
+
codex: false,
|
|
14891
|
+
codexConfig: false,
|
|
14892
|
+
hookScript: false,
|
|
14893
|
+
emitHookScript: false,
|
|
14894
|
+
summaryHookScript: false
|
|
14895
|
+
};
|
|
14896
|
+
mkdirSync4(dirname5(paths.hookScriptPath), { recursive: true, mode: 448 });
|
|
14897
|
+
const scriptContent = buildRuntimeHookScriptContent();
|
|
14898
|
+
if (readTextIfExists(paths.hookScriptPath) !== scriptContent) {
|
|
14899
|
+
const backup = backupExisting(paths.hookScriptPath, now);
|
|
14900
|
+
if (backup) backups.push(backup);
|
|
14901
|
+
writeTextFile(paths.hookScriptPath, scriptContent, { mode: 448 });
|
|
14902
|
+
changed.hookScript = true;
|
|
14903
|
+
}
|
|
14904
|
+
mkdirSync4(dirname5(paths.emitHookScriptPath), { recursive: true, mode: 448 });
|
|
14905
|
+
const emitScriptContent = buildExecutionGraphEmitScriptContent();
|
|
14906
|
+
if (readTextIfExists(paths.emitHookScriptPath) !== emitScriptContent) {
|
|
14907
|
+
const backup = backupExisting(paths.emitHookScriptPath, now);
|
|
14908
|
+
if (backup) backups.push(backup);
|
|
14909
|
+
writeTextFile(paths.emitHookScriptPath, emitScriptContent, { mode: 448 });
|
|
14910
|
+
changed.emitHookScript = true;
|
|
14911
|
+
}
|
|
14912
|
+
mkdirSync4(dirname5(paths.summaryHookScriptPath), { recursive: true, mode: 448 });
|
|
14913
|
+
const summaryScriptContent = buildSessionSummaryHookScriptContent();
|
|
14914
|
+
if (readTextIfExists(paths.summaryHookScriptPath) !== summaryScriptContent) {
|
|
14915
|
+
const backup = backupExisting(paths.summaryHookScriptPath, now);
|
|
14916
|
+
if (backup) backups.push(backup);
|
|
14917
|
+
writeTextFile(paths.summaryHookScriptPath, summaryScriptContent, { mode: 448 });
|
|
14918
|
+
changed.summaryHookScript = true;
|
|
14919
|
+
}
|
|
14920
|
+
mkdirSync4(paths.sessionStateDir, { recursive: true, mode: 448 });
|
|
14921
|
+
mkdirSync4(paths.sessionSummaryQueueDir, { recursive: true, mode: 448 });
|
|
14922
|
+
if (targets.includes("codex")) {
|
|
14923
|
+
const rawConfig = readTextIfExists(paths.codexConfigPath);
|
|
14924
|
+
const nextConfig = ensureCodexHooksFeature(rawConfig);
|
|
14925
|
+
if (nextConfig.changed) {
|
|
14926
|
+
const backup = backupExisting(paths.codexConfigPath, now);
|
|
14927
|
+
if (backup) backups.push(backup);
|
|
14928
|
+
writeTextFile(paths.codexConfigPath, nextConfig.value, { mode: 384 });
|
|
14929
|
+
changed.codexConfig = true;
|
|
14930
|
+
}
|
|
14931
|
+
const rawHooks = readTextIfExists(paths.codexHooksPath);
|
|
14932
|
+
const nextHooks = mergeCodexHooks(rawHooks, paths);
|
|
14933
|
+
if (nextHooks.changed) {
|
|
14934
|
+
const backup = backupExisting(paths.codexHooksPath, now);
|
|
14935
|
+
if (backup) backups.push(backup);
|
|
14936
|
+
writeJsonFile(paths.codexHooksPath, nextHooks.value, { mode: 384 });
|
|
14937
|
+
changed.codex = true;
|
|
14938
|
+
}
|
|
14939
|
+
}
|
|
14940
|
+
if (targets.includes("claude-code")) {
|
|
14941
|
+
const rawSettings = readTextIfExists(paths.claudeSettingsPath);
|
|
14942
|
+
const nextSettings = mergeClaudeHooks(rawSettings, paths);
|
|
14943
|
+
if (nextSettings.changed) {
|
|
14944
|
+
const backup = backupExisting(paths.claudeSettingsPath, now);
|
|
14945
|
+
if (backup) backups.push(backup);
|
|
14946
|
+
writeJsonFile(paths.claudeSettingsPath, nextSettings.value, { mode: 384 });
|
|
14947
|
+
changed.claudeCode = true;
|
|
14948
|
+
}
|
|
14949
|
+
}
|
|
14950
|
+
return {
|
|
14951
|
+
...inspectRuntimeHooks(options),
|
|
14952
|
+
changed,
|
|
14953
|
+
backups
|
|
14954
|
+
};
|
|
14955
|
+
}
|
|
14956
|
+
function parseRuntimeHookTargets(value) {
|
|
14957
|
+
if (!value?.trim()) return ["codex", "claude-code"];
|
|
14958
|
+
const requested = value.split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
|
|
14959
|
+
const expanded = requested.includes("all") ? ["codex", "claude-code"] : requested;
|
|
14960
|
+
const normalized = expanded.map((target) => {
|
|
14961
|
+
if (target === "claude" || target === "claude_code") return "claude-code";
|
|
14962
|
+
return target;
|
|
14963
|
+
});
|
|
14964
|
+
const invalid = normalized.filter((target) => target !== "codex" && target !== "claude-code");
|
|
14965
|
+
if (invalid.length > 0) {
|
|
14966
|
+
throw new Error(`Unsupported hook target: ${invalid.join(", ")}. Use codex, claude-code, or all.`);
|
|
14967
|
+
}
|
|
14968
|
+
return [...new Set(normalized)];
|
|
14969
|
+
}
|
|
14970
|
+
|
|
14971
|
+
// src/lib/session-summary-queue.ts
|
|
14972
|
+
import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
14973
|
+
import { join as join9 } from "path";
|
|
14974
|
+
|
|
14975
|
+
// src/lib/session-summary-backfill.ts
|
|
14976
|
+
import { createReadStream, renameSync as renameSync2, statSync as statSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
14977
|
+
import { basename as basename5 } from "path";
|
|
14978
|
+
import { createInterface } from "readline";
|
|
14979
|
+
|
|
14980
|
+
// src/lib/session-work-context.ts
|
|
14981
|
+
import { z } from "zod";
|
|
14982
|
+
var SESSION_WORK_CONTEXT_VERSION = "orgx-session-work-context/v1";
|
|
14983
|
+
var SESSION_WORK_CONTEXT_MAX_JSON_BYTES = 4 * 1024;
|
|
14984
|
+
var boundedText = (max) => z.string().trim().min(1).max(max);
|
|
14985
|
+
var currency = boundedText(12).regex(/^[A-Z][A-Z0-9_-]{1,11}$/);
|
|
14986
|
+
var externalReferenceSchema = z.object({
|
|
14987
|
+
system: boundedText(120),
|
|
14988
|
+
type: boundedText(120),
|
|
14989
|
+
id: boundedText(500),
|
|
14990
|
+
uri: boundedText(1e3).refine((value) => !/[\u0000-\u0020]/.test(value)).optional(),
|
|
14991
|
+
version: boundedText(120).optional()
|
|
14992
|
+
});
|
|
14993
|
+
var stringList = (max) => z.array(boundedText(max)).max(20).default([]).transform((values) => [
|
|
14994
|
+
...new Set(values)
|
|
14995
|
+
]);
|
|
14996
|
+
var referenceList = z.array(externalReferenceSchema).max(20).default([]).transform((refs) => {
|
|
14997
|
+
const unique = /* @__PURE__ */ new Map();
|
|
14998
|
+
for (const ref of refs) {
|
|
14999
|
+
unique.set(`${ref.system}\0${ref.type}\0${ref.id}`, ref);
|
|
15000
|
+
}
|
|
15001
|
+
return [...unique.values()].sort((left, right) => {
|
|
15002
|
+
const a = `${left.system}\0${left.type}\0${left.id}`;
|
|
15003
|
+
const b = `${right.system}\0${right.type}\0${right.id}`;
|
|
15004
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
15005
|
+
});
|
|
15006
|
+
});
|
|
15007
|
+
var authoritySchema = z.object({
|
|
15008
|
+
mode: z.enum([
|
|
15009
|
+
"explicit",
|
|
15010
|
+
"delegated",
|
|
15011
|
+
"inherited",
|
|
15012
|
+
"policy",
|
|
15013
|
+
"none",
|
|
15014
|
+
"unknown"
|
|
15015
|
+
]),
|
|
15016
|
+
status: z.enum(["granted", "restricted", "denied", "expired", "unknown"]),
|
|
15017
|
+
scope: z.object({
|
|
15018
|
+
actions: stringList(120),
|
|
15019
|
+
resources: referenceList,
|
|
15020
|
+
systems: stringList(120),
|
|
15021
|
+
spend_limit: z.object({
|
|
15022
|
+
currency,
|
|
15023
|
+
amount: z.number().finite().min(0).max(1e5)
|
|
15024
|
+
}).optional()
|
|
15025
|
+
}),
|
|
15026
|
+
authorization_ref: externalReferenceSchema.optional(),
|
|
15027
|
+
constraints: stringList(1e3),
|
|
15028
|
+
valid_from: z.string().datetime({ offset: true }).optional(),
|
|
15029
|
+
valid_until: z.string().datetime({ offset: true }).optional()
|
|
15030
|
+
}).refine(
|
|
15031
|
+
(value) => !value.valid_from || !value.valid_until || Date.parse(value.valid_until) >= Date.parse(value.valid_from),
|
|
15032
|
+
{ message: "valid_until must not precede valid_from" }
|
|
15033
|
+
);
|
|
15034
|
+
var sessionWorkContextInputSchema = z.object({
|
|
15035
|
+
schema_version: z.literal(SESSION_WORK_CONTEXT_VERSION),
|
|
15036
|
+
intent: z.object({
|
|
15037
|
+
summary: boundedText(2e3),
|
|
15038
|
+
objective: boundedText(4e3).optional(),
|
|
15039
|
+
acceptance_criteria: stringList(1e3),
|
|
15040
|
+
constraints: stringList(1e3),
|
|
15041
|
+
request_ref: externalReferenceSchema.optional()
|
|
15042
|
+
}),
|
|
15043
|
+
authority: authoritySchema,
|
|
15044
|
+
cost: z.object({
|
|
15045
|
+
currency,
|
|
15046
|
+
total: z.number().finite().min(0).max(1e5),
|
|
15047
|
+
estimated: z.boolean().default(true),
|
|
15048
|
+
source: boundedText(120).optional()
|
|
15049
|
+
}),
|
|
15050
|
+
artifact_refs: referenceList,
|
|
15051
|
+
evidence_refs: referenceList
|
|
15052
|
+
});
|
|
15053
|
+
function normalizeSessionWorkContext(value) {
|
|
15054
|
+
const parsed = sessionWorkContextInputSchema.safeParse(value);
|
|
15055
|
+
return parsed.success ? { ...parsed.data, provenance: "producer_asserted" } : null;
|
|
15056
|
+
}
|
|
15057
|
+
|
|
15058
|
+
// src/lib/session-summary-backfill.ts
|
|
15059
|
+
var SESSION_EXECUTION_OBSERVATION_VERSION = "orgx-session-execution-observation/v1";
|
|
15060
|
+
var SESSION_EXECUTION_MAX_ACTIONS = 32;
|
|
15061
|
+
var SESSION_EXECUTION_UNAVAILABLE_FIELDS = /* @__PURE__ */ new Set([
|
|
15062
|
+
"intent",
|
|
15063
|
+
"authority_decision",
|
|
15064
|
+
"artifact_refs",
|
|
15065
|
+
"cost",
|
|
15066
|
+
"verification"
|
|
15067
|
+
]);
|
|
15068
|
+
var SESSION_EXECUTION_PERMISSION_MODES = /* @__PURE__ */ new Set([
|
|
15069
|
+
"default",
|
|
15070
|
+
"acceptEdits",
|
|
15071
|
+
"plan",
|
|
15072
|
+
"dontAsk",
|
|
15073
|
+
"bypassPermissions"
|
|
15074
|
+
]);
|
|
15075
|
+
function isRecord3(value) {
|
|
15076
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
15077
|
+
}
|
|
15078
|
+
function boundedString(value, maxLength) {
|
|
15079
|
+
if (typeof value !== "string") return void 0;
|
|
15080
|
+
const trimmed = value.trim();
|
|
15081
|
+
return trimmed ? trimmed.slice(0, maxLength) : void 0;
|
|
15082
|
+
}
|
|
15083
|
+
function boundedCount(value) {
|
|
15084
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? Math.min(value, 1e7) : 0;
|
|
15085
|
+
}
|
|
15086
|
+
function validTimestamp(value) {
|
|
15087
|
+
const text2 = boundedString(value, 100);
|
|
15088
|
+
return text2 && !Number.isNaN(Date.parse(text2)) ? text2 : void 0;
|
|
15089
|
+
}
|
|
15090
|
+
function normalizeObservedAction(value) {
|
|
15091
|
+
if (!isRecord3(value)) return null;
|
|
15092
|
+
const id = boundedString(value.id, 80);
|
|
15093
|
+
const toolName = boundedString(value.tool_name, 48);
|
|
15094
|
+
const status = boundedString(value.status, 40);
|
|
15095
|
+
if (!id || !toolName || !status || !["running", "succeeded", "failed", "completed_unknown"].includes(status)) {
|
|
15096
|
+
return null;
|
|
15097
|
+
}
|
|
15098
|
+
const startedAt = validTimestamp(value.started_at);
|
|
15099
|
+
const completedAt = validTimestamp(value.completed_at);
|
|
15100
|
+
const durationMs = typeof value.duration_ms === "number" && Number.isInteger(value.duration_ms) && value.duration_ms >= 0 ? Math.min(value.duration_ms, 864e5) : void 0;
|
|
15101
|
+
const turnId = boundedString(value.turn_id, 120);
|
|
15102
|
+
return {
|
|
15103
|
+
id,
|
|
15104
|
+
type: "tool.invoke",
|
|
15105
|
+
tool_name: toolName,
|
|
15106
|
+
status,
|
|
15107
|
+
provenance: "runtime_observed",
|
|
15108
|
+
...startedAt ? { started_at: startedAt } : {},
|
|
15109
|
+
...completedAt ? { completed_at: completedAt } : {},
|
|
15110
|
+
...durationMs !== void 0 ? { duration_ms: durationMs } : {},
|
|
15111
|
+
...turnId ? { turn_id: turnId } : {}
|
|
15112
|
+
};
|
|
15113
|
+
}
|
|
15114
|
+
function normalizeSessionExecutionObservation(value) {
|
|
15115
|
+
if (!isRecord3(value) || value.schema_version !== SESSION_EXECUTION_OBSERVATION_VERSION) {
|
|
15116
|
+
return null;
|
|
15117
|
+
}
|
|
15118
|
+
const boundary = boundedString(value.boundary, 40);
|
|
15119
|
+
if (!boundary || ![
|
|
15120
|
+
"turn_boundary",
|
|
15121
|
+
"session_end",
|
|
15122
|
+
"historical_backfill",
|
|
15123
|
+
"legacy_unspecified"
|
|
15124
|
+
].includes(boundary)) {
|
|
15125
|
+
return null;
|
|
15126
|
+
}
|
|
15127
|
+
const actions = (Array.isArray(value.actions) ? value.actions : []).slice(0, SESSION_EXECUTION_MAX_ACTIONS).map(normalizeObservedAction).filter((action) => action !== null);
|
|
15128
|
+
const unavailableFields = (Array.isArray(value.unavailable_fields) ? value.unavailable_fields : []).filter(
|
|
15129
|
+
(field) => typeof field === "string" && SESSION_EXECUTION_UNAVAILABLE_FIELDS.has(field)
|
|
15130
|
+
);
|
|
15131
|
+
const permissionModes = (Array.isArray(value.permission_modes_observed) ? value.permission_modes_observed : []).filter(
|
|
15132
|
+
(mode) => typeof mode === "string" && SESSION_EXECUTION_PERMISSION_MODES.has(mode)
|
|
15133
|
+
);
|
|
15134
|
+
return {
|
|
15135
|
+
schema_version: SESSION_EXECUTION_OBSERVATION_VERSION,
|
|
15136
|
+
boundary,
|
|
15137
|
+
terminal_observed: value.terminal_observed === true,
|
|
15138
|
+
actions,
|
|
15139
|
+
actions_completed_observed: boundedCount(value.actions_completed_observed),
|
|
15140
|
+
actions_omitted: boundedCount(value.actions_omitted),
|
|
15141
|
+
start_times_omitted: boundedCount(value.start_times_omitted),
|
|
15142
|
+
pending_actions: boundedCount(value.pending_actions),
|
|
15143
|
+
permission_requests_observed: boundedCount(value.permission_requests_observed),
|
|
15144
|
+
permission_modes_observed: [...new Set(permissionModes)].slice(0, 8),
|
|
15145
|
+
action_capture_complete: value.action_capture_complete === true,
|
|
15146
|
+
unavailable_fields: [...new Set(unavailableFields)]
|
|
15147
|
+
};
|
|
15148
|
+
}
|
|
15149
|
+
function buildMinimalCloudSummary(summary, metadata) {
|
|
15150
|
+
const execution = normalizeSessionExecutionObservation(summary.metadata?.execution);
|
|
15151
|
+
const workContext = normalizeSessionWorkContext(summary.metadata?.work_context);
|
|
15152
|
+
const { cwd: _localOnlyCwd, metadata: _localOnlyMetadata, ...minimal } = summary;
|
|
15153
|
+
return {
|
|
15154
|
+
...minimal,
|
|
15155
|
+
metadata: {
|
|
15156
|
+
capture: {
|
|
15157
|
+
...metadata.captureId ? { id: metadata.captureId } : {},
|
|
15158
|
+
kind: metadata.captureKind,
|
|
15159
|
+
...metadata.queuedAt ? { queued_at: metadata.queuedAt } : {}
|
|
15160
|
+
},
|
|
15161
|
+
privacy: {
|
|
15162
|
+
profile: "minimal",
|
|
15163
|
+
raw_content_included: false,
|
|
15164
|
+
cwd_included: false
|
|
15165
|
+
},
|
|
15166
|
+
...execution ? { execution } : {},
|
|
15167
|
+
...workContext ? { work_context: workContext } : {}
|
|
15168
|
+
}
|
|
15169
|
+
};
|
|
15170
|
+
}
|
|
15171
|
+
var MAX_REPORTED_TOOLS = 12;
|
|
15172
|
+
var MAX_TOOL_NAME = 48;
|
|
15173
|
+
function repoOf(cwd) {
|
|
15174
|
+
if (!cwd) return "unknown";
|
|
15175
|
+
const marker = "/Code/";
|
|
15176
|
+
const at = cwd.indexOf(marker);
|
|
15177
|
+
if (at >= 0) {
|
|
15178
|
+
const rest = cwd.slice(at + marker.length);
|
|
15179
|
+
const slash = rest.indexOf("/");
|
|
15180
|
+
const name = slash < 0 ? rest : rest.slice(0, slash);
|
|
15181
|
+
if (name) return name;
|
|
15182
|
+
}
|
|
15183
|
+
return basename5(cwd) || "unknown";
|
|
15184
|
+
}
|
|
15185
|
+
function normalizeToolName(name) {
|
|
15186
|
+
const mcp = /^mcp__[0-9a-fA-F-]{16,}__(.+)$/.exec(name);
|
|
15187
|
+
const base = mcp ? `mcp__${mcp[1]}` : name;
|
|
15188
|
+
return base.length > MAX_TOOL_NAME ? base.slice(0, MAX_TOOL_NAME) : base;
|
|
15189
|
+
}
|
|
15190
|
+
function boundTools(tools) {
|
|
15191
|
+
const merged = {};
|
|
15192
|
+
for (const [name, count] of Object.entries(tools)) {
|
|
15193
|
+
const key = normalizeToolName(name);
|
|
15194
|
+
merged[key] = (merged[key] ?? 0) + count;
|
|
15195
|
+
}
|
|
15196
|
+
const entries = Object.entries(merged).sort((a, b) => b[1] - a[1]);
|
|
15197
|
+
if (entries.length <= MAX_REPORTED_TOOLS) return Object.fromEntries(entries);
|
|
15198
|
+
const out = Object.fromEntries(entries.slice(0, MAX_REPORTED_TOOLS));
|
|
15199
|
+
const tail = entries.slice(MAX_REPORTED_TOOLS);
|
|
15200
|
+
out.__other__ = tail.reduce((sum, [, count]) => sum + count, 0);
|
|
15201
|
+
out.__other_tools__ = tail.length;
|
|
15202
|
+
return out;
|
|
15203
|
+
}
|
|
15204
|
+
function toSummary(state) {
|
|
15205
|
+
const { firstTs, lastTs } = state;
|
|
15206
|
+
return {
|
|
15207
|
+
schema_version: SESSION_SUMMARY_SCHEMA_VERSION,
|
|
15208
|
+
source: "orgx_session_summary",
|
|
15209
|
+
session_id: state.session_id,
|
|
15210
|
+
source_client: state.source_client,
|
|
15211
|
+
repo: state.repo,
|
|
15212
|
+
cwd: state.cwd,
|
|
15213
|
+
day: firstTs === null ? "unknown" : new Date(firstTs).toISOString().slice(0, 10),
|
|
15214
|
+
started_at: firstTs === null ? null : new Date(firstTs).toISOString(),
|
|
15215
|
+
ended_at: lastTs === null ? null : new Date(lastTs).toISOString(),
|
|
15216
|
+
duration_min: firstTs !== null && lastTs !== null ? Math.round((lastTs - firstTs) / 6e4) : 0,
|
|
15217
|
+
events: state.events,
|
|
15218
|
+
prompts: state.prompts,
|
|
15219
|
+
tool_calls: state.tool_calls,
|
|
15220
|
+
tools: boundTools(state.tools),
|
|
15221
|
+
edits: state.edits,
|
|
15222
|
+
bash: state.bash
|
|
15223
|
+
};
|
|
15224
|
+
}
|
|
15225
|
+
function percentile(sorted, p) {
|
|
15226
|
+
if (sorted.length === 0) return 0;
|
|
15227
|
+
return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * p))] ?? 0;
|
|
15228
|
+
}
|
|
15229
|
+
async function distillSpool(spoolPath) {
|
|
15230
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
15231
|
+
let lines = 0;
|
|
15232
|
+
let badLines = 0;
|
|
15233
|
+
const reader = createInterface({
|
|
15234
|
+
input: createReadStream(spoolPath),
|
|
15235
|
+
crlfDelay: Infinity
|
|
15236
|
+
});
|
|
15237
|
+
for await (const line of reader) {
|
|
15238
|
+
lines += 1;
|
|
15239
|
+
if (!line.trim()) continue;
|
|
15240
|
+
let record;
|
|
15241
|
+
try {
|
|
15242
|
+
record = JSON.parse(line);
|
|
15243
|
+
} catch {
|
|
15244
|
+
badLines += 1;
|
|
15245
|
+
continue;
|
|
15246
|
+
}
|
|
15247
|
+
const sessionId = typeof record.session_id === "string" ? record.session_id : "unknown";
|
|
15248
|
+
const cwd = typeof record.cwd === "string" ? record.cwd : null;
|
|
15249
|
+
const timestamp = typeof record.timestamp === "string" ? Date.parse(record.timestamp) : NaN;
|
|
15250
|
+
const ts = Number.isFinite(timestamp) ? timestamp : null;
|
|
15251
|
+
let state = sessions.get(sessionId);
|
|
15252
|
+
if (!state) {
|
|
15253
|
+
state = {
|
|
15254
|
+
session_id: sessionId,
|
|
15255
|
+
source_client: typeof record.source_client === "string" ? record.source_client : "unknown",
|
|
15256
|
+
repo: repoOf(cwd),
|
|
15257
|
+
cwd,
|
|
15258
|
+
firstTs: ts,
|
|
15259
|
+
lastTs: ts,
|
|
15260
|
+
events: 0,
|
|
15261
|
+
prompts: 0,
|
|
15262
|
+
tool_calls: 0,
|
|
15263
|
+
tools: {},
|
|
15264
|
+
edits: 0,
|
|
15265
|
+
bash: 0
|
|
15266
|
+
};
|
|
15267
|
+
sessions.set(sessionId, state);
|
|
15268
|
+
}
|
|
15269
|
+
state.events += 1;
|
|
15270
|
+
if (ts !== null) {
|
|
15271
|
+
if (state.firstTs === null || ts < state.firstTs) state.firstTs = ts;
|
|
15272
|
+
if (state.lastTs === null || ts > state.lastTs) state.lastTs = ts;
|
|
15273
|
+
}
|
|
15274
|
+
if (cwd && state.cwd === null) {
|
|
15275
|
+
state.cwd = cwd;
|
|
15276
|
+
state.repo = repoOf(cwd);
|
|
15277
|
+
}
|
|
15278
|
+
if (typeof record.source_client === "string" && state.source_client === "unknown") {
|
|
15279
|
+
state.source_client = record.source_client;
|
|
15280
|
+
}
|
|
15281
|
+
const event = typeof record.event === "string" ? record.event : "unknown";
|
|
15282
|
+
if (event === "UserPromptSubmit" || event.includes("user_prompt")) state.prompts += 1;
|
|
15283
|
+
const summary = record.summary;
|
|
15284
|
+
const tool = summary && typeof summary.tool_name === "string" ? summary.tool_name : null;
|
|
15285
|
+
if (tool && (event === "PostToolUse" || event.includes("post_tool_use"))) {
|
|
15286
|
+
state.tool_calls += 1;
|
|
15287
|
+
state.tools[tool] = (state.tools[tool] ?? 0) + 1;
|
|
15288
|
+
if (tool === "Write" || tool === "Edit" || tool === "NotebookEdit") state.edits += 1;
|
|
15289
|
+
if (tool === "Bash") state.bash += 1;
|
|
15290
|
+
}
|
|
15291
|
+
}
|
|
15292
|
+
const summaries = [...sessions.values()].map(toSummary).sort((a, b) => (a.started_at ?? "").localeCompare(b.started_at ?? ""));
|
|
15293
|
+
const sizes = summaries.map((summary) => Buffer.byteLength(JSON.stringify(summary), "utf8")).sort((a, b) => a - b);
|
|
15294
|
+
const total = sizes.reduce((sum, value) => sum + value, 0);
|
|
15295
|
+
let spoolBytes = 0;
|
|
15296
|
+
try {
|
|
15297
|
+
spoolBytes = statSync5(spoolPath).size;
|
|
15298
|
+
} catch {
|
|
15299
|
+
spoolBytes = 0;
|
|
15300
|
+
}
|
|
15301
|
+
return {
|
|
15302
|
+
spoolPath,
|
|
15303
|
+
spoolBytes,
|
|
15304
|
+
lines,
|
|
15305
|
+
badLines,
|
|
15306
|
+
summaries,
|
|
15307
|
+
bytes: {
|
|
15308
|
+
total,
|
|
15309
|
+
max: sizes.at(-1) ?? 0,
|
|
15310
|
+
p50: percentile(sizes, 0.5),
|
|
15311
|
+
p90: percentile(sizes, 0.9),
|
|
15312
|
+
mean: sizes.length > 0 ? Math.round(total / sizes.length) : 0
|
|
15313
|
+
}
|
|
15314
|
+
};
|
|
15315
|
+
}
|
|
15316
|
+
async function postSummaries(summaries, post, options = {}) {
|
|
15317
|
+
const maxConsecutiveFailures = options.maxConsecutiveFailures ?? 5;
|
|
15318
|
+
const result = { attempted: 0, posted: 0, failed: 0 };
|
|
15319
|
+
let consecutiveFailures = 0;
|
|
15320
|
+
for (const summary of summaries) {
|
|
15321
|
+
result.attempted += 1;
|
|
15322
|
+
const response = await post(summary);
|
|
15323
|
+
if (response.ok) {
|
|
15324
|
+
result.posted += 1;
|
|
15325
|
+
consecutiveFailures = 0;
|
|
15326
|
+
continue;
|
|
15327
|
+
}
|
|
15328
|
+
result.failed += 1;
|
|
15329
|
+
consecutiveFailures += 1;
|
|
15330
|
+
result.firstError ??= `HTTP ${response.status} for session ${summary.session_id}`;
|
|
15331
|
+
if (consecutiveFailures >= maxConsecutiveFailures) break;
|
|
15332
|
+
}
|
|
15333
|
+
return result;
|
|
15334
|
+
}
|
|
15335
|
+
function truncateSpool(spoolPath, post, options = {}) {
|
|
15336
|
+
if (!options.force && (post.failed > 0 || post.attempted === 0)) {
|
|
15337
|
+
return {
|
|
15338
|
+
truncated: false,
|
|
15339
|
+
reason: post.attempted === 0 ? "nothing was posted, so there is nothing safe to truncate" : `${post.failed} session(s) failed to post`,
|
|
15340
|
+
bytesReleased: 0
|
|
15341
|
+
};
|
|
15342
|
+
}
|
|
15343
|
+
let bytesReleased = 0;
|
|
15344
|
+
try {
|
|
15345
|
+
bytesReleased = statSync5(spoolPath).size;
|
|
15346
|
+
} catch {
|
|
15347
|
+
return { truncated: false, reason: "spool not found", bytesReleased: 0 };
|
|
15348
|
+
}
|
|
15349
|
+
const stamp = (options.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
15350
|
+
const archivePath = `${spoolPath}.backfilled-${stamp}`;
|
|
15351
|
+
renameSync2(spoolPath, archivePath);
|
|
15352
|
+
writeFileSync4(spoolPath, "", { encoding: "utf8", mode: 384 });
|
|
15353
|
+
return { truncated: true, archivePath, bytesReleased };
|
|
15354
|
+
}
|
|
15355
|
+
|
|
15356
|
+
// src/lib/session-summary-queue.ts
|
|
15357
|
+
var SESSION_SUMMARY_CAPTURE_VERSION = "orgx-session-summary-capture/v1";
|
|
15358
|
+
var SESSION_SUMMARY_CAPTURE_FILE_SUFFIX = ".capture.json";
|
|
15359
|
+
var SESSION_SUMMARY_CAPTURE_MAX_BYTES = 16 * 1024;
|
|
15360
|
+
function sessionSummaryCaptureFiles(queueDir) {
|
|
15361
|
+
if (!existsSync10(queueDir)) return [];
|
|
15362
|
+
try {
|
|
15363
|
+
return readdirSync7(queueDir).filter((name) => name.endsWith(SESSION_SUMMARY_CAPTURE_FILE_SUFFIX)).sort().map((name) => join9(queueDir, name));
|
|
15364
|
+
} catch {
|
|
15365
|
+
return [];
|
|
15366
|
+
}
|
|
15367
|
+
}
|
|
15368
|
+
function isRecord4(value) {
|
|
15369
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
15370
|
+
}
|
|
15371
|
+
function isSessionSummary(value) {
|
|
15372
|
+
if (!isRecord4(value)) return false;
|
|
15373
|
+
return typeof value.schema_version === "string" && typeof value.session_id === "string" && typeof value.source_client === "string";
|
|
15374
|
+
}
|
|
15375
|
+
function workGraphFingerprint(sessionId) {
|
|
15376
|
+
let left = 2166136261;
|
|
15377
|
+
let right = 16777619;
|
|
15378
|
+
for (let index = 0; index < sessionId.length; index += 1) {
|
|
15379
|
+
left = Math.imul(left ^ sessionId.charCodeAt(index), 16777619) >>> 0;
|
|
15380
|
+
right = Math.imul(
|
|
15381
|
+
right ^ sessionId.charCodeAt(sessionId.length - 1 - index),
|
|
15382
|
+
2246822507
|
|
15383
|
+
) >>> 0;
|
|
15384
|
+
}
|
|
15385
|
+
const seed = `${left.toString(16).padStart(8, "0")}${right.toString(16).padStart(8, "0")}`;
|
|
15386
|
+
return `wgf_${(seed + seed).slice(0, 24)}`;
|
|
15387
|
+
}
|
|
15388
|
+
function parseSessionSummaryCapture(raw) {
|
|
15389
|
+
if (Buffer.byteLength(raw, "utf8") > SESSION_SUMMARY_CAPTURE_MAX_BYTES) return null;
|
|
15390
|
+
try {
|
|
15391
|
+
const value = JSON.parse(raw);
|
|
15392
|
+
if (!isRecord4(value) || value.schema_version !== SESSION_SUMMARY_CAPTURE_VERSION) return null;
|
|
15393
|
+
if (typeof value.capture_id !== "string" || typeof value.queued_at !== "string" || !isSessionSummary(value.session)) {
|
|
15394
|
+
return null;
|
|
15395
|
+
}
|
|
15396
|
+
if (value.initiative_id !== void 0 && typeof value.initiative_id !== "string") {
|
|
15397
|
+
return null;
|
|
15398
|
+
}
|
|
13844
15399
|
return {
|
|
13845
|
-
|
|
13846
|
-
|
|
15400
|
+
schema_version: SESSION_SUMMARY_CAPTURE_VERSION,
|
|
15401
|
+
capture_id: value.capture_id,
|
|
15402
|
+
// Captures created by wizard 0.1.56 before this field existed must remain
|
|
15403
|
+
// deliverable. Preserve the uncertainty instead of guessing that an old
|
|
15404
|
+
// snapshot represented a real SessionEnd.
|
|
15405
|
+
capture_kind: value.capture_kind === "turn_boundary" || value.capture_kind === "session_end" ? value.capture_kind : "legacy_unspecified",
|
|
15406
|
+
queued_at: value.queued_at,
|
|
15407
|
+
session: value.session,
|
|
15408
|
+
...typeof value.initiative_id === "string" ? { initiative_id: value.initiative_id } : {}
|
|
13847
15409
|
};
|
|
15410
|
+
} catch {
|
|
15411
|
+
return null;
|
|
13848
15412
|
}
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
|
|
13852
|
-
|
|
13853
|
-
|
|
15413
|
+
}
|
|
15414
|
+
function readSessionSummaryCapture(path) {
|
|
15415
|
+
try {
|
|
15416
|
+
return parseSessionSummaryCapture(readFileSync8(path, "utf8"));
|
|
15417
|
+
} catch {
|
|
15418
|
+
return null;
|
|
13854
15419
|
}
|
|
13855
|
-
|
|
15420
|
+
}
|
|
15421
|
+
function buildCaptureEndpoint(path, baseUrl) {
|
|
15422
|
+
return buildOrgxApiUrl(path.replace(/^\/api/, ""), baseUrl);
|
|
15423
|
+
}
|
|
15424
|
+
function buildWorkGraphFallback(capture) {
|
|
15425
|
+
const summary = buildMinimalCloudSummary(capture.session, {
|
|
15426
|
+
captureId: capture.capture_id,
|
|
15427
|
+
captureKind: capture.capture_kind,
|
|
15428
|
+
queuedAt: capture.queued_at
|
|
15429
|
+
});
|
|
13856
15430
|
return {
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
|
|
13860
|
-
|
|
15431
|
+
report: {
|
|
15432
|
+
schema_version: "2.0.0",
|
|
15433
|
+
work_graph_fingerprint: workGraphFingerprint(summary.session_id),
|
|
15434
|
+
session_id: summary.session_id,
|
|
15435
|
+
investigation: { schema_version: "2.0.0" },
|
|
15436
|
+
raw_transcripts_sent: false,
|
|
15437
|
+
events: [
|
|
15438
|
+
{
|
|
15439
|
+
source_client: summary.source_client,
|
|
15440
|
+
session_id: summary.session_id,
|
|
15441
|
+
occurred_at: summary.ended_at,
|
|
15442
|
+
metadata: summary
|
|
15443
|
+
}
|
|
15444
|
+
]
|
|
15445
|
+
}
|
|
13861
15446
|
};
|
|
13862
15447
|
}
|
|
13863
|
-
function
|
|
13864
|
-
const paths = defaultPaths(options);
|
|
13865
|
-
const codexConfigRaw = readTextIfExists(paths.codexConfigPath);
|
|
13866
|
-
const codexHooksRaw = readTextIfExists(paths.codexHooksPath);
|
|
13867
|
-
const claudeSettingsRaw = readTextIfExists(paths.claudeSettingsPath);
|
|
15448
|
+
function deliveryBody(capture) {
|
|
13868
15449
|
return {
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
},
|
|
13876
|
-
codex: {
|
|
13877
|
-
configExists: Boolean(codexConfigRaw),
|
|
13878
|
-
hooksEnabled: codexHooksEnabled(codexConfigRaw),
|
|
13879
|
-
hasNotify: codexHasNotify(codexConfigRaw),
|
|
13880
|
-
notifyPreserved: !codexHasNotify(codexConfigRaw) || !hasOrgxHook(codexConfigRaw)
|
|
13881
|
-
},
|
|
13882
|
-
outboxEvents: countJsonlLines(paths.outboxPath)
|
|
15450
|
+
session: buildMinimalCloudSummary(capture.session, {
|
|
15451
|
+
captureId: capture.capture_id,
|
|
15452
|
+
captureKind: capture.capture_kind,
|
|
15453
|
+
queuedAt: capture.queued_at
|
|
15454
|
+
}),
|
|
15455
|
+
...capture.initiative_id ? { initiative_id: capture.initiative_id } : {}
|
|
13883
15456
|
};
|
|
13884
15457
|
}
|
|
13885
|
-
function
|
|
13886
|
-
const
|
|
13887
|
-
const
|
|
13888
|
-
const
|
|
13889
|
-
const
|
|
13890
|
-
|
|
13891
|
-
|
|
13892
|
-
codexConfig: false,
|
|
13893
|
-
hookScript: false,
|
|
13894
|
-
emitHookScript: false
|
|
15458
|
+
async function flushSessionSummaryCaptures(options) {
|
|
15459
|
+
const files = sessionSummaryCaptureFiles(options.queueDir);
|
|
15460
|
+
const limit = Math.max(1, Math.min(options.limit ?? 100, 1e3));
|
|
15461
|
+
const selected = files.slice(0, limit);
|
|
15462
|
+
const headers = {
|
|
15463
|
+
Authorization: `Bearer ${options.auth.apiKey}`,
|
|
15464
|
+
"Content-Type": "application/json"
|
|
13895
15465
|
};
|
|
13896
|
-
|
|
13897
|
-
|
|
13898
|
-
|
|
13899
|
-
|
|
13900
|
-
|
|
13901
|
-
|
|
13902
|
-
|
|
13903
|
-
|
|
13904
|
-
|
|
13905
|
-
|
|
13906
|
-
|
|
13907
|
-
|
|
13908
|
-
|
|
13909
|
-
|
|
13910
|
-
|
|
13911
|
-
}
|
|
13912
|
-
|
|
13913
|
-
const
|
|
13914
|
-
|
|
13915
|
-
|
|
13916
|
-
|
|
13917
|
-
|
|
13918
|
-
|
|
13919
|
-
changed.codexConfig = true;
|
|
15466
|
+
const primaryUrl = buildCaptureEndpoint(
|
|
15467
|
+
SESSION_SUMMARY_ENDPOINT_PATH,
|
|
15468
|
+
options.auth.baseUrl
|
|
15469
|
+
);
|
|
15470
|
+
const fallbackUrl = buildCaptureEndpoint(
|
|
15471
|
+
SESSION_SUMMARY_FALLBACK_ENDPOINT_PATH,
|
|
15472
|
+
options.auth.baseUrl
|
|
15473
|
+
);
|
|
15474
|
+
const result = {
|
|
15475
|
+
found: files.length,
|
|
15476
|
+
attempted: 0,
|
|
15477
|
+
acknowledged: 0,
|
|
15478
|
+
retained: 0,
|
|
15479
|
+
malformed: 0,
|
|
15480
|
+
fallbackAcknowledged: 0
|
|
15481
|
+
};
|
|
15482
|
+
for (const path of selected) {
|
|
15483
|
+
const capture = readSessionSummaryCapture(path);
|
|
15484
|
+
if (!capture) {
|
|
15485
|
+
result.malformed += 1;
|
|
15486
|
+
result.retained += 1;
|
|
15487
|
+
result.firstError ??= `Malformed capture retained: ${path}`;
|
|
15488
|
+
continue;
|
|
13920
15489
|
}
|
|
13921
|
-
|
|
13922
|
-
|
|
13923
|
-
|
|
13924
|
-
|
|
13925
|
-
|
|
13926
|
-
|
|
13927
|
-
|
|
15490
|
+
result.attempted += 1;
|
|
15491
|
+
let response;
|
|
15492
|
+
let usedFallback = false;
|
|
15493
|
+
try {
|
|
15494
|
+
response = await options.send(primaryUrl, deliveryBody(capture), headers);
|
|
15495
|
+
if (!response.ok && (response.status === 404 || response.status === 405)) {
|
|
15496
|
+
response = await options.send(fallbackUrl, buildWorkGraphFallback(capture), headers);
|
|
15497
|
+
usedFallback = true;
|
|
15498
|
+
}
|
|
15499
|
+
} catch {
|
|
15500
|
+
response = { ok: false, status: 0 };
|
|
13928
15501
|
}
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
15502
|
+
if (!response.ok) {
|
|
15503
|
+
result.retained += 1;
|
|
15504
|
+
result.firstError ??= `HTTP ${response.status} for ${capture.capture_id}`;
|
|
15505
|
+
if (options.stopOnFailure) break;
|
|
15506
|
+
continue;
|
|
15507
|
+
}
|
|
15508
|
+
try {
|
|
15509
|
+
unlinkSync2(path);
|
|
15510
|
+
result.acknowledged += 1;
|
|
15511
|
+
if (usedFallback) result.fallbackAcknowledged += 1;
|
|
15512
|
+
} catch {
|
|
15513
|
+
result.retained += 1;
|
|
15514
|
+
result.firstError ??= `Acknowledged capture could not be removed: ${path}`;
|
|
13938
15515
|
}
|
|
13939
15516
|
}
|
|
13940
|
-
return
|
|
13941
|
-
...inspectRuntimeHooks(options),
|
|
13942
|
-
changed,
|
|
13943
|
-
backups
|
|
13944
|
-
};
|
|
15517
|
+
return result;
|
|
13945
15518
|
}
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
13949
|
-
|
|
13950
|
-
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
15519
|
+
|
|
15520
|
+
// src/lib/session-summary-flush-lease.ts
|
|
15521
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
15522
|
+
import {
|
|
15523
|
+
closeSync as closeSync2,
|
|
15524
|
+
fsyncSync,
|
|
15525
|
+
mkdirSync as mkdirSync5,
|
|
15526
|
+
openSync as openSync2,
|
|
15527
|
+
readFileSync as readFileSync9,
|
|
15528
|
+
renameSync as renameSync3,
|
|
15529
|
+
statSync as statSync6,
|
|
15530
|
+
unlinkSync as unlinkSync3,
|
|
15531
|
+
writeSync
|
|
15532
|
+
} from "fs";
|
|
15533
|
+
import { join as join10 } from "path";
|
|
15534
|
+
var SESSION_SUMMARY_FLUSH_LOCK_NAME = ".flush.lock";
|
|
15535
|
+
var SESSION_SUMMARY_FLUSH_MAX_AGE_MS = 2 * 60 * 60 * 1e3;
|
|
15536
|
+
var MALFORMED_LOCK_GRACE_MS = 5 * 60 * 1e3;
|
|
15537
|
+
function errorCode(error) {
|
|
15538
|
+
return error && typeof error === "object" && "code" in error ? String(error.code) : null;
|
|
15539
|
+
}
|
|
15540
|
+
function readLock(path) {
|
|
15541
|
+
try {
|
|
15542
|
+
const value = JSON.parse(readFileSync9(path, "utf8"));
|
|
15543
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
15544
|
+
const record = value;
|
|
15545
|
+
return typeof record.token === "string" && typeof record.pid === "number" && Number.isInteger(record.pid) && typeof record.claimed_at === "string" ? {
|
|
15546
|
+
token: record.token,
|
|
15547
|
+
pid: record.pid,
|
|
15548
|
+
claimed_at: record.claimed_at
|
|
15549
|
+
} : null;
|
|
15550
|
+
} catch {
|
|
15551
|
+
return null;
|
|
13957
15552
|
}
|
|
13958
|
-
|
|
15553
|
+
}
|
|
15554
|
+
function processIsAlive(pid) {
|
|
15555
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
15556
|
+
try {
|
|
15557
|
+
process.kill(pid, 0);
|
|
15558
|
+
return true;
|
|
15559
|
+
} catch (error) {
|
|
15560
|
+
return errorCode(error) === "EPERM";
|
|
15561
|
+
}
|
|
15562
|
+
}
|
|
15563
|
+
function lockAgeMs(path, now) {
|
|
15564
|
+
try {
|
|
15565
|
+
return Math.max(0, now.getTime() - statSync6(path).mtimeMs);
|
|
15566
|
+
} catch {
|
|
15567
|
+
return null;
|
|
15568
|
+
}
|
|
15569
|
+
}
|
|
15570
|
+
function shouldReclaimLock(path, now, isProcessAlive) {
|
|
15571
|
+
const age = lockAgeMs(path, now);
|
|
15572
|
+
if (age === null) return true;
|
|
15573
|
+
if (age >= SESSION_SUMMARY_FLUSH_MAX_AGE_MS) return true;
|
|
15574
|
+
const record = readLock(path);
|
|
15575
|
+
if (!record) return age >= MALFORMED_LOCK_GRACE_MS;
|
|
15576
|
+
return !isProcessAlive(record.pid);
|
|
15577
|
+
}
|
|
15578
|
+
function writeLock(path, record) {
|
|
15579
|
+
let descriptor = null;
|
|
15580
|
+
try {
|
|
15581
|
+
descriptor = openSync2(path, "wx", 384);
|
|
15582
|
+
writeSync(descriptor, JSON.stringify(record), void 0, "utf8");
|
|
15583
|
+
fsyncSync(descriptor);
|
|
15584
|
+
return "acquired";
|
|
15585
|
+
} catch (error) {
|
|
15586
|
+
return errorCode(error) === "EEXIST" ? "exists" : "unavailable";
|
|
15587
|
+
} finally {
|
|
15588
|
+
if (descriptor !== null) closeSync2(descriptor);
|
|
15589
|
+
}
|
|
15590
|
+
}
|
|
15591
|
+
function releaseOwnedLock(path, token) {
|
|
15592
|
+
try {
|
|
15593
|
+
if (readLock(path)?.token === token) unlinkSync3(path);
|
|
15594
|
+
} catch {
|
|
15595
|
+
}
|
|
15596
|
+
}
|
|
15597
|
+
function claimSessionSummaryFlushLease(queueDir, options = {}) {
|
|
15598
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
15599
|
+
const pid = options.pid ?? process.pid;
|
|
15600
|
+
const token = options.token ?? randomUUID3();
|
|
15601
|
+
const isProcessAlive = options.isProcessAlive ?? processIsAlive;
|
|
15602
|
+
const path = join10(queueDir, SESSION_SUMMARY_FLUSH_LOCK_NAME);
|
|
15603
|
+
const record = { token, pid, claimed_at: now.toISOString() };
|
|
15604
|
+
try {
|
|
15605
|
+
mkdirSync5(queueDir, { recursive: true, mode: 448 });
|
|
15606
|
+
} catch {
|
|
15607
|
+
return { acquired: false, reason: "unavailable" };
|
|
15608
|
+
}
|
|
15609
|
+
const firstAttempt = writeLock(path, record);
|
|
15610
|
+
if (firstAttempt === "acquired") {
|
|
15611
|
+
return {
|
|
15612
|
+
acquired: true,
|
|
15613
|
+
path,
|
|
15614
|
+
release: () => releaseOwnedLock(path, token)
|
|
15615
|
+
};
|
|
15616
|
+
}
|
|
15617
|
+
if (firstAttempt === "unavailable") {
|
|
15618
|
+
return { acquired: false, reason: "unavailable" };
|
|
15619
|
+
}
|
|
15620
|
+
if (!shouldReclaimLock(path, now, isProcessAlive)) {
|
|
15621
|
+
return { acquired: false, reason: "busy" };
|
|
15622
|
+
}
|
|
15623
|
+
const stalePath = `${path}.stale-${token}`;
|
|
15624
|
+
try {
|
|
15625
|
+
renameSync3(path, stalePath);
|
|
15626
|
+
unlinkSync3(stalePath);
|
|
15627
|
+
} catch {
|
|
15628
|
+
return { acquired: false, reason: "busy" };
|
|
15629
|
+
}
|
|
15630
|
+
const retry = writeLock(path, record);
|
|
15631
|
+
if (retry !== "acquired") {
|
|
15632
|
+
return {
|
|
15633
|
+
acquired: false,
|
|
15634
|
+
reason: retry === "exists" ? "busy" : "unavailable"
|
|
15635
|
+
};
|
|
15636
|
+
}
|
|
15637
|
+
return {
|
|
15638
|
+
acquired: true,
|
|
15639
|
+
path,
|
|
15640
|
+
release: () => releaseOwnedLock(path, token)
|
|
15641
|
+
};
|
|
13959
15642
|
}
|
|
13960
15643
|
|
|
13961
15644
|
// src/spinner.ts
|
|
@@ -13973,40 +15656,40 @@ function createOrgxSpinner(text2) {
|
|
|
13973
15656
|
}
|
|
13974
15657
|
|
|
13975
15658
|
// src/lib/workload-diagnosis.ts
|
|
13976
|
-
import { closeSync as
|
|
13977
|
-
import { resolve as
|
|
15659
|
+
import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2 } from "fs";
|
|
15660
|
+
import { resolve as resolve3 } from "path";
|
|
13978
15661
|
|
|
13979
15662
|
// src/lib/workload-diagnosis-schema.ts
|
|
13980
|
-
import { z } from "zod";
|
|
15663
|
+
import { z as z2 } from "zod";
|
|
13981
15664
|
var WORKLOAD_DIAGNOSIS_SCHEMA_VERSION = "workload-diagnosis/0.1";
|
|
13982
15665
|
var SECRET_PATTERN = /(?:\b(?:api[_-]?key|authorization|cookie|access[_-]?token|refresh[_-]?token|client[_-]?secret|password|secret|session|token)\s*[:=]\s*\S+|\bbearer\s+[a-z0-9._~+/=-]{8,}|\b(?:oxk_[a-z0-9_-]{8,}|sk-(?:live|test|proj)?[_-]?[a-z0-9_-]{8,}|[sr]k_(?:live|test)_[a-z0-9_-]{8,}|gh[pousr]_[a-z0-9]{8,}|github_pat_[a-z0-9_]{8,}|glpat-[a-z0-9_-]{16,}|xox[baprs]-[a-z0-9-]{8,}|npm_[a-z0-9]{8,}|sntrys_[a-z0-9_-]{8,}|whsec_[a-z0-9_-]{8,}|SG\.[a-z0-9_-]{16,}\.[a-z0-9_-]{16,}|pypi-[a-z0-9_-]{24,}|dop_v1_[a-f0-9]{16,}|hf_[a-z0-9]{20,}|A(?:KI|SI)A[A-Z0-9]{16}|AIza[a-z0-9_-]{20,})\b|\beyJ[a-z0-9_-]{8,}\.eyJ[a-z0-9_-]{8,}\.[a-z0-9_-]{8,}|[a-z][a-z0-9+.-]*:\/\/[^\s/@:]+:[^\s/@]+@|-----BEGIN [A-Z ]*PRIVATE KEY-----)/i;
|
|
13983
15666
|
var BASIC_AUTH_SECRET_PATTERN = /\bbasic\s+(?:[a-z0-9+/]{4})*(?:[a-z0-9+/]{4}|[a-z0-9+/]{2}==|[a-z0-9+/]{3}=)(?=$|[^a-z0-9+/=])/i;
|
|
13984
15667
|
function containsCredentialPattern(value) {
|
|
13985
15668
|
return SECRET_PATTERN.test(value) || BASIC_AUTH_SECRET_PATTERN.test(value);
|
|
13986
15669
|
}
|
|
13987
|
-
var SafeSummarySchema =
|
|
15670
|
+
var SafeSummarySchema = z2.string().trim().min(1).max(500).refine(
|
|
13988
15671
|
(value) => !/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(value),
|
|
13989
15672
|
{ message: "Control characters are not allowed" }
|
|
13990
15673
|
).refine((value) => !containsCredentialPattern(value), {
|
|
13991
15674
|
message: "Do not include credentials, tokens, passwords, or private keys"
|
|
13992
15675
|
});
|
|
13993
|
-
var SafeResponseText = (max) =>
|
|
15676
|
+
var SafeResponseText = (max) => z2.string().min(1).max(max).refine((value) => !/[\u0000-\u001f\u007f-\u009f]/.test(value), {
|
|
13994
15677
|
message: "Control characters are not allowed"
|
|
13995
15678
|
});
|
|
13996
|
-
var WorkloadTimeHorizonSchema =
|
|
15679
|
+
var WorkloadTimeHorizonSchema = z2.enum([
|
|
13997
15680
|
"single_turn",
|
|
13998
15681
|
"single_session",
|
|
13999
15682
|
"multi_day",
|
|
14000
15683
|
"recurring",
|
|
14001
15684
|
"continuous"
|
|
14002
15685
|
]);
|
|
14003
|
-
var WorkloadCoordinationSchema =
|
|
15686
|
+
var WorkloadCoordinationSchema = z2.enum([
|
|
14004
15687
|
"none",
|
|
14005
15688
|
"handoff",
|
|
14006
15689
|
"parallel",
|
|
14007
15690
|
"hierarchical"
|
|
14008
15691
|
]);
|
|
14009
|
-
var WorkloadSystemCategorySchema =
|
|
15692
|
+
var WorkloadSystemCategorySchema = z2.enum([
|
|
14010
15693
|
"code_repository",
|
|
14011
15694
|
"issue_tracker",
|
|
14012
15695
|
"document_store",
|
|
@@ -14020,20 +15703,20 @@ var WorkloadSystemCategorySchema = z.enum([
|
|
|
14020
15703
|
"identity",
|
|
14021
15704
|
"other"
|
|
14022
15705
|
]);
|
|
14023
|
-
var WorkloadAccessModeSchema =
|
|
14024
|
-
var WorkloadSideEffectSchema =
|
|
15706
|
+
var WorkloadAccessModeSchema = z2.enum(["read", "write", "admin"]);
|
|
15707
|
+
var WorkloadSideEffectSchema = z2.enum([
|
|
14025
15708
|
"none",
|
|
14026
15709
|
"reversible",
|
|
14027
15710
|
"external",
|
|
14028
15711
|
"irreversible"
|
|
14029
15712
|
]);
|
|
14030
|
-
var WorkloadDataClassSchema =
|
|
15713
|
+
var WorkloadDataClassSchema = z2.enum([
|
|
14031
15714
|
"public",
|
|
14032
15715
|
"internal",
|
|
14033
15716
|
"confidential",
|
|
14034
15717
|
"regulated"
|
|
14035
15718
|
]);
|
|
14036
|
-
var WorkloadActionSchema =
|
|
15719
|
+
var WorkloadActionSchema = z2.enum([
|
|
14037
15720
|
"research",
|
|
14038
15721
|
"draft",
|
|
14039
15722
|
"read_internal_data",
|
|
@@ -14046,28 +15729,28 @@ var WorkloadActionSchema = z.enum([
|
|
|
14046
15729
|
"change_permissions",
|
|
14047
15730
|
"delete_data"
|
|
14048
15731
|
]);
|
|
14049
|
-
var WorkloadApprovalPolicySchema =
|
|
15732
|
+
var WorkloadApprovalPolicySchema = z2.enum([
|
|
14050
15733
|
"not_applicable",
|
|
14051
15734
|
"per_action",
|
|
14052
15735
|
"sensitive_actions",
|
|
14053
15736
|
"exceptions_only",
|
|
14054
15737
|
"undefined"
|
|
14055
15738
|
]);
|
|
14056
|
-
var WorkloadBudgetControlSchema =
|
|
15739
|
+
var WorkloadBudgetControlSchema = z2.enum([
|
|
14057
15740
|
"not_applicable",
|
|
14058
15741
|
"fixed_limit",
|
|
14059
15742
|
"dynamic_limit",
|
|
14060
15743
|
"unbounded",
|
|
14061
15744
|
"undefined"
|
|
14062
15745
|
]);
|
|
14063
|
-
var SystemAccessSchema =
|
|
15746
|
+
var SystemAccessSchema = z2.object({
|
|
14064
15747
|
category: WorkloadSystemCategorySchema,
|
|
14065
15748
|
access: WorkloadAccessModeSchema,
|
|
14066
15749
|
side_effect: WorkloadSideEffectSchema,
|
|
14067
15750
|
data_class: WorkloadDataClassSchema
|
|
14068
15751
|
}).strict();
|
|
14069
|
-
var AuthoritySchema =
|
|
14070
|
-
actions:
|
|
15752
|
+
var AuthoritySchema = z2.object({
|
|
15753
|
+
actions: z2.array(WorkloadActionSchema).max(11).superRefine((actions, context) => {
|
|
14071
15754
|
if (new Set(actions).size !== actions.length) {
|
|
14072
15755
|
context.addIssue({
|
|
14073
15756
|
code: "custom",
|
|
@@ -14104,21 +15787,21 @@ var AuthoritySchema = z.object({
|
|
|
14104
15787
|
});
|
|
14105
15788
|
}
|
|
14106
15789
|
});
|
|
14107
|
-
var AccountabilitySchema =
|
|
14108
|
-
evidence:
|
|
14109
|
-
acceptance:
|
|
14110
|
-
consequence:
|
|
14111
|
-
retention:
|
|
15790
|
+
var AccountabilitySchema = z2.object({
|
|
15791
|
+
evidence: z2.enum(["none", "activity_log", "artifact", "verified_outcome"]),
|
|
15792
|
+
acceptance: z2.enum(["none", "agent", "human", "downstream_system"]),
|
|
15793
|
+
consequence: z2.enum(["low", "moderate", "high", "regulated"]),
|
|
15794
|
+
retention: z2.enum(["none", "short_term", "long_term", "regulated"])
|
|
14112
15795
|
}).strict();
|
|
14113
|
-
var WorkloadDiagnosisRequestSchema =
|
|
14114
|
-
schema_version:
|
|
14115
|
-
workload:
|
|
15796
|
+
var WorkloadDiagnosisRequestSchema = z2.object({
|
|
15797
|
+
schema_version: z2.literal(WORKLOAD_DIAGNOSIS_SCHEMA_VERSION),
|
|
15798
|
+
workload: z2.object({
|
|
14116
15799
|
name: SafeSummarySchema.max(120),
|
|
14117
15800
|
outcome: SafeSummarySchema,
|
|
14118
15801
|
time_horizon: WorkloadTimeHorizonSchema,
|
|
14119
|
-
agent_count:
|
|
15802
|
+
agent_count: z2.number().int().min(1).max(64),
|
|
14120
15803
|
coordination: WorkloadCoordinationSchema,
|
|
14121
|
-
systems:
|
|
15804
|
+
systems: z2.array(SystemAccessSchema).max(12).superRefine((systems, context) => {
|
|
14122
15805
|
const seen = /* @__PURE__ */ new Set();
|
|
14123
15806
|
systems.forEach((system, index) => {
|
|
14124
15807
|
if (seen.has(system.category)) {
|
|
@@ -14198,28 +15881,28 @@ var WorkloadDiagnosisRequestSchema = z.object({
|
|
|
14198
15881
|
}
|
|
14199
15882
|
});
|
|
14200
15883
|
});
|
|
14201
|
-
var BoundaryNameSchema =
|
|
15884
|
+
var BoundaryNameSchema = z2.enum([
|
|
14202
15885
|
"time",
|
|
14203
15886
|
"agents",
|
|
14204
15887
|
"systems",
|
|
14205
15888
|
"authority",
|
|
14206
15889
|
"accountability"
|
|
14207
15890
|
]);
|
|
14208
|
-
var BoundaryFindingSchema =
|
|
14209
|
-
state:
|
|
14210
|
-
score:
|
|
15891
|
+
var BoundaryFindingSchema = z2.object({
|
|
15892
|
+
state: z2.enum(["absent", "present", "critical"]),
|
|
15893
|
+
score: z2.number().int().min(0).max(2),
|
|
14211
15894
|
reason: SafeResponseText(300)
|
|
14212
15895
|
}).strict();
|
|
14213
|
-
var ProposedResourceSchema =
|
|
15896
|
+
var ProposedResourceSchema = z2.object({
|
|
14214
15897
|
resource: SafeResponseText(80),
|
|
14215
15898
|
requested_access: WorkloadAccessModeSchema,
|
|
14216
|
-
scope_intents:
|
|
15899
|
+
scope_intents: z2.array(SafeResponseText(80)).min(1).max(12),
|
|
14217
15900
|
purpose: SafeResponseText(300),
|
|
14218
|
-
credential_input_required:
|
|
15901
|
+
credential_input_required: z2.literal(false)
|
|
14219
15902
|
}).strict();
|
|
14220
|
-
var HumanApprovalSchema =
|
|
14221
|
-
id:
|
|
14222
|
-
owner_role:
|
|
15903
|
+
var HumanApprovalSchema = z2.object({
|
|
15904
|
+
id: z2.string().regex(/^[a-z0-9_-]{1,80}$/),
|
|
15905
|
+
owner_role: z2.enum([
|
|
14223
15906
|
"workload_owner",
|
|
14224
15907
|
"system_owner",
|
|
14225
15908
|
"code_owner",
|
|
@@ -14228,63 +15911,63 @@ var HumanApprovalSchema = z.object({
|
|
|
14228
15911
|
"identity_admin",
|
|
14229
15912
|
"data_owner"
|
|
14230
15913
|
]),
|
|
14231
|
-
timing:
|
|
15914
|
+
timing: z2.enum([
|
|
14232
15915
|
"before_installation",
|
|
14233
15916
|
"before_first_use",
|
|
14234
15917
|
"per_action",
|
|
14235
15918
|
"when_threshold_exceeded"
|
|
14236
15919
|
]),
|
|
14237
15920
|
decision: SafeResponseText(300),
|
|
14238
|
-
scope:
|
|
15921
|
+
scope: z2.array(SafeResponseText(100)).min(1).max(16)
|
|
14239
15922
|
}).strict();
|
|
14240
|
-
var WorkloadDiagnosisResponseSchema =
|
|
14241
|
-
schema_version:
|
|
14242
|
-
diagnosis_id:
|
|
14243
|
-
recommendation:
|
|
14244
|
-
verdict:
|
|
14245
|
-
mode:
|
|
15923
|
+
var WorkloadDiagnosisResponseSchema = z2.object({
|
|
15924
|
+
schema_version: z2.literal(WORKLOAD_DIAGNOSIS_SCHEMA_VERSION),
|
|
15925
|
+
diagnosis_id: z2.string().regex(/^wdg_[a-f0-9]{24}$/),
|
|
15926
|
+
recommendation: z2.object({
|
|
15927
|
+
verdict: z2.enum(["needed", "conditional", "not_needed"]),
|
|
15928
|
+
mode: z2.enum(["none", "receipt_only", "governed_workspace"]),
|
|
14246
15929
|
summary: SafeResponseText(400),
|
|
14247
|
-
rationale:
|
|
14248
|
-
active_boundary_count:
|
|
15930
|
+
rationale: z2.array(SafeResponseText(300)).max(5),
|
|
15931
|
+
active_boundary_count: z2.number().int().min(0).max(5)
|
|
14249
15932
|
}).strict(),
|
|
14250
|
-
boundaries:
|
|
15933
|
+
boundaries: z2.object({
|
|
14251
15934
|
time: BoundaryFindingSchema,
|
|
14252
15935
|
agents: BoundaryFindingSchema,
|
|
14253
15936
|
systems: BoundaryFindingSchema,
|
|
14254
15937
|
authority: BoundaryFindingSchema,
|
|
14255
15938
|
accountability: BoundaryFindingSchema
|
|
14256
15939
|
}).strict(),
|
|
14257
|
-
missing_capabilities:
|
|
14258
|
-
|
|
14259
|
-
id:
|
|
15940
|
+
missing_capabilities: z2.array(
|
|
15941
|
+
z2.object({
|
|
15942
|
+
id: z2.string().regex(/^[a-z0-9_]{1,60}$/),
|
|
14260
15943
|
boundary: BoundaryNameSchema,
|
|
14261
15944
|
reason: SafeResponseText(300)
|
|
14262
15945
|
}).strict()
|
|
14263
15946
|
).max(15),
|
|
14264
|
-
proposed_resources:
|
|
14265
|
-
what_remains_local:
|
|
14266
|
-
|
|
15947
|
+
proposed_resources: z2.array(ProposedResourceSchema).max(13),
|
|
15948
|
+
what_remains_local: z2.array(
|
|
15949
|
+
z2.object({
|
|
14267
15950
|
item: SafeResponseText(100),
|
|
14268
15951
|
reason: SafeResponseText(300)
|
|
14269
15952
|
}).strict()
|
|
14270
15953
|
).min(1).max(6),
|
|
14271
|
-
risks:
|
|
14272
|
-
|
|
14273
|
-
id:
|
|
14274
|
-
severity:
|
|
15954
|
+
risks: z2.array(
|
|
15955
|
+
z2.object({
|
|
15956
|
+
id: z2.string().regex(/^[a-z0-9_]{1,60}$/),
|
|
15957
|
+
severity: z2.enum(["low", "medium", "high", "critical"]),
|
|
14275
15958
|
boundary: BoundaryNameSchema,
|
|
14276
15959
|
description: SafeResponseText(300),
|
|
14277
15960
|
mitigation: SafeResponseText(300)
|
|
14278
15961
|
}).strict()
|
|
14279
15962
|
).max(15),
|
|
14280
|
-
capabilities_after_installation:
|
|
14281
|
-
human_approvals:
|
|
14282
|
-
approval_handoff:
|
|
14283
|
-
kind:
|
|
14284
|
-
url:
|
|
14285
|
-
mutates_state:
|
|
14286
|
-
carries_sensitive_data:
|
|
14287
|
-
approval_ids:
|
|
15963
|
+
capabilities_after_installation: z2.array(SafeResponseText(300)).max(10),
|
|
15964
|
+
human_approvals: z2.array(HumanApprovalSchema).max(32),
|
|
15965
|
+
approval_handoff: z2.object({
|
|
15966
|
+
kind: z2.enum(["none", "browser_review"]),
|
|
15967
|
+
url: z2.string().url().nullable(),
|
|
15968
|
+
mutates_state: z2.literal(false),
|
|
15969
|
+
carries_sensitive_data: z2.boolean(),
|
|
15970
|
+
approval_ids: z2.array(z2.string().regex(/^[a-z0-9_-]{1,80}$/)).max(32)
|
|
14288
15971
|
}).strict()
|
|
14289
15972
|
}).strict().superRefine((response, context) => {
|
|
14290
15973
|
const activeCount = Object.values(response.boundaries).filter(
|
|
@@ -14327,7 +16010,7 @@ var MAX_HANDOFF_TOKEN_CHARACTERS = 7e3;
|
|
|
14327
16010
|
var SENSITIVE_KEY2 = /(?:^|[_-])(?:authorization|cookie|credentials?|password|secrets?|session|tokens?|api[_-]?keys?|access[_-]?tokens?|refresh[_-]?tokens?|client[_-]?secrets?|private[_-]?keys?|database[_-]?(?:url|uri)|db[_-]?(?:url|uri))(?:$|[_-])/i;
|
|
14328
16011
|
function readBoundedUtf8(source) {
|
|
14329
16012
|
const shouldClose = source !== "-";
|
|
14330
|
-
const fd = shouldClose ?
|
|
16013
|
+
const fd = shouldClose ? openSync3(resolve3(source), "r") : 0;
|
|
14331
16014
|
const buffer = Buffer.alloc(MAX_WORKLOAD_DIAGNOSIS_REQUEST_BYTES + 1);
|
|
14332
16015
|
let offset = 0;
|
|
14333
16016
|
try {
|
|
@@ -14343,7 +16026,7 @@ function readBoundedUtf8(source) {
|
|
|
14343
16026
|
offset += bytesRead;
|
|
14344
16027
|
}
|
|
14345
16028
|
} finally {
|
|
14346
|
-
if (shouldClose)
|
|
16029
|
+
if (shouldClose) closeSync3(fd);
|
|
14347
16030
|
}
|
|
14348
16031
|
if (offset > MAX_WORKLOAD_DIAGNOSIS_REQUEST_BYTES) {
|
|
14349
16032
|
throw new Error(
|
|
@@ -14698,8 +16381,171 @@ function printRuntimeHookInspection(report) {
|
|
|
14698
16381
|
console.log(` ${report.codex.hooksEnabled ? ICON.ok : ICON.warn} ${pc3.bold("Codex flag ")} ${report.codex.hooksEnabled ? pc3.green("enabled") : pc3.yellow("not enabled")} ${pc3.dim(report.paths.codexConfigPath)}`);
|
|
14699
16382
|
console.log(` ${report.codex.notifyPreserved ? ICON.ok : ICON.warn} ${pc3.bold("notify ")} ${report.codex.notifyPreserved ? pc3.green("preserved") : pc3.yellow("check config")} ${pc3.dim(report.codex.hasNotify ? "existing notify detected" : "no notify entry")}`);
|
|
14700
16383
|
console.log(` ${report.installed.claudeCode ? ICON.ok : ICON.warn} ${pc3.bold("Claude Code ")} ${report.installed.claudeCode ? pc3.green("installed") : pc3.yellow("missing")} ${pc3.dim(report.paths.claudeSettingsPath)}`);
|
|
16384
|
+
console.log(` ${report.installed.summaryHookScript ? ICON.ok : ICON.warn} ${pc3.bold("summary hook")} ${report.installed.summaryHookScript ? pc3.green("installed") : pc3.yellow("missing")} ${pc3.dim(report.paths.summaryHookScriptPath)}`);
|
|
16385
|
+
console.log(` ${report.installed.automaticDelivery ? ICON.ok : ICON.warn} ${pc3.bold("auto delivery")} ${report.installed.automaticDelivery ? pc3.green("configured") : pc3.yellow("not configured")} ${pc3.dim("detached, ACK-gated worker")}`);
|
|
16386
|
+
console.log(` ${ICON.skip} ${pc3.bold("capture queue")} ${pc3.dim(`${report.sessionSummaryCaptures} capture${report.sessionSummaryCaptures === 1 ? "" : "s"} at ${report.paths.sessionSummaryQueueDir}`)}`);
|
|
14701
16387
|
console.log(` ${ICON.skip} ${pc3.bold("outbox ")} ${pc3.dim(`${report.outboxEvents} event${report.outboxEvents === 1 ? "" : "s"} at ${report.paths.outboxPath}`)}`);
|
|
14702
16388
|
}
|
|
16389
|
+
function printSessionSummaryFlush(result) {
|
|
16390
|
+
if (result.skipped) {
|
|
16391
|
+
console.log(` ${ICON.skip} ${pc3.bold("captures ")} ${pc3.dim(result.skipped.replaceAll("_", " "))}`);
|
|
16392
|
+
return;
|
|
16393
|
+
}
|
|
16394
|
+
const delivered = `${result.acknowledged}/${result.attempted} acknowledged`;
|
|
16395
|
+
console.log(` ${result.retained === 0 ? ICON.ok : ICON.warn} ${pc3.bold("captures ")} ${pc3.dim(`${delivered}, ${result.retained} retained, ${result.malformed} malformed`)}`);
|
|
16396
|
+
if (result.fallbackAcknowledged > 0) {
|
|
16397
|
+
console.log(` ${ICON.warn} ${pc3.bold("fallback ")} ${pc3.dim(`${result.fallbackAcknowledged} capture(s) delivered through the legacy Work Graph endpoint`)}`);
|
|
16398
|
+
}
|
|
16399
|
+
if (result.firstError) console.log(` ${ICON.warn} ${pc3.dim(result.firstError)}`);
|
|
16400
|
+
}
|
|
16401
|
+
async function runSessionSummaryFlushCommand(options) {
|
|
16402
|
+
const queueDir = resolve4(options.queue?.trim() || inspectRuntimeHooks().paths.sessionSummaryQueueDir);
|
|
16403
|
+
const found = sessionSummaryCaptureFiles(queueDir).length;
|
|
16404
|
+
let result;
|
|
16405
|
+
if (found === 0) {
|
|
16406
|
+
result = {
|
|
16407
|
+
found: 0,
|
|
16408
|
+
attempted: 0,
|
|
16409
|
+
acknowledged: 0,
|
|
16410
|
+
retained: 0,
|
|
16411
|
+
malformed: 0,
|
|
16412
|
+
fallbackAcknowledged: 0,
|
|
16413
|
+
skipped: "empty_queue"
|
|
16414
|
+
};
|
|
16415
|
+
} else {
|
|
16416
|
+
const lease = claimSessionSummaryFlushLease(queueDir);
|
|
16417
|
+
if (!lease.acquired) {
|
|
16418
|
+
if (lease.reason === "unavailable") {
|
|
16419
|
+
if (options.background) return;
|
|
16420
|
+
throw new Error(`Could not acquire the session-summary flush lease at ${queueDir}.`);
|
|
16421
|
+
}
|
|
16422
|
+
result = {
|
|
16423
|
+
found,
|
|
16424
|
+
attempted: 0,
|
|
16425
|
+
acknowledged: 0,
|
|
16426
|
+
retained: 0,
|
|
16427
|
+
malformed: 0,
|
|
16428
|
+
fallbackAcknowledged: 0,
|
|
16429
|
+
skipped: "already_running"
|
|
16430
|
+
};
|
|
16431
|
+
} else {
|
|
16432
|
+
try {
|
|
16433
|
+
const auth = await resolveOrgxAuth();
|
|
16434
|
+
if (!auth) {
|
|
16435
|
+
if (options.background) return;
|
|
16436
|
+
throw new Error("No OrgX credential found. Run `orgx-wizard login` first.");
|
|
16437
|
+
}
|
|
16438
|
+
result = await flushSessionSummaryCaptures({
|
|
16439
|
+
queueDir,
|
|
16440
|
+
auth,
|
|
16441
|
+
limit: parsePositiveInt(options.limit, 100),
|
|
16442
|
+
stopOnFailure: options.background === true,
|
|
16443
|
+
send: async (url, body, headers) => {
|
|
16444
|
+
try {
|
|
16445
|
+
const response = await fetchWithRetry(
|
|
16446
|
+
url,
|
|
16447
|
+
{
|
|
16448
|
+
method: "POST",
|
|
16449
|
+
headers,
|
|
16450
|
+
body: JSON.stringify(body)
|
|
16451
|
+
},
|
|
16452
|
+
options.background ? { timeoutMs: 8e3, retries: 0 } : { timeoutMs: 12e3, retries: 1 }
|
|
16453
|
+
);
|
|
16454
|
+
return { ok: response.ok, status: response.status };
|
|
16455
|
+
} catch {
|
|
16456
|
+
return { ok: false, status: 0 };
|
|
16457
|
+
}
|
|
16458
|
+
}
|
|
16459
|
+
});
|
|
16460
|
+
} finally {
|
|
16461
|
+
lease.release();
|
|
16462
|
+
}
|
|
16463
|
+
}
|
|
16464
|
+
}
|
|
16465
|
+
if (options.background) return;
|
|
16466
|
+
await safeTrackWizardTelemetry("hooks_session_summary_flush_ran", {
|
|
16467
|
+
command: "hooks flush",
|
|
16468
|
+
attempted: String(result.attempted),
|
|
16469
|
+
acknowledged: String(result.acknowledged),
|
|
16470
|
+
retained: String(result.retained)
|
|
16471
|
+
});
|
|
16472
|
+
if (result.retained > 0) process.exitCode = 1;
|
|
16473
|
+
if (options.json) {
|
|
16474
|
+
console.log(JSON.stringify({ ok: result.retained === 0, queueDir, ...result }, null, 2));
|
|
16475
|
+
return;
|
|
16476
|
+
}
|
|
16477
|
+
printSessionSummaryFlush(result);
|
|
16478
|
+
}
|
|
16479
|
+
async function runHookBackfillCommand(options) {
|
|
16480
|
+
const paths = inspectRuntimeHooks().paths;
|
|
16481
|
+
const spoolPath = resolve4(options.spool?.trim() || paths.outboxPath);
|
|
16482
|
+
if (!fileExists(spoolPath)) {
|
|
16483
|
+
const message = `No hook spool found at ${spoolPath}`;
|
|
16484
|
+
if (options.json) {
|
|
16485
|
+
console.log(JSON.stringify({ ok: true, distilled: 0, reason: "missing_spool", spoolPath }, null, 2));
|
|
16486
|
+
return;
|
|
16487
|
+
}
|
|
16488
|
+
console.log(` ${ICON.skip} ${pc3.dim(message)}`);
|
|
16489
|
+
return;
|
|
16490
|
+
}
|
|
16491
|
+
if ((options.post || options.truncate) && !options.yes) {
|
|
16492
|
+
throw new Error("Posting or truncating the hook spool requires --yes. Run without --post to preview.");
|
|
16493
|
+
}
|
|
16494
|
+
if (options.truncate && !options.post) {
|
|
16495
|
+
throw new Error("--truncate is only valid together with --post; nothing may be discarded unsent.");
|
|
16496
|
+
}
|
|
16497
|
+
const distilled = await distillSpool(spoolPath);
|
|
16498
|
+
const reduction = distilled.bytes.total > 0 ? Math.round(distilled.spoolBytes / distilled.bytes.total) : 0;
|
|
16499
|
+
let post = { attempted: 0, posted: 0, failed: 0 };
|
|
16500
|
+
let truncation = { truncated: false, bytesReleased: 0 };
|
|
16501
|
+
if (options.post) {
|
|
16502
|
+
const auth = await resolveOrgxAuth();
|
|
16503
|
+
if (!auth) {
|
|
16504
|
+
throw new Error("No OrgX credential found. Run `orgx-wizard login` first.");
|
|
16505
|
+
}
|
|
16506
|
+
const url = `${auth.baseUrl.replace(/\/+$/, "")}${SESSION_SUMMARY_ENDPOINT_PATH}`;
|
|
16507
|
+
post = await postSummaries(distilled.summaries, async (summary) => {
|
|
16508
|
+
const minimalSummary = buildMinimalCloudSummary(summary, {
|
|
16509
|
+
captureKind: "historical_backfill"
|
|
16510
|
+
});
|
|
16511
|
+
const response = await fetchWithRetry(url, {
|
|
16512
|
+
method: "POST",
|
|
16513
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.apiKey}` },
|
|
16514
|
+
body: JSON.stringify({ session: minimalSummary, backfill: true })
|
|
16515
|
+
});
|
|
16516
|
+
return { ok: response.ok, status: response.status };
|
|
16517
|
+
});
|
|
16518
|
+
if (options.truncate) {
|
|
16519
|
+
truncation = truncateSpool(spoolPath, post);
|
|
16520
|
+
}
|
|
16521
|
+
}
|
|
16522
|
+
const result = {
|
|
16523
|
+
ok: true,
|
|
16524
|
+
spoolPath,
|
|
16525
|
+
spoolBytes: distilled.spoolBytes,
|
|
16526
|
+
lines: distilled.lines,
|
|
16527
|
+
badLines: distilled.badLines,
|
|
16528
|
+
sessions: distilled.summaries.length,
|
|
16529
|
+
payloadBytes: distilled.bytes,
|
|
16530
|
+
reductionFactor: reduction,
|
|
16531
|
+
posted: options.post ? post : null,
|
|
16532
|
+
truncated: options.truncate ? truncation : null
|
|
16533
|
+
};
|
|
16534
|
+
if (options.json) {
|
|
16535
|
+
console.log(JSON.stringify(result, null, 2));
|
|
16536
|
+
return;
|
|
16537
|
+
}
|
|
16538
|
+
console.log(` ${ICON.ok} ${pc3.bold("spool ")} ${pc3.dim(`${(distilled.spoolBytes / 1048576).toFixed(1)}MB, ${distilled.lines} lines, ${distilled.badLines} unparsable`)}`);
|
|
16539
|
+
console.log(` ${ICON.ok} ${pc3.bold("distilled ")} ${pc3.dim(`${distilled.summaries.length} sessions, ${(distilled.bytes.total / 1024).toFixed(0)}KB total (p50 ${distilled.bytes.p50}B, max ${distilled.bytes.max}B), ${reduction}x smaller`)}`);
|
|
16540
|
+
if (!options.post) {
|
|
16541
|
+
console.log(` ${ICON.skip} ${pc3.dim("preview only. Re-run with --post --yes to upload, and add --truncate to archive the spool.")}`);
|
|
16542
|
+
return;
|
|
16543
|
+
}
|
|
16544
|
+
console.log(` ${post.failed === 0 ? ICON.ok : ICON.warn} ${pc3.bold("posted ")} ${pc3.dim(`${post.posted}/${post.attempted} sessions${post.firstError ? ` (${post.firstError})` : ""}`)}`);
|
|
16545
|
+
if (options.truncate) {
|
|
16546
|
+
console.log(` ${truncation.truncated ? ICON.ok : ICON.warn} ${pc3.bold("truncated ")} ${pc3.dim(truncation.truncated ? `${(truncation.bytesReleased / 1048576).toFixed(1)}MB archived to ${truncation.archivePath}` : `skipped: ${truncation.reason}`)}`);
|
|
16547
|
+
}
|
|
16548
|
+
}
|
|
14703
16549
|
function requireHookReplayApproval(options, interactive) {
|
|
14704
16550
|
if (options.yes) return true;
|
|
14705
16551
|
if (!interactive) {
|
|
@@ -14730,7 +16576,7 @@ async function runHookReplayCommand(options) {
|
|
|
14730
16576
|
}
|
|
14731
16577
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
14732
16578
|
const paths = inspectRuntimeHooks().paths;
|
|
14733
|
-
const outboxPath =
|
|
16579
|
+
const outboxPath = resolve4(options.outbox?.trim() || paths.outboxPath);
|
|
14734
16580
|
const readResult = readRuntimeHookOutbox(outboxPath, parsePositiveInt(options.limit, 200));
|
|
14735
16581
|
const replay = buildWorkGraphHookReplayPatch(readResult);
|
|
14736
16582
|
if (replay.records === 0) {
|
|
@@ -14762,10 +16608,10 @@ async function runHookReplayCommand(options) {
|
|
|
14762
16608
|
}
|
|
14763
16609
|
function readAuditInput(options, interactive) {
|
|
14764
16610
|
if (options.input?.trim()) {
|
|
14765
|
-
return
|
|
16611
|
+
return readFileSync10(resolve4(options.input.trim()), "utf8");
|
|
14766
16612
|
}
|
|
14767
16613
|
if (!process.stdin.isTTY) {
|
|
14768
|
-
return
|
|
16614
|
+
return readFileSync10(0, "utf8");
|
|
14769
16615
|
}
|
|
14770
16616
|
if (!interactive) {
|
|
14771
16617
|
throw new Error("Audit input is required. Pass --input <file> or pipe text into wizard audit.");
|
|
@@ -14797,8 +16643,8 @@ function collectPathOption(value, previous = []) {
|
|
|
14797
16643
|
];
|
|
14798
16644
|
}
|
|
14799
16645
|
function parseClientExtractionFile(path) {
|
|
14800
|
-
const resolvedPath =
|
|
14801
|
-
const parsed = JSON.parse(
|
|
16646
|
+
const resolvedPath = resolve4(path);
|
|
16647
|
+
const parsed = JSON.parse(readFileSync10(resolvedPath, "utf8"));
|
|
14802
16648
|
if (!isRecord(parsed)) {
|
|
14803
16649
|
throw new Error(`AI-client extraction must be a JSON object: ${resolvedPath}`);
|
|
14804
16650
|
}
|
|
@@ -14820,8 +16666,8 @@ async function readAuditImports(options, interactive) {
|
|
|
14820
16666
|
const missingSources = [];
|
|
14821
16667
|
if (sources.length > 0) {
|
|
14822
16668
|
const imported = loadAiSessionImports({
|
|
14823
|
-
...options.claudeProjectsDir?.trim() ? { claudeProjectsDir:
|
|
14824
|
-
...options.codexSessionsDir?.trim() ? { codexSessionsDir:
|
|
16669
|
+
...options.claudeProjectsDir?.trim() ? { claudeProjectsDir: resolve4(options.claudeProjectsDir.trim()) } : {},
|
|
16670
|
+
...options.codexSessionsDir?.trim() ? { codexSessionsDir: resolve4(options.codexSessionsDir.trim()) } : {},
|
|
14825
16671
|
limitPerSource: parsePositiveInteger(options.sessionLimit, 3, "--session-limit"),
|
|
14826
16672
|
sinceDays: parsePositiveInteger(options.sessionDays, 30, "--session-days"),
|
|
14827
16673
|
sources
|
|
@@ -14861,8 +16707,8 @@ async function readWorkGraphInputs(options, interactive) {
|
|
|
14861
16707
|
const clientExtractions = readClientExtractions(options);
|
|
14862
16708
|
const investigationSources = parseInvestigationSourceList(options.from);
|
|
14863
16709
|
const investigationSourceData = investigationSources.length > 0 ? loadWorkGraphInvestigationSourceData({
|
|
14864
|
-
...options.claudeProjectsDir?.trim() ? { claudeProjectsDir:
|
|
14865
|
-
...options.codexSessionsDir?.trim() ? { codexSessionsDir:
|
|
16710
|
+
...options.claudeProjectsDir?.trim() ? { claudeProjectsDir: resolve4(options.claudeProjectsDir.trim()) } : {},
|
|
16711
|
+
...options.codexSessionsDir?.trim() ? { codexSessionsDir: resolve4(options.codexSessionsDir.trim()) } : {},
|
|
14866
16712
|
cwd: process.cwd(),
|
|
14867
16713
|
limitPerSource: parsePositiveInteger(options.sessionLimit, 8, "--session-limit"),
|
|
14868
16714
|
sinceDays: parsePositiveInteger(options.sessionDays, 45, "--session-days"),
|
|
@@ -14979,10 +16825,10 @@ async function runAuditCommand(options) {
|
|
|
14979
16825
|
workspace
|
|
14980
16826
|
});
|
|
14981
16827
|
const markdown = renderSelfAuditMarkdown(plan);
|
|
14982
|
-
const outputDir =
|
|
16828
|
+
const outputDir = resolve4(options.outputDir?.trim() || ".orgx/audits");
|
|
14983
16829
|
const timestamp = plan.generated_at.replace(/[:.]/g, "-");
|
|
14984
|
-
const jsonPath =
|
|
14985
|
-
const markdownPath =
|
|
16830
|
+
const jsonPath = resolve4(outputDir, `ai-native-self-audit-${timestamp}.json`);
|
|
16831
|
+
const markdownPath = resolve4(outputDir, `ai-native-self-audit-${timestamp}.md`);
|
|
14986
16832
|
writeJsonFile(jsonPath, plan);
|
|
14987
16833
|
writeTextFile(markdownPath, markdown);
|
|
14988
16834
|
if (options.json) {
|
|
@@ -15031,9 +16877,89 @@ async function runAuditCommand(options) {
|
|
|
15031
16877
|
console.log(` ${ICON.ok} ${pc3.green("follow-up ")} ${pc3.bold(followUp.title)} ${pc3.dim(followUp.id)}`);
|
|
15032
16878
|
}
|
|
15033
16879
|
}
|
|
16880
|
+
async function runOperatingMapCommand(queryParts, options) {
|
|
16881
|
+
const auth = await resolveOrgxAuth();
|
|
16882
|
+
if (!auth) {
|
|
16883
|
+
throw new Error("Operating-map discovery requires OrgX auth. Run `wizard auth login` first.");
|
|
16884
|
+
}
|
|
16885
|
+
const workspace = options.workspaceId?.trim() ? { id: options.workspaceId.trim(), name: options.workspaceId.trim() } : await getCurrentWorkspace();
|
|
16886
|
+
if (!workspace) {
|
|
16887
|
+
throw new Error("No current OrgX workspace found. Run `wizard workspace create <name>` first.");
|
|
16888
|
+
}
|
|
16889
|
+
const query = queryParts.join(" ").trim() || null;
|
|
16890
|
+
const mode = options.deepSearch ? "deep_search" : "bounded_sync";
|
|
16891
|
+
const sourceKinds = (options.source ?? "").split(",").map((source) => source.trim()).filter(Boolean);
|
|
16892
|
+
const idempotencyKey = options.idempotencyKey?.trim() || defaultOperatingMapIdempotencyKey({
|
|
16893
|
+
workspaceId: workspace.id,
|
|
16894
|
+
mode,
|
|
16895
|
+
query,
|
|
16896
|
+
sourceKinds
|
|
16897
|
+
});
|
|
16898
|
+
const spinner = createOrgxSpinner(mode === "deep_search" ? "Mapping workflows with cited deep search" : "Mapping observed workflows");
|
|
16899
|
+
spinner.start();
|
|
16900
|
+
const discovery = await startOperatingMapDiscovery({
|
|
16901
|
+
workspaceId: workspace.id,
|
|
16902
|
+
mode,
|
|
16903
|
+
query,
|
|
16904
|
+
sourceKinds,
|
|
16905
|
+
idempotencyKey
|
|
16906
|
+
});
|
|
16907
|
+
spinner.succeed(discovery.duplicate ? "Replayed the existing operating-map discovery" : "Operating-map discovery complete");
|
|
16908
|
+
const payload = {
|
|
16909
|
+
workspaceId: workspace.id,
|
|
16910
|
+
idempotencyKey,
|
|
16911
|
+
duplicate: discovery.duplicate,
|
|
16912
|
+
run: discovery.result.run,
|
|
16913
|
+
processCards: discovery.result.processCards
|
|
16914
|
+
};
|
|
16915
|
+
if (options.json) {
|
|
16916
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
16917
|
+
} else {
|
|
16918
|
+
console.log(` ${ICON.ok} ${pc3.green("observations ")} ${pc3.dim(String(discovery.result.run.observationCount))}`);
|
|
16919
|
+
console.log(` ${ICON.ok} ${pc3.green("candidates ")} ${pc3.dim(String(discovery.result.processCards.length))}`);
|
|
16920
|
+
console.log(` ${ICON.ok} ${pc3.green("citations ")} ${pc3.dim(String(discovery.result.run.citationCount))}`);
|
|
16921
|
+
for (const [index, card] of discovery.result.processCards.entries()) {
|
|
16922
|
+
console.log("");
|
|
16923
|
+
console.log(` ${pc3.bold(`${index + 1}. ${card.displayName}`)} ${pc3.dim(`${Math.round(card.confidence * 100)}% confidence \xB7 ${card.processCandidateRef.id}`)}`);
|
|
16924
|
+
if (card.nextConfirmationQuestion) console.log(` ${pc3.yellow("confirm:")} ${card.nextConfirmationQuestion}`);
|
|
16925
|
+
if (card.riskFlags.length > 0) console.log(` ${pc3.yellow("limits:")} ${card.riskFlags.join("; ")}`);
|
|
16926
|
+
}
|
|
16927
|
+
if (discovery.result.run.limitations.length > 0) {
|
|
16928
|
+
console.log("");
|
|
16929
|
+
console.log(` ${pc3.yellow("limitations:")} ${discovery.result.run.limitations.join("; ")}`);
|
|
16930
|
+
}
|
|
16931
|
+
}
|
|
16932
|
+
const candidate = options.candidate?.trim();
|
|
16933
|
+
if (!candidate) return;
|
|
16934
|
+
const selected = /^\d+$/.test(candidate) ? discovery.result.processCards[Number(candidate) - 1] : discovery.result.processCards.find((card) => card.processCandidateRef.id === candidate);
|
|
16935
|
+
if (!selected?.processCandidateRef.id) {
|
|
16936
|
+
throw new Error(`Process candidate ${candidate} was not found in discovery run ${discovery.result.run.id}.`);
|
|
16937
|
+
}
|
|
16938
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
16939
|
+
if (!options.yes) {
|
|
16940
|
+
if (!interactive) throw new Error("Proposing an OperatingProcess requires --yes in non-interactive mode.");
|
|
16941
|
+
const confirmed = await clack.confirm({
|
|
16942
|
+
message: `Propose \u201C${selected.displayName}\u201D as an OperatingProcess (still requires human confirmation)?`,
|
|
16943
|
+
initialValue: false
|
|
16944
|
+
});
|
|
16945
|
+
if (clack.isCancel(confirmed) || !confirmed) return;
|
|
16946
|
+
}
|
|
16947
|
+
const proposal = await proposeOperatingProcessFromMap({
|
|
16948
|
+
workspaceId: workspace.id,
|
|
16949
|
+
discoveryRunId: discovery.result.run.id,
|
|
16950
|
+
processCandidateId: selected.processCandidateRef.id,
|
|
16951
|
+
idempotencyKey: `wizard:operating-process:${discovery.result.run.id}:${selected.processCandidateRef.id}`
|
|
16952
|
+
});
|
|
16953
|
+
if (options.json) {
|
|
16954
|
+
console.log(JSON.stringify({ ...payload, proposal }, null, 2));
|
|
16955
|
+
} else {
|
|
16956
|
+
console.log(` ${ICON.ok} ${pc3.green("proposal ")} ${pc3.dim(selected.displayName)}`);
|
|
16957
|
+
console.log(` ${pc3.dim("Next step ")} Review and confirm the OperatingProcess in OrgX; the wizard never auto-activates inferred workflow ownership.`);
|
|
16958
|
+
}
|
|
16959
|
+
}
|
|
15034
16960
|
function runWorkGraphExtractionSchemaCommand(options) {
|
|
15035
16961
|
const protocol = buildWorkGraphExtractionProtocol();
|
|
15036
|
-
const outputPath = options.output?.trim() ?
|
|
16962
|
+
const outputPath = options.output?.trim() ? resolve4(options.output.trim()) : "";
|
|
15037
16963
|
if (outputPath) {
|
|
15038
16964
|
if (options.json) {
|
|
15039
16965
|
writeJsonFile(outputPath, protocol);
|
|
@@ -15063,8 +16989,8 @@ function normalizeRuntimePacketRole(role) {
|
|
|
15063
16989
|
}
|
|
15064
16990
|
function runWorkGraphRuntimeEventCommand(options) {
|
|
15065
16991
|
const source = normalizeRuntimePacketSource(options.source);
|
|
15066
|
-
const cwd =
|
|
15067
|
-
const outputRoot =
|
|
16992
|
+
const cwd = resolve4(options.cwd?.trim() || process.cwd());
|
|
16993
|
+
const outputRoot = resolve4(cwd, options.outputDir?.trim() || ".orgx/work-graph/runtime-events");
|
|
15068
16994
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15069
16995
|
const timestamp = generatedAt.replace(/[:.]/g, "-");
|
|
15070
16996
|
const summary = options.summary?.trim() || options.message?.trim();
|
|
@@ -15085,7 +17011,7 @@ function runWorkGraphRuntimeEventCommand(options) {
|
|
|
15085
17011
|
collection_method: "runtime_packet",
|
|
15086
17012
|
redaction_state: "agent_redacted"
|
|
15087
17013
|
};
|
|
15088
|
-
const path =
|
|
17014
|
+
const path = resolve4(outputRoot, source, `${timestamp}-${process.pid}.jsonl`);
|
|
15089
17015
|
writeTextFile(path, `${JSON.stringify(packet)}
|
|
15090
17016
|
`, { mode: 384 });
|
|
15091
17017
|
if (options.json) {
|
|
@@ -15128,11 +17054,11 @@ async function runWorkGraphCommand(options, defaults = {}) {
|
|
|
15128
17054
|
workspace
|
|
15129
17055
|
});
|
|
15130
17056
|
const markdown = renderWorkGraphMarkdown(report);
|
|
15131
|
-
const outputDir =
|
|
17057
|
+
const outputDir = resolve4(commandOptions.outputDir?.trim() || ".orgx/work-graph");
|
|
15132
17058
|
const timestamp = report.generated_at.replace(/[:.]/g, "-");
|
|
15133
|
-
const jsonPath =
|
|
15134
|
-
const markdownPath =
|
|
15135
|
-
const agentBriefPath =
|
|
17059
|
+
const jsonPath = resolve4(outputDir, `work-graph-report-${timestamp}.json`);
|
|
17060
|
+
const markdownPath = resolve4(outputDir, `work-graph-report-${timestamp}.md`);
|
|
17061
|
+
const agentBriefPath = resolve4(outputDir, `work-graph-agent-brief-${timestamp}.md`);
|
|
15136
17062
|
writeJsonFile(jsonPath, report);
|
|
15137
17063
|
writeTextFile(markdownPath, markdown);
|
|
15138
17064
|
let published = null;
|
|
@@ -15547,14 +17473,14 @@ async function readSingleKey() {
|
|
|
15547
17473
|
const stdin = process.stdin;
|
|
15548
17474
|
if (!stdin.isTTY) return null;
|
|
15549
17475
|
const previousRawMode = stdin.isRaw === true;
|
|
15550
|
-
return await new Promise((
|
|
17476
|
+
return await new Promise((resolve5) => {
|
|
15551
17477
|
const cleanup = (result) => {
|
|
15552
17478
|
stdin.off("data", onData);
|
|
15553
17479
|
if (stdin.isTTY) {
|
|
15554
17480
|
stdin.setRawMode(previousRawMode);
|
|
15555
17481
|
}
|
|
15556
17482
|
stdin.pause();
|
|
15557
|
-
|
|
17483
|
+
resolve5(result);
|
|
15558
17484
|
};
|
|
15559
17485
|
const onData = (chunk) => {
|
|
15560
17486
|
const text2 = chunk.toString("utf8");
|
|
@@ -16319,7 +18245,7 @@ async function main() {
|
|
|
16319
18245
|
initializeWizardSentry();
|
|
16320
18246
|
const program = new Command();
|
|
16321
18247
|
program.name("orgx-wizard").description("Add OrgX MCP configs, skills/rules, and companion plugins to your local AI tools.").showHelpAfterError();
|
|
16322
|
-
const pkgVersion = true ? "0.1.
|
|
18248
|
+
const pkgVersion = true ? "0.1.57" : void 0;
|
|
16323
18249
|
program.version(pkgVersion ?? "unknown", "-V, --version");
|
|
16324
18250
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
16325
18251
|
if (Boolean(actionCommand.optsWithGlobals().json)) return;
|
|
@@ -17040,6 +18966,15 @@ async function main() {
|
|
|
17040
18966
|
});
|
|
17041
18967
|
await runAuditCommand(options);
|
|
17042
18968
|
});
|
|
18969
|
+
program.command("map").alias("discovery").description("Map observed company workflows into evidence-gated OperatingProcess candidates.").argument("[query...]", "workflow, handoff, or system-of-record question for discovery/deep search").option("--deep-search", "query cited external research in addition to connected repository signals").option("--source <kinds>", "comma-separated source kinds to report and constrain (for example github,notion,slack)").option("--workspace-id <id>", "workspace id override; defaults to the current OrgX workspace").option("--candidate <id-or-number>", "propose one returned ProcessCard by id or 1-based display number").option("--idempotency-key <key>", "stable retry key; defaults to a hash of workspace, mode, query, and sources").option("--yes", "approve the explicit proposal in non-interactive mode").option("--json", "emit machine-readable discovery/proposal output").action(async (queryParts, options) => {
|
|
18970
|
+
await safeTrackWizardTelemetry("operating_map_started", {
|
|
18971
|
+
command: "map",
|
|
18972
|
+
deep_search: Boolean(options.deepSearch),
|
|
18973
|
+
has_candidate: Boolean(options.candidate),
|
|
18974
|
+
json: Boolean(options.json)
|
|
18975
|
+
});
|
|
18976
|
+
await runOperatingMapCommand(queryParts, options);
|
|
18977
|
+
});
|
|
17043
18978
|
const workGraph = program.command("work-graph").description("Run AQ from real AI-work receipts and surface the first repair that raises execution capacity.");
|
|
17044
18979
|
workGraph.command("extraction-schema").description("Print the packaged AI-client audit skill used to search sessions, messages, tools, domains, and logs.").option("--output <path>", "write the schema prompt to a file").option("--json", "emit the protocol as JSON instead of Markdown").action(async (options) => {
|
|
17045
18980
|
await safeTrackWizardTelemetry("work_graph_extraction_schema_started", {
|
|
@@ -17110,9 +19045,15 @@ async function main() {
|
|
|
17110
19045
|
}
|
|
17111
19046
|
}
|
|
17112
19047
|
});
|
|
19048
|
+
hooks.command("flush").description("Deliver queued session summaries and retain every unacknowledged capture.").option("--queue <path>", "session-summary capture queue path").option("--limit <count>", "maximum queued captures to attempt", "100").option("--background", "run quietly as a hook-triggered delivery worker").option("--json", "emit a JSON command summary").action(async (options) => {
|
|
19049
|
+
await runSessionSummaryFlushCommand(options);
|
|
19050
|
+
});
|
|
17113
19051
|
hooks.command("replay").description("Replay passive hook outbox events into a claimed Work Graph profile.").requiredOption("--fingerprint <fingerprint>", "target Work Graph fingerprint, for example wgf_0123...").option("--outbox <path>", "runtime hook JSONL outbox path").option("--limit <count>", "maximum recent hook events to replay", "200").option("--yes", "approve publishing hook evidence without prompting").option("--json", "emit a JSON command summary").action(async (options) => {
|
|
17114
19052
|
await runHookReplayCommand(options);
|
|
17115
19053
|
});
|
|
19054
|
+
hooks.command("backfill").description("Distill the historical hook spool into lean session summaries (preview by default).").option("--spool <path>", "runtime hook JSONL spool path").option("--post", "upload the distilled summaries to OrgX (requires --yes)").option("--truncate", "archive and empty the spool after a fully successful --post (requires --yes)").option("--yes", "confirm uploading or truncating your local work history").option("--json", "emit a JSON command summary").action(async (options) => {
|
|
19055
|
+
await runHookBackfillCommand(options);
|
|
19056
|
+
});
|
|
17116
19057
|
program.command("doctor").description("Verify OrgX health, or diagnose a credential-free workload shape.").option("--workload <path>", "diagnose a sanitized workload-shape JSON file; use - for stdin").option("--base-url <url>", "explicit workload endpoint override for testing or self-hosting").option("--json", "emit the selected doctor result as JSON").action(async (options) => {
|
|
17117
19058
|
if (options.baseUrl && !options.workload) {
|
|
17118
19059
|
const message = "--base-url is only valid together with --workload.";
|
|
@@ -17348,4 +19289,4 @@ main().catch(async (error) => {
|
|
|
17348
19289
|
process.exitCode = 1;
|
|
17349
19290
|
});
|
|
17350
19291
|
//# sourceMappingURL=cli.js.map
|
|
17351
|
-
//# debugId=
|
|
19292
|
+
//# debugId=8eca99e4-958f-5704-a1cb-bd96bbd10293
|