@cmodjs/core 0.3.0 → 1.5.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 (280) hide show
  1. package/docs/commands.md +61 -10
  2. package/docs/dependencies.md +16 -14
  3. package/docs/hooks.md +64 -29
  4. package/docs/index.md +29 -25
  5. package/docs/install-steps.md +13 -9
  6. package/docs/jobs.md +59 -111
  7. package/docs/mod.md +227 -175
  8. package/docs/options.md +2 -2
  9. package/docs/permissions.md +27 -16
  10. package/docs/session.md +311 -0
  11. package/docs/state.md +11 -7
  12. package/docs/testing.md +68 -23
  13. package/docs/ui.md +116 -76
  14. package/jobs/check.d.ts.map +1 -1
  15. package/jobs/check.js +5 -2
  16. package/jobs/context.d.ts +7 -8
  17. package/jobs/context.d.ts.map +1 -1
  18. package/jobs/context.js +23 -73
  19. package/jobs/permissions/decide-permission.d.ts +5 -3
  20. package/jobs/permissions/decide-permission.d.ts.map +1 -1
  21. package/jobs/permissions/decide-permission.js +6 -4
  22. package/jobs/permissions/find-project-scope.d.ts +1 -1
  23. package/jobs/permissions/find-project-scope.d.ts.map +1 -1
  24. package/jobs/permissions/find-project-scope.js +8 -11
  25. package/jobs/permissions/match-target.d.ts +2 -2
  26. package/jobs/permissions/match-target.d.ts.map +1 -1
  27. package/jobs/permissions.d.ts +1 -1
  28. package/jobs/permissions.d.ts.map +1 -1
  29. package/jobs/permissions.js +6 -4
  30. package/jobs/program.d.ts.map +1 -1
  31. package/jobs/program.js +14 -4
  32. package/jobs/prompt.d.ts +4 -2
  33. package/jobs/prompt.d.ts.map +1 -1
  34. package/jobs/prompt.js +16 -15
  35. package/jobs/schedule.d.ts +2 -2
  36. package/jobs/schedule.d.ts.map +1 -1
  37. package/jobs/schedule.js +9 -9
  38. package/jobs/slash-command.d.ts +4 -4
  39. package/jobs/slash-command.d.ts.map +1 -1
  40. package/jobs/slash-command.js +14 -9
  41. package/jobs/status-line.d.ts +1 -11
  42. package/jobs/status-line.d.ts.map +1 -1
  43. package/jobs/status-line.js +8 -12
  44. package/jobs/tool.d.ts +2 -2
  45. package/jobs/tool.d.ts.map +1 -1
  46. package/jobs/tool.js +15 -9
  47. package/mod.d.ts +178 -75
  48. package/mod.d.ts.map +1 -1
  49. package/mod.js +127 -3
  50. package/options.d.ts +2 -2
  51. package/options.js +19 -19
  52. package/package.json +3 -3
  53. package/records.d.ts +13 -8
  54. package/records.d.ts.map +1 -1
  55. package/records.js +33 -17
  56. package/register.d.ts.map +1 -1
  57. package/register.js +34 -5
  58. package/runtime/announce.d.ts +18 -0
  59. package/runtime/announce.d.ts.map +1 -0
  60. package/runtime/announce.js +68 -0
  61. package/runtime/before.d.ts +32 -0
  62. package/runtime/before.d.ts.map +1 -0
  63. package/runtime/before.js +214 -0
  64. package/runtime/bound.d.ts +4 -0
  65. package/runtime/bound.d.ts.map +1 -0
  66. package/runtime/bound.js +9 -0
  67. package/runtime/claude.d.ts +4 -2
  68. package/runtime/claude.d.ts.map +1 -1
  69. package/runtime/conversations.d.ts +17 -0
  70. package/runtime/conversations.d.ts.map +1 -0
  71. package/runtime/conversations.js +563 -0
  72. package/runtime/deadline.d.ts +0 -1
  73. package/runtime/deadline.d.ts.map +1 -1
  74. package/runtime/deadline.js +1 -1
  75. package/runtime/dependencies.d.ts +2 -0
  76. package/runtime/dependencies.d.ts.map +1 -1
  77. package/runtime/dependencies.js +10 -1
  78. package/runtime/engine.d.ts +13 -0
  79. package/runtime/engine.d.ts.map +1 -0
  80. package/runtime/engine.js +10 -0
  81. package/runtime/grants.d.ts +4 -0
  82. package/runtime/grants.d.ts.map +1 -1
  83. package/runtime/grants.js +68 -31
  84. package/runtime/hooks.d.ts +5 -4
  85. package/runtime/hooks.d.ts.map +1 -1
  86. package/runtime/hooks.js +29 -9
  87. package/runtime/installer.d.ts +1 -1
  88. package/runtime/installer.d.ts.map +1 -1
  89. package/runtime/installer.js +17 -14
  90. package/runtime/lifecycle.d.ts +3 -1
  91. package/runtime/lifecycle.d.ts.map +1 -1
  92. package/runtime/lifecycle.js +414 -212
  93. package/runtime/metadata.d.ts +3 -1
  94. package/runtime/metadata.d.ts.map +1 -1
  95. package/runtime/metadata.js +25 -14
  96. package/runtime/options.d.ts +3 -1
  97. package/runtime/options.d.ts.map +1 -1
  98. package/runtime/options.js +4 -4
  99. package/runtime/programs.d.ts +8 -0
  100. package/runtime/programs.d.ts.map +1 -1
  101. package/runtime/programs.js +39 -1
  102. package/runtime/router.d.ts +2 -1
  103. package/runtime/router.d.ts.map +1 -1
  104. package/runtime/router.js +11 -3
  105. package/runtime/sessions.d.ts +58 -0
  106. package/runtime/sessions.d.ts.map +1 -0
  107. package/runtime/sessions.js +433 -0
  108. package/runtime/state.d.ts +6 -2
  109. package/runtime/state.d.ts.map +1 -1
  110. package/runtime/state.js +46 -13
  111. package/runtime/toaster.d.ts +24 -0
  112. package/runtime/toaster.d.ts.map +1 -0
  113. package/runtime/toaster.js +286 -0
  114. package/runtime/ui.d.ts +6 -17
  115. package/runtime/ui.d.ts.map +1 -1
  116. package/runtime/ui.js +62 -118
  117. package/session/context/context.d.ts +12 -0
  118. package/session/context/context.d.ts.map +1 -0
  119. package/session/context/context.js +2 -0
  120. package/session/context/turn/message/document.d.ts +7 -0
  121. package/session/context/turn/message/document.d.ts.map +1 -0
  122. package/session/context/turn/message/document.js +1 -0
  123. package/session/context/turn/message/message.d.ts +55 -0
  124. package/session/context/turn/message/message.d.ts.map +1 -0
  125. package/session/context/turn/message/message.js +178 -0
  126. package/session/context/turn/message/note.d.ts +8 -0
  127. package/session/context/turn/message/note.d.ts.map +1 -0
  128. package/session/context/turn/message/note.js +1 -0
  129. package/session/context/turn/message/notice.d.ts +6 -0
  130. package/session/context/turn/message/notice.d.ts.map +1 -0
  131. package/session/context/turn/message/notice.js +1 -0
  132. package/session/context/turn/message/prompt.d.ts +8 -0
  133. package/session/context/turn/message/prompt.d.ts.map +1 -0
  134. package/session/context/turn/message/prompt.js +1 -0
  135. package/session/context/turn/message/reply.d.ts +6 -0
  136. package/session/context/turn/message/reply.d.ts.map +1 -0
  137. package/session/context/turn/message/reply.js +1 -0
  138. package/session/context/turn/message/sender.d.ts +14 -0
  139. package/session/context/turn/message/sender.d.ts.map +1 -0
  140. package/session/context/turn/message/sender.js +30 -0
  141. package/session/context/turn/message/skill.d.ts +7 -0
  142. package/session/context/turn/message/skill.d.ts.map +1 -0
  143. package/session/context/turn/message/skill.js +1 -0
  144. package/session/context/turn/message/tool-call.d.ts +9 -0
  145. package/session/context/turn/message/tool-call.d.ts.map +1 -0
  146. package/session/context/turn/message/tool-call.js +1 -0
  147. package/session/context/turn/message/tool-result.d.ts +8 -0
  148. package/session/context/turn/message/tool-result.d.ts.map +1 -0
  149. package/session/context/turn/message/tool-result.js +1 -0
  150. package/session/context/turn/turn.d.ts +13 -0
  151. package/session/context/turn/turn.d.ts.map +1 -0
  152. package/session/context/turn/turn.js +2 -0
  153. package/session/conversation.d.ts +73 -0
  154. package/session/conversation.d.ts.map +1 -0
  155. package/session/conversation.js +146 -0
  156. package/session/goal/goal.d.ts +11 -0
  157. package/session/goal/goal.d.ts.map +1 -0
  158. package/session/goal/goal.js +1 -0
  159. package/session/goal/task.d.ts +16 -0
  160. package/session/goal/task.d.ts.map +1 -0
  161. package/session/goal/task.js +3 -0
  162. package/session/session.d.ts +158 -0
  163. package/session/session.d.ts.map +1 -0
  164. package/session/session.js +2 -0
  165. package/src/jobs/check.ts +7 -4
  166. package/src/jobs/context.ts +24 -82
  167. package/src/jobs/permissions/decide-permission.ts +10 -8
  168. package/src/jobs/permissions/find-project-scope.ts +8 -14
  169. package/src/jobs/permissions/match-target.ts +3 -3
  170. package/src/jobs/permissions.ts +8 -6
  171. package/src/jobs/program.ts +13 -4
  172. package/src/jobs/prompt.ts +21 -24
  173. package/src/jobs/schedule.ts +14 -14
  174. package/src/jobs/slash-command.ts +20 -14
  175. package/src/jobs/status-line.ts +9 -21
  176. package/src/jobs/tool.ts +17 -11
  177. package/src/mod.ts +252 -97
  178. package/src/options.ts +22 -22
  179. package/src/records.ts +40 -23
  180. package/src/register.ts +34 -5
  181. package/src/runtime/announce.ts +76 -0
  182. package/src/runtime/before.ts +200 -0
  183. package/src/runtime/bound.ts +12 -0
  184. package/src/runtime/claude.ts +7 -2
  185. package/src/runtime/conversations.ts +579 -0
  186. package/src/runtime/deadline.ts +2 -2
  187. package/src/runtime/dependencies.ts +13 -1
  188. package/src/runtime/engine.ts +23 -0
  189. package/src/runtime/grants.ts +69 -29
  190. package/src/runtime/hooks.ts +39 -14
  191. package/src/runtime/installer.ts +16 -14
  192. package/src/runtime/lifecycle.ts +413 -201
  193. package/src/runtime/metadata.ts +37 -14
  194. package/src/runtime/options.ts +19 -4
  195. package/src/runtime/programs.ts +40 -1
  196. package/src/runtime/router.ts +11 -4
  197. package/src/runtime/sessions.ts +447 -0
  198. package/src/runtime/state.ts +50 -14
  199. package/src/runtime/toaster.ts +334 -0
  200. package/src/runtime/ui.ts +65 -143
  201. package/src/session/context/context.ts +14 -0
  202. package/src/session/context/turn/message/document.ts +8 -0
  203. package/src/session/context/turn/message/message.ts +203 -0
  204. package/src/session/context/turn/message/note.ts +9 -0
  205. package/src/session/context/turn/message/notice.ts +7 -0
  206. package/src/session/context/turn/message/prompt.ts +9 -0
  207. package/src/session/context/turn/message/reply.ts +7 -0
  208. package/src/session/context/turn/message/sender.ts +38 -0
  209. package/src/session/context/turn/message/skill.ts +8 -0
  210. package/src/session/context/turn/message/tool-call.ts +10 -0
  211. package/src/session/context/turn/message/tool-result.ts +9 -0
  212. package/src/session/context/turn/turn.ts +15 -0
  213. package/src/session/conversation.ts +194 -0
  214. package/src/session/goal/goal.ts +10 -0
  215. package/src/session/goal/task.ts +19 -0
  216. package/src/session/session.ts +141 -0
  217. package/src/testing/fake-claude.ts +25 -17
  218. package/src/testing/fake-elements.ts +3 -2
  219. package/src/testing/fake-files.ts +1 -1
  220. package/src/testing/fake-sessions.ts +277 -0
  221. package/src/testing.ts +138 -35
  222. package/src/ui/components.ts +3 -3
  223. package/src/ui/elements.ts +22 -8
  224. package/src/ui/markdown.ts +22 -26
  225. package/src/ui/places.ts +49 -0
  226. package/src/utils/call-effects.ts +5 -5
  227. package/src/utils/changelog.ts +32 -0
  228. package/src/utils/disposable.ts +3 -0
  229. package/src/utils/matcher.ts +37 -0
  230. package/src/utils/metadata.ts +17 -15
  231. package/src/utils/parse-shell.ts +9 -8
  232. package/src/utils/paths.ts +21 -4
  233. package/src/utils/text.ts +4 -0
  234. package/testing/fake-claude.d.ts +5 -1
  235. package/testing/fake-claude.d.ts.map +1 -1
  236. package/testing/fake-claude.js +21 -12
  237. package/testing/fake-elements.js +4 -2
  238. package/testing/fake-files.js +1 -1
  239. package/testing/fake-sessions.d.ts +81 -0
  240. package/testing/fake-sessions.d.ts.map +1 -0
  241. package/testing/fake-sessions.js +249 -0
  242. package/testing.d.ts +34 -13
  243. package/testing.d.ts.map +1 -1
  244. package/testing.js +115 -23
  245. package/ui/components.d.ts +2 -3
  246. package/ui/components.d.ts.map +1 -1
  247. package/ui/components.js +3 -3
  248. package/ui/elements.d.ts.map +1 -1
  249. package/ui/elements.js +19 -9
  250. package/ui/markdown.d.ts +35 -33
  251. package/ui/markdown.d.ts.map +1 -1
  252. package/ui/markdown.js +14 -16
  253. package/ui/places.d.ts +43 -0
  254. package/ui/places.d.ts.map +1 -0
  255. package/ui/places.js +31 -0
  256. package/utils/call-effects.d.ts +2 -1
  257. package/utils/call-effects.d.ts.map +1 -1
  258. package/utils/call-effects.js +3 -3
  259. package/utils/changelog.d.ts +9 -0
  260. package/utils/changelog.d.ts.map +1 -0
  261. package/utils/changelog.js +29 -0
  262. package/utils/disposable.d.ts +4 -0
  263. package/utils/disposable.d.ts.map +1 -0
  264. package/utils/disposable.js +1 -0
  265. package/utils/matcher.d.ts +13 -0
  266. package/utils/matcher.d.ts.map +1 -0
  267. package/utils/matcher.js +32 -0
  268. package/utils/metadata.d.ts +3 -3
  269. package/utils/metadata.js +18 -14
  270. package/utils/parse-shell.js +9 -7
  271. package/utils/paths.d.ts +4 -1
  272. package/utils/paths.d.ts.map +1 -1
  273. package/utils/paths.js +19 -4
  274. package/utils/text.d.ts +1 -0
  275. package/utils/text.d.ts.map +1 -1
  276. package/utils/text.js +3 -0
  277. package/src/ui/slots.ts +0 -36
  278. package/ui/slots.d.ts +0 -208
  279. package/ui/slots.d.ts.map +0 -1
  280. package/ui/slots.js +0 -19
