@cmodjs/core 2.1.0 → 3.0.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 (70) hide show
  1. package/docs/commands.md +1 -1
  2. package/docs/mod.md +2 -0
  3. package/docs/session.md +9 -1
  4. package/docs/testing.md +4 -2
  5. package/jobs/context.d.ts.map +1 -1
  6. package/jobs/context.js +2 -1
  7. package/jobs/permissions.d.ts.map +1 -1
  8. package/jobs/permissions.js +1 -1
  9. package/jobs/prompt.d.ts.map +1 -1
  10. package/jobs/prompt.js +2 -1
  11. package/jobs/slash-command.d.ts.map +1 -1
  12. package/jobs/slash-command.js +4 -3
  13. package/package.json +1 -1
  14. package/records.d.ts +1 -1
  15. package/records.js +1 -1
  16. package/register.d.ts.map +1 -1
  17. package/register.js +58 -51
  18. package/runtime/before.d.ts +1 -1
  19. package/runtime/before.d.ts.map +1 -1
  20. package/runtime/before.js +6 -5
  21. package/runtime/conversations.d.ts.map +1 -1
  22. package/runtime/conversations.js +46 -19
  23. package/runtime/hooks.d.ts +2 -1
  24. package/runtime/hooks.d.ts.map +1 -1
  25. package/runtime/hooks.js +7 -0
  26. package/runtime/lifecycle.d.ts +2 -1
  27. package/runtime/lifecycle.d.ts.map +1 -1
  28. package/runtime/lifecycle.js +31 -9
  29. package/runtime/permission-check.d.ts +3 -0
  30. package/runtime/permission-check.d.ts.map +1 -1
  31. package/runtime/permission-check.js +19 -6
  32. package/runtime/router.d.ts +3 -1
  33. package/runtime/router.d.ts.map +1 -1
  34. package/runtime/router.js +19 -11
  35. package/runtime/sessions.d.ts +13 -4
  36. package/runtime/sessions.d.ts.map +1 -1
  37. package/runtime/sessions.js +67 -26
  38. package/runtime/toaster.d.ts.map +1 -1
  39. package/runtime/toaster.js +3 -0
  40. package/session/context/turn/message/message.d.ts.map +1 -1
  41. package/session/context/turn/message/message.js +30 -2
  42. package/session/context/turn/message/note.d.ts +7 -0
  43. package/session/context/turn/message/note.d.ts.map +1 -1
  44. package/session/context/turn/message/note.js +12 -0
  45. package/session/session.d.ts +3 -1
  46. package/session/session.d.ts.map +1 -1
  47. package/src/jobs/context.ts +2 -1
  48. package/src/jobs/permissions.ts +8 -4
  49. package/src/jobs/prompt.ts +2 -1
  50. package/src/jobs/slash-command.ts +4 -3
  51. package/src/records.ts +1 -1
  52. package/src/register.ts +63 -52
  53. package/src/runtime/before.ts +7 -6
  54. package/src/runtime/conversations.ts +54 -27
  55. package/src/runtime/hooks.ts +9 -1
  56. package/src/runtime/lifecycle.ts +37 -13
  57. package/src/runtime/permission-check.ts +44 -22
  58. package/src/runtime/router.ts +23 -13
  59. package/src/runtime/sessions.ts +91 -37
  60. package/src/runtime/toaster.ts +2 -0
  61. package/src/session/context/turn/message/message.ts +27 -2
  62. package/src/session/context/turn/message/note.ts +16 -0
  63. package/src/session/session.ts +1 -2
  64. package/src/testing/fake-claude.ts +4 -3
  65. package/src/testing.ts +6 -5
  66. package/testing/fake-claude.d.ts +1 -0
  67. package/testing/fake-claude.d.ts.map +1 -1
  68. package/testing/fake-claude.js +3 -2
  69. package/testing.d.ts.map +1 -1
  70. package/testing.js +5 -4
@@ -1,8 +1,8 @@
1
- import type { Args, CommandRunResult } from 'claude-code'
1
+ import type { Args, CommandRunResult, SessionCompactResult } from 'claude-code'
2
2
  import type { Disposable } from '../mod.js'
3
3
  import { ContextEnd, ContextStart, type Context } from '../session/context/context.js'
4
4
  import { Message, noFiles } from '../session/context/turn/message/message.js'
5
- import { Note } from '../session/context/turn/message/note.js'
5
+ import { modNote, Note } from '../session/context/turn/message/note.js'
6
6
  import { Person, senderOf } from '../session/context/turn/message/sender.js'
7
7
  import { ToolCall } from '../session/context/turn/message/tool-call.js'
8
8
  import { ToolResult } from '../session/context/turn/message/tool-result.js'
