@cmodjs/core 1.6.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/commands.md +4 -4
- 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 +3 -1
- package/runtime/before.d.ts +5 -1
- package/runtime/before.d.ts.map +1 -1
- package/runtime/before.js +81 -39
- 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 +45 -26
- 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 +28 -11
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +20 -123
- package/runtime/lifecycle.d.ts +2 -1
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +66 -49
- package/runtime/sessions.d.ts +31 -5
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +342 -81
- 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 +5 -4
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +7 -6
- 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 +3 -1
- package/src/runtime/before.ts +90 -37
- package/src/runtime/claude.ts +5 -1
- package/src/runtime/conversations.ts +41 -25
- 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 +48 -116
- package/src/runtime/lifecycle.ts +65 -50
- package/src/runtime/sessions.ts +366 -90
- 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 +11 -9
- 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 +84 -16
- 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 +54 -5
- 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
|
@@ -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,7 +44,7 @@ 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
|
|
|
@@ -94,6 +94,8 @@ export function rowOfAppend(append: { readonly uuid: string; readonly door: stri
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
export const noFiles: CallFiles = { read: [], changed: [] }
|
|
98
|
+
|
|
97
99
|
export function messagesOf(row: Row, isInTask: boolean): Unplaced[] {
|
|
98
100
|
const found = unplacedOf(row, isInTask)
|
|
99
101
|
return found.length === 1 ? found : found.map((message, index) => ({ ...message, id: `${row.id}:${index}` }))
|
|
@@ -103,17 +105,17 @@ function unplacedOf(row: Row, isInTask: boolean): Unplaced[] {
|
|
|
103
105
|
const base = { id: row.id, at: row.at }
|
|
104
106
|
const text = textOf(row.content).trim()
|
|
105
107
|
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:
|
|
108
|
+
if (row.type === 'attachment') return row.name === 'nested_memory' ? [{ ...base, type: InstructionFile, path: row.path ?? '', text, isRule: isRulePath(row.path ?? '') }] : []
|
|
107
109
|
if (row.type === 'assistant') {
|
|
108
110
|
return row.content.filter(isObject).flatMap((block): Unplaced[] => {
|
|
109
111
|
if (block['type'] === 'text' && typeof block['text'] === 'string' && block['text'].trim() !== '') return [{ ...base, type: Reply, text: block['text'].trim() }]
|
|
110
112
|
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 ?? '' }]
|
|
113
|
+
return [{ ...base, type: ToolCall, callId: block['id'], name: block['name'], input: isObject(block['input']) ? block['input'] : {}, cwd: row.cwd ?? '', files: noFiles }]
|
|
112
114
|
})
|
|
113
115
|
}
|
|
114
116
|
if (row.isSummary) return []
|
|
115
117
|
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,
|
|
118
|
+
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
119
|
if (text === '') return []
|
|
118
120
|
if (text.startsWith(skillPrefix)) return [{ ...base, type: Skill, name: basename(text.slice(skillPrefix.length).split('\n')[0]?.trim() ?? ''), text }]
|
|
119
121
|
if (text.startsWith('<task-notification')) return [{ ...base, type: Note, text: tagOf(text, 'summary') ?? text, from: { type: Task } }]
|
|
@@ -175,7 +177,7 @@ const toolValueChars = 1024
|
|
|
175
177
|
export function searchText(message: Unplaced): string {
|
|
176
178
|
if (message.type === ToolCall) return [message.name, ...stringsIn(message.input).map((value) => value.slice(0, toolValueChars))].join('\n')
|
|
177
179
|
if (message.type === ToolResult) return ''
|
|
178
|
-
if (message.type ===
|
|
180
|
+
if (message.type === InstructionFile) return `${message.path}\n${message.text}`
|
|
179
181
|
return message.text
|
|
180
182
|
}
|
|
181
183
|
|
|
@@ -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
|