@@ -0,0 +1,203 @@
1
+ import { isObject } from '../../../../records.js'
2
+ import { Task } from '../../../goal/task.js'
3
+ import { Document } from './document.js'
4
+ import { Note } from './note.js'
5
+ import { Notice } from './notice.js'
6
+ import { Prompt } from './prompt.js'
7
+ import { Reply } from './reply.js'
8
+ import { senderOf } from './sender.js'
9
+ import { Skill } from './skill.js'
10
+ import { ToolCall } from './tool-call.js'
11
+ import { ToolResult } from './tool-result.js'
12
+
13
+ export const Message = 'message'
14
+
15
+ export type MessageType = typeof Prompt | typeof Note | typeof Reply | typeof ToolCall | typeof ToolResult | typeof Skill | typeof Document | typeof Notice
16
+
17
+ export type MessageOf<Type extends MessageType> = {
18
+ readonly id: string
19
+ readonly type: Type
20
+ readonly at: string
21
+ readonly session: string
22
+ readonly context: number
23
+ readonly turn?: string
24
+ readonly task?: string
25
+ }
26
+
27
+ export type Message = Prompt | Note | Reply | ToolCall | ToolResult | Skill | Document | Notice
28
+
29
+ export type Placed = 'session' | 'context' | 'turn' | 'task'
30
+
31
+ export type Unplaced<M extends Message = Message> = M extends Message ? Omit<M, Placed> : never
32
+
33
+ export type Row = {
34
+ readonly id: string
35
+ readonly at: string
36
+ readonly type: 'user' | 'assistant' | 'attachment' | 'system'
37
+ readonly name?: string
38
+ readonly isMeta: boolean
39
+ readonly isSummary: boolean
40
+ readonly content: readonly unknown[]
41
+ readonly origin?: unknown
42
+ readonly cwd?: string
43
+ readonly path?: string
44
+ readonly model?: string
45
+ }
46
+
47
+ export const messageTypes: readonly MessageType[] = [Prompt, Note, Reply, ToolCall, ToolResult, Skill, Document, Notice]
48
+
49
+ export const skillPrefix = 'Base directory for this skill:'
50
+
51
+ const rowTypes: readonly string[] = ['user', 'assistant', 'attachment', 'system']
52
+
53
+ const memoryPath = /^Contents of (.+?) \(/
54
+
55
+ export function rowOfLine(row: Readonly<Record<string, unknown>>): Row | undefined {
56
+ const { uuid, timestamp, type } = row
57
+ if (typeof uuid !== 'string' || typeof timestamp !== 'string' || typeof type !== 'string' || !rowTypes.includes(type)) return undefined
58
+ const message = isObject(row['message']) ? row['message'] : {}
59
+ const attachment = isObject(row['attachment']) ? row['attachment'] : {}
60
+ const content = type === 'attachment' ? attachment['content'] : type === 'system' ? row['content'] : message['content']
61
+ const name = type === 'attachment' ? attachment['type'] : row['subtype']
62
+ return {
63
+ id: uuid,
64
+ at: timestamp,
65
+ type: type as Row['type'],
66
+ ...(typeof name === 'string' ? { name } : {}),
67
+ isMeta: row['isMeta'] === true,
68
+ isSummary: row['isCompactSummary'] === true,
69
+ content: blocksOf(content),
70
+ ...(row['origin'] === undefined ? {} : { origin: row['origin'] }),
71
+ ...(typeof row['cwd'] === 'string' ? { cwd: row['cwd'] } : {}),
72
+ ...(typeof attachment['path'] === 'string' ? { path: attachment['path'] } : {}),
73
+ ...(typeof message['model'] === 'string' ? { model: message['model'] } : {}),
74
+ }
75
+ }
76
+
77
+ export function rowOfAppend(append: { readonly uuid: string; readonly door: string; readonly origin: unknown; readonly message: { readonly type: Row['type']; readonly name?: string; readonly isMeta?: true; readonly content: readonly unknown[] } }, at: string, cwd: string): Row {
78
+ const { message, origin } = append
79
+ const text = textOf(message.content)
80
+ const path = message.type === 'attachment' ? memoryPath.exec(text)?.[1] : undefined
81
+ const model = isObject(origin) && origin['kind'] === 'model' && typeof origin['model'] === 'string' ? origin['model'] : undefined
82
+ return {
83
+ id: append.uuid,
84
+ at,
85
+ type: message.type,
86
+ ...(message.name === undefined ? {} : { name: message.name }),
87
+ isMeta: message.isMeta === true,
88
+ isSummary: append.door === 'compaction',
89
+ content: message.content,
90
+ origin,
91
+ cwd,
92
+ ...(path === undefined ? {} : { path }),
93
+ ...(model === undefined ? {} : { model }),
94
+ }
95
+ }
96
+
97
+ export function messagesOf(row: Row, isInTask: boolean): Unplaced[] {
98
+ const found = unplacedOf(row, isInTask)
99
+ return found.length === 1 ? found : found.map((message, index) => ({ ...message, id: `${row.id}:${index}` }))
100
+ }
101
+
102
+ function unplacedOf(row: Row, isInTask: boolean): Unplaced[] {
103
+ const base = { id: row.id, at: row.at }
104
+ const text = textOf(row.content).trim()
105
+ if (row.type === 'system') return row.name === 'turn_duration' || row.name === 'compact_boundary' || text === '' ? [] : [{ ...base, type: Notice, text }]
106
+ if (row.type === 'attachment') return row.name === 'nested_memory' ? [{ ...base, type: Document, path: row.path ?? '', text }] : []
107
+ if (row.type === 'assistant') {
108
+ return row.content.filter(isObject).flatMap((block): Unplaced[] => {
109
+ if (block['type'] === 'text' && typeof block['text'] === 'string' && block['text'].trim() !== '') return [{ ...base, type: Reply, text: block['text'].trim() }]
110
+ if (block['type'] !== 'tool_use' || typeof block['id'] !== 'string' || typeof block['name'] !== 'string') return []
111
+ return [{ ...base, type: ToolCall, callId: block['id'], name: block['name'], input: isObject(block['input']) ? block['input'] : {}, cwd: row.cwd ?? '' }]
112
+ })
113
+ }
114
+ if (row.isSummary) return []
115
+ const results = row.content.filter(isToolResultBlock)
116
+ if (results.length > 0) return results.map((block) => ({ ...base, type: ToolResult, callId: block['tool_use_id'] as string, text: textOf(block['content']), isError: block['is_error'] === true }))
117
+ if (text === '') return []
118
+ if (text.startsWith(skillPrefix)) return [{ ...base, type: Skill, name: basename(text.slice(skillPrefix.length).split('\n')[0]?.trim() ?? ''), text }]
119
+ if (text.startsWith('<task-notification')) return [{ ...base, type: Note, text: tagOf(text, 'summary') ?? text, from: { type: Task } }]
120
+ if (row.isMeta) return [{ ...base, type: Note, text, from: senderOf(row.origin, isInTask) }]
121
+ const prompt = promptText(text)
122
+ return prompt === undefined ? [] : [{ ...base, type: Prompt, text: prompt, from: senderOf(row.origin, isInTask) }]
123
+ }
124
+
125
+ export function withTexts(row: Row, texts: ReadonlyMap<number, string>): readonly unknown[] {
126
+ if (row.type === 'assistant') {
127
+ let index = -1
128
+ return row.content.map((block) => {
129
+ if (!isObject(block)) return block
130
+ const isText = block['type'] === 'text' && typeof block['text'] === 'string' && block['text'].trim() !== ''
131
+ const isCall = block['type'] === 'tool_use' && typeof block['id'] === 'string' && typeof block['name'] === 'string'
132
+ if (!isText && !isCall) return block
133
+ index += 1
134
+ const text = texts.get(index)
135
+ return isText && text !== undefined ? { ...block, text } : block
136
+ })
137
+ }
138
+ if (row.type === 'user' && row.content.some(isToolResultBlock)) {
139
+ let index = -1
140
+ return row.content.map((block) => {
141
+ if (!isToolResultBlock(block)) return block
142
+ index += 1
143
+ const text = texts.get(index)
144
+ if (text === undefined) return block
145
+ return { ...block, content: Array.isArray(block['content']) ? keepingMedia(block['content'], text) : text }
146
+ })
147
+ }
148
+ const text = texts.get(0)
149
+ if (text === undefined) return row.content
150
+ const whole = textOf(row.content)
151
+ if (whole.trim().startsWith('<task-notification') && tagOf(whole, 'summary') !== undefined) return keepingMedia(row.content, whole.replace(/<summary>[\s\S]*?<\/summary>/, () => `<summary>${text}</summary>`))
152
+ return keepingMedia(row.content, text)
153
+ }
154
+
155
+ function keepingMedia(content: readonly unknown[], text: string): unknown[] {
156
+ return [{ type: 'text', text }, ...content.filter((block) => !isObject(block) || block['type'] !== 'text')]
157
+ }
158
+
159
+ function isToolResultBlock(block: unknown): block is Readonly<Record<string, unknown>> {
160
+ return isObject(block) && block['type'] === 'tool_result' && typeof block['tool_use_id'] === 'string'
161
+ }
162
+
163
+ export function textOf(content: unknown): string {
164
+ if (typeof content === 'string') return content
165
+ if (!Array.isArray(content)) return ''
166
+ return content.flatMap((block) => (isObject(block) && block['type'] === 'text' && typeof block['text'] === 'string' ? [block['text']] : [])).join('\n')
167
+ }
168
+
169
+ export function tagOf(text: string, tag: string): string | undefined {
170
+ return new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`).exec(text)?.[1]?.trim()
171
+ }
172
+
173
+ const toolValueChars = 1024
174
+
175
+ export function searchText(message: Unplaced): string {
176
+ if (message.type === ToolCall) return [message.name, ...stringsIn(message.input).map((value) => value.slice(0, toolValueChars))].join('\n')
177
+ if (message.type === ToolResult) return ''
178
+ if (message.type === Document) return `${message.path}\n${message.text}`
179
+ return message.text
180
+ }
181
+
182
+ function blocksOf(content: unknown): readonly unknown[] {
183
+ if (Array.isArray(content)) return content
184
+ return typeof content === 'string' && content !== '' ? [{ type: 'text', text: content }] : []
185
+ }
186
+
187
+ function promptText(text: string): string | undefined {
188
+ if (text.startsWith('<local-command-') || text.startsWith('<bash-stdout') || text.startsWith('<bash-stderr')) return undefined
189
+ const command = tagOf(text, 'command-name')
190
+ if (command === undefined) return text
191
+ const args = tagOf(text, 'command-args') ?? ''
192
+ return args === '' ? command : `${command} ${args}`
193
+ }
194
+
195
+ function basename(path: string): string {
196
+ return path.slice(path.lastIndexOf('/') + 1)
197
+ }
198
+
199
+ function stringsIn(value: unknown): string[] {
200
+ if (typeof value === 'string') return [value]
201
+ if (Array.isArray(value)) return value.flatMap(stringsIn)
202
+ return isObject(value) ? Object.values(value).flatMap(stringsIn) : []
203
+ }
@@ -0,0 +1,9 @@
1
+ import type { MessageOf } from './message.js'
2
+ import type { Sender } from './sender.js'
3
+
4
+ export const Note = 'note'
5
+
6
+ export type Note = MessageOf<typeof Note> & {
7
+ readonly text: string
8
+ readonly from: Sender
9
+ }
@@ -0,0 +1,7 @@
1
+ import type { MessageOf } from './message.js'
2
+
3
+ export const Notice = 'notice'
4
+
5
+ export type Notice = MessageOf<typeof Notice> & {
6
+ readonly text: string
7
+ }
@@ -0,0 +1,9 @@
1
+ import type { MessageOf } from './message.js'
2
+ import type { Sender } from './sender.js'
3
+
4
+ export const Prompt = 'prompt'
5
+
6
+ export type Prompt = MessageOf<typeof Prompt> & {
7
+ readonly text: string
8
+ readonly from: Sender
9
+ }
@@ -0,0 +1,7 @@
1
+ import type { MessageOf } from './message.js'
2
+
3
+ export const Reply = 'reply'
4
+
5
+ export type Reply = MessageOf<typeof Reply> & {
6
+ readonly text: string
7
+ }
@@ -0,0 +1,38 @@
1
+ import { isObject } from '../../../../records.js'
2
+ import { Task } from '../../../goal/task.js'
3
+
4
+ export const Person = 'person'
5
+
6
+ export const Mod = 'mod'
7
+
8
+ export const Schedule = 'schedule'
9
+
10
+ export const Channel = 'channel'
11
+
12
+ export const Sdk = 'sdk'
13
+
14
+ export type SenderType = typeof Person | typeof Mod | 'session' | typeof Task | typeof Schedule | typeof Channel | typeof Sdk
15
+
16
+ export type Sender = {
17
+ readonly type: SenderType
18
+ readonly name?: string
19
+ readonly as?: typeof Person
20
+ }
21
+
22
+ const personOrigins: readonly string[] = ['human', 'composer', 'bridge', 'slack-ping', 'auto-continuation']
23
+
24
+ const sessionOrigins: readonly string[] = ['peer', 'peer-send-message', 'coordinator', 'projects-relay', 'observer', 'observer-activity']
25
+
26
+ export function senderOf(origin: unknown, isInTask: boolean): Sender {
27
+ const fields: Readonly<Record<string, unknown>> = isObject(origin) ? origin : {}
28
+ const kind = typeof fields['kind'] === 'string' ? fields['kind'] : undefined
29
+ if (kind === undefined) return { type: isInTask ? Task : Person }
30
+ if (personOrigins.includes(kind)) return { type: Person }
31
+ if (sessionOrigins.includes(kind)) return { type: 'session' }
32
+ if (kind === 'plugin' && typeof fields['name'] === 'string') return { type: Mod, name: fields['name'], ...(fields['asUser'] === true ? { as: Person } : {}) }
33
+ if (kind === 'scheduled-trigger') return { type: Schedule }
34
+ if (kind === 'channel' && typeof fields['server'] === 'string') return { type: Channel, name: fields['server'] }
35
+ if (kind === 'sdk') return { type: Sdk }
36
+ if (kind === 'task-notification') return { type: Task }
37
+ return { type: isInTask ? Task : Person }
38
+ }
@@ -0,0 +1,8 @@
1
+ import type { MessageOf } from './message.js'
2
+
3
+ export const Skill = 'skill'
4
+
5
+ export type Skill = MessageOf<typeof Skill> & {
6
+ readonly name: string
7
+ readonly text: string
8
+ }
@@ -0,0 +1,10 @@
1
+ import type { MessageOf } from './message.js'
2
+
3
+ export const ToolCall = 'toolCall'
4
+
5
+ export type ToolCall = MessageOf<typeof ToolCall> & {
6
+ readonly callId: string
7
+ readonly name: string
8
+ readonly input: Readonly<Record<string, unknown>>
9
+ readonly cwd: string
10
+ }
@@ -0,0 +1,9 @@
1
+ import type { MessageOf } from './message.js'
2
+
3
+ export const ToolResult = 'toolResult'
4
+
5
+ export type ToolResult = MessageOf<typeof ToolResult> & {
6
+ readonly callId: string
7
+ readonly text: string
8
+ readonly isError: boolean
9
+ }
@@ -0,0 +1,15 @@
1
+ import type { RecordPart } from '../../session.js'
2
+ import type { Message } from './message/message.js'
3
+ import type { Prompt } from './message/prompt.js'
4
+
5
+ export const TurnStart = 'turnStart'
6
+
7
+ export const TurnEnd = 'turnEnd'
8
+
9
+ export type Turn = RecordPart & {
10
+ readonly prompt: Prompt
11
+ readonly context: number
12
+ readonly openedAt: string
13
+ readonly closedAt?: string
14
+ messages(): Promise<Message[]>
15
+ }
@@ -0,0 +1,194 @@
1
+ import { ContextEnd, ContextStart, type Context } from './context/context.js'
2
+ import { Message, messagesOf, tagOf, textOf, type Row } from './context/turn/message/message.js'
3
+ import { Note } from './context/turn/message/note.js'
4
+ import { Prompt } from './context/turn/message/prompt.js'
5
+ import { ToolCall } from './context/turn/message/tool-call.js'
6
+ import { ToolResult } from './context/turn/message/tool-result.js'
7
+ import { TurnEnd, TurnStart, type Turn } from './context/turn/turn.js'
8
+ import { GoalChange, type Goal } from './goal/goal.js'
9
+ import { Task, TaskEnd, TaskStart } from './goal/task.js'
10
+ import type { UsageChange, Usage } from './session.js'
11
+
12
+ export type SessionData = {
13
+ readonly id: string
14
+ readonly title: string
15
+ readonly project: string
16
+ readonly cwd: string
17
+ readonly model: string
18
+ readonly startedAt: string
19
+ readonly lastAt: string
20
+ readonly forkedFrom?: string
21
+ readonly isRunning: boolean
22
+ readonly agentType?: string
23
+ }
24
+
25
+ export type ContextData = Pick<Context, 'number' | 'openedAt' | 'closedAt' | 'task'>
26
+
27
+ export type TurnData = Pick<Turn, 'prompt' | 'context' | 'openedAt' | 'closedAt'>
28
+
29
+ export type TaskData = Pick<Task, 'id' | 'title' | 'openedBy' | 'openedAt' | 'closedAt'> & { readonly agentType: string; readonly model?: string; readonly parent?: string }
30
+
31
+ export type RunningTask = TaskData & { readonly context: number; readonly contextOpenedAt?: string }
32
+
33
+ export type GoalData = Pick<Goal, 'text'> & { readonly task?: string }
34
+
35
+ export type SessionChange =
36
+ | { readonly type: typeof Message; readonly message: Message }
37
+ | { readonly type: typeof TurnStart | typeof TurnEnd; readonly turn: TurnData }
38
+ | { readonly type: typeof ContextStart | typeof ContextEnd; readonly context: ContextData }
39
+ | { readonly type: typeof TaskStart | typeof TaskEnd; readonly task: RunningTask }
40
+ | { readonly type: typeof GoalChange; readonly goal: GoalData }
41
+ | { readonly type: typeof UsageChange; readonly usage: Usage }
42
+
43
+ export type ConversationState = {
44
+ readonly session: string
45
+ readonly context: number
46
+ readonly contextOpenedAt: string
47
+ readonly turn?: TurnData
48
+ readonly goal?: string
49
+ readonly agentCalls: readonly string[]
50
+ readonly endedCalls: Readonly<Record<string, string>>
51
+ readonly tasks: Readonly<Record<string, RunningTask>>
52
+ readonly taskTurns: Readonly<Record<string, TurnData>>
53
+ }
54
+
55
+ export type Conversation = {
56
+ add(row: Row, task?: string): SessionChange[]
57
+ startTask(task: TaskData): SessionChange[]
58
+ startContext(at: string): SessionChange[]
59
+ changeGoal(text: string): SessionChange[]
60
+ readonly state: ConversationState
61
+ }
62
+
63
+ export const searchLimit = 50
64
+
65
+ const agentTools: readonly string[] = ['Agent', 'Task']
66
+
67
+ const asyncLaunch = 'Async agent launched'
68
+
69
+ export function emptyConversation(session: string): ConversationState {
70
+ return { session, context: 0, contextOpenedAt: '', agentCalls: [], endedCalls: {}, tasks: {}, taskTurns: {} }
71
+ }
72
+
73
+ export function createConversation(start: ConversationState): Conversation {
74
+ let { context, contextOpenedAt, turn, goal } = start
75
+ const agentCalls = new Set(start.agentCalls)
76
+ const endedCalls = new Map(Object.entries(start.endedCalls))
77
+ const tasks = new Map(Object.entries(start.tasks))
78
+ const taskTurns = new Map(Object.entries(start.taskTurns))
79
+
80
+ const endTaskTurn = (task: string, at: string): SessionChange[] => {
81
+ const open = taskTurns.get(task)
82
+ if (open === undefined) return []
83
+ taskTurns.delete(task)
84
+ return open.closedAt === undefined ? [{ type: TurnEnd, turn: { ...open, closedAt: at } }] : []
85
+ }
86
+
87
+ const startContext = (at: string): SessionChange[] => {
88
+ context += 1
89
+ contextOpenedAt = at
90
+ return [{ type: ContextStart, context: { number: context, openedAt: at } }]
91
+ }
92
+
93
+ const endContext = (at: string): SessionChange[] => (context === 0 ? [] : [{ type: ContextEnd, context: { number: context, openedAt: contextOpenedAt, closedAt: at } }])
94
+
95
+ const compactTask = (task: string, at: string): SessionChange[] => {
96
+ const owner = tasks.get(task)
97
+ if (owner === undefined) return []
98
+ const next = { ...owner, context: owner.context + 1, contextOpenedAt: at }
99
+ tasks.set(task, next)
100
+ return [
101
+ { type: ContextEnd, context: { number: owner.context, openedAt: owner.contextOpenedAt ?? owner.openedAt, closedAt: at, task } },
102
+ { type: ContextStart, context: { number: next.context, openedAt: at, task } },
103
+ ]
104
+ }
105
+
106
+ const endTurn = (at: string): SessionChange[] => {
107
+ if (turn === undefined) return []
108
+ const ended = { ...turn, closedAt: at }
109
+ turn = undefined
110
+ return [{ type: TurnEnd, turn: ended }]
111
+ }
112
+
113
+ const endTask = (call: string, at: string, only?: string): SessionChange[] => {
114
+ const opened = [...tasks.values()].filter((each) => each.openedBy === call && (only === undefined || each.id === only))
115
+ if (opened.length === 0) {
116
+ if (agentCalls.has(call)) endedCalls.set(call, at)
117
+ return []
118
+ }
119
+ if (only === undefined) agentCalls.delete(call)
120
+ return opened.flatMap((task) => {
121
+ if (task.closedAt !== undefined) return []
122
+ const ended = { ...task, closedAt: at }
123
+ tasks.set(task.id, ended)
124
+ return [...endTaskTurn(task.id, at), { type: TaskEnd, task: ended }]
125
+ })
126
+ }
127
+
128
+ const changeGoal = (text: string): SessionChange[] => {
129
+ goal = text
130
+ return [{ type: GoalChange, goal: { text } }]
131
+ }
132
+
133
+ return {
134
+ add(row, task) {
135
+ const changes: SessionChange[] = []
136
+ if (task === undefined && context === 0) changes.push(...startContext(row.at))
137
+ if (task === undefined && row.type === 'system' && row.name === 'turn_duration') return [...changes, ...endTurn(row.at)]
138
+ if (row.type === 'system' && row.name === 'compact_boundary') return [...changes, ...(task === undefined ? [...endContext(row.at), ...startContext(row.at)] : compactTask(task, row.at))]
139
+ const owner = task === undefined ? undefined : tasks.get(task)
140
+ const placedIn = owner?.context ?? context
141
+ for (const unplaced of messagesOf(row, task !== undefined)) {
142
+ if (unplaced.type === Prompt) {
143
+ changes.push(...(task === undefined ? endTurn(row.at) : endTaskTurn(task, row.at)))
144
+ const opened: TurnData = { prompt: { ...unplaced, session: start.session, context: placedIn, turn: unplaced.id, ...(task === undefined ? {} : { task }) }, context: placedIn, openedAt: row.at, ...(owner?.closedAt === undefined ? {} : { closedAt: owner.closedAt }) }
145
+ if (task === undefined) turn = opened
146
+ else taskTurns.set(task, opened)
147
+ changes.push({ type: TurnStart, turn: opened })
148
+ if (task === undefined && goal === undefined) changes.push(...changeGoal(unplaced.text))
149
+ }
150
+ const open = task === undefined ? turn : taskTurns.get(task)
151
+ const placed = {
152
+ ...unplaced,
153
+ session: start.session,
154
+ context: placedIn,
155
+ ...(open === undefined ? {} : { turn: open.prompt.id }),
156
+ ...(task === undefined ? {} : { task }),
157
+ } as Message
158
+ changes.push({ type: Message, message: placed })
159
+ if (placed.type === ToolCall && agentTools.includes(placed.name)) agentCalls.add(placed.callId)
160
+ if (placed.type === ToolResult && agentCalls.has(placed.callId) && !placed.text.startsWith(asyncLaunch)) changes.push(...endTask(placed.callId, row.at))
161
+ if (placed.type === Note && placed.from.type === Task) {
162
+ const call = tagOf(textOf(row.content), 'tool-use-id')
163
+ if (call !== undefined && agentCalls.has(call)) changes.push(...endTask(call, row.at, tagOf(textOf(row.content), 'task-id')))
164
+ }
165
+ }
166
+ return changes
167
+ },
168
+ startTask(data) {
169
+ if (tasks.has(data.id)) return []
170
+ const started = { ...data, context }
171
+ tasks.set(data.id, started)
172
+ agentCalls.add(data.openedBy)
173
+ const changes: SessionChange[] = [{ type: TaskStart, task: started }]
174
+ const endedAt = endedCalls.get(data.openedBy)
175
+ if (endedAt !== undefined) changes.push(...endTask(data.openedBy, endedAt, data.id))
176
+ return changes
177
+ },
178
+ startContext,
179
+ changeGoal,
180
+ get state() {
181
+ return {
182
+ session: start.session,
183
+ context,
184
+ contextOpenedAt,
185
+ ...(turn === undefined ? {} : { turn }),
186
+ ...(goal === undefined ? {} : { goal }),
187
+ agentCalls: [...agentCalls],
188
+ endedCalls: Object.fromEntries(endedCalls),
189
+ tasks: Object.fromEntries(tasks),
190
+ taskTurns: Object.fromEntries(taskTurns),
191
+ }
192
+ },
193
+ }
194
+ }
@@ -0,0 +1,10 @@
1
+ import type { RecordPart } from '../session.js'
2
+ import type { Task } from './task.js'
3
+
4
+ export const GoalChange = 'goalChange'
5
+
6
+ export type Goal = RecordPart & {
7
+ readonly text: string
8
+ tasks(): Promise<Task[]>
9
+ update(change: { readonly text: string }): Promise<void>
10
+ }
@@ -0,0 +1,19 @@
1
+ import type { Message } from '../context/turn/message/message.js'
2
+ import type { RecordPart, Session } from '../session.js'
3
+
4
+ export const Task = 'task'
5
+
6
+ export const TaskStart = 'taskStart'
7
+
8
+ export const TaskEnd = 'taskEnd'
9
+
10
+ export type Task = RecordPart & {
11
+ readonly id: string
12
+ readonly title: string
13
+ readonly owner: Session
14
+ readonly openedBy: string
15
+ readonly openedAt: string
16
+ readonly closedAt?: string
17
+ tasks(): Promise<Task[]>
18
+ messages(): Promise<Message[]>
19
+ }