@@ -20,7 +20,7 @@ import { listenerOf, PassedTimeout, withinTime, type ListenOptions, type ListenT
20
20
  import { callFiles, type HomeFolders, type HookFields } from './hooks.js'
21
21
  import type { PermissionRoutes } from './permission-check.js'
22
22
 
23
- export type Subscription = { readonly event: SessionEvent; readonly matcher: SentMatcher | undefined }
23
+ export type Subscription = { readonly event: SessionEvent; readonly matcher: SentMatcher | undefined; readonly onFailure?: 'refuse'; readonly scope?: Scope }
24
24
 
25
25
  export const everySession = '*'
26
26
 
@@ -67,6 +67,7 @@ export type SessionsClient = {
67
67
  readonly sessions: Sessions
68
68
  changed(session: string, change: SessionChange, isInProject: boolean): void
69
69
  answerBefore(session: string, event: BeforeEvent, change: SessionChange, isInProject: boolean): Promise<unknown>
70
+ failedBefore(session: string, event: BeforeEvent, change: SessionChange, isInProject: boolean, error: Error): { readonly refuse: string } | undefined
70
71
  raise(method: string, payload: Readonly<Record<string, unknown>>): Promise<unknown>
71
72
  listen(method: string, args: readonly unknown[]): Disposable
72
73
  /** An empty title keeps the title the session already has. */
@@ -137,7 +138,7 @@ export function withoutPermissionCheck(name: string, hearer: string): string {
137
138
  return `${name}: ${hearer} hears Claude Code's permission check, so hooks/register.ts must call registerPermissionCheck(addHook) after registerMod.`
138
139
  }
139
140
 
140
- export type HookOn = (hook: string, listener: (input: unknown) => HookFields | undefined | Promise<HookFields | undefined>) => Disposable
141
+ export type HookOn = (hook: string, listener: (input: unknown) => HookFields | undefined | Promise<HookFields | undefined>, failed?: (input: unknown, error: Error) => HookFields | undefined) => Disposable
141
142
 
142
143
  export function createSessionsClient({ name, claude, report, isGranted, hookOn, routeOn, permissionRoutes }: { readonly name: string; readonly claude: Claude; readonly report: (id: string, message: string, error: unknown) => void; readonly isGranted: (permission: string) => boolean; readonly hookOn?: HookOn; readonly routeOn?: RouteOn; readonly permissionRoutes?: () => PermissionRoutes | undefined }): SessionsClient {
143
144
  const call = <Answer>(method: string, input: unknown) => claude.cmod.call({ to: cmodPluginName, method, input }) as Promise<Answer>
@@ -155,7 +156,8 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
155
156
  queued.add(key)
156
157
  void Promise.resolve().then(async () => {
157
158
  queued.delete(key)
158
- const heard = (from: Map<string, Set<Listening>>): Subscription[] => [...(from.get(key) ?? [])].map(({ event, matcher }) => ({ event, matcher: matcher === undefined ? undefined : sentMatcher(matcher) }))
159
+ const heard = (from: Map<string, Set<Listening>>): Subscription[] =>
160
+ [...(from.get(key) ?? [])].map(({ event, matcher, options, scope }) => ({ event, matcher: matcher === undefined ? undefined : sentMatcher(matcher), ...(options.onFailure === 'refuse' ? { onFailure: 'refuse' as const, scope } : {}) }))
159
161
  const tracksTasks = [...(listening.get(key) ?? []), ...(deciding.get(key) ?? [])].some(({ scope }) => scope.task !== undefined || scope.isGoal === true)
160
162
  const subscriptions = [...heard(listening), ...(tracksTasks ? [{ event: TaskStart }] : [])]
161
163
  await call(conversationMethods.subscribe, { session: key, subscriptions }).catch((error: unknown) => report(`sessions:${key}`, `Hears nothing from ${key === everySession ? 'the other sessions' : `session ${key}`}`, error))
@@ -200,27 +202,32 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
200
202
  }
201
203
  }
202
204
 
203
-
205
+ const refusalOf = (options: ListenOptions, takes: readonly string[], error: unknown) => (options.onFailure === 'refuse' && takes.includes('refuse') ? { refuse: `${name}: ${messageOf(error)}` } : undefined)
204
206
 
205
207
  const invoke = (method: string, options: ListenOptions, run: (time: ListenTime) => unknown, takes: readonly string[] = []): Promise<unknown> =>
206
208
  withinTime(claude.clock, method, options, run).catch((error: unknown) => {
207
209
  if (error instanceof PassedTimeout && !method.startsWith('before')) return undefined
208
210
  report(`session:${method}`, `The ${method} listener failed`, error)
209
- return options.onFailure === 'refuse' && takes.includes('refuse') ? { refuse: `${name}: ${messageOf(error)}` } : undefined
211
+ return refusalOf(options, takes, error)
210
212
  })
211
213
 
214
+ const decidersOf = (session: string, event: BeforeEvent, isInProject: boolean) => [...(deciding.get(session) ?? []), ...(isInProject ? (deciding.get(everySession) ?? []) : [])].filter((entry) => entry.event === event)
215
+
216
+ const decides = (entry: Listening, session: string, change: SessionChange) => isInScope(entry.scope, session, change, isUnderIn(session)) && (entry.matcher === undefined || matches(payloadOf(change), entry.matcher))
217
+
218
+ const mayDecide = (session: string) => session === current.id || isGranted('conversation')
219
+
212
220
  const combined = async (session: string, event: BeforeEvent, change: SessionChange, isInProject: boolean): Promise<Partial<Answers> | undefined> => {
213
- const askers = [...(deciding.get(session) ?? []), ...(isInProject ? (deciding.get(everySession) ?? []) : [])]
214
- .filter((entry) => entry.event === event)
221
+ const askers = decidersOf(session, event, isInProject)
215
222
  .map((entry) => async (latest: SessionChange) => {
216
- if (!isInScope(entry.scope, session, latest, isUnderIn(session))) return undefined
217
- if (entry.matcher !== undefined && !matches(payloadOf(latest), entry.matcher)) return undefined
223
+ if (!decides(entry, session, latest)) return undefined
218
224
  const payload = await payloadFor(session, latest)
219
225
  const answer = await invoke(entry.method, entry.options, async (time) => takenAnswer(entry.method, beforeAnswerKeys[event], await entry.listener(payload as never, time)), beforeAnswerKeys[event])
220
226
  if (answer === undefined || answer === null || (typeof answer === 'object' && 'refuse' in answer) || isGranted(beforePermission(event))) return answer
221
227
  throw new Error(`Its ${entry.method} answer changes nothing until ${beforePermission(event)} is turned on for ${name} in /mods.`)
222
228
  })
223
- return foldBefore(event, change, askers, (error) => report(`session:${methodOf('before', event)}`, `The ${methodOf('before', event)} listener failed`, error))
229
+ const answer = await foldBefore(event, change, askers, (error) => report(`session:${methodOf('before', event)}`, `The ${methodOf('before', event)} listener failed`, error))
230
+ return answer?.note === undefined ? answer : { ...answer, note: modNote(name, answer.note) }
224
231
  }
225
232
 
226
233
  const eventMethods = (scopeOf: () => Scope, hasBefore: boolean, ended?: string): OnMethods & Partial<BeforeMethods> => {
@@ -329,7 +336,7 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
329
336
  const sendHere = async ({ type, text, as }: SessionSend) => {
330
337
  if (type === Note) {
331
338
  if (as !== undefined) throw new Error(`${name}: a Note is always from this mod, so it takes no as.`)
332
- const added = await claude.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } })
339
+ const added = await claude.session.append({ message: { type: 'user', content: [{ type: 'text', text: modNote(name, text) }] } })
333
340
  if (added.deny !== undefined) throw new Error(`${name}: Claude Code refused the note: ${added.deny}`)
334
341
  return
335
342
  }
@@ -432,21 +439,26 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
432
439
  )
433
440
  }
