@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.
- package/docs/commands.md +61 -10
- package/docs/dependencies.md +16 -14
- package/docs/hooks.md +64 -29
- package/docs/index.md +29 -25
- package/docs/install-steps.md +13 -9
- package/docs/jobs.md +59 -111
- package/docs/mod.md +227 -175
- package/docs/options.md +2 -2
- package/docs/permissions.md +27 -16
- package/docs/session.md +311 -0
- package/docs/state.md +11 -7
- package/docs/testing.md +68 -23
- package/docs/ui.md +116 -76
- package/jobs/check.d.ts.map +1 -1
- package/jobs/check.js +5 -2
- package/jobs/context.d.ts +7 -8
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +23 -73
- package/jobs/permissions/decide-permission.d.ts +5 -3
- package/jobs/permissions/decide-permission.d.ts.map +1 -1
- package/jobs/permissions/decide-permission.js +6 -4
- package/jobs/permissions/find-project-scope.d.ts +1 -1
- package/jobs/permissions/find-project-scope.d.ts.map +1 -1
- package/jobs/permissions/find-project-scope.js +8 -11
- package/jobs/permissions/match-target.d.ts +2 -2
- package/jobs/permissions/match-target.d.ts.map +1 -1
- package/jobs/permissions.d.ts +1 -1
- package/jobs/permissions.d.ts.map +1 -1
- package/jobs/permissions.js +6 -4
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +14 -4
- package/jobs/prompt.d.ts +4 -2
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +16 -15
- package/jobs/schedule.d.ts +2 -2
- package/jobs/schedule.d.ts.map +1 -1
- package/jobs/schedule.js +9 -9
- package/jobs/slash-command.d.ts +4 -4
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +14 -9
- package/jobs/status-line.d.ts +1 -11
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +8 -12
- package/jobs/tool.d.ts +2 -2
- package/jobs/tool.d.ts.map +1 -1
- package/jobs/tool.js +15 -9
- package/mod.d.ts +178 -75
- package/mod.d.ts.map +1 -1
- package/mod.js +127 -3
- package/options.d.ts +2 -2
- package/options.js +19 -19
- package/package.json +3 -3
- package/records.d.ts +13 -8
- package/records.d.ts.map +1 -1
- package/records.js +33 -17
- package/register.d.ts.map +1 -1
- package/register.js +34 -5
- package/runtime/announce.d.ts +18 -0
- package/runtime/announce.d.ts.map +1 -0
- package/runtime/announce.js +68 -0
- package/runtime/before.d.ts +32 -0
- package/runtime/before.d.ts.map +1 -0
- package/runtime/before.js +214 -0
- package/runtime/bound.d.ts +4 -0
- package/runtime/bound.d.ts.map +1 -0
- package/runtime/bound.js +9 -0
- package/runtime/claude.d.ts +4 -2
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts +17 -0
- package/runtime/conversations.d.ts.map +1 -0
- package/runtime/conversations.js +563 -0
- package/runtime/deadline.d.ts +0 -1
- package/runtime/deadline.d.ts.map +1 -1
- package/runtime/deadline.js +1 -1
- package/runtime/dependencies.d.ts +2 -0
- package/runtime/dependencies.d.ts.map +1 -1
- package/runtime/dependencies.js +10 -1
- package/runtime/engine.d.ts +13 -0
- package/runtime/engine.d.ts.map +1 -0
- package/runtime/engine.js +10 -0
- package/runtime/grants.d.ts +4 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +68 -31
- package/runtime/hooks.d.ts +5 -4
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +29 -9
- package/runtime/installer.d.ts +1 -1
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +17 -14
- package/runtime/lifecycle.d.ts +3 -1
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +414 -212
- package/runtime/metadata.d.ts +3 -1
- package/runtime/metadata.d.ts.map +1 -1
- package/runtime/metadata.js +25 -14
- package/runtime/options.d.ts +3 -1
- package/runtime/options.d.ts.map +1 -1
- package/runtime/options.js +4 -4
- package/runtime/programs.d.ts +8 -0
- package/runtime/programs.d.ts.map +1 -1
- package/runtime/programs.js +39 -1
- package/runtime/router.d.ts +2 -1
- package/runtime/router.d.ts.map +1 -1
- package/runtime/router.js +11 -3
- package/runtime/sessions.d.ts +58 -0
- package/runtime/sessions.d.ts.map +1 -0
- package/runtime/sessions.js +433 -0
- package/runtime/state.d.ts +6 -2
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +46 -13
- package/runtime/toaster.d.ts +24 -0
- package/runtime/toaster.d.ts.map +1 -0
- package/runtime/toaster.js +286 -0
- package/runtime/ui.d.ts +6 -17
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +62 -118
- package/session/context/context.d.ts +12 -0
- package/session/context/context.d.ts.map +1 -0
- package/session/context/context.js +2 -0
- package/session/context/turn/message/document.d.ts +7 -0
- package/session/context/turn/message/document.d.ts.map +1 -0
- package/session/context/turn/message/document.js +1 -0
- package/session/context/turn/message/message.d.ts +55 -0
- package/session/context/turn/message/message.d.ts.map +1 -0
- package/session/context/turn/message/message.js +178 -0
- package/session/context/turn/message/note.d.ts +8 -0
- package/session/context/turn/message/note.d.ts.map +1 -0
- package/session/context/turn/message/note.js +1 -0
- package/session/context/turn/message/notice.d.ts +6 -0
- package/session/context/turn/message/notice.d.ts.map +1 -0
- package/session/context/turn/message/notice.js +1 -0
- package/session/context/turn/message/prompt.d.ts +8 -0
- package/session/context/turn/message/prompt.d.ts.map +1 -0
- package/session/context/turn/message/prompt.js +1 -0
- package/session/context/turn/message/reply.d.ts +6 -0
- package/session/context/turn/message/reply.d.ts.map +1 -0
- package/session/context/turn/message/reply.js +1 -0
- package/session/context/turn/message/sender.d.ts +14 -0
- package/session/context/turn/message/sender.d.ts.map +1 -0
- package/session/context/turn/message/sender.js +30 -0
- package/session/context/turn/message/skill.d.ts +7 -0
- package/session/context/turn/message/skill.d.ts.map +1 -0
- package/session/context/turn/message/skill.js +1 -0
- package/session/context/turn/message/tool-call.d.ts +9 -0
- package/session/context/turn/message/tool-call.d.ts.map +1 -0
- package/session/context/turn/message/tool-call.js +1 -0
- package/session/context/turn/message/tool-result.d.ts +8 -0
- package/session/context/turn/message/tool-result.d.ts.map +1 -0
- package/session/context/turn/message/tool-result.js +1 -0
- package/session/context/turn/turn.d.ts +13 -0
- package/session/context/turn/turn.d.ts.map +1 -0
- package/session/context/turn/turn.js +2 -0
- package/session/conversation.d.ts +73 -0
- package/session/conversation.d.ts.map +1 -0
- package/session/conversation.js +146 -0
- package/session/goal/goal.d.ts +11 -0
- package/session/goal/goal.d.ts.map +1 -0
- package/session/goal/goal.js +1 -0
- package/session/goal/task.d.ts +16 -0
- package/session/goal/task.d.ts.map +1 -0
- package/session/goal/task.js +3 -0
- package/session/session.d.ts +158 -0
- package/session/session.d.ts.map +1 -0
- package/session/session.js +2 -0
- package/src/jobs/check.ts +7 -4
- package/src/jobs/context.ts +24 -82
- package/src/jobs/permissions/decide-permission.ts +10 -8
- package/src/jobs/permissions/find-project-scope.ts +8 -14
- package/src/jobs/permissions/match-target.ts +3 -3
- package/src/jobs/permissions.ts +8 -6
- package/src/jobs/program.ts +13 -4
- package/src/jobs/prompt.ts +21 -24
- package/src/jobs/schedule.ts +14 -14
- package/src/jobs/slash-command.ts +20 -14
- package/src/jobs/status-line.ts +9 -21
- package/src/jobs/tool.ts +17 -11
- package/src/mod.ts +252 -97
- package/src/options.ts +22 -22
- package/src/records.ts +40 -23
- package/src/register.ts +34 -5
- package/src/runtime/announce.ts +76 -0
- package/src/runtime/before.ts +200 -0
- package/src/runtime/bound.ts +12 -0
- package/src/runtime/claude.ts +7 -2
- package/src/runtime/conversations.ts +579 -0
- package/src/runtime/deadline.ts +2 -2
- package/src/runtime/dependencies.ts +13 -1
- package/src/runtime/engine.ts +23 -0
- package/src/runtime/grants.ts +69 -29
- package/src/runtime/hooks.ts +39 -14
- package/src/runtime/installer.ts +16 -14
- package/src/runtime/lifecycle.ts +413 -201
- package/src/runtime/metadata.ts +37 -14
- package/src/runtime/options.ts +19 -4
- package/src/runtime/programs.ts +40 -1
- package/src/runtime/router.ts +11 -4
- package/src/runtime/sessions.ts +447 -0
- package/src/runtime/state.ts +50 -14
- package/src/runtime/toaster.ts +334 -0
- package/src/runtime/ui.ts +65 -143
- package/src/session/context/context.ts +14 -0
- package/src/session/context/turn/message/document.ts +8 -0
- package/src/session/context/turn/message/message.ts +203 -0
- package/src/session/context/turn/message/note.ts +9 -0
- package/src/session/context/turn/message/notice.ts +7 -0
- package/src/session/context/turn/message/prompt.ts +9 -0
- package/src/session/context/turn/message/reply.ts +7 -0
- package/src/session/context/turn/message/sender.ts +38 -0
- package/src/session/context/turn/message/skill.ts +8 -0
- package/src/session/context/turn/message/tool-call.ts +10 -0
- package/src/session/context/turn/message/tool-result.ts +9 -0
- package/src/session/context/turn/turn.ts +15 -0
- package/src/session/conversation.ts +194 -0
- package/src/session/goal/goal.ts +10 -0
- package/src/session/goal/task.ts +19 -0
- package/src/session/session.ts +141 -0
- package/src/testing/fake-claude.ts +25 -17
- package/src/testing/fake-elements.ts +3 -2
- package/src/testing/fake-files.ts +1 -1
- package/src/testing/fake-sessions.ts +277 -0
- package/src/testing.ts +138 -35
- package/src/ui/components.ts +3 -3
- package/src/ui/elements.ts +22 -8
- package/src/ui/markdown.ts +22 -26
- package/src/ui/places.ts +49 -0
- package/src/utils/call-effects.ts +5 -5
- package/src/utils/changelog.ts +32 -0
- package/src/utils/disposable.ts +3 -0
- package/src/utils/matcher.ts +37 -0
- package/src/utils/metadata.ts +17 -15
- package/src/utils/parse-shell.ts +9 -8
- package/src/utils/paths.ts +21 -4
- package/src/utils/text.ts +4 -0
- package/testing/fake-claude.d.ts +5 -1
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +21 -12
- package/testing/fake-elements.js +4 -2
- package/testing/fake-files.js +1 -1
- package/testing/fake-sessions.d.ts +81 -0
- package/testing/fake-sessions.d.ts.map +1 -0
- package/testing/fake-sessions.js +249 -0
- package/testing.d.ts +34 -13
- package/testing.d.ts.map +1 -1
- package/testing.js +115 -23
- package/ui/components.d.ts +2 -3
- package/ui/components.d.ts.map +1 -1
- package/ui/components.js +3 -3
- package/ui/elements.d.ts.map +1 -1
- package/ui/elements.js +19 -9
- package/ui/markdown.d.ts +35 -33
- package/ui/markdown.d.ts.map +1 -1
- package/ui/markdown.js +14 -16
- package/ui/places.d.ts +43 -0
- package/ui/places.d.ts.map +1 -0
- package/ui/places.js +31 -0
- package/utils/call-effects.d.ts +2 -1
- package/utils/call-effects.d.ts.map +1 -1
- package/utils/call-effects.js +3 -3
- package/utils/changelog.d.ts +9 -0
- package/utils/changelog.d.ts.map +1 -0
- package/utils/changelog.js +29 -0
- package/utils/disposable.d.ts +4 -0
- package/utils/disposable.d.ts.map +1 -0
- package/utils/disposable.js +1 -0
- package/utils/matcher.d.ts +13 -0
- package/utils/matcher.d.ts.map +1 -0
- package/utils/matcher.js +32 -0
- package/utils/metadata.d.ts +3 -3
- package/utils/metadata.js +18 -14
- package/utils/parse-shell.js +9 -7
- package/utils/paths.d.ts +4 -1
- package/utils/paths.d.ts.map +1 -1
- package/utils/paths.js +19 -4
- package/utils/text.d.ts +1 -0
- package/utils/text.d.ts.map +1 -1
- package/utils/text.js +3 -0
- package/src/ui/slots.ts +0 -36
- package/ui/slots.d.ts +0 -208
- package/ui/slots.d.ts.map +0 -1
- 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,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,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,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
|
+
}
|