@neta-art/cohub-cli 8.3.0 → 8.3.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/dist/runtime/launch.js
CHANGED
|
@@ -10,7 +10,7 @@ import { createClient } from "../client.js";
|
|
|
10
10
|
import { currentIdentityKey, explicitSpace } from "../space.js";
|
|
11
11
|
import { canonicalRuntimeRoot, getRuntimeSpaceBinding, resolveRuntimeSpace } from "./space-binding.js";
|
|
12
12
|
import { controlRuntimeInstance, requestRuntimeInstance, runtimeInstanceDirectory } from "./instance.js";
|
|
13
|
-
import { ensureNativeSync } from "./native/attach.js";
|
|
13
|
+
import { ensureNativeSync, repairIntegrations } from "./native/attach.js";
|
|
14
14
|
import { createDiagnosticConsole, printRuntimeSummary, runtimeWebUrl } from "./presentation.js";
|
|
15
15
|
import { runRuntime } from "./supervisor.js";
|
|
16
16
|
export const resolveLocalSpaceName = (root, name) => name?.trim() || basename(root) || "local-space";
|
|
@@ -144,6 +144,7 @@ export async function runtimeUp(program, dir, options) {
|
|
|
144
144
|
if (createNew && binding && await requestRuntimeInstance(runtimeInstanceDirectory(identity, binding.spaceId))) {
|
|
145
145
|
throw new Error("Stop the existing Runtime before rebinding this directory");
|
|
146
146
|
}
|
|
147
|
+
await repairIntegrations(options.pi);
|
|
147
148
|
// Fail local preflight before creating remote state; the worker reuses this catalog.
|
|
148
149
|
const capabilities = await discoverHarnesses(harnesses, options, root);
|
|
149
150
|
const client = createClient();
|
|
@@ -30,4 +30,9 @@ export declare function nativeSyncSatisfied(config: NativeConfig | null, input:
|
|
|
30
30
|
* the user agrees; a failure here never blocks the Runtime, it only leaves sessions read-only.
|
|
31
31
|
*/
|
|
32
32
|
export declare function ensureNativeSync(input: Input, log?: (line: string) => void, ask?: Ask): Promise<NativeSyncReport>;
|
|
33
|
+
/**
|
|
34
|
+
* Before any harness starts, repair what earlier releases installed: an outdated Pi extension can keep
|
|
35
|
+
* Pi from starting, and legacy Codex hooks run a script that is gone. Both had consent back then.
|
|
36
|
+
*/
|
|
37
|
+
export declare function repairIntegrations(piBinary: string | undefined, log?: (line: string) => void): Promise<void>;
|
|
33
38
|
export {};
|
|
@@ -75,12 +75,22 @@ export async function ensureNativeSync(input, log = (line) => process.stderr.wri
|
|
|
75
75
|
}
|
|
76
76
|
return { enabled: true, harnesses, codexShared, importHistory };
|
|
77
77
|
}
|
|
78
|
-
/** Keep integrations current on every `up`.
|
|
78
|
+
/** Keep integrations current on every `up`. */
|
|
79
79
|
async function maintain(config, log, piBinary, announce = false) {
|
|
80
|
-
if (config.harnesses.includes("
|
|
81
|
-
await
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
if (config.harnesses.includes("pi"))
|
|
81
|
+
await maintainPiExtension(piBinary, log, announce);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Before any harness starts, repair what earlier releases installed: an outdated Pi extension can keep
|
|
85
|
+
* Pi from starting, and legacy Codex hooks run a script that is gone. Both had consent back then.
|
|
86
|
+
*/
|
|
87
|
+
export async function repairIntegrations(piBinary, log = (line) => process.stderr.write(line)) {
|
|
88
|
+
await removeLegacyCodexHooks().catch(() => undefined);
|
|
89
|
+
if (await piExtensionState().catch(() => null) === "outdated")
|
|
90
|
+
await maintainPiExtension(piBinary, log);
|
|
91
|
+
}
|
|
92
|
+
/** Pi without the extension still syncs, read-only. */
|
|
93
|
+
async function maintainPiExtension(piBinary, log, announce = false) {
|
|
84
94
|
const version = await harnessVersion(piBinary || "pi");
|
|
85
95
|
if (!atLeast(version, PI_MIN_VERSION)) {
|
|
86
96
|
log(`Pi ${formatVersion(version)} predates the Cohub extension (${PI_MIN_VERSION.join(".")}+); Pi chats sync read-only\n`);
|
|
@@ -7,19 +7,23 @@ import { withRuntimeSpaceBindingsLock } from "../space-binding.js";
|
|
|
7
7
|
const missing = (error) => error.code === "ENOENT";
|
|
8
8
|
/** First line of the installed extension; a file without it belongs to someone else. */
|
|
9
9
|
const PI_EXTENSION_HEADER = "// Cohub control extension for Pi. Managed by `cohub runtime`; local edits are replaced on upgrade.\n";
|
|
10
|
-
/**
|
|
11
|
-
const LEGACY_PI_EXTENSION_HEADER = "// Cohub native Turn sync
|
|
10
|
+
/** CLI 8.0–8.2 installed a re-export under this comment; its module is gone, so Pi fails to start while it remains. */
|
|
11
|
+
const LEGACY_PI_EXTENSION_HEADER = "// Cohub native Turn sync";
|
|
12
12
|
/** Earlier releases installed Codex hooks; they are removed on upgrade. */
|
|
13
13
|
const LEGACY_CODEX_HOOKS = /\n*# BEGIN COHUB NATIVE SYNC\n[\s\S]*?# END COHUB NATIVE SYNC\n?/;
|
|
14
14
|
export const piAgentDirectory = () => process.env.PI_CODING_AGENT_DIR?.trim() || join(homedir(), ".pi", "agent");
|
|
15
15
|
const codexConfigPath = () => join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "config.toml");
|
|
16
16
|
/** The extension shipped with this CLI: one self-contained module, loadable by Pi as is. */
|
|
17
17
|
export const piExtensionSource = () => fileURLToPath(new URL(import.meta.url.endsWith(".ts") ? "./pi-extension.ts" : "./pi-extension.js", import.meta.url));
|
|
18
|
-
const
|
|
18
|
+
const piExtensionPath = (extension) => join(piAgentDirectory(), "extensions", `cohub${extension}`);
|
|
19
|
+
const readOptional = (path) => readFile(path, "utf8").catch((error) => { if (missing(error))
|
|
20
|
+
return null; throw error; });
|
|
21
|
+
const isLegacy = (text) => Boolean(text?.startsWith(LEGACY_PI_EXTENSION_HEADER));
|
|
19
22
|
export async function piExtensionState() {
|
|
20
23
|
const source = piExtensionSource();
|
|
21
|
-
const [expected, current] = await Promise.all([readFile(source, "utf8"),
|
|
22
|
-
|
|
24
|
+
const [expected, current, legacy] = await Promise.all([readFile(source, "utf8"), readOptional(piExtensionPath(extname(source))), readOptional(piExtensionPath(".ts"))]);
|
|
25
|
+
if (isLegacy(legacy))
|
|
26
|
+
return "outdated";
|
|
23
27
|
if (current === null)
|
|
24
28
|
return "missing";
|
|
25
29
|
if (current === PI_EXTENSION_HEADER + expected)
|
|
@@ -30,22 +34,26 @@ export async function piExtensionState() {
|
|
|
30
34
|
export async function installPiExtension() {
|
|
31
35
|
const source = piExtensionSource();
|
|
32
36
|
const content = PI_EXTENSION_HEADER + await readFile(source, "utf8");
|
|
33
|
-
const target =
|
|
37
|
+
const target = piExtensionPath(extname(source));
|
|
34
38
|
await writeManaged(target, (existing) => {
|
|
35
|
-
if (existing !== null && !existing.startsWith(PI_EXTENSION_HEADER))
|
|
39
|
+
if (existing !== null && !existing.startsWith(PI_EXTENSION_HEADER) && !isLegacy(existing))
|
|
36
40
|
throw new Error(`${target} exists and is not managed by Cohub; move it aside, then retry`);
|
|
37
41
|
return content;
|
|
38
42
|
}, { backup: false });
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
// Pi loads every file here: drop a legacy or managed copy under the other extension.
|
|
44
|
+
for (const other of [".ts", ".js"].map(piExtensionPath).filter((path) => path !== target)) {
|
|
45
|
+
const text = await readOptional(other).catch(() => null);
|
|
46
|
+
if (isLegacy(text) || text?.startsWith(PI_EXTENSION_HEADER))
|
|
47
|
+
await rm(other, { force: true });
|
|
43
48
|
}
|
|
44
49
|
return "installed";
|
|
45
50
|
}
|
|
46
51
|
/** Remove the Codex hooks earlier releases installed; Cohub now reads Codex's own files instead. */
|
|
47
52
|
export async function removeLegacyCodexHooks() {
|
|
48
53
|
const path = codexConfigPath();
|
|
54
|
+
// Read first: the lock would create `~/.codex` for someone who never used Codex.
|
|
55
|
+
if (!LEGACY_CODEX_HOOKS.test(await readOptional(path) ?? ""))
|
|
56
|
+
return false;
|
|
49
57
|
let removed = false;
|
|
50
58
|
await writeManaged(path, (existing) => {
|
|
51
59
|
if (existing === null || !LEGACY_CODEX_HOOKS.test(existing))
|