434
441
 
435
- type ProcessListener = { readonly matcher: unknown; readonly listener: (payload: HookFields, time: ListenTime) => unknown; readonly options: ListenOptions }
442
+ type ProcessListener = { readonly matcher: unknown; readonly listener: (payload: HookFields, time: ListenTime) => unknown; readonly options: ListenOptions; readonly isEveryAgent: boolean }
436
443
  const processListeners = new Map<string, Set<ProcessListener>>()
437
444
 
445
+ const processHearers = (method: string, payload: HookFields) => [...(processListeners.get(method) ?? [])].filter(({ matcher, isEveryAgent }) => (payload['task'] === undefined || isEveryAgent) && (matcher === undefined || matches(payload, matcher)))
446
+
438
447
  const raise = async (method: string, payload: HookFields): Promise<HookFields | undefined> => {
439
- const heard = [...(processListeners.get(method) ?? [])].filter(({ matcher }) => payload['task'] === undefined && (matcher === undefined || matches(payload, matcher)))
448
+ const heard = processHearers(method, payload)
440
449
  if (!method.startsWith('before')) {
441
450
  await Promise.all(heard.map(({ listener, options }) => invoke(method, options, (time) => listener(payload, time))))
442
451
  return undefined
443
452
  }
444
- const failing = method === 'beforePermissionRequest' ? { onFailure: 'refuse' as const } : {}
445
- const takes = answerKeys[method.slice('before'.length)] ?? []
446
- const askers = heard.map(({ listener, options }) => () => invoke(method, { ...options, ...failing }, async (time) => takenAnswer(method, takes, await listener(payload, time)), takes))
453
+ const takes = takesOf(method)
454
+ const askers = heard.map(({ listener, options }) => () => invoke(method, options, async (time) => takenAnswer(method, takes, await listener(payload, time)), takes))
447
455
  return foldAnswers<undefined, HookFields>(method, takes, ['refuse'], undefined, askers, (error) => report(`session:${method}`, `The ${method} listener failed`, error))
448
456
  }
449
457
 
458
+ const takesOf = (method: string) => answerKeys[method.slice('before'.length)] ?? []
459
+
460
+ const refusalFor = (method: string, payload: HookFields, error: Error) => processHearers(method, payload).map((entry) => refusalOf(entry.options, takesOf(method), error)).find((answer) => answer !== undefined)
461
+
450
462
  const commandRun = async (e: Args<'command.run'>): Promise<HookFields> => {
451
463
  const source = (await claude.command.list()).find((command) => command.name === e.command)?.source
452
464
  return { session: current.id, cwd: current.cwd, name: e.command, args: e.args, ...defined({ source }), from: senderOf(e.origin, false) }
@@ -465,7 +477,10 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
465
477
  const args = typeof changes?.['args'] === 'string' ? changes['args'] : e.args
466
478
  const ran = await next(args === e.args ? e : { ...e, args })
467
479
  void raise('onRun', { ...run, args })
468
- return typeof changes?.['note'] === 'string' ? { ...ran, context: [...(ran.context ?? []), changes['note']] } : ran
480
+ return typeof changes?.['note'] === 'string' ? { ...ran, context: [...(ran.context ?? []), modNote(name, changes['note'])] } : ran
481
+ }, async (e, error) => {
482
+ const refusal = commandListeners.beforeRun.size === 0 ? undefined : refusalFor('beforeRun', await commandRun(e), error)
483
+ return refusal === undefined ? undefined : { text: refusal.refuse }
469
484
  })
