@codemieai/code 0.12.0 → 0.13.0

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.
Files changed (204) hide show
  1. package/README.md +109 -56
  2. package/bin/codemie-copilot.js +224 -0
  3. package/bin/codemie-pi.js +18 -0
  4. package/dist/agents/core/AgentCLI.d.ts.map +1 -1
  5. package/dist/agents/core/AgentCLI.js +16 -10
  6. package/dist/agents/core/AgentCLI.js.map +1 -1
  7. package/dist/agents/core/BaseAgentAdapter.d.ts.map +1 -1
  8. package/dist/agents/core/BaseAgentAdapter.js +43 -19
  9. package/dist/agents/core/BaseAgentAdapter.js.map +1 -1
  10. package/dist/agents/core/agent-aliases.d.ts +17 -0
  11. package/dist/agents/core/agent-aliases.d.ts.map +1 -0
  12. package/dist/agents/core/agent-aliases.js +41 -0
  13. package/dist/agents/core/agent-aliases.js.map +1 -0
  14. package/dist/agents/core/session/session-origin-audit.d.ts +13 -0
  15. package/dist/agents/core/session/session-origin-audit.d.ts.map +1 -1
  16. package/dist/agents/core/session/session-origin-audit.js +34 -0
  17. package/dist/agents/core/session/session-origin-audit.js.map +1 -1
  18. package/dist/agents/core/session/stale-session-reconciliation.d.ts +42 -0
  19. package/dist/agents/core/session/stale-session-reconciliation.d.ts.map +1 -0
  20. package/dist/agents/core/session/stale-session-reconciliation.js +141 -0
  21. package/dist/agents/core/session/stale-session-reconciliation.js.map +1 -0
  22. package/dist/agents/core/session/types.d.ts +19 -0
  23. package/dist/agents/core/session/types.d.ts.map +1 -1
  24. package/dist/agents/core/session/types.js +17 -1
  25. package/dist/agents/core/session/types.js.map +1 -1
  26. package/dist/agents/core/types.d.ts +1 -0
  27. package/dist/agents/core/types.d.ts.map +1 -1
  28. package/dist/agents/plugins/codex/codex.reconciliation.d.ts +15 -35
  29. package/dist/agents/plugins/codex/codex.reconciliation.d.ts.map +1 -1
  30. package/dist/agents/plugins/codex/codex.reconciliation.js +8 -139
  31. package/dist/agents/plugins/codex/codex.reconciliation.js.map +1 -1
  32. package/dist/agents/plugins/copilot-cli/copilot-cli.constants.d.ts +4 -0
  33. package/dist/agents/plugins/copilot-cli/copilot-cli.constants.d.ts.map +1 -1
  34. package/dist/agents/plugins/copilot-cli/copilot-cli.constants.js +4 -0
  35. package/dist/agents/plugins/copilot-cli/copilot-cli.constants.js.map +1 -1
  36. package/dist/agents/plugins/copilot-cli/copilot-cli.models.d.ts +8 -0
  37. package/dist/agents/plugins/copilot-cli/copilot-cli.models.d.ts.map +1 -0
  38. package/dist/agents/plugins/copilot-cli/copilot-cli.models.js +195 -0
  39. package/dist/agents/plugins/copilot-cli/copilot-cli.models.js.map +1 -0
  40. package/dist/agents/plugins/copilot-cli/copilot-cli.plugin.d.ts +4 -20
  41. package/dist/agents/plugins/copilot-cli/copilot-cli.plugin.d.ts.map +1 -1
  42. package/dist/agents/plugins/copilot-cli/copilot-cli.plugin.js +237 -38
  43. package/dist/agents/plugins/copilot-cli/copilot-cli.plugin.js.map +1 -1
  44. package/dist/agents/plugins/copilot-cli/copilot-cli.session.d.ts.map +1 -1
  45. package/dist/agents/plugins/copilot-cli/copilot-cli.session.js +14 -2
  46. package/dist/agents/plugins/copilot-cli/copilot-cli.session.js.map +1 -1
  47. package/dist/agents/plugins/copilot-cli/index.d.ts +2 -1
  48. package/dist/agents/plugins/copilot-cli/index.d.ts.map +1 -1
  49. package/dist/agents/plugins/copilot-cli/index.js +2 -1
  50. package/dist/agents/plugins/copilot-cli/index.js.map +1 -1
  51. package/dist/agents/plugins/copilot-cli/session/processors/copilot-cli.conversations-processor.d.ts +6 -3
  52. package/dist/agents/plugins/copilot-cli/session/processors/copilot-cli.conversations-processor.d.ts.map +1 -1
  53. package/dist/agents/plugins/copilot-cli/session/processors/copilot-cli.conversations-processor.js +298 -5
  54. package/dist/agents/plugins/copilot-cli/session/processors/copilot-cli.conversations-processor.js.map +1 -1
  55. package/dist/agents/plugins/pi/__tests__/home-redirect.d.ts +12 -0
  56. package/dist/agents/plugins/pi/__tests__/home-redirect.d.ts.map +1 -0
  57. package/dist/agents/plugins/pi/__tests__/home-redirect.js +27 -0
  58. package/dist/agents/plugins/pi/__tests__/home-redirect.js.map +1 -0
  59. package/dist/agents/plugins/pi/extension/README.md +68 -0
  60. package/dist/agents/plugins/pi/extension/index.js +161 -0
  61. package/dist/agents/plugins/pi/extension/package.json +8 -0
  62. package/dist/agents/plugins/pi/index.d.ts +3 -0
  63. package/dist/agents/plugins/pi/index.d.ts.map +1 -0
  64. package/dist/agents/plugins/pi/index.js +3 -0
  65. package/dist/agents/plugins/pi/index.js.map +1 -0
  66. package/dist/agents/plugins/pi/pi.extension.d.ts +60 -0
  67. package/dist/agents/plugins/pi/pi.extension.d.ts.map +1 -0
  68. package/dist/agents/plugins/pi/pi.extension.js +201 -0
  69. package/dist/agents/plugins/pi/pi.extension.js.map +1 -0
  70. package/dist/agents/plugins/pi/pi.incremental-sync.d.ts +63 -0
  71. package/dist/agents/plugins/pi/pi.incremental-sync.d.ts.map +1 -0
  72. package/dist/agents/plugins/pi/pi.incremental-sync.js +266 -0
  73. package/dist/agents/plugins/pi/pi.incremental-sync.js.map +1 -0
  74. package/dist/agents/plugins/pi/pi.models.d.ts +20 -0
  75. package/dist/agents/plugins/pi/pi.models.d.ts.map +1 -0
  76. package/dist/agents/plugins/pi/pi.models.js +201 -0
  77. package/dist/agents/plugins/pi/pi.models.js.map +1 -0
  78. package/dist/agents/plugins/pi/pi.packages.d.ts +11 -0
  79. package/dist/agents/plugins/pi/pi.packages.d.ts.map +1 -0
  80. package/dist/agents/plugins/pi/pi.packages.js +35 -0
  81. package/dist/agents/plugins/pi/pi.packages.js.map +1 -0
  82. package/dist/agents/plugins/pi/pi.paths.d.ts +37 -0
  83. package/dist/agents/plugins/pi/pi.paths.d.ts.map +1 -0
  84. package/dist/agents/plugins/pi/pi.paths.js +146 -0
  85. package/dist/agents/plugins/pi/pi.paths.js.map +1 -0
  86. package/dist/agents/plugins/pi/pi.plugin.d.ts +30 -0
  87. package/dist/agents/plugins/pi/pi.plugin.d.ts.map +1 -0
  88. package/dist/agents/plugins/pi/pi.plugin.js +453 -0
  89. package/dist/agents/plugins/pi/pi.plugin.js.map +1 -0
  90. package/dist/agents/plugins/pi/pi.session.d.ts +114 -0
  91. package/dist/agents/plugins/pi/pi.session.d.ts.map +1 -0
  92. package/dist/agents/plugins/pi/pi.session.js +388 -0
  93. package/dist/agents/plugins/pi/pi.session.js.map +1 -0
  94. package/dist/agents/plugins/pi/pi.setup.d.ts +2 -0
  95. package/dist/agents/plugins/pi/pi.setup.d.ts.map +1 -0
  96. package/dist/agents/plugins/pi/pi.setup.js +33 -0
  97. package/dist/agents/plugins/pi/pi.setup.js.map +1 -0
  98. package/dist/agents/plugins/pi/pi.types.d.ts +143 -0
  99. package/dist/agents/plugins/pi/pi.types.d.ts.map +1 -0
  100. package/dist/agents/plugins/pi/pi.types.js +74 -0
  101. package/dist/agents/plugins/pi/pi.types.js.map +1 -0
  102. package/dist/agents/plugins/pi/session/pi-file-operations.d.ts +17 -0
  103. package/dist/agents/plugins/pi/session/pi-file-operations.d.ts.map +1 -0
  104. package/dist/agents/plugins/pi/session/pi-file-operations.js +98 -0
  105. package/dist/agents/plugins/pi/session/pi-file-operations.js.map +1 -0
  106. package/dist/agents/plugins/pi/session/pi-named-invocations.d.ts +52 -0
  107. package/dist/agents/plugins/pi/session/pi-named-invocations.d.ts.map +1 -0
  108. package/dist/agents/plugins/pi/session/pi-named-invocations.js +189 -0
  109. package/dist/agents/plugins/pi/session/pi-named-invocations.js.map +1 -0
  110. package/dist/agents/plugins/pi/session/pi-user-prompt.d.ts +14 -0
  111. package/dist/agents/plugins/pi/session/pi-user-prompt.d.ts.map +1 -0
  112. package/dist/agents/plugins/pi/session/pi-user-prompt.js +51 -0
  113. package/dist/agents/plugins/pi/session/pi-user-prompt.js.map +1 -0
  114. package/dist/agents/plugins/pi/session/processors/pi.conversations-processor.d.ts +52 -0
  115. package/dist/agents/plugins/pi/session/processors/pi.conversations-processor.d.ts.map +1 -0
  116. package/dist/agents/plugins/pi/session/processors/pi.conversations-processor.js +716 -0
  117. package/dist/agents/plugins/pi/session/processors/pi.conversations-processor.js.map +1 -0
  118. package/dist/agents/plugins/pi/session/processors/pi.metrics-processor.d.ts +104 -0
  119. package/dist/agents/plugins/pi/session/processors/pi.metrics-processor.d.ts.map +1 -0
  120. package/dist/agents/plugins/pi/session/processors/pi.metrics-processor.js +595 -0
  121. package/dist/agents/plugins/pi/session/processors/pi.metrics-processor.js.map +1 -0
  122. package/dist/agents/registry.d.ts.map +1 -1
  123. package/dist/agents/registry.js +2 -2
  124. package/dist/agents/registry.js.map +1 -1
  125. package/dist/cli/commands/analytics/agent-labels.d.ts.map +1 -1
  126. package/dist/cli/commands/analytics/agent-labels.js +1 -0
  127. package/dist/cli/commands/analytics/agent-labels.js.map +1 -1
  128. package/dist/cli/commands/analytics/cost/usage-readers.d.ts +24 -0
  129. package/dist/cli/commands/analytics/cost/usage-readers.d.ts.map +1 -1
  130. package/dist/cli/commands/analytics/cost/usage-readers.js +199 -54
  131. package/dist/cli/commands/analytics/cost/usage-readers.js.map +1 -1
  132. package/dist/cli/commands/analytics/index.js +1 -0
  133. package/dist/cli/commands/analytics/index.js.map +1 -1
  134. package/dist/cli/commands/analytics/native-loader.d.ts +21 -4
  135. package/dist/cli/commands/analytics/native-loader.d.ts.map +1 -1
  136. package/dist/cli/commands/analytics/native-loader.js +250 -96
  137. package/dist/cli/commands/analytics/native-loader.js.map +1 -1
  138. package/dist/cli/commands/analytics/report/client/app.js +47 -3
  139. package/dist/cli/commands/analytics/report/payload-builder.d.ts.map +1 -1
  140. package/dist/cli/commands/analytics/report/payload-builder.js +25 -2
  141. package/dist/cli/commands/analytics/report/payload-builder.js.map +1 -1
  142. package/dist/cli/commands/analytics/report/types.d.ts +1 -0
  143. package/dist/cli/commands/analytics/report/types.d.ts.map +1 -1
  144. package/dist/cli/commands/hook.d.ts.map +1 -1
  145. package/dist/cli/commands/hook.js +71 -29
  146. package/dist/cli/commands/hook.js.map +1 -1
  147. package/dist/cli/commands/install.d.ts.map +1 -1
  148. package/dist/cli/commands/install.js +7 -5
  149. package/dist/cli/commands/install.js.map +1 -1
  150. package/dist/cli/commands/list.d.ts.map +1 -1
  151. package/dist/cli/commands/list.js +3 -1
  152. package/dist/cli/commands/list.js.map +1 -1
  153. package/dist/cli/commands/uninstall.d.ts.map +1 -1
  154. package/dist/cli/commands/uninstall.js +5 -3
  155. package/dist/cli/commands/uninstall.js.map +1 -1
  156. package/dist/cli/commands/update.d.ts.map +1 -1
  157. package/dist/cli/commands/update.js +5 -3
  158. package/dist/cli/commands/update.js.map +1 -1
  159. package/dist/cli/first-time.d.ts.map +1 -1
  160. package/dist/cli/first-time.js +7 -6
  161. package/dist/cli/first-time.js.map +1 -1
  162. package/dist/providers/core/auth-validation.d.ts +7 -2
  163. package/dist/providers/core/auth-validation.d.ts.map +1 -1
  164. package/dist/providers/core/auth-validation.js +11 -5
  165. package/dist/providers/core/auth-validation.js.map +1 -1
  166. package/dist/providers/plugins/sso/proxy/plugins/claude-request-normalizer.plugin.d.ts +8 -3
  167. package/dist/providers/plugins/sso/proxy/plugins/claude-request-normalizer.plugin.d.ts.map +1 -1
  168. package/dist/providers/plugins/sso/proxy/plugins/claude-request-normalizer.plugin.js +46 -11
  169. package/dist/providers/plugins/sso/proxy/plugins/claude-request-normalizer.plugin.js.map +1 -1
  170. package/dist/providers/plugins/sso/proxy/plugins/codex-encrypted-content-sanitizer.plugin.d.ts +23 -13
  171. package/dist/providers/plugins/sso/proxy/plugins/codex-encrypted-content-sanitizer.plugin.d.ts.map +1 -1
  172. package/dist/providers/plugins/sso/proxy/plugins/codex-encrypted-content-sanitizer.plugin.js +75 -18
  173. package/dist/providers/plugins/sso/proxy/plugins/codex-encrypted-content-sanitizer.plugin.js.map +1 -1
  174. package/dist/providers/plugins/sso/proxy/plugins/copilot-encrypted-content-sanitizer.plugin.d.ts +21 -0
  175. package/dist/providers/plugins/sso/proxy/plugins/copilot-encrypted-content-sanitizer.plugin.d.ts.map +1 -0
  176. package/dist/providers/plugins/sso/proxy/plugins/copilot-encrypted-content-sanitizer.plugin.js +133 -0
  177. package/dist/providers/plugins/sso/proxy/plugins/copilot-encrypted-content-sanitizer.plugin.js.map +1 -0
  178. package/dist/providers/plugins/sso/proxy/plugins/header-injection.plugin.js +1 -1
  179. package/dist/providers/plugins/sso/proxy/plugins/header-injection.plugin.js.map +1 -1
  180. package/dist/providers/plugins/sso/proxy/plugins/index.d.ts +2 -1
  181. package/dist/providers/plugins/sso/proxy/plugins/index.d.ts.map +1 -1
  182. package/dist/providers/plugins/sso/proxy/plugins/index.js +4 -2
  183. package/dist/providers/plugins/sso/proxy/plugins/index.js.map +1 -1
  184. package/dist/providers/plugins/sso/proxy/plugins/types.d.ts +11 -0
  185. package/dist/providers/plugins/sso/proxy/plugins/types.d.ts.map +1 -1
  186. package/dist/providers/plugins/sso/proxy/sso.proxy.d.ts +2 -0
  187. package/dist/providers/plugins/sso/proxy/sso.proxy.d.ts.map +1 -1
  188. package/dist/providers/plugins/sso/proxy/sso.proxy.js +37 -1
  189. package/dist/providers/plugins/sso/proxy/sso.proxy.js.map +1 -1
  190. package/dist/providers/plugins/sso/session/SessionSyncer.d.ts.map +1 -1
  191. package/dist/providers/plugins/sso/session/SessionSyncer.js +13 -0
  192. package/dist/providers/plugins/sso/session/SessionSyncer.js.map +1 -1
  193. package/dist/providers/plugins/sso/session/processors/conversations/syncProcessor.js +6 -0
  194. package/dist/providers/plugins/sso/session/processors/conversations/syncProcessor.js.map +1 -1
  195. package/dist/providers/plugins/sso/session/processors/metrics/metrics-post-processor.d.ts +0 -6
  196. package/dist/providers/plugins/sso/session/processors/metrics/metrics-post-processor.d.ts.map +1 -1
  197. package/dist/providers/plugins/sso/session/processors/metrics/metrics-post-processor.js +28 -19
  198. package/dist/providers/plugins/sso/session/processors/metrics/metrics-post-processor.js.map +1 -1
  199. package/dist/utils/interactive.d.ts +14 -0
  200. package/dist/utils/interactive.d.ts.map +1 -0
  201. package/dist/utils/interactive.js +16 -0
  202. package/dist/utils/interactive.js.map +1 -0
  203. package/package.json +6 -4
  204. package/scripts/copy-plugins.js +6 -0
