codiedev 0.9.69 → 0.9.71
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 +26 -8
- package/dist/limitEventReporter.d.ts +1 -1
- package/dist/limitEventReporter.js +1 -1
- package/dist/managedPi.d.ts +47 -0
- package/dist/managedPi.js +284 -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/selfUpdate.d.ts +7 -0
- package/dist/selfUpdate.js +39 -0
- package/dist/utils.d.ts +37 -2
- package/dist/utils.js +226 -4
- package/package.json +4 -4
package/dist/selfUpdate.js
CHANGED
|
@@ -48,6 +48,7 @@ exports.planSelfUpdate = planSelfUpdate;
|
|
|
48
48
|
exports.isSelfUpdateDue = isSelfUpdateDue;
|
|
49
49
|
exports.readSelfUpdateState = readSelfUpdateState;
|
|
50
50
|
exports.maybeSpawnSelfUpdate = maybeSpawnSelfUpdate;
|
|
51
|
+
exports.maybeRefreshInstructions = maybeRefreshInstructions;
|
|
51
52
|
exports.findInstalledHookScript = findInstalledHookScript;
|
|
52
53
|
exports.runSelfUpdate = runSelfUpdate;
|
|
53
54
|
const fs = __importStar(require("fs"));
|
|
@@ -58,6 +59,7 @@ const versionCheck_1 = require("./versionCheck");
|
|
|
58
59
|
const configDir_1 = require("./configDir");
|
|
59
60
|
const captureLog_1 = require("./captureLog");
|
|
60
61
|
const launcher_1 = require("./launcher");
|
|
62
|
+
const utils_1 = require("./utils");
|
|
61
63
|
const PKG = "codiedev";
|
|
62
64
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
63
65
|
const LOCK_STALE_MS = 10 * 60 * 1000;
|
|
@@ -161,6 +163,43 @@ function maybeSpawnSelfUpdate(hookEntry) {
|
|
|
161
163
|
// Best-effort; a failed spawn retries tomorrow.
|
|
162
164
|
}
|
|
163
165
|
}
|
|
166
|
+
function instructionsVersionPath() {
|
|
167
|
+
return path.join((0, configDir_1.codiedevStateDir)(), ".instructions-version");
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* First hook call on a new CLI version (self-updated or installed by hand)
|
|
171
|
+
* re-renders the codiedev block in the harness instruction files, so they
|
|
172
|
+
* stop listing commands this version no longer has. The steady-state cost is
|
|
173
|
+
* one small file read. Honors the same opt-outs as the self-update.
|
|
174
|
+
*/
|
|
175
|
+
function maybeRefreshInstructions(refresh = utils_1.refreshInstalledInstructions) {
|
|
176
|
+
if (process.env.CODIEDEV_NO_AUTO_UPDATE || process.env.CODIEDEV_NO_UPDATE_CHECK)
|
|
177
|
+
return;
|
|
178
|
+
try {
|
|
179
|
+
const marker = instructionsVersionPath();
|
|
180
|
+
try {
|
|
181
|
+
if (fs.readFileSync(marker, "utf8").trim() === version_1.CLI_VERSION)
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// No marker yet: first run on a version that ships this check.
|
|
186
|
+
}
|
|
187
|
+
const refreshed = refresh();
|
|
188
|
+
fs.mkdirSync((0, configDir_1.codiedevStateDir)(), { recursive: true });
|
|
189
|
+
fs.writeFileSync(marker, version_1.CLI_VERSION, "utf8");
|
|
190
|
+
if (refreshed.length > 0) {
|
|
191
|
+
(0, captureLog_1.appendCaptureLog)({
|
|
192
|
+
stage: "self-update",
|
|
193
|
+
sessionId: undefined,
|
|
194
|
+
outcome: "ok",
|
|
195
|
+
detail: `result=instructions-refreshed version=${version_1.CLI_VERSION} files=${refreshed.length}`,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
// Best-effort; retried on the next hook call.
|
|
201
|
+
}
|
|
202
|
+
}
|
|
164
203
|
function acquireLock() {
|
|
165
204
|
const lock = lockPath();
|
|
166
205
|
try {
|
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,8 +144,26 @@ 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;
|
|
154
|
+
export interface InstructionsTarget {
|
|
155
|
+
path: string;
|
|
156
|
+
body: string;
|
|
157
|
+
}
|
|
158
|
+
export declare function installedInstructionsTargets(): InstructionsTarget[];
|
|
159
|
+
/**
|
|
160
|
+
* Re-render the codiedev block in every instructions file that already has
|
|
161
|
+
* one, so an upgrade (background self-update or a manual `npm i`) stops
|
|
162
|
+
* teaching retired commands without the user re-running `connect`. Never
|
|
163
|
+
* creates a file or adds a block: a user who deleted it, or never connected
|
|
164
|
+
* that harness, is left alone. Returns the paths it rewrote.
|
|
165
|
+
*/
|
|
166
|
+
export declare function refreshInstalledInstructions(targets?: InstructionsTarget[]): string[];
|
|
132
167
|
/**
|
|
133
168
|
* Install the Codiedev MCP server into Claude Code's user-scope config.
|
|
134
169
|
* Safe to call multiple times — updates the existing entry if present.
|
|
@@ -199,7 +234,7 @@ export interface SteeringStats {
|
|
|
199
234
|
}
|
|
200
235
|
export declare function parseClaudeSteering(content: string): SteeringStats;
|
|
201
236
|
export declare function parseCodexSteering(content: string): SteeringStats;
|
|
202
|
-
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;
|
|
203
238
|
export interface UsageStats {
|
|
204
239
|
inputTokens: number;
|
|
205
240
|
outputTokens: number;
|
|
@@ -211,7 +246,7 @@ export interface UsageStats {
|
|
|
211
246
|
/** Engine-reported spend (opencode SDK `info.cost`). Desktop mode only. */
|
|
212
247
|
costUsd?: number;
|
|
213
248
|
}
|
|
214
|
-
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;
|
|
215
250
|
export declare function parseSessionStart(content: string): number | null;
|
|
216
251
|
export declare function parseClaudeCodeStats(content: string): ParsedStats;
|
|
217
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,8 +81,12 @@ 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;
|
|
88
|
+
exports.installedInstructionsTargets = installedInstructionsTargets;
|
|
89
|
+
exports.refreshInstalledInstructions = refreshInstalledInstructions;
|
|
82
90
|
exports.installClaudeCodeMcp = installClaudeCodeMcp;
|
|
83
91
|
exports.installCursorMcp = installCursorMcp;
|
|
84
92
|
exports.installVSCodeMcp = installVSCodeMcp;
|
|
@@ -138,6 +146,10 @@ const teamSkillContext_1 = require("./teamSkillContext");
|
|
|
138
146
|
const nativeSteering_1 = require("./nativeSteering");
|
|
139
147
|
const copilotHooks_1 = require("./copilotHooks");
|
|
140
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; } });
|
|
141
153
|
const CONFIG_DIR = (0, configDir_1.codiedevStateDir)();
|
|
142
154
|
exports.CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
143
155
|
function readConfig() {
|
|
@@ -1109,7 +1121,6 @@ function installOpenCodeCapturePlugin() {
|
|
|
1109
1121
|
`import os from "node:os";\n` +
|
|
1110
1122
|
`import path from "node:path";\n\n` +
|
|
1111
1123
|
`const START = ${JSON.stringify(start)};\n` +
|
|
1112
|
-
`const CAPTURE = ${JSON.stringify(capture)};\n` +
|
|
1113
1124
|
`const FINISH = ${JSON.stringify(finish)};\n` +
|
|
1114
1125
|
`const PROMPT = ${JSON.stringify(prompt)};\n` +
|
|
1115
1126
|
`// How this Codie process was started (run / serve / tui). OpenCode records\n` +
|
|
@@ -1201,6 +1212,167 @@ function installOpenCodeCapturePlugin() {
|
|
|
1201
1212
|
fs.writeFileSync(OPENCODE_PLUGIN_PATH, source, { encoding: "utf8", mode: 0o600 });
|
|
1202
1213
|
fs.chmodSync(OPENCODE_PLUGIN_PATH, 0o600);
|
|
1203
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
|
+
}
|
|
1204
1376
|
/**
|
|
1205
1377
|
* VS Code with the GitHub Copilot Chat extension installed. The extension
|
|
1206
1378
|
* directory pattern is `~/.vscode/extensions/github.copilot-chat-*`. We treat
|
|
@@ -1422,6 +1594,7 @@ function migrateHooksToLauncher() {
|
|
|
1422
1594
|
["cursor", CURSOR_HOOKS_PATH, installCursorHook],
|
|
1423
1595
|
["copilot", copilotHooksPath(), installCopilotHook],
|
|
1424
1596
|
["opencode", OPENCODE_PLUGIN_PATH, installOpenCodeCapturePlugin],
|
|
1597
|
+
["pi", (0, piHooks_1.piExtensionPath)(), installPiExtension],
|
|
1425
1598
|
];
|
|
1426
1599
|
const migrated = [];
|
|
1427
1600
|
for (const [name, file, install] of targets) {
|
|
@@ -1599,6 +1772,15 @@ function installCodexInstructions() {
|
|
|
1599
1772
|
function copilotInstructionsPath() {
|
|
1600
1773
|
return path.join((0, copilotHooks_1.copilotHome)(), "copilot-instructions.md");
|
|
1601
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
|
+
}
|
|
1602
1784
|
function installCopilotInstructions() {
|
|
1603
1785
|
writeInstructionsBlock(copilotInstructionsPath(), `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}`);
|
|
1604
1786
|
}
|
|
@@ -1645,6 +1827,46 @@ function installCursorInstructions() {
|
|
|
1645
1827
|
}
|
|
1646
1828
|
fs.writeFileSync(CURSOR_RULES_PATH, next, "utf8");
|
|
1647
1829
|
}
|
|
1830
|
+
function installedInstructionsTargets() {
|
|
1831
|
+
const withTeamContext = `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}`;
|
|
1832
|
+
return [
|
|
1833
|
+
{ path: CLAUDE_USER_INSTRUCTIONS_PATH, body: CODIEDEV_INSTRUCTIONS_BODY },
|
|
1834
|
+
{ path: CODEX_USER_INSTRUCTIONS_PATH, body: CODIEDEV_INSTRUCTIONS_BODY },
|
|
1835
|
+
{ path: copilotInstructionsPath(), body: withTeamContext },
|
|
1836
|
+
{ path: CURSOR_RULES_PATH, body: withTeamContext },
|
|
1837
|
+
{ path: piInstructionsPath(), body: withTeamContext },
|
|
1838
|
+
];
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Re-render the codiedev block in every instructions file that already has
|
|
1842
|
+
* one, so an upgrade (background self-update or a manual `npm i`) stops
|
|
1843
|
+
* teaching retired commands without the user re-running `connect`. Never
|
|
1844
|
+
* creates a file or adds a block: a user who deleted it, or never connected
|
|
1845
|
+
* that harness, is left alone. Returns the paths it rewrote.
|
|
1846
|
+
*/
|
|
1847
|
+
function refreshInstalledInstructions(targets = installedInstructionsTargets()) {
|
|
1848
|
+
const refreshed = [];
|
|
1849
|
+
for (const target of targets) {
|
|
1850
|
+
try {
|
|
1851
|
+
const existing = fs.readFileSync(target.path, "utf8");
|
|
1852
|
+
const beginIdx = existing.indexOf(CODIEDEV_INSTRUCTIONS_BEGIN);
|
|
1853
|
+
const endIdx = existing.indexOf(CODIEDEV_INSTRUCTIONS_END);
|
|
1854
|
+
if (beginIdx === -1 || endIdx === -1 || endIdx <= beginIdx)
|
|
1855
|
+
continue;
|
|
1856
|
+
const next = existing.slice(0, beginIdx) +
|
|
1857
|
+
`${CODIEDEV_INSTRUCTIONS_BEGIN}\n${target.body}\n${CODIEDEV_INSTRUCTIONS_END}` +
|
|
1858
|
+
existing.slice(endIdx + CODIEDEV_INSTRUCTIONS_END.length);
|
|
1859
|
+
if (next === existing)
|
|
1860
|
+
continue;
|
|
1861
|
+
fs.writeFileSync(target.path, next, "utf8");
|
|
1862
|
+
refreshed.push(target.path);
|
|
1863
|
+
}
|
|
1864
|
+
catch {
|
|
1865
|
+
// Missing or unreadable file: nothing of ours to refresh.
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return refreshed;
|
|
1869
|
+
}
|
|
1648
1870
|
function readMcpConfigSafely(path) {
|
|
1649
1871
|
if (!fs.existsSync(path))
|
|
1650
1872
|
return {};
|
|
@@ -2757,9 +2979,9 @@ exports.SWEEP_MIN_AGE_MS = exports.CAPTURE_THROTTLE_MS + 2 * 60 * 1000;
|
|
|
2757
2979
|
// to true would silently re-introduce the per-turn lease-close bug for every
|
|
2758
2980
|
// new agent.
|
|
2759
2981
|
function resolveCaptureFinal(argv2) {
|
|
2760
|
-
if (argv2 === "capture-opencode" || argv2 === "capture-desktop")
|
|
2982
|
+
if (argv2 === "capture-opencode" || argv2 === "capture-desktop" || argv2 === "capture-pi")
|
|
2761
2983
|
return false;
|
|
2762
|
-
if (argv2 === "capture-opencode-final" || argv2 === "capture-desktop-final")
|
|
2984
|
+
if (argv2 === "capture-opencode-final" || argv2 === "capture-desktop-final" || argv2 === "capture-pi-final")
|
|
2763
2985
|
return true;
|
|
2764
2986
|
return argv2 === "capture" || argv2 === "capture-cursor" || argv2 === "capture-copilot" || argv2 === undefined
|
|
2765
2987
|
? true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.71",
|
|
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",
|