@particle-academy/prism-acp 0.4.0 → 0.4.1

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/README.md CHANGED
@@ -172,6 +172,23 @@ proceeds exactly as it did before. Calling a real session absent would refuse a
172
172
  resume that would have worked, which is worse than the late error this replaces;
173
173
  that error is still there as the backstop.
174
174
 
175
+ **It looks where the CLI looks, including `CLAUDE_CONFIG_DIR`.** The CLI resolves
176
+ its configuration home as that variable or, unset, `<home>/.claude`, and keeps
177
+ `projects` under whichever it picked — so the probe does the same. This is not a
178
+ detail: an installation that sets it does so because the stored subscription
179
+ credential lives there, which is exactly the installation whose resumes matter,
180
+ and reading the wrong store would report a real conversation `absent` and refuse
181
+ a resume that would have worked.
182
+
183
+ `childEnv` passes that variable through unchanged, so a driver spawned from this
184
+ process resolves the same store the probe read, by construction. Hand the driver
185
+ a `parentEnv` you built yourself and hand the same environment to the probe, or
186
+ name the store outright:
187
+
188
+ ```ts
189
+ const probeSession = (sessionId: string) => probeSessionStore(sessionId, { env: parentEnv });
190
+ ```
191
+
175
192
  `probeSession` is yours to supply because the answer belongs to the agent being
176
193
  driven, not to ACP: `probeSessionStore` reads claude's session store, and a Codex
177
194
  driver would resolve the same question through `thread/resume`. Omit it and
@@ -15,5 +15,20 @@ export interface SessionProbe {
15
15
  export interface SessionStoreOptions {
16
16
  /** Overridable so tests never read the developer's real sessions. */
17
17
  readonly home?: string;
18
+ /**
19
+ * The CLI's configuration home, naming the store directly. Outranks both
20
+ * {@link home} and the environment -- for a caller that knows where the store
21
+ * is, or that builds the driven CLI's environment itself.
22
+ */
23
+ readonly configDir?: string;
24
+ /**
25
+ * The environment the DRIVEN CLI will see, read for `CLAUDE_CONFIG_DIR`.
26
+ *
27
+ * Defaults to this process's own, which is correct by construction when the
28
+ * driver is spawned from here: `childEnv` passes `CLAUDE_CONFIG_DIR` through
29
+ * unchanged, so the probe and the child resolve one store. Hand the driver a
30
+ * `parentEnv` of your own and hand the same one here, or they will not.
31
+ */
32
+ readonly env?: Readonly<Record<string, string | undefined>>;
18
33
  }
19
34
  export declare function probeSessionStore(sessionId: string, options?: SessionStoreOptions): SessionProbe;
@@ -26,9 +26,51 @@
26
26
  // every project, so looking for the FILE is enough and needs no slug at all.
27
27
  import { readdirSync, statSync } from 'node:fs';
28
28
  import { homedir } from 'node:os';
29
- import { join } from 'node:path';
29
+ import { isAbsolute, join } from 'node:path';
30
30
  /** The CLI accepts a UUID, or a session TITLE, and nothing else. */
31
31
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
32
+ /**
33
+ * WHERE THE STORE IS. The CLI resolves its configuration home as
34
+ * `CLAUDE_CONFIG_DIR` or, unset, `<home>/.claude`, and keeps `projects` under
35
+ * whichever it picked.
36
+ *
37
+ * Reading only the second was this probe's one serious bug (0.4.0). An
38
+ * installation that sets the variable -- Genie forwards it deliberately,
39
+ * because the stored subscription credential lives there and is what lets a
40
+ * child run with no API key at all -- had the probe read a store the CLI does
41
+ * not use, find nothing, and report `absent` for a conversation that exists.
42
+ * That refuses a resume that would have worked: the failure this whole
43
+ * three-state design exists to avoid, reintroduced by the check meant to
44
+ * prevent it.
45
+ *
46
+ * The variable names the configuration home ITSELF, so `projects` sits directly
47
+ * inside it and no `.claude` is appended. Resolving it as
48
+ * `home: dirname(CLAUDE_CONFIG_DIR)` instead would work only while the
49
+ * directory happens to be named `.claude`.
50
+ */
51
+ function storeRoot(options) {
52
+ const configured = options.configDir ?? (options.env ?? process.env).CLAUDE_CONFIG_DIR;
53
+ const trimmed = configured?.trim();
54
+ // The CLI reads it with `||`, so an empty or blank value is no value. Taking
55
+ // it literally would resolve `projects` against this process's working
56
+ // directory.
57
+ if (trimmed === undefined || trimmed === '') {
58
+ return { root: join(options.home ?? homedir(), '.claude', 'projects') };
59
+ }
60
+ // The CLI refuses to run at all with a relative configuration home -- "the
61
+ // configuration home (CLAUDE_CONFIG_DIR) is not an absolute path" -- so there
62
+ // is no store to name, and resolving it against our own cwd would answer
63
+ // about a directory the CLI never looks in. It is also not a case for the
64
+ // `<home>/.claude` fallback: the CLI will not fall back either.
65
+ if (!isAbsolute(trimmed)) {
66
+ // The value itself stays out of the message. Every other `detail` here
67
+ // names the path it looked at, which is useful and harmless for a path we
68
+ // derived; this one is an environment value, and a detail string travels to
69
+ // the client.
70
+ return { reason: 'CLAUDE_CONFIG_DIR is not an absolute path, so the session store it names cannot be located' };
71
+ }
72
+ return { root: join(trimmed, 'projects') };
73
+ }
32
74
  export function probeSessionStore(sessionId, options = {}) {
33
75
  // A non-UUID is refused by the CLI outright -- verified against claude
34
76
  // 2.1.292: "Provided value ... is not a UUID and does not match any session
@@ -45,7 +87,10 @@ export function probeSessionStore(sessionId, options = {}) {
45
87
  detail: `${sessionId} is not a session id the CLI can resume: it is not a UUID.`,
46
88
  };
47
89
  }
48
- const root = join(options.home ?? homedir(), '.claude', 'projects');
90
+ const resolved = storeRoot(options);
91
+ if ('reason' in resolved)
92
+ return { existence: 'indeterminate', detail: resolved.reason };
93
+ const { root } = resolved;
49
94
  let projects;
50
95
  try {
51
96
  if (!statSync(root).isDirectory()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/prism-acp",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Speak the Agent Client Protocol to a coding-agent CLI the user has already authenticated. No API key, no third-party adapter.",
5
5
  "license": "MIT",
6
6
  "repository": {