@adhdev/daemon-core 0.9.82-rc.164 → 0.9.82-rc.165

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.
@@ -4,6 +4,15 @@ export declare class SpecCliAdapter implements CliAdapter {
4
4
  readonly cliType: string;
5
5
  readonly cliName: string;
6
6
  readonly workingDir: string;
7
+ /**
8
+ * Marker the daemon's finalization gate checks: `getStatus()` returns
9
+ * `messages: []` by design here (chat history lives in the daemon's
10
+ * native-history pipeline, not the adapter). Without this flag,
11
+ * cli-provider-instance's `missing_final_assistant` gate would stall
12
+ * every turn until the 30s safety timeout because it expects the
13
+ * adapter to surface the final assistant message.
14
+ */
15
+ readonly chatMessagesOwnedExternally: true;
7
16
  private driver;
8
17
  private spec;
9
18
  private lastEvent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.164",
3
+ "version": "0.9.82-rc.165",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -966,7 +966,15 @@ export class CliProviderInstance implements ProviderInstance {
966
966
  return { reason: `parsed_status:${parsedStatus}`, terminal: isCliGeneratingLikeStatus(parsedStatus) };
967
967
  }
968
968
  if (parsed?.activeModal || parsed?.modal) return { reason: 'parsed_modal_active', terminal: true };
969
- if (!this.completionHasFinalAssistantMessage(parsed?.messages)) return { reason: 'missing_final_assistant' };
969
+ // SpecCliAdapter never populates parsed.messages chat history flows
970
+ // through the daemon's native-history pipeline, not the status hook.
971
+ // Skipping the final-assistant gate avoids a 30s stall on every turn
972
+ // for spec-routed providers (agy / codex / claude / hermes).
973
+ const adapterOwnsMessagesElsewhere = (this.adapter as any)?.chatMessagesOwnedExternally === true;
974
+ if (!adapterOwnsMessagesElsewhere
975
+ && !this.completionHasFinalAssistantMessage(parsed?.messages)) {
976
+ return { reason: 'missing_final_assistant' };
977
+ }
970
978
 
971
979
  // Guard: if the screen still shows an approval/choice prompt as the last visible text,
972
980
  // the turn is not complete even if the parsed status says idle and there is an assistant
@@ -948,18 +948,28 @@ export class ProviderLoader {
948
948
 
949
949
  for (const entry of compat) {
950
950
  if (this.matchesVersion(currentVersion, entry.ideVersion)) {
951
- const loaded = this.loadScriptsFromDir(type, entry.scriptDir);
952
- if (loaded) {
953
- resolved.scripts = loaded;
954
- this.debugLog(` [compatibility] ${type} v${currentVersion} ${entry.scriptDir}`);
955
- resolved._resolvedScriptDir = entry.scriptDir;
956
- resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
957
- if (providerDir) {
958
- const fullDir = path.join(providerDir, entry.scriptDir);
959
- resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
960
- ? path.join(fullDir, 'scripts.js')
961
- : fullDir;
951
+ // entry.scriptDir is optional now — spec-driven providers (agy,
952
+ // codex on >=0.137, claude on >=2.1) only ship `spec` here, so
953
+ // there's nothing to load from the filesystem. SpecCliAdapter
954
+ // takes over via the `spec` path later in this method.
955
+ if (entry.scriptDir) {
956
+ const loaded = this.loadScriptsFromDir(type, entry.scriptDir);
957
+ if (loaded) {
958
+ resolved.scripts = loaded;
959
+ this.debugLog(` [compatibility] ${type} v${currentVersion} → ${entry.scriptDir}`);
960
+ resolved._resolvedScriptDir = entry.scriptDir;
961
+ resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
962
+ if (providerDir) {
963
+ const fullDir = path.join(providerDir, entry.scriptDir);
964
+ resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
965
+ ? path.join(fullDir, 'scripts.js')
966
+ : fullDir;
967
+ }
968
+ matched = true;
962
969
  }
970
+ } else {
971
+ // Spec-only entry — still counts as a match so the
972
+ // defaultScriptDir fallback below doesn't kick in.
963
973
  matched = true;
964
974
  }
965
975
  break; // first match wins
@@ -237,14 +237,15 @@
237
237
  "minItems": 1,
238
238
  "items": {
239
239
  "type": "object",
240
- "required": ["scriptDir"],
240
+ "required": ["ideVersion"],
241
241
  "additionalProperties": false,
242
242
  "properties": {
243
243
  "ideVersion": { "type": "string", "description": "SemVer range." },
244
- "scriptDir": { "type": "string", "pattern": "^scripts/[^/]+$" }
244
+ "scriptDir": { "type": "string", "pattern": "^scripts/[^/]+$" },
245
+ "spec": { "type": "string", "pattern": "^specs/[^/]+\\.json$", "description": "Path to declarative spec.json driving SpecCliAdapter for this version range." }
245
246
  }
246
247
  },
247
- "description": "Maps installed agent versions to script subdirectories."
248
+ "description": "Maps installed agent versions to script subdirectories and/or declarative specs."
248
249
  },
249
250
  "defaultScriptDir": {
250
251
  "type": "string",
@@ -30,6 +30,15 @@ export class SpecCliAdapter implements CliAdapter {
30
30
  readonly cliType: string;
31
31
  readonly cliName: string;
32
32
  readonly workingDir: string;
33
+ /**
34
+ * Marker the daemon's finalization gate checks: `getStatus()` returns
35
+ * `messages: []` by design here (chat history lives in the daemon's
36
+ * native-history pipeline, not the adapter). Without this flag,
37
+ * cli-provider-instance's `missing_final_assistant` gate would stall
38
+ * every turn until the 30s safety timeout because it expects the
39
+ * adapter to surface the final assistant message.
40
+ */
41
+ readonly chatMessagesOwnedExternally = true as const;
33
42
 
34
43
  private driver: SpecDriver;
35
44
  private spec: CliSpec;
@@ -256,9 +256,20 @@ function expandPath(template: string, input: NativeHistoryInput): string | null
256
256
  // Re-expand ~ in case the fallback used it.
257
257
  if (out.startsWith('~/')) out = path.join(os.homedir(), out.slice(2));
258
258
  const now = new Date();
259
+ // claude (and a couple of other Anthropic-CLI–style providers) writes
260
+ // its per-cwd transcript under the *resolved* path
261
+ // (/private/tmp/foo, not /tmp/foo on macOS where /tmp -> /private/tmp).
262
+ // Without realpath, the spec template `~/.claude/projects/{cwd_dashed}/…`
263
+ // builds `-tmp-foo` and never finds the actual `-private-tmp-foo` dir.
264
+ const workspaceRaw = input.workspace ?? '';
265
+ let workspaceResolved = workspaceRaw;
266
+ if (workspaceRaw) {
267
+ try { workspaceResolved = fs.realpathSync(workspaceRaw); }
268
+ catch { /* path may not exist yet — keep the raw value */ }
269
+ }
259
270
  const vars: Record<string, string> = {
260
- cwd: input.workspace ?? '',
261
- cwd_dashed: (input.workspace ?? '').replace(/\//g, '-'),
271
+ cwd: workspaceResolved,
272
+ cwd_dashed: workspaceResolved.replace(/\//g, '-'),
262
273
  session_id: input.providerSessionId || input.sessionId || input.historySessionId || '',
263
274
  yyyy: String(now.getUTCFullYear()),
264
275
  mm: String(now.getUTCMonth() + 1).padStart(2, '0'),