saeeol 1.2.0 → 1.2.2

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 (193) hide show
  1. package/package.json +14 -14
  2. package/src/cli/cmd/tui/component/dialog/dialog-agent.tsx +32 -0
  3. package/src/cli/cmd/tui/component/dialog/dialog-command.tsx +190 -0
  4. package/src/cli/cmd/tui/component/dialog/dialog-console-org.tsx +103 -0
  5. package/src/cli/cmd/tui/component/dialog/dialog-go-upsell.tsx +159 -0
  6. package/src/cli/cmd/tui/component/dialog/dialog-mcp.tsx +86 -0
  7. package/src/cli/cmd/tui/component/dialog/dialog-model.tsx +238 -0
  8. package/src/cli/cmd/tui/component/dialog/dialog-provider.tsx +343 -0
  9. package/src/cli/cmd/tui/component/dialog/dialog-session-delete-failed.tsx +103 -0
  10. package/src/cli/cmd/tui/component/dialog/dialog-session-list.tsx +301 -0
  11. package/src/cli/cmd/tui/component/dialog/dialog-session-rename.tsx +35 -0
  12. package/src/cli/cmd/tui/component/dialog/dialog-skill.tsx +37 -0
  13. package/src/cli/cmd/tui/component/dialog/dialog-stash.tsx +87 -0
  14. package/src/cli/cmd/tui/component/dialog/dialog-status.tsx +190 -0
  15. package/src/cli/cmd/tui/component/dialog/dialog-tag.tsx +44 -0
  16. package/src/cli/cmd/tui/component/dialog/dialog-theme-list.tsx +50 -0
  17. package/src/cli/cmd/tui/component/dialog/dialog-variant.tsx +39 -0
  18. package/src/cli/cmd/tui/component/dialog/dialog-workspace-create.tsx +200 -0
  19. package/src/cli/cmd/tui/component/dialog/dialog-workspace-unavailable.tsx +81 -0
  20. package/src/cli/cmd/tui/component/dialog-agent.tsx +1 -32
  21. package/src/cli/cmd/tui/component/dialog-command.tsx +1 -190
  22. package/src/cli/cmd/tui/component/dialog-console-org.tsx +1 -103
  23. package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +1 -159
  24. package/src/cli/cmd/tui/component/dialog-mcp.tsx +1 -86
  25. package/src/cli/cmd/tui/component/dialog-model.tsx +1 -238
  26. package/src/cli/cmd/tui/component/dialog-provider.tsx +1 -343
  27. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +1 -103
  28. package/src/cli/cmd/tui/component/dialog-session-list.tsx +1 -301
  29. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +1 -35
  30. package/src/cli/cmd/tui/component/dialog-skill.tsx +1 -37
  31. package/src/cli/cmd/tui/component/dialog-stash.tsx +1 -87
  32. package/src/cli/cmd/tui/component/dialog-status.tsx +1 -190
  33. package/src/cli/cmd/tui/component/dialog-tag.tsx +1 -44
  34. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +1 -50
  35. package/src/cli/cmd/tui/component/dialog-variant.tsx +1 -39
  36. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +1 -200
  37. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +1 -81
  38. package/src/session/compaction-helpers.ts +1 -169
  39. package/src/session/compaction.ts +1 -712
  40. package/src/session/core/compaction/compaction-helpers.ts +169 -0
  41. package/src/session/core/compaction/compaction.ts +712 -0
  42. package/src/session/core/compaction/overflow.ts +28 -0
  43. package/src/session/core/instruction.ts +234 -0
  44. package/src/session/core/llm.ts +504 -0
  45. package/src/session/core/network.ts +392 -0
  46. package/src/session/core/processor.ts +731 -0
  47. package/src/session/core/projectors.ts +139 -0
  48. package/src/session/core/resolve-tools.ts +241 -0
  49. package/src/session/core/retry.ts +149 -0
  50. package/src/session/core/revert.ts +173 -0
  51. package/src/session/core/run-state.ts +110 -0
  52. package/src/session/core/schema.ts +35 -0
  53. package/src/session/core/session-types.ts +160 -0
  54. package/src/session/core/session.sql.ts +124 -0
  55. package/src/session/core/session.ts +948 -0
  56. package/src/session/core/shell-exec.ts +205 -0
  57. package/src/session/core/status.ts +100 -0
  58. package/src/session/core/subtask.ts +268 -0
  59. package/src/session/core/summary.ts +173 -0
  60. package/src/session/core/system.ts +114 -0
  61. package/src/session/core/todo.ts +86 -0
  62. package/src/session/core/user-part.ts +293 -0
  63. package/src/session/instruction.ts +1 -234
  64. package/src/session/llm.ts +1 -504
  65. package/src/session/message/message-errors.ts +83 -0
  66. package/src/session/message/message-parts.ts +89 -0
  67. package/src/session/message/message-query.ts +107 -0
  68. package/src/session/message/message-transform.ts +156 -0
  69. package/src/session/message/message-types.ts +68 -0
  70. package/src/session/message/message-v2.ts +73 -0
  71. package/src/session/message/message.ts +192 -0
  72. package/src/session/message-errors.ts +1 -83
  73. package/src/session/message-parts.ts +1 -89
  74. package/src/session/message-query.ts +1 -107
  75. package/src/session/message-transform.ts +1 -156
  76. package/src/session/message-types.ts +1 -68
  77. package/src/session/message-v2.ts +1 -73
  78. package/src/session/message.ts +1 -192
  79. package/src/session/network.ts +1 -392
  80. package/src/session/overflow.ts +1 -28
  81. package/src/session/processor.ts +1 -731
  82. package/src/session/projectors.ts +2 -139
  83. package/src/session/prompt/prompt-command.ts +93 -0
  84. package/src/session/prompt/prompt-loop.ts +299 -0
  85. package/src/session/prompt/prompt-model.ts +44 -0
  86. package/src/session/prompt/prompt-reminders.ts +120 -0
  87. package/src/session/prompt/prompt-resolve.ts +42 -0
  88. package/src/session/prompt/prompt-schemas.ts +128 -0
  89. package/src/session/prompt/prompt-title.ts +55 -0
  90. package/src/session/prompt/prompt-types.ts +47 -0
  91. package/src/session/prompt/prompt-user-msg.ts +80 -0
  92. package/src/session/prompt/prompt.ts +211 -0
  93. package/src/session/prompt-command.ts +1 -93
  94. package/src/session/prompt-loop.ts +1 -299
  95. package/src/session/prompt-model.ts +1 -44
  96. package/src/session/prompt-reminders.ts +1 -120
  97. package/src/session/prompt-resolve.ts +1 -42
  98. package/src/session/prompt-schemas.ts +1 -128
  99. package/src/session/prompt-title.ts +1 -55
  100. package/src/session/prompt-types.ts +1 -47
  101. package/src/session/prompt-user-msg.ts +1 -80
  102. package/src/session/prompt.ts +1 -211
  103. package/src/session/resolve-tools.ts +1 -241
  104. package/src/session/retry.ts +1 -149
  105. package/src/session/revert.ts +1 -173
  106. package/src/session/run-state.ts +1 -110
  107. package/src/session/schema.ts +1 -35
  108. package/src/session/session-types.ts +1 -160
  109. package/src/session/session.sql.ts +1 -124
  110. package/src/session/session.ts +1 -948
  111. package/src/session/shell-exec.ts +1 -205
  112. package/src/session/status.ts +1 -100
  113. package/src/session/subtask.ts +1 -268
  114. package/src/session/summary.ts +1 -173
  115. package/src/session/system.ts +1 -114
  116. package/src/session/todo.ts +1 -86
  117. package/src/session/user-part.ts +1 -293
  118. package/src/tool/apply_patch.ts +1 -334
  119. package/src/tool/bash.ts +1 -656
  120. package/src/tool/core/external-directory.ts +55 -0
  121. package/src/tool/core/invalid.ts +21 -0
  122. package/src/tool/core/recall.ts +164 -0
  123. package/src/tool/core/recall.txt +12 -0
  124. package/src/tool/core/schema.ts +16 -0
  125. package/src/tool/core/tool.ts +162 -0
  126. package/src/tool/core/truncate.ts +160 -0
  127. package/src/tool/core/truncation-dir.ts +4 -0
  128. package/src/tool/diagnostics.ts +1 -20
  129. package/src/tool/edit-replacers.ts +1 -288
  130. package/src/tool/edit-utils.ts +1 -86
  131. package/src/tool/edit.ts +1 -262
  132. package/src/tool/external-directory.ts +1 -55
  133. package/src/tool/file/apply_patch.ts +334 -0
  134. package/src/tool/file/apply_patch.txt +33 -0
  135. package/src/tool/file/bash.ts +656 -0
  136. package/src/tool/file/bash.txt +119 -0
  137. package/src/tool/file/edit-replacers.ts +288 -0
  138. package/src/tool/file/edit-utils.ts +86 -0
  139. package/src/tool/file/edit.ts +262 -0
  140. package/src/tool/file/edit.txt +10 -0
  141. package/src/tool/file/read.ts +389 -0
  142. package/src/tool/file/read.txt +14 -0
  143. package/src/tool/file/write.ts +114 -0
  144. package/src/tool/file/write.txt +8 -0
  145. package/src/tool/glob.ts +1 -115
  146. package/src/tool/grep.ts +1 -151
  147. package/src/tool/integration/diagnostics.ts +20 -0
  148. package/src/tool/integration/lsp.ts +113 -0
  149. package/src/tool/integration/lsp.txt +24 -0
  150. package/src/tool/integration/mcp-exa.ts +73 -0
  151. package/src/tool/integration/package.ts +168 -0
  152. package/src/tool/integration/registry.ts +375 -0
  153. package/src/tool/invalid.ts +1 -21
  154. package/src/tool/lsp.ts +1 -113
  155. package/src/tool/mcp-exa.ts +1 -73
  156. package/src/tool/package.ts +1 -168
  157. package/src/tool/plan.ts +1 -30
  158. package/src/tool/question.ts +1 -52
  159. package/src/tool/read.ts +1 -389
  160. package/src/tool/recall.ts +1 -164
  161. package/src/tool/registry.ts +1 -375
  162. package/src/tool/schema.ts +1 -16
  163. package/src/tool/search/glob.ts +115 -0
  164. package/src/tool/search/glob.txt +6 -0
  165. package/src/tool/search/grep.ts +151 -0
  166. package/src/tool/search/grep.txt +8 -0
  167. package/src/tool/search/warpgrep.ts +107 -0
  168. package/src/tool/search/warpgrep.txt +10 -0
  169. package/src/tool/search/webfetch.ts +202 -0
  170. package/src/tool/search/webfetch.txt +13 -0
  171. package/src/tool/search/websearch.ts +71 -0
  172. package/src/tool/search/websearch.txt +14 -0
  173. package/src/tool/skill.ts +1 -91
  174. package/src/tool/task.ts +1 -197
  175. package/src/tool/todo.ts +1 -62
  176. package/src/tool/tool.ts +1 -162
  177. package/src/tool/truncate.ts +1 -160
  178. package/src/tool/truncation-dir.ts +1 -4
  179. package/src/tool/warpgrep.ts +1 -107
  180. package/src/tool/webfetch.ts +1 -202
  181. package/src/tool/websearch.ts +1 -71
  182. package/src/tool/workflow/plan-enter.txt +14 -0
  183. package/src/tool/workflow/plan-exit.txt +13 -0
  184. package/src/tool/workflow/plan.ts +30 -0
  185. package/src/tool/workflow/question.ts +52 -0
  186. package/src/tool/workflow/question.txt +11 -0
  187. package/src/tool/workflow/skill.ts +91 -0
  188. package/src/tool/workflow/skill.txt +5 -0
  189. package/src/tool/workflow/task.ts +197 -0
  190. package/src/tool/workflow/task.txt +57 -0
  191. package/src/tool/workflow/todo.ts +62 -0
  192. package/src/tool/workflow/todowrite.txt +167 -0
  193. package/src/tool/write.ts +1 -114
