codiedev 0.9.70 → 0.9.72
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/captureSignals.js +4 -0
- package/dist/cli.js +4 -2
- package/dist/codie.js +25 -0
- package/dist/commands/doctor.js +11 -0
- package/dist/commands/pack.d.ts +1 -1
- package/dist/commands/pack.js +1 -1
- package/dist/connect.js +9 -1
- package/dist/harnessDirs.d.ts +3 -0
- package/dist/harnessDirs.js +6 -0
- package/dist/hook.js +25 -8
- package/dist/limitEventReporter.d.ts +1 -1
- package/dist/limitEventReporter.js +1 -1
- package/dist/managedPi.d.ts +48 -0
- package/dist/managedPi.js +288 -0
- package/dist/packs/harnesses.d.ts +4 -1
- package/dist/packs/harnesses.js +44 -3
- package/dist/piHooks.d.ts +22 -0
- package/dist/piHooks.js +257 -0
- package/dist/piRuntime.d.ts +25 -0
- package/dist/piRuntime.js +125 -0
- package/dist/utils.d.ts +24 -2
- package/dist/utils.js +185 -4
- package/package.json +4 -4
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const PI_PACKAGE = "@mariozechner/pi-coding-agent";
|
|
2
|
+
export declare const PI_VERSION = "0.73.1";
|
|
3
|
+
export declare const PI_RUNTIME_SPEC = "@mariozechner/pi-coding-agent@0.73.1";
|
|
4
|
+
export declare function piRuntimeDir(stateDir: string): string;
|
|
5
|
+
/** The installed pi's CLI entry, when it is the pinned version. */
|
|
6
|
+
export declare function installedPiEntry(installDir: string, readFile?: (file: string) => string): string | null;
|
|
7
|
+
export interface PiRuntimeDeps {
|
|
8
|
+
stateDir: string;
|
|
9
|
+
exists?: (file: string) => boolean;
|
|
10
|
+
readFile?: (file: string) => string;
|
|
11
|
+
makeStagingDir?: () => string;
|
|
12
|
+
run?: (args: string[]) => {
|
|
13
|
+
status: number | null;
|
|
14
|
+
stderr: string;
|
|
15
|
+
};
|
|
16
|
+
rename?: (from: string, to: string) => void;
|
|
17
|
+
remove?: (target: string) => void;
|
|
18
|
+
log?: (message: string) => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The pi CLI entry to run, installing the pinned version first if it is
|
|
22
|
+
* missing or a different version. Staged then renamed, so an interrupted
|
|
23
|
+
* download never leaves a half-installed pi that looks ready.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ensurePiRuntime(deps: PiRuntimeDeps): string;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PI_RUNTIME_SPEC = exports.PI_VERSION = exports.PI_PACKAGE = void 0;
|
|
37
|
+
exports.piRuntimeDir = piRuntimeDir;
|
|
38
|
+
exports.installedPiEntry = installedPiEntry;
|
|
39
|
+
exports.ensurePiRuntime = ensurePiRuntime;
|
|
40
|
+
// pi for `codie --pi`, installed on first use like the Codie engine
|
|
41
|
+
// (codieRuntimeInstall.ts): developers never run a second npm install, and
|
|
42
|
+
// every machine runs the version Codiedev tested its capture against.
|
|
43
|
+
//
|
|
44
|
+
// pi is plain JavaScript, so it runs on the same Node as `codie` from its
|
|
45
|
+
// package entry: no PATH lookup, no Windows .cmd shim, and no dependency on
|
|
46
|
+
// whatever `pi` (if any) the developer installed globally.
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const child_process_1 = require("child_process");
|
|
50
|
+
const codieRuntimeInstall_1 = require("./codieRuntimeInstall");
|
|
51
|
+
exports.PI_PACKAGE = "@mariozechner/pi-coding-agent";
|
|
52
|
+
exports.PI_VERSION = "0.73.1";
|
|
53
|
+
exports.PI_RUNTIME_SPEC = `${exports.PI_PACKAGE}@${exports.PI_VERSION}`;
|
|
54
|
+
function piRuntimeDir(stateDir) {
|
|
55
|
+
return path.join(stateDir, "runtime-pi");
|
|
56
|
+
}
|
|
57
|
+
function packageDir(installDir) {
|
|
58
|
+
return path.join(installDir, "node_modules", ...exports.PI_PACKAGE.split("/"));
|
|
59
|
+
}
|
|
60
|
+
/** The installed pi's CLI entry, when it is the pinned version. */
|
|
61
|
+
function installedPiEntry(installDir, readFile = (f) => fs.readFileSync(f, "utf8")) {
|
|
62
|
+
try {
|
|
63
|
+
const pkg = JSON.parse(readFile(path.join(packageDir(installDir), "package.json")));
|
|
64
|
+
if (pkg.version !== exports.PI_VERSION)
|
|
65
|
+
return null;
|
|
66
|
+
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.pi;
|
|
67
|
+
return typeof bin === "string" ? path.join(packageDir(installDir), bin) : null;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The pi CLI entry to run, installing the pinned version first if it is
|
|
75
|
+
* missing or a different version. Staged then renamed, so an interrupted
|
|
76
|
+
* download never leaves a half-installed pi that looks ready.
|
|
77
|
+
*/
|
|
78
|
+
function ensurePiRuntime(deps) {
|
|
79
|
+
const installDir = piRuntimeDir(deps.stateDir);
|
|
80
|
+
const exists = deps.exists ?? fs.existsSync;
|
|
81
|
+
const readFile = deps.readFile ?? ((f) => fs.readFileSync(f, "utf8"));
|
|
82
|
+
const ready = installedPiEntry(installDir, readFile);
|
|
83
|
+
if (ready && exists(ready))
|
|
84
|
+
return ready;
|
|
85
|
+
const remove = deps.remove ?? ((target) => fs.rmSync(target, { recursive: true, force: true }));
|
|
86
|
+
const rename = deps.rename ?? fs.renameSync;
|
|
87
|
+
const log = deps.log ?? ((message) => console.error(message));
|
|
88
|
+
const staging = (deps.makeStagingDir ?? (() => {
|
|
89
|
+
fs.mkdirSync(deps.stateDir, { recursive: true });
|
|
90
|
+
return fs.mkdtempSync(path.join(deps.stateDir, ".runtime-pi-"));
|
|
91
|
+
}))();
|
|
92
|
+
const run = deps.run ?? ((args) => {
|
|
93
|
+
const result = (0, child_process_1.spawnSync)(process.platform === "win32" ? "npm.cmd" : "npm", args, {
|
|
94
|
+
encoding: "utf8", stdio: ["ignore", "ignore", "pipe"], shell: process.platform === "win32",
|
|
95
|
+
});
|
|
96
|
+
if (result.error)
|
|
97
|
+
throw result.error;
|
|
98
|
+
return { status: result.status, stderr: result.stderr ?? "" };
|
|
99
|
+
});
|
|
100
|
+
log(`Installing pi (${exports.PI_RUNTIME_SPEC}). This runs once, and only for \`codie --pi\`.`);
|
|
101
|
+
try {
|
|
102
|
+
const result = run((0, codieRuntimeInstall_1.buildRuntimeInstallArgs)(staging, exports.PI_RUNTIME_SPEC));
|
|
103
|
+
if (result.status !== 0)
|
|
104
|
+
throw new Error(result.stderr?.trim() || `npm exited with ${result.status}`);
|
|
105
|
+
// Trust the artifact, not the exit code.
|
|
106
|
+
const staged = installedPiEntry(staging, readFile);
|
|
107
|
+
if (!staged || !exists(staged)) {
|
|
108
|
+
throw new Error(`the install did not produce pi. Check that ${exports.PI_RUNTIME_SPEC} is available from your npm registry.`);
|
|
109
|
+
}
|
|
110
|
+
remove(installDir);
|
|
111
|
+
rename(staging, installDir);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw new Error(`Couldn't install pi: ${error instanceof Error ? error.message : String(error)}`);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
// No-op after a successful promote; otherwise it keeps a ~200MB staging
|
|
118
|
+
// dir from being orphaned.
|
|
119
|
+
remove(staging);
|
|
120
|
+
}
|
|
121
|
+
const entry = installedPiEntry(installDir, readFile);
|
|
122
|
+
if (!entry)
|
|
123
|
+
throw new Error("Couldn't install pi: the installed package has no CLI entry.");
|
|
124
|
+
return entry;
|
|
125
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type TranscriptModelUsage } from "./transcriptModels";
|
|
|
4
4
|
import { spawn as nodeSpawn } from "child_process";
|
|
5
5
|
import type { CaptureLogEntry } from "./captureLog";
|
|
6
6
|
import { codiedevStateDir } from "./configDir";
|
|
7
|
+
export { inspectPiExtension, piExtensionPath } from "./piHooks";
|
|
7
8
|
export { codiedevStateDir };
|
|
8
9
|
export interface CodiedevConfig {
|
|
9
10
|
token: string;
|
|
@@ -108,6 +109,22 @@ export declare function openCodeInstalled(): boolean;
|
|
|
108
109
|
* keeps `codiedev connect` as the only setup step even when Codie/OpenCode is
|
|
109
110
|
* installed later. OpenCode loads this file from its documented plugin dir. */
|
|
110
111
|
export declare function installOpenCodeCapturePlugin(): void;
|
|
112
|
+
export declare function piInstalled(): boolean;
|
|
113
|
+
/** Subcommands the pi extension calls, in the order inspectPiExtension checks. */
|
|
114
|
+
export declare const PI_EXTENSION_SUBCOMMANDS: readonly ["session-start-pi", "backup-pi", "capture-pi-final", "discover-pi", "security-pre-pi", "security-post-pi", "security-failure-pi"];
|
|
115
|
+
/**
|
|
116
|
+
* pi has no hook config: it auto-loads TypeScript extensions from
|
|
117
|
+
* <agentDir>/extensions/. This one forwards pi's events to the same hook
|
|
118
|
+
* subcommands as every other harness. pi already writes the session JSONL, so
|
|
119
|
+
* capture just hands its path over (no transcript dump like OpenCode's).
|
|
120
|
+
*/
|
|
121
|
+
export declare function piExtensionSource(agentSecurityEnabled: boolean): string;
|
|
122
|
+
/** Install the pi extension. Unconditional like OpenCode's: pi installed
|
|
123
|
+
* later still gets captured without re-running `codiedev connect`. This
|
|
124
|
+
* release line has no Agent Security hooks, so security stays off. */
|
|
125
|
+
export declare function installPiExtension(agentSecurityEnabled?: boolean): void;
|
|
126
|
+
/** Self-heal after an upgrade or drift, like Copilot's hook file. */
|
|
127
|
+
export declare function ensurePiExtensionInstalled(agentSecurityEnabled?: boolean): void;
|
|
111
128
|
/**
|
|
112
129
|
* VS Code with the GitHub Copilot Chat extension installed. The extension
|
|
113
130
|
* directory pattern is `~/.vscode/extensions/github.copilot-chat-*`. We treat
|
|
@@ -127,6 +144,11 @@ export declare function ensureSessionStartHookInstalled(): void;
|
|
|
127
144
|
export declare function installClaudeCodeInstructions(): void;
|
|
128
145
|
export declare function installCodexInstructions(): void;
|
|
129
146
|
export declare function copilotInstructionsPath(): string;
|
|
147
|
+
/** pi's global context file, loaded into every session's system prompt. */
|
|
148
|
+
export declare function piInstructionsPath(): string;
|
|
149
|
+
/** pi has no session-start output channel, so team skill context rides in
|
|
150
|
+
* AGENTS.md the way it does for Copilot. */
|
|
151
|
+
export declare function installPiInstructions(): void;
|
|
130
152
|
export declare function installCopilotInstructions(): void;
|
|
131
153
|
export declare function installCursorInstructions(): void;
|
|
132
154
|
export interface InstructionsTarget {
|
|
@@ -212,7 +234,7 @@ export interface SteeringStats {
|
|
|
212
234
|
}
|
|
213
235
|
export declare function parseClaudeSteering(content: string): SteeringStats;
|
|
214
236
|
export declare function parseCodexSteering(content: string): SteeringStats;
|
|
215
|
-
export declare function parseSteering(content: string, mode: "claude-code" | "codex" | "cursor" | "opencode" | "copilot"): SteeringStats;
|
|
237
|
+
export declare function parseSteering(content: string, mode: "claude-code" | "codex" | "cursor" | "opencode" | "copilot" | "pi"): SteeringStats;
|
|
216
238
|
export interface UsageStats {
|
|
217
239
|
inputTokens: number;
|
|
218
240
|
outputTokens: number;
|
|
@@ -224,7 +246,7 @@ export interface UsageStats {
|
|
|
224
246
|
/** Engine-reported spend (opencode SDK `info.cost`). Desktop mode only. */
|
|
225
247
|
costUsd?: number;
|
|
226
248
|
}
|
|
227
|
-
export declare function parseUsage(content: string, mode: "claude-code" | "codex" | "cursor" | "opencode" | "desktop" | "copilot"): UsageStats;
|
|
249
|
+
export declare function parseUsage(content: string, mode: "claude-code" | "codex" | "cursor" | "opencode" | "desktop" | "copilot" | "pi"): UsageStats;
|
|
228
250
|
export declare function parseSessionStart(content: string): number | null;
|
|
229
251
|
export declare function parseClaudeCodeStats(content: string): ParsedStats;
|
|
230
252
|
/** Convert OpenCode's SDK message array (`{info, parts}[]`) into the same
|
package/dist/utils.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.DIRECT_UPLOAD_THRESHOLD_CHARS = exports.SWEEP_MIN_AGE_MS = exports.CAPTURE_THROTTLE_MS = exports.FINISH_ORPHAN_AGE_MS = exports.CLAIM_STALE_MS = exports.UPLOAD_TTL_MS = exports.MAX_UPLOAD_ATTEMPTS = exports.CONFIG_PATH = exports.codiedevStateDir = void 0;
|
|
36
|
+
exports.DIRECT_UPLOAD_THRESHOLD_CHARS = exports.SWEEP_MIN_AGE_MS = exports.CAPTURE_THROTTLE_MS = exports.FINISH_ORPHAN_AGE_MS = exports.CLAIM_STALE_MS = exports.UPLOAD_TTL_MS = exports.MAX_UPLOAD_ATTEMPTS = exports.PI_EXTENSION_SUBCOMMANDS = exports.CONFIG_PATH = exports.codiedevStateDir = exports.piExtensionPath = exports.inspectPiExtension = void 0;
|
|
37
37
|
exports.readConfig = readConfig;
|
|
38
38
|
exports.writeConfig = writeConfig;
|
|
39
39
|
exports.writeConfigAtPath = writeConfigAtPath;
|
|
@@ -64,6 +64,10 @@ exports.cursorInstalled = cursorInstalled;
|
|
|
64
64
|
exports.copilotInstalled = copilotInstalled;
|
|
65
65
|
exports.openCodeInstalled = openCodeInstalled;
|
|
66
66
|
exports.installOpenCodeCapturePlugin = installOpenCodeCapturePlugin;
|
|
67
|
+
exports.piInstalled = piInstalled;
|
|
68
|
+
exports.piExtensionSource = piExtensionSource;
|
|
69
|
+
exports.installPiExtension = installPiExtension;
|
|
70
|
+
exports.ensurePiExtensionInstalled = ensurePiExtensionInstalled;
|
|
67
71
|
exports.vscodeCopilotInstalled = vscodeCopilotInstalled;
|
|
68
72
|
exports.upsertCodiedevHook = upsertCodiedevHook;
|
|
69
73
|
exports.installHook = installHook;
|
|
@@ -77,6 +81,8 @@ exports.ensureSessionStartHookInstalled = ensureSessionStartHookInstalled;
|
|
|
77
81
|
exports.installClaudeCodeInstructions = installClaudeCodeInstructions;
|
|
78
82
|
exports.installCodexInstructions = installCodexInstructions;
|
|
79
83
|
exports.copilotInstructionsPath = copilotInstructionsPath;
|
|
84
|
+
exports.piInstructionsPath = piInstructionsPath;
|
|
85
|
+
exports.installPiInstructions = installPiInstructions;
|
|
80
86
|
exports.installCopilotInstructions = installCopilotInstructions;
|
|
81
87
|
exports.installCursorInstructions = installCursorInstructions;
|
|
82
88
|
exports.installedInstructionsTargets = installedInstructionsTargets;
|
|
@@ -140,6 +146,10 @@ const teamSkillContext_1 = require("./teamSkillContext");
|
|
|
140
146
|
const nativeSteering_1 = require("./nativeSteering");
|
|
141
147
|
const copilotHooks_1 = require("./copilotHooks");
|
|
142
148
|
const harnessDirs_1 = require("./harnessDirs");
|
|
149
|
+
const piHooks_1 = require("./piHooks");
|
|
150
|
+
var piHooks_2 = require("./piHooks");
|
|
151
|
+
Object.defineProperty(exports, "inspectPiExtension", { enumerable: true, get: function () { return piHooks_2.inspectPiExtension; } });
|
|
152
|
+
Object.defineProperty(exports, "piExtensionPath", { enumerable: true, get: function () { return piHooks_2.piExtensionPath; } });
|
|
143
153
|
const CONFIG_DIR = (0, configDir_1.codiedevStateDir)();
|
|
144
154
|
exports.CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
145
155
|
function readConfig() {
|
|
@@ -1111,7 +1121,6 @@ function installOpenCodeCapturePlugin() {
|
|
|
1111
1121
|
`import os from "node:os";\n` +
|
|
1112
1122
|
`import path from "node:path";\n\n` +
|
|
1113
1123
|
`const START = ${JSON.stringify(start)};\n` +
|
|
1114
|
-
`const CAPTURE = ${JSON.stringify(capture)};\n` +
|
|
1115
1124
|
`const FINISH = ${JSON.stringify(finish)};\n` +
|
|
1116
1125
|
`const PROMPT = ${JSON.stringify(prompt)};\n` +
|
|
1117
1126
|
`// How this Codie process was started (run / serve / tui). OpenCode records\n` +
|
|
@@ -1203,6 +1212,167 @@ function installOpenCodeCapturePlugin() {
|
|
|
1203
1212
|
fs.writeFileSync(OPENCODE_PLUGIN_PATH, source, { encoding: "utf8", mode: 0o600 });
|
|
1204
1213
|
fs.chmodSync(OPENCODE_PLUGIN_PATH, 0o600);
|
|
1205
1214
|
}
|
|
1215
|
+
function piInstalled() {
|
|
1216
|
+
if (fs.existsSync((0, harnessDirs_1.piAgentDir)()))
|
|
1217
|
+
return true;
|
|
1218
|
+
try {
|
|
1219
|
+
(0, child_process_1.execSync)(process.platform === "win32" ? "where pi" : "command -v pi", { stdio: ["pipe", "pipe", "pipe"] });
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
catch {
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
/** Subcommands the pi extension calls, in the order inspectPiExtension checks. */
|
|
1227
|
+
exports.PI_EXTENSION_SUBCOMMANDS = [
|
|
1228
|
+
"session-start-pi", "backup-pi", "capture-pi-final", "discover-pi",
|
|
1229
|
+
"security-pre-pi", "security-post-pi", "security-failure-pi",
|
|
1230
|
+
];
|
|
1231
|
+
/**
|
|
1232
|
+
* pi has no hook config: it auto-loads TypeScript extensions from
|
|
1233
|
+
* <agentDir>/extensions/. This one forwards pi's events to the same hook
|
|
1234
|
+
* subcommands as every other harness. pi already writes the session JSONL, so
|
|
1235
|
+
* capture just hands its path over (no transcript dump like OpenCode's).
|
|
1236
|
+
*/
|
|
1237
|
+
function piExtensionSource(agentSecurityEnabled) {
|
|
1238
|
+
const [start, backup, finish, prompt, securityPre, securityPost, securityFailure] = exports.PI_EXTENSION_SUBCOMMANDS.map((subcommand) => resolveHookInvocation(subcommand));
|
|
1239
|
+
return `// Managed by codiedev connect. Safe to remove; \`codiedev connect\` reinstalls it.\n` +
|
|
1240
|
+
`import { spawn, spawnSync } from "node:child_process";\n\n` +
|
|
1241
|
+
`const START = ${JSON.stringify(start)};\n` +
|
|
1242
|
+
`const FINISH = ${JSON.stringify(finish)};\n` +
|
|
1243
|
+
`const BACKUP = ${JSON.stringify(backup)};\n` +
|
|
1244
|
+
`const PROMPT = ${JSON.stringify(prompt)};\n` +
|
|
1245
|
+
`const SECURITY_PRE = ${JSON.stringify(securityPre)};\n` +
|
|
1246
|
+
`const SECURITY_POST = ${JSON.stringify(securityPost)};\n` +
|
|
1247
|
+
`const SECURITY_FAILURE = ${JSON.stringify(securityFailure)};\n` +
|
|
1248
|
+
`const AGENT_SECURITY_ENABLED = ${JSON.stringify(agentSecurityEnabled)};\n` +
|
|
1249
|
+
`const CONTEXT_TYPE = ${JSON.stringify(piHooks_1.PI_CONTEXT_CUSTOM_TYPE)};\n` +
|
|
1250
|
+
`const piLaunchFromArgv = ${piHooks_1.piLaunchFromArgv.toString()};\n` +
|
|
1251
|
+
`const LAUNCH = piLaunchFromArgv(process.argv.slice(2));\n\n` +
|
|
1252
|
+
`function dispatch(invocation, payload) {\n` +
|
|
1253
|
+
` try {\n` +
|
|
1254
|
+
` const child = spawn(invocation.command, invocation.args, { detached: true, stdio: ["pipe", "ignore", "ignore"] });\n` +
|
|
1255
|
+
` child.stdin.end(JSON.stringify(payload));\n` +
|
|
1256
|
+
` child.unref();\n` +
|
|
1257
|
+
` } catch {}\n` +
|
|
1258
|
+
`}\n\n` +
|
|
1259
|
+
`function dispatchSync(invocation, payload) {\n` +
|
|
1260
|
+
` try { spawnSync(invocation.command, invocation.args, { input: JSON.stringify(payload), stdio: ["pipe", "ignore", "ignore"], timeout: 1500 }); } catch {}\n` +
|
|
1261
|
+
`}\n\n` +
|
|
1262
|
+
`function invokeSync(invocation, payload) {\n` +
|
|
1263
|
+
` try {\n` +
|
|
1264
|
+
` const result = spawnSync(invocation.command, invocation.args, { input: JSON.stringify(payload), encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], timeout: 1500 });\n` +
|
|
1265
|
+
` if (result.error) return null;\n` +
|
|
1266
|
+
` return JSON.parse(result.stdout || "{}");\n` +
|
|
1267
|
+
` } catch { return null; }\n` +
|
|
1268
|
+
`}\n\n` +
|
|
1269
|
+
`function blockReason(decision) {\n` +
|
|
1270
|
+
` const denied = decision?.decision === "block" || decision?.hookSpecificOutput?.permissionDecision === "deny";\n` +
|
|
1271
|
+
` return denied ? (decision?.reason || decision?.hookSpecificOutput?.permissionDecisionReason || "Codie security policy blocked this action") : null;\n` +
|
|
1272
|
+
`}\n\n` +
|
|
1273
|
+
`function warn(ctx, reason) {\n` +
|
|
1274
|
+
` if (ctx?.hasUI) { try { ctx.ui.notify("Codie Security: " + reason, "error"); return; } catch {} }\n` +
|
|
1275
|
+
` process.stderr.write("\\nCodie Security blocked this action:\\n" + reason + "\\n");\n` +
|
|
1276
|
+
`}\n\n` +
|
|
1277
|
+
`export default function codiedev(pi) {\n` +
|
|
1278
|
+
` let session = null;\n` +
|
|
1279
|
+
` let pendingContext = null;\n` +
|
|
1280
|
+
` let finalized = false;\n` +
|
|
1281
|
+
` const current = (ctx) => {\n` +
|
|
1282
|
+
` const sm = ctx?.sessionManager;\n` +
|
|
1283
|
+
` const id = sm?.getSessionId?.() ?? session?.id;\n` +
|
|
1284
|
+
` const file = sm?.getSessionFile?.() ?? session?.file;\n` +
|
|
1285
|
+
` const cwd = ctx?.cwd || session?.cwd || process.cwd();\n` +
|
|
1286
|
+
` session = { id, file, cwd };\n` +
|
|
1287
|
+
` return session;\n` +
|
|
1288
|
+
` };\n` +
|
|
1289
|
+
` const payload = (s, extra) => ({ session_id: s.id, transcript_path: s.file, cwd: s.cwd, workspace_roots: [s.cwd], launch: LAUNCH, ...extra });\n` +
|
|
1290
|
+
` const finalize = () => {\n` +
|
|
1291
|
+
` // No file (pi --no-session) still finalizes: the hook reports a skip, so\n` +
|
|
1292
|
+
` // the start ping is not left open.\n` +
|
|
1293
|
+
` if (finalized || !session?.id) return;\n` +
|
|
1294
|
+
` finalized = true;\n` +
|
|
1295
|
+
` dispatchSync(FINISH, payload(session));\n` +
|
|
1296
|
+
` };\n` +
|
|
1297
|
+
` // Print mode can exit without a shutdown event; the session is still over.\n` +
|
|
1298
|
+
` // pi rebuilds extensions on every /new, /resume and /fork, so register one\n` +
|
|
1299
|
+
` // exit hook per process and point it at the newest instance.\n` +
|
|
1300
|
+
` globalThis.__codiedevPiFinalize = finalize;\n` +
|
|
1301
|
+
` if (!globalThis.__codiedevPiExitHook) {\n` +
|
|
1302
|
+
` globalThis.__codiedevPiExitHook = true;\n` +
|
|
1303
|
+
` process.once("exit", () => globalThis.__codiedevPiFinalize?.());\n` +
|
|
1304
|
+
` }\n\n` +
|
|
1305
|
+
` pi.on("session_start", async (event, ctx) => {\n` +
|
|
1306
|
+
` finalized = false;\n` +
|
|
1307
|
+
` if (event?.reason === "reload") { current(ctx); return; }\n` +
|
|
1308
|
+
` const s = current(ctx);\n` +
|
|
1309
|
+
` // pi can announce one new session twice across an extension rebind.\n` +
|
|
1310
|
+
` if (!s.id || globalThis.__codiedevPiStarted === s.id) return;\n` +
|
|
1311
|
+
` globalThis.__codiedevPiStarted = s.id;\n` +
|
|
1312
|
+
` dispatch(START, payload(s));\n` +
|
|
1313
|
+
` });\n\n` +
|
|
1314
|
+
` pi.on("input", async (event, ctx) => {\n` +
|
|
1315
|
+
` if (event?.source === "extension") return { action: "continue" };\n` +
|
|
1316
|
+
` const s = current(ctx);\n` +
|
|
1317
|
+
` const decision = invokeSync(PROMPT, { session_id: s.id, cwd: s.cwd, prompt: event?.text ?? "" });\n` +
|
|
1318
|
+
` const reason = blockReason(decision);\n` +
|
|
1319
|
+
` if (reason) { warn(ctx, reason); return { action: "handled" }; }\n` +
|
|
1320
|
+
` pendingContext = decision?.hookSpecificOutput?.additionalContext || null;\n` +
|
|
1321
|
+
` return { action: "continue" };\n` +
|
|
1322
|
+
` });\n\n` +
|
|
1323
|
+
` pi.on("before_agent_start", async () => {\n` +
|
|
1324
|
+
` if (!pendingContext) return;\n` +
|
|
1325
|
+
` const content = pendingContext;\n` +
|
|
1326
|
+
` pendingContext = null;\n` +
|
|
1327
|
+
` return { message: { customType: CONTEXT_TYPE, content, display: false } };\n` +
|
|
1328
|
+
` });\n\n` +
|
|
1329
|
+
` pi.on("tool_call", async (event, ctx) => {\n` +
|
|
1330
|
+
` if (!AGENT_SECURITY_ENABLED) return;\n` +
|
|
1331
|
+
` const s = current(ctx);\n` +
|
|
1332
|
+
` const reason = blockReason(invokeSync(SECURITY_PRE, { sessionId: s.id, cwd: s.cwd, toolName: event.toolName, toolUseId: event.toolCallId, toolInput: event.input }));\n` +
|
|
1333
|
+
` if (reason) { warn(ctx, reason); return { block: true, reason }; }\n` +
|
|
1334
|
+
` });\n\n` +
|
|
1335
|
+
` pi.on("tool_result", async (event, ctx) => {\n` +
|
|
1336
|
+
` if (!AGENT_SECURITY_ENABLED) return;\n` +
|
|
1337
|
+
` const s = current(ctx);\n` +
|
|
1338
|
+
` dispatch(event.isError ? SECURITY_FAILURE : SECURITY_POST, { sessionId: s.id, cwd: s.cwd, toolName: event.toolName, toolUseId: event.toolCallId, toolInput: event.input, toolResponse: { content: event.content, isError: event.isError } });\n` +
|
|
1339
|
+
` });\n\n` +
|
|
1340
|
+
` pi.on("agent_end", async (_event, ctx) => {\n` +
|
|
1341
|
+
` const s = current(ctx);\n` +
|
|
1342
|
+
` if (!s.id || !s.file) return;\n` +
|
|
1343
|
+
` // Interim capture + artifact backup, like every harness's Stop hook. pi\n` +
|
|
1344
|
+
` // retries provider errors itself; agent_end comes after the last try, so\n` +
|
|
1345
|
+
` // the capture reports a hit limit once per run, not once per retry.\n` +
|
|
1346
|
+
` dispatch(BACKUP, payload(s));\n` +
|
|
1347
|
+
` });\n\n` +
|
|
1348
|
+
` pi.on("session_shutdown", async (event, ctx) => {\n` +
|
|
1349
|
+
` // A reload keeps the same session running.\n` +
|
|
1350
|
+
` if (event?.reason === "reload") return;\n` +
|
|
1351
|
+
` current(ctx);\n` +
|
|
1352
|
+
` finalize();\n` +
|
|
1353
|
+
` });\n` +
|
|
1354
|
+
`}\n`;
|
|
1355
|
+
}
|
|
1356
|
+
/** Install the pi extension. Unconditional like OpenCode's: pi installed
|
|
1357
|
+
* later still gets captured without re-running `codiedev connect`. This
|
|
1358
|
+
* release line has no Agent Security hooks, so security stays off. */
|
|
1359
|
+
function installPiExtension(agentSecurityEnabled = false) {
|
|
1360
|
+
const target = (0, piHooks_1.piExtensionPath)();
|
|
1361
|
+
fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
1362
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
1363
|
+
fs.writeFileSync(tmp, piExtensionSource(agentSecurityEnabled), { encoding: "utf8", mode: 0o600 });
|
|
1364
|
+
fs.renameSync(tmp, target);
|
|
1365
|
+
}
|
|
1366
|
+
/** Self-heal after an upgrade or drift, like Copilot's hook file. */
|
|
1367
|
+
function ensurePiExtensionInstalled(agentSecurityEnabled = false) {
|
|
1368
|
+
try {
|
|
1369
|
+
const file = (0, piHooks_1.piExtensionPath)();
|
|
1370
|
+
if (fs.existsSync(file) && fs.readFileSync(file, "utf8") === piExtensionSource(agentSecurityEnabled))
|
|
1371
|
+
return;
|
|
1372
|
+
installPiExtension(agentSecurityEnabled);
|
|
1373
|
+
}
|
|
1374
|
+
catch { /* best-effort */ }
|
|
1375
|
+
}
|
|
1206
1376
|
/**
|
|
1207
1377
|
* VS Code with the GitHub Copilot Chat extension installed. The extension
|
|
1208
1378
|
* directory pattern is `~/.vscode/extensions/github.copilot-chat-*`. We treat
|
|
@@ -1424,6 +1594,7 @@ function migrateHooksToLauncher() {
|
|
|
1424
1594
|
["cursor", CURSOR_HOOKS_PATH, installCursorHook],
|
|
1425
1595
|
["copilot", copilotHooksPath(), installCopilotHook],
|
|
1426
1596
|
["opencode", OPENCODE_PLUGIN_PATH, installOpenCodeCapturePlugin],
|
|
1597
|
+
["pi", (0, piHooks_1.piExtensionPath)(), installPiExtension],
|
|
1427
1598
|
];
|
|
1428
1599
|
const migrated = [];
|
|
1429
1600
|
for (const [name, file, install] of targets) {
|
|
@@ -1601,6 +1772,15 @@ function installCodexInstructions() {
|
|
|
1601
1772
|
function copilotInstructionsPath() {
|
|
1602
1773
|
return path.join((0, copilotHooks_1.copilotHome)(), "copilot-instructions.md");
|
|
1603
1774
|
}
|
|
1775
|
+
/** pi's global context file, loaded into every session's system prompt. */
|
|
1776
|
+
function piInstructionsPath() {
|
|
1777
|
+
return path.join((0, harnessDirs_1.piAgentDir)(), "AGENTS.md");
|
|
1778
|
+
}
|
|
1779
|
+
/** pi has no session-start output channel, so team skill context rides in
|
|
1780
|
+
* AGENTS.md the way it does for Copilot. */
|
|
1781
|
+
function installPiInstructions() {
|
|
1782
|
+
writeInstructionsBlock(piInstructionsPath(), `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}`);
|
|
1783
|
+
}
|
|
1604
1784
|
function installCopilotInstructions() {
|
|
1605
1785
|
writeInstructionsBlock(copilotInstructionsPath(), `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}`);
|
|
1606
1786
|
}
|
|
@@ -1654,6 +1834,7 @@ function installedInstructionsTargets() {
|
|
|
1654
1834
|
{ path: CODEX_USER_INSTRUCTIONS_PATH, body: CODIEDEV_INSTRUCTIONS_BODY },
|
|
1655
1835
|
{ path: copilotInstructionsPath(), body: withTeamContext },
|
|
1656
1836
|
{ path: CURSOR_RULES_PATH, body: withTeamContext },
|
|
1837
|
+
{ path: piInstructionsPath(), body: withTeamContext },
|
|
1657
1838
|
];
|
|
1658
1839
|
}
|
|
1659
1840
|
/**
|
|
@@ -2798,9 +2979,9 @@ exports.SWEEP_MIN_AGE_MS = exports.CAPTURE_THROTTLE_MS + 2 * 60 * 1000;
|
|
|
2798
2979
|
// to true would silently re-introduce the per-turn lease-close bug for every
|
|
2799
2980
|
// new agent.
|
|
2800
2981
|
function resolveCaptureFinal(argv2) {
|
|
2801
|
-
if (argv2 === "capture-opencode" || argv2 === "capture-desktop")
|
|
2982
|
+
if (argv2 === "capture-opencode" || argv2 === "capture-desktop" || argv2 === "capture-pi")
|
|
2802
2983
|
return false;
|
|
2803
|
-
if (argv2 === "capture-opencode-final" || argv2 === "capture-desktop-final")
|
|
2984
|
+
if (argv2 === "capture-opencode-final" || argv2 === "capture-desktop-final" || argv2 === "capture-pi-final")
|
|
2804
2985
|
return true;
|
|
2805
2986
|
return argv2 === "capture" || argv2 === "capture-cursor" || argv2 === "capture-copilot" || argv2 === undefined
|
|
2806
2987
|
? true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.72",
|
|
4
4
|
"description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration — and run the governed Codie harness",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codiedev": "dist/cli.js",
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"dev": "tsc --watch",
|
|
13
13
|
"prepublishOnly": "tsc",
|
|
14
14
|
"test": "vitest run",
|
|
15
|
-
"test:functional": "vitest run src/__tests__/limitEvent.functional.test.ts src/__tests__/harnessParity.functional.test.ts src/__tests__/cursorParity.functional.test.ts src/__tests__/copilotCapture.functional.test.ts",
|
|
16
|
-
"test:harness-parity": "vitest run src/__tests__/harnessParity.functional.test.ts src/__tests__/cursorParity.functional.test.ts src/__tests__/copilotCapture.functional.test.ts",
|
|
15
|
+
"test:functional": "vitest run src/__tests__/limitEvent.functional.test.ts src/__tests__/harnessParity.functional.test.ts src/__tests__/cursorParity.functional.test.ts src/__tests__/copilotCapture.functional.test.ts src/__tests__/piCapture.functional.test.ts",
|
|
16
|
+
"test:harness-parity": "vitest run src/__tests__/harnessParity.functional.test.ts src/__tests__/cursorParity.functional.test.ts src/__tests__/copilotCapture.functional.test.ts src/__tests__/piCapture.functional.test.ts",
|
|
17
17
|
"test:harness-binaries": "node scripts/checkHarnessBinaries.mjs",
|
|
18
18
|
"test:watch": "vitest",
|
|
19
19
|
"test:live-sources": "npm run build && node scripts/liveSourceParity.mjs",
|
|
20
20
|
"test:ide-sources": "npm run build && node scripts/liveSourceParity.mjs --ide",
|
|
21
|
-
"test:metadata-parity": "vitest run src/__tests__/sourceMetadataMatrix.test.ts src/__tests__/copilotCapture.functional.test.ts src/__tests__/copilotCapture.test.ts src/__tests__/cursorParity.functional.test.ts src/__tests__/nativeSourceParity.test.ts src/__tests__/harnessParity.functional.test.ts src/__tests__/subagents.test.ts src/__tests__/transcriptModels.test.ts src/__tests__/captureRetry.test.ts src/__tests__/limitEvent.functional.test.ts src/__tests__/captureExclusions.test.ts"
|
|
21
|
+
"test:metadata-parity": "vitest run src/__tests__/sourceMetadataMatrix.test.ts src/__tests__/copilotCapture.functional.test.ts src/__tests__/copilotCapture.test.ts src/__tests__/piCapture.functional.test.ts src/__tests__/piHooks.test.ts src/__tests__/cursorParity.functional.test.ts src/__tests__/nativeSourceParity.test.ts src/__tests__/harnessParity.functional.test.ts src/__tests__/subagents.test.ts src/__tests__/transcriptModels.test.ts src/__tests__/captureRetry.test.ts src/__tests__/limitEvent.functional.test.ts src/__tests__/captureExclusions.test.ts"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|