@raidiant/notifai 9.3.0 → 10.1.0

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.
Files changed (126) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +1 -1
  3. package/dist/client.d.ts +3 -1
  4. package/dist/client.js +2 -0
  5. package/dist/client.js.map +1 -1
  6. package/dist/commands-agent-sessions.d.ts +6 -0
  7. package/dist/commands-agent-sessions.js +51 -0
  8. package/dist/commands-agent-sessions.js.map +1 -0
  9. package/dist/commands-auth.js +4 -4
  10. package/dist/commands-auth.js.map +1 -1
  11. package/dist/commands-config.js +11 -4
  12. package/dist/commands-config.js.map +1 -1
  13. package/dist/commands-core.d.ts +5 -0
  14. package/dist/commands-core.js +2 -0
  15. package/dist/commands-core.js.map +1 -1
  16. package/dist/commands-doctor.js +121 -40
  17. package/dist/commands-doctor.js.map +1 -1
  18. package/dist/commands-harness-context.d.ts +15 -2
  19. package/dist/commands-harness-context.js +40 -1
  20. package/dist/commands-harness-context.js.map +1 -1
  21. package/dist/commands-hooks.d.ts +4 -3
  22. package/dist/commands-hooks.js +139 -16
  23. package/dist/commands-hooks.js.map +1 -1
  24. package/dist/commands-init.js +10 -5
  25. package/dist/commands-init.js.map +1 -1
  26. package/dist/commands-send-support.js +18 -1
  27. package/dist/commands-send-support.js.map +1 -1
  28. package/dist/commands-send.js +2 -2
  29. package/dist/commands-send.js.map +1 -1
  30. package/dist/commands-sounds.d.ts +12 -0
  31. package/dist/commands-sounds.js +35 -0
  32. package/dist/commands-sounds.js.map +1 -0
  33. package/dist/commands.d.ts +2 -0
  34. package/dist/commands.js +2 -0
  35. package/dist/commands.js.map +1 -1
  36. package/dist/config-schema.js +5 -2
  37. package/dist/config-schema.js.map +1 -1
  38. package/dist/config.d.ts +3 -4
  39. package/dist/config.js +5 -2
  40. package/dist/config.js.map +1 -1
  41. package/dist/credentials.js +4 -1
  42. package/dist/credentials.js.map +1 -1
  43. package/dist/harnesses.d.ts +49 -7
  44. package/dist/harnesses.js +110 -24
  45. package/dist/harnesses.js.map +1 -1
  46. package/dist/hooks.d.ts +2 -2
  47. package/dist/hooks.js +2 -2
  48. package/dist/hooks.js.map +1 -1
  49. package/dist/install-hooks.d.ts +10 -11
  50. package/dist/install-hooks.js +18 -13
  51. package/dist/install-hooks.js.map +1 -1
  52. package/dist/interactive.js +20 -23
  53. package/dist/interactive.js.map +1 -1
  54. package/dist/invocation-context.d.ts +2 -2
  55. package/dist/openclaw-plugin.d.ts +49 -0
  56. package/dist/openclaw-plugin.js +350 -0
  57. package/dist/openclaw-plugin.js.map +1 -0
  58. package/dist/orca-session-title.d.ts +10 -0
  59. package/dist/orca-session-title.js +98 -0
  60. package/dist/orca-session-title.js.map +1 -0
  61. package/dist/program.d.ts +3 -1
  62. package/dist/program.js +37 -13
  63. package/dist/program.js.map +1 -1
  64. package/dist/send.js +11 -14
  65. package/dist/send.js.map +1 -1
  66. package/dist/session-activation.js +1 -1
  67. package/dist/session-activation.js.map +1 -1
  68. package/dist/session-labels.d.ts +18 -2
  69. package/dist/session-labels.js +79 -8
  70. package/dist/session-labels.js.map +1 -1
  71. package/dist/sound-ref.d.ts +23 -0
  72. package/dist/sound-ref.js +28 -0
  73. package/dist/sound-ref.js.map +1 -0
  74. package/dist/ui/help.d.ts +4 -5
  75. package/dist/ui/help.js +5 -6
  76. package/dist/ui/help.js.map +1 -1
  77. package/package.json +3 -3
  78. package/src/client.test.ts +12 -0
  79. package/src/client.ts +8 -0
  80. package/src/commands-agent-sessions.ts +72 -0
  81. package/src/commands-auth.ts +4 -3
  82. package/src/commands-config.ts +10 -4
  83. package/src/commands-core.ts +6 -0
  84. package/src/commands-doctor.ts +143 -38
  85. package/src/commands-harness-context.ts +56 -2
  86. package/src/commands-hooks.ts +180 -20
  87. package/src/commands-init.ts +10 -4
  88. package/src/commands-send-support.ts +23 -1
  89. package/src/commands-send.ts +2 -2
  90. package/src/commands-sounds.test.ts +106 -0
  91. package/src/commands-sounds.ts +49 -0
  92. package/src/commands.test.ts +529 -17
  93. package/src/commands.ts +5 -0
  94. package/src/config-schema.test.ts +1 -0
  95. package/src/config-schema.ts +5 -2
  96. package/src/config.test.ts +42 -20
  97. package/src/config.ts +6 -5
  98. package/src/credentials.test.ts +20 -0
  99. package/src/credentials.ts +4 -1
  100. package/src/fixtures/hermes-0.20.6-classic-cli-local.json +50 -0
  101. package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +57 -0
  102. package/src/harness-integration.test.ts +294 -0
  103. package/src/harnesses.test.ts +115 -8
  104. package/src/harnesses.ts +133 -30
  105. package/src/hooks.test.ts +4 -3
  106. package/src/hooks.ts +4 -4
  107. package/src/install-hooks.test.ts +213 -4
  108. package/src/install-hooks.ts +38 -30
  109. package/src/interactive.test.ts +31 -0
  110. package/src/interactive.ts +20 -26
  111. package/src/invocation-context.ts +2 -2
  112. package/src/openclaw-plugin.ts +409 -0
  113. package/src/orca-session-title.test.ts +54 -0
  114. package/src/orca-session-title.ts +103 -0
  115. package/src/program.test.ts +27 -0
  116. package/src/program.ts +47 -12
  117. package/src/send.ts +10 -19
  118. package/src/session-activation.ts +1 -1
  119. package/src/session-labels.test.ts +58 -9
  120. package/src/session-labels.ts +95 -12
  121. package/src/skill.test.ts +14 -3
  122. package/src/sound-ref.test.ts +31 -0
  123. package/src/sound-ref.ts +34 -0
  124. package/src/ui/config-view.test.ts +1 -0
  125. package/src/ui/help.test.ts +16 -0
  126. package/src/ui/help.ts +6 -7
