@adhdev/daemon-core 0.9.82-rc.144 → 0.9.82-rc.146

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.
@@ -445,8 +445,16 @@ export interface ProviderModule {
445
445
  /** History behavior config — controls message filtering and collapse during replay */
446
446
  historyBehavior?: ProviderHistoryBehavior;
447
447
  /**
448
- * Canonical history sync config — for providers that maintain native history files.
449
- * When set, daemon syncs from native format into ADHDev JSONL store on each tick.
448
+ * Native history config — for providers that maintain native history files.
449
+ * When set, daemon reads/lists provider-native transcripts directly. This is
450
+ * the canonical v1 field name; the legacy `canonicalHistory` field name is
451
+ * still accepted by the loader and aliased onto this field at load time.
452
+ */
453
+ nativeHistory?: NativeHistoryConfig;
454
+ /**
455
+ * @deprecated Legacy v0 alias for {@link ProviderModule.nativeHistory}.
456
+ * Loader populates this from `nativeHistory` so existing internal readers
457
+ * keep working during the transition. Remove after one release.
450
458
  */
451
459
  canonicalHistory?: ProviderCanonicalHistoryConfig;
452
460
  /**
@@ -577,20 +585,25 @@ export interface ProviderHistoryBehavior {
577
585
  * daemon-core. They let each provider own native transcript file discovery and
578
586
  * parsing while daemon-core only validates/pages the normalized result.
579
587
  */
580
- export interface ProviderCanonicalHistoryScriptsConfig {
588
+ export interface NativeHistoryScriptsConfig {
581
589
  /** Reads one native session. Default: 'readNativeHistory'. */
582
590
  readSession?: string;
583
591
  /** Lists native sessions with summary metadata. Default: 'listNativeHistory'. */
584
592
  listSessions?: string;
585
593
  }
586
594
  /**
587
- * Canonical history sync config for providers that maintain their own native history files.
595
+ * @deprecated Use {@link NativeHistoryScriptsConfig}. Retained as an alias for
596
+ * one release so external consumers that referenced the old name keep compiling.
597
+ */
598
+ export type ProviderCanonicalHistoryScriptsConfig = NativeHistoryScriptsConfig;
599
+ /**
600
+ * Native history config — for providers that maintain their own native history files.
588
601
  *
589
602
  * Preferred mode is provider-owned scripts via `scripts`. `format` is now an
590
603
  * opaque provider label retained for diagnostics/backward compatibility; daemon
591
604
  * live paths must not branch on provider-specific format values.
592
605
  */
593
- export interface ProviderCanonicalHistoryConfig {
606
+ export interface NativeHistoryConfig {
594
607
  /** Opaque provider-owned history format label. */
595
608
  format?: string;
596
609
  /** Optional native history glob/template for diagnostics only. */
@@ -618,6 +631,11 @@ export interface ProviderCanonicalHistoryConfig {
618
631
  */
619
632
  contractVersion?: import('./transcript-v2.js').ChatContractVersion;
620
633
  }
634
+ /**
635
+ * @deprecated Use {@link NativeHistoryConfig}. Retained as an alias for one
636
+ * release so external consumers that referenced the old name keep compiling.
637
+ */
638
+ export type ProviderCanonicalHistoryConfig = NativeHistoryConfig;
621
639
  /**
622
640
  * Auto-implement spawn config — controls how the provider is spawned for autonomous AI-driven
623
641
  * provider script implementation (dev-auto-implement.ts).
@@ -40,12 +40,20 @@ export interface TranscriptPtySpec {
40
40
  stripLeadingChrome?: boolean;
41
41
  scope?: 'screen' | 'buffer' | 'tail';
42
42
  }
43
+ export interface SessionIdExtractionSpec {
44
+ $schema?: 'adhdev:tui/session-id-extraction@1';
45
+ regex: string;
46
+ flags?: string;
47
+ scope?: 'screen' | 'tail' | 'buffer';
48
+ label?: string;
49
+ }
43
50
  export interface ParseSessionTuiSpec {
44
51
  spinner?: any;
45
52
  settledPrompt?: any;
46
53
  modal?: ModalTuiSpec;
47
54
  dispatchOrder?: any;
48
55
  transcriptPty: TranscriptPtySpec;
56
+ sessionIdExtraction?: SessionIdExtractionSpec;
49
57
  }
50
58
  export interface SynthesizedMessage {
51
59
  role: 'user' | 'assistant';
@@ -67,6 +75,7 @@ export interface SynthesizedSession {
67
75
  parsedStatus: string | null;
68
76
  transcriptAuthority?: 'provider' | 'daemon';
69
77
  coverage?: 'full' | 'tail' | 'current-turn';
78
+ providerSessionId?: string;
70
79
  }
71
80
  export declare function normalizeMessageIdentity<T extends {
72
81
  role?: string;
@@ -12,7 +12,7 @@ export { buildDetectStatusFromTui, type DetectStatusTuiSpec, } from './builders/
12
12
  export { buildParseApprovalFromTui, type ModalTuiSpec, } from './builders/cli/parse-approval.js';
13
13
  export { applyVisibleRegion, type VisibleRegionSpec, } from './builders/cli/visible-region.js';
14
14
  export { buildParseApprovalFromSquash, compactText, type ApprovalSquashSpec, } from './builders/cli/parse-approval-squash.js';
15
- export { buildParseSessionFromTui, normalizeMessageIdentity, type ParseSessionTuiSpec, type TranscriptPtySpec, type SynthesizedMessage, type SynthesizedSession, } from './builders/cli/parse-session.js';
15
+ export { buildParseSessionFromTui, normalizeMessageIdentity, type ParseSessionTuiSpec, type TranscriptPtySpec, type SessionIdExtractionSpec, type SynthesizedMessage, type SynthesizedSession, } from './builders/cli/parse-session.js';
16
16
  export { buildDetectStatusFromAcp, type AcpSessionSpec, type AcpStatusInput, type AcpDetectedStatus, } from './builders/acp/detect-status.js';
17
17
  export { loadFixtureExpected, replayFixture, formatReplayReport, type FixtureExpected, type FixtureAnchor, type MessageShape, type AnchorReplayResult, type FixtureReplayResult, type CliProviderHandlers, type ReplayOptions, } from './fixture-tooling/index.js';
18
18
  export { analyzeOverrideTaint, formatTaintResult, type TaintLevel, type TaintCategory, type TaintFinding, type TaintResult, validateCliProviderManifest, formatManifestValidationIssues, type ManifestValidationIssue, type ManifestValidationResult, } from './validators/index.js';
@@ -191,7 +191,9 @@ export interface CliListNativeHistoryEntry {
191
191
  export type CliListNativeHistoryFn = () => {
192
192
  sessions: CliListNativeHistoryEntry[];
193
193
  };
194
- export interface CliCanonicalHistoryDef {
194
+ export interface CliNativeHistoryDef {
195
+ /** Built-in adapter $schema (e.g. "adhdev:native-history/claude-jsonl@1"). */
196
+ $schema?: string;
195
197
  format: string;
196
198
  watchPath: string;
197
199
  mode: 'native-source' | 'daemon-mirror';
@@ -201,6 +203,11 @@ export interface CliCanonicalHistoryDef {
201
203
  listSessions?: string;
202
204
  };
203
205
  }
206
+ /**
207
+ * @deprecated Use {@link CliNativeHistoryDef}. Retained as an alias for one
208
+ * release so SDK consumers that referenced the v0 name keep compiling.
209
+ */
210
+ export type CliCanonicalHistoryDef = CliNativeHistoryDef;
204
211
  export interface CliPatternEntry {
205
212
  source: string;
206
213
  flags?: string;
@@ -259,7 +266,9 @@ export interface CliProviderManifest {
259
266
  capabilities?: CapabilitiesDef;
260
267
  /** Mesh coordinator setup */
261
268
  meshCoordinator?: MeshCoordinatorDef;
262
- /** Native history */
269
+ /** Native history (v1 canonical field name) */
270
+ nativeHistory?: CliNativeHistoryDef;
271
+ /** @deprecated v0 alias for `nativeHistory`. */
263
272
  canonicalHistory?: CliCanonicalHistoryDef;
264
273
  /** Settings (UI toggles persisted to ~/.adhdev/config.json) */
265
274
  settings?: SettingsDef;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.144",
3
+ "version": "0.9.82-rc.146",
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",
@@ -904,7 +904,7 @@ function supportsCliNativeTranscript(providerType: string, provider?: ProviderMo
904
904
  // provider.json. We trust that declaration regardless of the legacy
905
905
  // allow-list. A2 will additionally require canonicalHistory.contractVersion
906
906
  // to be a supported value (transcript-v2.ts).
907
- if (provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
907
+ if (provider?.category === 'cli' && isNativeSourceCanonicalHistory(provider?.nativeHistory)) {
908
908
  return true;
909
909
  }
910
910
  // Last-resort fallback for early call sites where the provider module is
@@ -1429,7 +1429,7 @@ function summarizeProviderForDebug(provider: ProviderModule | undefined): Record
1429
1429
  name: provider.name,
1430
1430
  category: provider.category,
1431
1431
  version: (provider as any).version,
1432
- canonicalHistory: provider.canonicalHistory,
1432
+ canonicalHistory: provider.nativeHistory,
1433
1433
  historyBehavior: provider.historyBehavior,
1434
1434
  webviewMatchText: provider.webviewMatchText,
1435
1435
  scriptNames: scripts,
@@ -1809,9 +1809,9 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
1809
1809
  || (typeof args?.historySessionId === 'string' && args.historySessionId.trim())
1810
1810
  || (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim())
1811
1811
  );
1812
- const result = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
1812
+ const result = supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.nativeHistory)
1813
1813
  ? readCliProviderNativeHistory(agentStr, {
1814
- canonicalHistory: provider?.canonicalHistory,
1814
+ canonicalHistory: provider?.nativeHistory,
1815
1815
  historySessionId,
1816
1816
  workspace,
1817
1817
  offset: offset || 0,
@@ -1821,7 +1821,7 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
1821
1821
  scripts: provider?.scripts as any,
1822
1822
  })
1823
1823
  : readProviderChatHistory(agentStr, {
1824
- canonicalHistory: provider?.canonicalHistory,
1824
+ canonicalHistory: provider?.nativeHistory,
1825
1825
  historySessionId,
1826
1826
  workspace,
1827
1827
  offset: offset || 0,
@@ -1830,7 +1830,7 @@ export async function handleChatHistory(h: CommandHelpers, args: any): Promise<C
1830
1830
  historyBehavior: provider?.historyBehavior,
1831
1831
  scripts: provider?.scripts as any,
1832
1832
  });
1833
- if (supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.canonicalHistory)) {
1833
+ if (supportsCliNativeTranscript(agentStr, provider) && isNativeSourceCanonicalHistory(provider?.nativeHistory)) {
1834
1834
  const lookup = (result as any).lookup === 'workspace' ? 'workspace' : 'session';
1835
1835
  const messages = Array.isArray((result as any).messages)
1836
1836
  ? normalizeNativeHistoryMessages(agentStr, (result as any).messages as ChatMessage[], (result as any)?.providerSessionId)
@@ -1969,7 +1969,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
1969
1969
  // ───────────────────────────────────────────────────────────
1970
1970
 
1971
1971
  const supportsNative = supportsCliNativeTranscript(providerType, provider)
1972
- && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
1972
+ && isNativeSourceCanonicalHistory(provider?.nativeHistory);
1973
1973
  const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
1974
1974
  const workspace = sessionWorkspace;
1975
1975
  const nativeHistoryLimit = Math.max(
@@ -1995,7 +1995,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
1995
1995
  if (supportsNative) {
1996
1996
  try {
1997
1997
  nativeHistory = readCliProviderNativeHistory(agentStr, {
1998
- canonicalHistory: provider?.canonicalHistory,
1998
+ canonicalHistory: provider?.nativeHistory,
1999
1999
  historySessionId: nativeHistorySessionId,
2000
2000
  workspace,
2001
2001
  offset: 0,
@@ -2089,7 +2089,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2089
2089
  && (!historyProviderSessionId || historyProviderSessionId === nativeHistorySessionId || historyProviderSessionId === historySessionId);
2090
2090
  const liveWorkspaceNativeHistory = mayProbeLiveCodexWorkspaceNative
2091
2091
  ? readLiveCodexWorkspaceNativeHistory(agentStr, {
2092
- canonicalHistory: provider?.canonicalHistory,
2092
+ canonicalHistory: provider?.nativeHistory,
2093
2093
  workspace,
2094
2094
  offset: 0,
2095
2095
  limit: nativeHistoryLimit,
@@ -2200,7 +2200,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2200
2200
  selectedMessageSource: (messageSource as any).selected,
2201
2201
  messageSource,
2202
2202
  shouldPreferAdapterMessages: supportsCliNativeTranscript(providerType, provider)
2203
- && isNativeSourceCanonicalHistory(provider?.canonicalHistory)
2203
+ && isNativeSourceCanonicalHistory(provider?.nativeHistory)
2204
2204
  && (messageSource as any).selected !== 'native-history'
2205
2205
  && typeof (messageSource as any).fallbackReason === 'string'
2206
2206
  && (messageSource as any).fallbackReason.startsWith('native_history_')
@@ -2229,10 +2229,10 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2229
2229
  : undefined;
2230
2230
  const intendedWorkspace = typeof args?.workspace === 'string' ? args.workspace : undefined;
2231
2231
  const supportsNative = supportsCliNativeTranscript(agentStr, provider)
2232
- && isNativeSourceCanonicalHistory(provider?.canonicalHistory);
2232
+ && isNativeSourceCanonicalHistory(provider?.nativeHistory);
2233
2233
  const history = supportsNative
2234
2234
  ? readCliProviderNativeHistory(agentStr, {
2235
- canonicalHistory: provider?.canonicalHistory,
2235
+ canonicalHistory: provider?.nativeHistory,
2236
2236
  historySessionId,
2237
2237
  workspace,
2238
2238
  offset: 0,
@@ -2242,7 +2242,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
2242
2242
  scripts: provider?.scripts as any,
2243
2243
  })
2244
2244
  : readProviderChatHistory(agentStr, {
2245
- canonicalHistory: provider?.canonicalHistory,
2245
+ canonicalHistory: provider?.nativeHistory,
2246
2246
  historySessionId,
2247
2247
  workspace,
2248
2248
  offset: 0,
@@ -3855,7 +3855,7 @@ export class DaemonCommandRouter {
3855
3855
  : '';
3856
3856
  const providerMeta = this.deps.providerLoader.resolve?.(providerType) || this.deps.providerLoader.getMeta(providerType);
3857
3857
  const { sessions: historySessions, hasMore, source } = listProviderHistorySessions(providerType, {
3858
- canonicalHistory: providerMeta?.canonicalHistory,
3858
+ canonicalHistory: providerMeta?.nativeHistory,
3859
3859
  offset,
3860
3860
  limit,
3861
3861
  historyBehavior: providerMeta?.historyBehavior,
@@ -1692,7 +1692,7 @@ export class CliProviderInstance implements ProviderInstance {
1692
1692
 
1693
1693
  private syncCanonicalSavedHistoryIfNeeded(): boolean {
1694
1694
  if (!this.providerSessionId) return false;
1695
- const canonicalHistory = this.provider.canonicalHistory;
1695
+ const canonicalHistory = this.provider.nativeHistory;
1696
1696
  if (!canonicalHistory) return false;
1697
1697
 
1698
1698
  if (isNativeSourceCanonicalHistory(canonicalHistory)) {
@@ -1754,9 +1754,9 @@ export class CliProviderInstance implements ProviderInstance {
1754
1754
  private restorePersistedHistoryFromCurrentSession(): void {
1755
1755
  if (!this.providerSessionId) return;
1756
1756
  this.syncCanonicalSavedHistoryIfNeeded();
1757
- const restoredHistory = isNativeSourceCanonicalHistory(this.provider.canonicalHistory)
1757
+ const restoredHistory = isNativeSourceCanonicalHistory(this.provider.nativeHistory)
1758
1758
  ? readProviderChatHistory(this.type, {
1759
- canonicalHistory: this.provider.canonicalHistory,
1759
+ canonicalHistory: this.provider.nativeHistory,
1760
1760
  historySessionId: this.providerSessionId,
1761
1761
  workspace: this.workingDir,
1762
1762
  offset: 0,
@@ -541,8 +541,16 @@ export interface ProviderModule {
541
541
  /** History behavior config — controls message filtering and collapse during replay */
542
542
  historyBehavior?: ProviderHistoryBehavior;
543
543
  /**
544
- * Canonical history sync config — for providers that maintain native history files.
545
- * When set, daemon syncs from native format into ADHDev JSONL store on each tick.
544
+ * Native history config — for providers that maintain native history files.
545
+ * When set, daemon reads/lists provider-native transcripts directly. This is
546
+ * the canonical v1 field name; the legacy `canonicalHistory` field name is
547
+ * still accepted by the loader and aliased onto this field at load time.
548
+ */
549
+ nativeHistory?: NativeHistoryConfig;
550
+ /**
551
+ * @deprecated Legacy v0 alias for {@link ProviderModule.nativeHistory}.
552
+ * Loader populates this from `nativeHistory` so existing internal readers
553
+ * keep working during the transition. Remove after one release.
546
554
  */
547
555
  canonicalHistory?: ProviderCanonicalHistoryConfig;
548
556
  /**
@@ -688,7 +696,7 @@ export interface ProviderHistoryBehavior {
688
696
  * daemon-core. They let each provider own native transcript file discovery and
689
697
  * parsing while daemon-core only validates/pages the normalized result.
690
698
  */
691
- export interface ProviderCanonicalHistoryScriptsConfig {
699
+ export interface NativeHistoryScriptsConfig {
692
700
  /** Reads one native session. Default: 'readNativeHistory'. */
693
701
  readSession?: string;
694
702
  /** Lists native sessions with summary metadata. Default: 'listNativeHistory'. */
@@ -696,13 +704,19 @@ export interface ProviderCanonicalHistoryScriptsConfig {
696
704
  }
697
705
 
698
706
  /**
699
- * Canonical history sync config for providers that maintain their own native history files.
707
+ * @deprecated Use {@link NativeHistoryScriptsConfig}. Retained as an alias for
708
+ * one release so external consumers that referenced the old name keep compiling.
709
+ */
710
+ export type ProviderCanonicalHistoryScriptsConfig = NativeHistoryScriptsConfig;
711
+
712
+ /**
713
+ * Native history config — for providers that maintain their own native history files.
700
714
  *
701
715
  * Preferred mode is provider-owned scripts via `scripts`. `format` is now an
702
716
  * opaque provider label retained for diagnostics/backward compatibility; daemon
703
717
  * live paths must not branch on provider-specific format values.
704
718
  */
705
- export interface ProviderCanonicalHistoryConfig {
719
+ export interface NativeHistoryConfig {
706
720
  /** Opaque provider-owned history format label. */
707
721
  format?: string;
708
722
  /** Optional native history glob/template for diagnostics only. */
@@ -731,6 +745,12 @@ export interface ProviderCanonicalHistoryConfig {
731
745
  contractVersion?: import('./transcript-v2.js').ChatContractVersion;
732
746
  }
733
747
 
748
+ /**
749
+ * @deprecated Use {@link NativeHistoryConfig}. Retained as an alias for one
750
+ * release so external consumers that referenced the old name keep compiling.
751
+ */
752
+ export type ProviderCanonicalHistoryConfig = NativeHistoryConfig;
753
+
734
754
  /**
735
755
  * Auto-implement spawn config — controls how the provider is spawned for autonomous AI-driven
736
756
  * provider script implementation (dev-auto-implement.ts).
@@ -1958,15 +1958,20 @@ export class ProviderLoader {
1958
1958
  ...(extensionIdPattern instanceof RegExp ? { extensionIdPattern } : {}),
1959
1959
  };
1960
1960
 
1961
- // v1 manifest contract calls this block `nativeHistory`; the
1962
- // daemon's runtime + downstream code still reads
1963
- // `canonicalHistory` (the legacy name used since v0). Alias
1964
- // them when only the v1 spelling is present so authors using
1965
- // the new contract get the same behavior without having to
1966
- // duplicate the block. If both are set the explicit
1967
- // canonicalHistory wins caller signaled it intentionally.
1968
- if ((normalizedProvider as any).nativeHistory && !(normalizedProvider as any).canonicalHistory) {
1969
- (normalizedProvider as any).canonicalHistory = (normalizedProvider as any).nativeHistory;
1961
+ // v1 manifests use `nativeHistory` as the canonical field name.
1962
+ // Legacy v0 manifests use `canonicalHistory`. The daemon's
1963
+ // runtime + downstream code reads `provider.nativeHistory`, so
1964
+ // for legacy manifests we copy `canonicalHistory` into
1965
+ // `nativeHistory` here. We also keep `canonicalHistory`
1966
+ // populated in both directions (deprecated alias) so any
1967
+ // external consumers still reading the old name keep working
1968
+ // during the one-release deprecation window.
1969
+ const nh = (normalizedProvider as any).nativeHistory;
1970
+ const ch = (normalizedProvider as any).canonicalHistory;
1971
+ if (nh && !ch) {
1972
+ (normalizedProvider as any).canonicalHistory = nh;
1973
+ } else if (ch && !nh) {
1974
+ (normalizedProvider as any).nativeHistory = ch;
1970
1975
  }
1971
1976
 
1972
1977
  const validation = validateProviderDefinition(normalizedProvider);
@@ -37,10 +37,13 @@ const KNOWN_PROVIDER_FIELDS = new Set<string>([
37
37
  'approvalPositiveHints',
38
38
  'sessionIdPattern',
39
39
  'historyBehavior',
40
- 'canonicalHistory',
41
- // v1 contract spelling; the loader aliases nativeHistory →
42
- // canonicalHistory so downstream code reads the legacy name.
40
+ // v1 canonical field name. The loader fills this in for legacy v0
41
+ // manifests that ship the old `canonicalHistory` spelling.
43
42
  'nativeHistory',
43
+ // Deprecated v0 alias for `nativeHistory`. Kept in the allowed-keys
44
+ // list so legacy provider.json manifests don't trip the unknown-key
45
+ // warning during the one-release deprecation window.
46
+ 'canonicalHistory',
44
47
  'autoFixProfile',
45
48
  'ideLevelScripts',
46
49
  'allowInputDuringGeneration',
@@ -133,7 +136,7 @@ export function validateProviderDefinition(raw: unknown): ProviderValidationResu
133
136
  }
134
137
 
135
138
  validateCapabilities(provider as unknown as ProviderModule, controls, errors)
136
- validateCanonicalHistory(provider.canonicalHistory, errors)
139
+ validateNativeHistory(provider.nativeHistory, errors)
137
140
  validateMeshCoordinator(provider.meshCoordinator, errors)
138
141
 
139
142
  for (const control of controls) {
@@ -233,43 +236,43 @@ function validateCapabilities(provider: ProviderModule, controls: ProviderContro
233
236
  }
234
237
  }
235
238
 
236
- function validateCanonicalHistory(raw: unknown, errors: string[]): void {
239
+ function validateNativeHistory(raw: unknown, errors: string[]): void {
237
240
  if (raw === undefined) return
238
241
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
239
- errors.push('canonicalHistory must be an object')
242
+ errors.push('nativeHistory must be an object')
240
243
  return
241
244
  }
242
245
 
243
- const canonicalHistory = raw as Record<string, unknown>
244
- const format = canonicalHistory.format
246
+ const nativeHistory = raw as Record<string, unknown>
247
+ const format = nativeHistory.format
245
248
  if (format !== undefined && (typeof format !== 'string' || !format.trim())) {
246
- errors.push('canonicalHistory.format must be a non-empty string when provided')
249
+ errors.push('nativeHistory.format must be a non-empty string when provided')
247
250
  }
248
251
 
249
- const watchPath = canonicalHistory.watchPath
252
+ const watchPath = nativeHistory.watchPath
250
253
  if (watchPath !== undefined && (typeof watchPath !== 'string' || !watchPath.trim())) {
251
- errors.push('canonicalHistory.watchPath must be a non-empty string when provided')
254
+ errors.push('nativeHistory.watchPath must be a non-empty string when provided')
252
255
  }
253
256
 
254
- const mode = canonicalHistory.mode
257
+ const mode = nativeHistory.mode
255
258
  if (mode !== undefined && !['native-source', 'materialized-mirror', 'disabled'].includes(String(mode))) {
256
- errors.push('canonicalHistory.mode must be one of: native-source, materialized-mirror, disabled')
259
+ errors.push('nativeHistory.mode must be one of: native-source, materialized-mirror, disabled')
257
260
  }
258
261
 
259
262
  // Chat transcript contract version (transcript-v2.ts). Absent → treated as
260
263
  // v1 by readDeclaredChatContractVersion(). A2 will reject unrecognised
261
264
  // values at load time; A1 only validates the field shape.
262
- const chatContractVersion = canonicalHistory.contractVersion
265
+ const chatContractVersion = nativeHistory.contractVersion
263
266
  if (chatContractVersion !== undefined
264
267
  && chatContractVersion !== '1.0'
265
268
  && chatContractVersion !== '2.0') {
266
- errors.push(`canonicalHistory.contractVersion must be '1.0' or '2.0' when provided (got ${JSON.stringify(chatContractVersion)})`)
269
+ errors.push(`nativeHistory.contractVersion must be '1.0' or '2.0' when provided (got ${JSON.stringify(chatContractVersion)})`)
267
270
  }
268
271
 
269
- const scripts = canonicalHistory.scripts
272
+ const scripts = nativeHistory.scripts
270
273
  if (scripts === undefined) return
271
274
  if (!scripts || typeof scripts !== 'object' || Array.isArray(scripts)) {
272
- errors.push('canonicalHistory.scripts must be an object')
275
+ errors.push('nativeHistory.scripts must be an object')
273
276
  return
274
277
  }
275
278
 
@@ -277,7 +280,7 @@ function validateCanonicalHistory(raw: unknown, errors: string[]): void {
277
280
  for (const key of ['readSession', 'listSessions']) {
278
281
  const value = scriptConfig[key]
279
282
  if (typeof value !== 'string' || !value.trim()) {
280
- errors.push(`canonicalHistory.scripts.${key} must be a non-empty string`)
283
+ errors.push(`nativeHistory.scripts.${key} must be a non-empty string`)
281
284
  }
282
285
  }
283
286
  }
@@ -48,12 +48,21 @@ export interface TranscriptPtySpec {
48
48
  scope?: 'screen' | 'buffer' | 'tail'
49
49
  }
50
50
 
51
+ export interface SessionIdExtractionSpec {
52
+ $schema?: 'adhdev:tui/session-id-extraction@1'
53
+ regex: string
54
+ flags?: string
55
+ scope?: 'screen' | 'tail' | 'buffer'
56
+ label?: string
57
+ }
58
+
51
59
  export interface ParseSessionTuiSpec {
52
60
  spinner?: any
53
61
  settledPrompt?: any
54
62
  modal?: ModalTuiSpec
55
63
  dispatchOrder?: any
56
64
  transcriptPty: TranscriptPtySpec
65
+ sessionIdExtraction?: SessionIdExtractionSpec
57
66
  }
58
67
 
59
68
  // ─── Output shape ────────────────────────────────
@@ -73,6 +82,7 @@ export interface SynthesizedSession {
73
82
  parsedStatus: string | null
74
83
  transcriptAuthority?: 'provider' | 'daemon'
75
84
  coverage?: 'full' | 'tail' | 'current-turn'
85
+ providerSessionId?: string
76
86
  }
77
87
 
78
88
  // ─── ANSI / line helpers ─────────────────────────
@@ -176,6 +186,16 @@ export function buildParseSessionFromTui(spec: ParseSessionTuiSpec): (input: any
176
186
  : () => null
177
187
  const parseApproval = spec.modal ? buildParseApprovalFromTui(spec.modal) : () => null
178
188
 
189
+ // Optional session-id extraction (tui/session-id-extraction@1). Compile once
190
+ // so each parseSession call is a single regex.exec on the chosen scope.
191
+ const sessionIdRe = spec.sessionIdExtraction
192
+ ? new RegExp(
193
+ spec.sessionIdExtraction.regex,
194
+ spec.sessionIdExtraction.flags ?? 'i',
195
+ )
196
+ : null
197
+ const sessionIdScope: TranscriptPtySpec['scope'] = spec.sessionIdExtraction?.scope ?? 'tail'
198
+
179
199
  return function parseSession(input: any): SynthesizedSession {
180
200
  // status + modal first
181
201
  const status = detectStatus(input as any) ?? 'idle'
@@ -236,12 +256,21 @@ export function buildParseSessionFromTui(spec: ParseSessionTuiSpec): (input: any
236
256
  if (cont) last.content = last.content ? `${last.content}\n${cont}` : cont
237
257
  }
238
258
 
239
- return {
259
+ const result: SynthesizedSession = {
240
260
  status,
241
261
  messages,
242
262
  activeModal: modal,
243
263
  modal,
244
264
  parsedStatus: status,
245
265
  }
266
+
267
+ if (sessionIdRe) {
268
+ const haystack = stripAnsi(pickInputText(input, sessionIdScope))
269
+ const match = haystack.match(sessionIdRe)
270
+ const captured = match?.[1]?.trim()
271
+ if (captured) result.providerSessionId = captured
272
+ }
273
+
274
+ return result
246
275
  }
247
276
  }
@@ -33,6 +33,7 @@ export {
33
33
  normalizeMessageIdentity,
34
34
  type ParseSessionTuiSpec,
35
35
  type TranscriptPtySpec,
36
+ type SessionIdExtractionSpec,
36
37
  type SynthesizedMessage,
37
38
  type SynthesizedSession,
38
39
  } from './builders/cli/parse-session.js';
@@ -309,13 +309,34 @@
309
309
  }
310
310
  }
311
311
  },
312
+ "nativeHistory": {
313
+ "type": "object",
314
+ "description": "Native transcript adapter declaration. v1 canonical field name. Daemon reads/lists provider-native history files via the named scripts (or the built-in adapter identified by $schema).",
315
+ "additionalProperties": true,
316
+ "properties": {
317
+ "$schema": { "type": "string" },
318
+ "format": { "type": "string" },
319
+ "watchPath": { "type": "string" },
320
+ "mode": { "enum": ["native-source", "materialized-mirror", "disabled", "daemon-mirror"] },
321
+ "contractVersion": { "type": "string" },
322
+ "scripts": {
323
+ "type": "object",
324
+ "additionalProperties": false,
325
+ "properties": {
326
+ "readSession": { "type": "string" },
327
+ "listSessions": { "type": "string" }
328
+ }
329
+ }
330
+ }
331
+ },
312
332
  "canonicalHistory": {
313
333
  "type": "object",
314
- "additionalProperties": false,
334
+ "description": "Deprecated v0 alias for `nativeHistory`. The loader copies this block into `nativeHistory` at load time. New manifests should use `nativeHistory` directly.",
335
+ "additionalProperties": true,
315
336
  "properties": {
316
337
  "format": { "type": "string" },
317
338
  "watchPath": { "type": "string" },
318
- "mode": { "enum": ["native-source", "daemon-mirror"] },
339
+ "mode": { "enum": ["native-source", "materialized-mirror", "disabled", "daemon-mirror"] },
319
340
  "contractVersion": { "type": "string" },
320
341
  "scripts": {
321
342
  "type": "object",
@@ -340,11 +361,6 @@
340
361
  "description": "Declarative TUI parsing primitives. When present, daemon's builder synthesises detectStatus / parseApproval / parseSession from these blocks. Each block is identified by its $schema.",
341
362
  "additionalProperties": true
342
363
  },
343
- "nativeHistory": {
344
- "type": "object",
345
- "description": "Declarative native-history adapter declaration. Daemon's builder routes readChat history through the named adapter ($schema).",
346
- "additionalProperties": true
347
- },
348
364
  "overrides": {
349
365
  "type": "object",
350
366
  "description": "Optional JS override pointers. Presence promotes the provider tier to `extended`. Each value identifies the handler being overridden and the relative path to the JS file implementing it.",
@@ -0,0 +1,46 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "adhdev:tui/session-id-extraction@1",
4
+ "title": "tui/session-id-extraction — v1",
5
+ "description": "Declares a regex that captures the provider-issued session UUID off the PTY chrome (e.g. codex footer `gpt-5.5 high · ~/Work/adhdev · 019e8e58-4bd2-7c80-8f5b-b49b6c0e25fa`). The builder runs the regex over the chosen scope's text and exposes the captured group (1) as `providerSessionId` on the parseSession result, so downstream native-history lookup can locate the matching transcript file (`~/.codex/sessions/*-019e8e58-...jsonl`, `~/.claude/projects/*/<uuid>.jsonl`, etc.). Without this primitive a manifest had to ship a scripts/v1 override or fall back to v0 scripts/1.0 just to surface a session id — that's the gap this closes.",
6
+ "type": "object",
7
+ "required": ["$schema", "regex"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "$schema": {
11
+ "const": "adhdev:tui/session-id-extraction@1"
12
+ },
13
+ "regex": {
14
+ "type": "string",
15
+ "description": "ECMAScript regex source. Capture group 1 must hold the session identifier."
16
+ },
17
+ "flags": {
18
+ "type": "string",
19
+ "pattern": "^[gimsuy]*$",
20
+ "default": "i"
21
+ },
22
+ "scope": {
23
+ "default": "tail",
24
+ "enum": ["screen", "tail", "buffer"],
25
+ "description": "Which input view to search. `tail` (default) covers the recent buffer where chrome footers usually live; `screen` is just the visible frame; `buffer` is the full scrollback."
26
+ },
27
+ "label": {
28
+ "type": "string",
29
+ "description": "Optional tag included in debug traces — handy when a manifest declares more than one extraction candidate during authoring."
30
+ }
31
+ },
32
+ "examples": [
33
+ {
34
+ "$schema": "adhdev:tui/session-id-extraction@1",
35
+ "regex": "(?:gpt-|o\\d|codex-)[^·]+·[^·]+·\\s*([0-9a-f-]{36})",
36
+ "scope": "tail",
37
+ "label": "codex-footer"
38
+ },
39
+ {
40
+ "$schema": "adhdev:tui/session-id-extraction@1",
41
+ "regex": "session id:\\s*([0-9a-f-]{36})",
42
+ "flags": "i",
43
+ "scope": "buffer"
44
+ }
45
+ ]
46
+ }