codiedev 0.9.31 → 0.9.32

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.
@@ -7,8 +7,6 @@ import { execFileSync } from "node:child_process";
7
7
  import { existsSync } from "node:fs";
8
8
  import { dirname, isAbsolute } from "node:path";
9
9
 
10
- const skillUseBySession = new Map();
11
-
12
10
  /**
13
11
  * The directory the agent last WROTE to. Reading refs from the plugin's cwd is
14
12
  * only right when `codie` was launched where the work lands. A dev who launches
@@ -27,15 +25,61 @@ const skillUseBySession = new Map();
27
25
  * catch it.
28
26
  */
29
27
  export const workDirBySession = new Map();
30
- const WRITE_TOOLS = new Set(["write", "edit", "patch"]);
28
+ /**
29
+ * Tool ids as OpenCode actually registers them, captured from a live run — not
30
+ * read off the binary, which is what got this wrong twice. The patch tool is
31
+ * `apply_patch`; `patch` is kept only as a defensive alias.
32
+ */
33
+ const WRITE_TOOLS = new Set(["write", "edit", "apply_patch", "patch"]);
34
+
35
+ /**
36
+ * Which directory a tool call says the agent is working in, or null.
37
+ *
38
+ * Covering only `write`/`edit` was not enough. Two live `codie run` sessions
39
+ * were driven to create a file and the agent picked `bash` once and `patch`
40
+ * once — neither carries a `filePath` in its args, so both sessions reported
41
+ * the launch branch:
42
+ *
43
+ * write / edit args.filePath — known before the write
44
+ * apply_patch result.metadata.files[].filePath — only known AFTER it runs
45
+ *
46
+ * `bash` is deliberately NOT handled. Honouring its `workdir` re-anchors on
47
+ * READS too — `bash {command:"git log", workdir:"/repo"}` would move the
48
+ * anchor off a tracked worktree and back to the primary checkout, which
49
+ * passes the trust check because both share an origin. That breaks the rule
50
+ * this module is built on: writes are the signal, reads are not. Mining the
51
+ * command text was tried instead and did not work live. See NEXT_RELEASE.md.
52
+ *
53
+ * `patch` takes `patchText`, so its args never name a file. Shape confirmed
54
+ * against the opencode binary:
55
+ * metadata:{files:[{type:"update",filePath:…,relativePath:…,diff:…}]}
56
+ *
57
+ */
58
+ function workDirFromTool(tool, args, result) {
59
+ if (!WRITE_TOOLS.has(tool)) return null;
60
+ const candidates = [
61
+ args?.filePath,
62
+ args?.file_path,
63
+ ...(Array.isArray(result?.metadata?.files) ? result.metadata.files : []).flatMap((file) => [
64
+ file?.movePath, // a move reports the destination, which is where the work is
65
+ file?.filePath,
66
+ ]),
67
+ ];
68
+ // relativePath is deliberately excluded — it resolves against a cwd we may
69
+ // not share, so it is not a usable anchor.
70
+ const hit = candidates.find((path) => typeof path === "string" && isAbsolute(path));
71
+ return hit ? dirname(hit) : null;
72
+ }
31
73
 
32
- export function trackWorkPath(tool, args, sessionID, state = workDirBySession) {
33
- if (!WRITE_TOOLS.has(tool)) return;
34
- const filePath = args?.filePath ?? args?.file_path;
35
- // Relative paths are resolved against a cwd we may not share, so they are
36
- // not usable anchors.
37
- if (typeof filePath !== "string" || !isAbsolute(filePath)) return;
38
- state.set(sessionID, dirname(filePath));
74
+ export function trackWorkPath({
75
+ tool,
76
+ args,
77
+ sessionID,
78
+ result = null,
79
+ state = workDirBySession,
80
+ }) {
81
+ const dir = workDirFromTool(tool, args, result);
82
+ if (dir) state.set(sessionID, dir);
39
83
  }
40
84
 
41
85
  /**
@@ -93,7 +137,7 @@ function normalizeRemote(url) {
93
137
  const GIT_TTL_MS = 5_000;
94
138
  const gitCache = {};
95
139
 
96
- function clean(value, max) {
140
+ export function clean(value, max) {
97
141
  const text = typeof value === "string" ? value.trim() : "";
98
142
  return text ? text.slice(0, max) : "";
99
143
  }
@@ -4,6 +4,7 @@
4
4
  // names to inference requests handled by the company gateway.
5
5
 
6
6
  import {
7
+ clean,
7
8
  buildAttributionHeaders,
8
9
  resolveGitRefs,
9
10
  trackWorkPath,
@@ -34,10 +35,30 @@ export const CodiedevMetadata = async () => ({
34
35
  // Follow where the agent writes so refs come from the tree that becomes
35
36
  // the PR. Only the path's DIRECTORY is kept, and only to run git in it —
36
37
  // no path ever leaves this process.
37
- trackWorkPath(input.tool, output.args, input.sessionID);
38
+ //
39
+ // `write`/`edit` name the file up front; `bash` may carry a workdir. Both
40
+ // are worth catching here so the very next request already reports the
41
+ // right branch.
42
+ trackWorkPath({
43
+ tool: input.tool,
44
+ args: output.args,
45
+ sessionID: input.sessionID,
46
+ });
38
47
  if (input.tool !== "skill") return;
39
48
  addSkill(input.sessionID, output.args?.name || output.args?.skill || output.args?.key);
40
49
  },
50
+ "tool.execute.after": async (input, output) => {
51
+ // `patch` takes patchText, so the files it touched are only known once it
52
+ // has run — they arrive in the result's metadata.files[]. Without this
53
+ // hook a patch-driven session never moves off the launch branch, which is
54
+ // exactly what two live runs showed.
55
+ trackWorkPath({
56
+ tool: input.tool,
57
+ args: input.args,
58
+ sessionID: input.sessionID,
59
+ result: output,
60
+ });
61
+ },
41
62
  "chat.headers": async (input, output) => {
42
63
  Object.assign(
43
64
  output.headers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.9.31",
3
+ "version": "0.9.32",
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",