@@ -20,15 +20,19 @@ import {
20
20
  type HookHostPlatform,
21
21
  } from './hook-adapter.js'
22
22
  import { opencodePluginPath, opencodePluginTarget } from './opencode-plugin.js'
23
- import { HARNESSES, type Harness } from './harnesses.js'
23
+ import {
24
+ OPENCLAW_EVENTS,
25
+ openclawHasGlobalEvidence,
26
+ openclawPluginPath,
27
+ openclawPluginTarget,
28
+ } from './openclaw-plugin.js'
29
+ import { HOOK_INSTALLABLE_HARNESSES, type HookInstallableHarness } from './harnesses.js'
24
30
  import { accountHome } from './platform.js'
25
31
  import {
26
32
  NON_ROUTING_STOP_TIMEOUT_SECONDS,
27
33
  QUESTION_STOP_TIMEOUT_SECONDS,
28
34
  } from './question-timing.js'
29
35
 
30
- export { HARNESSES, type Harness } from './harnesses.js'
31
-
32
36
  /**
33
37
  * The three joints the OpenCode plugin hooks into, as (harness event, the
34
38
  * `notifai hook` event it runs). The plugin is a module and has no settings
@@ -73,7 +77,7 @@ export interface HookGroup {
73
77
  export type HookConfig = Record<string, HookGroup[]>
74
78
 
75
79
  export interface InstallPlan {
76
- harness: Harness
80
+ harness: HookInstallableHarness
77
81
  /** Absolute path of the settings file that will be written. */
78
82
  file: string
79
83
  /** True when the file is machine-wide rather than per-project. */
