@cmodjs/core 1.6.0 → 2.0.1
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 +7 -7
- package/docs/dependencies.md +1 -1
- package/docs/index.md +12 -11
- package/docs/jobs.md +12 -16
- package/docs/mod.md +111 -36
- package/docs/permissions.md +14 -18
- package/docs/session.md +171 -82
- package/docs/state.md +5 -5
- package/docs/testing.md +57 -41
- package/docs/ui.md +9 -9
- package/jobs/permissions/decide-permission.d.ts.map +1 -1
- package/jobs/permissions/decide-permission.js +1 -1
- package/jobs/prompt.d.ts +1 -3
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +2 -21
- package/jobs/slash-command.d.ts +1 -1
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +1 -1
- package/jobs/status-line.d.ts +0 -1
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +3 -4
- package/mod.d.ts +53 -105
- package/mod.d.ts.map +1 -1
- package/mod.js +18 -80
- package/package.json +1 -1
- package/records.d.ts +1 -1
- package/records.js +1 -1
- package/register.d.ts.map +1 -1
- package/register.js +8 -5
- package/runtime/before.d.ts +11 -4
- package/runtime/before.d.ts.map +1 -1
- package/runtime/before.js +131 -67
- package/runtime/claude.d.ts +6 -1
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts.map +1 -1
- package/runtime/conversations.js +82 -38
- package/runtime/deadline.d.ts +17 -0
- package/runtime/deadline.d.ts.map +1 -1
- package/runtime/deadline.js +34 -0
- package/runtime/dependencies.d.ts +2 -0
- package/runtime/dependencies.d.ts.map +1 -1
- package/runtime/dependencies.js +12 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +13 -8
- package/runtime/hooks.d.ts +15 -11
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +8 -165
- package/runtime/lifecycle.d.ts +4 -2
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +96 -62
- package/runtime/permission-check.d.ts +15 -0
- package/runtime/permission-check.d.ts.map +1 -0
- package/runtime/permission-check.js +37 -0
- package/runtime/sessions.d.ts +35 -5
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +304 -82
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +14 -11
- package/runtime/tool-calls.d.ts +0 -1
- package/runtime/tool-calls.d.ts.map +1 -1
- package/runtime/tool-calls.js +0 -19
- package/session/context/turn/message/instruction-file.d.ts +9 -0
- package/session/context/turn/message/instruction-file.d.ts.map +1 -0
- package/session/context/turn/message/instruction-file.js +4 -0
- package/session/context/turn/message/message.d.ts +6 -4
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +12 -8
- package/session/context/turn/message/sender.d.ts +2 -1
- package/session/context/turn/message/sender.d.ts.map +1 -1
- package/session/context/turn/message/sender.js +4 -1
- package/session/context/turn/message/tool-call.d.ts +5 -0
- package/session/context/turn/message/tool-call.d.ts.map +1 -1
- package/session/context/turn/message/tool-result.d.ts +6 -1
- package/session/context/turn/message/tool-result.d.ts.map +1 -1
- package/session/context/turn/turn.d.ts +7 -0
- package/session/context/turn/turn.d.ts.map +1 -1
- package/session/conversation.d.ts +10 -3
- package/session/conversation.d.ts.map +1 -1
- package/session/conversation.js +14 -5
- package/session/goal/goal.d.ts +1 -1
- package/session/goal/goal.d.ts.map +1 -1
- package/session/goal/task.d.ts +2 -2
- package/session/goal/task.d.ts.map +1 -1
- package/session/session.d.ts +361 -26
- package/session/session.d.ts.map +1 -1
- package/session/session.js +40 -0
- package/src/jobs/permissions/decide-permission.ts +2 -1
- package/src/jobs/prompt.ts +3 -20
- package/src/jobs/slash-command.ts +2 -2
- package/src/jobs/status-line.ts +4 -7
- package/src/mod.ts +52 -147
- package/src/records.ts +1 -1
- package/src/register.ts +8 -5
- package/src/runtime/before.ts +140 -66
- package/src/runtime/claude.ts +5 -1
- package/src/runtime/conversations.ts +75 -37
- package/src/runtime/deadline.ts +38 -0
- package/src/runtime/dependencies.ts +12 -0
- package/src/runtime/grants.ts +11 -7
- package/src/runtime/hooks.ts +24 -163
- package/src/runtime/lifecycle.ts +110 -77
- package/src/runtime/permission-check.ts +47 -0
- package/src/runtime/sessions.ts +334 -91
- package/src/runtime/state.ts +15 -8
- package/src/runtime/tool-calls.ts +0 -19
- package/src/session/context/turn/message/instruction-file.ts +13 -0
- package/src/session/context/turn/message/message.ts +17 -11
- package/src/session/context/turn/message/sender.ts +5 -2
- package/src/session/context/turn/message/tool-call.ts +3 -0
- package/src/session/context/turn/message/tool-result.ts +6 -1
- package/src/session/context/turn/turn.ts +4 -0
- package/src/session/conversation.ts +21 -8
- package/src/session/goal/goal.ts +1 -1
- package/src/session/goal/task.ts +2 -2
- package/src/session/session.ts +227 -24
- package/src/testing/claude-code.ts +23 -0
- package/src/testing/fake-claude.ts +5 -1
- package/src/testing/fake-sessions.ts +38 -24
- package/src/testing.ts +88 -19
- package/testing/claude-code.d.ts +12 -0
- package/testing/claude-code.d.ts.map +1 -0
- package/testing/claude-code.js +10 -0
- package/testing/fake-claude.d.ts +4 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +4 -1
- package/testing/fake-sessions.d.ts +8 -6
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +35 -20
- package/testing.d.ts +38 -9
- package/testing.d.ts.map +1 -1
- package/testing.js +58 -8
- package/docs/hooks.md +0 -259
- package/session/context/turn/message/document.d.ts +0 -7
- package/session/context/turn/message/document.d.ts.map +0 -1
- package/session/context/turn/message/document.js +0 -1
- package/src/session/context/turn/message/document.ts +0 -8
package/src/runtime/state.ts
CHANGED
|
@@ -84,14 +84,19 @@ export function createState<State extends object>({ name, initial, session, root
|
|
|
84
84
|
|
|
85
85
|
const forgetEndedSessions = async (current: string) => {
|
|
86
86
|
const stored = await claude.store.keys()
|
|
87
|
-
|
|
88
|
-
const owner = recorded.slice(transcriptsKey.length)
|
|
89
|
-
if (owner === current) continue
|
|
87
|
+
const ended = async (recorded: string) => {
|
|
90
88
|
const transcript = await claude.store.get(recorded)
|
|
91
|
-
|
|
92
|
-
for (const value of stored.filter((key) => key.startsWith(`${name}.`) && key.endsWith(`.sessions.${owner}`))) await claude.store.delete(value)
|
|
93
|
-
await claude.store.delete(recorded)
|
|
89
|
+
return typeof transcript !== 'string' || !(await claude.fs.exists(transcript))
|
|
94
90
|
}
|
|
91
|
+
const owners = stored.filter((key) => key.startsWith(transcriptsKey) && key !== `${transcriptsKey}${current}`)
|
|
92
|
+
await Promise.all(
|
|
93
|
+
owners.map(async (recorded) => {
|
|
94
|
+
if (!(await ended(recorded))) return
|
|
95
|
+
const owner = recorded.slice(transcriptsKey.length)
|
|
96
|
+
await Promise.all(stored.filter((key) => key.startsWith(`${name}.`) && key.endsWith(`.sessions.${owner}`)).map((value) => claude.store.delete(value)))
|
|
97
|
+
await claude.store.delete(recorded)
|
|
98
|
+
}),
|
|
99
|
+
)
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
const loadGroups = async (only: readonly Lifetime[], next: { readonly root: string; readonly session: string }) => {
|
|
@@ -151,8 +156,10 @@ export function createState<State extends object>({ name, initial, session, root
|
|
|
151
156
|
moveTo: (nextRoot) => inTurn(async () => (nextRoot === projectRoot ? undefined : loadGroups(['project'], { root: nextRoot, session: sessionId }))),
|
|
152
157
|
switchSession: (nextSession, source, transcript) =>
|
|
153
158
|
inTurn(async () => {
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
if (Object.keys(declared.session).length > 0) {
|
|
160
|
+
await claude.store.set(`${transcriptsKey}${nextSession}`, transcript)
|
|
161
|
+
void forgetEndedSessions(nextSession).catch((error: unknown) => report({ id: 'state:sessions', message: 'Could not forget the session values of ended sessions', error }))
|
|
162
|
+
}
|
|
156
163
|
if (nextSession === sessionId) return
|
|
157
164
|
if (source === 'fork') await copySession(sessionId, nextSession)
|
|
158
165
|
return loadGroups(['memory', 'session'], { root: projectRoot, session: nextSession })
|
|
@@ -5,7 +5,6 @@ export type Agent = { agentId?: string; agentType?: string }
|
|
|
5
5
|
|
|
6
6
|
export type ToolCalls = {
|
|
7
7
|
agentOf(toolUseId: string | undefined): Promise<Agent>
|
|
8
|
-
cwdOf(toolUseId: string): string | undefined
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
export const reservedKeys: readonly string[] = ['tool', 'tool_use_id', 'consent', 'agentId']
|
|
@@ -16,12 +15,9 @@ export function toolInputOf(envelope: object): Record<string, unknown> {
|
|
|
16
15
|
|
|
17
16
|
type AgentFields = { readonly agent_id?: string; readonly agent_type?: string }
|
|
18
17
|
|
|
19
|
-
const keptCalls = 100
|
|
20
|
-
|
|
21
18
|
export function toolCalls(claude: Claude, router: Router): ToolCalls {
|
|
22
19
|
let mainAgentType: string | undefined
|
|
23
20
|
const agentIds = new Map<string, string>()
|
|
24
|
-
const cwds = new Map<string, string>()
|
|
25
21
|
const noteAgent = (e: AgentFields) => {
|
|
26
22
|
if (e.agent_id === undefined) mainAgentType = e.agent_type
|
|
27
23
|
}
|
|
@@ -34,11 +30,6 @@ export function toolCalls(claude: Claude, router: Router): ToolCalls {
|
|
|
34
30
|
return next(e)
|
|
35
31
|
})
|
|
36
32
|
router.add('tool.call', async (e, next) => {
|
|
37
|
-
cwds.set(e.tool_use_id, await claude.session.cwd())
|
|
38
|
-
for (const oldest of cwds.keys()) {
|
|
39
|
-
if (cwds.size <= keptCalls) break
|
|
40
|
-
cwds.delete(oldest)
|
|
41
|
-
}
|
|
42
33
|
if (e.agentId === undefined) return next(e)
|
|
43
34
|
agentIds.set(e.tool_use_id, e.agentId)
|
|
44
35
|
try {
|
|
@@ -47,21 +38,11 @@ export function toolCalls(claude: Claude, router: Router): ToolCalls {
|
|
|
47
38
|
agentIds.delete(e.tool_use_id)
|
|
48
39
|
}
|
|
49
40
|
})
|
|
50
|
-
for (const event of ['classic.PostToolUse', 'classic.PostToolUseFailure'] as const) {
|
|
51
|
-
router.add(event, async (e, next) => {
|
|
52
|
-
try {
|
|
53
|
-
return await next(e)
|
|
54
|
-
} finally {
|
|
55
|
-
cwds.delete(e.tool_use_id)
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
41
|
return {
|
|
60
42
|
async agentOf(toolUseId) {
|
|
61
43
|
const agentId = toolUseId === undefined ? undefined : agentIds.get(toolUseId)
|
|
62
44
|
const agentType = agentId === undefined ? mainAgentType : (await claude.agent.list()).find((agent) => agent.id === agentId)?.type
|
|
63
45
|
return { ...(agentId === undefined ? {} : { agentId }), ...(agentType === undefined ? {} : { agentType }) }
|
|
64
46
|
},
|
|
65
|
-
cwdOf: (toolUseId) => cwds.get(toolUseId),
|
|
66
47
|
}
|
|
67
48
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MessageOf } from './message.js'
|
|
2
|
+
|
|
3
|
+
export const InstructionFile = 'instructionFile'
|
|
4
|
+
|
|
5
|
+
export type InstructionFile = MessageOf<typeof InstructionFile> & {
|
|
6
|
+
readonly path: string
|
|
7
|
+
readonly text: string
|
|
8
|
+
readonly isRule: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isRulePath(path: string): boolean {
|
|
12
|
+
return /(^|\/)\.claude[^/]*\/rules\//.test(path)
|
|
13
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { isObject } from '../../../../records.js'
|
|
2
2
|
import { Task } from '../../../goal/task.js'
|
|
3
|
-
import {
|
|
3
|
+
import { InstructionFile, isRulePath } from './instruction-file.js'
|
|
4
4
|
import { 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
8
|
import { senderOf } from './sender.js'
|
|
9
9
|
import { Skill } from './skill.js'
|
|
10
|
-
import { ToolCall } from './tool-call.js'
|
|
10
|
+
import { ToolCall, type CallFiles } from './tool-call.js'
|
|
11
11
|
import { ToolResult } from './tool-result.js'
|
|
12
12
|
|
|
13
13
|
export const Message = 'message'
|
|
14
14
|
|
|
15
|
-
export type MessageType = typeof Prompt | typeof Note | typeof Reply | typeof ToolCall | typeof ToolResult | typeof Skill | typeof
|
|
15
|
+
export type MessageType = typeof Prompt | typeof Note | typeof Reply | typeof ToolCall | typeof ToolResult | typeof Skill | typeof InstructionFile | typeof Notice
|
|
16
16
|
|
|
17
17
|
export type MessageOf<Type extends MessageType> = {
|
|
18
18
|
readonly id: string
|
|
@@ -24,7 +24,7 @@ export type MessageOf<Type extends MessageType> = {
|
|
|
24
24
|
readonly task?: string
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export type Message = Prompt | Note | Reply | ToolCall | ToolResult | Skill |
|
|
27
|
+
export type Message = Prompt | Note | Reply | ToolCall | ToolResult | Skill | InstructionFile | Notice
|
|
28
28
|
|
|
29
29
|
export type Placed = 'session' | 'context' | 'turn' | 'task'
|
|
30
30
|
|
|
@@ -44,13 +44,17 @@ export type Row = {
|
|
|
44
44
|
readonly model?: string
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export const messageTypes: readonly MessageType[] = [Prompt, Note, Reply, ToolCall, ToolResult, Skill,
|
|
47
|
+
export const messageTypes: readonly MessageType[] = [Prompt, Note, Reply, ToolCall, ToolResult, Skill, InstructionFile, Notice]
|
|
48
48
|
|
|
49
49
|
export const skillPrefix = 'Base directory for this skill:'
|
|
50
50
|
|
|
51
51
|
const rowTypes: readonly string[] = ['user', 'assistant', 'attachment', 'system']
|
|
52
52
|
|
|
53
|
-
const memoryPath = /^Contents of (.+?) \(
|
|
53
|
+
const memoryPath = /^Contents of (.+?)(?: \([^)\n]*\))?:\r?$/m
|
|
54
|
+
|
|
55
|
+
export function instructionPathOf(text: string): string {
|
|
56
|
+
return memoryPath.exec(text)?.[1] ?? ''
|
|
57
|
+
}
|
|
54
58
|
|
|
55
59
|
export function rowOfLine(row: Readonly<Record<string, unknown>>): Row | undefined {
|
|
56
60
|
const { uuid, timestamp, type } = row
|
|
@@ -77,7 +81,7 @@ export function rowOfLine(row: Readonly<Record<string, unknown>>): Row | undefin
|
|
|
77
81
|
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
82
|
const { message, origin } = append
|
|
79
83
|
const text = textOf(message.content)
|
|
80
|
-
const path = message.type === 'attachment' ?
|
|
84
|
+
const path = message.type === 'attachment' ? instructionPathOf(text) || undefined : undefined
|
|
81
85
|
const model = isObject(origin) && origin['kind'] === 'model' && typeof origin['model'] === 'string' ? origin['model'] : undefined
|
|
82
86
|
return {
|
|
83
87
|
id: append.uuid,
|
|
@@ -94,6 +98,8 @@ export function rowOfAppend(append: { readonly uuid: string; readonly door: stri
|
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
|
|
101
|
+
export const noFiles: CallFiles = { read: [], changed: [] }
|
|
102
|
+
|
|
97
103
|
export function messagesOf(row: Row, isInTask: boolean): Unplaced[] {
|
|
98
104
|
const found = unplacedOf(row, isInTask)
|
|
99
105
|
return found.length === 1 ? found : found.map((message, index) => ({ ...message, id: `${row.id}:${index}` }))
|
|
@@ -103,17 +109,17 @@ function unplacedOf(row: Row, isInTask: boolean): Unplaced[] {
|
|
|
103
109
|
const base = { id: row.id, at: row.at }
|
|
104
110
|
const text = textOf(row.content).trim()
|
|
105
111
|
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:
|
|
112
|
+
if (row.type === 'attachment') return row.name === 'nested_memory' ? [{ ...base, type: InstructionFile, path: row.path ?? '', text, isRule: isRulePath(row.path ?? '') }] : []
|
|
107
113
|
if (row.type === 'assistant') {
|
|
108
114
|
return row.content.filter(isObject).flatMap((block): Unplaced[] => {
|
|
109
115
|
if (block['type'] === 'text' && typeof block['text'] === 'string' && block['text'].trim() !== '') return [{ ...base, type: Reply, text: block['text'].trim() }]
|
|
110
116
|
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 ?? '' }]
|
|
117
|
+
return [{ ...base, type: ToolCall, callId: block['id'], name: block['name'], input: isObject(block['input']) ? block['input'] : {}, cwd: row.cwd ?? '', files: noFiles }]
|
|
112
118
|
})
|
|
113
119
|
}
|
|
114
120
|
if (row.isSummary) return []
|
|
115
121
|
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,
|
|
122
|
+
if (results.length > 0) return results.map((block) => ({ ...base, type: ToolResult, callId: block['tool_use_id'] as string, name: '', input: {}, cwd: row.cwd ?? '', files: noFiles, output: textOf(block['content']), isError: block['is_error'] === true }))
|
|
117
123
|
if (text === '') return []
|
|
118
124
|
if (text.startsWith(skillPrefix)) return [{ ...base, type: Skill, name: basename(text.slice(skillPrefix.length).split('\n')[0]?.trim() ?? ''), text }]
|
|
119
125
|
if (text.startsWith('<task-notification')) return [{ ...base, type: Note, text: tagOf(text, 'summary') ?? text, from: { type: Task } }]
|
|
@@ -175,7 +181,7 @@ const toolValueChars = 1024
|
|
|
175
181
|
export function searchText(message: Unplaced): string {
|
|
176
182
|
if (message.type === ToolCall) return [message.name, ...stringsIn(message.input).map((value) => value.slice(0, toolValueChars))].join('\n')
|
|
177
183
|
if (message.type === ToolResult) return ''
|
|
178
|
-
if (message.type ===
|
|
184
|
+
if (message.type === InstructionFile) return `${message.path}\n${message.text}`
|
|
179
185
|
return message.text
|
|
180
186
|
}
|
|
181
187
|
|
|
@@ -11,7 +11,9 @@ export const Channel = 'channel'
|
|
|
11
11
|
|
|
12
12
|
export const Sdk = 'sdk'
|
|
13
13
|
|
|
14
|
-
export
|
|
14
|
+
export const Engine = 'engine'
|
|
15
|
+
|
|
16
|
+
export type SenderType = typeof Person | typeof Mod | 'session' | typeof Task | typeof Schedule | typeof Channel | typeof Sdk | typeof Engine
|
|
15
17
|
|
|
16
18
|
export type Sender = {
|
|
17
19
|
readonly type: SenderType
|
|
@@ -19,7 +21,7 @@ export type Sender = {
|
|
|
19
21
|
readonly as?: typeof Person
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
const personOrigins: readonly string[] = ['human', 'composer', 'bridge', 'slack-ping'
|
|
24
|
+
const personOrigins: readonly string[] = ['human', 'composer', 'bridge', 'slack-ping']
|
|
23
25
|
|
|
24
26
|
const sessionOrigins: readonly string[] = ['peer', 'peer-send-message', 'coordinator', 'projects-relay', 'observer', 'observer-activity']
|
|
25
27
|
|
|
@@ -29,6 +31,7 @@ export function senderOf(origin: unknown, isInTask: boolean): Sender {
|
|
|
29
31
|
if (kind === undefined) return { type: isInTask ? Task : Person }
|
|
30
32
|
if (personOrigins.includes(kind)) return { type: Person }
|
|
31
33
|
if (sessionOrigins.includes(kind)) return { type: 'session' }
|
|
34
|
+
if (kind === 'auto-continuation') return { type: Engine }
|
|
32
35
|
if (kind === 'plugin' && typeof fields['name'] === 'string') return { type: Mod, name: fields['name'], ...(fields['asUser'] === true ? { as: Person } : {}) }
|
|
33
36
|
if (kind === 'scheduled-trigger') return { type: Schedule }
|
|
34
37
|
if (kind === 'channel' && typeof fields['server'] === 'string') return { type: Channel, name: fields['server'] }
|
|
@@ -2,9 +2,12 @@ import type { MessageOf } from './message.js'
|
|
|
2
2
|
|
|
3
3
|
export const ToolCall = 'toolCall'
|
|
4
4
|
|
|
5
|
+
export type CallFiles = { readonly read: readonly string[]; readonly changed: readonly string[] }
|
|
6
|
+
|
|
5
7
|
export type ToolCall = MessageOf<typeof ToolCall> & {
|
|
6
8
|
readonly callId: string
|
|
7
9
|
readonly name: string
|
|
8
10
|
readonly input: Readonly<Record<string, unknown>>
|
|
9
11
|
readonly cwd: string
|
|
12
|
+
readonly files: CallFiles
|
|
10
13
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { MessageOf } from './message.js'
|
|
2
|
+
import type { CallFiles } from './tool-call.js'
|
|
2
3
|
|
|
3
4
|
export const ToolResult = 'toolResult'
|
|
4
5
|
|
|
5
6
|
export type ToolResult = MessageOf<typeof ToolResult> & {
|
|
6
7
|
readonly callId: string
|
|
7
|
-
readonly
|
|
8
|
+
readonly name: string
|
|
9
|
+
readonly input: Readonly<Record<string, unknown>>
|
|
10
|
+
readonly cwd: string
|
|
11
|
+
readonly files: CallFiles
|
|
12
|
+
readonly output: unknown
|
|
8
13
|
readonly isError: boolean
|
|
9
14
|
}
|
|
@@ -11,5 +11,9 @@ export type Turn = RecordPart & {
|
|
|
11
11
|
readonly context: number
|
|
12
12
|
readonly openedAt: string
|
|
13
13
|
readonly closedAt?: string
|
|
14
|
+
readonly answer?: string
|
|
15
|
+
readonly reason?: 'answer' | 'error'
|
|
16
|
+
readonly failure?: { readonly type: string; readonly text: string }
|
|
14
17
|
messages(): Promise<Message[]>
|
|
18
|
+
abort(): Promise<void>
|
|
15
19
|
}
|
|
@@ -5,7 +5,7 @@ import { Prompt } from './context/turn/message/prompt.js'
|
|
|
5
5
|
import { ToolCall } from './context/turn/message/tool-call.js'
|
|
6
6
|
import { ToolResult } from './context/turn/message/tool-result.js'
|
|
7
7
|
import { TurnEnd, TurnStart, type Turn } from './context/turn/turn.js'
|
|
8
|
-
import { GoalChange
|
|
8
|
+
import { GoalChange } from './goal/goal.js'
|
|
9
9
|
import { Task, TaskEnd, TaskStart } from './goal/task.js'
|
|
10
10
|
import type { UsageChange, Usage } from './session.js'
|
|
11
11
|
|
|
@@ -24,13 +24,15 @@ export type SessionData = {
|
|
|
24
24
|
|
|
25
25
|
export type ContextData = Pick<Context, 'number' | 'openedAt' | 'closedAt' | 'task'>
|
|
26
26
|
|
|
27
|
-
export type TurnData = Pick<Turn, 'prompt' | 'context' | 'openedAt' | 'closedAt'>
|
|
27
|
+
export type TurnData = Pick<Turn, 'prompt' | 'context' | 'openedAt' | 'closedAt' | 'answer' | 'reason' | 'failure'>
|
|
28
|
+
|
|
29
|
+
export type TurnEnding = Pick<Turn, 'answer' | 'reason' | 'failure'>
|
|
28
30
|
|
|
29
31
|
export type TaskData = Pick<Task, 'id' | 'title' | 'openedBy' | 'openedAt' | 'closedAt'> & { readonly agentType: string; readonly model?: string; readonly parent?: string }
|
|
30
32
|
|
|
31
33
|
export type RunningTask = TaskData & { readonly context: number; readonly contextOpenedAt?: string }
|
|
32
34
|
|
|
33
|
-
export type GoalData =
|
|
35
|
+
export type GoalData = { readonly text: string; readonly task?: string }
|
|
34
36
|
|
|
35
37
|
export type SessionChange =
|
|
36
38
|
| { readonly type: typeof Message; readonly message: Message }
|
|
@@ -50,12 +52,16 @@ export type ConversationState = {
|
|
|
50
52
|
readonly endedCalls: Readonly<Record<string, string>>
|
|
51
53
|
readonly tasks: Readonly<Record<string, RunningTask>>
|
|
52
54
|
readonly taskTurns: Readonly<Record<string, TurnData>>
|
|
55
|
+
readonly openCalls?: Readonly<Record<string, OpenCall>>
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
type OpenCall = Pick<ToolCall, 'name' | 'input' | 'cwd'>
|
|
59
|
+
|
|
55
60
|
export type Conversation = {
|
|
56
61
|
add(row: Row, task?: string): SessionChange[]
|
|
57
62
|
startTask(task: TaskData): SessionChange[]
|
|
58
63
|
startContext(at: string): SessionChange[]
|
|
64
|
+
endTurn(at: string, ending: TurnEnding, task?: string): SessionChange[]
|
|
59
65
|
changeGoal(text: string): SessionChange[]
|
|
60
66
|
readonly state: ConversationState
|
|
61
67
|
}
|
|
@@ -76,12 +82,13 @@ export function createConversation(start: ConversationState): Conversation {
|
|
|
76
82
|
const endedCalls = new Map(Object.entries(start.endedCalls))
|
|
77
83
|
const tasks = new Map(Object.entries(start.tasks))
|
|
78
84
|
const taskTurns = new Map(Object.entries(start.taskTurns))
|
|
85
|
+
const openCalls = new Map(Object.entries(start.openCalls ?? {}))
|
|
79
86
|
|
|
80
|
-
const endTaskTurn = (task: string, at: string): SessionChange[] => {
|
|
87
|
+
const endTaskTurn = (task: string, at: string, ending: TurnEnding = {}): SessionChange[] => {
|
|
81
88
|
const open = taskTurns.get(task)
|
|
82
89
|
if (open === undefined) return []
|
|
83
90
|
taskTurns.delete(task)
|
|
84
|
-
return open.closedAt === undefined ? [{ type: TurnEnd, turn: { ...open, closedAt: at } }] : []
|
|
91
|
+
return open.closedAt === undefined ? [{ type: TurnEnd, turn: { ...open, ...ending, closedAt: at } }] : []
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
const startContext = (at: string): SessionChange[] => {
|
|
@@ -103,9 +110,9 @@ export function createConversation(start: ConversationState): Conversation {
|
|
|
103
110
|
]
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
const endTurn = (at: string): SessionChange[] => {
|
|
113
|
+
const endTurn = (at: string, ending: TurnEnding = {}): SessionChange[] => {
|
|
107
114
|
if (turn === undefined) return []
|
|
108
|
-
const ended = { ...turn, closedAt: at }
|
|
115
|
+
const ended = { ...turn, ...ending, closedAt: at }
|
|
109
116
|
turn = undefined
|
|
110
117
|
return [{ type: TurnEnd, turn: ended }]
|
|
111
118
|
}
|
|
@@ -148,8 +155,12 @@ export function createConversation(start: ConversationState): Conversation {
|
|
|
148
155
|
if (task === undefined && goal === undefined) changes.push(...changeGoal(unplaced.text))
|
|
149
156
|
}
|
|
150
157
|
const open = task === undefined ? turn : taskTurns.get(task)
|
|
158
|
+
const call = unplaced.type === ToolResult ? openCalls.get(unplaced.callId) : undefined
|
|
159
|
+
if (unplaced.type === ToolCall) openCalls.set(unplaced.callId, { name: unplaced.name, input: unplaced.input, cwd: unplaced.cwd })
|
|
160
|
+
if (unplaced.type === ToolResult) openCalls.delete(unplaced.callId)
|
|
151
161
|
const placed = {
|
|
152
162
|
...unplaced,
|
|
163
|
+
...call,
|
|
153
164
|
session: start.session,
|
|
154
165
|
context: placedIn,
|
|
155
166
|
...(open === undefined ? {} : { turn: open.prompt.id }),
|
|
@@ -157,7 +168,7 @@ export function createConversation(start: ConversationState): Conversation {
|
|
|
157
168
|
} as Message
|
|
158
169
|
changes.push({ type: Message, message: placed })
|
|
159
170
|
if (placed.type === ToolCall && agentTools.includes(placed.name)) agentCalls.add(placed.callId)
|
|
160
|
-
if (placed.type === ToolResult && agentCalls.has(placed.callId) && !placed.
|
|
171
|
+
if (placed.type === ToolResult && agentCalls.has(placed.callId) && !String(placed.output).startsWith(asyncLaunch)) changes.push(...endTask(placed.callId, row.at))
|
|
161
172
|
if (placed.type === Note && placed.from.type === Task) {
|
|
162
173
|
const call = tagOf(textOf(row.content), 'tool-use-id')
|
|
163
174
|
if (call !== undefined && agentCalls.has(call)) changes.push(...endTask(call, row.at, tagOf(textOf(row.content), 'task-id')))
|
|
@@ -176,6 +187,7 @@ export function createConversation(start: ConversationState): Conversation {
|
|
|
176
187
|
return changes
|
|
177
188
|
},
|
|
178
189
|
startContext,
|
|
190
|
+
endTurn: (at, ending, task) => (task === undefined ? endTurn(at, ending) : endTaskTurn(task, at, ending)),
|
|
179
191
|
changeGoal,
|
|
180
192
|
get state() {
|
|
181
193
|
return {
|
|
@@ -188,6 +200,7 @@ export function createConversation(start: ConversationState): Conversation {
|
|
|
188
200
|
endedCalls: Object.fromEntries(endedCalls),
|
|
189
201
|
tasks: Object.fromEntries(tasks),
|
|
190
202
|
taskTurns: Object.fromEntries(taskTurns),
|
|
203
|
+
openCalls: Object.fromEntries(openCalls),
|
|
191
204
|
}
|
|
192
205
|
},
|
|
193
206
|
}
|
package/src/session/goal/goal.ts
CHANGED
package/src/session/goal/task.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Message } from '../context/turn/message/message.js'
|
|
2
|
-
import type { RecordPart,
|
|
2
|
+
import type { RecordPart, TaskOwner } from '../session.js'
|
|
3
3
|
|
|
4
4
|
export const Task = 'task'
|
|
5
5
|
|
|
@@ -10,7 +10,7 @@ export const TaskEnd = 'taskEnd'
|
|
|
10
10
|
export type Task = RecordPart & {
|
|
11
11
|
readonly id: string
|
|
12
12
|
readonly title: string
|
|
13
|
-
readonly owner:
|
|
13
|
+
readonly owner: TaskOwner
|
|
14
14
|
readonly openedBy: string
|
|
15
15
|
readonly openedAt: string
|
|
16
16
|
readonly closedAt?: string
|