@adhdev/daemon-core 0.9.82-rc.163 → 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.
- package/dist/index.js +50 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +50 -24
- package/dist/index.mjs.map +1 -1
- package/dist/providers/spec/cli-adapter.d.ts +9 -0
- package/package.json +1 -1
- package/src/commands/router.ts +21 -9
- package/src/providers/cli-provider-instance.ts +9 -1
- package/src/providers/provider-loader.ts +21 -11
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +4 -3
- package/src/providers/spec/cli-adapter.ts +9 -0
- package/src/providers/spec/native-history-executor.ts +13 -2
|
@@ -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
package/src/commands/router.ts
CHANGED
|
@@ -4088,24 +4088,36 @@ export class DaemonCommandRouter {
|
|
|
4088
4088
|
const sessionId = typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim()
|
|
4089
4089
|
: typeof args?.sessionId === 'string' ? args.sessionId.trim() : '';
|
|
4090
4090
|
if (!sessionId) return { success: false, error: 'targetSessionId required' };
|
|
4091
|
+
// Fetch both lookups up front. We used to bail with "Session not
|
|
4092
|
+
// found" when sessionRegistry forgot the SID (auto-cleanup,
|
|
4093
|
+
// daemon restart with the session not yet restored, etc), which
|
|
4094
|
+
// hid the coordinator-side metadata even though the
|
|
4095
|
+
// coordinator-registry still has it. Now we return whichever
|
|
4096
|
+
// side we have. The dashboard renders "no coordinator-specific
|
|
4097
|
+
// prompt" only when *neither* side knows the session.
|
|
4091
4098
|
const target = this.deps.sessionRegistry.get(sessionId);
|
|
4092
|
-
|
|
4093
|
-
|
|
4099
|
+
const coord = getCoordinatorForSession(sessionId);
|
|
4100
|
+
if (!target && !coord) return { success: false, error: 'Session not found', sessionId };
|
|
4101
|
+
const adapter = target
|
|
4102
|
+
? this.deps.cliManager.findAdapter(target.providerType, { instanceKey: sessionId })?.adapter
|
|
4103
|
+
: undefined;
|
|
4094
4104
|
const runtimeMeta = (adapter && typeof (adapter as any).getRuntimeMetadata === 'function')
|
|
4095
4105
|
? (adapter as any).getRuntimeMetadata()
|
|
4096
4106
|
: undefined;
|
|
4097
|
-
const
|
|
4098
|
-
const providerMetaForSession =
|
|
4107
|
+
const providerType = target?.providerType || coord?.cliType || '';
|
|
4108
|
+
const providerMetaForSession = providerType
|
|
4109
|
+
? this.deps.providerLoader.resolve?.(providerType) || this.deps.providerLoader.getMeta(providerType)
|
|
4110
|
+
: undefined;
|
|
4099
4111
|
return {
|
|
4100
4112
|
success: true,
|
|
4101
4113
|
session: {
|
|
4102
4114
|
sessionId,
|
|
4103
|
-
providerType
|
|
4115
|
+
providerType,
|
|
4104
4116
|
providerName: providerMetaForSession?.name,
|
|
4105
|
-
transport: target
|
|
4106
|
-
workspace: (target as any)
|
|
4107
|
-
spawnedAtMs: (target as any)
|
|
4108
|
-
providerSessionId: (target as any)
|
|
4117
|
+
transport: target?.transport,
|
|
4118
|
+
workspace: (target as any)?.workspace || coord?.workspace,
|
|
4119
|
+
spawnedAtMs: (target as any)?.spawnedAtMs || coord?.startedAt,
|
|
4120
|
+
providerSessionId: (target as any)?.providerSessionId,
|
|
4109
4121
|
runtimeMetadata: runtimeMeta,
|
|
4110
4122
|
},
|
|
4111
4123
|
coordinator: coord ? {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
if (
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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": ["
|
|
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:
|
|
261
|
-
cwd_dashed:
|
|
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'),
|