@wallbreakerno4/opencode-commandcode 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +54 -0
- package/dist/disguise/backoff.d.ts +33 -0
- package/dist/disguise/backoff.js +43 -0
- package/dist/disguise/config-block.d.ts +71 -0
- package/dist/disguise/config-block.js +166 -0
- package/dist/disguise/config-freeze.d.ts +27 -0
- package/dist/disguise/config-freeze.js +87 -0
- package/dist/disguise/fingerprint.d.ts +44 -0
- package/dist/disguise/fingerprint.js +127 -0
- package/dist/disguise/hash.d.ts +8 -0
- package/dist/disguise/hash.js +13 -0
- package/dist/disguise/headers.d.ts +25 -0
- package/dist/disguise/headers.js +25 -0
- package/dist/disguise/logger.d.ts +14 -0
- package/dist/disguise/logger.js +18 -0
- package/dist/disguise/preflight.d.ts +74 -0
- package/dist/disguise/preflight.js +139 -0
- package/dist/disguise/redact.d.ts +12 -0
- package/dist/disguise/redact.js +17 -0
- package/dist/disguise/session.d.ts +11 -0
- package/dist/disguise/session.js +24 -0
- package/dist/disguise/slug.d.ts +12 -0
- package/dist/disguise/slug.js +47 -0
- package/dist/disguise/state.d.ts +62 -0
- package/dist/disguise/state.js +157 -0
- package/dist/disguise/traceparent.d.ts +7 -0
- package/dist/disguise/traceparent.js +10 -0
- package/dist/disguise/version-cache.d.ts +27 -0
- package/dist/disguise/version-cache.js +69 -0
- package/dist/disguise/version-runtime.d.ts +42 -0
- package/dist/disguise/version-runtime.js +137 -0
- package/dist/disguise/version.d.ts +21 -0
- package/dist/disguise/version.js +17 -0
- package/dist/host/constants.d.ts +14 -0
- package/dist/host/constants.js +14 -0
- package/dist/host/v1.d.ts +85 -0
- package/dist/host/v1.js +118 -0
- package/dist/host/v2.d.ts +33 -0
- package/dist/host/v2.js +110 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +33 -0
- package/dist/models/api.d.ts +23 -0
- package/dist/models/api.js +44 -0
- package/dist/models/artifact.d.ts +45 -0
- package/dist/models/artifact.js +108 -0
- package/dist/models/cascade.d.ts +47 -0
- package/dist/models/cascade.js +40 -0
- package/dist/models/mapping.d.ts +66 -0
- package/dist/models/mapping.js +47 -0
- package/dist/models/pipeline.d.ts +89 -0
- package/dist/models/pipeline.js +331 -0
- package/dist/models/signature.d.ts +20 -0
- package/dist/models/signature.js +29 -0
- package/dist/models/snapshot.d.ts +15 -0
- package/dist/models/snapshot.js +16 -0
- package/dist/models/snapshot.json +535 -0
- package/dist/models/urls.d.ts +39 -0
- package/dist/models/urls.js +96 -0
- package/dist/protocol/envelope.d.ts +118 -0
- package/dist/protocol/envelope.js +331 -0
- package/dist/protocol/errors.d.ts +97 -0
- package/dist/protocol/errors.js +281 -0
- package/dist/protocol/generate.d.ts +53 -0
- package/dist/protocol/generate.js +173 -0
- package/dist/protocol/images.d.ts +45 -0
- package/dist/protocol/images.js +115 -0
- package/dist/protocol/json.d.ts +11 -0
- package/dist/protocol/json.js +8 -0
- package/dist/protocol/ndjson.d.ts +41 -0
- package/dist/protocol/ndjson.js +321 -0
- package/dist/protocol/watchdog.d.ts +37 -0
- package/dist/protocol/watchdog.js +61 -0
- package/dist/provider/model.d.ts +95 -0
- package/dist/provider/model.js +323 -0
- package/package.json +46 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 设备指纹(disguise.md §8,#9 校准后如实上报真机真值)。
|
|
3
|
+
*
|
|
4
|
+
* - 15 字段 components 结构、thumbmark 联合哈希的拼接序与「五字段不入哈希」结构
|
|
5
|
+
* 照抄调研 §1.2–§1.3(服务端无从校验原像,结构同构无成本);
|
|
6
|
+
* - 输入换成真机真值:platform / 内核版本 / CPU / 内存 / 时区 / 网卡——真机真值
|
|
7
|
+
* 天然确定,指纹跨进程稳定随之免费获得(MAXeaglet 的 win32 随机池假人格弃用);
|
|
8
|
+
* - 采集器直接读系统 API(testing.md §1.5:不单测、不抽象注入接口,正确性归
|
|
9
|
+
* 真机人工验收);哈希纯函数照测(注入固定输入)。
|
|
10
|
+
*/
|
|
11
|
+
import { exec } from "node:child_process";
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
13
|
+
import os from "node:os";
|
|
14
|
+
import { promisify } from "node:util";
|
|
15
|
+
import { sha256Hex } from "./hash.js";
|
|
16
|
+
const execAsync = promisify(exec);
|
|
17
|
+
/**
|
|
18
|
+
* 采集命令(ioreg / reg query / git config)的防御超时,到点按失败处理。取值与
|
|
19
|
+
* §11 参数速查的「git 查询防御超时」同参数(2s/条)同理由(纯客户端防御);
|
|
20
|
+
* 官方无指纹采集器可照抄,此为采集实现自身的防御决策。
|
|
21
|
+
*/
|
|
22
|
+
const COLLECT_COMMAND_TIMEOUT_MS = 2000;
|
|
23
|
+
/**
|
|
24
|
+
* thumbmark 联合哈希:参与字段为 machineIdHash → macHashes(按序全部)→ osUserHash
|
|
25
|
+
* → hostnameHash → gitEmailHash → platform → osRelease → cpuModel → cpuCount →
|
|
26
|
+
* memGiB,分隔符 `|`;arch / timezone / isContainer / runtime / collectorVersion
|
|
27
|
+
* 五字段不入哈希(调研 §1.3,原样保留该实现取舍)。
|
|
28
|
+
*/
|
|
29
|
+
export function computeThumbmark(components) {
|
|
30
|
+
const thumbData = [
|
|
31
|
+
components.machineIdHash,
|
|
32
|
+
...components.macHashes,
|
|
33
|
+
components.osUserHash,
|
|
34
|
+
components.hostnameHash,
|
|
35
|
+
components.gitEmailHash,
|
|
36
|
+
components.platform,
|
|
37
|
+
components.osRelease,
|
|
38
|
+
components.cpuModel,
|
|
39
|
+
String(components.cpuCount),
|
|
40
|
+
String(components.memGiB),
|
|
41
|
+
].join("|");
|
|
42
|
+
return sha256Hex(thumbData);
|
|
43
|
+
}
|
|
44
|
+
/** 组装 record body:thumbmark 重算,components 原样搬运 */
|
|
45
|
+
export function buildFingerprintBody(components) {
|
|
46
|
+
return { thumbmark: computeThumbmark(components), components };
|
|
47
|
+
}
|
|
48
|
+
async function runCapture(command) {
|
|
49
|
+
try {
|
|
50
|
+
const { stdout } = await execAsync(command, { signal: AbortSignal.timeout(COLLECT_COMMAND_TIMEOUT_MS), windowsHide: true });
|
|
51
|
+
return stdout.trim();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return "";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* machineId 原像:跨重启稳定的机器标识。Linux 读 /etc/machine-id,macOS 读
|
|
59
|
+
* IOPlatformUUID,Windows 读注册表 MachineGuid;取不到时退 hostname 拼接串——
|
|
60
|
+
* 服务端无从校验原像(调研 §1.2),只需保证 64hex 形状与跨进程稳定。
|
|
61
|
+
*/
|
|
62
|
+
async function machineIdSeed(hostname) {
|
|
63
|
+
if (process.platform === "linux") {
|
|
64
|
+
const id = await readFile("/etc/machine-id", "utf8").then((raw) => raw.trim()).catch(() => "");
|
|
65
|
+
if (id)
|
|
66
|
+
return id;
|
|
67
|
+
}
|
|
68
|
+
if (process.platform === "darwin") {
|
|
69
|
+
const out = await runCapture("ioreg -rd1 -c IOPlatformExpertDevice");
|
|
70
|
+
const uuid = /"IOPlatformUUID"\s*=\s*"([^"]+)"/.exec(out)?.[1];
|
|
71
|
+
if (uuid)
|
|
72
|
+
return uuid;
|
|
73
|
+
}
|
|
74
|
+
if (process.platform === "win32") {
|
|
75
|
+
const out = await runCapture("reg query HKLM\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid");
|
|
76
|
+
const guid = /MachineGuid\s+REG_SZ\s+(\S+)/.exec(out)?.[1];
|
|
77
|
+
if (guid)
|
|
78
|
+
return guid;
|
|
79
|
+
}
|
|
80
|
+
return `machine-id:${hostname}`;
|
|
81
|
+
}
|
|
82
|
+
/** macHashes 按真实网卡数量生成(#9:多网卡主机 29 个)——非 internal 接口逐个入哈希 */
|
|
83
|
+
function macSeeds() {
|
|
84
|
+
const seeds = [];
|
|
85
|
+
for (const interfaces of Object.values(os.networkInterfaces())) {
|
|
86
|
+
for (const iface of interfaces ?? []) {
|
|
87
|
+
if (!iface.internal)
|
|
88
|
+
seeds.push(iface.mac);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return seeds;
|
|
92
|
+
}
|
|
93
|
+
/** 采集真机真值并计算各字段哈希与 thumbmark;进程启动时调用一次(disguise.md §1) */
|
|
94
|
+
export async function collectFingerprintComponents() {
|
|
95
|
+
const hostname = os.hostname();
|
|
96
|
+
const [machineIdSeedValue, gitEmail] = await Promise.all([
|
|
97
|
+
machineIdSeed(hostname),
|
|
98
|
+
runCapture("git config --get user.email"),
|
|
99
|
+
]);
|
|
100
|
+
// 无 passwd 条目的环境(个别容器)会抛错,退环境变量兜底
|
|
101
|
+
let username = "";
|
|
102
|
+
try {
|
|
103
|
+
username = os.userInfo().username;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
username = process.env["USER"] ?? process.env["USERNAME"] ?? "";
|
|
107
|
+
}
|
|
108
|
+
const components = {
|
|
109
|
+
machineIdHash: sha256Hex(machineIdSeedValue),
|
|
110
|
+
macHashes: macSeeds().map(sha256Hex),
|
|
111
|
+
osUserHash: sha256Hex(username),
|
|
112
|
+
hostnameHash: sha256Hex(hostname),
|
|
113
|
+
gitEmailHash: sha256Hex(gitEmail),
|
|
114
|
+
platform: process.platform,
|
|
115
|
+
arch: process.arch,
|
|
116
|
+
osRelease: os.release(),
|
|
117
|
+
cpuModel: os.cpus()[0]?.model ?? "",
|
|
118
|
+
cpuCount: os.cpus().length,
|
|
119
|
+
memGiB: Math.round(os.totalmem() / 2 ** 30),
|
|
120
|
+
// 抓包样本恒 false(§11.3),真实 CLI 的检测语义无从考证,照抄观测值
|
|
121
|
+
isContainer: false,
|
|
122
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC",
|
|
123
|
+
runtime: "cli",
|
|
124
|
+
collectorVersion: 1,
|
|
125
|
+
};
|
|
126
|
+
return components;
|
|
127
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 伪装模块共用哈希原语(调研 §1.2 的 sha256 辅助函数):
|
|
3
|
+
* `sha256(s) = hex(sha256(utf8(s)))`,输出 64 位小写 hex。
|
|
4
|
+
*/
|
|
5
|
+
/** sha256(utf8(input)) → 64 位小写 hex */
|
|
6
|
+
export declare function sha256Hex(input: string): string;
|
|
7
|
+
/** sha256(utf8(input)) 的 32 字节摘要,供截取与位修补类派生(会话 id、slug 字节流)使用 */
|
|
8
|
+
export declare function sha256Digest(input: string): Buffer;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 伪装模块共用哈希原语(调研 §1.2 的 sha256 辅助函数):
|
|
3
|
+
* `sha256(s) = hex(sha256(utf8(s)))`,输出 64 位小写 hex。
|
|
4
|
+
*/
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
/** sha256(utf8(input)) → 64 位小写 hex */
|
|
7
|
+
export function sha256Hex(input) {
|
|
8
|
+
return createHash("sha256").update(input, "utf8").digest("hex");
|
|
9
|
+
}
|
|
10
|
+
/** sha256(utf8(input)) 的 32 字节摘要,供截取与位修补类派生(会话 id、slug 字节流)使用 */
|
|
11
|
+
export function sha256Digest(input) {
|
|
12
|
+
return createHash("sha256").update(input, "utf8").digest();
|
|
13
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* generate 主请求的伪装头组装(disguise.md §2/§3/§10,#9 抓包 ground truth)。
|
|
3
|
+
*
|
|
4
|
+
* 头全集 = 预请求基础九键(preflight.ts buildPreflightHeaders 同源)+ 主请求专属四键:
|
|
5
|
+
* - `x-session-id`:会话身份(§3 主路径派生值,调用方经 state.resolveSessionId 取得;
|
|
6
|
+
* 与信封顶层 `threadId` 同值,#9 抓包定案);
|
|
7
|
+
* - `x-project-slug`:workingDir 哈希近似(§3,与 config 块 workingDir 同源);
|
|
8
|
+
* - `traceparent`:逐请求新造(每次调用本函数即新值,零复用);
|
|
9
|
+
* - `x-taste-learning`:恒 `"true"`(#25 注记:本插件人格按官方规则恒 true)。
|
|
10
|
+
*
|
|
11
|
+
* 预请求**不带**这四键的边界由预请求侧自己保证(§2);`x-command-code-version`
|
|
12
|
+
* 缺值时省略该键(四层兜底皆空的极端场景,与预请求同款处理)。
|
|
13
|
+
*/
|
|
14
|
+
export interface GenerateHeadersInput {
|
|
15
|
+
apiKey: string;
|
|
16
|
+
/** 版本头全局单值(D6);undefined 时省略 x-command-code-version */
|
|
17
|
+
ccVersion: string | undefined;
|
|
18
|
+
/** 会话身份(与信封 threadId 同值,§3) */
|
|
19
|
+
sessionId: string;
|
|
20
|
+
/** slug 派生来源;与 config 块 workingDir 同源(process.cwd()) */
|
|
21
|
+
workingDir: string;
|
|
22
|
+
traceparent: string;
|
|
23
|
+
}
|
|
24
|
+
/** 组装 generate 主请求伪装头全集;键序照抓包样本(基础集在前,会话四键在后) */
|
|
25
|
+
export declare function buildGenerateHeaders(input: GenerateHeadersInput): Record<string, string>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* generate 主请求的伪装头组装(disguise.md §2/§3/§10,#9 抓包 ground truth)。
|
|
3
|
+
*
|
|
4
|
+
* 头全集 = 预请求基础九键(preflight.ts buildPreflightHeaders 同源)+ 主请求专属四键:
|
|
5
|
+
* - `x-session-id`:会话身份(§3 主路径派生值,调用方经 state.resolveSessionId 取得;
|
|
6
|
+
* 与信封顶层 `threadId` 同值,#9 抓包定案);
|
|
7
|
+
* - `x-project-slug`:workingDir 哈希近似(§3,与 config 块 workingDir 同源);
|
|
8
|
+
* - `traceparent`:逐请求新造(每次调用本函数即新值,零复用);
|
|
9
|
+
* - `x-taste-learning`:恒 `"true"`(#25 注记:本插件人格按官方规则恒 true)。
|
|
10
|
+
*
|
|
11
|
+
* 预请求**不带**这四键的边界由预请求侧自己保证(§2);`x-command-code-version`
|
|
12
|
+
* 缺值时省略该键(四层兜底皆空的极端场景,与预请求同款处理)。
|
|
13
|
+
*/
|
|
14
|
+
import { buildPreflightHeaders } from "./preflight.js";
|
|
15
|
+
import { deriveProjectSlug } from "./slug.js";
|
|
16
|
+
/** 组装 generate 主请求伪装头全集;键序照抓包样本(基础集在前,会话四键在后) */
|
|
17
|
+
export function buildGenerateHeaders(input) {
|
|
18
|
+
return {
|
|
19
|
+
...buildPreflightHeaders(input.apiKey, input.ccVersion),
|
|
20
|
+
"x-session-id": input.sessionId,
|
|
21
|
+
"x-project-slug": deriveProjectSlug(input.workingDir),
|
|
22
|
+
"x-taste-learning": "true",
|
|
23
|
+
traceparent: input.traceparent,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 注入式 logger 通道(disguise.md §7):伪装模块不直接依赖 opencode 客户端——
|
|
3
|
+
* v1 注 client.app.log 适配器(#37),v2 插件 ctx 无 log 域(beta d.ts 全字段
|
|
4
|
+
* 核对,models-url-override.md §3.1)与独立调用退化 console,测试注 no-op。
|
|
5
|
+
* 级别只用两档(§7 级别表):`debug`(预请求成功、版本刷新成功,默认安静)、
|
|
6
|
+
* `warn`(预请求失败、降级路径、落盘异常)。
|
|
7
|
+
*/
|
|
8
|
+
export interface DisguiseLogger {
|
|
9
|
+
debug(message: string): void;
|
|
10
|
+
warn(message: string): void;
|
|
11
|
+
}
|
|
12
|
+
export declare const noopLogger: DisguiseLogger;
|
|
13
|
+
/** v1 与独立调用的退化通道:宿主未注入 logger 时保证伪装层日志有处可去 */
|
|
14
|
+
export declare function consoleLogger(): DisguiseLogger;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 注入式 logger 通道(disguise.md §7):伪装模块不直接依赖 opencode 客户端——
|
|
3
|
+
* v1 注 client.app.log 适配器(#37),v2 插件 ctx 无 log 域(beta d.ts 全字段
|
|
4
|
+
* 核对,models-url-override.md §3.1)与独立调用退化 console,测试注 no-op。
|
|
5
|
+
* 级别只用两档(§7 级别表):`debug`(预请求成功、版本刷新成功,默认安静)、
|
|
6
|
+
* `warn`(预请求失败、降级路径、落盘异常)。
|
|
7
|
+
*/
|
|
8
|
+
export const noopLogger = {
|
|
9
|
+
debug() { },
|
|
10
|
+
warn() { },
|
|
11
|
+
};
|
|
12
|
+
/** v1 与独立调用的退化通道:宿主未注入 logger 时保证伪装层日志有处可去 */
|
|
13
|
+
export function consoleLogger() {
|
|
14
|
+
return {
|
|
15
|
+
debug: (message) => console.debug(message),
|
|
16
|
+
warn: (message) => console.warn(message),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 伪装预请求(disguise.md §2/§4/§5):指纹记录与 lifecycle 两个请求**并行**发出
|
|
3
|
+
* (同一 `Promise.allSettled`),各自挂独立 3s 超时(`AbortSignal.timeout`)——
|
|
4
|
+
* 修 MAXeaglet 无独立超时被网关挂起拖死主请求的真 bug(调研 §8.2)。
|
|
5
|
+
*
|
|
6
|
+
* 请求头照 #9 抓包校准全集(调研 §11.3):`User-Agent: cli`、`x-cli-environment:
|
|
7
|
+
* production`、accept 家族等;**不带** `x-session-id` / `x-project-slug` /
|
|
8
|
+
* `traceparent` / `x-taste-learning`(抓包样本 capture/samples/ 证实预请求无此头,
|
|
9
|
+
* 规格 disguise.md §2 同);`x-co-flag` 真实 CLI 不发送,不存在。
|
|
10
|
+
*
|
|
11
|
+
* 结局三态合记(两请求合记,照抄 MAXeaglet 耦合结构):两 2xx 成功;4xx 任一
|
|
12
|
+
* 拒绝(优先于瞬时——服务器明确说「不」后继续敲门更像机器人);网络错/超时/5xx
|
|
13
|
+
* 任一瞬时。推进动作归 backoff.ts 状态机。本模块任何函数绝不向上抛——预请求的
|
|
14
|
+
* 任何结局都不得影响主请求(§5 总则)。
|
|
15
|
+
*/
|
|
16
|
+
import type { FetchLike } from "../protocol/json.js";
|
|
17
|
+
import { type PreflightOutcome, type PreflightState } from "./backoff.js";
|
|
18
|
+
import type { FingerprintBody } from "./fingerprint.js";
|
|
19
|
+
import { type DisguiseLogger } from "./logger.js";
|
|
20
|
+
/** 指纹记录预请求端点(调研 §11.3 抓包 ground truth) */
|
|
21
|
+
export declare const FINGERPRINT_ENDPOINT = "https://api.commandcode.ai/alpha/fingerprint/record";
|
|
22
|
+
/** lifecycle 事件预请求端点 */
|
|
23
|
+
export declare const LIFECYCLE_ENDPOINT = "https://api.commandcode.ai/alpha/lifecycle-events";
|
|
24
|
+
/** 每个预请求的独立超时(§11 参数速查:3s) */
|
|
25
|
+
export declare const PREFLIGHT_TIMEOUT_MS = 3000;
|
|
26
|
+
/**
|
|
27
|
+
* 预请求头全集(§2,#9 校准后照抄真实 CLI)。`x-command-code-version` 缺值时
|
|
28
|
+
* 省略该键(四层兜底皆空的极端场景;真实 CLI 恒带)。
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildPreflightHeaders(apiKey: string, ccVersion: string | undefined): Record<string, string>;
|
|
31
|
+
/** lifecycle 事件 body(§4,#9 校准形状;wire 键序照抓包样本) */
|
|
32
|
+
export interface LifecycleEvent {
|
|
33
|
+
eventType: "cli_session_exists";
|
|
34
|
+
metadata: {
|
|
35
|
+
sessionId: string;
|
|
36
|
+
cliVersion: string;
|
|
37
|
+
mode: "interactive";
|
|
38
|
+
os: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 构造 lifecycle 事件:`metadata.sessionId` 为与 `x-session-id` **无关**的随机
|
|
43
|
+
* `sess_<16hex>`(#9 证实真实 CLI 两套 ID 互不引用,每进程现造,照抄);`mode`
|
|
44
|
+
* 恒 interactive(连 `-p` 一次性调用也报);`os` 报真实 `platform-arch`。
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildLifecycleEvent(ccVersion: string | undefined): LifecycleEvent;
|
|
47
|
+
/** 单轮预请求的结局与可日志化细节(只含 HTTP status 与错误类别,不含头与 body) */
|
|
48
|
+
export interface PreflightRound {
|
|
49
|
+
outcome: PreflightOutcome;
|
|
50
|
+
details: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 两请求合记结局:4xx 任一 → rejected(优先于瞬时);网络错/超时/5xx 任一 →
|
|
54
|
+
* transient;两 2xx → success。details 按请求序给出 status 或错误类别。
|
|
55
|
+
*/
|
|
56
|
+
export declare function summarizePreflightResults(results: readonly PromiseSettledResult<Response>[]): PreflightRound;
|
|
57
|
+
export interface PreflightDeps {
|
|
58
|
+
apiKey: string;
|
|
59
|
+
/** 预请求调度状态载体(§5);结局后经 applyPreflightOutcome 推进并合并回此对象 */
|
|
60
|
+
preflight: PreflightState;
|
|
61
|
+
fetchImpl: FetchLike;
|
|
62
|
+
logger?: DisguiseLogger;
|
|
63
|
+
/** 当前版本头值(预请求与主请求共享全局单值,D6a) */
|
|
64
|
+
getVersion: () => Promise<string | undefined>;
|
|
65
|
+
/** 指纹 body(KeyState 进程内缓存;采集失败 reject 时按瞬时失败处理) */
|
|
66
|
+
ensureFingerprintBody: () => Promise<FingerprintBody>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 执行一轮预请求:未到点(冷却/退避未过)直接返回;否则指纹 + lifecycle 并行
|
|
70
|
+
* 上线,按结局推进状态机并记脱敏日志。**绝不向上抛**(§5:预请求任何结局都不
|
|
71
|
+
* 影响主请求)。调用方以 fire-and-forget 方式触发,本函数内部也不可再等待其他
|
|
72
|
+
* 会阻塞主请求的东西——版本初解析自带 1.5s 上限,指纹采集为本地系统调用。
|
|
73
|
+
*/
|
|
74
|
+
export declare function runPreflight(deps: PreflightDeps): Promise<void>;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 伪装预请求(disguise.md §2/§4/§5):指纹记录与 lifecycle 两个请求**并行**发出
|
|
3
|
+
* (同一 `Promise.allSettled`),各自挂独立 3s 超时(`AbortSignal.timeout`)——
|
|
4
|
+
* 修 MAXeaglet 无独立超时被网关挂起拖死主请求的真 bug(调研 §8.2)。
|
|
5
|
+
*
|
|
6
|
+
* 请求头照 #9 抓包校准全集(调研 §11.3):`User-Agent: cli`、`x-cli-environment:
|
|
7
|
+
* production`、accept 家族等;**不带** `x-session-id` / `x-project-slug` /
|
|
8
|
+
* `traceparent` / `x-taste-learning`(抓包样本 capture/samples/ 证实预请求无此头,
|
|
9
|
+
* 规格 disguise.md §2 同);`x-co-flag` 真实 CLI 不发送,不存在。
|
|
10
|
+
*
|
|
11
|
+
* 结局三态合记(两请求合记,照抄 MAXeaglet 耦合结构):两 2xx 成功;4xx 任一
|
|
12
|
+
* 拒绝(优先于瞬时——服务器明确说「不」后继续敲门更像机器人);网络错/超时/5xx
|
|
13
|
+
* 任一瞬时。推进动作归 backoff.ts 状态机。本模块任何函数绝不向上抛——预请求的
|
|
14
|
+
* 任何结局都不得影响主请求(§5 总则)。
|
|
15
|
+
*/
|
|
16
|
+
import { randomBytes } from "node:crypto";
|
|
17
|
+
import { applyPreflightOutcome } from "./backoff.js";
|
|
18
|
+
import { consoleLogger } from "./logger.js";
|
|
19
|
+
import { redactApiKey } from "./redact.js";
|
|
20
|
+
/** 指纹记录预请求端点(调研 §11.3 抓包 ground truth) */
|
|
21
|
+
export const FINGERPRINT_ENDPOINT = "https://api.commandcode.ai/alpha/fingerprint/record";
|
|
22
|
+
/** lifecycle 事件预请求端点 */
|
|
23
|
+
export const LIFECYCLE_ENDPOINT = "https://api.commandcode.ai/alpha/lifecycle-events";
|
|
24
|
+
/** 每个预请求的独立超时(§11 参数速查:3s) */
|
|
25
|
+
export const PREFLIGHT_TIMEOUT_MS = 3000;
|
|
26
|
+
/**
|
|
27
|
+
* 预请求头全集(§2,#9 校准后照抄真实 CLI)。`x-command-code-version` 缺值时
|
|
28
|
+
* 省略该键(四层兜底皆空的极端场景;真实 CLI 恒带)。
|
|
29
|
+
*/
|
|
30
|
+
export function buildPreflightHeaders(apiKey, ccVersion) {
|
|
31
|
+
const headers = {
|
|
32
|
+
"content-type": "application/json",
|
|
33
|
+
"User-Agent": "cli",
|
|
34
|
+
"x-cli-environment": "production",
|
|
35
|
+
accept: "*/*",
|
|
36
|
+
"accept-language": "*",
|
|
37
|
+
"sec-fetch-mode": "cors",
|
|
38
|
+
"accept-encoding": "br, gzip, deflate",
|
|
39
|
+
Authorization: `Bearer ${apiKey}`,
|
|
40
|
+
};
|
|
41
|
+
if (ccVersion !== undefined)
|
|
42
|
+
headers["x-command-code-version"] = ccVersion;
|
|
43
|
+
return headers;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 构造 lifecycle 事件:`metadata.sessionId` 为与 `x-session-id` **无关**的随机
|
|
47
|
+
* `sess_<16hex>`(#9 证实真实 CLI 两套 ID 互不引用,每进程现造,照抄);`mode`
|
|
48
|
+
* 恒 interactive(连 `-p` 一次性调用也报);`os` 报真实 `platform-arch`。
|
|
49
|
+
*/
|
|
50
|
+
export function buildLifecycleEvent(ccVersion) {
|
|
51
|
+
return {
|
|
52
|
+
eventType: "cli_session_exists",
|
|
53
|
+
metadata: {
|
|
54
|
+
sessionId: `sess_${randomBytes(8).toString("hex")}`,
|
|
55
|
+
cliVersion: ccVersion ?? "",
|
|
56
|
+
mode: "interactive",
|
|
57
|
+
os: `${process.platform}-${process.arch}`,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function describeFailure(reason) {
|
|
62
|
+
// AbortSignal.timeout 到点的 reject reason 是 DOMException TimeoutError
|
|
63
|
+
return reason instanceof Error && reason.name === "TimeoutError" ? "timeout" : "network-error";
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 两请求合记结局:4xx 任一 → rejected(优先于瞬时);网络错/超时/5xx 任一 →
|
|
67
|
+
* transient;两 2xx → success。details 按请求序给出 status 或错误类别。
|
|
68
|
+
*/
|
|
69
|
+
export function summarizePreflightResults(results) {
|
|
70
|
+
const details = [];
|
|
71
|
+
let outcome = "success";
|
|
72
|
+
for (const result of results) {
|
|
73
|
+
if (result.status === "rejected") {
|
|
74
|
+
details.push(describeFailure(result.reason));
|
|
75
|
+
if (outcome !== "rejected")
|
|
76
|
+
outcome = "transient";
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const status = result.value.status;
|
|
80
|
+
details.push(String(status));
|
|
81
|
+
if (status >= 400 && status < 500)
|
|
82
|
+
outcome = "rejected";
|
|
83
|
+
else if ((status < 200 || status >= 300) && outcome !== "rejected")
|
|
84
|
+
outcome = "transient";
|
|
85
|
+
}
|
|
86
|
+
return { outcome, details };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 执行一轮预请求:未到点(冷却/退避未过)直接返回;否则指纹 + lifecycle 并行
|
|
90
|
+
* 上线,按结局推进状态机并记脱敏日志。**绝不向上抛**(§5:预请求任何结局都不
|
|
91
|
+
* 影响主请求)。调用方以 fire-and-forget 方式触发,本函数内部也不可再等待其他
|
|
92
|
+
* 会阻塞主请求的东西——版本初解析自带 1.5s 上限,指纹采集为本地系统调用。
|
|
93
|
+
*/
|
|
94
|
+
export async function runPreflight(deps) {
|
|
95
|
+
const logger = deps.logger ?? consoleLogger();
|
|
96
|
+
if (Date.now() < deps.preflight.nextAttemptAt)
|
|
97
|
+
return;
|
|
98
|
+
let round;
|
|
99
|
+
try {
|
|
100
|
+
round = await executeRound(deps);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// 本地前置(指纹采集/版本解析)意外失败:按瞬时失败处理,绝不外溢
|
|
104
|
+
round = { outcome: "transient", details: ["preparation-failed"] };
|
|
105
|
+
}
|
|
106
|
+
// 纯函数返回新状态对象,此处合并回 KeyState 持有的载体
|
|
107
|
+
Object.assign(deps.preflight, applyPreflightOutcome(deps.preflight, round.outcome));
|
|
108
|
+
const key = redactApiKey(deps.apiKey);
|
|
109
|
+
if (round.outcome === "success") {
|
|
110
|
+
logger.debug(`伪装预请求成功(指纹+lifecycle) ${key}`);
|
|
111
|
+
}
|
|
112
|
+
else if (round.outcome === "rejected") {
|
|
113
|
+
logger.warn(`伪装预请求被拒绝(4xx),进入长冷却 ${key} [${round.details.join(", ")}]`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
logger.warn(`伪装预请求瞬时失败,按退避状态机重试 ${key} [${round.details.join(", ")}]`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function executeRound(deps) {
|
|
120
|
+
// 指纹采集(本地)与版本初解析并行就绪后,两请求才并行上线(Promise.allSettled:
|
|
121
|
+
// 等两个都落定再合记结局,不因先失败者提前收场)
|
|
122
|
+
const [fingerprintBody, ccVersion] = await Promise.all([deps.ensureFingerprintBody(), deps.getVersion()]);
|
|
123
|
+
const headers = buildPreflightHeaders(deps.apiKey, ccVersion);
|
|
124
|
+
const results = await Promise.allSettled([
|
|
125
|
+
deps.fetchImpl(FINGERPRINT_ENDPOINT, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers,
|
|
128
|
+
body: JSON.stringify(fingerprintBody),
|
|
129
|
+
signal: AbortSignal.timeout(PREFLIGHT_TIMEOUT_MS),
|
|
130
|
+
}),
|
|
131
|
+
deps.fetchImpl(LIFECYCLE_ENDPOINT, {
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers,
|
|
134
|
+
body: JSON.stringify(buildLifecycleEvent(ccVersion)),
|
|
135
|
+
signal: AbortSignal.timeout(PREFLIGHT_TIMEOUT_MS),
|
|
136
|
+
}),
|
|
137
|
+
]);
|
|
138
|
+
return summarizePreflightResults(results);
|
|
139
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志脱敏(disguise.md §7,老板红线:不泄露敏感信息):
|
|
3
|
+
* - apiKey 只以 `cc-key#` + sha256 前 8 位 hex 出现,日志中零原文;
|
|
4
|
+
* - thumbmark / sessionId / trace-id 本身即伪标识符,截断为前 12 位 hex + `…`;
|
|
5
|
+
* - Authorization 头 / 完整 key 永不整串输出——错误日志只记 HTTP status 与错误
|
|
6
|
+
* 类别、不序列化请求头,该纪律由调用方遵守,无对应纯函数。
|
|
7
|
+
*/
|
|
8
|
+
/** apiKey 的日志形态:`cc-key#` + sha256(apiKey) 前 8 位 hex */
|
|
9
|
+
export declare function redactApiKey(apiKey: string): string;
|
|
10
|
+
/** 伪标识符(thumbmark 64hex / sessionId uuid / trace-id 32hex)截断为前 12 位 hex + `…`;
|
|
11
|
+
* uuid 的连字符剥除后再取,保证截出的是纯 hex 位。 */
|
|
12
|
+
export declare function redactIdentifier(id: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志脱敏(disguise.md §7,老板红线:不泄露敏感信息):
|
|
3
|
+
* - apiKey 只以 `cc-key#` + sha256 前 8 位 hex 出现,日志中零原文;
|
|
4
|
+
* - thumbmark / sessionId / trace-id 本身即伪标识符,截断为前 12 位 hex + `…`;
|
|
5
|
+
* - Authorization 头 / 完整 key 永不整串输出——错误日志只记 HTTP status 与错误
|
|
6
|
+
* 类别、不序列化请求头,该纪律由调用方遵守,无对应纯函数。
|
|
7
|
+
*/
|
|
8
|
+
import { sha256Hex } from "./hash.js";
|
|
9
|
+
/** apiKey 的日志形态:`cc-key#` + sha256(apiKey) 前 8 位 hex */
|
|
10
|
+
export function redactApiKey(apiKey) {
|
|
11
|
+
return `cc-key#${sha256Hex(apiKey).slice(0, 8)}`;
|
|
12
|
+
}
|
|
13
|
+
/** 伪标识符(thumbmark 64hex / sessionId uuid / trace-id 32hex)截断为前 12 位 hex + `…`;
|
|
14
|
+
* uuid 的连字符剥除后再取,保证截出的是纯 hex 位。 */
|
|
15
|
+
export function redactIdentifier(id) {
|
|
16
|
+
return `${id.replace(/-/g, "").slice(0, 12)}…`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话身份主路径(disguise.md §3):OpenCode 会话标识 → 确定性 uuid v4。
|
|
3
|
+
* sha256 摘要取前 16 字节(32 hex),修补 uuid v4 的版本位与变体位——无需任何
|
|
4
|
+
* 存储即保证「同会话同 id、跨重启同 id」。回退路径(per-key 随机 uuid + 12–13h
|
|
5
|
+
* 轮换)的存储与轮换语义归状态层(#33),不在本模块。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 确定性派生 `x-session-id`:同输入恒得同一 uuid v4 形状的 id。
|
|
9
|
+
* 摘要字节本身已是均匀分布,位修补只重写版本/变体两个半字节,不损失会话关联性。
|
|
10
|
+
*/
|
|
11
|
+
export declare function deriveSessionId(seed: string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话身份主路径(disguise.md §3):OpenCode 会话标识 → 确定性 uuid v4。
|
|
3
|
+
* sha256 摘要取前 16 字节(32 hex),修补 uuid v4 的版本位与变体位——无需任何
|
|
4
|
+
* 存储即保证「同会话同 id、跨重启同 id」。回退路径(per-key 随机 uuid + 12–13h
|
|
5
|
+
* 轮换)的存储与轮换语义归状态层(#33),不在本模块。
|
|
6
|
+
*/
|
|
7
|
+
import { sha256Digest } from "./hash.js";
|
|
8
|
+
/** uuid v4 版本位所在字节的掩码:低 4 位保留,高 4 位写 0100 */
|
|
9
|
+
const VERSION_MASK = 0x0f;
|
|
10
|
+
const VERSION_V4 = 0x40;
|
|
11
|
+
/** uuid 变体位所在字节的掩码:低 6 位保留,高 2 位写 10(RFC 4122 保留态) */
|
|
12
|
+
const VARIANT_MASK = 0x3f;
|
|
13
|
+
const VARIANT_RFC4122 = 0x80;
|
|
14
|
+
/**
|
|
15
|
+
* 确定性派生 `x-session-id`:同输入恒得同一 uuid v4 形状的 id。
|
|
16
|
+
* 摘要字节本身已是均匀分布,位修补只重写版本/变体两个半字节,不损失会话关联性。
|
|
17
|
+
*/
|
|
18
|
+
export function deriveSessionId(seed) {
|
|
19
|
+
const bytes = sha256Digest(seed).subarray(0, 16);
|
|
20
|
+
bytes[6] = (bytes[6] & VERSION_MASK) | VERSION_V4;
|
|
21
|
+
bytes[8] = (bytes[8] & VARIANT_MASK) | VARIANT_RFC4122;
|
|
22
|
+
const hex = bytes.toString("hex");
|
|
23
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `x-project-slug` 派生(disguise.md §3,#9 校准后改写):真实 CLI 的 slug 由
|
|
3
|
+
* workingDir 派生、跨会话恒定,形状为「小写字母数字短横分组 ×8 组、组长 4–11」,
|
|
4
|
+
* 确切算法未知(逐段哈希链为最像假说,调研 §11.4)——本实现取形状一致的
|
|
5
|
+
* workingDir 哈希近似。MAXeaglet 的 `users-dev-projects-*` 旧算法整体弃用。
|
|
6
|
+
*
|
|
7
|
+
* 字节流:sha256(workingDir) 作种子,耗尽后接 `sha256(种子#序号)` 哈希链续流;
|
|
8
|
+
* 字符按 base36 取(256 ∤ 36,尾部 4 个字节值拒绝采样弃用以保均匀);组长由
|
|
9
|
+
* 同一字节流驱动。纯函数,同 workingDir 跨调用、跨会话、跨重启恒定。
|
|
10
|
+
*/
|
|
11
|
+
/** 派生 `x-project-slug`:8 组、每组 4–11 字符、字符集 [0-9a-z]、短横连接 */
|
|
12
|
+
export declare function deriveProjectSlug(workingDir: string): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `x-project-slug` 派生(disguise.md §3,#9 校准后改写):真实 CLI 的 slug 由
|
|
3
|
+
* workingDir 派生、跨会话恒定,形状为「小写字母数字短横分组 ×8 组、组长 4–11」,
|
|
4
|
+
* 确切算法未知(逐段哈希链为最像假说,调研 §11.4)——本实现取形状一致的
|
|
5
|
+
* workingDir 哈希近似。MAXeaglet 的 `users-dev-projects-*` 旧算法整体弃用。
|
|
6
|
+
*
|
|
7
|
+
* 字节流:sha256(workingDir) 作种子,耗尽后接 `sha256(种子#序号)` 哈希链续流;
|
|
8
|
+
* 字符按 base36 取(256 ∤ 36,尾部 4 个字节值拒绝采样弃用以保均匀);组长由
|
|
9
|
+
* 同一字节流驱动。纯函数,同 workingDir 跨调用、跨会话、跨重启恒定。
|
|
10
|
+
*/
|
|
11
|
+
import { sha256Digest } from "./hash.js";
|
|
12
|
+
const GROUP_COUNT = 8;
|
|
13
|
+
const GROUP_MIN = 4;
|
|
14
|
+
const GROUP_MAX = 11;
|
|
15
|
+
const BASE36 = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
16
|
+
/** base36 取字符的拒绝采样阈值:256 % 36 = 4,>= 252 的字节值弃用 */
|
|
17
|
+
const REJECT_ABOVE = 256 - (256 % BASE36.length);
|
|
18
|
+
function createByteStream(seed) {
|
|
19
|
+
let pool = sha256Digest(seed);
|
|
20
|
+
let poolIndex = 0;
|
|
21
|
+
let chainCount = 0;
|
|
22
|
+
return () => {
|
|
23
|
+
if (poolIndex >= pool.length) {
|
|
24
|
+
chainCount += 1;
|
|
25
|
+
pool = sha256Digest(`${seed}#${chainCount}`);
|
|
26
|
+
poolIndex = 0;
|
|
27
|
+
}
|
|
28
|
+
return pool[poolIndex++];
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** 派生 `x-project-slug`:8 组、每组 4–11 字符、字符集 [0-9a-z]、短横连接 */
|
|
32
|
+
export function deriveProjectSlug(workingDir) {
|
|
33
|
+
const nextByte = createByteStream(workingDir);
|
|
34
|
+
const groups = [];
|
|
35
|
+
for (let group = 0; group < GROUP_COUNT; group++) {
|
|
36
|
+
const length = GROUP_MIN + (nextByte() % (GROUP_MAX - GROUP_MIN + 1));
|
|
37
|
+
let chars = "";
|
|
38
|
+
while (chars.length < length) {
|
|
39
|
+
const byte = nextByte();
|
|
40
|
+
if (byte >= REJECT_ABOVE)
|
|
41
|
+
continue;
|
|
42
|
+
chars += BASE36[byte % BASE36.length];
|
|
43
|
+
}
|
|
44
|
+
groups.push(chars);
|
|
45
|
+
}
|
|
46
|
+
return groups.join("-");
|
|
47
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 伪装模块的状态与调度门面(disguise.md §1/§3/§5,#33):
|
|
3
|
+
*
|
|
4
|
+
* - **per-key 内存 KeyState**(`Map<apiKey, KeyState>`,key 为 Map 键原文、绝不
|
|
5
|
+
* 落盘绝不进日志——日志一律经 redactApiKey 短哈希):指纹、回退会话、预请求
|
|
6
|
+
* 调度;全局内存态(ccVersion、config 冻结)收在对应子模块;
|
|
7
|
+
* - **指纹进程内稳定(D1)**:KeyState 只增不删,会话轮换只换 session 不删指纹,
|
|
8
|
+
* 进程重启 = Map 清空、首见 key 重新采集并立即重报(MAXeaglet 同款重启语义);
|
|
9
|
+
* - **会话身份主路径(D3)**:doStream 调用 headers 中 `X-Session-Id` /
|
|
10
|
+
* `x-session-affinity`(v2 另有同值 `x-opencode-session`)任一存在即确定性派生
|
|
11
|
+
* `x-session-id`(session.ts 纯函数)——无需存储即保证同会话同 id、跨重启同 id;
|
|
12
|
+
* 多头同场值不一致 warn(「值漂移打日志」的落地;跨请求漂移无法与正常的新会话
|
|
13
|
+
* 区分,不告警防噪音)。全部缺失才落回退路径:per-key 随机 uuid v4、12h +
|
|
14
|
+
* 0~1h 抖动惰性轮换;
|
|
15
|
+
* - **预请求调度(D2/D5)**:startPreflight 对齐真实 CLI「每进程启动发一次」
|
|
16
|
+
* (幂等守卫);主请求前 maybeRunPreflight 做惰性补发/退避重试检查,到点后台
|
|
17
|
+
* 发射,主请求永不等待;
|
|
18
|
+
* - 全部出网与采集依赖经构造 options 注入(工厂 options.fetch、注入式 logger、
|
|
19
|
+
* ConfigRuntime、兜底链 getter),测试零后门。
|
|
20
|
+
*/
|
|
21
|
+
import type { FetchLike } from "../protocol/json.js";
|
|
22
|
+
import type { ConfigBlock, ConfigRuntime } from "./config-block.js";
|
|
23
|
+
import { type DisguiseLogger } from "./logger.js";
|
|
24
|
+
/** 回退路径会话寿命:12h + 0~1h 抖动(§11 参数速查,惰性轮换) */
|
|
25
|
+
export declare const SESSION_TTL_BASE_MS: number;
|
|
26
|
+
export declare const SESSION_TTL_JITTER_MS: number;
|
|
27
|
+
export interface DisguiseStateOptions {
|
|
28
|
+
/** 出网接缝(工厂 options.fetch;预请求与版本查询全部经此注入 mock) */
|
|
29
|
+
fetch?: FetchLike;
|
|
30
|
+
/** 注入式 logger(§7;v1 注 client.app.log,v2 ctx 无 log 域走 console) */
|
|
31
|
+
logger?: DisguiseLogger;
|
|
32
|
+
/** config 采集 runtime 接缝(生产 createNodeConfigRuntime(),测试注 fake) */
|
|
33
|
+
configRuntime?: ConfigRuntime;
|
|
34
|
+
/** 版本兜底链 ③:模型管线内存中已拉取产物的 sourceCliVersion */
|
|
35
|
+
artifactSourceCliVersion?: () => string | undefined;
|
|
36
|
+
/** 版本兜底链 ④:包内快照的 sourceCliVersion */
|
|
37
|
+
snapshotSourceCliVersion?: () => string | undefined;
|
|
38
|
+
}
|
|
39
|
+
export interface DisguiseState {
|
|
40
|
+
/**
|
|
41
|
+
* 进程启动预请求(D2):指纹 + lifecycle 并行、各 3s 超时、fire-and-forget。
|
|
42
|
+
* 幂等:每进程每 key 至多发一次;对 key 的首请求也可能先于本调用发生(此时
|
|
43
|
+
* maybeRunPreflight 的首次检查即承担首发)。
|
|
44
|
+
*/
|
|
45
|
+
startPreflight(apiKey: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* 主请求前的惰性检查(D5):预请求到点(8–10h 补发 / 退避重试)即后台发射,
|
|
48
|
+
* 未到点零开销返回。绝不阻塞调用方。
|
|
49
|
+
*/
|
|
50
|
+
maybeRunPreflight(apiKey: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* 会话身份(D3):主路径从 doStream headers 确定性派生;全部缺失落 per-key
|
|
53
|
+
* 回退轮换。同步无 IO,主请求零等待。
|
|
54
|
+
*/
|
|
55
|
+
resolveSessionId(apiKey: string, headers: Record<string, string | undefined> | undefined): string;
|
|
56
|
+
resolveTraceparent(apiKey: string, sessionId: string, turnKey: string): string;
|
|
57
|
+
/** 版本头全局单值(D6):初解析 1.5s 竞速 + 24h 惰性刷新;四层皆空返回 undefined */
|
|
58
|
+
getCommandCodeVersion(): Promise<string | undefined>;
|
|
59
|
+
/** config 块(D9/§9.3):进程级冻结 + 24h 过期后台重采,主请求除首次构建外零等待 */
|
|
60
|
+
getConfigBlock(): Promise<ConfigBlock>;
|
|
61
|
+
}
|
|
62
|
+
export declare function createDisguiseState(options?: DisguiseStateOptions): DisguiseState;
|