470
485
 
471
486
  const listenTo = (method: string, named: string) => {
@@ -474,23 +489,54 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
474
489
  if (named === 'PermissionCheck' || named === 'PermissionRequest') {
475
490
  const routes = permissionRoutes?.()
476
491
  if (routes === undefined) throw new Error(withoutPermissionCheck(name, method))
477
- return routes(method, { raise, hookOn, routeOn, current: () => current })
492
+ return routes(method, { raise, refusalFor, hookOn, routeOn, current: () => current })
478
493
  }
479
494
  const event: { readonly on: string; readonly before?: string } = processEvents[named as ProcessName]
480
495
  const hook = (isBefore ? event.before : undefined) ?? event.on
481
- return hookOn(hook, async (input: unknown) => {
482
- const payload = processPayloadOf(named as ProcessFieldsName, input)
483
- if (!isBefore) {
484
- if (named !== 'SessionStart' || payload['source'] !== 'compact') void raise(method, payload)
485
- return undefined
486
- }
487
- const answer = await raise(method, payload)
488
- return answer === undefined ? undefined : hookAnswerOf(named, answer)
489
- })
496
+ if (method === 'beforeSessionStart') routeOn('session.compact', compactionNote)
497
+ const failed = (input: unknown, error: Error) => {
498
+ const refusal = refusalFor(method, processPayloadOf(named as ProcessFieldsName, input), error)
499
+ return refusal === undefined ? undefined : hookAnswerOf(name, named, refusal)
500
+ }
501
+ return hookOn(
502
+ hook,
503
+ async (input: unknown) => {
504
+ const payload = processPayloadOf(named as ProcessFieldsName, input)
505
+ if (named === 'SessionStart' && payload['source'] === 'compact') {
506
+ const started = isBefore ? compactionStarts.shift() : undefined
507
+ return started === undefined ? undefined : hookAnswerOf(name, named, started)
508
+ }
509
+ if (!isBefore) {
510
+ void raise(method, payload)
511
+ return undefined
512
+ }
513
+ const answer = await raise(method, payload)
514
+ return answer === undefined ? undefined : hookAnswerOf(name, named, answer)
515
+ },
516
+ isBefore ? failed : undefined,
517
+ )
490
518
  }
491
519
 
