@neta-art/cohub-cli 7.1.2 → 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.
Files changed (48) hide show
  1. package/README.md +40 -2
  2. package/dist/auth.js +38 -5
  3. package/dist/client.js +5 -2
  4. package/dist/commands/runtime.d.ts +1 -2
  5. package/dist/commands/runtime.js +169 -293
  6. package/dist/commands/sandboxd-binary.d.ts +1 -1
  7. package/dist/commands/sandboxd-binary.js +13 -5
  8. package/dist/runtime/archive-store.d.ts +5 -1
  9. package/dist/runtime/archive-store.js +22 -6
  10. package/dist/runtime/connection.d.ts +4 -2
  11. package/dist/runtime/connection.js +113 -44
  12. package/dist/runtime/diagnostics.d.ts +3 -0
  13. package/dist/runtime/diagnostics.js +3 -0
  14. package/dist/runtime/harness.d.ts +7 -0
  15. package/dist/runtime/harness.js +63 -17
  16. package/dist/runtime/instance.d.ts +5 -0
  17. package/dist/runtime/instance.js +159 -0
  18. package/dist/runtime/json-rpc.d.ts +2 -0
  19. package/dist/runtime/json-rpc.js +2 -0
  20. package/dist/runtime/launch.d.ts +20 -0
  21. package/dist/runtime/launch.js +176 -0
  22. package/dist/runtime/native-codex-hook.d.ts +1 -0
  23. package/dist/runtime/native-codex-hook.js +28 -0
  24. package/dist/runtime/native-install.d.ts +21 -0
  25. package/dist/runtime/native-install.js +130 -0
  26. package/dist/runtime/native-ipc.d.ts +26 -0
  27. package/dist/runtime/native-ipc.js +101 -0
  28. package/dist/runtime/native-pi-extension.d.ts +20 -0
  29. package/dist/runtime/native-pi-extension.js +47 -0
  30. package/dist/runtime/native-sync-store.d.ts +97 -0
  31. package/dist/runtime/native-sync-store.js +365 -0
  32. package/dist/runtime/native-sync.d.ts +25 -0
  33. package/dist/runtime/native-sync.js +128 -0
  34. package/dist/runtime/native-transcript.d.ts +27 -0
  35. package/dist/runtime/native-transcript.js +281 -0
  36. package/dist/runtime/presentation.d.ts +21 -0
  37. package/dist/runtime/presentation.js +78 -0
  38. package/dist/runtime/process-group.d.ts +2 -0
  39. package/dist/runtime/process-group.js +124 -31
  40. package/dist/runtime/session-store.d.ts +17 -2
  41. package/dist/runtime/session-store.js +118 -9
  42. package/dist/runtime/space-binding.d.ts +3 -0
  43. package/dist/runtime/space-binding.js +43 -6
  44. package/dist/runtime/supervisor.d.ts +16 -0
  45. package/dist/runtime/supervisor.js +277 -0
  46. package/dist/runtime/worker.d.ts +1 -0
  47. package/dist/runtime/worker.js +20 -0
  48. package/package.json +3 -2
package/README.md CHANGED
@@ -84,12 +84,37 @@ COHUB_SPACE_ID=<spaceId> cohub spaces prompt "message" --json
84
84
 
85
85
  Connect one local workspace to a Space and select Pi or Codex per turn.
86
86
 
