harness-wingman 0.4.0 → 0.4.2
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 +1 -1
- package/apps/cli/dist/main.js +1115 -224
- package/apps/panel/dist/assets/index-Bi0Z36tF.js +209 -0
- package/apps/panel/dist/assets/index-DCNNABGS.css +1 -0
- package/apps/panel/dist/index.html +2 -2
- package/package.json +3 -2
- package/apps/panel/dist/assets/index-CP99dQS1.css +0 -1
- package/apps/panel/dist/assets/index-N-unp8i7.js +0 -202
package/apps/cli/dist/main.js
CHANGED
|
@@ -48,6 +48,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
48
48
|
mod
|
|
49
49
|
));
|
|
50
50
|
|
|
51
|
+
// packages/core/dist/feedback-url.js
|
|
52
|
+
var init_feedback_url = __esm({
|
|
53
|
+
"packages/core/dist/feedback-url.js"() {
|
|
54
|
+
"use strict";
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
51
58
|
// packages/core/dist/refscan.js
|
|
52
59
|
function scanReferences(sources, name) {
|
|
53
60
|
if (name.length === 0)
|
|
@@ -228,6 +235,7 @@ var SLOT_IDS, HARNESS_IDS, ACTION_KINDS, SOURCES_BY_HARNESS, CONFIG_KEYS, ITEM_K
|
|
|
228
235
|
var init_dist = __esm({
|
|
229
236
|
"packages/core/dist/index.js"() {
|
|
230
237
|
"use strict";
|
|
238
|
+
init_feedback_url();
|
|
231
239
|
init_refscan();
|
|
232
240
|
SLOT_IDS = ["base", "rules", "skills", "tools", "gates", "history"];
|
|
233
241
|
HARNESS_IDS = ["cc", "codex", "dsh"];
|
|
@@ -1185,10 +1193,10 @@ function readState(homeDir) {
|
|
|
1185
1193
|
}
|
|
1186
1194
|
return rec;
|
|
1187
1195
|
}
|
|
1188
|
-
function writeState(homeDir,
|
|
1196
|
+
function writeState(homeDir, state) {
|
|
1189
1197
|
const file = statePath(homeDir);
|
|
1190
1198
|
mkdirSync3(dirname5(file), { recursive: true });
|
|
1191
|
-
writeFileSync4(file, `${JSON.stringify(
|
|
1199
|
+
writeFileSync4(file, `${JSON.stringify(state, null, 2)}
|
|
1192
1200
|
`);
|
|
1193
1201
|
}
|
|
1194
1202
|
function makeBackupDir(homeDir) {
|
|
@@ -1347,7 +1355,7 @@ async function installHooksCc(port, options) {
|
|
|
1347
1355
|
async function uninstallHooksCc(options) {
|
|
1348
1356
|
const homeDir = options?.homeDir ?? homedir6();
|
|
1349
1357
|
const target = targetPath(homeDir);
|
|
1350
|
-
const
|
|
1358
|
+
const state = readState(homeDir);
|
|
1351
1359
|
if (!existsSync5(target)) {
|
|
1352
1360
|
rmSync2(statePath(homeDir), { force: true });
|
|
1353
1361
|
return;
|
|
@@ -1361,14 +1369,14 @@ async function uninstallHooksCc(options) {
|
|
|
1361
1369
|
const backupDir = makeBackupDir(homeDir);
|
|
1362
1370
|
if (bytes !== null)
|
|
1363
1371
|
writeFileSync4(join9(backupDir, "settings.json"), bytes);
|
|
1364
|
-
if (
|
|
1365
|
-
const originalParsed = asRecord3(JSON.parse(
|
|
1372
|
+
if (state && state.original !== null) {
|
|
1373
|
+
const originalParsed = asRecord3(JSON.parse(state.original));
|
|
1366
1374
|
if (originalParsed && deepEqual2(normalizeForCompare(settings), normalizeForCompare(originalParsed))) {
|
|
1367
|
-
writeFileSync4(target,
|
|
1375
|
+
writeFileSync4(target, state.original);
|
|
1368
1376
|
rmSync2(statePath(homeDir), { force: true });
|
|
1369
1377
|
return;
|
|
1370
1378
|
}
|
|
1371
|
-
} else if (
|
|
1379
|
+
} else if (state && state.original === null) {
|
|
1372
1380
|
if (deepEqual2(normalizeForCompare(settings), {})) {
|
|
1373
1381
|
rmSync2(target, { force: true });
|
|
1374
1382
|
rmSync2(statePath(homeDir), { force: true });
|
|
@@ -1631,33 +1639,33 @@ function parseString(ctx) {
|
|
|
1631
1639
|
}
|
|
1632
1640
|
let parsed = "";
|
|
1633
1641
|
let sliceStart = ctx.p;
|
|
1634
|
-
let
|
|
1642
|
+
let state = 0;
|
|
1635
1643
|
for (; ctx.p < ctx.s.length; ctx.p++) {
|
|
1636
1644
|
c = ctx.s.charCodeAt(ctx.p);
|
|
1637
1645
|
if (isMultiline && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) {
|
|
1638
|
-
|
|
1646
|
+
state = state && 3;
|
|
1639
1647
|
} else if (c < 32 && c !== 9 || c === 127) {
|
|
1640
1648
|
throw new TomlError("control characters are not allowed in strings", {
|
|
1641
1649
|
toml: ctx.s,
|
|
1642
1650
|
ptr: ctx.p
|
|
1643
1651
|
});
|
|
1644
|
-
} else if ((!
|
|
1652
|
+
} else if ((!state || state === 3) && c === first && (!isMultiline || ctx.s.charCodeAt(ctx.p + 1) === first && ctx.s.charCodeAt(ctx.p + 2) === first)) {
|
|
1645
1653
|
if (isMultiline) {
|
|
1646
1654
|
if (ctx.s.charCodeAt(ctx.p + 3) === first)
|
|
1647
1655
|
ctx.p++;
|
|
1648
1656
|
if (ctx.s.charCodeAt(ctx.p + 3) === first)
|
|
1649
1657
|
ctx.p++;
|
|
1650
1658
|
}
|
|
1651
|
-
if (!
|
|
1659
|
+
if (!state)
|
|
1652
1660
|
parsed += ctx.s.slice(sliceStart, ctx.p);
|
|
1653
1661
|
ctx.p += isMultiline ? 3 : 1;
|
|
1654
1662
|
return parsed;
|
|
1655
|
-
} else if (!
|
|
1663
|
+
} else if (!state) {
|
|
1656
1664
|
if (!isLiteral && c === 92) {
|
|
1657
1665
|
parsed += ctx.s.slice(sliceStart, sliceStart = ctx.p);
|
|
1658
|
-
|
|
1666
|
+
state = 1;
|
|
1659
1667
|
}
|
|
1660
|
-
} else if (
|
|
1668
|
+
} else if (state === 1) {
|
|
1661
1669
|
if (c === 120 || c === 117 || c === 85) {
|
|
1662
1670
|
let value = 0;
|
|
1663
1671
|
let len = c === 120 ? 2 : c === 117 ? 4 : 8;
|
|
@@ -1682,9 +1690,9 @@ function parseString(ctx) {
|
|
|
1682
1690
|
}
|
|
1683
1691
|
parsed += String.fromCodePoint(value);
|
|
1684
1692
|
sliceStart = ctx.p + 1;
|
|
1685
|
-
|
|
1693
|
+
state = 0;
|
|
1686
1694
|
} else if (c === 32 || c === 9) {
|
|
1687
|
-
|
|
1695
|
+
state = 2;
|
|
1688
1696
|
} else {
|
|
1689
1697
|
if (c === 98)
|
|
1690
1698
|
parsed += "\b";
|
|
@@ -1705,16 +1713,16 @@ function parseString(ctx) {
|
|
|
1705
1713
|
else
|
|
1706
1714
|
throw new TomlError("unrecognized escape sequence", { toml: ctx.s, ptr: ctx.p });
|
|
1707
1715
|
sliceStart = ctx.p + 1;
|
|
1708
|
-
|
|
1716
|
+
state = 0;
|
|
1709
1717
|
}
|
|
1710
1718
|
} else if (c !== 32 && c !== 9) {
|
|
1711
|
-
if (
|
|
1719
|
+
if (state === 2) {
|
|
1712
1720
|
throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
|
|
1713
1721
|
toml: ctx.s,
|
|
1714
1722
|
ptr: sliceStart
|
|
1715
1723
|
});
|
|
1716
1724
|
}
|
|
1717
|
-
|
|
1725
|
+
state = !isLiteral && c === 92 ? 1 : 0;
|
|
1718
1726
|
sliceStart = ctx.p;
|
|
1719
1727
|
}
|
|
1720
1728
|
}
|
|
@@ -1977,15 +1985,15 @@ function peekTable(key, table, meta, type) {
|
|
|
1977
1985
|
let m = meta;
|
|
1978
1986
|
let k;
|
|
1979
1987
|
let hasOwn = false;
|
|
1980
|
-
let
|
|
1988
|
+
let state;
|
|
1981
1989
|
for (let i = 0; i < key.length; i++) {
|
|
1982
1990
|
if (i) {
|
|
1983
1991
|
t = hasOwn ? t[k] : t[k] = {};
|
|
1984
|
-
m = (
|
|
1985
|
-
if (type === 0 && (
|
|
1992
|
+
m = (state = m[k]).c;
|
|
1993
|
+
if (type === 0 && (state.t === 1 || state.t === 2)) {
|
|
1986
1994
|
return null;
|
|
1987
1995
|
}
|
|
1988
|
-
if (
|
|
1996
|
+
if (state.t === 2) {
|
|
1989
1997
|
let l = t.length - 1;
|
|
1990
1998
|
t = t[l];
|
|
1991
1999
|
m = m[l].c;
|
|
@@ -2008,28 +2016,28 @@ function peekTable(key, table, meta, type) {
|
|
|
2008
2016
|
};
|
|
2009
2017
|
}
|
|
2010
2018
|
}
|
|
2011
|
-
|
|
2012
|
-
if (
|
|
2019
|
+
state = m[k];
|
|
2020
|
+
if (state.t !== type && !(type === 1 && state.t === 3)) {
|
|
2013
2021
|
return null;
|
|
2014
2022
|
}
|
|
2015
2023
|
if (type === 2) {
|
|
2016
|
-
if (!
|
|
2017
|
-
|
|
2024
|
+
if (!state.d) {
|
|
2025
|
+
state.d = true;
|
|
2018
2026
|
t[k] = [];
|
|
2019
2027
|
}
|
|
2020
2028
|
t[k].push(t = {});
|
|
2021
|
-
|
|
2029
|
+
state.c[state.i++] = state = { t: 1, d: false, i: 0, c: {} };
|
|
2022
2030
|
}
|
|
2023
|
-
if (
|
|
2031
|
+
if (state.d) {
|
|
2024
2032
|
return null;
|
|
2025
2033
|
}
|
|
2026
|
-
|
|
2034
|
+
state.d = true;
|
|
2027
2035
|
if (type === 1) {
|
|
2028
2036
|
t = hasOwn ? t[k] : t[k] = {};
|
|
2029
2037
|
} else if (type === 0 && hasOwn) {
|
|
2030
2038
|
return null;
|
|
2031
2039
|
}
|
|
2032
|
-
return [k, t,
|
|
2040
|
+
return [k, t, state.c];
|
|
2033
2041
|
}
|
|
2034
2042
|
function parse2(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
|
|
2035
2043
|
let ctx = { s: toml, p: 0, d: maxDepth };
|
|
@@ -2846,16 +2854,30 @@ async function installHooks(port, options) {
|
|
|
2846
2854
|
const raw = existsSync7(configPath) ? readFileSync8(configPath) : void 0;
|
|
2847
2855
|
const text = raw?.toString("utf8") ?? "";
|
|
2848
2856
|
const { remainder, block } = splitManagedBlock(text, configPath);
|
|
2849
|
-
const newBlock = buildManagedBlock(scriptPath
|
|
2857
|
+
const newBlock = buildManagedBlock(scriptPath);
|
|
2858
|
+
if (block !== void 0 && !isKnownManagedBlock(block, scriptPath)) {
|
|
2859
|
+
throw new Error(`Wingman Codex hook \u53D7\u7BA1\u5757\u5DF2\u88AB\u4FEE\u6539\u6216\u6B8B\u7F3A(${configPath}),\u62D2\u7EDD\u8C0E\u62A5\u63A5\u7EBF\u6210\u529F;\u8BF7\u5148\u4ECE <wingman\u6839>/backups/ \u6062\u590D\u5B8C\u6574\u5757,\u6216\u4EBA\u5DE5\u6838\u5BF9\u540E\u79FB\u9664\u8BE5\u53D7\u7BA1\u5757\u518D\u91CD\u65B0\u5B89\u88C5\u5E76\u4FE1\u4EFB`);
|
|
2860
|
+
}
|
|
2850
2861
|
mkdirSync5(root, { recursive: true });
|
|
2851
2862
|
writeFileSync6(scriptPath, FORWARD_SCRIPT);
|
|
2852
|
-
|
|
2863
|
+
const statePath2 = hooksStatePath(root);
|
|
2864
|
+
const prior = readState2(statePath2);
|
|
2865
|
+
if (block !== void 0) {
|
|
2866
|
+
if (prior?.port !== port || prior.configPath !== configPath || prior.forwardScriptPath !== scriptPath) {
|
|
2867
|
+
writeState2(statePath2, {
|
|
2868
|
+
...prior,
|
|
2869
|
+
version: 1,
|
|
2870
|
+
port,
|
|
2871
|
+
configPath,
|
|
2872
|
+
forwardScriptPath: scriptPath,
|
|
2873
|
+
installedAt: prior?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2853
2876
|
return;
|
|
2877
|
+
}
|
|
2854
2878
|
assertWritable(remainder, configPath);
|
|
2855
2879
|
const backupDir = raw === void 0 ? void 0 : backupFile(root, configPath, raw);
|
|
2856
|
-
const
|
|
2857
|
-
const prior = readState2(statePath2);
|
|
2858
|
-
const original = prior?.original ?? (block === void 0 ? raw === void 0 ? { existed: false } : { existed: true, base64: raw.toString("base64") } : void 0);
|
|
2880
|
+
const original = prior?.original ?? (raw === void 0 ? { existed: false } : { existed: true, base64: raw.toString("base64") });
|
|
2859
2881
|
writeState2(statePath2, {
|
|
2860
2882
|
version: 1,
|
|
2861
2883
|
port,
|
|
@@ -2877,14 +2899,17 @@ async function uninstallHooks(options) {
|
|
|
2877
2899
|
const scriptPath = join12(root, FORWARD_SCRIPT_NAME);
|
|
2878
2900
|
const statePath2 = hooksStatePath(root);
|
|
2879
2901
|
const configPath = join12(paths.codexHome, "config.toml");
|
|
2880
|
-
const
|
|
2902
|
+
const state = readState2(statePath2);
|
|
2881
2903
|
const raw = existsSync7(configPath) ? readFileSync8(configPath) : void 0;
|
|
2882
2904
|
if (raw !== void 0) {
|
|
2883
2905
|
const text = raw.toString("utf8");
|
|
2884
2906
|
const { remainder, block } = splitManagedBlock(text, configPath);
|
|
2885
2907
|
if (block !== void 0) {
|
|
2908
|
+
if (!isKnownManagedBlock(block, scriptPath)) {
|
|
2909
|
+
throw new Error(`Wingman Codex hook \u53D7\u7BA1\u5757\u5DF2\u88AB\u4FEE\u6539\u6216\u6B8B\u7F3A(${configPath}),\u62D2\u7EDD\u81EA\u52A8\u5220\u9664;\u8BF7\u5148\u4ECE <wingman\u6839>/backups/ \u6062\u590D\u5B8C\u6574\u5757,\u6216\u4EBA\u5DE5\u6838\u5BF9\u540E\u79FB\u9664\u8BE5\u53D7\u7BA1\u5757`);
|
|
2910
|
+
}
|
|
2886
2911
|
backupFile(root, configPath, raw);
|
|
2887
|
-
const restored = restoreTarget(remainder,
|
|
2912
|
+
const restored = restoreTarget(remainder, state);
|
|
2888
2913
|
if (restored === null)
|
|
2889
2914
|
rmSync3(configPath);
|
|
2890
2915
|
else
|
|
@@ -2898,10 +2923,15 @@ function hasManagedHooks(paths) {
|
|
|
2898
2923
|
const configPath = join12(paths.codexHome, "config.toml");
|
|
2899
2924
|
if (!existsSync7(configPath))
|
|
2900
2925
|
return false;
|
|
2901
|
-
|
|
2926
|
+
try {
|
|
2927
|
+
const { block } = splitManagedBlock(readFileSync8(configPath, "utf8"), configPath);
|
|
2928
|
+
return block !== void 0 && isKnownManagedBlock(block, join12(wingmanStateRoot(paths), FORWARD_SCRIPT_NAME));
|
|
2929
|
+
} catch {
|
|
2930
|
+
return false;
|
|
2931
|
+
}
|
|
2902
2932
|
}
|
|
2903
|
-
function restoreTarget(remainder,
|
|
2904
|
-
const original =
|
|
2933
|
+
function restoreTarget(remainder, state) {
|
|
2934
|
+
const original = state?.original;
|
|
2905
2935
|
if (original === void 0)
|
|
2906
2936
|
return remainder;
|
|
2907
2937
|
if (!original.existed) {
|
|
@@ -2915,8 +2945,11 @@ function restoreTarget(remainder, state2) {
|
|
|
2915
2945
|
}
|
|
2916
2946
|
return remainder;
|
|
2917
2947
|
}
|
|
2918
|
-
function buildManagedBlock(scriptPath
|
|
2919
|
-
|
|
2948
|
+
function buildManagedBlock(scriptPath) {
|
|
2949
|
+
return buildManagedBlockForCommand(`node "${scriptPath}"`);
|
|
2950
|
+
}
|
|
2951
|
+
function buildManagedBlockForCommand(rawCommand) {
|
|
2952
|
+
const command = tomlBasicString(rawCommand);
|
|
2920
2953
|
const handler = `[{ hooks = [{ type = "command", command = ${command} }] }]`;
|
|
2921
2954
|
const lines = [MANAGED_BLOCK_START, "[hooks]"];
|
|
2922
2955
|
for (const event of HOOK_EVENTS)
|
|
@@ -2925,6 +2958,38 @@ function buildManagedBlock(scriptPath, port) {
|
|
|
2925
2958
|
return `${lines.join("\n")}
|
|
2926
2959
|
`;
|
|
2927
2960
|
}
|
|
2961
|
+
function isKnownManagedBlock(block, scriptPath) {
|
|
2962
|
+
if (block === buildManagedBlock(scriptPath))
|
|
2963
|
+
return true;
|
|
2964
|
+
let table;
|
|
2965
|
+
try {
|
|
2966
|
+
table = parse2(block);
|
|
2967
|
+
} catch {
|
|
2968
|
+
return false;
|
|
2969
|
+
}
|
|
2970
|
+
const hooks = table.hooks;
|
|
2971
|
+
if (!isTable(hooks))
|
|
2972
|
+
return false;
|
|
2973
|
+
const first = hooks[HOOK_EVENTS[0]];
|
|
2974
|
+
if (!Array.isArray(first) || first.length !== 1 || !isTable(first[0]))
|
|
2975
|
+
return false;
|
|
2976
|
+
const handlers = first[0].hooks;
|
|
2977
|
+
if (!Array.isArray(handlers) || handlers.length !== 1 || !isTable(handlers[0]))
|
|
2978
|
+
return false;
|
|
2979
|
+
const command = handlers[0].command;
|
|
2980
|
+
if (typeof command !== "string")
|
|
2981
|
+
return false;
|
|
2982
|
+
const prefix = `node "${scriptPath}" `;
|
|
2983
|
+
if (!command.startsWith(prefix))
|
|
2984
|
+
return false;
|
|
2985
|
+
const legacyPortText = command.slice(prefix.length);
|
|
2986
|
+
if (!/^[1-9]\d{0,4}$/.test(legacyPortText))
|
|
2987
|
+
return false;
|
|
2988
|
+
const legacyPort = Number(legacyPortText);
|
|
2989
|
+
if (legacyPort > 65535 || String(legacyPort) !== legacyPortText)
|
|
2990
|
+
return false;
|
|
2991
|
+
return block === buildManagedBlockForCommand(command);
|
|
2992
|
+
}
|
|
2928
2993
|
function splitManagedBlock(text, configPath) {
|
|
2929
2994
|
const start = text.indexOf(MANAGED_BLOCK_START);
|
|
2930
2995
|
const endMark = text.indexOf(MANAGED_BLOCK_END);
|
|
@@ -2980,9 +3045,9 @@ function readState2(statePath2) {
|
|
|
2980
3045
|
return void 0;
|
|
2981
3046
|
}
|
|
2982
3047
|
}
|
|
2983
|
-
function writeState2(statePath2,
|
|
3048
|
+
function writeState2(statePath2, state) {
|
|
2984
3049
|
mkdirSync5(dirname7(statePath2), { recursive: true });
|
|
2985
|
-
writeFileSync6(statePath2, `${JSON.stringify(
|
|
3050
|
+
writeFileSync6(statePath2, `${JSON.stringify(state, null, 2)}
|
|
2986
3051
|
`);
|
|
2987
3052
|
}
|
|
2988
3053
|
function deepEqual3(a, b) {
|
|
@@ -3013,19 +3078,123 @@ var init_hooks2 = __esm({
|
|
|
3013
3078
|
FORWARD_SCRIPT_NAME = "codex-forward.mjs";
|
|
3014
3079
|
FORWARD_SCRIPT = `#!/usr/bin/env node
|
|
3015
3080
|
// wingman codex hook \u8F6C\u53D1\u811A\u672C(\u81EA\u52A8\u751F\u6210,\`wingman hook uninstall codex\` \u65F6\u5220\u9664)\u3002
|
|
3016
|
-
// \u8BFB stdin \u5168\u91CF(codex hook \u7684 CC \u57FA\u51C6\u5F62\u72B6 JSON)\u2192 \u539F\u6837 POST \u672C\u673A wingman \u9762\u677F
|
|
3017
|
-
//
|
|
3018
|
-
|
|
3081
|
+
// \u8BFB stdin \u5168\u91CF(codex hook \u7684 CC \u57FA\u51C6\u5F62\u72B6 JSON)\u2192 \u539F\u6837 POST \u672C\u673A wingman \u9762\u677F\u3002
|
|
3082
|
+
// \u53EA\u8BA4 serve \u5C31\u7EEA\u540E\u5199\u4E0B\u7684 v2 \u8FD0\u884C\u6001\uFF1B\u5148\u505A HMAC \u8EAB\u4EFD\u63A2\u6D4B\uFF0C\u9A8C\u8BC1\u901A\u8FC7\u624D\u53D1\u9001\u4F1A\u8BDD\u5185\u5BB9\u3002
|
|
3083
|
+
// \u5019\u9009\u6709\u754C,\u8EAB\u4EFD\u63A2\u6D4B\u5E76\u884C\u4E14\u5171\u4EAB\u603B\u7F51\u7EDC\u9884\u7B97;\u4EFB\u4F55\u5931\u8D25\u9759\u9ED8\u9000\u51FA 0,\u7EDD\u4E0D\u963B\u585E harness\u3002
|
|
3084
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3085
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3086
|
+
import { dirname, join } from 'node:path';
|
|
3087
|
+
import { fileURLToPath } from 'node:url';
|
|
3088
|
+
|
|
3089
|
+
const root = dirname(fileURLToPath(import.meta.url));
|
|
3090
|
+
const stateRoot = join(root, 'state');
|
|
3091
|
+
const stateName = /^serve-(\\d+)-(\\d+)-[A-Za-z0-9_-]+\\.json$/;
|
|
3092
|
+
const maxCandidates = 5;
|
|
3093
|
+
const probeTimeoutMs = 1000;
|
|
3094
|
+
const postTimeoutMs = 3000;
|
|
3095
|
+
const totalNetworkBudgetMs = 4500;
|
|
3096
|
+
|
|
3097
|
+
function validPort(value) {
|
|
3098
|
+
return Number.isInteger(value) && value > 0 && value <= 65535;
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
function validToken(value) {
|
|
3102
|
+
return typeof value === 'string' && /^[A-Za-z0-9_-]{32,128}$/.test(value);
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3105
|
+
async function candidates() {
|
|
3106
|
+
try {
|
|
3107
|
+
const dir = join(stateRoot, 'serve');
|
|
3108
|
+
const names = await readdir(dir);
|
|
3109
|
+
const selected = names
|
|
3110
|
+
.flatMap((name) => {
|
|
3111
|
+
const match = stateName.exec(name);
|
|
3112
|
+
return match === null ? [] : [{ name, startedAtMs: Number(match[1]) }];
|
|
3113
|
+
})
|
|
3114
|
+
.filter((entry) => Number.isSafeInteger(entry.startedAtMs) && entry.startedAtMs >= 0)
|
|
3115
|
+
.sort((a, b) => b.startedAtMs - a.startedAtMs)
|
|
3116
|
+
.slice(0, maxCandidates);
|
|
3117
|
+
const states = await Promise.all(
|
|
3118
|
+
selected.map(async ({ name }) => {
|
|
3119
|
+
try {
|
|
3120
|
+
const state = JSON.parse(await readFile(join(dir, name), 'utf8'));
|
|
3121
|
+
return state.version === 2 && validPort(state.port) && validToken(state.token)
|
|
3122
|
+
? { port: state.port, token: state.token }
|
|
3123
|
+
: undefined;
|
|
3124
|
+
} catch {
|
|
3125
|
+
return undefined;
|
|
3126
|
+
}
|
|
3127
|
+
}),
|
|
3128
|
+
);
|
|
3129
|
+
return states.filter(Boolean);
|
|
3130
|
+
} catch {
|
|
3131
|
+
return [];
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
function sameProof(expected, actual) {
|
|
3136
|
+
if (typeof actual !== 'string') return false;
|
|
3137
|
+
const left = Buffer.from(expected);
|
|
3138
|
+
const right = Buffer.from(actual);
|
|
3139
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
async function verified(candidate) {
|
|
3143
|
+
const nonce = randomBytes(18).toString('base64url');
|
|
3144
|
+
const expected = createHmac('sha256', candidate.token).update(nonce).digest('base64url');
|
|
3145
|
+
try {
|
|
3146
|
+
const response = await fetch(
|
|
3147
|
+
\`http://127.0.0.1:\${candidate.port}/api/hook-target?nonce=\${nonce}\`,
|
|
3148
|
+
// \u51B7\u542F\u52A8\u6216\u9AD8\u8D1F\u8F7D\u673A\u5668\u4E0A Node/SEA \u53EF\u80FD\u6570\u767E\u6BEB\u79D2\u624D\u8C03\u5EA6\u5230\u672C\u673A\u54CD\u5E94\uFF1B
|
|
3149
|
+
// \u4ECD\u7ED9\u6BCF\u4E2A\u5019\u9009\u72EC\u7ACB\u4E0A\u9650\uFF0C\u4F46\u4E0D\u8981\u56E0 400ms \u8FC7\u7D27\u800C\u8BEF\u4E22\u771F\u5B9E hook\u3002
|
|
3150
|
+
// \u7981\u6B62\u8DDF\u968F\u91CD\u5B9A\u5411\uFF1A\u5426\u5219\u590D\u7528\u7AEF\u53E3\u53EF\u628A\u63A2\u6D4B\u8F6C\u53D1\u7ED9\u771F\u6B63 Wingman\u3001\u518D\u9A97\u53D6\u6B63\u6587\u3002
|
|
3151
|
+
{ signal: AbortSignal.timeout(probeTimeoutMs), redirect: 'error' },
|
|
3152
|
+
);
|
|
3153
|
+
return response.status === 204 && sameProof(expected, response.headers.get('x-wingman-hook-proof'));
|
|
3154
|
+
} catch {
|
|
3155
|
+
return false;
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3019
3159
|
const chunks = [];
|
|
3020
3160
|
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
3021
3161
|
process.stdin.on('end', async () => {
|
|
3022
3162
|
try {
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3163
|
+
const body = Buffer.concat(chunks);
|
|
3164
|
+
const networkStartedAt = performance.now();
|
|
3165
|
+
const available = await candidates();
|
|
3166
|
+
// \u8EAB\u4EFD\u63A2\u6D4B\u4E0D\u542B\u6B63\u6587\uFF0C\u53EF\u5B89\u5168\u5E76\u884C\uFF1B\u5171\u4EAB deadline \u907F\u514D\u5E95\u5C42 fetch
|
|
3167
|
+
// \u5BF9\u591A\u4E2A\u6302\u8D77 socket \u9010\u4E2A\u5EF6\u8FDF\u8D85\u65F6\u3002\u53EA\u8BB0\u5F55 deadline \u524D\u5DF2\u5B8C\u6210\u9A8C\u8BC1\u7684\u5019\u9009\u3002
|
|
3168
|
+
const checks = Array(available.length);
|
|
3169
|
+
const probes = available.map(async (candidate, index) => {
|
|
3170
|
+
if (await verified(candidate)) checks[index] = candidate;
|
|
3028
3171
|
});
|
|
3172
|
+
let deadline;
|
|
3173
|
+
await Promise.race([
|
|
3174
|
+
Promise.all(probes),
|
|
3175
|
+
new Promise((resolve) => {
|
|
3176
|
+
deadline = setTimeout(resolve, probeTimeoutMs + 250);
|
|
3177
|
+
}),
|
|
3178
|
+
]);
|
|
3179
|
+
clearTimeout(deadline);
|
|
3180
|
+
for (const candidate of checks.filter(Boolean)) {
|
|
3181
|
+
const remainingMs = totalNetworkBudgetMs - (performance.now() - networkStartedAt);
|
|
3182
|
+
if (remainingMs <= 0) break;
|
|
3183
|
+
try {
|
|
3184
|
+
const response = await fetch(\`http://127.0.0.1:\${candidate.port}/hook?harness=codex\`, {
|
|
3185
|
+
method: 'POST',
|
|
3186
|
+
headers: {
|
|
3187
|
+
'content-type': 'application/json',
|
|
3188
|
+
'x-wingman-hook-token': candidate.token,
|
|
3189
|
+
},
|
|
3190
|
+
body,
|
|
3191
|
+
// \u5DF2\u9A8C\u8BC1\u4E3A\u672C\u5B9E\u4F8B\u540E\u7ED9\u6B63\u6587\u63A5\u6536\u7559\u8DB3\u65F6\u95F4\uFF1B\u5927 payload/\u6162\u78C1\u76D8\u4E0D\u5E94\u88AB 1s \u8BEF\u6740\u3002
|
|
3192
|
+
signal: AbortSignal.timeout(Math.max(1, Math.min(postTimeoutMs, Math.ceil(remainingMs)))),
|
|
3193
|
+
redirect: 'error',
|
|
3194
|
+
});
|
|
3195
|
+
if (response.ok) break;
|
|
3196
|
+
} catch {}
|
|
3197
|
+
}
|
|
3029
3198
|
} catch {}
|
|
3030
3199
|
process.exit(0);
|
|
3031
3200
|
});
|
|
@@ -9134,11 +9303,11 @@ var require_lexer = __commonJS({
|
|
|
9134
9303
|
hasChars(n) {
|
|
9135
9304
|
return this.pos + n <= this.buffer.length;
|
|
9136
9305
|
}
|
|
9137
|
-
setNext(
|
|
9306
|
+
setNext(state) {
|
|
9138
9307
|
this.buffer = this.buffer.substring(this.pos);
|
|
9139
9308
|
this.pos = 0;
|
|
9140
9309
|
this.lineEndPos = null;
|
|
9141
|
-
this.next =
|
|
9310
|
+
this.next = state;
|
|
9142
9311
|
return null;
|
|
9143
9312
|
}
|
|
9144
9313
|
peek(n) {
|
|
@@ -11028,14 +11197,14 @@ async function readState3(paths) {
|
|
|
11028
11197
|
const buffer = await readIfExists(paths.stateFile);
|
|
11029
11198
|
if (buffer === void 0)
|
|
11030
11199
|
return void 0;
|
|
11031
|
-
const
|
|
11032
|
-
if (
|
|
11033
|
-
throw new Error(`\u672A\u77E5\u7684\u63A5\u7EBF\u72B6\u6001\u7248\u672C:${String(
|
|
11034
|
-
return
|
|
11200
|
+
const state = JSON.parse(buffer.toString("utf8"));
|
|
11201
|
+
if (state.version !== 1)
|
|
11202
|
+
throw new Error(`\u672A\u77E5\u7684\u63A5\u7EBF\u72B6\u6001\u7248\u672C:${String(state.version)},\u4E0D\u731C`);
|
|
11203
|
+
return state;
|
|
11035
11204
|
}
|
|
11036
|
-
async function writeState3(paths,
|
|
11205
|
+
async function writeState3(paths, state) {
|
|
11037
11206
|
await mkdir(dirname9(paths.stateFile), { recursive: true });
|
|
11038
|
-
await writeFile(paths.stateFile, `${JSON.stringify(
|
|
11207
|
+
await writeFile(paths.stateFile, `${JSON.stringify(state, void 0, 2)}
|
|
11039
11208
|
`, "utf8");
|
|
11040
11209
|
}
|
|
11041
11210
|
async function backupPatchFile(paths, bytes) {
|
|
@@ -11101,13 +11270,13 @@ async function installDshHooks(port, options) {
|
|
|
11101
11270
|
}
|
|
11102
11271
|
async function uninstallDshHooks(options) {
|
|
11103
11272
|
const paths = resolveDshHookPaths(options);
|
|
11104
|
-
const
|
|
11273
|
+
const state = await readState3(paths);
|
|
11105
11274
|
const currentBuffer = await readIfExists(paths.patchFile);
|
|
11106
11275
|
if (currentBuffer !== void 0) {
|
|
11107
11276
|
const currentText = decodeUtf8Strict(currentBuffer, paths.patchFile);
|
|
11108
11277
|
if (currentText.includes(HOOKS_MARKER_BEGIN_PREFIX)) {
|
|
11109
11278
|
const surgical = stripLiveHooksBlocks(currentText, paths.patchFile);
|
|
11110
|
-
const restored = resolveRestoredText(surgical,
|
|
11279
|
+
const restored = resolveRestoredText(surgical, state, paths);
|
|
11111
11280
|
if (restored !== currentText) {
|
|
11112
11281
|
await backupPatchFile(paths, currentBuffer);
|
|
11113
11282
|
if (restored === void 0)
|
|
@@ -11121,9 +11290,9 @@ async function uninstallDshHooks(options) {
|
|
|
11121
11290
|
await rm(paths.forwarderFile, { force: true });
|
|
11122
11291
|
await rm(paths.stateFile, { force: true });
|
|
11123
11292
|
}
|
|
11124
|
-
function resolveRestoredText(surgical,
|
|
11125
|
-
if (
|
|
11126
|
-
const originalText = Buffer3.from(
|
|
11293
|
+
function resolveRestoredText(surgical, state, paths) {
|
|
11294
|
+
if (state?.original.existed === true) {
|
|
11295
|
+
const originalText = Buffer3.from(state.original.bytesBase64, "base64").toString("utf8");
|
|
11127
11296
|
const surgicalEntries = tryParsePatchEntries(surgical, paths.patchFile);
|
|
11128
11297
|
const originalEntries = tryParsePatchEntries(originalText, paths.patchFile);
|
|
11129
11298
|
if (surgicalEntries !== void 0 && originalEntries !== void 0 && JSON.stringify(surgicalEntries) === JSON.stringify(originalEntries)) {
|
|
@@ -11716,7 +11885,7 @@ var init_dist5 = __esm({
|
|
|
11716
11885
|
init_dist();
|
|
11717
11886
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
11718
11887
|
import { dirname as dirname12, resolve as resolve6 } from "node:path";
|
|
11719
|
-
import { fileURLToPath as
|
|
11888
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
11720
11889
|
|
|
11721
11890
|
// packages/fixtures/dist/index.js
|
|
11722
11891
|
import { dirname, join } from "node:path";
|
|
@@ -11896,7 +12065,7 @@ function verifyEntitlement(raw, now, publicKeyPem) {
|
|
|
11896
12065
|
let claims;
|
|
11897
12066
|
try {
|
|
11898
12067
|
const decoded = JSON.parse(Buffer.from(parsed.token, "base64url").toString("utf8"));
|
|
11899
|
-
if (typeof decoded.sub !== "string" ||
|
|
12068
|
+
if (typeof decoded.sub !== "string" || decoded.plan !== "trial" && decoded.plan !== "subscription" || typeof decoded.iat !== "string" || typeof decoded.exp !== "string" || Number.isNaN(Date.parse(decoded.exp))) {
|
|
11900
12069
|
return { state: "invalid", detail: "license \u8F7D\u8377\u5B57\u6BB5\u4E0D\u5B8C\u6574" };
|
|
11901
12070
|
}
|
|
11902
12071
|
claims = { sub: decoded.sub, plan: decoded.plan, iat: decoded.iat, exp: decoded.exp };
|
|
@@ -11904,10 +12073,22 @@ function verifyEntitlement(raw, now, publicKeyPem) {
|
|
|
11904
12073
|
return { state: "invalid", detail: "license \u8F7D\u8377\u65E0\u6CD5\u89E3\u7801" };
|
|
11905
12074
|
}
|
|
11906
12075
|
const exp = Date.parse(claims.exp);
|
|
11907
|
-
const
|
|
12076
|
+
const isTrial = claims.plan === "trial";
|
|
11908
12077
|
if (now.getTime() < exp) {
|
|
11909
|
-
return {
|
|
12078
|
+
return {
|
|
12079
|
+
state: "active",
|
|
12080
|
+
detail: `${isTrial ? "\u8BD5\u7528" : "\u8BA2\u9605"}\u6709\u6548\u81F3 ${claims.exp}`,
|
|
12081
|
+
claims
|
|
12082
|
+
};
|
|
11910
12083
|
}
|
|
12084
|
+
if (isTrial) {
|
|
12085
|
+
return {
|
|
12086
|
+
state: "expired",
|
|
12087
|
+
detail: `\u8BD5\u7528\u5DF2\u4E8E ${claims.exp} \u5230\u671F,\u5199\u52A8\u4F5C\u5DF2\u9501;\u8BA2\u9605\u540E\u5728\u9762\u677F\u70B9\u300C\u5237\u65B0\u6388\u6743\u300D\u5373\u53EF\u6062\u590D`,
|
|
12088
|
+
claims
|
|
12089
|
+
};
|
|
12090
|
+
}
|
|
12091
|
+
const graceEnd = exp + OFFLINE_GRACE_DAYS * 24 * 60 * 60 * 1e3;
|
|
11911
12092
|
if (now.getTime() < graceEnd) {
|
|
11912
12093
|
return {
|
|
11913
12094
|
state: "grace",
|
|
@@ -11965,7 +12146,8 @@ async function refreshLicense(options = {}) {
|
|
|
11965
12146
|
} catch {
|
|
11966
12147
|
return {
|
|
11967
12148
|
ok: false,
|
|
11968
|
-
message: `\u672A\u627E\u5230 ${licensePath}\u2014\u2014\u5148\u8BA2\u9605,\u4ECE\u4ED8\u6B3E\u540E\u7684\u6FC0\u6D3B\u9875\u4E0B\u8F7D license.json \u653E\u8FDB\u53BB
|
|
12149
|
+
message: `\u672A\u627E\u5230 ${licensePath}\u2014\u2014\u5148\u8BA2\u9605,\u4ECE\u4ED8\u6B3E\u540E\u7684\u6FC0\u6D3B\u9875\u4E0B\u8F7D license.json \u653E\u8FDB\u53BB`,
|
|
12150
|
+
reason: "missing"
|
|
11969
12151
|
};
|
|
11970
12152
|
}
|
|
11971
12153
|
let response;
|
|
@@ -11977,43 +12159,91 @@ async function refreshLicense(options = {}) {
|
|
|
11977
12159
|
signal: AbortSignal.timeout(15e3)
|
|
11978
12160
|
});
|
|
11979
12161
|
} catch {
|
|
11980
|
-
return {
|
|
12162
|
+
return {
|
|
12163
|
+
ok: false,
|
|
12164
|
+
message: "\u8054\u7F51\u5931\u8D25,\u7A0D\u540E\u518D\u8BD5;\u5F53\u524D\u6388\u6743\u72B6\u6001\u4FDD\u6301\u4E0D\u53D8",
|
|
12165
|
+
reason: "network"
|
|
12166
|
+
};
|
|
11981
12167
|
}
|
|
11982
12168
|
if (!response.ok) {
|
|
11983
12169
|
let detail = `\u7B7E\u53D1\u7AEF\u8FD4\u56DE HTTP ${response.status}`;
|
|
12170
|
+
let inactive = false;
|
|
11984
12171
|
try {
|
|
11985
12172
|
const body = await response.json();
|
|
11986
12173
|
if (typeof body.error === "string")
|
|
11987
12174
|
detail = body.error;
|
|
12175
|
+
inactive = body.reason === "inactive";
|
|
11988
12176
|
} catch {
|
|
11989
12177
|
}
|
|
11990
|
-
return {
|
|
12178
|
+
return {
|
|
12179
|
+
ok: false,
|
|
12180
|
+
message: `\u7EED\u671F\u88AB\u62D2:${detail}`,
|
|
12181
|
+
reason: inactive ? "inactive" : "server"
|
|
12182
|
+
};
|
|
11991
12183
|
}
|
|
11992
12184
|
let entitlement;
|
|
11993
12185
|
try {
|
|
11994
12186
|
entitlement = await response.json();
|
|
11995
12187
|
} catch {
|
|
11996
|
-
return {
|
|
12188
|
+
return {
|
|
12189
|
+
ok: false,
|
|
12190
|
+
message: "\u7B7E\u53D1\u7AEF\u8FD4\u56DE\u7684\u4E0D\u662F\u5408\u6CD5 JSON;\u539F license \u672A\u52A8",
|
|
12191
|
+
reason: "invalid-response"
|
|
12192
|
+
};
|
|
11997
12193
|
}
|
|
11998
12194
|
if (typeof entitlement.token !== "string" || typeof entitlement.signature !== "string") {
|
|
11999
|
-
return {
|
|
12195
|
+
return {
|
|
12196
|
+
ok: false,
|
|
12197
|
+
message: "\u7B7E\u53D1\u7AEF\u8FD4\u56DE\u7F3A token/signature;\u539F license \u672A\u52A8",
|
|
12198
|
+
reason: "invalid-response"
|
|
12199
|
+
};
|
|
12000
12200
|
}
|
|
12001
12201
|
const next = `${JSON.stringify({ token: entitlement.token, signature: entitlement.signature })}
|
|
12002
12202
|
`;
|
|
12203
|
+
const publicKeyPem = options.publicKeyPem ?? PROD_PUBLIC_KEY_PEM;
|
|
12204
|
+
if (publicKeyPem !== "") {
|
|
12205
|
+
const status = verifyEntitlement(next, options.now ?? /* @__PURE__ */ new Date(), publicKeyPem);
|
|
12206
|
+
if (status.state !== "active" && status.state !== "grace") {
|
|
12207
|
+
return {
|
|
12208
|
+
ok: false,
|
|
12209
|
+
message: `\u7B7E\u53D1\u7AEF\u8FD4\u56DE\u7684 license \u65E0\u6548:${status.detail};\u539F license \u672A\u52A8`,
|
|
12210
|
+
reason: "invalid-response"
|
|
12211
|
+
};
|
|
12212
|
+
}
|
|
12213
|
+
}
|
|
12214
|
+
try {
|
|
12215
|
+
if (readFileSync2(licensePath, "utf8") !== raw) {
|
|
12216
|
+
return {
|
|
12217
|
+
ok: false,
|
|
12218
|
+
message: "\u5237\u65B0\u671F\u95F4\u672C\u673A\u6388\u6743\u5DF2\u88AB\u66F4\u6362;\u5DF2\u4FDD\u7559\u8F83\u65B0\u7684\u6587\u4EF6,\u8BF7\u518D\u70B9\u4E00\u6B21\u5237\u65B0",
|
|
12219
|
+
reason: "changed"
|
|
12220
|
+
};
|
|
12221
|
+
}
|
|
12222
|
+
} catch {
|
|
12223
|
+
return {
|
|
12224
|
+
ok: false,
|
|
12225
|
+
message: "\u5237\u65B0\u671F\u95F4\u672C\u673A\u6388\u6743\u5DF2\u88AB\u66F4\u6362;\u5F53\u524D\u6587\u4EF6\u672A\u88AB\u8986\u76D6",
|
|
12226
|
+
reason: "changed"
|
|
12227
|
+
};
|
|
12228
|
+
}
|
|
12003
12229
|
const tmpPath = join3(dirname2(licensePath), `.license.json.tmp-${process.pid}`);
|
|
12004
12230
|
try {
|
|
12005
12231
|
writeFileSync2(tmpPath, next);
|
|
12006
12232
|
renameSync2(tmpPath, licensePath);
|
|
12007
12233
|
} catch (error) {
|
|
12008
12234
|
const message = error instanceof Error ? error.message : String(error);
|
|
12009
|
-
return { ok: false, message: `\u5199\u5165 ${licensePath} \u5931\u8D25:${message}
|
|
12235
|
+
return { ok: false, message: `\u5199\u5165 ${licensePath} \u5931\u8D25:${message}`, reason: "write" };
|
|
12010
12236
|
}
|
|
12011
12237
|
return { ok: true, message: `license \u5DF2\u7EED\u671F${expSuffix(entitlement.token)}` };
|
|
12012
12238
|
}
|
|
12013
12239
|
function expSuffix(token) {
|
|
12014
12240
|
try {
|
|
12015
12241
|
const claims = JSON.parse(Buffer.from(token, "base64url").toString("utf8"));
|
|
12016
|
-
|
|
12242
|
+
if (typeof claims.exp !== "string")
|
|
12243
|
+
return "";
|
|
12244
|
+
if (claims.plan !== "trial" && claims.plan !== "subscription")
|
|
12245
|
+
return "";
|
|
12246
|
+
return `,${claims.plan === "trial" ? "\u8BD5\u7528" : "\u8BA2\u9605"}\u6709\u6548\u81F3 ${claims.exp}`;
|
|
12017
12247
|
} catch {
|
|
12018
12248
|
return "";
|
|
12019
12249
|
}
|
|
@@ -12114,6 +12344,246 @@ function isInProgressAt(entry, ts) {
|
|
|
12114
12344
|
return entry.spans.some((span) => span.status === "in_progress" && span.from !== void 0 && span.from <= ts && (span.to === void 0 || ts < span.to));
|
|
12115
12345
|
}
|
|
12116
12346
|
|
|
12347
|
+
// packages/narrative/dist/codex.js
|
|
12348
|
+
function parseCodexRolloutLine(line) {
|
|
12349
|
+
const trimmed = line.trim();
|
|
12350
|
+
if (trimmed === "")
|
|
12351
|
+
return { kind: "unknown", reason: "\u7A7A\u884C", rawText: line };
|
|
12352
|
+
let value;
|
|
12353
|
+
try {
|
|
12354
|
+
value = JSON.parse(trimmed);
|
|
12355
|
+
} catch {
|
|
12356
|
+
return { kind: "unknown", reason: "JSON \u89E3\u6790\u5931\u8D25", rawText: line };
|
|
12357
|
+
}
|
|
12358
|
+
if (!isRecord2(value))
|
|
12359
|
+
return { kind: "unknown", reason: "\u884C\u4E0D\u662F JSON \u5BF9\u8C61", rawText: line };
|
|
12360
|
+
if (typeof value.type !== "string") {
|
|
12361
|
+
return { kind: "unknown", reason: "\u7F3A type \u5B57\u6BB5\u6216\u975E\u5B57\u7B26\u4E32", rawText: line };
|
|
12362
|
+
}
|
|
12363
|
+
const timestamp = optionalString(value.timestamp);
|
|
12364
|
+
const payload = isRecord2(value.payload) ? value.payload : void 0;
|
|
12365
|
+
if (value.type === "session_meta")
|
|
12366
|
+
return parseSessionMeta(payload, timestamp);
|
|
12367
|
+
if (value.type === "turn_context")
|
|
12368
|
+
return parseTurnContext(payload, timestamp);
|
|
12369
|
+
if (value.type === "event_msg")
|
|
12370
|
+
return parseEventMessage(payload, timestamp);
|
|
12371
|
+
if (value.type === "response_item")
|
|
12372
|
+
return parseResponseItem(payload, timestamp);
|
|
12373
|
+
return otherLine(value.type, payload, timestamp);
|
|
12374
|
+
}
|
|
12375
|
+
function parseSessionMeta(payload, timestamp) {
|
|
12376
|
+
const line = { kind: "session_meta" };
|
|
12377
|
+
assignString(line, "timestamp", timestamp ?? optionalString(payload?.timestamp));
|
|
12378
|
+
assignString(line, "threadId", optionalString(payload?.id));
|
|
12379
|
+
assignString(line, "sessionId", optionalString(payload?.session_id));
|
|
12380
|
+
assignString(line, "parentThreadId", optionalString(payload?.parent_thread_id));
|
|
12381
|
+
assignString(line, "cwd", optionalString(payload?.cwd));
|
|
12382
|
+
assignString(line, "cliVersion", optionalString(payload?.cli_version));
|
|
12383
|
+
assignString(line, "modelProvider", optionalString(payload?.model_provider));
|
|
12384
|
+
return line;
|
|
12385
|
+
}
|
|
12386
|
+
function parseTurnContext(payload, timestamp) {
|
|
12387
|
+
const line = { kind: "turn_context" };
|
|
12388
|
+
assignString(line, "timestamp", timestamp);
|
|
12389
|
+
assignString(line, "turnId", optionalString(payload?.turn_id));
|
|
12390
|
+
assignString(line, "model", optionalString(payload?.model));
|
|
12391
|
+
assignString(line, "cwd", optionalString(payload?.cwd));
|
|
12392
|
+
return line;
|
|
12393
|
+
}
|
|
12394
|
+
function parseEventMessage(payload, timestamp) {
|
|
12395
|
+
const payloadType = optionalString(payload?.type);
|
|
12396
|
+
if (payloadType === "user_message") {
|
|
12397
|
+
const line = { kind: "user" };
|
|
12398
|
+
assignString(line, "timestamp", timestamp);
|
|
12399
|
+
assignString(line, "text", optionalString(payload?.message));
|
|
12400
|
+
return line;
|
|
12401
|
+
}
|
|
12402
|
+
if (payloadType === "agent_message") {
|
|
12403
|
+
const line = { kind: "assistant", source: "event_msg" };
|
|
12404
|
+
assignString(line, "timestamp", timestamp);
|
|
12405
|
+
assignString(line, "text", optionalString(payload?.message));
|
|
12406
|
+
assignString(line, "phase", optionalString(payload?.phase));
|
|
12407
|
+
return line;
|
|
12408
|
+
}
|
|
12409
|
+
if (payloadType === "task_started") {
|
|
12410
|
+
const line = { kind: "task_started" };
|
|
12411
|
+
assignString(line, "timestamp", timestamp);
|
|
12412
|
+
assignString(line, "turnId", optionalString(payload?.turn_id));
|
|
12413
|
+
return line;
|
|
12414
|
+
}
|
|
12415
|
+
if (payloadType === "task_complete") {
|
|
12416
|
+
const line = { kind: "task_complete" };
|
|
12417
|
+
assignString(line, "timestamp", timestamp);
|
|
12418
|
+
assignString(line, "turnId", optionalString(payload?.turn_id));
|
|
12419
|
+
return line;
|
|
12420
|
+
}
|
|
12421
|
+
if (payloadType === "turn_aborted") {
|
|
12422
|
+
const line = { kind: "turn_aborted" };
|
|
12423
|
+
assignString(line, "timestamp", timestamp);
|
|
12424
|
+
assignString(line, "turnId", optionalString(payload?.turn_id));
|
|
12425
|
+
return line;
|
|
12426
|
+
}
|
|
12427
|
+
if (payloadType === "token_count" && payload !== void 0) {
|
|
12428
|
+
return parseTokenCount(payload, timestamp);
|
|
12429
|
+
}
|
|
12430
|
+
return otherLine("event_msg", payload, timestamp);
|
|
12431
|
+
}
|
|
12432
|
+
function parseResponseItem(payload, timestamp) {
|
|
12433
|
+
const payloadType = optionalString(payload?.type);
|
|
12434
|
+
if (payloadType === "message" && payload?.role === "assistant") {
|
|
12435
|
+
const line = { kind: "assistant", source: "response_item" };
|
|
12436
|
+
assignString(line, "timestamp", timestamp);
|
|
12437
|
+
assignString(line, "text", extractText(payload.content, "output_text"));
|
|
12438
|
+
assignString(line, "phase", optionalString(payload.phase));
|
|
12439
|
+
assignString(line, "messageId", optionalString(payload.id));
|
|
12440
|
+
assignString(line, "turnId", extractTurnId(payload));
|
|
12441
|
+
return line;
|
|
12442
|
+
}
|
|
12443
|
+
if (payload !== void 0 && (payloadType === "function_call" || payloadType === "custom_tool_call")) {
|
|
12444
|
+
const call = { kind: "tool_call", callType: payloadType };
|
|
12445
|
+
assignString(call, "timestamp", timestamp);
|
|
12446
|
+
assignString(call, "callId", optionalString(payload.call_id));
|
|
12447
|
+
assignString(call, "itemId", optionalString(payload.id));
|
|
12448
|
+
assignString(call, "name", optionalString(payload.name));
|
|
12449
|
+
assignString(call, "namespace", optionalString(payload.namespace));
|
|
12450
|
+
assignString(call, "status", optionalString(payload.status));
|
|
12451
|
+
assignString(call, "turnId", extractTurnId(payload));
|
|
12452
|
+
const input = payloadType === "function_call" ? parseJsonString(payload.arguments) : payload.input;
|
|
12453
|
+
if (input !== void 0)
|
|
12454
|
+
call.input = input;
|
|
12455
|
+
return call;
|
|
12456
|
+
}
|
|
12457
|
+
if (payload !== void 0 && (payloadType === "function_call_output" || payloadType === "custom_tool_call_output")) {
|
|
12458
|
+
const result = { kind: "tool_result", resultType: payloadType };
|
|
12459
|
+
assignString(result, "timestamp", timestamp);
|
|
12460
|
+
assignString(result, "callId", optionalString(payload.call_id));
|
|
12461
|
+
assignString(result, "itemId", optionalString(payload.id));
|
|
12462
|
+
assignString(result, "turnId", extractTurnId(payload));
|
|
12463
|
+
if (payload.output !== void 0) {
|
|
12464
|
+
result.output = payload.output;
|
|
12465
|
+
if (codexToolResultIsError(payload.output))
|
|
12466
|
+
result.isError = true;
|
|
12467
|
+
}
|
|
12468
|
+
return result;
|
|
12469
|
+
}
|
|
12470
|
+
return otherLine("response_item", payload, timestamp);
|
|
12471
|
+
}
|
|
12472
|
+
function parseTokenCount(payload, timestamp) {
|
|
12473
|
+
const line = { kind: "token_count" };
|
|
12474
|
+
assignString(line, "timestamp", timestamp);
|
|
12475
|
+
const info = isRecord2(payload.info) ? payload.info : void 0;
|
|
12476
|
+
const lastRaw = isRecord2(info?.last_token_usage) ? info.last_token_usage : void 0;
|
|
12477
|
+
const totalRaw = isRecord2(info?.total_token_usage) ? info.total_token_usage : void 0;
|
|
12478
|
+
const lastUsage = parseCodexTokenUsage(lastRaw);
|
|
12479
|
+
const totalUsage = parseCodexTokenUsage(totalRaw);
|
|
12480
|
+
if (lastUsage !== void 0)
|
|
12481
|
+
line.lastUsage = lastUsage;
|
|
12482
|
+
if (totalUsage !== void 0)
|
|
12483
|
+
line.totalUsage = totalUsage;
|
|
12484
|
+
const modelContextWindow = tokenInteger(info?.model_context_window);
|
|
12485
|
+
if (modelContextWindow !== void 0)
|
|
12486
|
+
line.modelContextWindow = modelContextWindow;
|
|
12487
|
+
return line;
|
|
12488
|
+
}
|
|
12489
|
+
function parseCodexTokenUsage(value) {
|
|
12490
|
+
if (value === void 0)
|
|
12491
|
+
return void 0;
|
|
12492
|
+
const usage = { kind: "exact" };
|
|
12493
|
+
assignToken(usage, "inputTokens", value.input_tokens);
|
|
12494
|
+
assignToken(usage, "cachedInputTokens", value.cached_input_tokens);
|
|
12495
|
+
if (value.cache_write_input_tokens === void 0)
|
|
12496
|
+
usage.cacheWriteInputTokens = 0;
|
|
12497
|
+
else
|
|
12498
|
+
assignToken(usage, "cacheWriteInputTokens", value.cache_write_input_tokens);
|
|
12499
|
+
assignToken(usage, "outputTokens", value.output_tokens);
|
|
12500
|
+
assignToken(usage, "reasoningOutputTokens", value.reasoning_output_tokens);
|
|
12501
|
+
assignToken(usage, "totalTokens", value.total_tokens);
|
|
12502
|
+
return Object.keys(usage).length > 1 ? usage : void 0;
|
|
12503
|
+
}
|
|
12504
|
+
function codexToolResultIsError(output) {
|
|
12505
|
+
return explicitResultFailure(output, 0);
|
|
12506
|
+
}
|
|
12507
|
+
function explicitResultFailure(value, depth) {
|
|
12508
|
+
if (depth > 2)
|
|
12509
|
+
return false;
|
|
12510
|
+
if (typeof value === "string") {
|
|
12511
|
+
try {
|
|
12512
|
+
return explicitResultFailure(JSON.parse(value), depth + 1);
|
|
12513
|
+
} catch {
|
|
12514
|
+
return false;
|
|
12515
|
+
}
|
|
12516
|
+
}
|
|
12517
|
+
if (Array.isArray(value)) {
|
|
12518
|
+
return value.some((block) => {
|
|
12519
|
+
if (!isRecord2(block))
|
|
12520
|
+
return false;
|
|
12521
|
+
if (block.isError === true || block.is_error === true)
|
|
12522
|
+
return true;
|
|
12523
|
+
return (block.type === "text" || block.type === "input_text") && typeof block.text === "string" ? explicitResultFailure(block.text, depth + 1) : false;
|
|
12524
|
+
});
|
|
12525
|
+
}
|
|
12526
|
+
if (!isRecord2(value))
|
|
12527
|
+
return false;
|
|
12528
|
+
if (value.isError === true || value.is_error === true)
|
|
12529
|
+
return true;
|
|
12530
|
+
if (explicitExitFailure(value.exit_code))
|
|
12531
|
+
return true;
|
|
12532
|
+
const metadata = isRecord2(value.metadata) ? value.metadata : void 0;
|
|
12533
|
+
return explicitExitFailure(metadata?.exit_code);
|
|
12534
|
+
}
|
|
12535
|
+
function explicitExitFailure(value) {
|
|
12536
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value !== 0;
|
|
12537
|
+
}
|
|
12538
|
+
function otherLine(type, payload, timestamp) {
|
|
12539
|
+
const line = { kind: "other", type };
|
|
12540
|
+
assignString(line, "timestamp", timestamp);
|
|
12541
|
+
assignString(line, "payloadType", optionalString(payload?.type));
|
|
12542
|
+
return line;
|
|
12543
|
+
}
|
|
12544
|
+
function extractText(value, blockType) {
|
|
12545
|
+
if (!Array.isArray(value))
|
|
12546
|
+
return void 0;
|
|
12547
|
+
const texts = [];
|
|
12548
|
+
for (const block of value) {
|
|
12549
|
+
if (!isRecord2(block) || block.type !== blockType || typeof block.text !== "string")
|
|
12550
|
+
continue;
|
|
12551
|
+
texts.push(block.text);
|
|
12552
|
+
}
|
|
12553
|
+
return texts.length > 0 ? texts.join("\n") : void 0;
|
|
12554
|
+
}
|
|
12555
|
+
function extractTurnId(payload) {
|
|
12556
|
+
const metadata = isRecord2(payload.internal_chat_message_metadata_passthrough) ? payload.internal_chat_message_metadata_passthrough : void 0;
|
|
12557
|
+
return optionalString(metadata?.turn_id);
|
|
12558
|
+
}
|
|
12559
|
+
function parseJsonString(value) {
|
|
12560
|
+
if (typeof value !== "string")
|
|
12561
|
+
return value;
|
|
12562
|
+
try {
|
|
12563
|
+
return JSON.parse(value);
|
|
12564
|
+
} catch {
|
|
12565
|
+
return value;
|
|
12566
|
+
}
|
|
12567
|
+
}
|
|
12568
|
+
function tokenInteger(value) {
|
|
12569
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
12570
|
+
}
|
|
12571
|
+
function optionalString(value) {
|
|
12572
|
+
return typeof value === "string" ? value : void 0;
|
|
12573
|
+
}
|
|
12574
|
+
function assignString(target, key, value) {
|
|
12575
|
+
if (value !== void 0)
|
|
12576
|
+
target[key] = value;
|
|
12577
|
+
}
|
|
12578
|
+
function assignToken(usage, key, value) {
|
|
12579
|
+
const parsed = tokenInteger(value);
|
|
12580
|
+
if (parsed !== void 0)
|
|
12581
|
+
usage[key] = parsed;
|
|
12582
|
+
}
|
|
12583
|
+
function isRecord2(value) {
|
|
12584
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12585
|
+
}
|
|
12586
|
+
|
|
12117
12587
|
// packages/narrative/dist/parse.js
|
|
12118
12588
|
function parseTranscriptLine(line) {
|
|
12119
12589
|
const trimmed = line.trim();
|
|
@@ -12125,7 +12595,7 @@ function parseTranscriptLine(line) {
|
|
|
12125
12595
|
} catch {
|
|
12126
12596
|
return { kind: "unknown", reason: "JSON \u89E3\u6790\u5931\u8D25", rawText: line };
|
|
12127
12597
|
}
|
|
12128
|
-
if (!
|
|
12598
|
+
if (!isRecord3(value))
|
|
12129
12599
|
return { kind: "unknown", reason: "\u884C\u4E0D\u662F JSON \u5BF9\u8C61", rawText: line };
|
|
12130
12600
|
const type = value.type;
|
|
12131
12601
|
if (typeof type !== "string") {
|
|
@@ -12140,7 +12610,7 @@ function parseTranscriptLine(line) {
|
|
|
12140
12610
|
const attachment = {
|
|
12141
12611
|
kind: "attachment",
|
|
12142
12612
|
...common,
|
|
12143
|
-
attachment:
|
|
12613
|
+
attachment: isRecord3(value.attachment) ? value.attachment : {}
|
|
12144
12614
|
};
|
|
12145
12615
|
return attachment;
|
|
12146
12616
|
}
|
|
@@ -12164,14 +12634,14 @@ function extractCommon(raw) {
|
|
|
12164
12634
|
}
|
|
12165
12635
|
function parseUser(raw, common) {
|
|
12166
12636
|
const line = { kind: "user", ...common, toolResults: [] };
|
|
12167
|
-
const message =
|
|
12637
|
+
const message = isRecord3(raw.message) ? raw.message : void 0;
|
|
12168
12638
|
const content = message?.content;
|
|
12169
12639
|
if (typeof content === "string") {
|
|
12170
12640
|
line.text = content;
|
|
12171
12641
|
} else if (Array.isArray(content)) {
|
|
12172
12642
|
const texts = [];
|
|
12173
12643
|
for (const block of content) {
|
|
12174
|
-
if (!
|
|
12644
|
+
if (!isRecord3(block))
|
|
12175
12645
|
continue;
|
|
12176
12646
|
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
12177
12647
|
const result = {
|
|
@@ -12194,20 +12664,20 @@ function parseUser(raw, common) {
|
|
|
12194
12664
|
}
|
|
12195
12665
|
function parseAssistant(raw, common) {
|
|
12196
12666
|
const line = { kind: "assistant", ...common, blocks: [] };
|
|
12197
|
-
const message =
|
|
12667
|
+
const message = isRecord3(raw.message) ? raw.message : void 0;
|
|
12198
12668
|
if (message === void 0)
|
|
12199
12669
|
return line;
|
|
12200
12670
|
if (typeof message.model === "string")
|
|
12201
12671
|
line.model = message.model;
|
|
12202
12672
|
if (typeof message.id === "string")
|
|
12203
12673
|
line.messageId = message.id;
|
|
12204
|
-
if (
|
|
12674
|
+
if (isRecord3(message.usage))
|
|
12205
12675
|
line.usage = message.usage;
|
|
12206
12676
|
const content = message.content;
|
|
12207
12677
|
if (!Array.isArray(content))
|
|
12208
12678
|
return line;
|
|
12209
12679
|
for (const block of content) {
|
|
12210
|
-
if (!
|
|
12680
|
+
if (!isRecord3(block))
|
|
12211
12681
|
continue;
|
|
12212
12682
|
line.blocks.push(parseAssistantBlock(block));
|
|
12213
12683
|
}
|
|
@@ -12225,7 +12695,7 @@ function parseAssistantBlock(block) {
|
|
|
12225
12695
|
kind: "tool_use",
|
|
12226
12696
|
id: block.id,
|
|
12227
12697
|
name: block.name,
|
|
12228
|
-
input:
|
|
12698
|
+
input: isRecord3(block.input) ? block.input : {}
|
|
12229
12699
|
};
|
|
12230
12700
|
}
|
|
12231
12701
|
return {
|
|
@@ -12234,7 +12704,7 @@ function parseAssistantBlock(block) {
|
|
|
12234
12704
|
raw: block
|
|
12235
12705
|
};
|
|
12236
12706
|
}
|
|
12237
|
-
function
|
|
12707
|
+
function isRecord3(value) {
|
|
12238
12708
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12239
12709
|
}
|
|
12240
12710
|
|
|
@@ -12419,7 +12889,7 @@ function buildTaskCreateResultIndex(lines) {
|
|
|
12419
12889
|
if (line.kind !== "user")
|
|
12420
12890
|
continue;
|
|
12421
12891
|
const result = line.toolUseResult;
|
|
12422
|
-
if (!
|
|
12892
|
+
if (!isRecord3(result) || !isRecord3(result.task) || typeof result.task.id !== "string")
|
|
12423
12893
|
continue;
|
|
12424
12894
|
for (const toolResult of line.toolResults)
|
|
12425
12895
|
index.set(toolResult.toolUseId, result.task.id);
|
|
@@ -12432,7 +12902,7 @@ function applyTodoWrite(input, tasks) {
|
|
|
12432
12902
|
return false;
|
|
12433
12903
|
tasks.clear();
|
|
12434
12904
|
for (const raw of todos) {
|
|
12435
|
-
if (!
|
|
12905
|
+
if (!isRecord3(raw) || typeof raw.content !== "string" || !isTodoStatus(raw.status))
|
|
12436
12906
|
continue;
|
|
12437
12907
|
const item = { id: raw.content, content: raw.content, status: raw.status };
|
|
12438
12908
|
if (typeof raw.activeForm === "string")
|
|
@@ -12538,7 +13008,7 @@ function turnUsage(line) {
|
|
|
12538
13008
|
}
|
|
12539
13009
|
};
|
|
12540
13010
|
const breakdown = usage.cache_creation;
|
|
12541
|
-
if (
|
|
13011
|
+
if (isRecord3(breakdown)) {
|
|
12542
13012
|
const fiveMinute = finiteNumber(breakdown.ephemeral_5m_input_tokens);
|
|
12543
13013
|
const oneHour = finiteNumber(breakdown.ephemeral_1h_input_tokens);
|
|
12544
13014
|
if (fiveMinute !== void 0)
|
|
@@ -12587,7 +13057,33 @@ function createNarrativeService(options = {}) {
|
|
|
12587
13057
|
const intervalMs = options.intervalMs ?? 500;
|
|
12588
13058
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
12589
13059
|
const harnesses = /* @__PURE__ */ new Map();
|
|
13060
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
13061
|
+
const pendingNotifications = /* @__PURE__ */ new Set();
|
|
13062
|
+
let notificationQueued = false;
|
|
12590
13063
|
let closed = false;
|
|
13064
|
+
function notify(harness) {
|
|
13065
|
+
pendingNotifications.add(harness);
|
|
13066
|
+
if (notificationQueued)
|
|
13067
|
+
return;
|
|
13068
|
+
notificationQueued = true;
|
|
13069
|
+
queueMicrotask(() => {
|
|
13070
|
+
notificationQueued = false;
|
|
13071
|
+
if (closed) {
|
|
13072
|
+
pendingNotifications.clear();
|
|
13073
|
+
return;
|
|
13074
|
+
}
|
|
13075
|
+
const harnesses2 = [...pendingNotifications];
|
|
13076
|
+
pendingNotifications.clear();
|
|
13077
|
+
for (const changed of harnesses2) {
|
|
13078
|
+
for (const listener of listeners) {
|
|
13079
|
+
try {
|
|
13080
|
+
listener(changed);
|
|
13081
|
+
} catch {
|
|
13082
|
+
}
|
|
13083
|
+
}
|
|
13084
|
+
}
|
|
13085
|
+
});
|
|
13086
|
+
}
|
|
12591
13087
|
function sessionsOf(harness) {
|
|
12592
13088
|
let sessions = harnesses.get(harness);
|
|
12593
13089
|
if (sessions === void 0) {
|
|
@@ -12600,6 +13096,7 @@ function createNarrativeService(options = {}) {
|
|
|
12600
13096
|
return {
|
|
12601
13097
|
id,
|
|
12602
13098
|
lines: [],
|
|
13099
|
+
codexLines: [],
|
|
12603
13100
|
follows: /* @__PURE__ */ new Map(),
|
|
12604
13101
|
hookIntervals: [],
|
|
12605
13102
|
waiting: null,
|
|
@@ -12650,20 +13147,27 @@ function createNarrativeService(options = {}) {
|
|
|
12650
13147
|
function resetTranscript(session, path) {
|
|
12651
13148
|
closeSession(session);
|
|
12652
13149
|
session.lines = [];
|
|
13150
|
+
session.codexLines = [];
|
|
12653
13151
|
session.hookIntervals = [];
|
|
13152
|
+
session.hookPrompt = void 0;
|
|
13153
|
+
session.hookPromptAt = void 0;
|
|
12654
13154
|
session.mockNote = void 0;
|
|
12655
13155
|
session.transcriptPath = path;
|
|
12656
13156
|
session.dirty = true;
|
|
12657
13157
|
}
|
|
12658
|
-
function startFollowing(session, path) {
|
|
13158
|
+
function startFollowing(harness, session, path) {
|
|
12659
13159
|
if (closed || session.follows.has(path))
|
|
12660
13160
|
return;
|
|
12661
13161
|
session.follows.set(path, followTranscript(path, (line) => {
|
|
12662
|
-
|
|
13162
|
+
if (harness === "codex")
|
|
13163
|
+
session.codexLines.push(parseCodexRolloutLine(line));
|
|
13164
|
+
else
|
|
13165
|
+
session.lines.push(parseTranscriptLine(line));
|
|
12663
13166
|
session.dirty = true;
|
|
13167
|
+
notify(harness);
|
|
12664
13168
|
}, { intervalMs }));
|
|
12665
13169
|
}
|
|
12666
|
-
function startSubagentDiscovery(session, transcriptPath) {
|
|
13170
|
+
function startSubagentDiscovery(harness, session, transcriptPath) {
|
|
12667
13171
|
const sessionDir = transcriptPath.replace(/\.jsonl$/, "");
|
|
12668
13172
|
if (sessionDir === transcriptPath)
|
|
12669
13173
|
return;
|
|
@@ -12672,8 +13176,9 @@ function createNarrativeService(options = {}) {
|
|
|
12672
13176
|
try {
|
|
12673
13177
|
const entries = await readdir(subagentsDir);
|
|
12674
13178
|
for (const entry of entries) {
|
|
12675
|
-
if (/^agent-.*\.jsonl$/.test(entry))
|
|
12676
|
-
startFollowing(session, join4(subagentsDir, entry));
|
|
13179
|
+
if (/^agent-.*\.jsonl$/.test(entry)) {
|
|
13180
|
+
startFollowing(harness, session, join4(subagentsDir, entry));
|
|
13181
|
+
}
|
|
12677
13182
|
}
|
|
12678
13183
|
} catch {
|
|
12679
13184
|
}
|
|
@@ -12700,8 +13205,17 @@ function createNarrativeService(options = {}) {
|
|
|
12700
13205
|
if (follow && transcriptPath !== void 0) {
|
|
12701
13206
|
if (session.transcriptPath !== transcriptPath) {
|
|
12702
13207
|
resetTranscript(session, transcriptPath);
|
|
12703
|
-
startFollowing(session, transcriptPath);
|
|
12704
|
-
|
|
13208
|
+
startFollowing(event.harness, session, transcriptPath);
|
|
13209
|
+
if (event.harness === "cc") {
|
|
13210
|
+
startSubagentDiscovery(event.harness, session, transcriptPath);
|
|
13211
|
+
}
|
|
13212
|
+
}
|
|
13213
|
+
}
|
|
13214
|
+
if (name === "UserPromptSubmit" && typeof payload.prompt === "string") {
|
|
13215
|
+
const prompt = payload.prompt.trim();
|
|
13216
|
+
if (prompt !== "") {
|
|
13217
|
+
session.hookPrompt = truncate(prompt, DETAIL_LIMIT);
|
|
13218
|
+
session.hookPromptAt = event.received_at;
|
|
12705
13219
|
}
|
|
12706
13220
|
}
|
|
12707
13221
|
if (name === "SubagentStart") {
|
|
@@ -12753,13 +13267,34 @@ function createNarrativeService(options = {}) {
|
|
|
12753
13267
|
}
|
|
12754
13268
|
session.lastEvent = { name, at: event.received_at };
|
|
12755
13269
|
session.dirty = true;
|
|
13270
|
+
notify(event.harness);
|
|
12756
13271
|
}
|
|
12757
13272
|
function seedFixture(harness, rawLines, mockNote) {
|
|
12758
|
-
const
|
|
13273
|
+
const sourceLines = rawLines.filter((line) => line.trim() !== "");
|
|
13274
|
+
if (harness === "codex") {
|
|
13275
|
+
const codexLines = sourceLines.map((line) => parseCodexRolloutLine(line));
|
|
13276
|
+
const meta = codexLines.find((line) => line.kind === "session_meta");
|
|
13277
|
+
const id2 = meta?.threadId ?? meta?.sessionId ?? "__fixture__";
|
|
13278
|
+
const session2 = sessionOf(harness, id2);
|
|
13279
|
+
session2.lines = [];
|
|
13280
|
+
session2.codexLines = codexLines;
|
|
13281
|
+
session2.mockNote = mockNote;
|
|
13282
|
+
const cwd = meta?.cwd ?? codexLines.find((line) => line.kind === "turn_context")?.cwd;
|
|
13283
|
+
if (cwd !== void 0) {
|
|
13284
|
+
const base = pathBasename(cwd);
|
|
13285
|
+
if (base !== "")
|
|
13286
|
+
session2.label = base;
|
|
13287
|
+
}
|
|
13288
|
+
session2.dirty = true;
|
|
13289
|
+
notify(harness);
|
|
13290
|
+
return;
|
|
13291
|
+
}
|
|
13292
|
+
const lines = sourceLines.map((line) => parseTranscriptLine(line));
|
|
12759
13293
|
const sessionLine = lines.find((l) => l.kind !== "unknown" && typeof l.sessionId === "string");
|
|
12760
13294
|
const id = sessionLine?.sessionId ?? "__fixture__";
|
|
12761
13295
|
const session = sessionOf(harness, id);
|
|
12762
13296
|
session.lines = lines;
|
|
13297
|
+
session.codexLines = [];
|
|
12763
13298
|
session.mockNote = mockNote;
|
|
12764
13299
|
for (const line of lines) {
|
|
12765
13300
|
if (line.kind === "unknown")
|
|
@@ -12773,6 +13308,7 @@ function createNarrativeService(options = {}) {
|
|
|
12773
13308
|
}
|
|
12774
13309
|
}
|
|
12775
13310
|
session.dirty = true;
|
|
13311
|
+
notify(harness);
|
|
12776
13312
|
}
|
|
12777
13313
|
function deriveCached(harness, session) {
|
|
12778
13314
|
if (!session.dirty && session.cached !== void 0)
|
|
@@ -12800,9 +13336,9 @@ function createNarrativeService(options = {}) {
|
|
|
12800
13336
|
const overview = getOverview(harness);
|
|
12801
13337
|
const first = overview.sessions[0];
|
|
12802
13338
|
if (first !== void 0) {
|
|
12803
|
-
const
|
|
12804
|
-
if (
|
|
12805
|
-
return
|
|
13339
|
+
const state = overview.states[first.id];
|
|
13340
|
+
if (state !== void 0)
|
|
13341
|
+
return state;
|
|
12806
13342
|
}
|
|
12807
13343
|
return emptyState(harness, now().toISOString());
|
|
12808
13344
|
}
|
|
@@ -12812,8 +13348,17 @@ function createNarrativeService(options = {}) {
|
|
|
12812
13348
|
for (const session of sessions.values())
|
|
12813
13349
|
closeSession(session);
|
|
12814
13350
|
}
|
|
13351
|
+
pendingNotifications.clear();
|
|
13352
|
+
listeners.clear();
|
|
12815
13353
|
}
|
|
12816
|
-
|
|
13354
|
+
function subscribe(listener) {
|
|
13355
|
+
if (closed)
|
|
13356
|
+
return () => {
|
|
13357
|
+
};
|
|
13358
|
+
listeners.add(listener);
|
|
13359
|
+
return () => listeners.delete(listener);
|
|
13360
|
+
}
|
|
13361
|
+
return { handleEvent, getState, getOverview, seedFixture, subscribe, close };
|
|
12817
13362
|
}
|
|
12818
13363
|
function lastActivityOf(session) {
|
|
12819
13364
|
if (session.lastActivity !== void 0)
|
|
@@ -12826,6 +13371,13 @@ function lastActivityOf(session) {
|
|
|
12826
13371
|
if (ts !== void 0 && ts > latest)
|
|
12827
13372
|
latest = ts;
|
|
12828
13373
|
}
|
|
13374
|
+
for (const line of session.codexLines) {
|
|
13375
|
+
if (line.kind === "unknown")
|
|
13376
|
+
continue;
|
|
13377
|
+
const ts = line.timestamp;
|
|
13378
|
+
if (ts !== void 0 && ts > latest)
|
|
13379
|
+
latest = ts;
|
|
13380
|
+
}
|
|
12829
13381
|
return latest;
|
|
12830
13382
|
}
|
|
12831
13383
|
function isEnded(session, nowIso) {
|
|
@@ -12871,7 +13423,8 @@ function waitingMessage(payload, fallback) {
|
|
|
12871
13423
|
return fallback;
|
|
12872
13424
|
}
|
|
12873
13425
|
function derive(harness, session, nowIso) {
|
|
12874
|
-
const
|
|
13426
|
+
const codexLines = sortedCodexByTimestamp(session.codexLines);
|
|
13427
|
+
const lines = harness === "codex" ? adaptCodexNarrativeLines(codexLines) : sortedByTimestamp(session.lines);
|
|
12875
13428
|
const timeline = deriveTodoTimeline(extractTodoSnapshots(lines));
|
|
12876
13429
|
const allSteps = extractSteps(lines);
|
|
12877
13430
|
const steps = allSteps.filter((s) => !TODO_TOOLS.has(s.toolName));
|
|
@@ -12964,11 +13517,12 @@ function derive(harness, session, nowIso) {
|
|
|
12964
13517
|
todos,
|
|
12965
13518
|
...textTodos !== void 0 && textTodos.length > 0 ? { textTodos } : {},
|
|
12966
13519
|
traces,
|
|
12967
|
-
turnUsages: deriveTurnUsages(lines),
|
|
13520
|
+
turnUsages: harness === "codex" ? deriveCodexTurnUsages(codexLines) : deriveTurnUsages(lines),
|
|
12968
13521
|
currentAction: deriveCurrentAction(steps, session.lastEvent, session.pendingHookTool),
|
|
12969
13522
|
waiting: session.waiting,
|
|
12970
13523
|
agentQuote: deriveAgentQuote(lines),
|
|
12971
|
-
|
|
13524
|
+
// transcript 是会话事实源;hook prompt 只在 tail 尚未读到/格式暂不可读时兜底。
|
|
13525
|
+
lastPrompt: deriveLastPrompt(lines, session.hookPrompt, session.hookPromptAt),
|
|
12972
13526
|
updatedAt: nowIso
|
|
12973
13527
|
};
|
|
12974
13528
|
}
|
|
@@ -13015,6 +13569,109 @@ function sortedByTimestamp(lines) {
|
|
|
13015
13569
|
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
13016
13570
|
});
|
|
13017
13571
|
}
|
|
13572
|
+
function sortedCodexByTimestamp(lines) {
|
|
13573
|
+
return [...lines].sort((a, b) => {
|
|
13574
|
+
const ta = a.kind === "unknown" ? void 0 : a.timestamp;
|
|
13575
|
+
const tb = b.kind === "unknown" ? void 0 : b.timestamp;
|
|
13576
|
+
if (ta === void 0 || tb === void 0)
|
|
13577
|
+
return 0;
|
|
13578
|
+
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
13579
|
+
});
|
|
13580
|
+
}
|
|
13581
|
+
function adaptCodexNarrativeLines(lines) {
|
|
13582
|
+
const responseTexts = new Set(lines.flatMap((line) => line.kind === "assistant" && line.source === "response_item" && line.text !== void 0 ? [line.text] : []));
|
|
13583
|
+
const modelByTurn = /* @__PURE__ */ new Map();
|
|
13584
|
+
for (const line of lines) {
|
|
13585
|
+
if (line.kind === "turn_context" && line.turnId !== void 0 && line.model !== void 0) {
|
|
13586
|
+
modelByTurn.set(line.turnId, line.model);
|
|
13587
|
+
}
|
|
13588
|
+
}
|
|
13589
|
+
const adapted = [];
|
|
13590
|
+
let activeTurnId;
|
|
13591
|
+
for (const line of lines) {
|
|
13592
|
+
if (line.kind === "task_started") {
|
|
13593
|
+
activeTurnId = line.turnId;
|
|
13594
|
+
continue;
|
|
13595
|
+
}
|
|
13596
|
+
if (line.kind === "task_complete" || line.kind === "turn_aborted") {
|
|
13597
|
+
if (line.turnId === void 0 || line.turnId === activeTurnId)
|
|
13598
|
+
activeTurnId = void 0;
|
|
13599
|
+
continue;
|
|
13600
|
+
}
|
|
13601
|
+
if (line.kind === "turn_context" && line.turnId !== void 0) {
|
|
13602
|
+
activeTurnId = line.turnId;
|
|
13603
|
+
continue;
|
|
13604
|
+
}
|
|
13605
|
+
if (line.kind === "user") {
|
|
13606
|
+
adapted.push({
|
|
13607
|
+
kind: "user",
|
|
13608
|
+
...line.timestamp !== void 0 ? { timestamp: line.timestamp } : {},
|
|
13609
|
+
...line.text !== void 0 ? { text: line.text } : {},
|
|
13610
|
+
toolResults: [],
|
|
13611
|
+
raw: { source: "codex-rollout" }
|
|
13612
|
+
});
|
|
13613
|
+
continue;
|
|
13614
|
+
}
|
|
13615
|
+
if (line.kind === "assistant") {
|
|
13616
|
+
if (line.text === void 0 || line.source === "event_msg" && responseTexts.has(line.text)) {
|
|
13617
|
+
continue;
|
|
13618
|
+
}
|
|
13619
|
+
const turnId = line.turnId ?? activeTurnId;
|
|
13620
|
+
const model = turnId === void 0 ? void 0 : modelByTurn.get(turnId);
|
|
13621
|
+
adapted.push({
|
|
13622
|
+
kind: "assistant",
|
|
13623
|
+
...line.timestamp !== void 0 ? { timestamp: line.timestamp } : {},
|
|
13624
|
+
...line.messageId !== void 0 ? { messageId: line.messageId } : {},
|
|
13625
|
+
...model !== void 0 ? { model } : {},
|
|
13626
|
+
blocks: [{ kind: "text", text: line.text }],
|
|
13627
|
+
raw: { source: "codex-rollout" }
|
|
13628
|
+
});
|
|
13629
|
+
continue;
|
|
13630
|
+
}
|
|
13631
|
+
if (line.kind === "tool_call") {
|
|
13632
|
+
if (line.callId === void 0 || line.name === void 0)
|
|
13633
|
+
continue;
|
|
13634
|
+
const turnId = line.turnId ?? activeTurnId;
|
|
13635
|
+
const model = turnId === void 0 ? void 0 : modelByTurn.get(turnId);
|
|
13636
|
+
adapted.push({
|
|
13637
|
+
kind: "assistant",
|
|
13638
|
+
...line.timestamp !== void 0 ? { timestamp: line.timestamp } : {},
|
|
13639
|
+
...model !== void 0 ? { model } : {},
|
|
13640
|
+
blocks: [
|
|
13641
|
+
{
|
|
13642
|
+
kind: "tool_use",
|
|
13643
|
+
id: line.callId,
|
|
13644
|
+
name: line.name,
|
|
13645
|
+
input: codexToolInput(line.input)
|
|
13646
|
+
}
|
|
13647
|
+
],
|
|
13648
|
+
raw: { source: "codex-rollout" }
|
|
13649
|
+
});
|
|
13650
|
+
continue;
|
|
13651
|
+
}
|
|
13652
|
+
if (line.kind === "tool_result" && line.callId !== void 0) {
|
|
13653
|
+
adapted.push({
|
|
13654
|
+
kind: "user",
|
|
13655
|
+
...line.timestamp !== void 0 ? { timestamp: line.timestamp } : {},
|
|
13656
|
+
toolResults: [
|
|
13657
|
+
{
|
|
13658
|
+
toolUseId: line.callId,
|
|
13659
|
+
isError: line.isError === true,
|
|
13660
|
+
content: line.output
|
|
13661
|
+
}
|
|
13662
|
+
],
|
|
13663
|
+
raw: { source: "codex-rollout" }
|
|
13664
|
+
});
|
|
13665
|
+
}
|
|
13666
|
+
}
|
|
13667
|
+
return sortedByTimestamp(adapted);
|
|
13668
|
+
}
|
|
13669
|
+
function codexToolInput(value) {
|
|
13670
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
13671
|
+
return value;
|
|
13672
|
+
}
|
|
13673
|
+
return value === void 0 ? {} : { input: value };
|
|
13674
|
+
}
|
|
13018
13675
|
function collectResults(lines) {
|
|
13019
13676
|
const results = /* @__PURE__ */ new Map();
|
|
13020
13677
|
for (const line of lines) {
|
|
@@ -13081,7 +13738,15 @@ function nodeEstTokens(steps, results) {
|
|
|
13081
13738
|
}
|
|
13082
13739
|
return counted ? { estTokens: sum } : {};
|
|
13083
13740
|
}
|
|
13084
|
-
var DETAIL_KEYS = [
|
|
13741
|
+
var DETAIL_KEYS = [
|
|
13742
|
+
"command",
|
|
13743
|
+
"file_path",
|
|
13744
|
+
"description",
|
|
13745
|
+
"subject",
|
|
13746
|
+
"pattern",
|
|
13747
|
+
"prompt",
|
|
13748
|
+
"input"
|
|
13749
|
+
];
|
|
13085
13750
|
function inputDetail(input) {
|
|
13086
13751
|
for (const key of DETAIL_KEYS) {
|
|
13087
13752
|
const value = input[key];
|
|
@@ -13130,6 +13795,131 @@ function deriveTurnUsages(lines) {
|
|
|
13130
13795
|
}
|
|
13131
13796
|
return [...byMessage.values()];
|
|
13132
13797
|
}
|
|
13798
|
+
var ZERO_CODEX_USAGE = {
|
|
13799
|
+
inputTokens: 0,
|
|
13800
|
+
cachedInputTokens: 0,
|
|
13801
|
+
cacheWriteInputTokens: 0,
|
|
13802
|
+
outputTokens: 0,
|
|
13803
|
+
reasoningOutputTokens: 0,
|
|
13804
|
+
totalTokens: 0
|
|
13805
|
+
};
|
|
13806
|
+
function deriveCodexTurnUsages(lines) {
|
|
13807
|
+
const modelByTurn = /* @__PURE__ */ new Map();
|
|
13808
|
+
for (const line of lines) {
|
|
13809
|
+
if (line.kind === "turn_context" && line.turnId !== void 0 && line.model !== void 0) {
|
|
13810
|
+
modelByTurn.set(line.turnId, line.model);
|
|
13811
|
+
}
|
|
13812
|
+
}
|
|
13813
|
+
const turns = [];
|
|
13814
|
+
let latestTotal;
|
|
13815
|
+
let active;
|
|
13816
|
+
function finishActive() {
|
|
13817
|
+
const current = active;
|
|
13818
|
+
if (current?.terminal === void 0)
|
|
13819
|
+
return;
|
|
13820
|
+
active = void 0;
|
|
13821
|
+
const delta = current.latest === void 0 ? void 0 : subtractCodexUsage(current.latest, current.baseline);
|
|
13822
|
+
if (delta === void 0)
|
|
13823
|
+
return;
|
|
13824
|
+
if (delta.inputTokens === 0 && delta.outputTokens === 0 && (delta.totalTokens ?? 0) === 0) {
|
|
13825
|
+
return;
|
|
13826
|
+
}
|
|
13827
|
+
const inputTokens = delta.inputTokens - delta.cachedInputTokens - delta.cacheWriteInputTokens;
|
|
13828
|
+
if (inputTokens < 0)
|
|
13829
|
+
return;
|
|
13830
|
+
const model = current.turnId === void 0 ? void 0 : modelByTurn.get(current.turnId);
|
|
13831
|
+
const view = {
|
|
13832
|
+
...current.turnId !== void 0 ? { messageId: current.turnId } : {},
|
|
13833
|
+
...model !== void 0 ? { model } : {},
|
|
13834
|
+
inputTokens,
|
|
13835
|
+
cacheCreationInputTokens: delta.cacheWriteInputTokens,
|
|
13836
|
+
cacheReadInputTokens: delta.cachedInputTokens,
|
|
13837
|
+
outputTokens: delta.outputTokens,
|
|
13838
|
+
inputTotalTokens: delta.inputTokens,
|
|
13839
|
+
...current.terminal === "aborted" ? { status: "aborted" } : current.terminal === "incomplete" ? { status: "incomplete" } : {},
|
|
13840
|
+
...current.startedAt !== void 0 ? { ts: current.startedAt } : {}
|
|
13841
|
+
};
|
|
13842
|
+
if (delta.reasoningOutputTokens !== void 0 && delta.reasoningOutputTokens <= delta.outputTokens) {
|
|
13843
|
+
view.reasoningOutputTokens = delta.reasoningOutputTokens;
|
|
13844
|
+
}
|
|
13845
|
+
if (delta.totalTokens !== void 0)
|
|
13846
|
+
view.totalTokens = delta.totalTokens;
|
|
13847
|
+
turns.push(view);
|
|
13848
|
+
}
|
|
13849
|
+
for (const line of lines) {
|
|
13850
|
+
if (line.kind === "task_started") {
|
|
13851
|
+
if (active !== void 0 && active.terminal === void 0)
|
|
13852
|
+
active.terminal = "incomplete";
|
|
13853
|
+
finishActive();
|
|
13854
|
+
active = {
|
|
13855
|
+
...line.turnId !== void 0 ? { turnId: line.turnId } : {},
|
|
13856
|
+
...line.timestamp !== void 0 ? { startedAt: line.timestamp } : {},
|
|
13857
|
+
baseline: latestTotal ?? ZERO_CODEX_USAGE
|
|
13858
|
+
};
|
|
13859
|
+
continue;
|
|
13860
|
+
}
|
|
13861
|
+
if (line.kind === "token_count") {
|
|
13862
|
+
const snapshot = codexUsageSnapshot(line.totalUsage);
|
|
13863
|
+
if (snapshot === void 0)
|
|
13864
|
+
continue;
|
|
13865
|
+
latestTotal = snapshot;
|
|
13866
|
+
if (active !== void 0) {
|
|
13867
|
+
active.latest = snapshot;
|
|
13868
|
+
if (active.terminal !== void 0)
|
|
13869
|
+
finishActive();
|
|
13870
|
+
}
|
|
13871
|
+
continue;
|
|
13872
|
+
}
|
|
13873
|
+
if (line.kind !== "task_complete" && line.kind !== "turn_aborted" || active === void 0) {
|
|
13874
|
+
continue;
|
|
13875
|
+
}
|
|
13876
|
+
if (line.turnId !== void 0 && active.turnId !== void 0 && line.turnId !== active.turnId) {
|
|
13877
|
+
continue;
|
|
13878
|
+
}
|
|
13879
|
+
active.terminal = line.kind === "turn_aborted" ? "aborted" : "complete";
|
|
13880
|
+
}
|
|
13881
|
+
if (active !== void 0 && active.terminal === void 0)
|
|
13882
|
+
active.terminal = "incomplete";
|
|
13883
|
+
finishActive();
|
|
13884
|
+
return turns;
|
|
13885
|
+
}
|
|
13886
|
+
function codexUsageSnapshot(usage) {
|
|
13887
|
+
if (usage?.inputTokens === void 0 || usage.cachedInputTokens === void 0 || usage.cacheWriteInputTokens === void 0 || usage.outputTokens === void 0) {
|
|
13888
|
+
return void 0;
|
|
13889
|
+
}
|
|
13890
|
+
return {
|
|
13891
|
+
inputTokens: usage.inputTokens,
|
|
13892
|
+
cachedInputTokens: usage.cachedInputTokens,
|
|
13893
|
+
cacheWriteInputTokens: usage.cacheWriteInputTokens,
|
|
13894
|
+
outputTokens: usage.outputTokens,
|
|
13895
|
+
...usage.reasoningOutputTokens !== void 0 ? { reasoningOutputTokens: usage.reasoningOutputTokens } : {},
|
|
13896
|
+
...usage.totalTokens !== void 0 ? { totalTokens: usage.totalTokens } : {}
|
|
13897
|
+
};
|
|
13898
|
+
}
|
|
13899
|
+
function subtractCodexUsage(current, baseline) {
|
|
13900
|
+
const inputTokens = current.inputTokens - baseline.inputTokens;
|
|
13901
|
+
const cachedInputTokens = current.cachedInputTokens - baseline.cachedInputTokens;
|
|
13902
|
+
const cacheWriteInputTokens = current.cacheWriteInputTokens - baseline.cacheWriteInputTokens;
|
|
13903
|
+
const outputTokens = current.outputTokens - baseline.outputTokens;
|
|
13904
|
+
if (inputTokens < 0 || cachedInputTokens < 0 || cacheWriteInputTokens < 0 || outputTokens < 0) {
|
|
13905
|
+
return void 0;
|
|
13906
|
+
}
|
|
13907
|
+
const reasoningOutputTokens = optionalUsageDelta(current.reasoningOutputTokens, baseline.reasoningOutputTokens);
|
|
13908
|
+
const totalTokens = optionalUsageDelta(current.totalTokens, baseline.totalTokens);
|
|
13909
|
+
return {
|
|
13910
|
+
inputTokens,
|
|
13911
|
+
cachedInputTokens,
|
|
13912
|
+
cacheWriteInputTokens,
|
|
13913
|
+
outputTokens,
|
|
13914
|
+
...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens } : {},
|
|
13915
|
+
...totalTokens !== void 0 ? { totalTokens } : {}
|
|
13916
|
+
};
|
|
13917
|
+
}
|
|
13918
|
+
function optionalUsageDelta(current, baseline) {
|
|
13919
|
+
if (current === void 0 || baseline === void 0 || current < baseline)
|
|
13920
|
+
return void 0;
|
|
13921
|
+
return current - baseline;
|
|
13922
|
+
}
|
|
13133
13923
|
function deriveCurrentAction(steps, lastEvent, pendingHookTool) {
|
|
13134
13924
|
const open2 = [...steps].reverse().find((s) => s.endTs === void 0);
|
|
13135
13925
|
if (open2) {
|
|
@@ -13164,7 +13954,7 @@ function deriveAgentQuote(lines) {
|
|
|
13164
13954
|
}
|
|
13165
13955
|
return null;
|
|
13166
13956
|
}
|
|
13167
|
-
function deriveLastPrompt(lines) {
|
|
13957
|
+
function deriveLastPrompt(lines, hookPrompt, hookPromptAt) {
|
|
13168
13958
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
13169
13959
|
const line = lines[i];
|
|
13170
13960
|
if (line === void 0 || line.kind !== "user" || line.agentId !== void 0)
|
|
@@ -13173,9 +13963,12 @@ function deriveLastPrompt(lines) {
|
|
|
13173
13963
|
continue;
|
|
13174
13964
|
if (line.text === void 0 || line.text.trim() === "")
|
|
13175
13965
|
continue;
|
|
13966
|
+
if (hookPrompt !== void 0 && hookPromptAt !== void 0 && line.timestamp !== void 0 && hookPromptAt > line.timestamp) {
|
|
13967
|
+
return hookPrompt;
|
|
13968
|
+
}
|
|
13176
13969
|
return truncate(line.text.trim(), DETAIL_LIMIT);
|
|
13177
13970
|
}
|
|
13178
|
-
return null;
|
|
13971
|
+
return hookPrompt ?? null;
|
|
13179
13972
|
}
|
|
13180
13973
|
function truncate(text, limit) {
|
|
13181
13974
|
return text.length <= limit ? text : `${text.slice(0, limit - 1)}\u2026`;
|
|
@@ -13237,96 +14030,65 @@ async function loadHarness(harness, mode) {
|
|
|
13237
14030
|
};
|
|
13238
14031
|
}
|
|
13239
14032
|
|
|
13240
|
-
// apps/cli/dist/
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
import { readFileSync as readFileSync11 } from "node:fs";
|
|
14033
|
+
// apps/cli/dist/runtime-state.js
|
|
14034
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
14035
|
+
import { closeSync, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync11, renameSync as renameSync3, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
14036
|
+
import { homedir as homedir9 } from "node:os";
|
|
13245
14037
|
import { dirname as dirname11, join as join17 } from "node:path";
|
|
13246
|
-
|
|
13247
|
-
|
|
13248
|
-
var SEND_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
13249
|
-
var SEND_TIMEOUT_MS = 3e3;
|
|
13250
|
-
var MAX_ERROR_TYPES = 10;
|
|
13251
|
-
function emptyState2() {
|
|
13252
|
-
return {
|
|
13253
|
-
scans: 0,
|
|
13254
|
-
toggleApplies: 0,
|
|
13255
|
-
detected: { cc: false, codex: false, dsh: false },
|
|
13256
|
-
liveWired: { cc: false, codex: false, dsh: false },
|
|
13257
|
-
errorTypes: /* @__PURE__ */ new Set()
|
|
13258
|
-
};
|
|
13259
|
-
}
|
|
13260
|
-
var state = emptyState2();
|
|
13261
|
-
function recordDetect(harness, installed) {
|
|
13262
|
-
state.detected[harness] = installed;
|
|
13263
|
-
}
|
|
13264
|
-
function recordScan(config) {
|
|
13265
|
-
state.scans += 1;
|
|
13266
|
-
state.detected[config.harness] = true;
|
|
13267
|
-
state.liveWired[config.harness] = config.capabilities.live !== "none";
|
|
13268
|
-
}
|
|
13269
|
-
function recordHookInstall(harness) {
|
|
13270
|
-
state.liveWired[harness] = true;
|
|
13271
|
-
}
|
|
13272
|
-
function recordApply() {
|
|
13273
|
-
state.toggleApplies += 1;
|
|
13274
|
-
}
|
|
13275
|
-
function recordError(error) {
|
|
13276
|
-
if (state.errorTypes.size >= MAX_ERROR_TYPES)
|
|
13277
|
-
return;
|
|
13278
|
-
state.errorTypes.add(error instanceof Error ? error.constructor.name : typeof error);
|
|
14038
|
+
function createHookToken() {
|
|
14039
|
+
return randomBytes2(32).toString("base64url");
|
|
13279
14040
|
}
|
|
13280
|
-
function
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
error_types: [...state.errorTypes].sort()
|
|
13293
|
-
};
|
|
13294
|
-
}
|
|
13295
|
-
function appVersion() {
|
|
13296
|
-
if (typeof __WINGMAN_VERSION__ === "string")
|
|
13297
|
-
return __WINGMAN_VERSION__;
|
|
14041
|
+
function writeServeRuntimeState(port, token, options = {}) {
|
|
14042
|
+
const warn = options.warn ?? (() => {
|
|
14043
|
+
});
|
|
14044
|
+
const startedAtMs = (options.now ?? Date.now)();
|
|
14045
|
+
const pid = options.pid ?? process.pid;
|
|
14046
|
+
const suffix = randomBytes2(8).toString("base64url");
|
|
14047
|
+
const owner = `serve-${startedAtMs}-${pid}-${suffix}`;
|
|
14048
|
+
const path = join17(options.homeDir ?? homedir9(), ".wingman", "state", "serve", `${owner}.json`);
|
|
14049
|
+
const tempPath = join17(dirname11(path), `.${owner}.tmp`);
|
|
14050
|
+
const contents = Buffer.from(`${JSON.stringify({ version: 2, owner, pid, port, startedAtMs, token })}
|
|
14051
|
+
`, "utf8");
|
|
14052
|
+
let fd;
|
|
13298
14053
|
try {
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
14054
|
+
mkdirSync6(dirname11(path), { recursive: true, mode: 448 });
|
|
14055
|
+
fd = openSync(tempPath, "wx", 384);
|
|
14056
|
+
writeFileSync7(fd, contents);
|
|
14057
|
+
closeSync(fd);
|
|
14058
|
+
fd = void 0;
|
|
14059
|
+
renameSync3(tempPath, path);
|
|
14060
|
+
return { path, contents };
|
|
14061
|
+
} catch (error) {
|
|
14062
|
+
if (fd !== void 0) {
|
|
14063
|
+
try {
|
|
14064
|
+
closeSync(fd);
|
|
14065
|
+
} catch {
|
|
14066
|
+
}
|
|
14067
|
+
}
|
|
14068
|
+
try {
|
|
14069
|
+
rmSync4(tempPath, { force: true });
|
|
14070
|
+
} catch {
|
|
14071
|
+
}
|
|
14072
|
+
warn(`Codex hook \u8FD0\u884C\u6001\u5199\u5165\u5931\u8D25,\u9762\u677F\u7EE7\u7EED\u8FD0\u884C:${errorMessage(error)}`);
|
|
14073
|
+
return void 0;
|
|
13304
14074
|
}
|
|
13305
14075
|
}
|
|
13306
|
-
|
|
13307
|
-
|
|
14076
|
+
function removeServeRuntimeState(registration) {
|
|
14077
|
+
if (registration === void 0)
|
|
14078
|
+
return;
|
|
13308
14079
|
try {
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13312
|
-
body: JSON.stringify(payload),
|
|
13313
|
-
signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
|
|
13314
|
-
});
|
|
14080
|
+
const current = readFileSync11(registration.path);
|
|
14081
|
+
if (current.equals(registration.contents))
|
|
14082
|
+
rmSync4(registration.path, { force: true });
|
|
13315
14083
|
} catch {
|
|
13316
14084
|
}
|
|
13317
14085
|
}
|
|
13318
|
-
function
|
|
13319
|
-
|
|
13320
|
-
const licenseState = licenseStatus(options.homeDir === void 0 ? {} : { homeDir: options.homeDir }).state;
|
|
13321
|
-
void sendMetrics(buildPayload({ version: appVersion(), licenseState }), options.baseUrl);
|
|
13322
|
-
};
|
|
13323
|
-
send();
|
|
13324
|
-
const timer = setInterval(send, options.intervalMs ?? SEND_INTERVAL_MS);
|
|
13325
|
-
timer.unref();
|
|
13326
|
-
return () => clearInterval(timer);
|
|
14086
|
+
function errorMessage(error) {
|
|
14087
|
+
return error instanceof Error ? error.message : String(error);
|
|
13327
14088
|
}
|
|
13328
14089
|
|
|
13329
14090
|
// apps/cli/dist/scan.js
|
|
14091
|
+
init_dist();
|
|
13330
14092
|
async function scanValidated(adapter, options) {
|
|
13331
14093
|
const config = await adapter.scan(options);
|
|
13332
14094
|
const errors = validateUnifiedConfig(config);
|
|
@@ -13334,12 +14096,12 @@ async function scanValidated(adapter, options) {
|
|
|
13334
14096
|
throw new Error(`${adapter.harness} \u7684 scan \u8F93\u51FA\u4E0D\u7B26\u5408 UnifiedConfig \u5408\u540C:
|
|
13335
14097
|
- ${errors.join("\n- ")}`);
|
|
13336
14098
|
}
|
|
13337
|
-
recordScan(config);
|
|
13338
14099
|
return config;
|
|
13339
14100
|
}
|
|
13340
14101
|
|
|
13341
14102
|
// apps/cli/dist/server.js
|
|
13342
14103
|
init_dist();
|
|
14104
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
13343
14105
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
13344
14106
|
import { createServer } from "node:http";
|
|
13345
14107
|
import { extname, resolve as resolve5, sep as sep2 } from "node:path";
|
|
@@ -13392,9 +14154,9 @@ function residentTotal(config) {
|
|
|
13392
14154
|
}
|
|
13393
14155
|
return counted ? { kind, value: sum } : void 0;
|
|
13394
14156
|
}
|
|
13395
|
-
function stepToolNames(
|
|
14157
|
+
function stepToolNames(state) {
|
|
13396
14158
|
const names = [];
|
|
13397
|
-
for (const trace of
|
|
14159
|
+
for (const trace of state.traces) {
|
|
13398
14160
|
for (const node of trace.nodes) {
|
|
13399
14161
|
for (const step of node.steps)
|
|
13400
14162
|
names.push(step.toolName);
|
|
@@ -13402,23 +14164,23 @@ function stepToolNames(state2) {
|
|
|
13402
14164
|
}
|
|
13403
14165
|
return names;
|
|
13404
14166
|
}
|
|
13405
|
-
function deriveSuggestions(
|
|
14167
|
+
function deriveSuggestions(state, config, nowMs) {
|
|
13406
14168
|
const suggestions = [];
|
|
13407
|
-
if (
|
|
13408
|
-
const sinceMs = Date.parse(
|
|
14169
|
+
if (state.waiting !== null) {
|
|
14170
|
+
const sinceMs = Date.parse(state.waiting.since);
|
|
13409
14171
|
if (Number.isFinite(sinceMs) && nowMs - sinceMs > WAITING_TIMEOUT_MS) {
|
|
13410
14172
|
suggestions.push({
|
|
13411
14173
|
id: "waiting-timeout",
|
|
13412
14174
|
rule: "waiting-timeout",
|
|
13413
|
-
text: `\u5DF2\u7B49\u5F85 ${fmtDuration(nowMs - sinceMs)}:${
|
|
13414
|
-
source: `\u6765\u6E90:${
|
|
14175
|
+
text: `\u5DF2\u7B49\u5F85 ${fmtDuration(nowMs - sinceMs)}:${state.waiting.message}`,
|
|
14176
|
+
source: `\u6765\u6E90:${state.waiting.kind === "permission" ? "PermissionRequest" : "Notification"} \u4E8B\u4EF6,${state.waiting.since} \u8D77\u672A\u51B3;\u9608\u503C 60s`
|
|
13415
14177
|
});
|
|
13416
14178
|
}
|
|
13417
14179
|
}
|
|
13418
14180
|
if (config !== void 0) {
|
|
13419
14181
|
const refSources = [
|
|
13420
|
-
...
|
|
13421
|
-
...
|
|
14182
|
+
...state.lastPrompt !== null ? [{ file: "\u7528\u6237\u63D0\u793A", text: state.lastPrompt }] : [],
|
|
14183
|
+
...state.agentQuote !== null ? [{ file: "agent \u81EA\u8FF0", text: state.agentQuote }] : []
|
|
13422
14184
|
];
|
|
13423
14185
|
if (refSources.length > 0) {
|
|
13424
14186
|
for (const items of Object.values(config.slots)) {
|
|
@@ -13440,8 +14202,9 @@ function deriveSuggestions(state2, config, nowMs) {
|
|
|
13440
14202
|
}
|
|
13441
14203
|
}
|
|
13442
14204
|
}
|
|
13443
|
-
const toolNames = stepToolNames(
|
|
13444
|
-
|
|
14205
|
+
const toolNames = stepToolNames(state);
|
|
14206
|
+
const hasCompletedTurn = state.turnUsages.some((turn) => turn.status === void 0);
|
|
14207
|
+
if (toolNames.length > 0 && hasCompletedTurn) {
|
|
13445
14208
|
for (const item of config.slots.tools) {
|
|
13446
14209
|
if (!item.enabled || !item.toggleable)
|
|
13447
14210
|
continue;
|
|
@@ -13465,9 +14228,9 @@ function deriveSuggestions(state2, config, nowMs) {
|
|
|
13465
14228
|
});
|
|
13466
14229
|
}
|
|
13467
14230
|
}
|
|
13468
|
-
const lastTurn =
|
|
14231
|
+
const lastTurn = state.turnUsages[state.turnUsages.length - 1];
|
|
13469
14232
|
const resident = residentTotal(config);
|
|
13470
|
-
if (lastTurn !== void 0 && resident !== void 0) {
|
|
14233
|
+
if (lastTurn?.contextTotal !== void 0 && resident !== void 0) {
|
|
13471
14234
|
const ctx = lastTurn.contextTotal.value;
|
|
13472
14235
|
const history = ctx - resident.value;
|
|
13473
14236
|
if (ctx >= HISTORY_BLOAT_MIN_CONTEXT && history / ctx > HISTORY_BLOAT_RATIO) {
|
|
@@ -13500,13 +14263,16 @@ var CONTENT_TYPES = {
|
|
|
13500
14263
|
".woff2": "font/woff2"
|
|
13501
14264
|
};
|
|
13502
14265
|
var ANATOMY_CACHE_TTL_MS = 3e4;
|
|
14266
|
+
var HOOK_IDENTITY_PATH = "/api/hook-target";
|
|
14267
|
+
var HOOK_TOKEN_HEADER = "x-wingman-hook-token";
|
|
14268
|
+
var HOOK_PROOF_HEADER = "x-wingman-hook-proof";
|
|
14269
|
+
var PANEL_ACTION_HEADER = "x-wingman-panel-action";
|
|
13503
14270
|
function createWingmanServer(options) {
|
|
13504
14271
|
const warn = options.warn ?? ((message) => console.error(message));
|
|
13505
14272
|
const runtime = { anatomy: null };
|
|
13506
14273
|
return createServer((req, res) => {
|
|
13507
14274
|
handle(req, res, options, warn, runtime).catch((error) => {
|
|
13508
|
-
|
|
13509
|
-
warn(`[wingman] \u8BF7\u6C42\u5904\u7406\u5931\u8D25:${errorMessage(error)}`);
|
|
14275
|
+
warn(`[wingman] \u8BF7\u6C42\u5904\u7406\u5931\u8D25:${errorMessage2(error)}`);
|
|
13510
14276
|
if (!res.headersSent)
|
|
13511
14277
|
sendText2(res, 500, "internal error");
|
|
13512
14278
|
else
|
|
@@ -13532,7 +14298,28 @@ async function handle(req, res, options, warn, runtime) {
|
|
|
13532
14298
|
return;
|
|
13533
14299
|
}
|
|
13534
14300
|
}
|
|
14301
|
+
if (options.hookToken && url.pathname === HOOK_IDENTITY_PATH) {
|
|
14302
|
+
if (req.method !== "GET") {
|
|
14303
|
+
sendText2(res, 405, `method not allowed:${HOOK_IDENTITY_PATH} \u53EA\u63A5\u53D7 GET`);
|
|
14304
|
+
return;
|
|
14305
|
+
}
|
|
14306
|
+
const nonce = url.searchParams.get("nonce");
|
|
14307
|
+
if (nonce === null || !/^[A-Za-z0-9_-]{16,128}$/.test(nonce)) {
|
|
14308
|
+
sendText2(res, 400, "nonce \u9700\u8981 16-128 \u4F4D base64url \u5B57\u7B26");
|
|
14309
|
+
return;
|
|
14310
|
+
}
|
|
14311
|
+
res.writeHead(204, {
|
|
14312
|
+
[HOOK_PROOF_HEADER]: hookProof(options.hookToken, nonce),
|
|
14313
|
+
"cache-control": "no-store"
|
|
14314
|
+
});
|
|
14315
|
+
res.end();
|
|
14316
|
+
return;
|
|
14317
|
+
}
|
|
13535
14318
|
if (options.receiver && url.pathname === "/hook") {
|
|
14319
|
+
if (url.searchParams.get("harness") === "codex" && options.hookToken !== void 0 && !sameToken(options.hookToken, req.headers[HOOK_TOKEN_HEADER])) {
|
|
14320
|
+
sendText2(res, 403, "Codex hook token \u65E0\u6548");
|
|
14321
|
+
return;
|
|
14322
|
+
}
|
|
13536
14323
|
options.receiver.handleHook(req, res);
|
|
13537
14324
|
return;
|
|
13538
14325
|
}
|
|
@@ -13540,6 +14327,14 @@ async function handle(req, res, options, warn, runtime) {
|
|
|
13540
14327
|
options.receiver.handleEvents(req, res);
|
|
13541
14328
|
return;
|
|
13542
14329
|
}
|
|
14330
|
+
if (options.narrative && url.pathname === "/api/narrative/events") {
|
|
14331
|
+
if (req.method !== "GET") {
|
|
14332
|
+
sendText2(res, 405, "method not allowed:/api/narrative/events \u53EA\u63A5\u53D7 GET");
|
|
14333
|
+
return;
|
|
14334
|
+
}
|
|
14335
|
+
handleNarrativeEvents(req, res, options.narrative);
|
|
14336
|
+
return;
|
|
14337
|
+
}
|
|
13543
14338
|
if (url.pathname === "/api/action/plan" && req.method === "POST") {
|
|
13544
14339
|
await handleActionPlan(req, res, options);
|
|
13545
14340
|
return;
|
|
@@ -13552,13 +14347,24 @@ async function handle(req, res, options, warn, runtime) {
|
|
|
13552
14347
|
await handleLicenseInstall(req, res, options);
|
|
13553
14348
|
return;
|
|
13554
14349
|
}
|
|
14350
|
+
if (url.pathname === "/api/license/refresh") {
|
|
14351
|
+
if (req.method !== "POST") {
|
|
14352
|
+
sendText2(res, 405, "method not allowed:/api/license/refresh \u53EA\u63A5\u53D7 POST");
|
|
14353
|
+
return;
|
|
14354
|
+
}
|
|
14355
|
+
if (req.headers[PANEL_ACTION_HEADER] !== "refresh-license") {
|
|
14356
|
+
sendJson(res, 403, { error: "\u5237\u65B0\u6388\u6743\u5FC5\u987B\u7531 Wingman \u9762\u677F\u4E2D\u7684\u6309\u94AE\u89E6\u53D1" });
|
|
14357
|
+
return;
|
|
14358
|
+
}
|
|
14359
|
+
await handleLicenseRefresh(res, options);
|
|
14360
|
+
return;
|
|
14361
|
+
}
|
|
13555
14362
|
if (req.method !== "GET") {
|
|
13556
14363
|
sendText2(res, 405, "method not allowed");
|
|
13557
14364
|
return;
|
|
13558
14365
|
}
|
|
13559
14366
|
if (url.pathname === "/api/license") {
|
|
13560
|
-
|
|
13561
|
-
sendJson(res, 200, { state: state2, detail });
|
|
14367
|
+
sendJson(res, 200, publicLicenseStatus(licenseStatus(options.licenseOptions)));
|
|
13562
14368
|
return;
|
|
13563
14369
|
}
|
|
13564
14370
|
if (url.pathname === "/api/narrative") {
|
|
@@ -13578,9 +14384,9 @@ async function handle(req, res, options, warn, runtime) {
|
|
|
13578
14384
|
const config = (await cachedAnatomy(options, warn, runtime)).find((c) => c.harness === harness);
|
|
13579
14385
|
const nowMs = Date.now();
|
|
13580
14386
|
const states = {};
|
|
13581
|
-
for (const [id,
|
|
13582
|
-
const suggestions = deriveSuggestions(
|
|
13583
|
-
states[id] = suggestions.length > 0 ? { ...
|
|
14387
|
+
for (const [id, state] of Object.entries(overview.states)) {
|
|
14388
|
+
const suggestions = deriveSuggestions(state, config, nowMs);
|
|
14389
|
+
states[id] = suggestions.length > 0 ? { ...state, suggestions } : state;
|
|
13584
14390
|
}
|
|
13585
14391
|
overview.states = states;
|
|
13586
14392
|
}
|
|
@@ -13599,6 +14405,40 @@ async function handle(req, res, options, warn, runtime) {
|
|
|
13599
14405
|
}
|
|
13600
14406
|
await serveStatic(url.pathname, res, options.panelDistDir);
|
|
13601
14407
|
}
|
|
14408
|
+
function hookProof(token, nonce) {
|
|
14409
|
+
return createHmac("sha256", token).update(nonce).digest("base64url");
|
|
14410
|
+
}
|
|
14411
|
+
function sameToken(expected, actual) {
|
|
14412
|
+
if (typeof actual !== "string")
|
|
14413
|
+
return false;
|
|
14414
|
+
const left = Buffer.from(expected, "utf8");
|
|
14415
|
+
const right = Buffer.from(actual, "utf8");
|
|
14416
|
+
return left.length === right.length && timingSafeEqual(left, right);
|
|
14417
|
+
}
|
|
14418
|
+
function handleNarrativeEvents(_req, res, narrative) {
|
|
14419
|
+
res.writeHead(200, {
|
|
14420
|
+
"content-type": "text/event-stream",
|
|
14421
|
+
"cache-control": "no-cache",
|
|
14422
|
+
connection: "keep-alive"
|
|
14423
|
+
});
|
|
14424
|
+
res.on("error", () => {
|
|
14425
|
+
});
|
|
14426
|
+
res.write(": connected\n\n");
|
|
14427
|
+
const unsubscribe = narrative.subscribe((harness) => {
|
|
14428
|
+
if (!res.destroyed)
|
|
14429
|
+
res.write(`data: ${JSON.stringify({ harness })}
|
|
14430
|
+
|
|
14431
|
+
`);
|
|
14432
|
+
});
|
|
14433
|
+
const heartbeat = setInterval(() => {
|
|
14434
|
+
if (!res.destroyed)
|
|
14435
|
+
res.write(": heartbeat\n\n");
|
|
14436
|
+
}, 15e3);
|
|
14437
|
+
res.once("close", () => {
|
|
14438
|
+
clearInterval(heartbeat);
|
|
14439
|
+
unsubscribe();
|
|
14440
|
+
});
|
|
14441
|
+
}
|
|
13602
14442
|
async function cachedAnatomy(options, warn, runtime) {
|
|
13603
14443
|
const nowMs = Date.now();
|
|
13604
14444
|
if (runtime.anatomy !== null && nowMs - runtime.anatomy.at < ANATOMY_CACHE_TTL_MS) {
|
|
@@ -13614,11 +14454,9 @@ async function scanAll(harnesses, warn) {
|
|
|
13614
14454
|
let installed;
|
|
13615
14455
|
try {
|
|
13616
14456
|
const detected = await adapter.detect(scanOptions);
|
|
13617
|
-
recordDetect(harness, detected.installed);
|
|
13618
14457
|
installed = detected.installed;
|
|
13619
14458
|
} catch (error) {
|
|
13620
|
-
|
|
13621
|
-
warn(`[wingman] ${harness} detect \u5931\u8D25,\u5DF2\u8DF3\u8FC7:${errorMessage(error)}`);
|
|
14459
|
+
warn(`[wingman] ${harness} detect \u5931\u8D25,\u5DF2\u8DF3\u8FC7:${errorMessage2(error)}`);
|
|
13622
14460
|
continue;
|
|
13623
14461
|
}
|
|
13624
14462
|
if (!installed)
|
|
@@ -13626,8 +14464,7 @@ async function scanAll(harnesses, warn) {
|
|
|
13626
14464
|
try {
|
|
13627
14465
|
configs.push(await scanValidated(adapter, scanOptions));
|
|
13628
14466
|
} catch (error) {
|
|
13629
|
-
|
|
13630
|
-
warn(`[wingman] ${harness} \u626B\u63CF\u5931\u8D25,\u964D\u7EA7\u663E\u793A:${errorMessage(error)}`);
|
|
14467
|
+
warn(`[wingman] ${harness} \u626B\u63CF\u5931\u8D25,\u964D\u7EA7\u663E\u793A:${errorMessage2(error)}`);
|
|
13631
14468
|
configs.push(degradedScanConfig(harness, error));
|
|
13632
14469
|
}
|
|
13633
14470
|
}
|
|
@@ -13664,7 +14501,7 @@ function degradedScanConfig(harness, error) {
|
|
|
13664
14501
|
};
|
|
13665
14502
|
}
|
|
13666
14503
|
function errorSummary(error) {
|
|
13667
|
-
const first =
|
|
14504
|
+
const first = errorMessage2(error).split("\n", 1)[0] ?? "";
|
|
13668
14505
|
const scrubbed = first.replace(/(?:[A-Za-z]:)?[\\/][^\s'"()|,;]+/g, "\u2026").trim();
|
|
13669
14506
|
return scrubbed.length <= 120 ? scrubbed : `${scrubbed.slice(0, 119)}\u2026`;
|
|
13670
14507
|
}
|
|
@@ -13688,7 +14525,7 @@ async function handleActionPlan(req, res, options) {
|
|
|
13688
14525
|
const plan = await loaded.adapter.planAction(action, loaded.scanOptions);
|
|
13689
14526
|
sendJson(res, 200, plan);
|
|
13690
14527
|
} catch (error) {
|
|
13691
|
-
sendJson(res, 400, { error:
|
|
14528
|
+
sendJson(res, 400, { error: errorMessage2(error) });
|
|
13692
14529
|
}
|
|
13693
14530
|
}
|
|
13694
14531
|
async function handleActionApply(req, res, options, runtime) {
|
|
@@ -13718,13 +14555,11 @@ async function handleActionApply(req, res, options, runtime) {
|
|
|
13718
14555
|
try {
|
|
13719
14556
|
const result = await loaded.adapter.applyAction(plan, loaded.scanOptions);
|
|
13720
14557
|
if (result.ok) {
|
|
13721
|
-
recordApply();
|
|
13722
14558
|
runtime.anatomy = null;
|
|
13723
14559
|
}
|
|
13724
14560
|
sendJson(res, 200, result);
|
|
13725
14561
|
} catch (error) {
|
|
13726
|
-
|
|
13727
|
-
sendJson(res, 400, { error: errorMessage(error) });
|
|
14562
|
+
sendJson(res, 400, { error: errorMessage2(error) });
|
|
13728
14563
|
}
|
|
13729
14564
|
}
|
|
13730
14565
|
var LICENSE_BODY_LIMIT = 64 * 1024;
|
|
@@ -13739,7 +14574,44 @@ async function handleLicenseInstall(req, res, options) {
|
|
|
13739
14574
|
sendJson(res, 400, { error: result.error });
|
|
13740
14575
|
return;
|
|
13741
14576
|
}
|
|
13742
|
-
sendJson(res, 200,
|
|
14577
|
+
sendJson(res, 200, publicLicenseStatus(result.status));
|
|
14578
|
+
}
|
|
14579
|
+
async function handleLicenseRefresh(res, options) {
|
|
14580
|
+
const homeDir = options.licenseRefreshOptions?.homeDir ?? options.licenseOptions?.homeDir;
|
|
14581
|
+
const publicKeyPem = options.licenseRefreshOptions?.publicKeyPem ?? options.licenseOptions?.publicKeyPem;
|
|
14582
|
+
const now = options.licenseRefreshOptions?.now ?? options.licenseOptions?.now;
|
|
14583
|
+
const refreshOptions = {
|
|
14584
|
+
...options.licenseRefreshOptions,
|
|
14585
|
+
...homeDir === void 0 ? {} : { homeDir },
|
|
14586
|
+
...publicKeyPem === void 0 ? {} : { publicKeyPem },
|
|
14587
|
+
...now === void 0 ? {} : { now }
|
|
14588
|
+
};
|
|
14589
|
+
const result = await refreshLicense(refreshOptions);
|
|
14590
|
+
if (!result.ok) {
|
|
14591
|
+
sendJson(res, 400, {
|
|
14592
|
+
error: result.message,
|
|
14593
|
+
canSubscribe: result.reason === "inactive"
|
|
14594
|
+
});
|
|
14595
|
+
return;
|
|
14596
|
+
}
|
|
14597
|
+
const statusOptions = {
|
|
14598
|
+
...options.licenseOptions,
|
|
14599
|
+
...homeDir === void 0 ? {} : { homeDir },
|
|
14600
|
+
...publicKeyPem === void 0 ? {} : { publicKeyPem },
|
|
14601
|
+
...now === void 0 ? {} : { now }
|
|
14602
|
+
};
|
|
14603
|
+
sendJson(res, 200, {
|
|
14604
|
+
...publicLicenseStatus(licenseStatus(statusOptions)),
|
|
14605
|
+
message: result.message
|
|
14606
|
+
});
|
|
14607
|
+
}
|
|
14608
|
+
function publicLicenseStatus(status) {
|
|
14609
|
+
const body = { state: status.state, detail: status.detail };
|
|
14610
|
+
if (status.claims) {
|
|
14611
|
+
body.plan = status.claims.plan;
|
|
14612
|
+
body.exp = status.claims.exp;
|
|
14613
|
+
}
|
|
14614
|
+
return body;
|
|
13743
14615
|
}
|
|
13744
14616
|
function readTextBody(req, maxBytes) {
|
|
13745
14617
|
return new Promise((resolveBody) => {
|
|
@@ -13830,7 +14702,7 @@ function sendText2(res, status, body) {
|
|
|
13830
14702
|
res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
|
|
13831
14703
|
res.end(body);
|
|
13832
14704
|
}
|
|
13833
|
-
function
|
|
14705
|
+
function errorMessage2(error) {
|
|
13834
14706
|
return error instanceof Error ? error.message : String(error);
|
|
13835
14707
|
}
|
|
13836
14708
|
|
|
@@ -13914,6 +14786,7 @@ ${counts}
|
|
|
13914
14786
|
}
|
|
13915
14787
|
async function runServe(args) {
|
|
13916
14788
|
const { port, fixtures, panelDist } = parseServeArgs(args);
|
|
14789
|
+
const hookToken = createHookToken();
|
|
13917
14790
|
const harnesses = [];
|
|
13918
14791
|
for (const id of HARNESS_IDS) {
|
|
13919
14792
|
try {
|
|
@@ -13924,7 +14797,7 @@ async function runServe(args) {
|
|
|
13924
14797
|
`);
|
|
13925
14798
|
}
|
|
13926
14799
|
}
|
|
13927
|
-
const panelDistDir = panelDist === void 0 ? resolve6(dirname12(
|
|
14800
|
+
const panelDistDir = panelDist === void 0 ? resolve6(dirname12(fileURLToPath2(import.meta.url)), "../../panel/dist") : resolve6(panelDist);
|
|
13928
14801
|
const narrative = createNarrativeService({ followTranscripts: !fixtures });
|
|
13929
14802
|
if (fixtures) {
|
|
13930
14803
|
try {
|
|
@@ -13941,13 +14814,28 @@ async function runServe(args) {
|
|
|
13941
14814
|
panelDistDir,
|
|
13942
14815
|
receiver: createReceiver({ onEvent: narrative.handleEvent }),
|
|
13943
14816
|
readonlyActions: fixtures,
|
|
13944
|
-
narrative
|
|
14817
|
+
narrative,
|
|
14818
|
+
hookToken
|
|
13945
14819
|
});
|
|
13946
14820
|
const actualPort = await listen(server, port);
|
|
13947
|
-
|
|
14821
|
+
const runtimeRegistration = fixtures ? void 0 : writeServeRuntimeState(actualPort, hookToken, {
|
|
14822
|
+
warn: (message) => process.stderr.write(`[wingman] ${message}
|
|
14823
|
+
`)
|
|
14824
|
+
});
|
|
14825
|
+
const cleanupRuntime = () => removeServeRuntimeState(runtimeRegistration);
|
|
14826
|
+
server.once("close", cleanupRuntime);
|
|
14827
|
+
process.once("exit", cleanupRuntime);
|
|
14828
|
+
process.once("SIGINT", () => {
|
|
14829
|
+
cleanupRuntime();
|
|
14830
|
+
process.exit(130);
|
|
14831
|
+
});
|
|
14832
|
+
process.once("SIGTERM", () => {
|
|
14833
|
+
cleanupRuntime();
|
|
14834
|
+
process.exit(143);
|
|
14835
|
+
});
|
|
13948
14836
|
const mode = fixtures ? "(fixtures \u6A21\u5F0F:\u6570\u636E\u6765\u81EA @wingman/fixtures \u6837\u4F8B,\u975E\u672C\u673A\u914D\u7F6E)" : "";
|
|
13949
14837
|
process.stdout.write(`Wingman \u9762\u677F:http://127.0.0.1:${actualPort}${mode}
|
|
13950
|
-
\u53EA\u7ED1 127.0.0.1,\u9762\u677F\u4E0E\u6570\u636E\u90FD\u5728\u672C\u673A;\
|
|
14838
|
+
\u53EA\u7ED1 127.0.0.1,\u9762\u677F\u4E0E\u6570\u636E\u90FD\u5728\u672C\u673A;\u96F6\u9065\u6D4B\u2014\u2014\u552F\u4E00\u51FA\u95E8\u7684\u6570\u636E\u662F\u4F60\u5728\u9762\u677F\u9010\u6B21\u4E3B\u52A8\u5206\u4EAB\u7684\u6837\u672C,\u53E3\u5F84 getwingman.dev/data\u3002Ctrl+C \u9000\u51FA\u3002
|
|
13951
14839
|
`);
|
|
13952
14840
|
return 0;
|
|
13953
14841
|
}
|
|
@@ -13975,10 +14863,13 @@ async function runHook(args) {
|
|
|
13975
14863
|
const { adapter } = await loadHarness(harness, { fixtures: false });
|
|
13976
14864
|
if (action === "install") {
|
|
13977
14865
|
await adapter.installHooks(port);
|
|
13978
|
-
|
|
13979
|
-
|
|
14866
|
+
if (harness === "codex") {
|
|
14867
|
+
process.stdout.write("wingman:codex \u914D\u7F6E\u5B8C\u6210\uFF1B\u9996\u6B21\u63A5\u7EBF\u8BF7\u5728 Codex \u8F93\u5165 /hooks\uFF0C\u5C06 Wingman hooks \u8BBE\u4E3A Trusted + Enabled\u3002\u6388\u6743\u540E\u65B0\u4E8B\u4EF6\u5E94\u8FDB\u5165 Wingman\uFF1B\u82E5\u5F53\u524D\u4EFB\u52A1\u4ECD\u65E0\u4E8B\u4EF6\uFF0C\u8BF7\u5B8C\u6574\u9000\u51FA\u91CD\u5F00\u540E\u7528\u65B0\u4EFB\u52A1\u9A8C\u8BC1\u3002\n\u5347\u7EA7\u65F6\u53EA\u8981\u53D7\u7BA1 command \u4FDD\u6301\u4E0D\u53D8\uFF0C\u65E0\u9700\u91CD\u590D\u4FE1\u4EFB\uFF1Bcommand \u53D8\u5316\u6216\u53D7\u7BA1\u5757\u6B8B\u7F3A/\u88AB\u4FEE\u6539\u65F6\u4F8B\u5916\u3002\n\u5199\u524D\u5DF2\u5907\u4EFD;\u91CD\u590D\u6267\u884C\u5E42\u7B49;\u89E3\u7EBF:wingman hook uninstall codex\n");
|
|
14868
|
+
} else {
|
|
14869
|
+
process.stdout.write(`wingman:${harness} \u5B9E\u51B5\u63A5\u7EBF\u5B8C\u6210(POST http://127.0.0.1:${port}/hook?harness=${harness})\u3002
|
|
13980
14870
|
\u5199\u524D\u5DF2\u5907\u4EFD;\u91CD\u590D\u6267\u884C\u5E42\u7B49;\u89E3\u7EBF:wingman hook uninstall ${harness}
|
|
13981
14871
|
`);
|
|
14872
|
+
}
|
|
13982
14873
|
} else {
|
|
13983
14874
|
await adapter.uninstallHooks();
|
|
13984
14875
|
process.stdout.write(`wingman:${harness} \u5DF2\u89E3\u7EBF,\u914D\u7F6E\u5DF2\u6062\u590D\u3002
|