492
- const listen = (method: string, args: readonly unknown[]): Disposable => {
493
- const entry = listenerOf(name, method, args) as ProcessListener
520
+ const compactionStarts: HookFields[] = []
521
+
522
+ const compactionNote = async (e: Args<'session.compact'>, next: (e: Args<'session.compact'>) => Promise<SessionCompactResult>): Promise<SessionCompactResult> => {
523
+ const compacted = await next(e)
524
+ if (e.trigger === 'precompute' || compacted.skip !== undefined) return compacted
525
+ const model = e.agentId === undefined ? await claude.session.model() : undefined
526
+ const answer = await raise('beforeSessionStart', { session: current.id, cwd: current.cwd, ...defined({ task: e.agentId, model, title: current.title || undefined, contextTokenCount: compacted.tokensAfter }), source: 'compact' })
527
+ const { note, ...started } = answer ?? {}
528
+ if (Object.keys(started).length > 0) compactionStarts.push(started)
529
+ if (typeof note !== 'string') return compacted
530
+ if (!isGranted('prompt')) {
531
+ report('session:beforeSessionStart', 'The beforeSessionStart listener failed', new Error(`${name}: its note adds text Claude reads, which needs the prompt permission.`))
532
+ return compacted
533
+ }
534
+ return { ...compacted, messages: [...compacted.messages, { role: 'user', text: modNote(name, note), toolUses: [] }] }
535
+ }
536
+
537
+ const listen = (method: string, args: readonly unknown[], isEveryAgent = false): Disposable => {
538
+ const heard = listenerOf(name, method, args)
539
+ const entry = { ...heard, options: method === 'beforePermissionRequest' ? { ...heard.options, onFailure: 'refuse' } : heard.options, isEveryAgent } as ProcessListener
494
540
  const held = processListeners.get(method) ?? new Set()
495
541
  if (!processListeners.has(method)) {
496
542
  listenTo(method, method.replace(/^(on|before)/, ''))
@@ -505,10 +551,16 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
505
551
  Object.entries(processEvents).flatMap(([named, event]) => [`on${named}`, ...('before' in event ? [`before${named}`] : [])]).map((method) => [method, (...args: unknown[]) => listen(method, args)]),
506
552
  ) as ProcessOnMethods & ProcessBeforeMethods
507
553
 
508
- const currentSession = Object.assign(sessionOf(() => current), processMethods())
554
+ const here = sessionOf(() => current)
555
+ const currentSession = Object.assign(here, processMethods(), { goal: Object.assign(here.goal, { beforeSessionStart: (...args: unknown[]) => listen('beforeSessionStart', args, true) }) })
509
556
  const every = eventMethods(() => ({}), false)
510
557
 
511
- const answerBefore = async (session: string, event: BeforeEvent, change: SessionChange, isInProject: boolean) => (session === current.id || isGranted('conversation') ? combined(session, event, change, isInProject) : undefined)
558
+ const answerBefore = async (session: string, event: BeforeEvent, change: SessionChange, isInProject: boolean) => (mayDecide(session) ? combined(session, event, change, isInProject) : undefined)
559
+
560
+ const failedBefore = (session: string, event: BeforeEvent, change: SessionChange, isInProject: boolean, error: Error) => {
561
+ const asked = mayDecide(session) ? decidersOf(session, event, isInProject).filter((entry) => decides(entry, session, change)) : []
562
+ return asked.map((entry) => refusalOf(entry.options, beforeAnswerKeys[event], error)).find((answer) => answer !== undefined)
563
+ }
512
564
 
513
565
  return {
514
566
  sessions: {
@@ -541,6 +593,7 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
541
593
  }
542
594
  },
543
595
  answerBefore,
596
+ failedBefore,
544
597
  raise,
545
598
  listen,
546
599
  follow(next) {
@@ -565,8 +618,9 @@ export function createSessionsClient({ name, claude, report, isGranted, hookOn,
565
618
  export type ToolCheck = { readonly tool: string; readonly input: unknown; readonly tool_use_id?: string; readonly agentId?: string; readonly ceiling?: 'allow' | 'ask' | 'deny' }
566
619
 
567
620
  export type RouteOn = {
568
- (event: 'tool.check', hook: (e: ToolCheck, next: (e: ToolCheck) => Promise<unknown>) => Promise<unknown>): Disposable
569
- (event: 'command.run', hook: (e: Args<'command.run'>, next: (e: Args<'command.run'>) => Promise<CommandRunResult>) => Promise<CommandRunResult>): Disposable
621
+ (event: 'tool.check', hook: (e: ToolCheck, next: (e: ToolCheck) => Promise<unknown>) => Promise<unknown>, failed?: (e: ToolCheck, error: Error, next: (e: ToolCheck) => Promise<unknown>) => Promise<{ readonly decision: 'deny'; readonly reason: string } | undefined>): Disposable
622
+ (event: 'command.run', hook: (e: Args<'command.run'>, next: (e: Args<'command.run'>) => Promise<CommandRunResult>) => Promise<CommandRunResult>, failed?: (e: Args<'command.run'>, error: Error) => Promise<CommandRunResult | undefined>): Disposable
623
+ (event: 'session.compact', hook: (e: Args<'session.compact'>, next: (e: Args<'session.compact'>) => Promise<SessionCompactResult>) => Promise<SessionCompactResult>): Disposable
570
624
  }
571
625
 
572
626
  export const recordOf = (value: unknown): Readonly<Record<string, unknown>> => (typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : {})
@@ -663,9 +717,9 @@ export function processPayloadOf(named: ProcessFieldsName, input: unknown): Hook
663
717
  return { ...placementOf(fields), ...defined(processFields[named](fields)) }
664
718
  }
665
719
 
666
- function hookAnswerOf(named: string, answer: HookFields): HookFields | undefined {
720
+ function hookAnswerOf(name: string, named: string, answer: HookFields): HookFields | undefined {
667
721
  const refuse = typeof answer['refuse'] === 'string' ? answer['refuse'] : undefined
668
- const note = typeof answer['note'] === 'string' ? [answer['note']] : undefined
722
+ const note = typeof answer['note'] === 'string' ? [modNote(name, answer['note'])] : undefined
669
723
  if (named === 'SessionStart') return defined({ additionalContext: note, sessionTitle: answer['title'], reloadSkills: answer['reloadSkills'] === true ? true : undefined, initialUserMessage: answer['firstPrompt'] })
670
724
  if (named === 'ModelChange') {
671
725
  if (refuse !== undefined) return { permissionDecision: 'deny', permissionDecisionReason: refuse }
@@ -180,6 +180,8 @@ export function createToaster(claude: Claude, name: string, version: () => strin
180
180
  const hides = `${version() ?? ''}\n${message}\n${error === undefined ? '' : describe(error)}`
181
181
  if (hidden.get(id) === hides) return id
182
182
  hidden.delete(id)
183
+ const shown = entries.get(id)
184
+ if (shown?.hides === hides && shown.trying === undefined && shown.options.description === description) return id
183
185
  const stack = error instanceof Error && error.stack !== undefined ? `\n\n${error.stack}` : ''
184
186
  const details = async () => {
185
187
  const modVersion = await claude.fs
@@ -1,11 +1,11 @@
1
1
  import { isObject } from '../../../../records.js'
2
2
  import { Task } from '../../../goal/task.js'
3
3
  import { InstructionFile, isRulePath } from './instruction-file.js'
4
- import { Note } from './note.js'
4
+ import { modNote, modNoteBlock, modNotesIn, Note } from './note.js'
5
5
  import { Notice } from './notice.js'
6
6
  import { Prompt } from './prompt.js'
7
7
  import { Reply } from './reply.js'
8
- import { senderOf } from './sender.js'
8
+ import { Mod, senderOf } from './sender.js'
9
9
  import { Skill } from './skill.js'
10
10
  import { ToolCall, type CallFiles } from './tool-call.js'
11
11
  import { ToolResult } from './tool-result.js'
@@ -108,8 +108,11 @@ export function messagesOf(row: Row, isInTask: boolean): Unplaced[] {
108
108
  function unplacedOf(row: Row, isInTask: boolean): Unplaced[] {
109
109
  const base = { id: row.id, at: row.at }
110
110
  const text = textOf(row.content).trim()
111
+ const notesFrom = (each: readonly { readonly mod: string; readonly text: string }[]): Unplaced[] => each.map((note) => ({ ...base, type: Note, text: note.text, from: { type: Mod, name: note.mod } }))
111
112
  if (row.type === 'system') return row.name === 'turn_duration' || row.name === 'compact_boundary' || text === '' ? [] : [{ ...base, type: Notice, text }]
113
+ if (row.type === 'attachment' && row.name === 'hook_additional_context') return notesFrom(row.content.flatMap((entry) => modNotesIn(typeof entry === 'string' ? entry : textOf([entry]))))
112
114
  if (row.type === 'attachment') return row.name === 'nested_memory' ? [{ ...base, type: InstructionFile, path: row.path ?? '', text, isRule: isRulePath(row.path ?? '') }] : []
115
+ if (isModNotesRow(row)) return notesFrom(modNotesIn(text))
113
116
  if (row.type === 'assistant') {
114
117
  return row.content.filter(isObject).flatMap((block): Unplaced[] => {
115
118
  if (block['type'] === 'text' && typeof block['text'] === 'string' && block['text'].trim() !== '') return [{ ...base, type: Reply, text: block['text'].trim() }]
@@ -128,7 +131,29 @@ function unplacedOf(row: Row, isInTask: boolean): Unplaced[] {
128
131
  return prompt === undefined ? [] : [{ ...base, type: Prompt, text: prompt, from: senderOf(row.origin, isInTask) }]
129
132
  }
130
133
 
134
+ function isModNotesRow(row: Row): boolean {
135
+ const text = textOf(row.content)
136
+ return row.type === 'user' && !row.content.some(isToolResultBlock) && modNotesIn(text).length > 0 && text.replace(modNoteBlock, '').trim() === ''
137
+ }
138
+
139
+ function withNoteTexts(entries: readonly unknown[], texts: ReadonlyMap<number, string>): unknown[] {
140
+ let index = -1
141
+ const replaced = (text: string) =>
142
+ text.replace(modNoteBlock, (whole, mod: string) => {
143
+ index += 1
144
+ const changed = texts.get(index)
145
+ if (changed === undefined) return whole
146
+ return changed === '' ? '' : modNote(mod, changed)
147
+ })
148
+ return entries.map((entry) => {
149
+ if (typeof entry === 'string') return replaced(entry)
150
+ return isObject(entry) && entry['type'] === 'text' && typeof entry['text'] === 'string' ? { ...entry, text: replaced(entry['text']) } : entry
151
+ })
152
+ }
153
+
131
154
  export function withTexts(row: Row, texts: ReadonlyMap<number, string>): readonly unknown[] {
155
+ if (row.type === 'attachment' && row.name === 'hook_additional_context') return withNoteTexts(row.content, texts)
156
+ if (isModNotesRow(row)) return keepingMedia(row.content, withNoteTexts([textOf(row.content)], texts)[0] as string)
132
157
  if (row.type === 'assistant') {
133
158
  let index = -1
134
159
  return row.content.map((block) => {
@@ -1,3 +1,4 @@
1
+ import { pluginName } from '../../../../records.js'
1
2
  import type { MessageOf } from './message.js'
2
3
  import type { Sender } from './sender.js'
3
4
 
@@ -7,3 +8,18 @@ export type Note = MessageOf<typeof Note> & {
7
8
  readonly text: string
8
9
  readonly from: Sender
9
10
  }
11
+
12
+ export const modNoteBlock = new RegExp(`<(${pluginName.source.slice(1, -1)})_mod>\\n([\\s\\S]*?)\\n</\\1_mod>`, 'g')
13
+
14
+ export function modNote(mod: string, text: string): string {
15
+ return `<${mod}_mod>\n${text}\n</${mod}_mod>`
16
+ }
17
+
18
+ export function modNotesIn(text: string): { readonly mod: string; readonly text: string }[] {
19
+ return [...text.matchAll(modNoteBlock)].map((match) => ({ mod: match[1] as string, text: match[2] as string }))
20
+ }
21
+
22
+ export function untagged(text: string): string {
23
+ const notes = modNotesIn(text)
24
+ return notes.length === 0 ? text : notes.map((note) => note.text).join('\n\n')
25
+ }
@@ -120,7 +120,6 @@ export type OnMethods = { readonly [Name in EventName as `on${Name}`]: OnMethod<
120
120
 
121
121
  export type BeforeMethods = { readonly [Name in BeforeName as `before${Name}`]: BeforeMethod<PayloadOf<Name>, AnswerOf<Name>> }
122
122
 
123
-
124
123
  export type RecordPart = OnMethods &
125
124
  BeforeMethods & {
126
125
  search(text: string, matcher?: Matcher<Message>): AsyncIterable<Message>
@@ -327,7 +326,7 @@ export type ProcessOnMethods = { readonly [Name in ProcessName as `on${Name}`]:
327
326
 
328
327
  export type ProcessBeforeMethods = { readonly [Name in keyof ProcessAnswer as `before${Name}`]: BeforeMethod<ProcessAsked[Name], ProcessAnswer[Name]> }
329
328
 
330
- export type CurrentSession = Session & ProcessOnMethods & ProcessBeforeMethods
329
+ export type CurrentSession = Omit<Session, 'goal'> & ProcessOnMethods & ProcessBeforeMethods & { readonly goal: Goal & Pick<ProcessBeforeMethods, 'beforeSessionStart'> }
331
330
 
332
331
  export type SessionScope = {
333
332
  readonly running?: boolean
@@ -1,6 +1,7 @@
1
1
  import type { EventResult, Timer } from 'claude-code'
2
2
  import { isObject } from '../records.js'
3
3
  import type { Claude } from '../runtime/claude.js'
4
+ import { untagged } from '../session/context/turn/message/note.js'
4
5
  import { elements } from './fake-elements.js'
5
6
  import { fakeFiles } from './fake-files.js'
6
7
 
@@ -21,7 +22,7 @@ export type Fakes = {
21
22
  prompt: { submit?: Claude['prompt']['submit'] }
22
23
  model: { complete?: Claude['model']['complete'] }
23
24
  agent: { list?: Claude['agent']['list']; spawn?: Claude['agent']['spawn'] }
24
- clock: { after?: Claude['clock']['after']; every?: Claude['clock']['every'] }
25
+ clock: { now?: Claude['clock']['now']; after?: Claude['clock']['after']; every?: Claude['clock']['every'] }
25
26
  cmod: { call?: Claude['cmod']['call'] }
26
27
  tool: { call?: Claude['tool']['call'] }
27
28
  }
@@ -79,7 +80,7 @@ export function fakeClaude(plugin: { readonly name: string; readonly root: strin
79
80
  }
80
81
 
81
82
  const appendNote: Claude['session']['append'] = async ({ message }) => {
82
- shown.notes.push(message.content.map((block) => (typeof block['text'] === 'string' ? block['text'] : '')).join(''))
83
+ shown.notes.push(untagged(message.content.map((block) => (typeof block['text'] === 'string' ? block['text'] : '')).join('')))
83
84
  return { message, uuid: `note-${shown.notes.length}` }
84
85
  }
85
86
  const submitPrompt: Claude['prompt']['submit'] = async ({ text }) => {
@@ -175,7 +176,7 @@ export function fakeClaude(plugin: { readonly name: string; readonly root: strin
175
176
  keys: async () => [...store.keys()],
176
177
  },
177
178
  clock: {
178
- now: async () => Date.now(),
179
+ now: () => (fakes.clock.now === undefined ? Promise.resolve(Date.now()) : fakes.clock.now()),
179
180
  after: faked('clock.after', () => fakes.clock.after ?? realTimer),
180
181
  every: faked('clock.every', () => fakes.clock.every ?? idleInterval),
181
182
  },
package/src/testing.ts CHANGED
@@ -23,7 +23,7 @@ import type { Options, OptionValues } from './options.js'
23
23
  import { readSteps, storeFolder, type Steps } from './records.js'
24
24
  import type { Claude } from './runtime/claude.js'
25
25
  import { answerCall, cmodPluginName, notInstalled } from './runtime/dependencies.js'
26
- import { closingContext, foldBefore, installBefore, withAnswer } from './runtime/before.js'
26
+ import { closingContext, foldBefore, installBefore, withAnswer, type Answers } from './runtime/before.js'
27
27
  import { permissionRoutes } from './runtime/permission-check.js'
28
28
  import type { RoutedEvent } from './runtime/hooks.js'
29
29
  import { keepClaudeCode, type ClaudeCode } from './testing/claude-code.js'
@@ -34,7 +34,7 @@ import { MissingOptions } from './runtime/options.js'
34
34
  import { conversationMethods } from './runtime/sessions.js'
35
35
  import type { ShownToast } from './runtime/toaster.js'
36
36
  import { Message } from './session/context/turn/message/message.js'
37
- import { Note } from './session/context/turn/message/note.js'
37
+ import { Note, untagged } from './session/context/turn/message/note.js'
38
38
  import { Prompt } from './session/context/turn/message/prompt.js'
39
39
  import { Mod, Person } from './session/context/turn/message/sender.js'
40
40
  import type { SessionChange } from './session/conversation.js'
@@ -215,7 +215,7 @@ export function testMod<State extends object, Declared extends Options = Options
215
215
  hooks: {
216
216
  place: async () => conversations.place(),
217
217
  wants: async (event) => conversations.wants(name, event),
218
- decide: (event, change) => foldBefore(event, change, [(latest) => askMod(event, latest)], (error) => fake.claude.ui.log(`cmod: passed the ${event} on without ${name}'s answer: ${messageOf(error)}`, { to: 'debug' })),
218
+ fold: () => (event, change) => foldBefore(event, change, [(latest) => askMod(event, latest)], (error) => fake.claude.ui.log(`cmod: passed the ${event} on without ${name}'s answer: ${messageOf(error)}`, { to: 'debug' })),
219
219
  },
220
220
  })
221
221
 
@@ -273,7 +273,8 @@ export function testMod<State extends object, Declared extends Options = Options
273
273
 
274
274
  const asked = async (event: BeforeEvent, change: SessionChange) => {
275
275
  await start()
276
- return (await askMod(event, change)) ?? undefined
276
+ const answer = ((await askMod(event, change)) as Partial<Answers> | null | undefined) ?? undefined
277
+ return answer?.note === undefined ? answer : { ...answer, note: untagged(answer.note) }
277
278
  }
278
279
 
279
280
  const raise = Object.fromEntries([
@@ -470,7 +471,7 @@ function plainOutput({ component, props }: Args<'ui.render'>): RenderElement {
470
471
  }
471
472
 
472
473
  function replyOf({ text, context }: CommandRunResult): CommandReply {
473
- return { ...(text === undefined ? {} : { text }), ...(context === undefined ? {} : { note: context.join('\n') }) }
474
+ return { ...(text === undefined ? {} : { text }), ...(context === undefined ? {} : { note: context.map(untagged).join('\n') }) }
474
475
  }
475
476
 
476
477
  async function modSteps(): Promise<Steps> {
@@ -52,6 +52,7 @@ export type Fakes = {
52
52
  spawn?: Claude['agent']['spawn'];
53
53
  };
54
54
  clock: {
55
+ now?: Claude['clock']['now'];
55
56
  after?: Claude['clock']['after'];
56
57
  every?: Claude['clock']['every'];
57
58
  };
@@ -1 +1 @@
1
- {"version":3,"file":"fake-claude.d.ts","sourceRoot":"","sources":["../src/testing/fake-claude.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fake-claude.d.ts","sourceRoot":"","sources":["../src/testing/fake-claude.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { isObject } from '../records.js';
2
+ import { untagged } from '../session/context/turn/message/note.js';
2
3
  import { elements } from './fake-elements.js';
3
4
  import { fakeFiles } from './fake-files.js';
4
5
  export function calledAnswer(caller, answer) {
@@ -29,7 +30,7 @@ export function fakeClaude(plugin) {
29
30
  return async (...args) => run(...args);
30
31
  };
31
32
  const appendNote = async ({ message }) => {
32
- shown.notes.push(message.content.map((block) => (typeof block['text'] === 'string' ? block['text'] : '')).join(''));
33
+ shown.notes.push(untagged(message.content.map((block) => (typeof block['text'] === 'string' ? block['text'] : '')).join('')));
33
34
  return { message, uuid: `note-${shown.notes.length}` };
34
35
  };
35
36
  const submitPrompt = async ({ text }) => {
@@ -129,7 +130,7 @@ export function fakeClaude(plugin) {
129
130
  keys: async () => [...store.keys()],
130
131
  },
131
132
  clock: {
132
- now: async () => Date.now(),
133
+ now: () => (fakes.clock.now === undefined ? Promise.resolve(Date.now()) : fakes.clock.now()),
133
134
  after: faked('clock.after', () => fakes.clock.after ?? realTimer),
134
135
  every: faked('clock.every', () => fakes.clock.every ?? idleInterval),
135
136
  },
package/testing.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["src/testing.ts"],"names":[],"mappings":"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"}
package/testing.js CHANGED
@@ -9,7 +9,7 @@ import { createLifecycle } from './runtime/lifecycle.js';
9
9
  import { MissingOptions } from './runtime/options.js';
10
10
  import { conversationMethods } from './runtime/sessions.js';
11
11
  import { Message } from './session/context/turn/message/message.js';
12
- import { Note } from './session/context/turn/message/note.js';
12
+ import { Note, untagged } from './session/context/turn/message/note.js';
13
13
  import { Prompt } from './session/context/turn/message/prompt.js';
14
14
  import { Mod, Person } from './session/context/turn/message/sender.js';
15
15
  import { TurnEnd } from './session/context/turn/turn.js';
@@ -117,7 +117,7 @@ export function testMod(definition, options = {}) {
117
117
  hooks: {
118
118
  place: async () => conversations.place(),
119
119
  wants: async (event) => conversations.wants(name, event),
120
- decide: (event, change) => foldBefore(event, change, [(latest) => askMod(event, latest)], (error) => fake.claude.ui.log(`cmod: passed the ${event} on without ${name}'s answer: ${messageOf(error)}`, { to: 'debug' })),
120
+ fold: () => (event, change) => foldBefore(event, change, [(latest) => askMod(event, latest)], (error) => fake.claude.ui.log(`cmod: passed the ${event} on without ${name}'s answer: ${messageOf(error)}`, { to: 'debug' })),
121
121
  },
122
122
  });
123
123
  const route = async (event, input, below) => {
@@ -172,7 +172,8 @@ export function testMod(definition, options = {}) {
172
172
  });
173
173
  const asked = async (event, change) => {
174
174
  await start();
175
- return (await askMod(event, change)) ?? undefined;
175
+ const answer = (await askMod(event, change)) ?? undefined;
176
+ return answer?.note === undefined ? answer : { ...answer, note: untagged(answer.note) };
176
177
  };
177
178
  const raise = Object.fromEntries([
178
179
  ...beforeEvents
@@ -378,7 +379,7 @@ function plainOutput({ component, props }) {
378
379
  return elements.Box({ flexDirection: 'column', children: [elements.Text({ children: component }), elements.Box({ flexDirection: 'column', paddingLeft: 2, children: rows })] });
379
380
  }
380
381
  function replyOf({ text, context }) {
381
- return { ...(text === undefined ? {} : { text }), ...(context === undefined ? {} : { note: context.join('\n') }) };
382
+ return { ...(text === undefined ? {} : { text }), ...(context === undefined ? {} : { note: context.map(untagged).join('\n') }) };
382
383
  }
383
384
  async function modSteps() {
384
385
  const manifest = '../../../package.json';