87
+ To keep using native terminals with ordinary Cohub Chats / Turns:
88
+ 使用原生终端,并同步到普通 Cohub Chat / Turn:
89
+
90
+ ```bash
91
+ cohub runtime up -d --harness pi --harness codex
92
+ cohub runtime attach --harness pi --harness codex
93
+ # Reload Pi / restart Codex and approve its hook trust prompt.
94
+ # 重载 Pi / 重启 Codex,并审核 Hook 信任提示。
95
+ cohub runtime detach --harness pi --harness codex # Pause; retain data / 暂停,保留数据
96
+ ```
97
+
98
+ `attach` explicitly authorizes this project's opened conversation history and raw native archives.
99
+ The Runtime Supervisor is the single local Daemon: Pi / Codex integrations use its private IPC,
100
+ while native Turn events use the existing Runtime WebSocket. Configuration is backed up, existing
101
+ Runtime bindings are reused, and competing continuations fork at complete Turns without blocking native work. Requires Pi 0.85.1+ or Codex with stable
102
+ Hooks enabled. User-level integrations remain inert outside opted-in directories. See
103
+ [boundaries and privacy](../../docs/local-runtime.md#native-clients--原生客户端).
104
+
105
+ `attach` 明确授权上传当前项目打开的对话历史与原生归档;复用现有绑定,配置修改前备份。
106
+ 冲突时按完整 Turn 分支,不阻塞本地执行。用户级集成在未授权目录中不会采集数据。
107
+
108
+
87
109
  ```bash
88
110
  cohub runtime up ./project --harness pi --harness codex
89
111
  cohub runtime up ./project --space <spaceId> --harness codex
90
112
  cohub -s <spaceId> spaces prompt "Continue" --harness codex
91
113
  cohub runtime status --space <spaceId>
92
114
  cohub runtime logs --space <spaceId> --follow
115
+ cohub runtime up -d
116
+ cohub runtime down
117
+ cohub runtime up -n --name another-project
93
118
  ```
94
119
 
95
120
  Runtime diagnostics stay as redacted JSONL under the local Runtime state directory and
@@ -105,8 +130,21 @@ confirmation. See [Runtime details](../../docs/local-runtime.md).
105
130
 
106
131
  When `--space` is omitted, Runtime remembers a Space for the canonical local directory,
107
132
  account, and environment in `~/.config/cohub/runtime-spaces.json`. The first start creates
108
- and records a local Space; later starts reuse it. An explicit `--space` or `COHUB_SPACE_ID`
109
- overrides and updates the directory binding.
133
+ and records a local Space after prompting; later starts recommend reuse. `-n` now means
134
+ `--new` (boolean); use `--name <name>` for naming. An explicit `--space` or `COHUB_SPACE_ID`
135
+ overrides and updates the directory binding. `--yes` accepts defaults and local execution;
136
+ with explicit `--new`, it creates another Space.
137
+
138
+ `-d` runs the same supervisor in the background and returns the URL, PID and log location.
139
+ Readiness requires both Harness and files. If it is still starting after 30 seconds, exit
140
+ code 2 means it remains in the background trying to connect. `status --json` includes
141
+ local process and remote component status. `down` retains all data and requires `--yes`
142
+ when executions remain unconfirmed. `logs --level warn --follow` shows sensitive events;
143
+ foreground startup prints these automatically. Runtime commands never fall back to Home.
144
+
145
+ 普通使用只需 `cohub runtime up`,首次提示新建,后续优先复用。`-n` 是 `--new` 的简写,
146
+ `--name` 单独指定名称。`-d` 后台运行并返回链接、PID 和日志位置;`down` 停止但保留所有数据。
147
+ 断网及临时凭证错误自动重试;断连不代表任务已停止,也不会自动重跑工具。
110
148
 
111
149
  ## Chats and prompts
112
150
 
package/dist/auth.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { resolveCohubEnvironment } from "@neta-art/cohub";
2
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { randomUUID } from "node:crypto";
4
+ import { withRuntimeSpaceBindingsLock } from "./runtime/space-binding.js";
3
5
  import { homedir } from "node:os";
4
6
  import { join } from "node:path";
5
7
  import { setTimeout as delay } from "node:timers/promises";
@@ -31,7 +33,14 @@ const deviceCodePath = (env = currentEnv()) => join(CONFIG_DIR, env === "dev" ?
31
33
  const legacyTokenPath = (env = currentEnv()) => join(CONFIG_DIR, env === "dev" ? "token.dev" : "token");
32
34
  const writePrivateJson = (path, value) => {
33
35
  mkdirSync(CONFIG_DIR, { recursive: true });
34
- writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
36
+ const temporary = `${path}.${randomUUID()}.tmp`;
37
+ try {
38
+ writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf-8", mode: 0o600, flag: "wx" });
39
+ renameSync(temporary, path);
40
+ }
41
+ finally {
42
+ rmSync(temporary, { force: true });
43
+ }
35
44
  };
36
45
  const readJson = (path) => {
37
46
  if (!existsSync(path))
@@ -55,6 +64,7 @@ const formPost = async (url, body) => {
55
64
  method: "POST",
56
65
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
57
66
  body,
67
+ signal: AbortSignal.timeout(15_000),
58
68
  });
59
69
  const contentType = response.headers.get("content-type") ?? "";
60
70
  const data = contentType.includes("application/json")
@@ -142,9 +152,25 @@ export async function requireAccessToken() {
142
152
  throw new AuthRequiredError();
143
153
  return token;
144
154
  }
145
- export async function refreshAccessToken(session = readAuthSession()) {
155
+ let refreshInFlight = null;
156
+ export function refreshAccessToken(session = readAuthSession()) {
146
157
  if (!session?.refreshToken)
147
- throw new AuthRequiredError();
158
+ return Promise.reject(new AuthRequiredError());
159
+ if (refreshInFlight)
160
+ return refreshInFlight;
161
+ const path = sessionPath(session.env);
162
+ refreshInFlight = withRuntimeSpaceBindingsLock(async () => {
163
+ const current = readJson(path);
164
+ if (!current)
165
+ throw new AuthRequiredError();
166
+ // Another process may already have rotated this refresh token.
167
+ if (current.refreshToken !== session.refreshToken || current.updatedAt !== session.updatedAt)
168
+ return current.accessToken;
169
+ return performRefresh(current);
170
+ }, { path, lockPath: `${path}.refresh.lock` }).finally(() => { refreshInFlight = null; });
171
+ return refreshInFlight;
172
+ }
173
+ async function performRefresh(session) {
148
174
  const config = authConfig(session.env);
149
175
  const { response, data } = await formPost(tokenEndpoint(session.issuer), new URLSearchParams({
150
176
  client_id: session.clientId,
@@ -155,12 +181,19 @@ export async function refreshAccessToken(session = readAuthSession()) {
155
181
  }));
156
182
  if (!response.ok) {
157
183
  if (isUnrecoverableRefreshError(data)) {
158
- clearAuthSession();
184
+ if (readJson(sessionPath(session.env))?.refreshToken === session.refreshToken)
185
+ clearAuthSession();
159
186
  return null;
160
187
  }
161
188
  throw new Error(formatAuthError(data, response.status));
162
189
  }
163
190
  const next = toSession(data, config, session.env, session);
191
+ // Never overwrite a login/logout that completed while the request was in flight.
192
+ const current = readJson(sessionPath(session.env));
193
+ if (!current)
194
+ throw new AuthRequiredError();
195
+ if (current.refreshToken !== session.refreshToken)
196
+ return current.accessToken;
164
197
  writePrivateJson(sessionPath(session.env), next);
165
198
  return next.accessToken;
166
199
  }
package/dist/client.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { CohubClient, CohubHttpClient, readRequestSourceFromEnv } from "@neta-art/cohub";
2
- import { clearAuthSession, resolveAccessToken } from "./auth.js";
2
+ import { clearAuthSession, readAuthSession, resolveAccessToken } from "./auth.js";
3
3
  const clientOptions = () => ({
4
4
  getAccessToken: resolveAccessToken,
5
- onUnauthorized: clearAuthSession,
5
+ onUnauthorized: (context) => {
6
+ if (!process.env.COHUB_EXECUTION_TOKEN?.trim() && context.matchesRejectedToken(readAuthSession()?.accessToken ?? null))
7
+ clearAuthSession();
8
+ },
6
9
  requestSource: () => readRequestSourceFromEnv(process.env, { via: "cli" }) ?? {
7
10
  via: "cli",
8
11
  },
@@ -1,4 +1,3 @@
1
1
  import type { Command } from "commander";
2
- export declare const resolveLocalSpaceName: (root: string, name?: string) => string;
3
- export declare function parseRuntimeHarnesses(values: string[]): ("pi" | "codex")[];
2
+ export { resolveLocalSpaceName, parseRuntimeHarnesses } from "../runtime/launch.js";
4
3
  export declare function registerRuntime(program: Command): void;