@adhdev/daemon-core 0.9.4 → 0.9.6
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/config/chat-history.d.ts +4 -3
- package/dist/daemon/dev-auto-implement.d.ts +6 -0
- package/dist/index.js +194 -186
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +194 -186
- package/dist/index.mjs.map +1 -1
- package/dist/providers/cli-provider-instance.d.ts +7 -0
- package/dist/providers/contracts.d.ts +111 -0
- package/dist/providers/provider-session-id.d.ts +6 -2
- package/node_modules/@adhdev/session-host-core/package.json +1 -1
- package/package.json +1 -1
- package/src/commands/cli-manager.ts +19 -10
- package/src/commands/stream-commands.ts +2 -3
- package/src/config/chat-history.ts +33 -32
- package/src/config/state-store.ts +6 -14
- package/src/daemon/dev-auto-implement.ts +36 -45
- package/src/providers/cli-provider-instance.ts +92 -83
- package/src/providers/contracts.ts +115 -0
- package/src/providers/provider-schema.ts +6 -0
- package/src/providers/provider-session-id.ts +17 -15
|
@@ -51,6 +51,13 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
51
51
|
private runtimeMessages;
|
|
52
52
|
private lastPersistedHistoryMessages;
|
|
53
53
|
private lastCanonicalHermesSyncMtimeMs;
|
|
54
|
+
private lastCanonicalHermesExistCheckAt;
|
|
55
|
+
private lastCanonicalHermesWatchPath;
|
|
56
|
+
private lastCanonicalClaudeRebuildMtimeMs;
|
|
57
|
+
private lastCanonicalClaudeCheckAt;
|
|
58
|
+
private cachedSqliteDb;
|
|
59
|
+
private cachedSqliteDbPath;
|
|
60
|
+
private cachedSqliteDbMissingUntil;
|
|
54
61
|
readonly instanceId: string;
|
|
55
62
|
private suppressIdleHistoryReplay;
|
|
56
63
|
private errorMessage;
|
|
@@ -329,12 +329,21 @@ export interface ProviderModule {
|
|
|
329
329
|
extensionIdPattern_flags?: string;
|
|
330
330
|
compatibility?: ProviderCompatibilityEntry[];
|
|
331
331
|
defaultScriptDir?: string;
|
|
332
|
+
/**
|
|
333
|
+
* Scripts that can run at the IDE main-page level (not just inside the extension webview session frame).
|
|
334
|
+
* Default: ['listModes', 'setMode', 'listModels', 'setModel'].
|
|
335
|
+
* Add extra scripts here if the provider supports them at the IDE level (e.g. 'setModelGui').
|
|
336
|
+
* Replaces hardcoded claude-code-vscode special-case in stream-commands.ts.
|
|
337
|
+
*/
|
|
338
|
+
ideLevelScripts?: string[];
|
|
332
339
|
binary?: string;
|
|
333
340
|
spawn?: {
|
|
334
341
|
command: string;
|
|
335
342
|
args?: string[];
|
|
336
343
|
shell?: boolean;
|
|
337
344
|
env?: Record<string, string>;
|
|
345
|
+
/** Auto-implement spawn config — controls how this provider is invoked for autonomous script generation */
|
|
346
|
+
autoImpl?: ProviderAutoImplSpawnConfig;
|
|
338
347
|
};
|
|
339
348
|
/** Delay before submitting typed CLI input (provider-specific TUI tuning) */
|
|
340
349
|
sendDelayMs?: number;
|
|
@@ -360,6 +369,24 @@ export interface ProviderModule {
|
|
|
360
369
|
allowInputDuringGeneration?: boolean;
|
|
361
370
|
/** Approval button priority hints used when auto-approve must pick a positive action */
|
|
362
371
|
approvalPositiveHints?: string[];
|
|
372
|
+
/**
|
|
373
|
+
* Regex pattern (as string) that a valid provider session ID must match.
|
|
374
|
+
* If set and the ID doesn't match, it is rejected (treated as invalid).
|
|
375
|
+
* Replaces hardcoded HERMES_SESSION_ID_RE / CLAUDE_SESSION_ID_RE checks.
|
|
376
|
+
*/
|
|
377
|
+
sessionIdPattern?: string;
|
|
378
|
+
/** History behavior config — controls message filtering and collapse during replay */
|
|
379
|
+
historyBehavior?: ProviderHistoryBehavior;
|
|
380
|
+
/**
|
|
381
|
+
* Canonical history sync config — for providers that maintain native history files.
|
|
382
|
+
* When set, daemon syncs from native format into ADHDev JSONL store on each tick.
|
|
383
|
+
*/
|
|
384
|
+
canonicalHistory?: ProviderCanonicalHistoryConfig;
|
|
385
|
+
/**
|
|
386
|
+
* Auto-fix verification profile — provider-specific test expectations for `provider fix`.
|
|
387
|
+
* If not set, provider fix runs without pre/post verification.
|
|
388
|
+
*/
|
|
389
|
+
autoFixProfile?: ProviderAutoFixProfile;
|
|
363
390
|
scripts?: ProviderScripts;
|
|
364
391
|
vscodeCommands?: {
|
|
365
392
|
focusPanel?: string;
|
|
@@ -438,6 +465,90 @@ export interface ProviderResumeCapability {
|
|
|
438
465
|
resumeSessionArgs?: string[];
|
|
439
466
|
newSessionArgs?: string[];
|
|
440
467
|
sessionIdFormat?: 'uuid' | 'string';
|
|
468
|
+
/** Skip session ID probing when launchMode is 'new' — for providers that manage their own session IDs on new sessions */
|
|
469
|
+
skipProbeOnNewSession?: boolean;
|
|
470
|
+
/**
|
|
471
|
+
* Subcommands that carry a session ID as their next positional argument.
|
|
472
|
+
* e.g. ['resume', 'fork'] for codex-cli (codex resume <id> / codex fork <id>).
|
|
473
|
+
* Replaces the hardcoded readCodexResumeSessionId check in cli-manager.ts.
|
|
474
|
+
*/
|
|
475
|
+
sessionIdFromSubcommand?: string[];
|
|
476
|
+
/**
|
|
477
|
+
* When --session-id is present without an explicit resume flag, treat as 'new' rather than 'resume'.
|
|
478
|
+
* e.g. goose-cli passes --session-id on new sessions but requires --resume/-r to actually resume.
|
|
479
|
+
* Replaces the hardcoded goose-cli check in cli-manager.ts.
|
|
480
|
+
*/
|
|
481
|
+
sessionIdIsNewByDefault?: boolean;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* History behavior config — controls how history messages are processed for this provider.
|
|
485
|
+
* Replaces hardcoded agentType checks in chat-history.ts.
|
|
486
|
+
*/
|
|
487
|
+
export interface ProviderHistoryBehavior {
|
|
488
|
+
/** Collapse consecutive assistant turns during history replay (e.g. codex-cli shows replayed intermediate turns) */
|
|
489
|
+
collapseConsecutiveAssistantTurns?: boolean;
|
|
490
|
+
/** Regex patterns (as strings) to filter out from assistant messages — e.g. CLI starter prompt suggestions */
|
|
491
|
+
filterAssistantPatterns?: string[];
|
|
492
|
+
/** If true, session ID must match sessionIdPattern exactly — reject and return '' if it doesn't match */
|
|
493
|
+
requireStrictSessionIdFormat?: boolean;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Canonical history sync config — for providers that maintain their own native history files.
|
|
497
|
+
* When set, daemon syncs from the provider's native format into the ADHDev JSONL store.
|
|
498
|
+
* Replaces hardcoded hermes-cli / claude-cli checks in cli-provider-instance.ts.
|
|
499
|
+
*/
|
|
500
|
+
export interface ProviderCanonicalHistoryConfig {
|
|
501
|
+
/**
|
|
502
|
+
* Native history format.
|
|
503
|
+
* - 'hermes-json': single JSON file per session (~/.hermes/sessions/session_{{sessionId}}.json)
|
|
504
|
+
* - 'claude-jsonl': JSONL transcript under ~/.claude/projects/
|
|
505
|
+
*/
|
|
506
|
+
format: 'hermes-json' | 'claude-jsonl';
|
|
507
|
+
/**
|
|
508
|
+
* Path to the native history file. Supports ~ and {{sessionId}} placeholder.
|
|
509
|
+
* e.g. "~/.hermes/sessions/session_{{sessionId}}.json"
|
|
510
|
+
*/
|
|
511
|
+
watchPath: string;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Auto-implement spawn config — controls how the provider is spawned for autonomous AI-driven
|
|
515
|
+
* provider script implementation (dev-auto-implement.ts).
|
|
516
|
+
* Replaces hardcoded per-command branching.
|
|
517
|
+
*/
|
|
518
|
+
export interface ProviderAutoImplSpawnConfig {
|
|
519
|
+
/**
|
|
520
|
+
* How the meta-prompt is passed to the agent.
|
|
521
|
+
* - 'flag': passed via a CLI flag (e.g. `claude -p "..."`)
|
|
522
|
+
* - 'stdin': piped via stdin (generic fallback)
|
|
523
|
+
* - 'subcommand': prepended as a subcommand (e.g. `codex exec "..."`)
|
|
524
|
+
*/
|
|
525
|
+
promptMode: 'flag' | 'stdin' | 'subcommand';
|
|
526
|
+
/** CLI flag used to pass the prompt (promptMode: 'flag') — e.g. '-p' */
|
|
527
|
+
promptFlag?: string;
|
|
528
|
+
/** Subcommand prepended before the prompt (promptMode: 'subcommand') — e.g. 'exec' */
|
|
529
|
+
subcommand?: string;
|
|
530
|
+
/** Extra args appended in auto-impl mode — e.g. ['--dangerously-skip-permissions'] */
|
|
531
|
+
extraArgs?: string[];
|
|
532
|
+
/** Custom meta-prompt template; use {{promptFile}} placeholder. If omitted, generic prompt is used. */
|
|
533
|
+
metaPrompt?: string;
|
|
534
|
+
/**
|
|
535
|
+
* If true, schedule an auto-stop timer when the agent output goes quiet during verification.
|
|
536
|
+
* Replaces the hardcoded `command !== 'codex'` check in dev-auto-implement.ts.
|
|
537
|
+
*/
|
|
538
|
+
autoStopOnQuiet?: boolean;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Auto-fix verification profile — provider-specific test expectations for `provider fix`.
|
|
542
|
+
* Replaces the hardcoded CLI_AUTO_FIX_VERIFICATION_PROFILES record in provider-commands.ts.
|
|
543
|
+
*/
|
|
544
|
+
export interface ProviderAutoFixProfile {
|
|
545
|
+
fixtureName: string;
|
|
546
|
+
description: string;
|
|
547
|
+
inspectFields?: string[];
|
|
548
|
+
focusAreas?: string[];
|
|
549
|
+
lastAssistantMustContainAny?: string[];
|
|
550
|
+
lastAssistantMustNotContainAny?: string[];
|
|
551
|
+
timeoutMs?: number;
|
|
441
552
|
}
|
|
442
553
|
/**
|
|
443
554
|
* Declarative session ID probe config for CLI providers.
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { ProviderModule } from './contracts.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalize and validate a provider session ID using the declarative `sessionIdPattern`
|
|
4
|
+
* from the provider's ProviderModule definition.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalizeProviderSessionId(provider: ProviderModule | undefined, providerSessionId: string | null | undefined): string;
|
package/package.json
CHANGED
|
@@ -131,8 +131,8 @@ function expandResumeArgs(template: string[] | undefined, sessionId: string): st
|
|
|
131
131
|
return template.map((part) => part === '{{id}}' ? sessionId : part);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
function
|
|
135
|
-
const resumeIndex = args.findIndex((arg) => arg
|
|
134
|
+
function readSubcommandSessionId(args: string[], subcommands: string[]): string | undefined {
|
|
135
|
+
const resumeIndex = args.findIndex((arg) => subcommands.includes(arg));
|
|
136
136
|
if (resumeIndex < 0) return undefined;
|
|
137
137
|
const candidate = args[resumeIndex + 1];
|
|
138
138
|
if (!candidate || candidate.startsWith('-')) return undefined;
|
|
@@ -140,9 +140,11 @@ function readCodexResumeSessionId(args: string[]): string | undefined {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
function detectExplicitProviderSessionId(
|
|
143
|
-
|
|
143
|
+
provider: ProviderModule | undefined,
|
|
144
144
|
args: string[],
|
|
145
145
|
): { providerSessionId?: string; launchMode: CliLaunchMode } {
|
|
146
|
+
const resume = provider?.resume;
|
|
147
|
+
|
|
146
148
|
const explicitResumeId = readArgValue(args, ['--resume', '-r']);
|
|
147
149
|
if (explicitResumeId) {
|
|
148
150
|
return { providerSessionId: explicitResumeId, launchMode: 'resume' };
|
|
@@ -158,10 +160,10 @@ function detectExplicitProviderSessionId(
|
|
|
158
160
|
|
|
159
161
|
const explicitSessionId = readArgValue(args, ['--session-id']);
|
|
160
162
|
if (explicitSessionId) {
|
|
161
|
-
if (
|
|
163
|
+
if (resume?.sessionIdIsNewByDefault && !hasArg(args, ['--resume', '-r'])) {
|
|
162
164
|
return { launchMode: 'manual' };
|
|
163
165
|
}
|
|
164
|
-
const isResume =
|
|
166
|
+
const isResume = resume?.sessionIdIsNewByDefault
|
|
165
167
|
? hasArg(args, ['--resume', '-r'])
|
|
166
168
|
: (hasArg(args, ['--continue']) || hasArg(args, ['--resume', '-r']));
|
|
167
169
|
return {
|
|
@@ -170,10 +172,11 @@ function detectExplicitProviderSessionId(
|
|
|
170
172
|
};
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
const subcommands = resume?.sessionIdFromSubcommand;
|
|
176
|
+
if (Array.isArray(subcommands) && subcommands.length > 0) {
|
|
177
|
+
const subcommandSessionId = readSubcommandSessionId(args, subcommands);
|
|
178
|
+
if (subcommandSessionId) {
|
|
179
|
+
return { providerSessionId: subcommandSessionId, launchMode: 'resume' };
|
|
177
180
|
}
|
|
178
181
|
}
|
|
179
182
|
|
|
@@ -200,7 +203,7 @@ export function resolveCliSessionBinding(
|
|
|
200
203
|
return { cliArgs: baseArgs, launchMode: 'manual' };
|
|
201
204
|
}
|
|
202
205
|
|
|
203
|
-
const explicit = detectExplicitProviderSessionId(
|
|
206
|
+
const explicit = detectExplicitProviderSessionId(provider, baseArgs || []);
|
|
204
207
|
if (explicit.providerSessionId) {
|
|
205
208
|
return {
|
|
206
209
|
cliArgs: baseArgs,
|
|
@@ -208,6 +211,12 @@ export function resolveCliSessionBinding(
|
|
|
208
211
|
launchMode: explicit.launchMode,
|
|
209
212
|
};
|
|
210
213
|
}
|
|
214
|
+
if (explicit.launchMode === 'manual' && hasArg(baseArgs || [], ['--session-id'])) {
|
|
215
|
+
return {
|
|
216
|
+
cliArgs: baseArgs,
|
|
217
|
+
launchMode: 'manual',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
211
220
|
|
|
212
221
|
if (requestedResumeSessionId) {
|
|
213
222
|
if (resume.sessionIdFormat === 'uuid' && !isUuid(requestedResumeSessionId)) {
|
|
@@ -382,9 +382,8 @@ async function executeProviderScript(h: CommandHelpers, args: any, scriptName: s
|
|
|
382
382
|
const targetSessionId = managed?.cdpSessionId || null;
|
|
383
383
|
|
|
384
384
|
// IDE-level scripts (model/mode) — try session frame first, fallback to main page
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
: ['listModes', 'setMode', 'listModels', 'setModel'];
|
|
385
|
+
const DEFAULT_IDE_LEVEL_SCRIPTS = ['listModes', 'setMode', 'listModels', 'setModel'];
|
|
386
|
+
const IDE_LEVEL_SCRIPTS = provider.ideLevelScripts ?? DEFAULT_IDE_LEVEL_SCRIPTS;
|
|
388
387
|
if (IDE_LEVEL_SCRIPTS.includes(scriptName)) {
|
|
389
388
|
// Try session frame first (some extensions embed mode selector in their webview)
|
|
390
389
|
if (targetSessionId) {
|
|
@@ -13,7 +13,7 @@ import * as fs from 'fs';
|
|
|
13
13
|
import * as path from 'path';
|
|
14
14
|
import * as os from 'os';
|
|
15
15
|
import { buildRuntimeSystemChatMessage } from '../providers/chat-message-normalization.js';
|
|
16
|
-
import {
|
|
16
|
+
import type { ProviderHistoryBehavior } from '../providers/contracts.js';
|
|
17
17
|
|
|
18
18
|
const HISTORY_DIR = path.join(os.homedir(), '.adhdev', 'history');
|
|
19
19
|
const RETAIN_DAYS = 30;
|
|
@@ -72,24 +72,27 @@ interface HistoryMessage {
|
|
|
72
72
|
workspace?: string; // Working directory at session start (kind: 'session_start' only)
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const CODEX_STARTER_PROMPT_RE = /^(?:[›❯]\s*)?(?:Find and fix a bug in @filename|Improve documentation in @filename|Write tests for @filename|Explain this codebase|Summarize recent commits|Implement \{feature\}|Use \/skills(?: to list available skills)?|Run \/review on my current changes)$/i;
|
|
76
|
-
|
|
77
75
|
function normalizeHistoryComparable(text: string): string {
|
|
78
76
|
return String(text || '').replace(/\s+/g, ' ').trim();
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
function cleanupHistoryContent(agentType: string, role: HistoryMessage['role'], content: string): string {
|
|
79
|
+
function cleanupHistoryContent(agentType: string, role: HistoryMessage['role'], content: string, historyBehavior?: ProviderHistoryBehavior): string {
|
|
82
80
|
let value = String(content || '').replace(/\r\n/g, '\n').trim();
|
|
83
81
|
if (!value) return '';
|
|
84
82
|
|
|
85
|
-
if (
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
if (role === 'assistant' && historyBehavior?.filterAssistantPatterns?.length) {
|
|
84
|
+
const filters = historyBehavior.filterAssistantPatterns.map((p) => {
|
|
85
|
+
try { return new RegExp(p, 'i'); } catch { return null; }
|
|
86
|
+
}).filter(Boolean) as RegExp[];
|
|
87
|
+
if (filters.length > 0) {
|
|
88
|
+
const filtered = value
|
|
89
|
+
.split('\n')
|
|
90
|
+
.filter((line) => !filters.some((re) => re.test(line.trim())))
|
|
91
|
+
.join('\n')
|
|
92
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
93
|
+
.trim();
|
|
94
|
+
value = filtered;
|
|
95
|
+
}
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
return value;
|
|
@@ -121,8 +124,8 @@ function isAdjacentHistoryDuplicate(
|
|
|
121
124
|
return buildHistoryMessageSignature(agentType, previous) === buildHistoryMessageSignature(agentType, next);
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
function collapseReplayAssistantTurns(
|
|
125
|
-
if (
|
|
127
|
+
function collapseReplayAssistantTurns(messages: HistoryMessage[], historyBehavior?: ProviderHistoryBehavior): HistoryMessage[] {
|
|
128
|
+
if (!historyBehavior?.collapseConsecutiveAssistantTurns) return messages;
|
|
126
129
|
|
|
127
130
|
const collapsed: HistoryMessage[] = [];
|
|
128
131
|
let sawAssistantSinceLastUser = false;
|
|
@@ -257,17 +260,13 @@ function listHistoryFiles(dir: string, historySessionId?: string): string[] {
|
|
|
257
260
|
.reverse();
|
|
258
261
|
}
|
|
259
262
|
|
|
260
|
-
function normalizeSavedHistorySessionId(
|
|
261
|
-
|
|
262
|
-
if (!normalizedId) return '';
|
|
263
|
-
const strictProviderId = normalizeProviderSessionId(agentType, normalizedId);
|
|
264
|
-
if (strictProviderId) return strictProviderId;
|
|
265
|
-
return agentType === 'hermes-cli' ? '' : normalizedId;
|
|
263
|
+
function normalizeSavedHistorySessionId(historySessionId: string): string {
|
|
264
|
+
return String(historySessionId || '').trim();
|
|
266
265
|
}
|
|
267
266
|
|
|
268
|
-
function extractSavedHistorySessionIdFromFile(
|
|
267
|
+
function extractSavedHistorySessionIdFromFile(file: string): string {
|
|
269
268
|
const match = file.match(/^([A-Za-z0-9_-]+)_\d{4}-\d{2}-\d{2}\.jsonl$/);
|
|
270
|
-
return normalizeSavedHistorySessionId(
|
|
269
|
+
return normalizeSavedHistorySessionId(match?.[1] || '');
|
|
271
270
|
}
|
|
272
271
|
|
|
273
272
|
function buildSavedHistoryFileSignatureMap(dir: string, files: string[]): Map<string, string> {
|
|
@@ -484,7 +483,7 @@ function persistSavedHistoryFileSummaryEntry(agentType: string, dir: string, fil
|
|
|
484
483
|
}
|
|
485
484
|
|
|
486
485
|
function updateSavedHistoryIndexForSessionStart(agentType: string, dir: string, file: string, historySessionId: string, workspace: string): void {
|
|
487
|
-
const normalizedSessionId = normalizeSavedHistorySessionId(
|
|
486
|
+
const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId);
|
|
488
487
|
const normalizedWorkspace = String(workspace || '').trim();
|
|
489
488
|
if (!normalizedSessionId || !normalizedWorkspace) return;
|
|
490
489
|
persistSavedHistoryFileSummaryEntry(agentType, dir, file, (currentSummary) => ({
|
|
@@ -506,7 +505,7 @@ function updateSavedHistoryIndexForAppendedMessages(
|
|
|
506
505
|
historySessionId: string | undefined,
|
|
507
506
|
messages: HistoryMessage[],
|
|
508
507
|
): void {
|
|
509
|
-
const normalizedSessionId = normalizeSavedHistorySessionId(
|
|
508
|
+
const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId || '');
|
|
510
509
|
if (!normalizedSessionId || messages.length === 0) return;
|
|
511
510
|
persistSavedHistoryFileSummaryEntry(agentType, dir, file, (currentSummary) => {
|
|
512
511
|
const nextSummary: SavedHistoryFileSummary = {
|
|
@@ -546,8 +545,8 @@ function updateSavedHistoryIndexForAppendedMessages(
|
|
|
546
545
|
});
|
|
547
546
|
}
|
|
548
547
|
|
|
549
|
-
function computeSavedHistoryFileSummary(
|
|
550
|
-
const historySessionId = extractSavedHistorySessionIdFromFile(
|
|
548
|
+
function computeSavedHistoryFileSummary(dir: string, file: string): SavedHistoryFileSummary | null {
|
|
549
|
+
const historySessionId = extractSavedHistorySessionIdFromFile(file);
|
|
551
550
|
if (!historySessionId) return null;
|
|
552
551
|
|
|
553
552
|
const filePath = path.join(dir, file);
|
|
@@ -660,7 +659,7 @@ function computeSavedHistorySessionSummaries(
|
|
|
660
659
|
: persisted?.signature === signature
|
|
661
660
|
? persisted
|
|
662
661
|
: null;
|
|
663
|
-
const fileSummary = reusableEntry?.summary || computeSavedHistoryFileSummary(
|
|
662
|
+
const fileSummary = reusableEntry?.summary || computeSavedHistoryFileSummary(dir, file);
|
|
664
663
|
const nextEntry: SavedHistoryFileSummaryCacheEntry = reusableEntry || {
|
|
665
664
|
signature,
|
|
666
665
|
summary: fileSummary,
|
|
@@ -1027,7 +1026,7 @@ export class ChatHistoryWriter {
|
|
|
1027
1026
|
}
|
|
1028
1027
|
}
|
|
1029
1028
|
|
|
1030
|
-
compactHistorySession(agentType: string, historySessionId: string): void {
|
|
1029
|
+
compactHistorySession(agentType: string, historySessionId: string, historyBehavior?: ProviderHistoryBehavior): void {
|
|
1031
1030
|
const sessionId = String(historySessionId || '').trim();
|
|
1032
1031
|
if (!sessionId) return;
|
|
1033
1032
|
|
|
@@ -1072,7 +1071,7 @@ export class ChatHistoryWriter {
|
|
|
1072
1071
|
dedupedAdjacent.push(entry);
|
|
1073
1072
|
if (entry.role !== 'system') lastTurn = entry;
|
|
1074
1073
|
}
|
|
1075
|
-
const collapsed = collapseReplayAssistantTurns(
|
|
1074
|
+
const collapsed = collapseReplayAssistantTurns(dedupedAdjacent, historyBehavior);
|
|
1076
1075
|
if (collapsed.length === 0) {
|
|
1077
1076
|
fs.unlinkSync(filePath);
|
|
1078
1077
|
continue;
|
|
@@ -1145,6 +1144,7 @@ export function readChatHistory(
|
|
|
1145
1144
|
limit: number = 30,
|
|
1146
1145
|
historySessionId?: string,
|
|
1147
1146
|
excludeRecentCount: number = 0,
|
|
1147
|
+
historyBehavior?: ProviderHistoryBehavior,
|
|
1148
1148
|
): { messages: HistoryMessage[]; hasMore: boolean } {
|
|
1149
1149
|
try {
|
|
1150
1150
|
const sanitized = agentType.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
@@ -1185,7 +1185,7 @@ export function readChatHistory(
|
|
|
1185
1185
|
chronological.push(message);
|
|
1186
1186
|
if (message.role !== 'system') lastTurn = message;
|
|
1187
1187
|
}
|
|
1188
|
-
const collapsed = collapseReplayAssistantTurns(
|
|
1188
|
+
const collapsed = collapseReplayAssistantTurns(chronological, historyBehavior);
|
|
1189
1189
|
|
|
1190
1190
|
// Page backwards from the newest saved messages while keeping the returned
|
|
1191
1191
|
// slice in chronological order for prepend-based UI rendering.
|
|
@@ -1206,6 +1206,7 @@ export function readChatHistory(
|
|
|
1206
1206
|
export function listSavedHistorySessions(
|
|
1207
1207
|
agentType: string,
|
|
1208
1208
|
options: { offset?: number; limit?: number } = {},
|
|
1209
|
+
historyBehavior?: ProviderHistoryBehavior,
|
|
1209
1210
|
): { sessions: SavedHistorySessionSummary[]; hasMore: boolean } {
|
|
1210
1211
|
try {
|
|
1211
1212
|
const sanitized = agentType.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
@@ -1351,7 +1352,7 @@ function rewriteCanonicalSavedHistory(agentType: string, historySessionId: strin
|
|
|
1351
1352
|
}
|
|
1352
1353
|
|
|
1353
1354
|
export function rebuildHermesSavedHistoryFromCanonicalSession(historySessionId: string): boolean {
|
|
1354
|
-
const normalizedSessionId = normalizeSavedHistorySessionId(
|
|
1355
|
+
const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId);
|
|
1355
1356
|
if (!normalizedSessionId) return false;
|
|
1356
1357
|
|
|
1357
1358
|
try {
|
|
@@ -1523,7 +1524,7 @@ function extractClaudeUserContentParts(content: unknown): Array<{ role: 'user' |
|
|
|
1523
1524
|
}
|
|
1524
1525
|
|
|
1525
1526
|
export function rebuildClaudeSavedHistoryFromNativeProject(historySessionId: string, workspace?: string): boolean {
|
|
1526
|
-
const normalizedSessionId = normalizeSavedHistorySessionId(
|
|
1527
|
+
const normalizedSessionId = normalizeSavedHistorySessionId(historySessionId);
|
|
1527
1528
|
if (!normalizedSessionId) return false;
|
|
1528
1529
|
|
|
1529
1530
|
try {
|
|
@@ -12,7 +12,6 @@ import { join } from 'path';
|
|
|
12
12
|
import { getConfigDir } from './config.js';
|
|
13
13
|
import type { RecentActivityEntry } from './recent-activity.js';
|
|
14
14
|
import type { SavedProviderSessionEntry } from './saved-sessions.js';
|
|
15
|
-
import { isLegacyVolatileSessionReadKey, normalizeProviderSessionId } from '../providers/provider-session-id.js';
|
|
16
15
|
|
|
17
16
|
export interface DaemonState {
|
|
18
17
|
/** Unified recent activity across IDE / CLI / ACP launch flows */
|
|
@@ -52,38 +51,31 @@ function normalizeState(raw: unknown): DaemonState {
|
|
|
52
51
|
const recentActivity = (Array.isArray(parsed.recentActivity) ? parsed.recentActivity : [])
|
|
53
52
|
.filter((entry): entry is RecentActivityEntry => {
|
|
54
53
|
if (!isPlainObject(entry)) return false;
|
|
55
|
-
|
|
56
|
-
typeof entry.providerType === 'string' ? entry.providerType : '',
|
|
57
|
-
typeof entry.providerSessionId === 'string' ? entry.providerSessionId : '',
|
|
58
|
-
);
|
|
59
|
-
if (typeof entry.providerSessionId === 'string' && !normalizedId) return false;
|
|
54
|
+
if (typeof entry.providerSessionId === 'string' && !entry.providerSessionId.trim()) return false;
|
|
60
55
|
return true;
|
|
61
56
|
});
|
|
62
57
|
|
|
63
58
|
const savedProviderSessions = (Array.isArray(parsed.savedProviderSessions) ? parsed.savedProviderSessions : [])
|
|
64
59
|
.filter((entry): entry is SavedProviderSessionEntry => {
|
|
65
60
|
if (!isPlainObject(entry)) return false;
|
|
66
|
-
return !!
|
|
67
|
-
typeof entry.providerType === 'string' ? entry.providerType : '',
|
|
68
|
-
typeof entry.providerSessionId === 'string' ? entry.providerSessionId : '',
|
|
69
|
-
);
|
|
61
|
+
return typeof entry.providerSessionId === 'string' && !!entry.providerSessionId.trim();
|
|
70
62
|
});
|
|
71
63
|
|
|
72
64
|
const sessionReads = Object.fromEntries(
|
|
73
65
|
Object.entries(isPlainObject(parsed.sessionReads) ? parsed.sessionReads : {})
|
|
74
|
-
.filter(([
|
|
66
|
+
.filter(([, value]) => typeof value === 'number' && Number.isFinite(value as number))
|
|
75
67
|
);
|
|
76
68
|
const sessionReadMarkers = Object.fromEntries(
|
|
77
69
|
Object.entries(isPlainObject(parsed.sessionReadMarkers) ? parsed.sessionReadMarkers : {})
|
|
78
|
-
.filter(([
|
|
70
|
+
.filter(([, value]) => typeof value === 'string')
|
|
79
71
|
);
|
|
80
72
|
const sessionNotificationDismissals = Object.fromEntries(
|
|
81
73
|
Object.entries(isPlainObject(parsed.sessionNotificationDismissals) ? parsed.sessionNotificationDismissals : {})
|
|
82
|
-
.filter(([
|
|
74
|
+
.filter(([, value]) => typeof value === 'string' && value.length > 0)
|
|
83
75
|
);
|
|
84
76
|
const sessionNotificationUnreadOverrides = Object.fromEntries(
|
|
85
77
|
Object.entries(isPlainObject(parsed.sessionNotificationUnreadOverrides) ? parsed.sessionNotificationUnreadOverrides : {})
|
|
86
|
-
.filter(([
|
|
78
|
+
.filter(([, value]) => typeof value === 'string' && value.length > 0)
|
|
87
79
|
);
|
|
88
80
|
|
|
89
81
|
return {
|
|
@@ -67,20 +67,18 @@ function tryKillAutoImplProcess(processRef: ChildProcess | null, signal: NodeJS.
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
export function shouldScheduleAutoStopOnQuiet(options: {
|
|
71
|
+
verification?: unknown;
|
|
72
|
+
autoImpl?: { autoStopOnQuiet?: boolean } | null;
|
|
73
|
+
}): boolean {
|
|
74
|
+
return !!options.verification && options.autoImpl?.autoStopOnQuiet === true;
|
|
75
|
+
}
|
|
76
|
+
|
|
70
77
|
export function getDefaultAutoImplReference(ctx: DevServerContext, category: string, type: string): string {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
const preferred = ['claude-code-vscode', 'codex', 'cline', 'roo-code'];
|
|
76
|
-
for (const ref of preferred) {
|
|
77
|
-
if (ref === type) continue;
|
|
78
|
-
if (ctx.providerLoader.resolve(ref) || ctx.providerLoader.getMeta(ref)) return ref;
|
|
79
|
-
}
|
|
80
|
-
const all = ctx.providerLoader.getAll();
|
|
81
|
-
const fb = all.find((p: any) => p.category === 'extension' && p.type !== type);
|
|
82
|
-
if (fb?.type) return fb.type;
|
|
83
|
-
}
|
|
78
|
+
const all = ctx.providerLoader.getAll();
|
|
79
|
+
// Pick any other provider in the same category as a reference
|
|
80
|
+
const sameCategoryOther = all.find((p: any) => p.category === category && p.type !== type);
|
|
81
|
+
if (sameCategoryOther?.type) return sameCategoryOther.type;
|
|
84
82
|
return 'antigravity';
|
|
85
83
|
}
|
|
86
84
|
|
|
@@ -424,49 +422,43 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
|
|
|
424
422
|
return;
|
|
425
423
|
}
|
|
426
424
|
|
|
427
|
-
// ─── CLI Agent:
|
|
425
|
+
// ─── CLI Agent: declarative autoImpl config from provider.json ───
|
|
428
426
|
const command: string = spawn.command;
|
|
427
|
+
const autoImpl = spawn.autoImpl;
|
|
429
428
|
// Strip interactive-only flags for auto-implement (non-interactive mode)
|
|
430
429
|
const interactiveFlags = ['--yolo', '--interactive', '-i'];
|
|
431
430
|
const baseArgs: string[] = [...(spawn.args || [])].filter((a: string) => !interactiveFlags.includes(a));
|
|
432
431
|
|
|
433
|
-
// 6. Construct the complete shell command
|
|
432
|
+
// 6. Construct the complete shell command from provider.json autoImpl config
|
|
434
433
|
let shellCmd: string;
|
|
435
434
|
const isWin = os.platform() === 'win32';
|
|
436
435
|
const escapeArg = (a: string) => isWin ? `"${a.replace(/"/g, '""')}"` : `'${a.replace(/'/g, "'\\''")}'`;
|
|
437
436
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
437
|
+
const promptMode = autoImpl?.promptMode ?? 'stdin';
|
|
438
|
+
const extraArgs = autoImpl?.extraArgs ?? [];
|
|
439
|
+
const rawMetaPrompt = autoImpl?.metaPrompt
|
|
440
|
+
? autoImpl.metaPrompt.replace('{{promptFile}}', promptFile)
|
|
441
|
+
: `Read the file at ${promptFile} and follow ALL the instructions in it exactly. Do not ask questions, just execute.`;
|
|
442
|
+
|
|
443
|
+
if (promptMode === 'flag') {
|
|
444
|
+
const flag = autoImpl?.promptFlag ?? '-p';
|
|
445
|
+
const args = [...baseArgs, ...extraArgs];
|
|
441
446
|
if (model) args.push('--model', model);
|
|
442
447
|
const escapedArgs = args.map(escapeArg).join(' ');
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const args = [...baseArgs, '-y', '-s', 'false'];
|
|
450
|
-
if (model) args.push('-m', model);
|
|
451
|
-
const escapedArgs = args.map(escapeArg).join(' ');
|
|
452
|
-
const metaPrompt = `Read the file at ${promptFile} and follow ALL the instructions in it exactly. Do not ask questions, just execute.`;
|
|
453
|
-
shellCmd = `${command} ${escapedArgs} -p ${escapeArg(metaPrompt)}`;
|
|
454
|
-
|
|
455
|
-
} else if (command === 'codex') {
|
|
456
|
-
const args = ['exec', ...baseArgs];
|
|
457
|
-
if (!args.includes('--dangerously-bypass-approvals-and-sandbox')) {
|
|
458
|
-
args.push('--dangerously-bypass-approvals-and-sandbox');
|
|
459
|
-
}
|
|
460
|
-
if (!args.includes('--skip-git-repo-check')) {
|
|
461
|
-
args.push('--skip-git-repo-check');
|
|
448
|
+
shellCmd = `${command} ${escapedArgs} ${flag} ${escapeArg(rawMetaPrompt)}`;
|
|
449
|
+
} else if (promptMode === 'subcommand') {
|
|
450
|
+
const subcommand = autoImpl?.subcommand ?? '';
|
|
451
|
+
const args = subcommand ? [subcommand, ...baseArgs] : [...baseArgs];
|
|
452
|
+
for (const extra of extraArgs) {
|
|
453
|
+
if (!args.includes(extra)) args.push(extra);
|
|
462
454
|
}
|
|
463
455
|
if (model) args.push('--model', model);
|
|
464
456
|
const escapedArgs = args.map(escapeArg).join(' ');
|
|
465
|
-
|
|
466
|
-
shellCmd = `${command} ${escapedArgs} ${escapeArg(metaPrompt)}`;
|
|
457
|
+
shellCmd = `${command} ${escapedArgs} ${escapeArg(rawMetaPrompt)}`;
|
|
467
458
|
} else {
|
|
468
|
-
//
|
|
469
|
-
const
|
|
459
|
+
// stdin fallback (generic)
|
|
460
|
+
const args = [...baseArgs, ...extraArgs];
|
|
461
|
+
const escapedArgs = args.map(escapeArg).join(' ');
|
|
470
462
|
if (isWin) {
|
|
471
463
|
shellCmd = `type "${promptFile}" | ${command} ${escapedArgs}`;
|
|
472
464
|
} else {
|
|
@@ -503,10 +495,9 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
|
|
|
503
495
|
shell: false,
|
|
504
496
|
timeout: 900000,
|
|
505
497
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
506
|
-
env: {
|
|
507
|
-
...process.env,
|
|
498
|
+
env: {
|
|
499
|
+
...process.env,
|
|
508
500
|
...(spawn.env || {}),
|
|
509
|
-
...(command === 'gemini' ? { SANDBOX: '1', GEMINI_CLI_NO_RELAUNCH: '1' } : {}),
|
|
510
501
|
},
|
|
511
502
|
});
|
|
512
503
|
child.on('error', (err: Error) => {
|
|
@@ -582,7 +573,7 @@ export async function handleAutoImplement(ctx: DevServerContext, type: string, r
|
|
|
582
573
|
};
|
|
583
574
|
|
|
584
575
|
const scheduleAutoStopForVerification = () => {
|
|
585
|
-
if (!verification
|
|
576
|
+
if (!shouldScheduleAutoStopOnQuiet({ verification, autoImpl }) || completionSignalSeen || autoStopIssued) return;
|
|
586
577
|
const elapsed = Date.now() - spawnedAt;
|
|
587
578
|
if (elapsed < 30000) return;
|
|
588
579
|
clearAutoStopTimer();
|