codiedev 0.9.59 → 0.9.61

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/utils.js CHANGED
@@ -53,6 +53,7 @@ exports.parseAuthoredBranches = parseAuthoredBranches;
53
53
  exports.parseAuthoredShas = parseAuthoredShas;
54
54
  exports.getGitBranch = getGitBranch;
55
55
  exports.getGitHeadSha = getGitHeadSha;
56
+ exports.stripRepoUrlCredentials = stripRepoUrlCredentials;
56
57
  exports.buildCapturePayload = buildCapturePayload;
57
58
  exports.hashToken = hashToken;
58
59
  exports.extractHookNodePath = extractHookNodePath;
@@ -60,6 +61,7 @@ exports.resetStableNodeCacheForTests = resetStableNodeCacheForTests;
60
61
  exports.claudeCodeInstalled = claudeCodeInstalled;
61
62
  exports.codexInstalled = codexInstalled;
62
63
  exports.cursorInstalled = cursorInstalled;
64
+ exports.copilotInstalled = copilotInstalled;
63
65
  exports.openCodeInstalled = openCodeInstalled;
64
66
  exports.installOpenCodeCapturePlugin = installOpenCodeCapturePlugin;
65
67
  exports.vscodeCopilotInstalled = vscodeCopilotInstalled;
@@ -72,6 +74,8 @@ exports.ensureAllCodiedevHooksInstalled = ensureAllCodiedevHooksInstalled;
72
74
  exports.ensureSessionStartHookInstalled = ensureSessionStartHookInstalled;
73
75
  exports.installClaudeCodeInstructions = installClaudeCodeInstructions;
74
76
  exports.installCodexInstructions = installCodexInstructions;
77
+ exports.copilotInstructionsPath = copilotInstructionsPath;
78
+ exports.installCopilotInstructions = installCopilotInstructions;
75
79
  exports.installCursorInstructions = installCursorInstructions;
76
80
  exports.installClaudeCodeMcp = installClaudeCodeMcp;
77
81
  exports.installCursorMcp = installCursorMcp;
@@ -84,6 +88,9 @@ exports.installCodexMcp = installCodexMcp;
84
88
  exports.installCodexHook = installCodexHook;
85
89
  exports.ensureCodexLimitHookInstalled = ensureCodexLimitHookInstalled;
86
90
  exports.installCursorHook = installCursorHook;
91
+ exports.copilotHooksPath = copilotHooksPath;
92
+ exports.installCopilotHook = installCopilotHook;
93
+ exports.ensureCopilotCodiedevHooksInstalled = ensureCopilotCodiedevHooksInstalled;
87
94
  exports.ensureCursorCodiedevHooksInstalled = ensureCursorCodiedevHooksInstalled;
88
95
  exports.parseClaudeSteering = parseClaudeSteering;
89
96
  exports.parseCodexSteering = parseCodexSteering;
@@ -114,6 +121,7 @@ exports.runDeferredUpload = runDeferredUpload;
114
121
  exports.transcriptReadSkipReason = transcriptReadSkipReason;
115
122
  exports.shouldReportCaptureSkip = shouldReportCaptureSkip;
116
123
  const fs = __importStar(require("fs"));
124
+ const transcriptModels_1 = require("./transcriptModels");
117
125
  const path = __importStar(require("path"));
118
126
  const os = __importStar(require("os"));
119
127
  const child_process_1 = require("child_process");
@@ -122,6 +130,9 @@ const captureLog_1 = require("./captureLog");
122
130
  const configDir_1 = require("./configDir");
123
131
  Object.defineProperty(exports, "codiedevStateDir", { enumerable: true, get: function () { return configDir_1.codiedevStateDir; } });
124
132
  const codexHookTrust_1 = require("./codexHookTrust");
133
+ const teamSkillContext_1 = require("./teamSkillContext");
134
+ const nativeSteering_1 = require("./nativeSteering");
135
+ const copilotHooks_1 = require("./copilotHooks");
125
136
  const CONFIG_DIR = (0, configDir_1.codiedevStateDir)();