@@ -0,0 +1,146 @@
1
+ import { join, resolve } from 'path';
2
+ import { homedir } from 'os';
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { logger } from '../../../utils/logger.js';
6
+ export function getUserPiAgentDir() {
7
+ return join(homedir(), '.pi', 'agent');
8
+ }
9
+ export function getPiAgentDir(cwd = process.cwd()) {
10
+ return join(cwd, '.pi', 'codemie', 'agent');
11
+ }
12
+ export function getPiModelsPath(cwd = process.cwd()) {
13
+ return join(getPiAgentDir(cwd), 'models.json');
14
+ }
15
+ /**
16
+ * Compute the default Pi session storage directory for a given cwd.
17
+ *
18
+ * Pi encodes the cwd as `--<cwd with leading slash removed and path
19
+ * separators/colons replaced by dashes>--` under the agent dir's `sessions/`.
20
+ */
21
+ export function getPiSessionDir(cwd = process.cwd()) {
22
+ const resolved = resolve(cwd);
23
+ const safeName = `--${resolved.replace(/^[/\\]/, '').replace(/[/\\:]/g, '-')}--`;
24
+ return join(getPiAgentDir(cwd), 'sessions', safeName);
25
+ }
26
+ /**
27
+ * Convert a Git Bash / MSYS / Cygwin / WSL drive path to native Windows form,
28
+ * matching Pi's `normalizeWindowsShellPath()`.
29
+ */
30
+ function normalizeWindowsShellPath(filePath) {
31
+ if (!filePath.startsWith('/') || filePath.startsWith('//') || filePath.includes('\\')) {
32
+ return filePath;
33
+ }
34
+ const match = filePath.match(/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i);
35
+ if (!match) {
36
+ return filePath;
37
+ }
38
+ const suffix = match[2]?.replace(/\//g, '\\');
39
+ return `${match[1].toUpperCase()}:\\${suffix ?? ''}`;
40
+ }
41
+ /**
42
+ * Normalize a session-directory path the way Pi does, then resolve it to an
43
+ * absolute path. Returns undefined for a configured value that has no path form.
44
+ *
45
+ * Every channel Pi accepts a path from runs through `normalizePath()`, which
46
+ * rewrites Windows shell paths, expands a leading `~`, and converts `file://`
47
+ * URLs. Node's `resolve()` does none of that, so a raw `~/pi-sessions` would
48
+ * create a literal `~` directory and `file:///tmp/x` would become
49
+ * `<cwd>/file:/tmp/x` — in both cases discovery would scan a directory Pi never
50
+ * writes to, and the run's metrics would be lost without a trace.
51
+ *
52
+ * A `file://` URL that `fileURLToPath` rejects is the one value with no path form. Pi's
53
+ * `normalizePath()` lets the conversion throw, so a bare `pi` aborts before writing
54
+ * anything, whichever channel carried the value. This function cannot throw — it also runs
55
+ * during discovery after the agent has exited, where it would take the whole metrics upload
56
+ * with it — so it reports the absence instead; see {@link resolvePiSessionDir} for what is
57
+ * done with it.
58
+ */
59
+ function normalizeSessionDir(inputPath) {
60
+ const normalized = process.platform === 'win32' ? normalizeWindowsShellPath(inputPath) : inputPath;
61
+ if (normalized === '~') {
62
+ return homedir();
63
+ }
64
+ const hasTildePrefix = normalized.startsWith('~/') || (process.platform === 'win32' && normalized.startsWith('~\\'));
65
+ if (hasTildePrefix) {
66
+ return join(homedir(), normalized.slice(2));
67
+ }
68
+ if (/^file:\/\//.test(normalized)) {
69
+ try {
70
+ return fileURLToPath(normalized);
71
+ }
72
+ catch {
73
+ return undefined;
74
+ }
75
+ }
76
+ return resolve(normalized);
77
+ }
78
+ /**
79
+ * Read `sessionDir` from a Pi `settings.json`, if the file exists and declares one.
80
+ * Malformed JSON is ignored so a broken settings file cannot break discovery.
81
+ */
82
+ function readSettingsSessionDir(settingsPath) {
83
+ if (!existsSync(settingsPath)) {
84
+ return undefined;
85
+ }
86
+ try {
87
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
88
+ if (typeof settings.sessionDir === 'string' && settings.sessionDir.length > 0) {
89
+ return settings.sessionDir;
90
+ }
91
+ }
92
+ catch {
93
+ // Malformed settings.json is ignored; fall back to the next source.
94
+ }
95
+ return undefined;
96
+ }
97
+ /**
98
+ * The session directory the user configured, from the highest-precedence source that
99
+ * declares one.
100
+ *
101
+ * Mirrors Pi's own precedence (`main.ts` session-dir resolution and `SettingsManager`,
102
+ * which deep-merges global settings under project settings):
103
+ * 1. `PI_CODING_AGENT_SESSION_DIR` environment variable (set by the user or by
104
+ * this plugin when it injects `--session-dir`).
105
+ * 2. `sessionDir` from the project-local `<cwd>/.pi/settings.json`.
106
+ * 3. `sessionDir` from the Pi `settings.json` located under the effective agent
107
+ * directory (`PI_CODING_AGENT_DIR` or the project-local default).
108
+ */
109
+ function readConfiguredSessionDir(cwd, env) {
110
+ if (env.PI_CODING_AGENT_SESSION_DIR) {
111
+ return env.PI_CODING_AGENT_SESSION_DIR;
112
+ }
113
+ const agentDir = env.PI_CODING_AGENT_DIR ? resolve(env.PI_CODING_AGENT_DIR) : getPiAgentDir(cwd);
114
+ return (readSettingsSessionDir(join(resolve(cwd), '.pi', 'settings.json')) ??
115
+ readSettingsSessionDir(join(agentDir, 'settings.json')));
116
+ }
117
+ /**
118
+ * Resolve the effective Pi session directory: the configured one when it has a path form,
119
+ * the default per-cwd encoded directory from {@link getPiSessionDir} otherwise.
120
+ *
121
+ * Falling back is not a substitute directory handed to Pi. `isCustom` is false with it, so
122
+ * no caller injects `PI_CODING_AGENT_SESSION_DIR`, and Pi still reads the user's own value
123
+ * from the flag, the environment, or `settings.json` and aborts on it exactly as a bare
124
+ * `pi` would — the wrapper neither rescues the run nor relocates the transcripts of one
125
+ * that works.
126
+ *
127
+ * What the fallback does prevent is a value with no path form reaching the filesystem.
128
+ * Every unresolvable value is a `file://` URL, and to `existsSync` those are RELATIVE
129
+ * paths: `file://bad` is `<cwd>/file:/bad`, an ordinary directory any working tree may
130
+ * happen to contain. Returning one would have discovery list whatever `.jsonl` files live
131
+ * there and upload them as this session's transcripts.
132
+ */
133
+ export function resolvePiSessionDir(cwd = process.cwd(), env = process.env) {
134
+ const configured = readConfiguredSessionDir(cwd, env);
135
+ if (configured) {
136
+ const sessionDir = normalizeSessionDir(configured);
137
+ if (sessionDir) {
138
+ return { sessionDir, isCustom: true };
139
+ }
140
+ logger.debug('[pi-paths] Session directory has no path form; using the default', {
141
+ configured,
142
+ });
143
+ }
144
+ return { sessionDir: getPiSessionDir(cwd), isCustom: false };
145
+ }
146
+ //# sourceMappingURL=pi.paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi.paths.js","sourceRoot":"","sources":["../../../../src/agents/plugins/pi/pi.paths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { AgentMetadata } from '../../core/types.js';
2
+ import { BaseAgentAdapter } from '../../core/BaseAgentAdapter.js';
3
+ import type { SessionAdapter } from '../../core/session/BaseSessionAdapter.js';
4
+ /**
5
+ * Reconcile CodeMie's CLI surface with Pi's session flags before the run starts.
6
+ *
7
+ * Two argv-derived facts are needed later by hooks that cannot see argv:
8
+ * `beforeRun` receives only `env`, and `enrichArgs` receives no env at all. Both
9
+ * are published through `envOverrides`, which `BaseAgentAdapter` spreads into the
10
+ * run-scoped `env` object that also reaches `onSessionEnd` and session discovery.
11
+ */
12
+ export declare function preparePiInvocation(args: string[], envOverrides?: Record<string, string>): {
13
+ args: string[];
14
+ envOverrides: Record<string, string>;
15
+ };
16
+ export declare const PiPluginMetadata: AgentMetadata;
17
+ export declare class PiPlugin extends BaseAgentAdapter {
18
+ private sessionAdapter;
19
+ constructor();
20
+ getSessionAdapter(): SessionAdapter;
21
+ /**
22
+ * Translate CodeMie's session flags to Pi's and publish the argv-derived facts
23
+ * that `beforeRun` and `enrichArgs` need but cannot read from argv themselves.
24
+ */
25
+ run(args: string[], envOverrides?: Record<string, string>, runOptions?: {
26
+ dryRun?: boolean;
27
+ }): Promise<void>;
28
+ additionalInstallation(_options?: import('../../core/types.js').AgentInstallationOptions): Promise<void>;
29
+ }
30
+ //# sourceMappingURL=pi.plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi.plugin.d.ts","sourceRoot":"","sources":["../../../../src/agents/plugins/pi/pi.plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AA4C/E;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CA4B1D;AA6ED,eAAO,MAAM,gBAAgB,EAAE,aAmU9B,CAAC;AAEF,qBAAa,QAAS,SAAQ,gBAAgB;IAC5C,OAAO,CAAC,cAAc,CAAiB;;IAOvC,iBAAiB,IAAI,cAAc;IAInC;;;OAGG;IACG,GAAG,CACP,IAAI,EAAE,MAAM,EAAE,EACd,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,UAAU,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAChC,OAAO,CAAC,IAAI,CAAC;IAKV,sBAAsB,CAC1B,QAAQ,CAAC,EAAE,OAAO,qBAAqB,EAAE,wBAAwB,GAChE,OAAO,CAAC,IAAI,CAAC;CAGjB"}
@@ -0,0 +1,453 @@
1
+ import { BaseAgentAdapter } from '../../core/BaseAgentAdapter.js';
2
+ import { logger } from '../../../utils/logger.js';
3
+ import { preparePiAgentDir } from './pi.setup.js';
4
+ import { fetchAndBuildPiModels, classifyPiModel } from './pi.models.js';
5
+ import { getPiAgentDir, resolvePiSessionDir } from './pi.paths.js';
6
+ import { LEDGER_PATH_ENV, ensurePiCodeMieExtension, getPiRunLedgerPath, readPiRunLedger, } from './pi.extension.js';
7
+ import { resolve } from 'path';
8
+ import { detectGitBranch, detectGitRemoteRepo } from '../../../utils/processes.js';
9
+ import { appendTranscriptMarker } from '../../core/session/session-origin-audit.js';
10
+ import { reconcileStaleSessions } from '../../core/session/stale-session-reconciliation.js';
11
+ import { startPiIncrementalSync, stopPiIncrementalSync } from './pi.incremental-sync.js';
12
+ import { installRequiredPiPackages } from './pi.packages.js';
13
+ import { PiSessionAdapter } from './pi.session.js';
14
+ /**
15
+ * Pi flags that select an existing session. Pi rejects `--session-id` when any of
16
+ * them is present (`validateSessionIdFlags` in Pi's `main.ts` calls `process.exit(1)`),
17
+ * so the injected id must be suppressed for these runs. `--fork` is deliberately
18
+ * absent: Pi allows `--fork` together with `--session-id` and uses the id to name
19
+ * the forked session.
20
+ */
21
+ const PI_SESSION_SELECTION_FLAGS = ['--session', '--continue', '-c', '--resume', '-r'];
22
+ /**
23
+ * Read the value of a `<flag> <value>` pair. Pi's parser only accepts the
24
+ * space-separated form, so `--flag=value` is deliberately not matched, and it
25
+ * assigns on every occurrence, so the last one wins.
26
+ */
27
+ function readFlagValue(args, flag) {
28
+ const index = args.lastIndexOf(flag);
29
+ return index !== -1 && index + 1 < args.length ? args[index + 1] : undefined;
30
+ }
31
+ function hasSessionSelectionFlag(args) {
32
+ return PI_SESSION_SELECTION_FLAGS.some((flag) => args.includes(flag));
33
+ }
34
+ /**
35
+ * Reconcile CodeMie's CLI surface with Pi's session flags before the run starts.
36
+ *
37
+ * Two argv-derived facts are needed later by hooks that cannot see argv:
38
+ * `beforeRun` receives only `env`, and `enrichArgs` receives no env at all. Both
39
+ * are published through `envOverrides`, which `BaseAgentAdapter` spreads into the
40
+ * run-scoped `env` object that also reaches `onSessionEnd` and session discovery.
41
+ */
42
+ export function preparePiInvocation(args, envOverrides) {
43
+ // CodeMie declares `--resume <session-id>`, but Pi's `--resume`/`-r` is a boolean
44
+ // that opens an interactive picker, which would leave the id as a bare positional
45
+ // and send it to the model as a chat message. `--session <id>` is Pi's
46
+ // value-taking "open this session and keep appending" flag. If the user also
47
+ // passed `--session`, theirs wins in Pi and this pair is dropped rather than
48
+ // rewritten, so a second `--session` cannot shadow it and the orphaned id cannot
49
+ // leak into the conversation.
50
+ const resumeIndex = args.indexOf('--resume');
51
+ const hasResumeValue = resumeIndex !== -1 && resumeIndex + 1 < args.length;
52
+ let piArgs = args;
53
+ if (hasResumeValue) {
54
+ piArgs = args.includes('--session')
55
+ ? [...args.slice(0, resumeIndex), ...args.slice(resumeIndex + 2)]
56
+ : args.map((arg, index) => (index === resumeIndex ? '--session' : arg));
57
+ }
58
+ const overrides = { ...envOverrides };
59
+ // `--session-dir` is an unknown option to CodeMie's parser and reaches Pi verbatim,
60
+ // so discovery would otherwise scan a directory Pi never writes to. Seed the raw
61
+ // value; resolvePiSessionDir performs the tilde expansion Pi also applies.
62
+ const sessionDir = readFlagValue(piArgs, '--session-dir');
63
+ if (sessionDir) {
64
+ overrides.PI_CODING_AGENT_SESSION_DIR = sessionDir;
65
+ }
66
+ return { args: piArgs, envOverrides: overrides };
67
+ }
68
+ function buildPiHookConfig(env, sessionId) {
69
+ return {
70
+ agentName: env.CODEMIE_AGENT || 'pi',
71
+ sessionId,
72
+ provider: env.CODEMIE_PROVIDER,
73
+ apiBaseUrl: env.CODEMIE_BASE_URL,
74
+ ssoUrl: env.CODEMIE_URL,
75
+ syncApiUrl: env.CODEMIE_SYNC_API_URL,
76
+ version: env.CODEMIE_CLI_VERSION,
77
+ profileName: env.CODEMIE_PROFILE_NAME,
78
+ project: env.CODEMIE_PROJECT,
79
+ model: env.CODEMIE_MODEL,
80
+ clientType: 'codemie-pi',
81
+ };
82
+ }
83
+ /**
84
+ * Point the CodeMie session record at what the run actually produced.
85
+ *
86
+ * Three things are only knowable once Pi has exited, and each has a consumer that
87
+ * silently degrades without it:
88
+ *
89
+ * - `correlation.agentSessionFile` — the analytics cost path resolves the native
90
+ * transcript through this field. Pi left it empty, so token and cost figures were
91
+ * never readable for a Pi session, and the report blamed a missing token reader.
92
+ * - `correlation.agentSessionId` — the metrics aggregator prefers it when labelling
93
+ * the wire `session_id`, which should be Pi's id rather than CodeMie's.
94
+ * - `workingDirectory` / `gitBranch` / `repository` — `--session <path>` runs the
95
+ * whole session under the transcript's cwd, which may be a different repository
96
+ * than the one CodeMie was launched from.
97
+ */
98
+ async function correlateRunToSession(sessionId, ledger) {
99
+ if (!ledger.primaryTranscript)
100
+ return;
101
+ try {
102
+ const { SessionStore } = await import('../../core/session/SessionStore.js');
103
+ const store = new SessionStore();
104
+ const session = await store.loadSession(sessionId);
105
+ if (!session) {
106
+ logger.debug(`[pi] No session record for ${sessionId}; skipping correlation`);
107
+ return;
108
+ }
109
+ session.correlation = {
110
+ ...session.correlation,
111
+ status: 'matched',
112
+ agentSessionFile: ledger.primaryTranscript,
113
+ ...(ledger.piSessionId && { agentSessionId: ledger.piSessionId }),
114
+ detectedAt: Date.now(),
115
+ };
116
+ if (ledger.cwd && resolve(ledger.cwd) !== resolve(session.workingDirectory)) {
117
+ logger.debug(`[pi] Session ran under ${ledger.cwd}, not ${session.workingDirectory}`);
118
+ session.workingDirectory = ledger.cwd;
119
+ const [branch, repository] = await Promise.all([
120
+ detectGitBranch(ledger.cwd),
121
+ detectGitRemoteRepo(ledger.cwd),
122
+ ]);
123
+ if (branch)
124
+ session.gitBranch = branch;
125
+ if (repository)
126
+ session.repository = repository;
127
+ }
128
+ await store.saveSession(session);
129
+ // Ownership sidecars, so analytics can tell a CodeMie-managed transcript from a
130
+ // bare `pi` one.
131
+ for (const transcript of ledger.transcripts) {
132
+ appendTranscriptMarker(transcript, sessionId, 'pi');
133
+ }
134
+ }
135
+ catch (error) {
136
+ const msg = error instanceof Error ? error.message : String(error);
137
+ logger.debug(`[pi] Correlating the run to its session record failed (non-blocking): ${msg}`);
138
+ }
139
+ }
140
+ export const PiPluginMetadata = {
141
+ name: 'pi',
142
+ displayName: 'Pi',
143
+ description: 'Pi - open-source coding agent harness',
144
+ npmPackage: '@earendil-works/pi-coding-agent',
145
+ cliCommand: process.env.CODEMIE_PI_BIN || 'pi',
146
+ sessionAnalyticsReport: true,
147
+ metricsConfig: {
148
+ excludeErrorsFromTools: ['bash'],
149
+ },
150
+ dataPaths: {
151
+ home: '.pi',
152
+ },
153
+ /**
154
+ * Pi names its resource directories differently from the scanner's defaults, so the
155
+ * lifecycle metric counted zero of everything until this mapping existed:
156
+ *
157
+ * - `prompts/` holds Pi's prompt templates, its slash-command equivalent. Flat `.md`
158
+ * files, which is what the `commands` counter looks for.
159
+ * - `extensions/` holds Pi's in-process hooks as `.ts`/`.js` files, which is what the
160
+ * `hooks` counter looks for.
161
+ * - `skills/` is a directory per skill with a `SKILL.md` entry, matching the default.
162
+ * - Pi has no agents or rules concept. The empty arrays are deliberate: they hold
163
+ * those categories at zero rather than scanning a directory Pi never reads.
164
+ *
165
+ * `global` is the user's own Pi home rather than the agent dir CodeMie redirects Pi
166
+ * to (`<cwd>/.pi/codemie/agent`). That dir is a copy of this one plus CodeMie's own
167
+ * metrics extension, so counting it would report our plumbing as a user-authored hook.
168
+ */
169
+ extensionsConfig: {
170
+ project: '.pi',
171
+ global: '~/.pi/agent',
172
+ skillsEntryFile: 'SKILL.md',
173
+ dirNames: {
174
+ agents: [],
175
+ commands: ['prompts'],
176
+ skills: ['skills'],
177
+ hooks: ['extensions'],
178
+ rules: [],
179
+ },
180
+ },
181
+ /**
182
+ * MCP is not part of Pi core — Pi ships without it deliberately ("It intentionally
183
+ * does not include built-in MCP", Pi's `docs/usage.md`). It arrives through the
184
+ * `pi-mcp-adapter` package, which CodeMie installs for every managed Pi
185
+ * (`REQUIRED_PI_PACKAGES` in `pi.packages.ts`), so under CodeMie the adapter is
186
+ * effectively core and its servers are worth counting.
187
+ *
188
+ * The adapter merges six files, lowest precedence first: `~/.config/mcp/mcp.json`,
189
+ * `~/.agents/mcp.json`, `~/.agents/mcp/mcp.json`, `<agentDir>/mcp.json`,
190
+ * `<cwd>/.mcp.json`, `<cwd>/.pi/mcp.json`. Three scopes cannot hold six files, so the
191
+ * two project files map directly and the four global ones become ordered candidates —
192
+ * and note `readMCPFromSource` takes the first candidate that *parses*, it never
193
+ * merges.
194
+ *
195
+ * `user` names the redirected agent dir, and deliberately **not** the user's own
196
+ * `~/.pi/agent/mcp.json`. `beforeRun` always sets `PI_CODING_AGENT_DIR` to
197
+ * `<cwd>/.pi/codemie/agent` and the adapter resolves `<agentDir>/mcp.json` through that
198
+ * variable, so under CodeMie the home file is never one of the adapter's six sources.
199
+ * Listing it would report servers this Pi does not load: `preparePiAgentDir` copies once
200
+ * and returns early forever after, so a project whose copy predates the user's MCP setup
201
+ * never acquires an `mcp.json` — a permanent state, not a first-run one — while the home
202
+ * file keeps growing, because a bare `pi` writes precisely there. Worse, because
203
+ * `readMCPFromSource` returns on the first candidate that *parses*, that phantom would
204
+ * also hide `~/.agents/*` and `~/.config/mcp`, which the adapter genuinely does merge.
205
+ * (This is the opposite call from `extensionsConfig.global` above, which avoids the copy
206
+ * because CodeMie injects its own extension into it. Nothing injects MCP servers, so
207
+ * here the copy is simply the truth.)
208
+ *
209
+ * The invariant that buys: every server reported is one this run actually loads.
210
+ *
211
+ * Knowingly an approximation otherwise:
212
+ * - Under-counts the first run in a project, which reports zero user servers because the
213
+ * copy is made in `beforeRun`, after `onSessionStart` has already sent the metric.
214
+ * Self-healing from the second run on.
215
+ * - Over-counts a server named in two scopes. The adapter merges those into one live
216
+ * server; `totalServers` sums the scopes, while `serverNames` stays deduplicated.
217
+ * - Under-counts every global file after the first that parses, the legacy
218
+ * `mcp-servers` alias key, and any config written as true JSONC — the adapter strips
219
+ * comments before parsing, `readJsonFile` does not.
220
+ * - Under-counts host-config imports (`hostConfigDiscovery: "on"`, or a per-file
221
+ * `"imports"` array), which pull servers from Claude/Codex/Cursor/VS Code files.
222
+ * Following a key into other files with other key names is beyond `navigateJsonPath`.
223
+ * Both mechanisms are opt-in and default off.
224
+ *
225
+ * Every one of those failures is silent: a wrong path and a genuinely MCP-less machine
226
+ * are indistinguishable in the summary.
227
+ */
228
+ mcpConfig: {
229
+ local: {
230
+ path: '.pi/mcp.json',
231
+ jsonPath: 'mcpServers',
232
+ },
233
+ project: {
234
+ path: '.mcp.json',
235
+ jsonPath: 'mcpServers',
236
+ },
237
+ user: {
238
+ path: [
239
+ '.pi/codemie/agent/mcp.json',
240
+ '~/.agents/mcp/mcp.json',
241
+ '~/.agents/mcp.json',
242
+ '~/.config/mcp/mcp.json',
243
+ ],
244
+ jsonPath: 'mcpServers',
245
+ },
246
+ },
247
+ envMapping: {
248
+ baseUrl: [],
249
+ apiKey: [],
250
+ model: [],
251
+ },
252
+ supportedProviders: ['ai-run-sso', 'bearer-auth', 'litellm'],
253
+ ssoConfig: {
254
+ enabled: true,
255
+ clientType: 'codemie-pi',
256
+ },
257
+ /**
258
+ * `--task` must become `-p`, not a bare positional. Pi treats a positional as an
259
+ * opening prompt for an *interactive* session, so the TUI takes over the terminal
260
+ * and the run never exits — while `-p` is Pi's "process the prompt and exit" mode
261
+ * and consumes the argument that follows it as the prompt.
262
+ */
263
+ flagMappings: {
264
+ '--task': {
265
+ type: 'flag',
266
+ target: '-p',
267
+ },
268
+ },
269
+ lifecycle: {
270
+ async beforeRun(env, _config) {
271
+ const cwd = process.cwd();
272
+ await preparePiAgentDir(cwd);
273
+ await fetchAndBuildPiModels(env, cwd);
274
+ env.PI_CODING_AGENT_DIR = getPiAgentDir(cwd);
275
+ // Have Pi report which transcripts this run writes, instead of inferring them
276
+ // from the session directory after the fact. `preparePiAgentDir` skips the copy
277
+ // when the agent dir already exists, so this runs unconditionally to pick up
278
+ // asset changes after an upgrade.
279
+ const extension = await ensurePiCodeMieExtension(cwd, env);
280
+ if (extension.installed && env.CODEMIE_SESSION_ID) {
281
+ const ledgerPath = getPiRunLedgerPath(env.CODEMIE_SESSION_ID);
282
+ env[LEDGER_PATH_ENV] = ledgerPath;
283
+ process.env[LEDGER_PATH_ENV] = ledgerPath;
284
+ }
285
+ // Honor session-directory overrides early so both Pi and discovery agree.
286
+ const { sessionDir, isCustom } = resolvePiSessionDir(cwd, env);
287
+ if (isCustom) {
288
+ env.PI_CODING_AGENT_SESSION_DIR = sessionDir;
289
+ process.env.PI_CODING_AGENT_SESSION_DIR = sessionDir;
290
+ }
291
+ logger.debug('[pi] Configured PI_CODING_AGENT_DIR', { path: env.PI_CODING_AGENT_DIR });
292
+ return env;
293
+ },
294
+ enrichArgs(args, _config) {
295
+ const model = process.env.CODEMIE_MODEL;
296
+ if (!model) {
297
+ throw new Error('No model configured for codemie-pi. Run codemie setup to select a model.');
298
+ }
299
+ const classification = classifyPiModel(model);
300
+ const providerId = classification.provider;
301
+ // `--task` is not handled here: `flagMappings` rewrites it to Pi's `-p`, which
302
+ // BaseAgentAdapter applies after this hook. Consuming it here would leave a bare
303
+ // positional, and Pi treats a bare positional as an interactive opening prompt.
304
+ const result = ['--provider', providerId, '--model', model, ...args];
305
+ // Share CodeMie's session id with Pi so discovery can exact-match the
306
+ // transcript. Pi exits with code 1 if this is combined with a flag that
307
+ // already selects a session, so those runs correlate by run window instead.
308
+ const sessionId = process.env.CODEMIE_SESSION_ID;
309
+ if (sessionId && !result.includes('--session-id') && !hasSessionSelectionFlag(result)) {
310
+ result.push('--session-id', sessionId);
311
+ }
312
+ else if (sessionId) {
313
+ logger.debug('[pi] Skipping --session-id injection: argv already selects a session');
314
+ }
315
+ // Forward the effective session directory. beforeRun resolved it onto the run
316
+ // env, which BaseAgentAdapter has already merged into process.env. A
317
+ // user-supplied --session-dir is in argv already and takes precedence.
318
+ if (!result.includes('--session-dir') && process.env.PI_CODING_AGENT_SESSION_DIR) {
319
+ result.push('--session-dir', process.env.PI_CODING_AGENT_SESSION_DIR);
320
+ }
321
+ return result;
322
+ },
323
+ async onSessionStart(sessionId, env) {
324
+ try {
325
+ const { processEvent } = await import('../../../cli/commands/hook.js');
326
+ await processEvent({
327
+ hook_event_name: 'SessionStart',
328
+ session_id: sessionId,
329
+ transcript_path: '',
330
+ permission_mode: 'default',
331
+ cwd: process.cwd(),
332
+ source: 'startup',
333
+ }, buildPiHookConfig(env, sessionId));
334
+ logger.info(`[pi] SessionStart hook completed for session ${sessionId}`);
335
+ }
336
+ catch (error) {
337
+ const msg = error instanceof Error ? error.message : String(error);
338
+ logger.error(`[pi] SessionStart hook failed (non-blocking): ${msg}`);
339
+ }
340
+ // A run killed with SIGKILL, or whose terminal was closed, never reaches onSessionEnd and
341
+ // leaves its record `active` forever with no terminal metric. Recover those on the next
342
+ // start. Fire-and-forget: a stranded earlier session must not delay this one.
343
+ void reconcileStaleSessions('pi', env, buildPiHookConfig).catch((error) => {
344
+ const msg = error instanceof Error ? error.message : String(error);
345
+ logger.debug(`[pi] Stale-session reconciliation failed (non-blocking): ${msg}`);
346
+ });
347
+ // Flush metrics during the run, so a hard kill loses at most one interval instead of the
348
+ // whole session. Ledger-driven: it processes exactly the transcripts the extension
349
+ // recorded, never a directory scan.
350
+ startPiIncrementalSync({
351
+ sessionId,
352
+ metadata: PiPluginMetadata,
353
+ buildContext: () => ({
354
+ sessionId,
355
+ apiBaseUrl: env.CODEMIE_SYNC_API_URL || env.CODEMIE_BASE_URL || '',
356
+ cookies: '',
357
+ clientType: 'codemie-pi',
358
+ version: env.CODEMIE_CLI_VERSION ?? '',
359
+ dryRun: false,
360
+ }),
361
+ ssoUrl: env.CODEMIE_URL,
362
+ syncApiUrl: env.CODEMIE_SYNC_API_URL ?? env.CODEMIE_BASE_URL,
363
+ cliVersion: env.CODEMIE_CLI_VERSION,
364
+ });
365
+ },
366
+ async onSessionEnd(exitCode, env) {
367
+ const sessionId = env.CODEMIE_SESSION_ID;
368
+ if (!sessionId) {
369
+ logger.debug('[pi] No CODEMIE_SESSION_ID in environment, skipping session end');
370
+ return;
371
+ }
372
+ // Stop the mid-run flush FIRST, and wait for any tick already running: it appends to the
373
+ // same metrics JSONL this final flush is about to, and two writers that both read the
374
+ // existing record ids before either appends would upload every delta twice.
375
+ await stopPiIncrementalSync(sessionId);
376
+ // The extension recorded which transcripts this run wrote, from inside Pi.
377
+ // Nothing else is claimed: a transcript absent from the ledger belongs to
378
+ // another process, and uploading it would leak that session's prompts.
379
+ const ledger = await readPiRunLedger(sessionId);
380
+ // Slash commands never reach the transcript — Pi expands prompt templates into plain user
381
+ // text before persisting — so the ledger is their only record. Hand them to the adapter the
382
+ // hook pipeline will reach through the registry.
383
+ try {
384
+ const { AgentRegistry } = await import('../../registry.js');
385
+ const adapter = AgentRegistry.getAgent('pi')?.getSessionAdapter?.();
386
+ if (adapter instanceof PiSessionAdapter) {
387
+ adapter.setLedgerInvocations({
388
+ commandInvocations: ledger.commandInvocations,
389
+ skillCommandInvocations: ledger.skillCommandInvocations,
390
+ });
391
+ }
392
+ }
393
+ catch (error) {
394
+ const msg = error instanceof Error ? error.message : String(error);
395
+ logger.debug(`[pi] Could not attach ledger invocations (non-blocking): ${msg}`);
396
+ }
397
+ if (!ledger.loaded) {
398
+ logger.warn('[pi] The CodeMie run ledger is missing, so this run\'s transcripts cannot be '
399
+ + 'identified. Reporting session lifecycle only — no tool metrics for this session.');
400
+ }
401
+ else if (ledger.transcripts.length === 0) {
402
+ logger.debug('[pi] Run produced no transcript (session ended before its first reply)');
403
+ }
404
+ await correlateRunToSession(sessionId, ledger);
405
+ const reason = env.CODEMIE_EXIT_SIGNAL
406
+ ? `signal(${env.CODEMIE_EXIT_SIGNAL})`
407
+ : exitCode === 0
408
+ ? 'exit'
409
+ : `exit(${exitCode})`;
410
+ try {
411
+ const { processEvent } = await import('../../../cli/commands/hook.js');
412
+ await processEvent({
413
+ hook_event_name: 'SessionEnd',
414
+ // Pi's own session id, so the backend joins on the agent's identity.
415
+ session_id: ledger.piSessionId ?? sessionId,
416
+ transcript_path: ledger.transcripts[0] ?? '',
417
+ transcript_paths: ledger.transcripts.length > 0 ? ledger.transcripts : undefined,
418
+ permission_mode: 'default',
419
+ // `--session <path>` runs the whole session under the transcript's cwd.
420
+ cwd: ledger.cwd ?? process.cwd(),
421
+ reason,
422
+ }, buildPiHookConfig(env, sessionId));
423
+ logger.info(`[pi] SessionEnd hook completed for session ${sessionId}`);
424
+ }
425
+ catch (error) {
426
+ const msg = error instanceof Error ? error.message : String(error);
427
+ logger.error(`[pi] SessionEnd hook failed (non-blocking): ${msg}`);
428
+ }
429
+ },
430
+ },
431
+ };
432
+ export class PiPlugin extends BaseAgentAdapter {
433
+ sessionAdapter;
434
+ constructor() {
435
+ super(PiPluginMetadata);
436
+ this.sessionAdapter = new PiSessionAdapter(PiPluginMetadata);
437
+ }
438
+ getSessionAdapter() {
439
+ return this.sessionAdapter;
440
+ }
441
+ /**
442
+ * Translate CodeMie's session flags to Pi's and publish the argv-derived facts
443
+ * that `beforeRun` and `enrichArgs` need but cannot read from argv themselves.
444
+ */
445
+ async run(args, envOverrides, runOptions) {
446
+ const prepared = preparePiInvocation(args, envOverrides);
447
+ return super.run(prepared.args, prepared.envOverrides, runOptions);
448
+ }
449
+ async additionalInstallation(_options) {
450
+ await installRequiredPiPackages({ cliCommand: this.metadata.cliCommand });
451
+ }
452
+ }
453
+ //# sourceMappingURL=pi.plugin.js.map