@@ -1,205 +1 @@
1
- import { ulid } from "ulid"
2
- import { Effect, Context, Layer, Exit, Cause, Latch } from "effect"
3
- import { MessageID, PartID } from "./schema"
4
- import { MessageV2 } from "./message-v2"
5
- import * as Session from "./session"
6
- import { Agent } from "../agent/agent"
7
- import { Provider } from "@/provider/provider"
8
- import { ProviderID, ModelID } from "../provider/schema"
9
- import { Bus } from "../bus"
10
- import { Plugin } from "../plugin"
11
- import { Config } from "@/config/config"
12
- import { Shell } from "@/shell/shell"
13
- import { SessionRevert } from "./revert"
14
- import { InstanceState } from "@/effect/instance-state"
15
- import { ChildProcess } from "effect/unstable/process"
16
- import { ChildProcessSpawner } from "effect/unstable/process"
17
- import { CrossSpawnSpawner } from "@saeeol/core/cross-spawn-spawner"
18
- import * as Stream from "effect/Stream"
19
- import { NamedError } from "@saeeol/core/util/error"
20
- import type { ShellInput } from "./prompt-schemas"
21
-
22
- export interface Interface {
23
- readonly exec: (
24
- input: ShellInput,
25
- getModel: (p: ProviderID, m: ModelID, s: any) => Effect.Effect<Provider.Model>,
26
- lastModel: (s: any) => Effect.Effect<any>,
27
- ready?: Latch.Latch,
28
- ) => Effect.Effect<MessageV2.WithParts>
29
- }
30
-
31
- export class Service extends Context.Service<Service, Interface>()("@saeeol/SessionShell") {}
32
-
33
- export const layer = Layer.effect(
34
- Service,
35
- Effect.gen(function* () {
36
- const sessions = yield* Session.Service
37
- const agents = yield* Agent.Service
38
- const bus = yield* Bus.Service
39
- const plugin = yield* Plugin.Service
40
- const config = yield* Config.Service
41
- const revert = yield* SessionRevert.Service
42
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
43
-
44
- const exec = Effect.fn("SessionShell.exec")(function* (
45
- input: ShellInput,
46
- getModel: (p: ProviderID, m: ModelID, s: any) => Effect.Effect<Provider.Model>,
47
- lastModel: (s: any) => Effect.Effect<any>,
48
- ready?: Latch.Latch,
49
- ) {
50
- return yield* Effect.uninterruptibleMask((restore) =>
51
- Effect.gen(function* () {
52
- const markReady = ready ? ready.open.pipe(Effect.asVoid) : Effect.void
53
- const { msg, part, cwd } = yield* Effect.gen(function* () {
54
- const ctx = yield* InstanceState.context
55
- const session = yield* sessions.get(input.sessionID)
56
- if (session.revert) {
57
- yield* revert.cleanup(session)
58
- }
59
- const agent = yield* agents.get(input.agent)
60
- if (!agent) {
61
- const available = (yield* agents.list()).filter((a: any) => !a.hidden).map((a: any) => a.name)
62
- const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
63
- const error = new NamedError.Unknown({ message: `Agent not found: "${input.agent}".${hint}` })
64
- yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
65
- throw error
66
- }
67
- const model = input.model ?? agent.model ?? (yield* lastModel(input.sessionID))!
68
- const userMsg: MessageV2.User = {
69
- id: input.messageID ?? MessageID.ascending(),
70
- sessionID: input.sessionID,
71
- time: { created: Date.now() },
72
- role: "user",
73
- agent: input.agent,
74
- model: { providerID: model.providerID, modelID: model.modelID },
75
- }
76
- yield* sessions.updateMessage(userMsg)
77
- const userPart: MessageV2.Part = {
78
- type: "text",
79
- id: PartID.ascending(),
80
- messageID: userMsg.id,
81
- sessionID: input.sessionID,
82
- text: "The following tool was executed by the user",
83
- synthetic: true,
84
- }
85
- yield* sessions.updatePart(userPart)
86
-
87
- const msg: MessageV2.Assistant = {
88
- id: MessageID.ascending(),
89
- sessionID: input.sessionID,
90
- parentID: userMsg.id,
91
- mode: input.agent,
92
- agent: input.agent,
93
- cost: 0,
94
- path: { cwd: ctx.directory, root: ctx.worktree },
95
- time: { created: Date.now() },
96
- role: "assistant",
97
- tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
98
- modelID: model.modelID,
99
- providerID: model.providerID,
100
- }
101
- yield* sessions.updateMessage(msg)
102
- const part: MessageV2.ToolPart = {
103
- type: "tool",
104
- id: PartID.ascending(),
105
- messageID: msg.id,
106
- sessionID: input.sessionID,
107
- tool: "bash",
108
- callID: ulid(),
109
- state: {
110
- status: "running",
111
- time: { start: Date.now() },
112
- input: { command: input.command },
113
- },
114
- }
115
- yield* sessions.updatePart(part)
116
- return { msg, part, cwd: ctx.directory }
117
- }).pipe(Effect.ensuring(markReady))
118
-
119
- const cfg = yield* config.get()
120
- const sh = Shell.preferred(cfg.shell)
121
- const args = Shell.args(sh, input.command, cwd)
122
- let output = ""
123
- let aborted = false
124
-
125
- const finish = Effect.uninterruptible(
126
- Effect.gen(function* () {
127
- if (aborted) {
128
- output += "\n\n" + ["<metadata>", "User aborted the command", "</metadata>"].join("\n")
129
- }
130
- if (!msg.time.completed) {
131
- msg.time.completed = Date.now()
132
- yield* sessions.updateMessage(msg)
133
- }
134
- if (part.state.status === "running") {
135
- part.state = {
136
- status: "completed",
137
- time: { ...part.state.time, end: Date.now() },
138
- input: part.state.input,
139
- title: "",
140
- metadata: { output, description: "" },
141
- output,
142
- }
143
- yield* sessions.updatePart(part)
144
- }
145
- }),
146
- )
147
-
148
- const exit = yield* restore(
149
- Effect.gen(function* () {
150
- const shellEnv = yield* plugin.trigger(
151
- "shell.env",
152
- { cwd, sessionID: input.sessionID, callID: part.callID },
153
- { env: {} },
154
- )
155
- const cmd = ChildProcess.make(sh, args, {
156
- cwd,
157
- extendEnv: true,
158
- env: { ...shellEnv.env, TERM: "dumb" },
159
- stdin: "ignore",
160
- forceKillAfter: "3 seconds",
161
- })
162
- const handle = yield* spawner.spawn(cmd)
163
- yield* Stream.runForEach(Stream.decodeText(handle.all), (chunk) =>
164
- Effect.gen(function* () {
165
- output += chunk
166
- if (part.state.status === "running") {
167
- part.state.metadata = { output, description: "" }
168
- yield* sessions.updatePart(part)
169
- }
170
- }),
171
- )
172
- yield* handle.exitCode
173
- }).pipe(Effect.scoped, Effect.orDie),
174
- ).pipe(Effect.exit)
175
-
176
- if (Exit.isFailure(exit) && Cause.hasInterrupts(exit.cause) && !Cause.hasDies(exit.cause)) {
177
- aborted = true
178
- }
179
- yield* finish
180
-
181
- if (Exit.isFailure(exit) && !aborted && !Cause.hasInterruptsOnly(exit.cause)) {
182
- return yield* Effect.failCause(exit.cause)
183
- }
184
-
185
- return { info: msg, parts: [part] }
186
- }),
187
- )
188
- })
189
-
190
- return { exec }
191
- }),
192
- )
193
-
194
- export const defaultLayer = Layer.suspend(() =>
195
- layer.pipe(
196
- Layer.provide(Session.defaultLayer),
197
- Layer.provide(Agent.defaultLayer),
198
- Layer.provide(Plugin.defaultLayer),
199
- Layer.provide(Config.defaultLayer),
200
- Layer.provide(SessionRevert.defaultLayer),
201
- Layer.provide(Provider.defaultLayer),
202
- Layer.provide(Bus.layer),
203
- Layer.provide(CrossSpawnSpawner.defaultLayer),
204
- ),
205
- )
1
+ export * from "./core/shell-exec"
@@ -1,100 +1 @@
1
- import { BusEvent } from "@/bus/bus-event"
2
- import { Bus } from "@/bus"
3
- import { InstanceState } from "@/effect/instance-state"
4
- import { SessionID } from "./schema"
5
- import { QuestionID } from "@/question/schema"
6
- import { makeRuntime } from "@/effect/run-service"
7
- import { zod } from "@/util/effect-zod"
8
- import { NonNegativeInt, withStatics } from "@/util/schema"
9
- import { Effect, Layer, Context, Schema } from "effect"
10
- import z from "zod"
11
-
12
- export const Info = Schema.Union([
13
- Schema.Struct({
14
- type: Schema.Literal("idle"),
15
- }),
16
- Schema.Struct({
17
- type: Schema.Literal("retry"),
18
- attempt: NonNegativeInt,
19
- message: Schema.String,
20
- next: NonNegativeInt,
21
- }),
22
- Schema.Struct({
23
- type: Schema.Literal("busy"),
24
- }),
25
- Schema.Struct({
26
- type: Schema.Literal("offline"),
27
- requestID: QuestionID,
28
- message: Schema.String,
29
- }),
30
- ])
31
- .annotate({ identifier: "SessionStatus" })
32
- .pipe(withStatics((s) => ({ zod: zod(s) })))
33
- export type Info = Schema.Schema.Type<typeof Info>
34
-
35
- export const Event = {
36
- Status: BusEvent.define(
37
- "session.status",
38
- Schema.Struct({
39
- sessionID: SessionID,
40
- status: Info,
41
- }),
42
- ),
43
- // deprecated
44
- Idle: BusEvent.define(
45
- "session.idle",
46
- Schema.Struct({
47
- sessionID: SessionID,
48
- }),
49
- ),
50
- }
51
-
52
- export interface Interface {
53
- readonly get: (sessionID: SessionID) => Effect.Effect<Info>
54
- readonly list: () => Effect.Effect<Map<SessionID, Info>>
55
- readonly set: (sessionID: SessionID, status: Info) => Effect.Effect<void>
56
- }
57
-
58
- export class Service extends Context.Service<Service, Interface>()("@saeeol/SessionStatus") {}
59
-
60
- export const layer = Layer.effect(
61
- Service,
62
- Effect.gen(function* () {
63
- const bus = yield* Bus.Service
64
-
65
- const state = yield* InstanceState.make(
66
- Effect.fn("SessionStatus.state")(() => Effect.succeed(new Map<SessionID, Info>())),
67
- )
68
-
69
- const get = Effect.fn("SessionStatus.get")(function* (sessionID: SessionID) {
70
- const data = yield* InstanceState.get(state)
71
- return data.get(sessionID) ?? { type: "idle" as const }
72
- })
73
-
74
- const list = Effect.fn("SessionStatus.list")(function* () {
75
- return new Map(yield* InstanceState.get(state))
76
- })
77
-
78
- const set = Effect.fn("SessionStatus.set")(function* (sessionID: SessionID, status: Info) {
79
- const data = yield* InstanceState.get(state)
80
- yield* bus.publish(Event.Status, { sessionID, status })
81
- if (status.type === "idle") {
82
- yield* bus.publish(Event.Idle, { sessionID })
83
- data.delete(sessionID)
84
- return
85
- }
86
- data.set(sessionID, status)
87
- })
88
-
89
- return Service.of({ get, list, set })
90
- }),
91
- )
92
-
93
- export const defaultLayer = layer.pipe(Layer.provide(Bus.layer))
94
- const { runPromise } = makeRuntime(Service, defaultLayer)
95
-
96
- export const list = () => runPromise((svc) => svc.list())
97
- export const get = (sessionID: SessionID) => runPromise((svc) => svc.get(sessionID))
98
- export const set = (sessionID: SessionID, status: Info) => runPromise((svc) => svc.set(sessionID, status))
99
-
100
- export * as SessionStatus from "./status"
1
+ export * from "./core/status"
@@ -1,268 +1 @@
1
- import { ulid } from "ulid"
2
- import { Effect, Context, Layer, Cause } from "effect"
3
- import { MessageID, PartID, SessionID } from "./schema"
4
- import { MessageV2 } from "./message-v2"
5
- import * as Session from "./session"
6
- import { Agent } from "../agent/agent"
7
- import { Provider } from "@/provider/provider"
8
- import { ProviderID, ModelID } from "../provider/schema"
9
- import { Bus } from "../bus"
10
- import { Permission } from "@/permission"
11
- import { Plugin } from "../plugin"
12
- import { ToolRegistry } from "@/tool/registry"
13
- import { TaskTool, type TaskPromptOps } from "@/tool/task"
14
- import { EffectBridge } from "@/effect/bridge"
15
- import { NamedError } from "@saeeol/core/util/error"
16
- import { InstanceState } from "@/effect/instance-state"
17
- import { SaeeolSessionPrompt } from "@/saeeol/session/prompt"
18
- import { SaeeolCostPropagation } from "@/saeeol/session/cost-propagation"
19
- import * as Log from "@saeeol/core/util/log"
20
-
21
- const log = Log.create({ service: "session.subtask" })
22
-
23
- export interface SubtaskInput {
24
- task: MessageV2.SubtaskPart
25
- model: Provider.Model
26
- lastUser: MessageV2.User
27
- sessionID: SessionID
28
- session: Session.Info
29
- msgs: MessageV2.WithParts[]
30
- getModel: (p: ProviderID, m: ModelID, s: SessionID) => Effect.Effect<Provider.Model>
31
- promptOps: TaskPromptOps
32
- }
33
-
34
- export interface Interface {
35
- readonly handle: (input: SubtaskInput) => Effect.Effect<void>
36
- }
37
-
38
- export class Service extends Context.Service<Service, Interface>()("@saeeol/SessionSubtask") {}
39
-
40
- export const layer = Layer.effect(
41
- Service,
42
- Effect.gen(function* () {
43
- const sessions = yield* Session.Service
44
- const agents = yield* Agent.Service
45
- const bus = yield* Bus.Service
46
- const plugin = yield* Plugin.Service
47
- const permission = yield* Permission.Service
48
- const registry = yield* ToolRegistry.Service
49
- const bridge = yield* EffectBridge.make()
50
-
51
- const handle = Effect.fn("SessionSubtask.handle")(function* (input: SubtaskInput) {
52
- const { task, model, lastUser, sessionID, session, msgs } = input
53
- const ctx = yield* InstanceState.context
54
- const { task: taskTool } = yield* registry.named()
55
- const taskModel = task.model ? yield* input.getModel(task.model.providerID, task.model.modelID, sessionID) : model
56
- const assistantMessage: MessageV2.Assistant = yield* sessions.updateMessage({
57
- id: MessageID.ascending(),
58
- role: "assistant",
59
- parentID: lastUser.id,
60
- sessionID,
61
- mode: task.agent,
62
- agent: task.agent,
63
- variant: lastUser.model.variant,
64
- path: { cwd: ctx.directory, root: ctx.worktree },
65
- cost: 0,
66
- tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
67
- modelID: taskModel.id,
68
- providerID: taskModel.providerID,
69
- time: { created: Date.now() },
70
- })
71
- let part: MessageV2.ToolPart = yield* sessions.updatePart({
72
- id: PartID.ascending(),
73
- messageID: assistantMessage.id,
74
- sessionID: assistantMessage.sessionID,
75
- type: "tool",
76
- callID: ulid(),
77
- tool: TaskTool.id,
78
- state: {
79
- status: "running",
80
- input: {
81
- prompt: task.prompt,
82
- description: task.description,
83
- subagent_type: task.agent,
84
- command: task.command,
85
- },
86
- time: { start: Date.now() },
87
- },
88
- })
89
- const taskArgs = {
90
- prompt: task.prompt,
91
- description: task.description,
92
- subagent_type: task.agent,
93
- command: task.command,
94
- }
95
- yield* plugin.trigger(
96
- "tool.execute.before",
97
- { tool: TaskTool.id, sessionID, callID: part.id },
98
- { args: taskArgs },
99
- )
100
-
101
- const taskAgent = yield* agents.get(task.agent)
102
- if (!taskAgent) {
103
- const available = (yield* agents.list()).filter((a: any) => !a.hidden).map((a: any) => a.name)
104
- const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
105
- const error = new NamedError.Unknown({ message: `Agent not found: "${task.agent}".${hint}` })
106
- yield* bus.publish(Session.Event.Error, { sessionID, error: error.toObject() })
107
- throw error
108
- }
109
-
110
- let error: Error | undefined
111
- const taskAbort = new AbortController()
112
- const childID = () => {
113
- const meta = part.state.status !== "pending" ? part.state.metadata : undefined
114
- return (meta as { sessionId?: string } | undefined)?.sessionId
115
- }
116
-
117
- const result = yield* taskTool
118
- .execute(taskArgs, {
119
- agent: task.agent,
120
- messageID: assistantMessage.id,
121
- sessionID,
122
- abort: taskAbort.signal,
123
- callID: part.callID,
124
- extra: { bypassAgentCheck: true, promptOps: input.promptOps },
125
- messages: msgs,
126
- metadata: (val: any) =>
127
- Effect.gen(function* () {
128
- part = yield* sessions.updatePart({
129
- ...part,
130
- type: "tool",
131
- state: { ...part.state, ...val },
132
- } satisfies MessageV2.ToolPart)
133
- }),
134
- ask: (req: any) =>
135
- permission
136
- .ask({
137
- ...req,
138
- sessionID,
139
- ruleset: Permission.merge(
140
- taskAgent.permission,
141
- SaeeolSessionPrompt.guardPermissions({ agent: taskAgent, session }),
142
- ),
143
- hardRuleset: SaeeolSessionPrompt.hardPermissions({ agent: taskAgent }),
144
- })
145
- .pipe(Effect.orDie),
146
- })
147
- .pipe(
148
- Effect.catchCause((cause) => {
149
- const defect = Cause.squash(cause)
150
- error = defect instanceof Error ? defect : new Error(String(defect))
151
- log.error("subtask execution failed", { error, agent: task.agent, description: task.description })
152
- return Effect.void
153
- }),
154
- Effect.onInterrupt(() =>
155
- Effect.gen(function* () {
156
- taskAbort.abort()
157
- assistantMessage.finish = "tool-calls"
158
- assistantMessage.time.completed = Date.now()
159
- const cid = childID()
160
- if (cid) {
161
- assistantMessage.cost = yield* SaeeolCostPropagation.childCost(sessions, SessionID.make(cid))
162
- }
163
- yield* sessions.updateMessage(assistantMessage)
164
- if (part.state.status === "running") {
165
- yield* sessions.updatePart({
166
- ...part,
167
- state: {
168
- status: "error",
169
- error: "Cancelled",
170
- time: { start: part.state.time.start, end: Date.now() },
171
- metadata: part.state.metadata,
172
- input: part.state.input,
173
- },
174
- } satisfies MessageV2.ToolPart)
175
- }
176
- }),
177
- ),
178
- )
179
-
180
- const attachments = result?.attachments?.map((attachment: any) => ({
181
- ...attachment,
182
- id: PartID.ascending(),
183
- sessionID,
184
- messageID: assistantMessage.id,
185
- }))
186
-
187
- yield* plugin.trigger(
188
- "tool.execute.after",
189
- { tool: TaskTool.id, sessionID, callID: part.id, args: taskArgs },
190
- result,
191
- )
192
-
193
- assistantMessage.finish = "tool-calls"
194
- assistantMessage.time.completed = Date.now()
195
- const cid = result?.metadata?.sessionId ?? childID()
196
- if (cid) {
197
- assistantMessage.cost = yield* SaeeolCostPropagation.childCost(sessions, SessionID.make(cid))
198
- }
199
- yield* sessions.updateMessage(assistantMessage)
200
-
201
- if (result && part.state.status === "running") {
202
- yield* sessions.updatePart({
203
- ...part,
204
- state: {
205
- status: "completed",
206
- input: part.state.input,
207
- title: result.title,
208
- metadata: result.metadata,
209
- output: result.output,
210
- attachments,
211
- time: { ...part.state.time, end: Date.now() },
212
- },
213
- } satisfies MessageV2.ToolPart)
214
- }
215
-
216
- if (!result) {
217
- yield* sessions.updatePart({
218
- ...part,
219
- state: {
220
- status: "error",
221
- error: error ? `Tool execution failed: ${error.message}` : "Tool execution failed",
222
- time: {
223
- start: part.state.status === "running" ? part.state.time.start : Date.now(),
224
- end: Date.now(),
225
- },
226
- metadata: part.state.status === "pending" ? undefined : part.state.metadata,
227
- input: part.state.input,
228
- },
229
- } satisfies MessageV2.ToolPart)
230
- }
231
-
232
- if (!task.command) return
233
-
234
- const summaryUserMsg: MessageV2.User = {
235
- id: MessageID.ascending(),
236
- sessionID,
237
- role: "user",
238
- time: { created: Date.now() },
239
- agent: lastUser.agent,
240
- model: lastUser.model,
241
- editorContext: lastUser.editorContext,
242
- }
243
- yield* sessions.updateMessage(summaryUserMsg)
244
- yield* sessions.updatePart({
245
- id: PartID.ascending(),
246
- messageID: summaryUserMsg.id,
247
- sessionID,
248
- type: "text",
249
- text: "Summarize the task tool output above and continue with your task.",
250
- synthetic: true,
251
- } satisfies MessageV2.TextPart)
252
- })
253
-
254
- return { handle }
255
- }),
256
- )
257
-
258
- export const defaultLayer = Layer.suspend(() =>
259
- layer.pipe(
260
- Layer.provide(Session.defaultLayer),
261
- Layer.provide(Agent.defaultLayer),
262
- Layer.provide(Plugin.defaultLayer),
263
- Layer.provide(Permission.defaultLayer),
264
- Layer.provide(ToolRegistry.defaultLayer),
265
- Layer.provide(Provider.defaultLayer),
266
- Layer.provide(Bus.layer),
267
- ),
268
- )
1
+ export * from "./core/subtask"