126
137
  exports.CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
127
138
  function readConfig() {
@@ -824,10 +835,13 @@ function getGitHeadSha(cwd) {
824
835
  return null;
825
836
  }
826
837
  }
838
+ function stripRepoUrlCredentials(url) {
839
+ return url.replace(/^(\s*[a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/?#]*@/, "$1");
840
+ }
827
841
  function buildCapturePayload(input) {
828
842
  return {
829
843
  sessionId: input.sessionId,
830
- repoUrl: input.remoteUrl ?? "",
844
+ repoUrl: stripRepoUrlCredentials(input.remoteUrl ?? ""),
831
845
  repoId: input.matchedRepoId,
832
846
  companyId: input.companyId,
833
847
  branch: input.branch ?? "",
@@ -836,7 +850,7 @@ function buildCapturePayload(input) {
836
850
  // see no change; newer servers index the session under every key.
837
851
  branches: input.branches && input.branches.length ? input.branches : undefined,
838
852
  headShas: input.headShas && input.headShas.length ? input.headShas : undefined,
839
- gitRefs: input.gitRefs && input.gitRefs.length ? input.gitRefs : undefined,
853
+ gitRefs: input.gitRefs && input.gitRefs.length ? input.gitRefs.map(ref => ({ ...ref, repoUrl: stripRepoUrlCredentials(ref.repoUrl) })) : undefined,
840
854
  pullRequestUrls: input.pullRequestUrls && input.pullRequestUrls.length
841
855
  ? input.pullRequestUrls
842
856
  : undefined,
@@ -1044,6 +1058,24 @@ function codexInstalled() {
1044
1058
  function cursorInstalled() {
1045
1059
  return fs.existsSync(CURSOR_DIR);
1046
1060
  }
1061
+ /**
1062
+ * GitHub Copilot: `$COPILOT_HOME` / ~/.copilot (either surface has run once),
1063
+ * the `copilot` CLI on PATH, or VS Code's Copilot Chat extension. VS Code's
1064
+ * agent and the CLI read the same user hooks, so one install covers both.
1065
+ */
1066
+ function copilotInstalled() {
1067
+ if (fs.existsSync((0, copilotHooks_1.copilotHome)()))
1068
+ return true;
1069
+ if (vscodeCopilotInstalled())
1070
+ return true;
1071
+ try {
1072
+ (0, child_process_1.execSync)(process.platform === "win32" ? "where copilot" : "command -v copilot", { stdio: ["pipe", "pipe", "pipe"] });
1073
+ return true;
1074
+ }
1075
+ catch {
1076
+ return false;
1077
+ }
1078
+ }
1047
1079
  function openCodeInstalled() {
1048
1080
  if (fs.existsSync(OPENCODE_DIR))
1049
1081
  return true;
@@ -1072,6 +1104,9 @@ function installOpenCodeCapturePlugin() {
1072
1104
  `const CAPTURE = ${JSON.stringify(capture)};\n` +
1073
1105
  `const FINISH = ${JSON.stringify(finish)};\n` +
1074
1106
  `const PROMPT = ${JSON.stringify(prompt)};\n` +
1107
+ `// How this Codie process was started (run / serve / tui). OpenCode records\n` +
1108
+ `// nothing about it, so the plugin reads its own argv; see launchContext.ts.\n` +
1109
+ `const LAUNCH = (() => { const sub = process.argv.slice(2).find((a) => ["run", "serve", "acp", "web", "attach"].includes(a)); return sub ? "codie:" + sub : "codie:tui"; })();\n` +
1075
1110
  `const fingerprints = new Map();\n\n` +
1076
1111
  `const transcriptPaths = new Map();\n\n` +
1077
1112
  `function sessionId(event) {\n` +
@@ -1098,7 +1133,7 @@ function installOpenCodeCapturePlugin() {
1098
1133
  ` const finalizeAll = () => {\n` +
1099
1134
  ` for (const [id, transcriptPath] of transcriptPaths) {\n` +
1100
1135
  ` const cwd = worktree || directory || process.cwd();\n` +
1101
- ` dispatchSync(FINISH, { session_id: id, transcript_path: transcriptPath, cwd, workspace_roots: [cwd] });\n` +
1136
+ ` dispatchSync(FINISH, { session_id: id, transcript_path: transcriptPath, cwd, workspace_roots: [cwd], launch: LAUNCH });\n` +
1102
1137
  ` }\n` +
1103
1138
  ` };\n` +
1104
1139
  ` process.once("exit", finalizeAll);\n` +
@@ -1116,12 +1151,12 @@ function installOpenCodeCapturePlugin() {
1116
1151
  ` if (!id) return;\n` +
1117
1152
  ` const cwd = worktree || directory || process.cwd();\n` +
1118
1153
  ` if (event.type === "session.created") {\n` +
1119
- ` dispatch(START, { session_id: id, cwd, workspace_roots: [cwd] });\n` +
1154
+ ` dispatch(START, { session_id: id, cwd, workspace_roots: [cwd], launch: LAUNCH });\n` +
1120
1155
  ` return;\n` +
1121
1156
  ` }\n` +
1122
1157
  ` if (event.type === "session.deleted") {\n` +
1123
1158
  ` const transcriptPath = transcriptPaths.get(id);\n` +
1124
- ` if (transcriptPath) dispatch(FINISH, { session_id: id, transcript_path: transcriptPath, cwd, workspace_roots: [cwd] });\n` +
1159
+ ` if (transcriptPath) dispatch(FINISH, { session_id: id, transcript_path: transcriptPath, cwd, workspace_roots: [cwd], launch: LAUNCH });\n` +
1125
1160
  ` transcriptPaths.delete(id);\n` +
1126
1161
  ` fingerprints.delete(id);\n` +
1127
1162
  ` return;\n` +
@@ -1141,7 +1176,7 @@ function installOpenCodeCapturePlugin() {
1141
1176
  ` const transcriptPath = path.join(captureDir, safeId + ".json");\n` +
1142
1177
  ` await writeFile(transcriptPath, JSON.stringify(list), { mode: 0o600 });\n` +
1143
1178
  ` transcriptPaths.set(id, transcriptPath);\n` +
1144
- ` dispatch(CAPTURE, { session_id: id, transcript_path: transcriptPath, cwd, workspace_roots: [cwd] });\n` +
1179
+ ` dispatch(CAPTURE, { session_id: id, transcript_path: transcriptPath, cwd, workspace_roots: [cwd], launch: LAUNCH });\n` +
1145
1180
  ` } catch {}\n` +
1146
1181
  ` },\n` +
1147
1182
  ` });\n` +
@@ -1472,13 +1507,13 @@ Tell the user the portal URL when the submit returns.
1472
1507
  * (no per-repo CLAUDE.md needed). Idempotent — replaces the existing block
1473
1508
  * between its begin/end markers on re-run.
1474
1509
  */
1475
- function writeInstructionsBlock(targetPath) {
1510
+ function writeInstructionsBlock(targetPath, body = CODIEDEV_INSTRUCTIONS_BODY) {
1476
1511
  const dir = path.dirname(targetPath);
1477
1512
  if (!fs.existsSync(dir)) {
1478
1513
  fs.mkdirSync(dir, { recursive: true });
1479
1514
  }
1480
1515
  const block = `${CODIEDEV_INSTRUCTIONS_BEGIN}\n` +
1481
- CODIEDEV_INSTRUCTIONS_BODY +
1516
+ body +
1482
1517
  `\n${CODIEDEV_INSTRUCTIONS_END}\n`;
1483
1518
  let existing = "";
1484
1519
  if (fs.existsSync(targetPath)) {
@@ -1509,6 +1544,12 @@ function installClaudeCodeInstructions() {
1509
1544
  function installCodexInstructions() {
1510
1545
  writeInstructionsBlock(CODEX_USER_INSTRUCTIONS_PATH);
1511
1546
  }
1547
+ function copilotInstructionsPath() {
1548
+ return path.join((0, copilotHooks_1.copilotHome)(), "copilot-instructions.md");
1549
+ }
1550
+ function installCopilotInstructions() {
1551
+ writeInstructionsBlock(copilotInstructionsPath(), `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}`);
1552
+ }
1512
1553
  // Cursor uses .mdc rule files with YAML frontmatter (description, alwaysApply,
1513
1554
  // globs). `.cursorrules` is deprecated and ignored by Agent mode as of 2026.
1514
1555
  const CURSOR_INSTRUCTIONS_FRONTMATTER = `---
@@ -1524,7 +1565,7 @@ function installCursorInstructions() {
1524
1565
  const block = CURSOR_INSTRUCTIONS_FRONTMATTER +
1525
1566
  CODIEDEV_INSTRUCTIONS_BEGIN +
1526
1567
  "\n" +
1527
- CODIEDEV_INSTRUCTIONS_BODY +
1568
+ `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}` +
1528
1569
  "\n" +
1529
1570
  CODIEDEV_INSTRUCTIONS_END +
1530
1571
  "\n";
@@ -1542,7 +1583,7 @@ function installCursorInstructions() {
1542
1583
  existing.slice(0, beginIdx) +
1543
1584
  CODIEDEV_INSTRUCTIONS_BEGIN +
1544
1585
  "\n" +
1545
- CODIEDEV_INSTRUCTIONS_BODY +
1586
+ `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}` +
1546
1587
  "\n" +
1547
1588
  CODIEDEV_INSTRUCTIONS_END +
1548
1589
  existing.slice(endIdx + CODIEDEV_INSTRUCTIONS_END.length);
@@ -1901,12 +1942,93 @@ function installCursorHook() {
1901
1942
  replaceManaged("sessionStart", ["session-start-cursor"]);
1902
1943
  replaceManaged("sessionEnd", ["capture-cursor"]);
1903
1944
  replaceManaged("stop", ["backup-cursor"]);
1945
+ for (const event of ["postToolUse", "postToolUseFailure", "subagentStart", "subagentStop"])
1946
+ replaceManaged(event, ["observe-cursor"]);
1904
1947
  // Cursor cannot inject dynamic context from beforeSubmitPrompt today.
1905
1948
  // Remove older Codiedev discovery entries so prompts are not uploaded for
1906
1949
  // a response the host will discard; preserve any third-party hooks.
1907
1950
  replaceManaged("beforeSubmitPrompt", []);
1908
1951
  fs.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
1909
1952
  }
1953
+ // GitHub Copilot reads every `$COPILOT_HOME/hooks/*.json`, so Codiedev owns
1954
+ // one file and never edits anyone else's. camelCase events only: VS Code also
1955
+ // accepts Claude-style PascalCase names from the same file, and configuring
1956
+ // both would fire every hook twice.
1957
+ function copilotHooksPath() {
1958
+ return path.join((0, copilotHooks_1.copilotHome)(), "hooks", "codiedev.json");
1959
+ }
1960
+ function copilotManagedHooks(agentSecurityEnabled) {
1961
+ return [
1962
+ ["sessionStart", "session-start-copilot", 10],
1963
+ ["userPromptSubmitted", "discover-copilot", 10],
1964
+ // agentStop = end of a turn: a non-final snapshot. VS Code sessions live
1965
+ // until the chat is closed, so this is what keeps them captured.
1966
+ ["agentStop", "backup-copilot", 30],
1967
+ ["sessionEnd", "capture-copilot", 30],
1968
+ ["errorOccurred", "limit-copilot", 10],
1969
+ ];
1970
+ }
1971
+ const COPILOT_EVENTS = ["sessionStart", "userPromptSubmitted", "agentStop", "sessionEnd", "preToolUse", "postToolUse", "errorOccurred"];
1972
+ function installCopilotHook(agentSecurityEnabled = false) {
1973
+ const hooksPath = copilotHooksPath();
1974
+ let hooksFile = {};
1975
+ try {
1976
+ if (fs.existsSync(hooksPath)) {
1977
+ const raw = fs.readFileSync(hooksPath, "utf8");
1978
+ hooksFile = raw.trim() ? JSON.parse(raw) : {};
1979
+ }
1980
+ }
1981
+ catch {
1982
+ hooksFile = {};
1983
+ }
1984
+ hooksFile.version = 1;
1985
+ const hooks = (hooksFile.hooks && typeof hooksFile.hooks === "object" ? hooksFile.hooks : {});
1986
+ const managed = copilotManagedHooks(agentSecurityEnabled);
1987
+ for (const eventName of COPILOT_EVENTS) {
1988
+ const existing = Array.isArray(hooks[eventName]) ? hooks[eventName] : [];
1989
+ // Anything a user added to this file by hand stays.
1990
+ const preserved = existing.filter((hook) => !(0, detection_1.isCodiedevHookCommand)((hook.bash ?? hook.powershell ?? hook.command)));
1991
+ const ours = managed
1992
+ .filter(([event]) => event === eventName)
1993
+ .map(([, subcommand, timeoutSec]) => {
1994
+ const command = resolveHookCommand(subcommand);
1995
+ return { type: "command", bash: command, powershell: command, timeoutSec };
1996
+ });
1997
+ if (preserved.length + ours.length === 0)
1998
+ delete hooks[eventName];
1999
+ else
2000
+ hooks[eventName] = [...preserved, ...ours];
2001
+ }
2002
+ hooksFile.hooks = hooks;
2003
+ fs.mkdirSync(path.dirname(hooksPath), { recursive: true });
2004
+ fs.writeFileSync(hooksPath, JSON.stringify(hooksFile, null, 2), "utf8");
2005
+ }
2006
+ /** Self-heal the Copilot hook file after upgrades or drift (like Cursor's). */
2007
+ function ensureCopilotCodiedevHooksInstalled() {
2008
+ const agentSecurityEnabled = false; // production line: no security hooks
2009
+ try {
2010
+ const hooksPath = copilotHooksPath();
2011
+ if (!fs.existsSync(hooksPath))
2012
+ return;
2013
+ const parsed = JSON.parse(fs.readFileSync(hooksPath, "utf8"));
2014
+ const hooks = parsed.hooks ?? {};
2015
+ const commandsFor = (eventName) => (Array.isArray(hooks[eventName]) ? hooks[eventName] : [])
2016
+ .map((entry) => (entry.bash ?? entry.powershell ?? entry.command))
2017
+ .filter((command) => typeof command === "string" && (0, detection_1.isCodiedevHookCommand)(command));
2018
+ const required = copilotManagedHooks(agentSecurityEnabled);
2019
+ const complete = required.every(([eventName, subcommand]) => commandsFor(eventName).filter((command) => command.trim().endsWith(subcommand)).length === 1);
2020
+ const stale = COPILOT_EVENTS.some((eventName) => commandsFor(eventName).length !== required.filter(([event]) => event === eventName).length);
2021
+ if (complete && !stale)
2022
+ return;
2023
+ installCopilotHook(agentSecurityEnabled);
2024
+ }
2025
+ catch {
2026
+ // Best-effort — a malformed hook file must never block capture.
2027
+ }
2028
+ }
2029
+ /** Repair Cursor's complete flat hook set after upgrades or configuration
2030
+ * drift. This is deliberately separate from Claude Code's nested-hook healer:
2031
+ * the schemas and event names differ, and mixing them risks corrupting both. */
1910
2032
  function ensureCursorCodiedevHooksInstalled() {
1911
2033
  try {
1912
2034
  if (!fs.existsSync(CURSOR_HOOKS_PATH))
@@ -1917,6 +2039,10 @@ function ensureCursorCodiedevHooksInstalled() {
1917
2039
  ["sessionStart", "session-start-cursor"],
1918
2040
  ["sessionEnd", "capture-cursor"],
1919
2041
  ["stop", "backup-cursor"],
2042
+ ["postToolUse", "observe-cursor"],
2043
+ ["postToolUseFailure", "observe-cursor"],
2044
+ ["subagentStart", "observe-cursor"],
2045
+ ["subagentStop", "observe-cursor"],
1920
2046
  ];
1921
2047
  const has = (eventName, subcommand) => {
1922
2048
  const entries = hooks[eventName];
@@ -1978,46 +2104,27 @@ function parseCodexSteering(content) {
1978
2104
  return { interrupts, rejections };
1979
2105
  }
1980
2106
  function parseSteering(content, mode) {
2107
+ if (mode === "cursor" || mode === "copilot")
2108
+ return (0, nativeSteering_1.parseNativeSteering)(content);
1981
2109
  return mode === "codex"
1982
2110
  ? parseCodexSteering(content)
1983
2111
  : parseClaudeSteering(content);
1984
2112
  }
1985
2113
  function parseClaudeUsage(content) {
1986
- // Claude Code logs per-turn usage; sum across the session. `input_tokens` is
1987
- // already fresh (cache read/creation are reported separately).
1988
- let inputTokens = 0;
1989
- let outputTokens = 0;
1990
- let cacheReadTokens = 0;
1991
- let cacheCreationTokens = 0;
2114
+ const models = (0, transcriptModels_1.parseTranscriptModels)(content, "claude-code");
1992
2115
  let model = null;
1993
2116
  for (const line of content.split("\n")) {
1994
- if (!line.trim())
1995
- continue;
1996
2117
  try {
1997
- const e = JSON.parse(line);
1998
- const m = e?.message;
1999
- const u = m?.usage;
2000
- if (u) {
2001
- inputTokens += u.input_tokens ?? 0;
2002
- outputTokens += u.output_tokens ?? 0;
2003
- cacheReadTokens += u.cache_read_input_tokens ?? 0;
2004
- cacheCreationTokens += u.cache_creation_input_tokens ?? 0;
2005
- }
2006
- if (typeof m?.model === "string" && m.model)
2007
- model = m.model;
2008
- }
2009
- catch {
2010
- // skip malformed
2118
+ const value = JSON.parse(line)?.message?.model;
2119
+ if (typeof value === "string" && value.trim() && !value.startsWith("<"))
2120
+ model = value;
2011
2121
  }
2122
+ catch { /* partial records */ }
2012
2123
  }
2013
- return {
2014
- inputTokens,
2015
- outputTokens,
2016
- cacheTokens: cacheReadTokens + cacheCreationTokens,
2017
- cacheReadTokens,
2018
- cacheCreationTokens,
2019
- model,
2020
- };
2124
+ const sum = (field) => models.reduce((total, usage) => total + usage[field], 0);
2125
+ const cacheReadTokens = sum("cacheReadTokens"), cacheCreationTokens = sum("cacheCreationTokens");
2126
+ return { inputTokens: sum("inputTokens"), outputTokens: sum("outputTokens"),
2127
+ cacheTokens: cacheReadTokens + cacheCreationTokens, cacheReadTokens, cacheCreationTokens, model };
2021
2128
  }
2022
2129
  function parseCodexUsage(content) {
2023
2130
  // Codex reports a CUMULATIVE token_count per turn — the last one is the
@@ -2151,20 +2258,30 @@ function parseSessionStart(content) {
2151
2258
  function parseClaudeCodeStats(content) {
2152
2259
  let messageCount = 0;
2153
2260
  let toolCallCount = 0;
2261
+ const messages = new Set();
2262
+ const tools = new Set();
2154
2263
  const lines = content.split("\n").filter((l) => l.trim());
2155
2264
  for (const line of lines) {
2156
2265
  try {
2157
2266
  const entry = JSON.parse(line);
2158
2267
  if (!entry || typeof entry !== "object")
2159
2268
  continue;
2160
- if (entry.type === "user" || entry.type === "assistant") {
2269
+ const id = entry.message?.id ?? entry.uuid;
2270
+ const messageKey = `${entry.type}:${id ?? messages.size}`;
2271
+ if ((entry.type === "user" || entry.type === "assistant") && !messages.has(messageKey)) {
2272
+ messages.add(messageKey);
2161
2273
  messageCount++;
2162
2274
  }
2163
2275
  const c = entry.message?.content;
2164
2276
  if (Array.isArray(c)) {
2165
- for (const block of c) {
2166
- if (block && block.type === "tool_use")
2277
+ for (const [index, block] of c.entries()) {
2278
+ if (!block || block.type !== "tool_use")
2279
+ continue;
2280
+ const toolKey = block.id ?? `${messageKey}:${index}`;
2281
+ if (!tools.has(toolKey)) {
2282
+ tools.add(toolKey);
2167
2283
  toolCallCount++;
2284
+ }
2168
2285
  }
2169
2286
  }
2170
2287
  }
@@ -2212,14 +2329,14 @@ function normalizeOpenCodeTranscript(content) {
2212
2329
  else if (part.type === "tool") {
2213
2330
  normalizedParts.push({
2214
2331
  type: "tool_use",
2215
- id: part.callID ?? part.id ?? "tool",
2332
+ id: part.callID ?? part.id,
2216
2333
  name: part.tool ?? "tool",
2217
2334
  input: part.state?.input ?? {},
2218
2335
  });
2219
2336
  if (part.state?.output != null || part.state?.error != null) {
2220
2337
  normalizedParts.push({
2221
2338
  type: "tool_result",
2222
- tool_use_id: part.callID ?? part.id ?? "tool",
2339
+ tool_use_id: part.callID ?? part.id,
2223
2340
  content: part.state?.output ?? part.state?.error,
2224
2341
  is_error: part.state?.status === "error" || part.state?.error != null,
2225
2342
  });
@@ -2235,6 +2352,7 @@ function normalizeOpenCodeTranscript(content) {
2235
2352
  type: role,
2236
2353
  timestamp,
2237
2354
  message: {
2355
+ id: typeof info.id === "string" ? info.id : undefined,
2238
2356
  role,
2239
2357
  model: typeof info.modelID === "string" ? info.modelID : undefined,
2240
2358
  usage: role === "assistant" ? {
@@ -2565,7 +2683,7 @@ function resolveCaptureFinal(argv2) {
2565
2683
  return false;
2566
2684
  if (argv2 === "capture-opencode-final" || argv2 === "capture-desktop-final")
2567
2685
  return true;
2568
- return argv2 === "capture" || argv2 === "capture-cursor" || argv2 === undefined
2686
+ return argv2 === "capture" || argv2 === "capture-cursor" || argv2 === "capture-copilot" || argv2 === undefined
2569
2687
  ? true
2570
2688
  : undefined;
2571
2689
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.9.59",
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",
3
+ "version": "0.9.61",
4
+ "description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration \u2014 and run the governed Codie harness",
5
5
  "bin": {
6
6
  "codiedev": "dist/cli.js",
7
7
  "codiedev-hook": "dist/hook.js",
@@ -12,10 +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",
16
- "test:harness-parity": "vitest run src/__tests__/harnessParity.functional.test.ts src/__tests__/cursorParity.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",
16
+ "test:harness-parity": "vitest run src/__tests__/harnessParity.functional.test.ts src/__tests__/cursorParity.functional.test.ts src/__tests__/copilotCapture.functional.test.ts",
17
17
  "test:harness-binaries": "node scripts/checkHarnessBinaries.mjs",
18
- "test:watch": "vitest"
18
+ "test:watch": "vitest",
19
+ "test:live-sources": "npm run build && node scripts/liveSourceParity.mjs",
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"
19
22
  },
20
23
  "files": [
21
24
  "dist",