harness-wingman 0.3.0 → 0.4.1
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 +1134 -226
- 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-CHt8loKP.js +0 -202
- package/apps/panel/dist/assets/index-CP99dQS1.css +0 -1
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";
|
|
@@ -11767,7 +11936,7 @@ function createReceiver(options = {}) {
|
|
|
11767
11936
|
}
|
|
11768
11937
|
accept({ harness, received_at: (/* @__PURE__ */ new Date()).toISOString(), payload: parsed });
|
|
11769
11938
|
sendText(res, 200, "ok");
|
|
11770
|
-
}, () => sendText(res, 400, "\u8BFB\u53D6\u8BF7\u6C42\u4F53\u5931\u8D25"));
|
|
11939
|
+
}, (error) => sendText(res, 400, error instanceof Error ? error.message : "\u8BFB\u53D6\u8BF7\u6C42\u4F53\u5931\u8D25"));
|
|
11771
11940
|
}
|
|
11772
11941
|
function handleEvents(req, res) {
|
|
11773
11942
|
if (req.method !== "GET") {
|
|
@@ -11824,10 +11993,20 @@ function isHookPayload(value) {
|
|
|
11824
11993
|
const name = value.hook_event_name;
|
|
11825
11994
|
return typeof name === "string" && name.length > 0;
|
|
11826
11995
|
}
|
|
11996
|
+
var HOOK_BODY_LIMIT = 1024 * 1024;
|
|
11827
11997
|
function readBody(req) {
|
|
11828
11998
|
return new Promise((resolve7, reject) => {
|
|
11829
11999
|
const chunks = [];
|
|
11830
|
-
|
|
12000
|
+
let size = 0;
|
|
12001
|
+
req.on("data", (chunk) => {
|
|
12002
|
+
size += chunk.length;
|
|
12003
|
+
if (size > HOOK_BODY_LIMIT) {
|
|
12004
|
+
reject(new Error("body \u8D85\u8FC7 1MB \u4E0A\u9650"));
|
|
12005
|
+
req.destroy();
|
|
12006
|
+
return;
|
|
12007
|
+
}
|
|
12008
|
+
chunks.push(chunk);
|
|
12009
|
+
});
|
|
11831
12010
|
req.on("end", () => resolve7(Buffer.concat(chunks).toString("utf8")));
|
|
11832
12011
|
req.on("error", reject);
|
|
11833
12012
|
});
|
|
@@ -11886,7 +12065,7 @@ function verifyEntitlement(raw, now, publicKeyPem) {
|
|
|
11886
12065
|
let claims;
|
|
11887
12066
|
try {
|
|
11888
12067
|
const decoded = JSON.parse(Buffer.from(parsed.token, "base64url").toString("utf8"));
|
|
11889
|
-
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))) {
|
|
11890
12069
|
return { state: "invalid", detail: "license \u8F7D\u8377\u5B57\u6BB5\u4E0D\u5B8C\u6574" };
|
|
11891
12070
|
}
|
|
11892
12071
|
claims = { sub: decoded.sub, plan: decoded.plan, iat: decoded.iat, exp: decoded.exp };
|
|
@@ -11894,10 +12073,22 @@ function verifyEntitlement(raw, now, publicKeyPem) {
|
|
|
11894
12073
|
return { state: "invalid", detail: "license \u8F7D\u8377\u65E0\u6CD5\u89E3\u7801" };
|
|
11895
12074
|
}
|
|
11896
12075
|
const exp = Date.parse(claims.exp);
|
|
11897
|
-
const
|
|
12076
|
+
const isTrial = claims.plan === "trial";
|
|
11898
12077
|
if (now.getTime() < exp) {
|
|
11899
|
-
return {
|
|
12078
|
+
return {
|
|
12079
|
+
state: "active",
|
|
12080
|
+
detail: `${isTrial ? "\u8BD5\u7528" : "\u8BA2\u9605"}\u6709\u6548\u81F3 ${claims.exp}`,
|
|
12081
|
+
claims
|
|
12082
|
+
};
|
|
11900
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;
|
|
11901
12092
|
if (now.getTime() < graceEnd) {
|
|
11902
12093
|
return {
|
|
11903
12094
|
state: "grace",
|
|
@@ -11955,7 +12146,8 @@ async function refreshLicense(options = {}) {
|
|
|
11955
12146
|
} catch {
|
|
11956
12147
|
return {
|
|
11957
12148
|
ok: false,
|
|
11958
|
-
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"
|
|
11959
12151
|
};
|
|
11960
12152
|
}
|
|
11961
12153
|
let response;
|
|
@@ -11967,43 +12159,91 @@ async function refreshLicense(options = {}) {
|
|
|
11967
12159
|
signal: AbortSignal.timeout(15e3)
|
|
11968
12160
|
});
|
|
11969
12161
|
} catch {
|
|
11970
|
-
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
|
+
};
|
|
11971
12167
|
}
|
|
11972
12168
|
if (!response.ok) {
|
|
11973
12169
|
let detail = `\u7B7E\u53D1\u7AEF\u8FD4\u56DE HTTP ${response.status}`;
|
|
12170
|
+
let inactive = false;
|
|
11974
12171
|
try {
|
|
11975
12172
|
const body = await response.json();
|
|
11976
12173
|
if (typeof body.error === "string")
|
|
11977
12174
|
detail = body.error;
|
|
12175
|
+
inactive = body.reason === "inactive";
|
|
11978
12176
|
} catch {
|
|
11979
12177
|
}
|
|
11980
|
-
return {
|
|
12178
|
+
return {
|
|
12179
|
+
ok: false,
|
|
12180
|
+
message: `\u7EED\u671F\u88AB\u62D2:${detail}`,
|
|
12181
|
+
reason: inactive ? "inactive" : "server"
|
|
12182
|
+
};
|
|
11981
12183
|
}
|
|
11982
12184
|
let entitlement;
|
|
11983
12185
|
try {
|
|
11984
12186
|
entitlement = await response.json();
|
|
11985
12187
|
} catch {
|
|
11986
|
-
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
|
+
};
|
|
11987
12193
|
}
|
|
11988
12194
|
if (typeof entitlement.token !== "string" || typeof entitlement.signature !== "string") {
|
|
11989
|
-
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
|
+
};
|
|
11990
12200
|
}
|
|
11991
12201
|
const next = `${JSON.stringify({ token: entitlement.token, signature: entitlement.signature })}
|
|
11992
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
|
+
}
|
|
11993
12229
|
const tmpPath = join3(dirname2(licensePath), `.license.json.tmp-${process.pid}`);
|
|
11994
12230
|
try {
|
|
11995
12231
|
writeFileSync2(tmpPath, next);
|
|
11996
12232
|
renameSync2(tmpPath, licensePath);
|
|
11997
12233
|
} catch (error) {
|
|
11998
12234
|
const message = error instanceof Error ? error.message : String(error);
|
|
11999
|
-
return { ok: false, message: `\u5199\u5165 ${licensePath} \u5931\u8D25:${message}
|
|
12235
|
+
return { ok: false, message: `\u5199\u5165 ${licensePath} \u5931\u8D25:${message}`, reason: "write" };
|
|
12000
12236
|
}
|
|
12001
12237
|
return { ok: true, message: `license \u5DF2\u7EED\u671F${expSuffix(entitlement.token)}` };
|
|
12002
12238
|
}
|
|
12003
12239
|
function expSuffix(token) {
|
|
12004
12240
|
try {
|
|
12005
12241
|
const claims = JSON.parse(Buffer.from(token, "base64url").toString("utf8"));
|
|
12006
|
-
|
|
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}`;
|
|
12007
12247
|
} catch {
|
|
12008
12248
|
return "";
|
|
12009
12249
|
}
|
|
@@ -12104,6 +12344,246 @@ function isInProgressAt(entry, ts) {
|
|
|
12104
12344
|
return entry.spans.some((span) => span.status === "in_progress" && span.from !== void 0 && span.from <= ts && (span.to === void 0 || ts < span.to));
|
|
12105
12345
|
}
|
|
12106
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
|
+
|
|
12107
12587
|
// packages/narrative/dist/parse.js
|
|
12108
12588
|
function parseTranscriptLine(line) {
|
|
12109
12589
|
const trimmed = line.trim();
|
|
@@ -12115,7 +12595,7 @@ function parseTranscriptLine(line) {
|
|
|
12115
12595
|
} catch {
|
|
12116
12596
|
return { kind: "unknown", reason: "JSON \u89E3\u6790\u5931\u8D25", rawText: line };
|
|
12117
12597
|
}
|
|
12118
|
-
if (!
|
|
12598
|
+
if (!isRecord3(value))
|
|
12119
12599
|
return { kind: "unknown", reason: "\u884C\u4E0D\u662F JSON \u5BF9\u8C61", rawText: line };
|
|
12120
12600
|
const type = value.type;
|
|
12121
12601
|
if (typeof type !== "string") {
|
|
@@ -12130,7 +12610,7 @@ function parseTranscriptLine(line) {
|
|
|
12130
12610
|
const attachment = {
|
|
12131
12611
|
kind: "attachment",
|
|
12132
12612
|
...common,
|
|
12133
|
-
attachment:
|
|
12613
|
+
attachment: isRecord3(value.attachment) ? value.attachment : {}
|
|
12134
12614
|
};
|
|
12135
12615
|
return attachment;
|
|
12136
12616
|
}
|
|
@@ -12154,14 +12634,14 @@ function extractCommon(raw) {
|
|
|
12154
12634
|
}
|
|
12155
12635
|
function parseUser(raw, common) {
|
|
12156
12636
|
const line = { kind: "user", ...common, toolResults: [] };
|
|
12157
|
-
const message =
|
|
12637
|
+
const message = isRecord3(raw.message) ? raw.message : void 0;
|
|
12158
12638
|
const content = message?.content;
|
|
12159
12639
|
if (typeof content === "string") {
|
|
12160
12640
|
line.text = content;
|
|
12161
12641
|
} else if (Array.isArray(content)) {
|
|
12162
12642
|
const texts = [];
|
|
12163
12643
|
for (const block of content) {
|
|
12164
|
-
if (!
|
|
12644
|
+
if (!isRecord3(block))
|
|
12165
12645
|
continue;
|
|
12166
12646
|
if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
|
|
12167
12647
|
const result = {
|
|
@@ -12184,20 +12664,20 @@ function parseUser(raw, common) {
|
|
|
12184
12664
|
}
|
|
12185
12665
|
function parseAssistant(raw, common) {
|
|
12186
12666
|
const line = { kind: "assistant", ...common, blocks: [] };
|
|
12187
|
-
const message =
|
|
12667
|
+
const message = isRecord3(raw.message) ? raw.message : void 0;
|
|
12188
12668
|
if (message === void 0)
|
|
12189
12669
|
return line;
|
|
12190
12670
|
if (typeof message.model === "string")
|
|
12191
12671
|
line.model = message.model;
|
|
12192
12672
|
if (typeof message.id === "string")
|
|
12193
12673
|
line.messageId = message.id;
|
|
12194
|
-
if (
|
|
12674
|
+
if (isRecord3(message.usage))
|
|
12195
12675
|
line.usage = message.usage;
|
|
12196
12676
|
const content = message.content;
|
|
12197
12677
|
if (!Array.isArray(content))
|
|
12198
12678
|
return line;
|
|
12199
12679
|
for (const block of content) {
|
|
12200
|
-
if (!
|
|
12680
|
+
if (!isRecord3(block))
|
|
12201
12681
|
continue;
|
|
12202
12682
|
line.blocks.push(parseAssistantBlock(block));
|
|
12203
12683
|
}
|
|
@@ -12215,7 +12695,7 @@ function parseAssistantBlock(block) {
|
|
|
12215
12695
|
kind: "tool_use",
|
|
12216
12696
|
id: block.id,
|
|
12217
12697
|
name: block.name,
|
|
12218
|
-
input:
|
|
12698
|
+
input: isRecord3(block.input) ? block.input : {}
|
|
12219
12699
|
};
|
|
12220
12700
|
}
|
|
12221
12701
|
return {
|
|
@@ -12224,7 +12704,7 @@ function parseAssistantBlock(block) {
|
|
|
12224
12704
|
raw: block
|
|
12225
12705
|
};
|
|
12226
12706
|
}
|
|
12227
|
-
function
|
|
12707
|
+
function isRecord3(value) {
|
|
12228
12708
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12229
12709
|
}
|
|
12230
12710
|
|
|
@@ -12409,7 +12889,7 @@ function buildTaskCreateResultIndex(lines) {
|
|
|
12409
12889
|
if (line.kind !== "user")
|
|
12410
12890
|
continue;
|
|
12411
12891
|
const result = line.toolUseResult;
|
|
12412
|
-
if (!
|
|
12892
|
+
if (!isRecord3(result) || !isRecord3(result.task) || typeof result.task.id !== "string")
|
|
12413
12893
|
continue;
|
|
12414
12894
|
for (const toolResult of line.toolResults)
|
|
12415
12895
|
index.set(toolResult.toolUseId, result.task.id);
|
|
@@ -12422,7 +12902,7 @@ function applyTodoWrite(input, tasks) {
|
|
|
12422
12902
|
return false;
|
|
12423
12903
|
tasks.clear();
|
|
12424
12904
|
for (const raw of todos) {
|
|
12425
|
-
if (!
|
|
12905
|
+
if (!isRecord3(raw) || typeof raw.content !== "string" || !isTodoStatus(raw.status))
|
|
12426
12906
|
continue;
|
|
12427
12907
|
const item = { id: raw.content, content: raw.content, status: raw.status };
|
|
12428
12908
|
if (typeof raw.activeForm === "string")
|
|
@@ -12528,7 +13008,7 @@ function turnUsage(line) {
|
|
|
12528
13008
|
}
|
|
12529
13009
|
};
|
|
12530
13010
|
const breakdown = usage.cache_creation;
|
|
12531
|
-
if (
|
|
13011
|
+
if (isRecord3(breakdown)) {
|
|
12532
13012
|
const fiveMinute = finiteNumber(breakdown.ephemeral_5m_input_tokens);
|
|
12533
13013
|
const oneHour = finiteNumber(breakdown.ephemeral_1h_input_tokens);
|
|
12534
13014
|
if (fiveMinute !== void 0)
|
|
@@ -12577,7 +13057,33 @@ function createNarrativeService(options = {}) {
|
|
|
12577
13057
|
const intervalMs = options.intervalMs ?? 500;
|
|
12578
13058
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
12579
13059
|
const harnesses = /* @__PURE__ */ new Map();
|
|
13060
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
13061
|
+
const pendingNotifications = /* @__PURE__ */ new Set();
|
|
13062
|
+
let notificationQueued = false;
|
|
12580
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
|
+
}
|
|
12581
13087
|
function sessionsOf(harness) {
|
|
12582
13088
|
let sessions = harnesses.get(harness);
|
|
12583
13089
|
if (sessions === void 0) {
|
|
@@ -12590,6 +13096,7 @@ function createNarrativeService(options = {}) {
|
|
|
12590
13096
|
return {
|
|
12591
13097
|
id,
|
|
12592
13098
|
lines: [],
|
|
13099
|
+
codexLines: [],
|
|
12593
13100
|
follows: /* @__PURE__ */ new Map(),
|
|
12594
13101
|
hookIntervals: [],
|
|
12595
13102
|
waiting: null,
|
|
@@ -12640,20 +13147,27 @@ function createNarrativeService(options = {}) {
|
|
|
12640
13147
|
function resetTranscript(session, path) {
|
|
12641
13148
|
closeSession(session);
|
|
12642
13149
|
session.lines = [];
|
|
13150
|
+
session.codexLines = [];
|
|
12643
13151
|
session.hookIntervals = [];
|
|
13152
|
+
session.hookPrompt = void 0;
|
|
13153
|
+
session.hookPromptAt = void 0;
|
|
12644
13154
|
session.mockNote = void 0;
|
|
12645
13155
|
session.transcriptPath = path;
|
|
12646
13156
|
session.dirty = true;
|
|
12647
13157
|
}
|
|
12648
|
-
function startFollowing(session, path) {
|
|
13158
|
+
function startFollowing(harness, session, path) {
|
|
12649
13159
|
if (closed || session.follows.has(path))
|
|
12650
13160
|
return;
|
|
12651
13161
|
session.follows.set(path, followTranscript(path, (line) => {
|
|
12652
|
-
|
|
13162
|
+
if (harness === "codex")
|
|
13163
|
+
session.codexLines.push(parseCodexRolloutLine(line));
|
|
13164
|
+
else
|
|
13165
|
+
session.lines.push(parseTranscriptLine(line));
|
|
12653
13166
|
session.dirty = true;
|
|
13167
|
+
notify(harness);
|
|
12654
13168
|
}, { intervalMs }));
|
|
12655
13169
|
}
|
|
12656
|
-
function startSubagentDiscovery(session, transcriptPath) {
|
|
13170
|
+
function startSubagentDiscovery(harness, session, transcriptPath) {
|
|
12657
13171
|
const sessionDir = transcriptPath.replace(/\.jsonl$/, "");
|
|
12658
13172
|
if (sessionDir === transcriptPath)
|
|
12659
13173
|
return;
|
|
@@ -12662,8 +13176,9 @@ function createNarrativeService(options = {}) {
|
|
|
12662
13176
|
try {
|
|
12663
13177
|
const entries = await readdir(subagentsDir);
|
|
12664
13178
|
for (const entry of entries) {
|
|
12665
|
-
if (/^agent-.*\.jsonl$/.test(entry))
|
|
12666
|
-
startFollowing(session, join4(subagentsDir, entry));
|
|
13179
|
+
if (/^agent-.*\.jsonl$/.test(entry)) {
|
|
13180
|
+
startFollowing(harness, session, join4(subagentsDir, entry));
|
|
13181
|
+
}
|
|
12667
13182
|
}
|
|
12668
13183
|
} catch {
|
|
12669
13184
|
}
|
|
@@ -12690,8 +13205,17 @@ function createNarrativeService(options = {}) {
|
|
|
12690
13205
|
if (follow && transcriptPath !== void 0) {
|
|
12691
13206
|
if (session.transcriptPath !== transcriptPath) {
|
|
12692
13207
|
resetTranscript(session, transcriptPath);
|
|
12693
|
-
startFollowing(session, transcriptPath);
|
|
12694
|
-
|
|
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;
|
|
12695
13219
|
}
|
|
12696
13220
|
}
|
|
12697
13221
|
if (name === "SubagentStart") {
|
|
@@ -12743,13 +13267,34 @@ function createNarrativeService(options = {}) {
|
|
|
12743
13267
|
}
|
|
12744
13268
|
session.lastEvent = { name, at: event.received_at };
|
|
12745
13269
|
session.dirty = true;
|
|
13270
|
+
notify(event.harness);
|
|
12746
13271
|
}
|
|
12747
13272
|
function seedFixture(harness, rawLines, mockNote) {
|
|
12748
|
-
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));
|
|
12749
13293
|
const sessionLine = lines.find((l) => l.kind !== "unknown" && typeof l.sessionId === "string");
|
|
12750
13294
|
const id = sessionLine?.sessionId ?? "__fixture__";
|
|
12751
13295
|
const session = sessionOf(harness, id);
|
|
12752
13296
|
session.lines = lines;
|
|
13297
|
+
session.codexLines = [];
|
|
12753
13298
|
session.mockNote = mockNote;
|
|
12754
13299
|
for (const line of lines) {
|
|
12755
13300
|
if (line.kind === "unknown")
|
|
@@ -12763,6 +13308,7 @@ function createNarrativeService(options = {}) {
|
|
|
12763
13308
|
}
|
|
12764
13309
|
}
|
|
12765
13310
|
session.dirty = true;
|
|
13311
|
+
notify(harness);
|
|
12766
13312
|
}
|
|
12767
13313
|
function deriveCached(harness, session) {
|
|
12768
13314
|
if (!session.dirty && session.cached !== void 0)
|
|
@@ -12790,9 +13336,9 @@ function createNarrativeService(options = {}) {
|
|
|
12790
13336
|
const overview = getOverview(harness);
|
|
12791
13337
|
const first = overview.sessions[0];
|
|
12792
13338
|
if (first !== void 0) {
|
|
12793
|
-
const
|
|
12794
|
-
if (
|
|
12795
|
-
return
|
|
13339
|
+
const state = overview.states[first.id];
|
|
13340
|
+
if (state !== void 0)
|
|
13341
|
+
return state;
|
|
12796
13342
|
}
|
|
12797
13343
|
return emptyState(harness, now().toISOString());
|
|
12798
13344
|
}
|
|
@@ -12802,8 +13348,17 @@ function createNarrativeService(options = {}) {
|
|
|
12802
13348
|
for (const session of sessions.values())
|
|
12803
13349
|
closeSession(session);
|
|
12804
13350
|
}
|
|
13351
|
+
pendingNotifications.clear();
|
|
13352
|
+
listeners.clear();
|
|
13353
|
+
}
|
|
13354
|
+
function subscribe(listener) {
|
|
13355
|
+
if (closed)
|
|
13356
|
+
return () => {
|
|
13357
|
+
};
|
|
13358
|
+
listeners.add(listener);
|
|
13359
|
+
return () => listeners.delete(listener);
|
|
12805
13360
|
}
|
|
12806
|
-
return { handleEvent, getState, getOverview, seedFixture, close };
|
|
13361
|
+
return { handleEvent, getState, getOverview, seedFixture, subscribe, close };
|
|
12807
13362
|
}
|
|
12808
13363
|
function lastActivityOf(session) {
|
|
12809
13364
|
if (session.lastActivity !== void 0)
|
|
@@ -12816,6 +13371,13 @@ function lastActivityOf(session) {
|
|
|
12816
13371
|
if (ts !== void 0 && ts > latest)
|
|
12817
13372
|
latest = ts;
|
|
12818
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
|
+
}
|
|
12819
13381
|
return latest;
|
|
12820
13382
|
}
|
|
12821
13383
|
function isEnded(session, nowIso) {
|
|
@@ -12861,7 +13423,8 @@ function waitingMessage(payload, fallback) {
|
|
|
12861
13423
|
return fallback;
|
|
12862
13424
|
}
|
|
12863
13425
|
function derive(harness, session, nowIso) {
|
|
12864
|
-
const
|
|
13426
|
+
const codexLines = sortedCodexByTimestamp(session.codexLines);
|
|
13427
|
+
const lines = harness === "codex" ? adaptCodexNarrativeLines(codexLines) : sortedByTimestamp(session.lines);
|
|
12865
13428
|
const timeline = deriveTodoTimeline(extractTodoSnapshots(lines));
|
|
12866
13429
|
const allSteps = extractSteps(lines);
|
|
12867
13430
|
const steps = allSteps.filter((s) => !TODO_TOOLS.has(s.toolName));
|
|
@@ -12954,11 +13517,12 @@ function derive(harness, session, nowIso) {
|
|
|
12954
13517
|
todos,
|
|
12955
13518
|
...textTodos !== void 0 && textTodos.length > 0 ? { textTodos } : {},
|
|
12956
13519
|
traces,
|
|
12957
|
-
turnUsages: deriveTurnUsages(lines),
|
|
13520
|
+
turnUsages: harness === "codex" ? deriveCodexTurnUsages(codexLines) : deriveTurnUsages(lines),
|
|
12958
13521
|
currentAction: deriveCurrentAction(steps, session.lastEvent, session.pendingHookTool),
|
|
12959
13522
|
waiting: session.waiting,
|
|
12960
13523
|
agentQuote: deriveAgentQuote(lines),
|
|
12961
|
-
|
|
13524
|
+
// transcript 是会话事实源;hook prompt 只在 tail 尚未读到/格式暂不可读时兜底。
|
|
13525
|
+
lastPrompt: deriveLastPrompt(lines, session.hookPrompt, session.hookPromptAt),
|
|
12962
13526
|
updatedAt: nowIso
|
|
12963
13527
|
};
|
|
12964
13528
|
}
|
|
@@ -13005,6 +13569,109 @@ function sortedByTimestamp(lines) {
|
|
|
13005
13569
|
return ta < tb ? -1 : ta > tb ? 1 : 0;
|
|
13006
13570
|
});
|
|
13007
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
|
+
}
|
|
13008
13675
|
function collectResults(lines) {
|
|
13009
13676
|
const results = /* @__PURE__ */ new Map();
|
|
13010
13677
|
for (const line of lines) {
|
|
@@ -13071,7 +13738,15 @@ function nodeEstTokens(steps, results) {
|
|
|
13071
13738
|
}
|
|
13072
13739
|
return counted ? { estTokens: sum } : {};
|
|
13073
13740
|
}
|
|
13074
|
-
var DETAIL_KEYS = [
|
|
13741
|
+
var DETAIL_KEYS = [
|
|
13742
|
+
"command",
|
|
13743
|
+
"file_path",
|
|
13744
|
+
"description",
|
|
13745
|
+
"subject",
|
|
13746
|
+
"pattern",
|
|
13747
|
+
"prompt",
|
|
13748
|
+
"input"
|
|
13749
|
+
];
|
|
13075
13750
|
function inputDetail(input) {
|
|
13076
13751
|
for (const key of DETAIL_KEYS) {
|
|
13077
13752
|
const value = input[key];
|
|
@@ -13120,6 +13795,131 @@ function deriveTurnUsages(lines) {
|
|
|
13120
13795
|
}
|
|
13121
13796
|
return [...byMessage.values()];
|
|
13122
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
|
+
}
|
|
13123
13923
|
function deriveCurrentAction(steps, lastEvent, pendingHookTool) {
|
|
13124
13924
|
const open2 = [...steps].reverse().find((s) => s.endTs === void 0);
|
|
13125
13925
|
if (open2) {
|
|
@@ -13154,7 +13954,7 @@ function deriveAgentQuote(lines) {
|
|
|
13154
13954
|
}
|
|
13155
13955
|
return null;
|
|
13156
13956
|
}
|
|
13157
|
-
function deriveLastPrompt(lines) {
|
|
13957
|
+
function deriveLastPrompt(lines, hookPrompt, hookPromptAt) {
|
|
13158
13958
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
13159
13959
|
const line = lines[i];
|
|
13160
13960
|
if (line === void 0 || line.kind !== "user" || line.agentId !== void 0)
|
|
@@ -13163,9 +13963,12 @@ function deriveLastPrompt(lines) {
|
|
|
13163
13963
|
continue;
|
|
13164
13964
|
if (line.text === void 0 || line.text.trim() === "")
|
|
13165
13965
|
continue;
|
|
13966
|
+
if (hookPrompt !== void 0 && hookPromptAt !== void 0 && line.timestamp !== void 0 && hookPromptAt > line.timestamp) {
|
|
13967
|
+
return hookPrompt;
|
|
13968
|
+
}
|
|
13166
13969
|
return truncate(line.text.trim(), DETAIL_LIMIT);
|
|
13167
13970
|
}
|
|
13168
|
-
return null;
|
|
13971
|
+
return hookPrompt ?? null;
|
|
13169
13972
|
}
|
|
13170
13973
|
function truncate(text, limit) {
|
|
13171
13974
|
return text.length <= limit ? text : `${text.slice(0, limit - 1)}\u2026`;
|
|
@@ -13227,96 +14030,65 @@ async function loadHarness(harness, mode) {
|
|
|
13227
14030
|
};
|
|
13228
14031
|
}
|
|
13229
14032
|
|
|
13230
|
-
// apps/cli/dist/
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
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";
|
|
13235
14037
|
import { dirname as dirname11, join as join17 } from "node:path";
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
var SEND_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
13239
|
-
var SEND_TIMEOUT_MS = 3e3;
|
|
13240
|
-
var MAX_ERROR_TYPES = 10;
|
|
13241
|
-
function emptyState2() {
|
|
13242
|
-
return {
|
|
13243
|
-
scans: 0,
|
|
13244
|
-
toggleApplies: 0,
|
|
13245
|
-
detected: { cc: false, codex: false, dsh: false },
|
|
13246
|
-
liveWired: { cc: false, codex: false, dsh: false },
|
|
13247
|
-
errorTypes: /* @__PURE__ */ new Set()
|
|
13248
|
-
};
|
|
13249
|
-
}
|
|
13250
|
-
var state = emptyState2();
|
|
13251
|
-
function recordDetect(harness, installed) {
|
|
13252
|
-
state.detected[harness] = installed;
|
|
13253
|
-
}
|
|
13254
|
-
function recordScan(config) {
|
|
13255
|
-
state.scans += 1;
|
|
13256
|
-
state.detected[config.harness] = true;
|
|
13257
|
-
state.liveWired[config.harness] = config.capabilities.live !== "none";
|
|
14038
|
+
function createHookToken() {
|
|
14039
|
+
return randomBytes2(32).toString("base64url");
|
|
13258
14040
|
}
|
|
13259
|
-
function
|
|
13260
|
-
|
|
13261
|
-
}
|
|
13262
|
-
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
return {
|
|
13272
|
-
app_version: options.version,
|
|
13273
|
-
platform: process.platform,
|
|
13274
|
-
arch: process.arch,
|
|
13275
|
-
harnesses: { ...state.detected },
|
|
13276
|
-
counts: {
|
|
13277
|
-
scans: state.scans,
|
|
13278
|
-
live_wired: Object.values(state.liveWired).filter(Boolean).length,
|
|
13279
|
-
toggle_applies: state.toggleApplies
|
|
13280
|
-
},
|
|
13281
|
-
license_state: options.licenseState,
|
|
13282
|
-
error_types: [...state.errorTypes].sort()
|
|
13283
|
-
};
|
|
13284
|
-
}
|
|
13285
|
-
function appVersion() {
|
|
13286
|
-
if (typeof __WINGMAN_VERSION__ === "string")
|
|
13287
|
-
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;
|
|
13288
14053
|
try {
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
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;
|
|
13294
14074
|
}
|
|
13295
14075
|
}
|
|
13296
|
-
|
|
13297
|
-
|
|
14076
|
+
function removeServeRuntimeState(registration) {
|
|
14077
|
+
if (registration === void 0)
|
|
14078
|
+
return;
|
|
13298
14079
|
try {
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
body: JSON.stringify(payload),
|
|
13303
|
-
signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
|
|
13304
|
-
});
|
|
14080
|
+
const current = readFileSync11(registration.path);
|
|
14081
|
+
if (current.equals(registration.contents))
|
|
14082
|
+
rmSync4(registration.path, { force: true });
|
|
13305
14083
|
} catch {
|
|
13306
14084
|
}
|
|
13307
14085
|
}
|
|
13308
|
-
function
|
|
13309
|
-
|
|
13310
|
-
const licenseState = licenseStatus(options.homeDir === void 0 ? {} : { homeDir: options.homeDir }).state;
|
|
13311
|
-
void sendMetrics(buildPayload({ version: appVersion(), licenseState }), options.baseUrl);
|
|
13312
|
-
};
|
|
13313
|
-
send();
|
|
13314
|
-
const timer = setInterval(send, options.intervalMs ?? SEND_INTERVAL_MS);
|
|
13315
|
-
timer.unref();
|
|
13316
|
-
return () => clearInterval(timer);
|
|
14086
|
+
function errorMessage(error) {
|
|
14087
|
+
return error instanceof Error ? error.message : String(error);
|
|
13317
14088
|
}
|
|
13318
14089
|
|
|
13319
14090
|
// apps/cli/dist/scan.js
|
|
14091
|
+
init_dist();
|
|
13320
14092
|
async function scanValidated(adapter, options) {
|
|
13321
14093
|
const config = await adapter.scan(options);
|
|
13322
14094
|
const errors = validateUnifiedConfig(config);
|
|
@@ -13324,12 +14096,12 @@ async function scanValidated(adapter, options) {
|
|
|
13324
14096
|
throw new Error(`${adapter.harness} \u7684 scan \u8F93\u51FA\u4E0D\u7B26\u5408 UnifiedConfig \u5408\u540C:
|
|
13325
14097
|
- ${errors.join("\n- ")}`);
|
|
13326
14098
|
}
|
|
13327
|
-
recordScan(config);
|
|
13328
14099
|
return config;
|
|
13329
14100
|
}
|
|
13330
14101
|
|
|
13331
14102
|
// apps/cli/dist/server.js
|
|
13332
14103
|
init_dist();
|
|
14104
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
13333
14105
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
13334
14106
|
import { createServer } from "node:http";
|
|
13335
14107
|
import { extname, resolve as resolve5, sep as sep2 } from "node:path";
|
|
@@ -13382,9 +14154,9 @@ function residentTotal(config) {
|
|
|
13382
14154
|
}
|
|
13383
14155
|
return counted ? { kind, value: sum } : void 0;
|
|
13384
14156
|
}
|
|
13385
|
-
function stepToolNames(
|
|
14157
|
+
function stepToolNames(state) {
|
|
13386
14158
|
const names = [];
|
|
13387
|
-
for (const trace of
|
|
14159
|
+
for (const trace of state.traces) {
|
|
13388
14160
|
for (const node of trace.nodes) {
|
|
13389
14161
|
for (const step of node.steps)
|
|
13390
14162
|
names.push(step.toolName);
|
|
@@ -13392,23 +14164,23 @@ function stepToolNames(state2) {
|
|
|
13392
14164
|
}
|
|
13393
14165
|
return names;
|
|
13394
14166
|
}
|
|
13395
|
-
function deriveSuggestions(
|
|
14167
|
+
function deriveSuggestions(state, config, nowMs) {
|
|
13396
14168
|
const suggestions = [];
|
|
13397
|
-
if (
|
|
13398
|
-
const sinceMs = Date.parse(
|
|
14169
|
+
if (state.waiting !== null) {
|
|
14170
|
+
const sinceMs = Date.parse(state.waiting.since);
|
|
13399
14171
|
if (Number.isFinite(sinceMs) && nowMs - sinceMs > WAITING_TIMEOUT_MS) {
|
|
13400
14172
|
suggestions.push({
|
|
13401
14173
|
id: "waiting-timeout",
|
|
13402
14174
|
rule: "waiting-timeout",
|
|
13403
|
-
text: `\u5DF2\u7B49\u5F85 ${fmtDuration(nowMs - sinceMs)}:${
|
|
13404
|
-
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`
|
|
13405
14177
|
});
|
|
13406
14178
|
}
|
|
13407
14179
|
}
|
|
13408
14180
|
if (config !== void 0) {
|
|
13409
14181
|
const refSources = [
|
|
13410
|
-
...
|
|
13411
|
-
...
|
|
14182
|
+
...state.lastPrompt !== null ? [{ file: "\u7528\u6237\u63D0\u793A", text: state.lastPrompt }] : [],
|
|
14183
|
+
...state.agentQuote !== null ? [{ file: "agent \u81EA\u8FF0", text: state.agentQuote }] : []
|
|
13412
14184
|
];
|
|
13413
14185
|
if (refSources.length > 0) {
|
|
13414
14186
|
for (const items of Object.values(config.slots)) {
|
|
@@ -13430,8 +14202,9 @@ function deriveSuggestions(state2, config, nowMs) {
|
|
|
13430
14202
|
}
|
|
13431
14203
|
}
|
|
13432
14204
|
}
|
|
13433
|
-
const toolNames = stepToolNames(
|
|
13434
|
-
|
|
14205
|
+
const toolNames = stepToolNames(state);
|
|
14206
|
+
const hasCompletedTurn = state.turnUsages.some((turn) => turn.status === void 0);
|
|
14207
|
+
if (toolNames.length > 0 && hasCompletedTurn) {
|
|
13435
14208
|
for (const item of config.slots.tools) {
|
|
13436
14209
|
if (!item.enabled || !item.toggleable)
|
|
13437
14210
|
continue;
|
|
@@ -13455,9 +14228,9 @@ function deriveSuggestions(state2, config, nowMs) {
|
|
|
13455
14228
|
});
|
|
13456
14229
|
}
|
|
13457
14230
|
}
|
|
13458
|
-
const lastTurn =
|
|
14231
|
+
const lastTurn = state.turnUsages[state.turnUsages.length - 1];
|
|
13459
14232
|
const resident = residentTotal(config);
|
|
13460
|
-
if (lastTurn !== void 0 && resident !== void 0) {
|
|
14233
|
+
if (lastTurn?.contextTotal !== void 0 && resident !== void 0) {
|
|
13461
14234
|
const ctx = lastTurn.contextTotal.value;
|
|
13462
14235
|
const history = ctx - resident.value;
|
|
13463
14236
|
if (ctx >= HISTORY_BLOAT_MIN_CONTEXT && history / ctx > HISTORY_BLOAT_RATIO) {
|
|
@@ -13490,13 +14263,16 @@ var CONTENT_TYPES = {
|
|
|
13490
14263
|
".woff2": "font/woff2"
|
|
13491
14264
|
};
|
|
13492
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";
|
|
13493
14270
|
function createWingmanServer(options) {
|
|
13494
14271
|
const warn = options.warn ?? ((message) => console.error(message));
|
|
13495
14272
|
const runtime = { anatomy: null };
|
|
13496
14273
|
return createServer((req, res) => {
|
|
13497
14274
|
handle(req, res, options, warn, runtime).catch((error) => {
|
|
13498
|
-
|
|
13499
|
-
warn(`[wingman] \u8BF7\u6C42\u5904\u7406\u5931\u8D25:${errorMessage(error)}`);
|
|
14275
|
+
warn(`[wingman] \u8BF7\u6C42\u5904\u7406\u5931\u8D25:${errorMessage2(error)}`);
|
|
13500
14276
|
if (!res.headersSent)
|
|
13501
14277
|
sendText2(res, 500, "internal error");
|
|
13502
14278
|
else
|
|
@@ -13515,7 +14291,35 @@ function listen(server, port) {
|
|
|
13515
14291
|
}
|
|
13516
14292
|
async function handle(req, res, options, warn, runtime) {
|
|
13517
14293
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
14294
|
+
if (req.method !== "GET") {
|
|
14295
|
+
const origin = req.headers.origin;
|
|
14296
|
+
if (typeof origin === "string" && origin !== "" && !/^https?:\/\/(127\.0\.0\.1|localhost)(:\d+)?$/.test(origin)) {
|
|
14297
|
+
sendJson(res, 403, { error: `\u62D2\u7EDD\u5F02\u6E90\u5199\u8BF7\u6C42(Origin: ${origin})` });
|
|
14298
|
+
return;
|
|
14299
|
+
}
|
|
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
|
+
}
|
|
13518
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
|
+
}
|
|
13519
14323
|
options.receiver.handleHook(req, res);
|
|
13520
14324
|
return;
|
|
13521
14325
|
}
|
|
@@ -13523,6 +14327,14 @@ async function handle(req, res, options, warn, runtime) {
|
|
|
13523
14327
|
options.receiver.handleEvents(req, res);
|
|
13524
14328
|
return;
|
|
13525
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
|
+
}
|
|
13526
14338
|
if (url.pathname === "/api/action/plan" && req.method === "POST") {
|
|
13527
14339
|
await handleActionPlan(req, res, options);
|
|
13528
14340
|
return;
|
|
@@ -13535,13 +14347,24 @@ async function handle(req, res, options, warn, runtime) {
|
|
|
13535
14347
|
await handleLicenseInstall(req, res, options);
|
|
13536
14348
|
return;
|
|
13537
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
|
+
}
|
|
13538
14362
|
if (req.method !== "GET") {
|
|
13539
14363
|
sendText2(res, 405, "method not allowed");
|
|
13540
14364
|
return;
|
|
13541
14365
|
}
|
|
13542
14366
|
if (url.pathname === "/api/license") {
|
|
13543
|
-
|
|
13544
|
-
sendJson(res, 200, { state: state2, detail });
|
|
14367
|
+
sendJson(res, 200, publicLicenseStatus(licenseStatus(options.licenseOptions)));
|
|
13545
14368
|
return;
|
|
13546
14369
|
}
|
|
13547
14370
|
if (url.pathname === "/api/narrative") {
|
|
@@ -13561,9 +14384,9 @@ async function handle(req, res, options, warn, runtime) {
|
|
|
13561
14384
|
const config = (await cachedAnatomy(options, warn, runtime)).find((c) => c.harness === harness);
|
|
13562
14385
|
const nowMs = Date.now();
|
|
13563
14386
|
const states = {};
|
|
13564
|
-
for (const [id,
|
|
13565
|
-
const suggestions = deriveSuggestions(
|
|
13566
|
-
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;
|
|
13567
14390
|
}
|
|
13568
14391
|
overview.states = states;
|
|
13569
14392
|
}
|
|
@@ -13582,6 +14405,40 @@ async function handle(req, res, options, warn, runtime) {
|
|
|
13582
14405
|
}
|
|
13583
14406
|
await serveStatic(url.pathname, res, options.panelDistDir);
|
|
13584
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
|
+
}
|
|
13585
14442
|
async function cachedAnatomy(options, warn, runtime) {
|
|
13586
14443
|
const nowMs = Date.now();
|
|
13587
14444
|
if (runtime.anatomy !== null && nowMs - runtime.anatomy.at < ANATOMY_CACHE_TTL_MS) {
|
|
@@ -13597,11 +14454,9 @@ async function scanAll(harnesses, warn) {
|
|
|
13597
14454
|
let installed;
|
|
13598
14455
|
try {
|
|
13599
14456
|
const detected = await adapter.detect(scanOptions);
|
|
13600
|
-
recordDetect(harness, detected.installed);
|
|
13601
14457
|
installed = detected.installed;
|
|
13602
14458
|
} catch (error) {
|
|
13603
|
-
|
|
13604
|
-
warn(`[wingman] ${harness} detect \u5931\u8D25,\u5DF2\u8DF3\u8FC7:${errorMessage(error)}`);
|
|
14459
|
+
warn(`[wingman] ${harness} detect \u5931\u8D25,\u5DF2\u8DF3\u8FC7:${errorMessage2(error)}`);
|
|
13605
14460
|
continue;
|
|
13606
14461
|
}
|
|
13607
14462
|
if (!installed)
|
|
@@ -13609,8 +14464,7 @@ async function scanAll(harnesses, warn) {
|
|
|
13609
14464
|
try {
|
|
13610
14465
|
configs.push(await scanValidated(adapter, scanOptions));
|
|
13611
14466
|
} catch (error) {
|
|
13612
|
-
|
|
13613
|
-
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)}`);
|
|
13614
14468
|
configs.push(degradedScanConfig(harness, error));
|
|
13615
14469
|
}
|
|
13616
14470
|
}
|
|
@@ -13647,7 +14501,7 @@ function degradedScanConfig(harness, error) {
|
|
|
13647
14501
|
};
|
|
13648
14502
|
}
|
|
13649
14503
|
function errorSummary(error) {
|
|
13650
|
-
const first =
|
|
14504
|
+
const first = errorMessage2(error).split("\n", 1)[0] ?? "";
|
|
13651
14505
|
const scrubbed = first.replace(/(?:[A-Za-z]:)?[\\/][^\s'"()|,;]+/g, "\u2026").trim();
|
|
13652
14506
|
return scrubbed.length <= 120 ? scrubbed : `${scrubbed.slice(0, 119)}\u2026`;
|
|
13653
14507
|
}
|
|
@@ -13671,7 +14525,7 @@ async function handleActionPlan(req, res, options) {
|
|
|
13671
14525
|
const plan = await loaded.adapter.planAction(action, loaded.scanOptions);
|
|
13672
14526
|
sendJson(res, 200, plan);
|
|
13673
14527
|
} catch (error) {
|
|
13674
|
-
sendJson(res, 400, { error:
|
|
14528
|
+
sendJson(res, 400, { error: errorMessage2(error) });
|
|
13675
14529
|
}
|
|
13676
14530
|
}
|
|
13677
14531
|
async function handleActionApply(req, res, options, runtime) {
|
|
@@ -13701,13 +14555,11 @@ async function handleActionApply(req, res, options, runtime) {
|
|
|
13701
14555
|
try {
|
|
13702
14556
|
const result = await loaded.adapter.applyAction(plan, loaded.scanOptions);
|
|
13703
14557
|
if (result.ok) {
|
|
13704
|
-
recordApply();
|
|
13705
14558
|
runtime.anatomy = null;
|
|
13706
14559
|
}
|
|
13707
14560
|
sendJson(res, 200, result);
|
|
13708
14561
|
} catch (error) {
|
|
13709
|
-
|
|
13710
|
-
sendJson(res, 400, { error: errorMessage(error) });
|
|
14562
|
+
sendJson(res, 400, { error: errorMessage2(error) });
|
|
13711
14563
|
}
|
|
13712
14564
|
}
|
|
13713
14565
|
var LICENSE_BODY_LIMIT = 64 * 1024;
|
|
@@ -13722,7 +14574,44 @@ async function handleLicenseInstall(req, res, options) {
|
|
|
13722
14574
|
sendJson(res, 400, { error: result.error });
|
|
13723
14575
|
return;
|
|
13724
14576
|
}
|
|
13725
|
-
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;
|
|
13726
14615
|
}
|
|
13727
14616
|
function readTextBody(req, maxBytes) {
|
|
13728
14617
|
return new Promise((resolveBody) => {
|
|
@@ -13813,7 +14702,7 @@ function sendText2(res, status, body) {
|
|
|
13813
14702
|
res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
|
|
13814
14703
|
res.end(body);
|
|
13815
14704
|
}
|
|
13816
|
-
function
|
|
14705
|
+
function errorMessage2(error) {
|
|
13817
14706
|
return error instanceof Error ? error.message : String(error);
|
|
13818
14707
|
}
|
|
13819
14708
|
|
|
@@ -13897,6 +14786,7 @@ ${counts}
|
|
|
13897
14786
|
}
|
|
13898
14787
|
async function runServe(args) {
|
|
13899
14788
|
const { port, fixtures, panelDist } = parseServeArgs(args);
|
|
14789
|
+
const hookToken = createHookToken();
|
|
13900
14790
|
const harnesses = [];
|
|
13901
14791
|
for (const id of HARNESS_IDS) {
|
|
13902
14792
|
try {
|
|
@@ -13907,7 +14797,7 @@ async function runServe(args) {
|
|
|
13907
14797
|
`);
|
|
13908
14798
|
}
|
|
13909
14799
|
}
|
|
13910
|
-
const panelDistDir = panelDist === void 0 ? resolve6(dirname12(
|
|
14800
|
+
const panelDistDir = panelDist === void 0 ? resolve6(dirname12(fileURLToPath2(import.meta.url)), "../../panel/dist") : resolve6(panelDist);
|
|
13911
14801
|
const narrative = createNarrativeService({ followTranscripts: !fixtures });
|
|
13912
14802
|
if (fixtures) {
|
|
13913
14803
|
try {
|
|
@@ -13924,13 +14814,28 @@ async function runServe(args) {
|
|
|
13924
14814
|
panelDistDir,
|
|
13925
14815
|
receiver: createReceiver({ onEvent: narrative.handleEvent }),
|
|
13926
14816
|
readonlyActions: fixtures,
|
|
13927
|
-
narrative
|
|
14817
|
+
narrative,
|
|
14818
|
+
hookToken
|
|
13928
14819
|
});
|
|
13929
14820
|
const actualPort = await listen(server, port);
|
|
13930
|
-
|
|
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
|
+
});
|
|
13931
14836
|
const mode = fixtures ? "(fixtures \u6A21\u5F0F:\u6570\u636E\u6765\u81EA @wingman/fixtures \u6837\u4F8B,\u975E\u672C\u673A\u914D\u7F6E)" : "";
|
|
13932
14837
|
process.stdout.write(`Wingman \u9762\u677F:http://127.0.0.1:${actualPort}${mode}
|
|
13933
|
-
\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
|
|
13934
14839
|
`);
|
|
13935
14840
|
return 0;
|
|
13936
14841
|
}
|
|
@@ -13958,10 +14863,13 @@ async function runHook(args) {
|
|
|
13958
14863
|
const { adapter } = await loadHarness(harness, { fixtures: false });
|
|
13959
14864
|
if (action === "install") {
|
|
13960
14865
|
await adapter.installHooks(port);
|
|
13961
|
-
|
|
13962
|
-
|
|
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
|
|
13963
14870
|
\u5199\u524D\u5DF2\u5907\u4EFD;\u91CD\u590D\u6267\u884C\u5E42\u7B49;\u89E3\u7EBF:wingman hook uninstall ${harness}
|
|
13964
14871
|
`);
|
|
14872
|
+
}
|
|
13965
14873
|
} else {
|
|
13966
14874
|
await adapter.uninstallHooks();
|
|
13967
14875
|
process.stdout.write(`wingman:${harness} \u5DF2\u89E3\u7EBF,\u914D\u7F6E\u5DF2\u6062\u590D\u3002
|