@neta-art/cohub-cli 8.0.0 → 8.0.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.
@@ -16,13 +16,13 @@ export const resolveLocalSpaceName = (root, name) => name?.trim() || basename(ro
16
16
  export function parseRuntimeHarnesses(values) {
17
17
  const names = values.flatMap((value) => value.split(",")).map((name) => name.trim()).filter(Boolean);
18
18
  if (names.some((name) => !isLocalHarness(name)))
19
- throw new Error("Harness must be pi or codex / Harness 必须是 pi 或 codex");
19
+ throw new Error("Harness must be pi or codex");
20
20
  return [...new Set(names.length ? names : ["pi"])];
21
21
  }
22
22
  export async function resolveRuntimeTarget(program, target) {
23
23
  const spaceId = target?.trim() || explicitSpace(program) || (await getRuntimeSpaceBinding(process.cwd(), currentIdentityKey()))?.spaceId;
24
24
  if (!spaceId)
25
- throw new Error("No directory binding. Use --space <id> or runtime up / 此目录未绑定 Space,请使用 --space <id> 或 runtime up");
25
+ throw new Error("No directory binding. Use --space <id> or runtime up");
26
26
  return spaceId;
27
27
  }
28
28
  export async function startBackgroundRuntime(config) {
@@ -47,11 +47,11 @@ export async function startBackgroundRuntime(config) {
47
47
  else if (last)
48
48
  resolve(last);
49
49
  else
50
- reject(new Error("Runtime did not start / Runtime 未启动"));
50
+ reject(new Error("Runtime did not start"));
51
51
  };
52
- const timeout = setTimeout(() => finish(last ? undefined : new Error("Runtime startup timed out / Runtime 启动超时")), 30_000);
52
+ const timeout = setTimeout(() => finish(last ? undefined : new Error("Runtime startup timed out")), 30_000);
53
53
  child.on("error", (error) => finish(error));
54
- child.on("exit", (code) => finish(new Error(`Runtime exited (${code}) / Runtime 已退出`)));
54
+ child.on("exit", (code) => finish(new Error(`Runtime exited (${code})`)));
55
55
  child.on("message", (message) => {
56
56
  if (message.type === "status" && message.status) {
57
57
  last = message.status;
@@ -69,17 +69,17 @@ export async function startBackgroundRuntime(config) {
69
69
  export async function runtimeUp(program, dir, options) {
70
70
  const requestedRoot = resolve(dir ?? process.cwd());
71
71
  if (!(await stat(requestedRoot)).isDirectory())
72
- throw new Error("Workspace is not a directory / 工作区不是目录");
72
+ throw new Error("Workspace is not a directory");
73
73
  const root = await canonicalRuntimeRoot(requestedRoot);
74
74
  const requested = options.space?.trim() || explicitSpace(program);
75
75
  if (options.new && requested)
76
- throw new Error("--new cannot be combined with --space or COHUB_SPACE_ID / --new 不能与显式 Space 同时使用");
76
+ throw new Error("--new cannot be combined with --space or COHUB_SPACE_ID");
77
77
  if (options.name && requested)
78
- throw new Error("--name only applies to a new Space / --name 仅用于新建 Space");
78
+ throw new Error("--name only applies to a new Space");
79
79
  const identity = currentIdentityKey();
80
80
  if (!identity) {
81
81
  await requireAccessToken();
82
- throw new Error("Cannot identify the signed-in account / 无法识别当前登录账号");
82
+ throw new Error("Cannot identify the signed-in account");
83
83
  }
84
84
  const binding = await getRuntimeSpaceBinding(root, identity);
85
85
  const existingId = requested || binding?.spaceId;
@@ -88,7 +88,7 @@ export async function runtimeUp(program, dir, options) {
88
88
  const existing = await requestRuntimeInstance(runtimeInstanceDirectory(identity, existingId));
89
89
  if (existing) {
90
90
  if (existing.root !== root || options.harness.length && [...existing.harnesses].sort().join() !== [...harnesses].sort().join() || options.pi || options.codex) {
91
- throw new Error("Runtime is running with a different configuration. Use down first / Runtime 正使用不同配置运行,请先 down");
91
+ throw new Error("Runtime is running with a different configuration. Use down first");
92
92
  }
93
93
  printRuntimeSummary(existing, options.json, true);
94
94
  return;
@@ -97,32 +97,32 @@ export async function runtimeUp(program, dir, options) {
97
97
  if (!options.harness.length)
98
98
  harnesses = await installedHarnesses(root, options);
99
99
  if (!harnesses.length)
100
- throw new Error("Install and sign in to Pi or Codex, or pass --harness / 请安装并登录 Pi 或 Codex,或显式指定 --harness");
100
+ throw new Error("Install and sign in to Pi or Codex, or pass --harness");
101
101
  let createNew = Boolean(options.new);
102
102
  let name = resolveLocalSpaceName(root, options.name);
103
103
  if (!options.yes) {
104
104
  if (!process.stdin.isTTY)
105
- throw new Error("Use --yes to authorize local execution / 请使用 --yes 授权本地执行");
105
+ throw new Error("Use --yes to authorize local execution");
106
106
  const rl = createInterface({ input: process.stdin, output: process.stderr });
107
107
  try {
108
108
  if (!requested && binding) {
109
- process.stderr.write(`\nLinked Space / 已关联 Space\n ${runtimeWebUrl(binding.spaceId)}\n`);
110
- const answer = (await rl.question("Reuse this Space? [Y/n, q to cancel] / 复用此 Space?[Y/n,q 取消] ")).trim().toLowerCase();
109
+ process.stderr.write(`\nLinked Space\n ${runtimeWebUrl(binding.spaceId)}\n`);
110
+ const answer = (await rl.question("Reuse this Space? [Y/n, q to cancel] ")).trim().toLowerCase();
111
111
  if (answer === "q")
112
112
  return;
113
113
  createNew = answer === "n" || answer === "no";
114
114
  }
115
115
  else if (!requested) {
116
- const answer = (await rl.question("Create a new Space? [Y/n] / 创建新 Space?[Y/n] ")).trim().toLowerCase();
116
+ const answer = (await rl.question("Create a new Space? [Y/n] ")).trim().toLowerCase();
117
117
  if (answer && answer !== "y" && answer !== "yes")
118
118
  return;
119
119
  }
120
120
  if (binding && createNew && !options.name)
121
121
  name = `${name}-${randomUUID().slice(0, 6)}`;
122
122
  if (!requested && (!binding || createNew))
123
- name = (await rl.question(`Space name / Space 名称 [${name}]: `)).trim() || name;
124
- process.stderr.write(`\nDirectory / 目录 ${root}\n${requested ? `Space / 空间 ${runtimeWebUrl(requested)}\n` : ""}`);
125
- const answer = await rl.question("Collaborators can execute as your OS user, beyond this folder. Allow? [y/N] / 协作者可使用你的系统身份执行命令,不限于此目录。允许?[y/N] ");
123
+ name = (await rl.question(`Space name [${name}]: `)).trim() || name;
124
+ process.stderr.write(`\nDirectory ${root}\n${requested ? `Space ${runtimeWebUrl(requested)}\n` : ""}`);
125
+ const answer = await rl.question("Collaborators can execute as your OS user, beyond this folder. Allow? [y/N] ");
126
126
  if (!/^y(es)?$/i.test(answer.trim()))
127
127
  return;
128
128
  }
@@ -133,7 +133,7 @@ export async function runtimeUp(program, dir, options) {
133
133
  if (options.yes && createNew && binding && !options.name)
134
134
  name = `${name}-${randomUUID().slice(0, 6)}`;
135
135
  if (createNew && binding && await requestRuntimeInstance(runtimeInstanceDirectory(identity, binding.spaceId))) {
136
- throw new Error("Stop the existing Runtime before rebinding this directory / 请先停止此目录的 Runtime,再创建新绑定");
136
+ throw new Error("Stop the existing Runtime before rebinding this directory");
137
137
  }
138
138
  // Fail local preflight before creating remote state; the worker reuses this catalog.
139
139
  const capabilities = await discoverHarnesses(harnesses, options, root);
@@ -145,14 +145,14 @@ export async function runtimeUp(program, dir, options) {
145
145
  validateSpace: async (id) => {
146
146
  const sandbox = (await client.space(id).sandbox.get()).sandbox;
147
147
  if (sandbox?.provider !== "local")
148
- throw new Error("Space does not have a local Runtime / 此 Space 不是本地 Runtime");
148
+ throw new Error("Space does not have a local Runtime");
149
149
  },
150
150
  });
151
151
  const config = { spaceId, root, identity, harnesses, capabilities, executables: { pi: options.pi, codex: options.codex }, background: Boolean(options.detach), verbose: options.verbose };
152
152
  const existing = await requestRuntimeInstance(runtimeInstanceDirectory(identity, spaceId));
153
153
  if (existing) {
154
154
  if (existing.root !== root)
155
- throw new Error("This Space is running in another directory / 此 Space 已在另一目录运行");
155
+ throw new Error("This Space is running in another directory");
156
156
  printRuntimeSummary(existing, options.json, true);
157
157
  return;
158
158
  }
@@ -170,7 +170,7 @@ export async function runtimeUp(program, dir, options) {
170
170
  printRuntimeSummary(status, options.json, source === "binding");
171
171
  }
172
172
  else if (!announced && status.state === "starting" && !options.json)
173
- process.stderr.write(`Connecting / 正在连接\n ${runtimeWebUrl(spaceId)}\n Logs / 日志 ${status.diagnosticsPath}\n`);
173
+ process.stderr.write(`Connecting\n ${runtimeWebUrl(spaceId)}\n Logs ${status.diagnosticsPath}\n`);
174
174
  });
175
175
  }
176
176
  }
@@ -5,7 +5,7 @@ export async function runCodexNativeHook(payload) {
5
5
  return;
6
6
  const value = payload;
7
7
  if (!value || typeof value.cwd !== "string" || typeof value.session_id !== "string")
8
- throw new Error("Invalid Codex hook identity / Codex Hook 身份无效");
8
+ throw new Error("Invalid Codex hook identity");
9
9
  if (typeof value.transcript_path !== "string" || !value.transcript_path)
10
10
  return;
11
11
  const result = await requestNativeDaemon({ harness: "codex", cwd: value.cwd, path: value.transcript_path, nativeSessionId: value.session_id });
@@ -17,12 +17,12 @@ async function main() {
17
17
  for await (const chunk of process.stdin) {
18
18
  input += chunk.toString();
19
19
  if (Buffer.byteLength(input) > 4 * 1024 * 1024)
20
- throw new Error("Codex hook input is too large / Codex Hook 输入过大");
20
+ throw new Error("Codex hook input is too large");
21
21
  }
22
22
  await runCodexNativeHook(JSON.parse(input));
23
23
  }
24
24
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
25
25
  void main().catch((error) => {
26
- process.stderr.write(`Cohub sync pending; native execution continues / Cohub 待同步,原生执行继续: ${error instanceof Error ? error.message : String(error)}\n`);
26
+ process.stderr.write(`Cohub sync pending; native execution continues: ${error instanceof Error ? error.message : String(error)}\n`);
27
27
  });
28
28
  }
@@ -21,7 +21,7 @@ async function installText(path, update) {
21
21
  const info = await lstat(path).catch((error) => { if (missing(error))
22
22
  return null; throw error; });
23
23
  if (info && (!info.isFile() || info.isSymbolicLink()))
24
- throw new Error(`Refusing to replace a non-regular file / 不覆盖非普通文件: ${path}`);
24
+ throw new Error(`Refusing to replace a non-regular file: ${path}`);
25
25
  const original = info ? await readFile(path, "utf8") : null;
26
26
  const next = update(original);
27
27
  if (next === original)
@@ -54,7 +54,7 @@ async function installText(path, update) {
54
54
  const current = await readFile(path, "utf8").catch((error) => { if (missing(error))
55
55
  return null; throw error; });
56
56
  if (current !== original)
57
- throw new Error(`Configuration changed during installation / 安装期间配置已变化: ${path}`);
57
+ throw new Error(`Configuration changed during installation: ${path}`);
58
58
  await rename(temporary, path);
59
59
  const directory = await open(dirname(path), "r");
60
60
  try {
@@ -75,10 +75,10 @@ export async function verifyNativeSyncSupport(harnesses, cwd, executables = {})
75
75
  if (harness === "pi") {
76
76
  const version = /\b(\d+)\.(\d+)\.(\d+)\b/.exec(stdout);
77
77
  if (!version || Number(version[1]) === 0 && (Number(version[2]) < 85 || Number(version[2]) === 85 && Number(version[3]) < 1))
78
- throw new Error("Native sync requires Pi 0.85.1+ / 原生同步需要 Pi 0.85.1 或更高版本");
78
+ throw new Error("Native sync requires Pi 0.85.1+");
79
79
  }
80
80
  else if (!/^hooks\s+stable\s+true\s*$/m.test(stdout))
81
- throw new Error("Install a Codex version with stable Hooks and enable hooks first / 请安装支持稳定 Hooks 的 Codex 版本并启用 Hooks");
81
+ throw new Error("Install a Codex version with stable Hooks and enable hooks first");
82
82
  }
83
83
  }
84
84
  /** Install once in the user's native configuration; data collection remains explicitly project-scoped. */
@@ -92,10 +92,10 @@ export async function installNativeSync(input) {
92
92
  if (!input.disabled)
93
93
  for (const harness of input.harnesses) {
94
94
  if (harness === "pi") {
95
- const content = `// Cohub native Turn sync / Cohub 原生 Turn 同步\nexport { default } from ${JSON.stringify(extension.href)};\n`;
95
+ const content = `// Cohub native Turn sync\nexport { default } from ${JSON.stringify(extension.href)};\n`;
96
96
  await installText(join(process.env.PI_CODING_AGENT_DIR?.trim() || join(homedir(), ".pi", "agent"), "extensions", "cohub.ts"), (existing) => {
97
97
  if (existing !== null && existing !== content)
98
- throw new Error("Pi Cohub extension already exists; preserve it and review manually / Pi Cohub 扩展已存在,请保留并手动核对");
98
+ throw new Error("Pi Cohub extension already exists; preserve it and review manually");
99
99
  return content;
100
100
  });
101
101
  }
@@ -105,9 +105,9 @@ export async function installNativeSync(input) {
105
105
  if (existing?.includes(block))
106
106
  return existing;
107
107
  if (existing?.includes(START) || existing?.includes(END))
108
- throw new Error("Codex Cohub hook block differs; preserve it and review manually / Codex Cohub Hook 配置不同,请保留并手动核对");
108
+ throw new Error("Codex Cohub hook block differs; preserve it and review manually");
109
109
  if (existing && /^\s*hooks\s*=/m.test(existing))
110
- throw new Error("Inline Codex hooks require manual merging / 内联 Codex Hooks 需要手动合并");
110
+ throw new Error("Inline Codex hooks require manual merging");
111
111
  return `${existing ?? ""}${existing?.endsWith("\n") ? "\n" : "\n\n"}${block}`;
112
112
  });
113
113
  }
@@ -115,7 +115,7 @@ export async function installNativeSync(input) {
115
115
  await withRuntimeSpaceBindingsLock(async () => {
116
116
  const previous = await readNativeSyncConfig(runtimeRoot, input.identity);
117
117
  if (previous && previous.root !== input.root)
118
- throw new Error("Space native sync belongs to another directory / 此 Space 原生同步属于其他目录");
118
+ throw new Error("Space native sync belongs to another directory");
119
119
  const harnesses = new Set(previous?.harnesses ?? []);
120
120
  for (const harness of input.harnesses) {
121
121
  if (input.disabled)
@@ -10,7 +10,7 @@ const parse = (raw) => {
10
10
  const value = JSON.parse(raw);
11
11
  if (value?.type !== "native.capture" || !["pi", "codex"].includes(value.harness)
12
12
  || typeof value.cwd !== "string" || typeof value.path !== "string" || typeof value.nativeSessionId !== "string") {
13
- throw new Error("Invalid native daemon request / 原生 Daemon 请求无效");
13
+ throw new Error("Invalid native daemon request");
14
14
  }
15
15
  return value;
16
16
  };
@@ -25,18 +25,18 @@ export async function nativeDaemonSocketFor(cwd) {
25
25
  export async function requestNativeDaemon(input) {
26
26
  const path = await nativeDaemonSocketFor(input.cwd);
27
27
  if (!path)
28
- return { ok: false, message: "Native Runtime is not bound / 原生 Runtime 未绑定" };
28
+ return { ok: false, message: "Native Runtime is not bound" };
29
29
  return new Promise((resolve, reject) => {
30
30
  const socket = createConnection(path);
31
31
  let buffer = "";
32
- const timer = setTimeout(() => { socket.destroy(); reject(new Error("Native Runtime daemon timed out / 原生 Runtime Daemon 超时")); }, 15_000);
32
+ const timer = setTimeout(() => { socket.destroy(); reject(new Error("Native Runtime daemon timed out")); }, 15_000);
33
33
  const finish = (error, result) => {
34
34
  clearTimeout(timer);
35
35
  socket.destroy();
36
36
  if (error)
37
37
  reject(error);
38
38
  else
39
- resolve(result ?? { ok: false, message: "Empty daemon response / Daemon 返回为空" });
39
+ resolve(result ?? { ok: false, message: "Empty daemon response" });
40
40
  };
41
41
  socket.once("error", (error) => finish(error));
42
42
  socket.on("data", (chunk) => {
@@ -10,7 +10,7 @@ export default function cohubNativeExtension(pi) {
10
10
  const report = (error) => {
11
11
  const message = error instanceof Error ? error.message : String(error);
12
12
  if (message !== lastError)
13
- context?.ui.notify(`Cohub sync pending / Cohub 待同步: ${message}`, "warning");
13
+ context?.ui.notify(`Cohub sync pending: ${message}`, "warning");
14
14
  lastError = message;
15
15
  };
16
16
  const capture = (ctx, settled = false) => {
@@ -54,7 +54,7 @@ export class NativeSyncStore {
54
54
  async binding() {
55
55
  const value = await readJson(this.bindingPath());
56
56
  if (value?.version !== 1 || value.identity !== this.options.identity || value.spaceId !== this.options.spaceId || value.nativeSessionId !== this.options.nativeSessionId || value.instanceKey !== this.options.instanceKey || value.harness !== this.options.harness)
57
- throw new Error("Native binding mismatch / 原生关联不匹配");
57
+ throw new Error("Native binding mismatch");
58
58
  return value;
59
59
  }
60
60
  async initialize(path, transcript) {
@@ -62,7 +62,7 @@ export class NativeSyncStore {
62
62
  if (existing) {
63
63
  const binding = await this.binding();
64
64
  if (binding.path !== path)
65
- throw new Error("Native path changed; original binding retained / 原生路径已变化,原关联已保留");
65
+ throw new Error("Native path changed; original binding retained");
66
66
  return binding;
67
67
  }
68
68
  const managed = await findRuntimeNativeSession(this.options.runtimeRoot, this.options.harness, transcript.nativeSessionId, path);
@@ -70,7 +70,7 @@ export class NativeSyncStore {
70
70
  const anchors = [];
71
71
  if (managed) {
72
72
  if (managed.pendingTurnId)
73
- throw new Error("Reconcile the managed Turn before native continuation / 请先确认 Runtime 中尚未确认的 Turn");
73
+ throw new Error("Reconcile the managed Turn before native continuation");
74
74
  if (managed.throughTurnId) {
75
75
  const index = await readJson(join(this.options.runtimeRoot, "archives", "versions", `${managed.throughTurnId}.json`));
76
76
  if (index) {
@@ -79,7 +79,7 @@ export class NativeSyncStore {
79
79
  for await (const bytes of createReadStream(path, { end: parsed.sizeBytes - 1 }))
80
80
  checksum.update(bytes);
81
81
  if (checksum.digest("hex") !== parsed.sha256)
82
- throw new Error("Runtime history prefix changed / Runtime 历史前缀已变化");
82
+ throw new Error("Runtime history prefix changed");
83
83
  throughBytes = parsed.sizeBytes;
84
84
  }
85
85
  else {
@@ -87,7 +87,7 @@ export class NativeSyncStore {
87
87
  for await (const bytes of createReadStream(path))
88
88
  checksum.update(bytes);
89
89
  if (checksum.digest("hex") !== managed.checksum)
90
- throw new Error("Cannot identify the last complete Runtime Turn / 无法识别 Runtime 最后一个完整 Turn");
90
+ throw new Error("Cannot identify the last complete Runtime Turn");
91
91
  throughBytes = (await stat(path)).size;
92
92
  anchors.push({ turnId: managed.throughTurnId, sizeBytes: throughBytes, sha256: managed.checksum });
93
93
  }
@@ -114,13 +114,13 @@ export class NativeSyncStore {
114
114
  }
115
115
  async capture(path, transcript) {
116
116
  if (transcript.nativeSessionId !== this.options.nativeSessionId)
117
- throw new Error("Native session identity mismatch / 原生会话身份不匹配");
117
+ throw new Error("Native session identity mismatch");
118
118
  await withRuntimeSpaceBindingsLock(async () => {
119
119
  const binding = await this.initialize(path, transcript);
120
120
  if (binding.throughBytes > 0) {
121
121
  const anchor = binding.anchors.find((entry) => entry.sizeBytes === binding.throughBytes);
122
122
  if (!anchor || transcript.prefixes.get(binding.throughBytes) !== anchor.sha256)
123
- throw new Error("Runtime history prefix changed; original binding retained / Runtime 历史前缀已变化,原关联已保留");
123
+ throw new Error("Runtime history prefix changed; original binding retained");
124
124
  }
125
125
  let parentKey = null;
126
126
  let parentCloudTurnId = binding.throughTurnId;
@@ -130,7 +130,7 @@ export class NativeSyncStore {
130
130
  // Native offsets only validate whole-Turn archive checkpoints; they never become cloud fork anchors.
131
131
  const matches = (binding.anchors ?? []).filter((anchor) => anchor.sizeBytes >= turn.contentEndBytes && turn.boundaries[anchor.sizeBytes] === anchor.sha256);
132
132
  if (new Set(matches.map((anchor) => anchor.turnId)).size > 1)
133
- throw new Error("Ambiguous Runtime Turn boundary / Runtime Turn 边界不明确");
133
+ throw new Error("Ambiguous Runtime Turn boundary");
134
134
  parentCloudTurnId = matches[0]?.turnId ?? null;
135
135
  knownBoundary = matches.length > 0;
136
136
  parentKey = null;
@@ -143,12 +143,12 @@ export class NativeSyncStore {
143
143
  continue;
144
144
  }
145
145
  if (!parentKey && !knownBoundary)
146
- throw new Error("Native continuation is not at a complete Runtime Turn boundary / 原生续聊不在完整 Runtime Turn 边界上");
146
+ throw new Error("Native continuation is not at a complete Runtime Turn boundary");
147
147
  const turnId = this.turnId(turn.key);
148
148
  const old = await readJson(this.receiptPath(turnId));
149
149
  if (old?.result) {
150
150
  if (turn.contentEndBytes < (old.contentEndBytes ?? old.endBytes) || JSON.stringify(turn.userContent) !== JSON.stringify(old.userContent) || turn.result && JSON.stringify(nativeTurnCompleteSchema.parse(turn.result)) !== JSON.stringify(old.result)) {
151
- throw new Error("Native branch is inside a settled Turn; only whole-Turn forks are supported / 原生分支位于已结束的 Turn 内,仅支持完整 Turn 分支");
151
+ throw new Error("Native branch is inside a settled Turn; only whole-Turn forks are supported");
152
152
  }
153
153
  parentKey = turn.key;
154
154
  continue;
@@ -158,7 +158,7 @@ export class NativeSyncStore {
158
158
  userContent: turn.userContent, startedAt: turn.startedAt, endBytes: turn.endBytes, contentEndBytes: turn.contentEndBytes, result,
159
159
  ...(!result ? { progress: nativeTurnProgressSchema.parse({ revision: turn.endBytes, messages: turn.messages }) } : {}) };
160
160
  if (old && (JSON.stringify(old.userContent) !== JSON.stringify(receipt.userContent) || old.parentKey !== receipt.parentKey))
161
- throw new Error("Native Turn changed; original receipt retained / 原生 Turn 已变化,原回执已保留");
161
+ throw new Error("Native Turn changed; original receipt retained");
162
162
  // Capture immutable native bytes before publishing the completed receipt. Subsequent Turns may change the source.
163
163
  if (result)
164
164
  await this.archives.stage({ sessionId: binding.originSessionId, harness: binding.harness, nativeSessionId: binding.nativeSessionId, path, sizeBytes: turn.endBytes, expectedChecksum: turn.sha256 }, turnId);
@@ -186,7 +186,7 @@ export class NativeSyncStore {
186
186
  continue;
187
187
  }
188
188
  if (receipt?.version !== 1 || receipt.turnId !== this.turnId(receipt.key))
189
- throw new Error("Native receipt is corrupt; original retained / 原生回执损坏,原件已保留");
189
+ throw new Error("Native receipt is corrupt; original retained");
190
190
  receipts.push(receipt);
191
191
  }
192
192
  return receipts.sort((a, b) => a.endBytes - b.endBytes || a.turnId.localeCompare(b.turnId));
@@ -233,7 +233,7 @@ export class NativeSyncStore {
233
233
  parent = await readJson(this.acknowledgementPath(parentId));
234
234
  }
235
235
  if (!parent)
236
- throw new Error("Parent binding is missing / 父 Turn 关联缺失");
236
+ throw new Error("Parent binding is missing");
237
237
  }
238
238
  let request = await readJson(this.requestPath(receipt.turnId));
239
239
  if (!request) {
@@ -244,10 +244,10 @@ export class NativeSyncStore {
244
244
  let remote = await readJson(this.cloudBindingPath(receipt.turnId));
245
245
  if (!remote) {
246
246
  if (!transport.startNativeTurn)
247
- throw new Error("Native Runtime WS is unavailable / 原生 Runtime WS 不可用");
247
+ throw new Error("Native Runtime WS is unavailable");
248
248
  remote = await transport.startNativeTurn(request, { signal });
249
249
  if (remote.turnId !== receipt.turnId)
250
- throw new Error("Server Turn identity mismatch / 服务端 Turn 身份不匹配");
250
+ throw new Error("Server Turn identity mismatch");
251
251
  await atomicRuntimeJson(this.cloudBindingPath(receipt.turnId), remote);
252
252
  }
253
253
  if (!receipt.result) {
@@ -267,7 +267,7 @@ export class NativeSyncStore {
267
267
  return false;
268
268
  }
269
269
  if (!transport.completeNativeTurn)
270
- throw new Error("Native Runtime WS is unavailable / 原生 Runtime WS 不可用");
270
+ throw new Error("Native Runtime WS is unavailable");
271
271
  // Artifact retries back off: the terminal state is durable, so hammering the completion
272
272
  // endpoint every flush cycle (5s) while object storage is down only adds load.
273
273
  const backoffPath = join(this.root, "backoff", `${receipt.turnId}.json`);
@@ -279,7 +279,7 @@ export class NativeSyncStore {
279
279
  // Terminal state is durable; only the artifact snapshot is missing. Keep the receipt pending
280
280
  // and retry on the next flush cycle (>= 30s) until artifacts persist.
281
281
  await atomicRuntimeJson(backoffPath, { at: Date.now() });
282
- throw new Error("Native artifacts are pending; completion replays later / 原生产物待生成,稍后重放完成请求");
282
+ throw new Error("Native artifacts are pending; completion replays later");
283
283
  }
284
284
  await rm(backoffPath, { force: true });
285
285
  await atomicRuntimeJson(this.acknowledgementPath(receipt.turnId), remote);
@@ -327,7 +327,7 @@ export class NativeSyncStore {
327
327
  async cloudArchive(index) {
328
328
  const binding = await readJson(this.acknowledgementPath(index.turnId));
329
329
  if (!binding)
330
- throw new Error("Native Turn result is not confirmed / 原生 Turn 结果尚未确认");
330
+ throw new Error("Native Turn result is not confirmed");
331
331
  if (index.parentTurnId) {
332
332
  const parent = await readJson(this.acknowledgementPath(index.parentTurnId));
333
333
  if (parent?.sessionId === binding.sessionId)
@@ -338,7 +338,7 @@ export class NativeSyncStore {
338
338
  let parentId = index.parentTurnId;
339
339
  while (parentId) {
340
340
  if (visited.has(parentId))
341
- throw new Error("Cyclic native archive / 原生归档存在循环");
341
+ throw new Error("Cyclic native archive");
342
342
  visited.add(parentId);
343
343
  const previous = harnessArchiveIndexSchema.parse(await readJson(join(this.archives.root, "versions", `${parentId}.json`)));
344
344
  segments.unshift(...previous.segments);
@@ -13,10 +13,10 @@ export function nativeArchiveTransport(spaceId, identity) {
13
13
  const client = createClient().space(spaceId);
14
14
  const guard = async (task) => {
15
15
  if (currentIdentityKey() !== identity)
16
- throw new Error("Native sync account changed / 原生同步账号已变化");
16
+ throw new Error("Native sync account changed");
17
17
  const result = await task();
18
18
  if (currentIdentityKey() !== identity)
19
- throw new Error("Native sync account changed / 原生同步账号已变化");
19
+ throw new Error("Native sync account changed");
20
20
  return result;
21
21
  };
22
22
  return {
@@ -29,7 +29,7 @@ export async function readNativeSyncConfig(runtimeRoot, identity) {
29
29
  try {
30
30
  const config = JSON.parse(await readFile(nativeSyncConfigPath(runtimeRoot, identity), "utf8"));
31
31
  if (config.version !== 1 || config.identity !== identity || !Array.isArray(config.harnesses))
32
- throw new Error("Invalid native sync configuration / 原生同步配置无效");
32
+ throw new Error("Invalid native sync configuration");
33
33
  return config;
34
34
  }
35
35
  catch (error) {
@@ -57,7 +57,7 @@ export async function captureNativeSession(input) {
57
57
  const path = await canonicalRuntimeRoot(input.path);
58
58
  const transcript = await readNativeTranscript(path, input.harness, input);
59
59
  if (await canonicalRuntimeRoot(transcript.cwd) !== root || input.nativeSessionId && transcript.nativeSessionId !== input.nativeSessionId)
60
- throw new Error("Native transcript belongs to another project or Session / 原生记录属于其他项目或会话");
60
+ throw new Error("Native transcript belongs to another project or Session");
61
61
  const key = JSON.stringify([identity, space.spaceId, input.harness, transcript.nativeSessionId, path]);
62
62
  let store = nativeStores.get(key);
63
63
  if (!store) {
@@ -74,7 +74,7 @@ export async function captureNativeSession(input) {
74
74
  const managedPath = managed ? await canonicalRuntimeRoot(managed.path).catch((error) => { if (error.code === "ENOENT")
75
75
  return null; throw error; }) : null;
76
76
  if (candidates.length && managedPath !== path)
77
- throw new Error("Native path changed; original bindings retained / 原生路径已变化,原关联已保留");
77
+ throw new Error("Native path changed; original bindings retained");
78
78
  // Restored Pi working copies can share a native ID. Existing Cohub sidecars disambiguate them.
79
79
  store = new NativeSyncStore({ runtimeRoot, spaceId: space.spaceId, identity, harness: input.harness, nativeSessionId: transcript.nativeSessionId,
80
80
  instanceKey: managedPath === path ? path : undefined, transport });
@@ -8,7 +8,7 @@ const list = (value) => Array.isArray(value) ? value : [];
8
8
  const iso = (value) => {
9
9
  const date = new Date(typeof value === "number" || typeof value === "string" ? value : 0);
10
10
  if (!Number.isFinite(date.getTime()))
11
- throw new Error("Invalid native timestamp / 原生时间无效");
11
+ throw new Error("Invalid native timestamp");
12
12
  return date.toISOString();
13
13
  };
14
14
  /** Partial trailing records are retried, never parsed or acknowledged as complete. */
@@ -24,9 +24,9 @@ export async function readNativeTranscript(path, harness, options = {}) {
24
24
  pendingBytes += bytes.length;
25
25
  checksum.update(bytes);
26
26
  if (pendingBytes > 32 * 1024 * 1024)
27
- throw new Error("Native record is too large / 原生记录过大");
27
+ throw new Error("Native record is too large");
28
28
  if (offset + pendingBytes > 128 * 1024 * 1024)
29
- throw new Error("Native transcript exceeds the capture limit; original retained / 原生记录超出采集上限,原件已保留");
29
+ throw new Error("Native transcript exceeds the capture limit; original retained");
30
30
  };
31
31
  for await (const chunk of createReadStream(path)) {
32
32
  let start = 0;
@@ -47,7 +47,7 @@ export async function readNativeTranscript(path, harness, options = {}) {
47
47
  value = record(JSON.parse(line.toString("utf8")));
48
48
  }
49
49
  catch {
50
- throw new Error("Invalid native JSON record; original retained / 原生 JSON 记录无效,原件已保留");
50
+ throw new Error("Invalid native JSON record; original retained");
51
51
  }
52
52
  lines.push({ value, startBytes, endBytes: offset, sha256 });
53
53
  }
@@ -56,24 +56,24 @@ export async function readNativeTranscript(path, harness, options = {}) {
56
56
  append(chunk.subarray(start));
57
57
  }
58
58
  if (!lines.length)
59
- throw new Error("Native transcript is empty / 原生记录为空");
59
+ throw new Error("Native transcript is empty");
60
60
  return { ...(harness === "pi" ? parsePiTranscript(lines, options) : parseCodexTranscript(lines)), prefixes };
61
61
  }
62
62
  function parsePiTranscript(lines, options) {
63
63
  const header = lines[0]?.value ?? {};
64
64
  if (header.type !== "session" || !text(header.id))
65
- throw new Error("Invalid Pi session / Pi 会话无效");
65
+ throw new Error("Invalid Pi session");
66
66
  const entries = new Map(lines.slice(1).filter((line) => text(line.value.id)).map((line) => [text(line.value.id), line]));
67
67
  const branch = [];
68
68
  let leaf = options.leafId ?? (lines.length > 1 ? text(lines.at(-1)?.value.id) : "");
69
69
  const visited = new Set();
70
70
  while (leaf) {
71
71
  if (visited.has(leaf))
72
- throw new Error("Cyclic Pi history / Pi 历史存在循环");
72
+ throw new Error("Cyclic Pi history");
73
73
  visited.add(leaf);
74
74
  const entry = entries.get(leaf);
75
75
  if (!entry)
76
- throw new Error("Pi parent history is missing / Pi 父历史缺失");
76
+ throw new Error("Pi parent history is missing");
77
77
  branch.push(entry);
78
78
  leaf = text(entry.value.parentId);
79
79
  }
@@ -128,7 +128,7 @@ function parsePiTranscript(lines, options) {
128
128
  else if (current && message.role === "toolResult") {
129
129
  const assistant = messages.at(-1);
130
130
  if (!assistant)
131
- throw new Error("Pi tool result has no assistant Turn / Pi 工具结果缺少所属 Turn");
131
+ throw new Error("Pi tool result has no assistant Turn");
132
132
  assistant.content.push({ type: "tool_result", tool_use_id: text(message.toolCallId), content: typeof message.content === "string" ? message.content : piContent(message.content), is_error: Boolean(message.isError) });
133
133
  }
134
134
  if (current) {
@@ -158,9 +158,9 @@ function parseCodexTranscript(lines) {
158
158
  const header = lines[0]?.value ?? {};
159
159
  const metadata = record(header.payload);
160
160
  if (header.type !== "session_meta" || !text(metadata.id))
161
- throw new Error("Invalid Codex session / Codex 会话无效");
161
+ throw new Error("Invalid Codex session");
162
162
  if (metadata.history_base || metadata.fork_source)
163
- throw new Error("Codex history references another rollout; retain the original and materialize its full history first / Codex 历史引用其他记录,请保留原件并先导出完整历史");
163
+ throw new Error("Codex history references another rollout; retain the original and materialize its full history first");
164
164
  const turns = [];
165
165
  let current = null;
166
166
  let messages = [];
@@ -188,7 +188,7 @@ function parseCodexTranscript(lines) {
188
188
  turns.push(current);
189
189
  current = { key: text(payload.turn_id), parentKey: turns.at(-1)?.key ?? null, userContent: [], messages: [], startedAt: iso(entry.timestamp), startBytes: line.startBytes, endBytes: line.endBytes, contentEndBytes: line.endBytes, boundaries: {}, sha256: line.sha256, result: null };
190
190
  if (!current.key)
191
- throw new Error("Codex Turn identity is missing / Codex Turn 身份缺失");
191
+ throw new Error("Codex Turn identity is missing");
192
192
  messages = current.messages;
193
193
  userFromResponse = false;
194
194
  }
@@ -255,7 +255,7 @@ function parseCodexTranscript(lines) {
255
255
  }
256
256
  if (entry.type === "event_msg" && ["turn_complete", "task_complete", "turn_aborted"].includes(text(payload.type))) {
257
257
  if (payload.turn_id && payload.turn_id !== current.key)
258
- throw new Error("Codex Turn boundary mismatch / Codex Turn 边界不匹配");
258
+ throw new Error("Codex Turn boundary mismatch");
259
259
  if (!messages.length && text(payload.last_agent_message))
260
260
  messages.push({ content: [{ type: "text", text: text(payload.last_agent_message) }], model, provider });
261
261
  const errorMessage = text(record(payload.error).message);