@@ -97,7 +101,7 @@ export interface HookCommandOptions {
97
101
  export function hookCommand(
98
102
  adapterPath: string,
99
103
  event: string,
100
- harness?: Harness,
104
+ harness?: HookInstallableHarness,
101
105
  options: HookCommandOptions = {},
102
106
  ): string {
103
107
  return (
@@ -174,7 +178,7 @@ export interface BuildOptions {
174
178
  /** Stable user-level executable installed by the hook adapter module. */
175
179
  adapterPath: string
176
180
  /** The installed adapter stamps its exact harness into project pointers. */
177
- harness?: Harness
181
+ harness?: HookInstallableHarness
178
182
  platform?: NodeJS.Platform | HookHostPlatform
179
183
  /** Registered Node named first in Windows hook commands. */
180
184
  nodePath?: string
@@ -195,7 +199,7 @@ export { QUESTION_STOP_TIMEOUT_SECONDS } from './question-timing.js'
195
199
  * `question-timing.ts`.
196
200
  */
197
201
  export function stopHandlerIsDetached(
198
- harness: Harness | undefined,
202
+ harness: HookInstallableHarness | undefined,
199
203
  platform?: Parameters<typeof hookHostPlatform>[0],
200
204
  ): boolean {
201
205
  return harness === 'claude-code' && hookHostPlatform(platform) === 'posix'
@@ -213,7 +217,7 @@ export function stopHandlerIsDetached(
213
217
  */
214
218
  function stopHandler(
215
219
  adapterPath: string,
216
- harness: Harness | undefined,
220
+ harness: HookInstallableHarness | undefined,
217
221
  options: HookCommandOptions,
218
222
  ): HookHandler {
219
223
  const command = hookCommand(adapterPath, 'stop', harness, options)
@@ -355,7 +359,7 @@ export function buildCursorHookConfig(options: BuildOptions): CursorHookConfig {
355
359
  * questions pushed to is not a property of the repository.
356
360
  */
357
361
  export function settingsFile(
358
- harness: Harness,
362
+ harness: HookInstallableHarness,
359
363
  global: boolean,
360
364
  cwd: string,
361
365
  env: NodeJS.ProcessEnv = process.env,
@@ -366,6 +370,8 @@ export function settingsFile(
366
370
  // generated plugin module, so it owns a whole file instead.
367
371
  case 'opencode':
368
372
  return opencodePluginPath(global, cwd, env, platform)
373
+ case 'openclaw':
374
+ return openclawPluginPath(global, cwd, env, platform)
369
375
  case 'cursor': {
370
376
  const home = harnessAccountHome(env, platform)
371
377
  return global
@@ -606,7 +612,7 @@ export function withCodexLayerTransaction<T>(
606
612
 
607
613
  /** Every file in this layer Codex might already be reading hooks from. */
608
614
  export function hookDefinitionFiles(
609
- harness: Harness,
615
+ harness: HookInstallableHarness,
610
616
  global: boolean,
611
617
  cwd: string,
612
618
  env: NodeJS.ProcessEnv = process.env,
@@ -741,14 +747,15 @@ function ourHandlerEvents(document: SettingsDocument | null): string[] {
741
747
  * document. Treating it as evidence of any one harness would be a guess
742
748
  * dressed up as detection.
743
749
  */
744
- function localHarnessEvidence(cwd: string): Harness[] {
745
- const found: Harness[] = []
750
+ function localHarnessEvidence(cwd: string): HookInstallableHarness[] {
751
+ const found: HookInstallableHarness[] = []
746
752
  if (existsSync(path.join(cwd, '.claude')) || existsSync(path.join(cwd, 'CLAUDE.md'))) {
747
753
  found.push('claude-code')
748
754
  }
749
755
  if (existsSync(path.join(cwd, '.codex'))) found.push('codex')
750
756
  if (existsSync(path.join(cwd, '.cursor'))) found.push('cursor')
751
757
  if (existsSync(path.join(cwd, '.opencode'))) found.push('opencode')
758
+ if (existsSync(path.join(cwd, '.openclaw'))) found.push('openclaw')
752
759
  return found
753
760
  }
754
761
 
@@ -756,13 +763,14 @@ function localHarnessEvidence(cwd: string): Harness[] {
756
763
  function globalHarnessEvidence(
757
764
  env: NodeJS.ProcessEnv = process.env,
758
765
  platform: NodeJS.Platform | HookHostPlatform = process.platform,
759
- ): Harness[] {
766
+ ): HookInstallableHarness[] {
760
767
  const home = harnessAccountHome(env, platform)
761
- const found: Harness[] = []
768
+ const found: HookInstallableHarness[] = []
762
769
  if (existsSync(path.join(home, '.claude'))) found.push('claude-code')
763
770
  if (existsSync(codexGlobalDir(env, platform))) found.push('codex')
764
771
  if (existsSync(path.join(home, '.cursor'))) found.push('cursor')
765
772
  if (existsSync(path.join(home, '.config', 'opencode'))) found.push('opencode')
773
+ if (openclawHasGlobalEvidence(existsSync, env, platform)) found.push('openclaw')
766
774
  return found
767
775
  }
768
776
 
@@ -777,12 +785,12 @@ export function detectedHarnesses(
777
785
  cwd: string,
778
786
  env: NodeJS.ProcessEnv = process.env,
779
787
  platform: NodeJS.Platform | HookHostPlatform = process.platform,
780
- ): Harness[] {
781
- const seen = new Set<Harness>([
788
+ ): HookInstallableHarness[] {
789
+ const seen = new Set<HookInstallableHarness>([
782
790
  ...localHarnessEvidence(cwd),
783
791
  ...globalHarnessEvidence(env, platform),
784
792
  ])
785
- return HARNESSES.filter((harness) => seen.has(harness))
793
+ return HOOK_INSTALLABLE_HARNESSES.filter((harness) => seen.has(harness))
786
794
  }
787
795
 
788
796
  /**
@@ -798,7 +806,7 @@ export function detectHarness(
798
806
  cwd: string,
799
807
  env: NodeJS.ProcessEnv = process.env,
800
808
  platform: NodeJS.Platform | HookHostPlatform = process.platform,
801
- ): Harness | null {
809
+ ): HookInstallableHarness | null {
802
810
  const local = localHarnessEvidence(cwd)
803
811
  if (local.length > 0) return local.length === 1 ? local[0]! : null
804
812
  const global = globalHarnessEvidence(env, platform)
@@ -1193,7 +1201,7 @@ export interface InstalledHandler {
1193
1201
  }
1194
1202
 
1195
1203
  export interface Installation {
1196
- harness: Harness
1204
+ harness: HookInstallableHarness
1197
1205
  file: string
1198
1206
  global: boolean
1199
1207
  handlers: InstalledHandler[]
@@ -1331,7 +1339,7 @@ export function findInstallations(
1331
1339
  ...(nodePath === undefined ? {} : { nodePath }),
1332
1340
  }
1333
1341
  const found: Installation[] = []
1334
- for (const harness of HARNESSES) {
1342
+ for (const harness of HOOK_INSTALLABLE_HARNESSES) {
1335
1343
  for (const global of [false, true]) {
1336
1344
  const files = hookDefinitionFiles(harness, global, cwd, env, platform)
1337
1345
  for (const file of files) {
@@ -1339,38 +1347,38 @@ export function findInstallations(
1339
1347
  // OpenCode's adapter is a plugin module, not a settings document, so it
1340
1348
  // is reported as one installation covering all three events rather than
1341
1349
  // parsed for handlers.
1342
- if (harness === 'opencode') {
1350
+ if (harness === 'opencode' || harness === 'openclaw') {
1343
1351
  let source: string
1344
1352
  try {
1345
1353
  source = readOwnedRegularFile(file)
1346
1354
  } catch {
1347
1355
  continue
1348
1356
  }
1349
- const target = opencodePluginTarget(source)
1357
+ const target =
1358
+ harness === 'openclaw' ? openclawPluginTarget(source) : opencodePluginTarget(source)
1350
1359
  if (target === null) continue
1360
+ const label = harness === 'openclaw' ? 'OpenClaw' : 'OpenCode'
1351
1361
  const problems = [
1352
1362
  ...(!target.current
1353
- ? ['obsolete OpenCode event wiring; rerun `notifai hooks install --harness opencode`']
1363
+ ? [`obsolete ${label} event wiring; rerun \`notifai hooks install --harness ${harness}\``]
1354
1364
  : []),
1355
1365
  ...(target.adapter !== hookAdapterPath(adapterHome)
1356
1366
  ? [
1357
- 'OpenCode still names a mutable CLI or runtime path; rerun `notifai hooks install --harness opencode`',
1367
+ `${label} still names a mutable CLI or runtime path; rerun \`notifai hooks install --harness ${harness}\``,
1358
1368
  ]
1359
1369
  : []),
1360
1370
  ]
1371
+ const events = harness === 'openclaw' ? OPENCLAW_EVENTS : OPENCODE_EVENTS
1361
1372
  found.push({
1362
1373
  harness,
1363
1374
  file,
1364
1375
  global,
1365
1376
  ...(problems.length > 0 ? { problems } : {}),
1366
- // Reported as the command line the plugin will actually run, not as
1367
- // the plugin's own path: every check downstream asks which build a
1368
- // handler invokes, and for a module that answer lives inside it.
1369
- handlers: OPENCODE_EVENTS.map(([event, hookEvent]) => ({
1377
+ handlers: events.map(([event, hookEvent]) => ({
1370
1378
  event,
1371
1379
  groupIndex: 0,
1372
1380
  handlerIndex: 0,
1373
- command: hookCommand(target.adapter, hookEvent, 'opencode', {
1381
+ command: hookCommand(target.adapter, hookEvent, harness, {
1374
1382
  ...commandOptions,
1375
1383
  ...(target.nodePath === undefined ? {} : { nodePath: target.nodePath }),
1376
1384
  }),
@@ -1411,7 +1419,7 @@ export function findInstallations(
1411
1419
  }
1412
1420
 
1413
1421
  function harnessMarkerProblems(
1414
- harness: Harness,
1422
+ harness: HookInstallableHarness,
1415
1423
  handlers: InstalledHandler[],
1416
1424
  adapterHome?: string,
1417
1425
  options: HookCommandOptions = {},
@@ -226,6 +226,37 @@ describe('interactiveCommand', () => {
226
226
  answers = []
227
227
  })
228
228
 
229
+ it('enters init on first-run instead of a parallel Sign in path', async () => {
230
+ const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-interactive-first-run-'))
231
+ const out: string[] = []
232
+ const deps = makeDeps(cwd)
233
+ deps.store.load = () => null
234
+ deps.io.out = (line: string) => {
235
+ out.push(line)
236
+ }
237
+ answers = [CANCEL]
238
+
239
+ expect(await interactiveCommand(deps, '0.0.0-test')).toBe(EXIT.ok)
240
+ expect(out.join('\n')).toContain('Next: This machine')
241
+ expect(out.join('\n')).toContain('notifai init')
242
+ expect(out.join('\n')).not.toContain('notifai login')
243
+ const menu = promptLog.find((entry) => entry.message === 'What would you like to do?')
244
+ expect(menu?.options?.[0]).toMatchObject({ value: 'setup', label: 'Finish setup' })
245
+ expect(optionLabels(menu!)).not.toContain('Sign in')
246
+ expect(optionLabels(menu!)).not.toContain('Add a device')
247
+ })
248
+
249
+ it('offers Finish setup instead of a parallel Add a device path', async () => {
250
+ const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-interactive-finish-setup-'))
251
+ answers = [CANCEL]
252
+
253
+ expect(await interactiveCommand(makeDeps(cwd), '0.0.0-test')).toBe(EXIT.ok)
254
+ const menu = promptLog.find((entry) => entry.message === 'What would you like to do?')
255
+ expect(menu?.options?.[0]).toMatchObject({ value: 'setup', label: 'Finish setup' })
256
+ expect(optionLabels(menu!)).not.toContain('Add a device')
257
+ expect(optionLabels(menu!)).not.toContain('Sign in')
258
+ })
259
+
229
260
  it('quits from the root menu on Escape', async () => {
230
261
  const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-interactive-root-esc-'))
231
262
  answers = [CANCEL]
@@ -15,10 +15,11 @@
15
15
  *
16
16
  * Three things shape the layout:
17
17
  *
18
- * The menu leads with whatever is actually in the way. Someone who is not
19
- * signed in is offered sign-in first; someone with no device that can receive
20
- * is offered a device; everyone else is offered the test notification, because
21
- * seeing the thing arrive on your device is what makes the product real.
18
+ * First-run is `init`, not a second sign-in/device split. An unsigned machine
19
+ * enters that journey immediately; a remaining blocker is offered as Finish
20
+ * setup. Once send-prerequisites are ready, the menu leads with the test
21
+ * notification, because seeing the thing arrive on a device is what makes the
22
+ * product real.
22
23
  *
23
24
  * Detail is earned, not given. A settings group shows fourteen one-line
24
25
  * summaries, and the paragraph explaining a key appears once the reader has
@@ -180,6 +181,10 @@ export async function interactiveCommand(deps: CommandDeps, version: string): Pr
180
181
  process.stdout.write(` ${tagline(version)}\n`)
181
182
 
182
183
  let readiness = await assess(deps)
184
+ if (stateById(readiness, 'credential')?.status !== 'ready') {
185
+ await initCommand(deps, {})
186
+ readiness = await assess(deps)
187
+ }
183
188
  let first = true
184
189
 
185
190
  for (;;) {
@@ -362,15 +367,13 @@ function menuFor(readiness: Readiness): { value: Screen; label: string; hint?: s
362
367
 
363
368
  const options: { value: Screen; label: string; hint?: string }[] = []
364
369
 
365
- if (!signedIn) {
366
- options.push({ value: 'account', label: 'Sign in', hint: 'approve this machine in your browser' })
367
- } else if (!hasDevice) {
370
+ if (blocker !== null) {
368
371
  options.push({
369
- value: 'devices',
370
- label: 'Add a device',
371
- hint: 'install a supported Companion App',
372
+ value: 'setup',
373
+ label: 'Finish setup',
374
+ hint: shorten(blocker.title.toLowerCase(), 40),
372
375
  })
373
- } else {
376
+ } else if (signedIn && hasDevice) {
374
377
  options.push({
375
378
  value: 'test',
376
379
  label: 'Send a test notification',
@@ -378,10 +381,6 @@ function menuFor(readiness: Readiness): { value: Screen; label: string; hint?: s
378
381
  })
379
382
  }
380
383
 
381
- if (blocker !== null && signedIn) {
382
- options.push({ value: 'setup', label: 'Finish setup', hint: shorten(blocker.title.toLowerCase(), 40) })
383
- }
384
-
385
384
  options.push(
386
385
  { value: 'settings', label: 'Settings', hint: 'sounds, timing, presence, project' },
387
386
  {
@@ -393,11 +392,11 @@ function menuFor(readiness: Readiness): { value: Screen; label: string; hint?: s
393
392
  if (signedIn && hasDevice) {
394
393
  options.push({ value: 'devices', label: 'Devices', hint: 'what can receive right now' })
395
394
  }
396
- options.push(
397
- { value: 'doctor', label: 'Run diagnostics', hint: 'check every part of the setup' },
398
- { value: 'account', label: 'Account', hint: signedIn ? 'plan, machine, sign out' : 'sign in' },
399
- { value: 'quit', label: 'Quit' },
400
- )
395
+ options.push({ value: 'doctor', label: 'Run diagnostics', hint: 'check every part of the setup' })
396
+ if (signedIn) {
397
+ options.push({ value: 'account', label: 'Account', hint: 'plan, machine, sign out' })
398
+ }
399
+ options.push({ value: 'quit', label: 'Quit' })
401
400
  return options
402
401
  }
403
402
 
@@ -822,12 +821,7 @@ async function accountScreen(deps: CommandDeps, readiness: Readiness): Promise<b
822
821
  const signedIn = stateById(readiness, 'credential')?.status === 'ready'
823
822
 
824
823
  if (!signedIn) {
825
- const go = await clack.confirm({
826
- message: 'Sign in now? This opens your browser to approve this machine.',
827
- initialValue: true,
828
- })
829
- if (cancelled(go) || go !== true) return false
830
- await loginCommand(deps, {})
824
+ await initCommand(deps, {})
831
825
  return true
832
826
  }
833
827
 
@@ -1,7 +1,7 @@
1
1
  import { execFileSync } from 'node:child_process'
2
2
  import path from 'node:path'
3
3
  import { type SourceContextT } from '@raidiant/notifai-protocol'
4
- import type { Harness } from './harnesses.js'
4
+ import type { SourceContextHarness } from './harnesses.js'
5
5
  import { resolveSessionLabel } from './session-labels.js'
6
6
 
7
7
  export type GitCommand = (cwd: string, args: readonly string[]) => string | null
@@ -138,7 +138,7 @@ export interface SourceContextInput {
138
138
  sessionId?: string
139
139
  sessionLabel?: string
140
140
  activeHarness?: {
141
- harness: Harness
141
+ harness: SourceContextHarness
142
142
  sessionId?: string
143
143
  sessionLabel?: string
144
144
  sessionLabelPending?: boolean