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,173 +1 @@
1
- import { Effect, Layer, Context, Schema } from "effect"
2
- import { Bus } from "../bus"
3
- import { Snapshot } from "../snapshot"
4
- import { Storage } from "@/storage/storage"
5
- import { SyncEvent } from "../sync"
6
- import * as Log from "@saeeol/core/util/log"
7
- import { zod } from "@/util/effect-zod"
8
- import { withStatics } from "@/util/schema"
9
- import * as Session from "./session"
10
- import { MessageV2 } from "./message-v2"
11
- import { SessionID, MessageID, PartID } from "./schema"
12
- import { SessionRunState } from "./run-state"
13
- import { SessionSummary } from "./summary"
14
-
15
- const log = Log.create({ service: "session.revert" })
16
-
17
- export const RevertInput = Schema.Struct({
18
- sessionID: SessionID,
19
- messageID: MessageID,
20
- partID: Schema.optional(PartID),
21
- }).pipe(withStatics((s) => ({ zod: zod(s) })))
22
- export type RevertInput = Schema.Schema.Type<typeof RevertInput>
23
-
24
- export interface Interface {
25
- readonly revert: (input: RevertInput) => Effect.Effect<Session.Info>
26
- readonly unrevert: (input: { sessionID: SessionID }) => Effect.Effect<Session.Info>
27
- readonly cleanup: (session: Session.Info) => Effect.Effect<void>
28
- }
29
-
30
- export class Service extends Context.Service<Service, Interface>()("@saeeol/SessionRevert") {}
31
-
32
- export const layer = Layer.effect(
33
- Service,
34
- Effect.gen(function* () {
35
- const sessions = yield* Session.Service
36
- const snap = yield* Snapshot.Service
37
- const storage = yield* Storage.Service
38
- const bus = yield* Bus.Service
39
- const summary = yield* SessionSummary.Service
40
- const state = yield* SessionRunState.Service
41
- const sync = yield* SyncEvent.Service
42
-
43
- const revert = Effect.fn("SessionRevert.revert")(function* (input: RevertInput) {
44
- yield* state.assertNotBusy(input.sessionID)
45
- const all = yield* sessions.messages({ sessionID: input.sessionID })
46
- let lastUser: MessageV2.User | undefined
47
- const session = yield* sessions.get(input.sessionID)
48
-
49
- let rev: Session.Info["revert"]
50
- const patches: Snapshot.Patch[] = []
51
- for (const msg of all) {
52
- if (msg.info.role === "user") lastUser = msg.info
53
- const remaining = []
54
- for (const part of msg.parts) {
55
- if (rev) {
56
- if (part.type === "patch") patches.push(part)
57
- continue
58
- }
59
-
60
- if (!rev) {
61
- if ((msg.info.id === input.messageID && !input.partID) || part.id === input.partID) {
62
- const partID = remaining.some((item) => ["text", "tool"].includes(item.type)) ? input.partID : undefined
63
- rev = {
64
- messageID: !partID && lastUser ? lastUser.id : msg.info.id,
65
- partID,
66
- }
67
- }
68
- remaining.push(part)
69
- }
70
- }
71
- }
72
-
73
- if (!rev) return session
74
-
75
- rev.snapshot = session.revert?.snapshot ?? (yield* snap.track())
76
- if (session.revert?.snapshot) yield* snap.restore(session.revert.snapshot)
77
- // reflects changes being undone (files on disk still have AI modifications)
78
- const range = all.filter((msg) => msg.info.id >= rev!.messageID)
79
- const diffs = yield* summary.computeDiff({ messages: range })
80
-
81
- yield* snap.revert(patches)
82
- if (rev.snapshot) rev.diff = yield* snap.diff(rev.snapshot as string)
83
- yield* storage.write(["session_diff", input.sessionID], diffs).pipe(Effect.ignore)
84
- yield* bus.publish(Session.Event.Diff, { sessionID: input.sessionID, diff: diffs })
85
- const summaryDiffs: Snapshot.SummaryFileDiff[] = diffs.map((d) => ({
86
- file: d.file,
87
- additions: d.additions,
88
- deletions: d.deletions,
89
- status: d.status,
90
- }))
91
- yield* sessions.setRevert({
92
- sessionID: input.sessionID,
93
- revert: rev,
94
- summary: {
95
- additions: diffs.reduce((sum, x) => sum + x.additions, 0),
96
- deletions: diffs.reduce((sum, x) => sum + x.deletions, 0),
97
- files: diffs.length,
98
- diffs: summaryDiffs,
99
- },
100
- })
101
- return yield* sessions.get(input.sessionID)
102
- })
103
-
104
- const unrevert = Effect.fn("SessionRevert.unrevert")(function* (input: { sessionID: SessionID }) {
105
- log.info("unreverting", input)
106
- yield* state.assertNotBusy(input.sessionID)
107
- const session = yield* sessions.get(input.sessionID)
108
- if (!session.revert) return session
109
- if (session.revert.snapshot) yield* snap.restore(session.revert!.snapshot!)
110
- yield* sessions.clearRevert(input.sessionID)
111
- return yield* sessions.get(input.sessionID)
112
- })
113
-
114
- const cleanup = Effect.fn("SessionRevert.cleanup")(function* (session: Session.Info) {
115
- if (!session.revert) return
116
- const sessionID = session.id
117
- const msgs = yield* sessions.messages({ sessionID })
118
- const messageID = session.revert.messageID
119
- const remove = [] as MessageV2.WithParts[]
120
- let target: MessageV2.WithParts | undefined
121
- for (const msg of msgs) {
122
- if (msg.info.id < messageID) continue
123
- if (msg.info.id > messageID) {
124
- remove.push(msg)
125
- continue
126
- }
127
- if (session.revert.partID) {
128
- target = msg
129
- continue
130
- }
131
- remove.push(msg)
132
- }
133
- for (const msg of remove) {
134
- yield* sync.run(MessageV2.Event.Removed, {
135
- sessionID,
136
- messageID: msg.info.id,
137
- })
138
- }
139
- if (session.revert.partID && target) {
140
- const partID = session.revert.partID
141
- const idx = target.parts.findIndex((part) => part.id === partID)
142
- if (idx >= 0) {
143
- const removeParts = target.parts.slice(idx)
144
- target.parts = target.parts.slice(0, idx)
145
- for (const part of removeParts) {
146
- yield* sync.run(MessageV2.Event.PartRemoved, {
147
- sessionID,
148
- messageID: target.info.id,
149
- partID: part.id,
150
- })
151
- }
152
- }
153
- }
154
- yield* sessions.clearRevert(sessionID)
155
- })
156
-
157
- return Service.of({ revert, unrevert, cleanup })
158
- }),
159
- )
160
-
161
- export const defaultLayer = Layer.suspend(() =>
162
- layer.pipe(
163
- Layer.provide(SessionRunState.defaultLayer),
164
- Layer.provide(Session.defaultLayer),
165
- Layer.provide(Snapshot.defaultLayer),
166
- Layer.provide(Storage.defaultLayer),
167
- Layer.provide(Bus.layer),
168
- Layer.provide(SessionSummary.defaultLayer),
169
- Layer.provide(SyncEvent.defaultLayer),
170
- ),
171
- )
172
-
173
- export * as SessionRevert from "./revert"
1
+ export * from "./core/revert"
@@ -1,110 +1 @@
1
- import { InstanceState } from "@/effect/instance-state"
2
- import { Runner } from "@/effect/runner"
3
- import { Effect, Latch, Layer, Scope, Context } from "effect"
4
- import * as Session from "./session"
5
- import { MessageV2 } from "./message-v2"
6
- import { SessionID } from "./schema"
7
- import { SessionStatus } from "./status"
8
-
9
- export interface Interface {
10
- readonly assertNotBusy: (sessionID: SessionID) => Effect.Effect<void>
11
- readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
12
- readonly ensureRunning: (
13
- sessionID: SessionID,
14
- onInterrupt: Effect.Effect<MessageV2.WithParts>,
15
- work: Effect.Effect<MessageV2.WithParts>,
16
- ) => Effect.Effect<MessageV2.WithParts>
17
- readonly startShell: (
18
- sessionID: SessionID,
19
- onInterrupt: Effect.Effect<MessageV2.WithParts>,
20
- work: Effect.Effect<MessageV2.WithParts>,
21
- ready?: Latch.Latch,
22
- ) => Effect.Effect<MessageV2.WithParts>
23
- }
24
-
25
- export class Service extends Context.Service<Service, Interface>()("@saeeol/SessionRunState") {}
26
-
27
- export const layer = Layer.effect(
28
- Service,
29
- Effect.gen(function* () {
30
- const status = yield* SessionStatus.Service
31
-
32
- const state = yield* InstanceState.make(
33
- Effect.fn("SessionRunState.state")(function* () {
34
- const scope = yield* Scope.Scope
35
- const runners = new Map<SessionID, Runner.Runner<MessageV2.WithParts>>()
36
- yield* Effect.addFinalizer(
37
- Effect.fnUntraced(function* () {
38
- yield* Effect.forEach(runners.values(), (runner) => runner.cancel, {
39
- concurrency: "unbounded",
40
- discard: true,
41
- })
42
- runners.clear()
43
- }),
44
- )
45
- return { runners, scope }
46
- }),
47
- )
48
-
49
- const runner = Effect.fn("SessionRunState.runner")(function* (
50
- sessionID: SessionID,
51
- onInterrupt: Effect.Effect<MessageV2.WithParts>,
52
- ) {
53
- const data = yield* InstanceState.get(state)
54
- const existing = data.runners.get(sessionID)
55
- if (existing) return existing
56
- const next = Runner.make<MessageV2.WithParts>(data.scope, {
57
- onIdle: Effect.gen(function* () {
58
- data.runners.delete(sessionID)
59
- yield* status.set(sessionID, { type: "idle" })
60
- }),
61
- onBusy: status.set(sessionID, { type: "busy" }),
62
- onInterrupt,
63
- busy: () => {
64
- throw new Session.BusyError(sessionID)
65
- },
66
- })
67
- data.runners.set(sessionID, next)
68
- return next
69
- })
70
-
71
- const assertNotBusy = Effect.fn("SessionRunState.assertNotBusy")(function* (sessionID: SessionID) {
72
- const data = yield* InstanceState.get(state)
73
- const existing = data.runners.get(sessionID)
74
- if (existing?.busy) throw new Session.BusyError(sessionID)
75
- })
76
-
77
- const cancel = Effect.fn("SessionRunState.cancel")(function* (sessionID: SessionID) {
78
- const data = yield* InstanceState.get(state)
79
- const existing = data.runners.get(sessionID)
80
- if (!existing || !existing.busy) {
81
- yield* status.set(sessionID, { type: "idle" })
82
- return
83
- }
84
- yield* existing.cancel
85
- })
86
-
87
- const ensureRunning = Effect.fn("SessionRunState.ensureRunning")(function* (
88
- sessionID: SessionID,
89
- onInterrupt: Effect.Effect<MessageV2.WithParts>,
90
- work: Effect.Effect<MessageV2.WithParts>,
91
- ) {
92
- return yield* (yield* runner(sessionID, onInterrupt)).ensureRunning(work)
93
- })
94
-
95
- const startShell = Effect.fn("SessionRunState.startShell")(function* (
96
- sessionID: SessionID,
97
- onInterrupt: Effect.Effect<MessageV2.WithParts>,
98
- work: Effect.Effect<MessageV2.WithParts>,
99
- ready?: Latch.Latch,
100
- ) {
101
- return yield* (yield* runner(sessionID, onInterrupt)).startShell(work, ready)
102
- })
103
-
104
- return Service.of({ assertNotBusy, cancel, ensureRunning, startShell })
105
- }),
106
- )
107
-
108
- export const defaultLayer = layer.pipe(Layer.provide(SessionStatus.defaultLayer))
109
-
110
- export * as SessionRunState from "./run-state"
1
+ export * from "./core/run-state"
@@ -1,35 +1 @@
1
- import { Schema } from "effect"
2
-
3
- import { Identifier } from "@/id/id"
4
- import { zod, ZodOverride } from "@/util/effect-zod"
5
- import { withStatics } from "@/util/schema"
6
-
7
- export const SessionID = Schema.String.annotate({ [ZodOverride]: Identifier.schema("session") }).pipe(
8
- Schema.brand("SessionID"),
9
- withStatics((s) => ({
10
- descending: (id?: string) => s.make(Identifier.descending("session", id)),
11
- zod: zod(s),
12
- })),
13
- )
14
-
15
- export type SessionID = Schema.Schema.Type<typeof SessionID>
16
-
17
- export const MessageID = Schema.String.annotate({ [ZodOverride]: Identifier.schema("message") }).pipe(
18
- Schema.brand("MessageID"),
19
- withStatics((s) => ({
20
- ascending: (id?: string) => s.make(Identifier.ascending("message", id)),
21
- zod: zod(s),
22
- })),
23
- )
24
-
25
- export type MessageID = Schema.Schema.Type<typeof MessageID>
26
-
27
- export const PartID = Schema.String.annotate({ [ZodOverride]: Identifier.schema("part") }).pipe(
28
- Schema.brand("PartID"),
29
- withStatics((s) => ({
30
- ascending: (id?: string) => s.make(Identifier.ascending("part", id)),
31
- zod: zod(s),
32
- })),
33
- )
34
-
35
- export type PartID = Schema.Schema.Type<typeof PartID>
1
+ export * from "./core/schema"
@@ -1,160 +1 @@
1
- import path from "path"
2
- import { Decimal } from "decimal.js"
3
- import z from "zod"
4
- import { type ProviderMetadata, type LanguageModelUsage } from "ai"
5
- import { InstallationVersion } from "@saeeol/core/installation/version"
6
- import { BusEvent } from "@/bus/bus-event"
7
- import { SyncEvent } from "../sync"
8
- import { PartTable, SessionTable } from "./session.sql"
9
- import * as Log from "@saeeol/core/util/log"
10
- import { MessageV2 } from "./message-v2"
11
- import { Snapshot } from "@/snapshot"
12
- import { ProjectID } from "../project/schema"
13
- import { WorkspaceID } from "../control-plane/schema"
14
- import { SessionID, MessageID, PartID } from "./schema"
15
- import type { Provider } from "@/provider/provider"
16
- import { Permission } from "@/permission"
17
- import { Global } from "@saeeol/core/global"
18
- import { SaeeolSession } from "@/saeeol/session"
19
- import { Effect, Schema, Types } from "effect"
20
- import { zod } from "@/util/effect-zod"
21
- import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@/util/schema"
22
-
23
- const log = Log.create({ service: "session" })
24
-
25
- const parentTitlePrefix = "New session - "
26
- const childTitlePrefix = "Child session - "
27
-
28
- export function createDefaultTitle(isChild = false) {
29
- return (isChild ? childTitlePrefix : parentTitlePrefix) + new Date().toISOString()
30
- }
31
-
32
- export function isDefaultTitle(title: string) {
33
- return new RegExp(`^(${parentTitlePrefix}|${childTitlePrefix})\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$`).test(title)
34
- }
35
-
36
- type SessionRow = typeof SessionTable.$inferSelect
37
-
38
- export function fromRow(row: SessionRow): Info {
39
- const summary = row.summary_additions !== null || row.summary_deletions !== null || row.summary_files !== null
40
- ? { additions: row.summary_additions ?? 0, deletions: row.summary_deletions ?? 0, files: row.summary_files ?? 0, diffs: row.summary_diffs ?? undefined }
41
- : undefined
42
- const share = row.share_url ? { url: row.share_url } : undefined
43
- return {
44
- id: row.id, slug: row.slug, projectID: row.project_id, workspaceID: row.workspace_id ?? undefined,
45
- directory: row.directory, path: row.path ?? undefined, parentID: row.parent_id ?? undefined,
46
- title: row.title, version: row.version, summary, share, revert: row.revert ?? undefined,
47
- permission: row.permission ?? undefined,
48
- time: { created: row.time_created, updated: row.time_updated, compacting: row.time_compacting ?? undefined, archived: row.time_archived ?? undefined },
49
- }
50
- }
51
-
52
- export function toRow(info: Info) {
53
- return {
54
- id: info.id, project_id: info.projectID, workspace_id: info.workspaceID, parent_id: info.parentID,
55
- slug: info.slug, directory: info.directory, path: info.path, title: info.title, version: info.version,
56
- share_url: info.share?.url, summary_additions: info.summary?.additions, summary_deletions: info.summary?.deletions,
57
- summary_files: info.summary?.files, summary_diffs: info.summary?.diffs, revert: info.revert ?? null,
58
- permission: info.permission, time_created: info.time.created, time_updated: info.time.updated,
59
- time_compacting: info.time.compacting, time_archived: info.time.archived,
60
- }
61
- }
62
-
63
- export function getForkedTitle(title: string): string {
64
- const match = title.match(/^(.+) \(fork #(\d+)\)$/)
65
- if (match) return `${match[1]} (fork #${parseInt(match[2], 10) + 1})`
66
- return `${title} (fork #1)`
67
- }
68
-
69
- export function sessionPath(worktree: string, cwd: string) {
70
- return path.relative(path.resolve(worktree), cwd).replaceAll("\\", "/")
71
- }
72
-
73
- const Summary = Schema.Struct({ additions: NonNegativeInt, deletions: NonNegativeInt, files: NonNegativeInt, diffs: optionalOmitUndefined(Schema.Array(Snapshot.SummaryFileDiff)) })
74
- const Share = Schema.Struct({ url: Schema.String })
75
- export const ArchivedTimestamp = Schema.Finite
76
- const Time = Schema.Struct({ created: NonNegativeInt, updated: NonNegativeInt, compacting: optionalOmitUndefined(NonNegativeInt), archived: optionalOmitUndefined(ArchivedTimestamp) })
77
- const Revert = Schema.Struct({ messageID: MessageID, partID: optionalOmitUndefined(PartID), snapshot: optionalOmitUndefined(Schema.String), diff: optionalOmitUndefined(Schema.String) })
78
-
79
- export const Info = Schema.Struct({
80
- id: SessionID, slug: Schema.String, projectID: ProjectID, workspaceID: optionalOmitUndefined(WorkspaceID),
81
- directory: Schema.String, path: optionalOmitUndefined(Schema.String), parentID: optionalOmitUndefined(SessionID),
82
- summary: optionalOmitUndefined(Summary), share: optionalOmitUndefined(Share), title: Schema.String,
83
- version: Schema.String, time: Time, permission: optionalOmitUndefined(Permission.Ruleset), revert: optionalOmitUndefined(Revert),
84
- }).annotate({ identifier: "Session" }).pipe(withStatics((s) => ({ zod: zod(s) })))
85
- export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>
86
-
87
- export const ProjectInfo = Schema.Struct({ id: ProjectID, name: optionalOmitUndefined(Schema.String), worktree: Schema.String })
88
- .annotate({ identifier: "ProjectSummary" }).pipe(withStatics((s) => ({ zod: zod(s) })))
89
- export type ProjectInfo = Types.DeepMutable<Schema.Schema.Type<typeof ProjectInfo>>
90
-
91
- export const GlobalInfo = Schema.Struct({ ...Info.fields, project: Schema.NullOr(ProjectInfo), worktreeName: Schema.optional(Schema.String) })
92
- .annotate({ identifier: "GlobalSession" }).pipe(withStatics((s) => ({ zod: zod(s) })))
93
- export type GlobalInfo = Types.DeepMutable<Schema.Schema.Type<typeof GlobalInfo>>
94
-
95
- export const CreateInput = Schema.optional(Schema.Struct({
96
- parentID: Schema.optional(SessionID), title: Schema.optional(Schema.String), permission: Schema.optional(Permission.Ruleset),
97
- platform: Schema.optional(Schema.String), workspaceID: Schema.optional(WorkspaceID),
98
- })).pipe(withStatics((s) => ({ zod: zod(s) })))
99
- export type CreateInput = Types.DeepMutable<Schema.Schema.Type<typeof CreateInput>>
100
-
101
- export const ForkInput = Schema.Struct({ sessionID: SessionID, messageID: Schema.optional(MessageID) }).pipe(withStatics((s) => ({ zod: zod(s) })))
102
- export const GetInput = SessionID
103
- export const ChildrenInput = SessionID
104
- export const RemoveInput = SessionID
105
- export const SetTitleInput = Schema.Struct({ sessionID: SessionID, title: Schema.String }).pipe(withStatics((s) => ({ zod: zod(s) })))
106
- export const SetArchivedInput = Schema.Struct({ sessionID: SessionID, time: Schema.optional(ArchivedTimestamp) }).pipe(withStatics((s) => ({ zod: zod(s) })))
107
- export const SetPermissionInput = Schema.Struct({ sessionID: SessionID, permission: Permission.Ruleset }).pipe(withStatics((s) => ({ zod: zod(s) })))
108
- export const SetRevertInput = Schema.Struct({ sessionID: SessionID, revert: Schema.optional(Revert), summary: Schema.optional(Summary) }).pipe(withStatics((s) => ({ zod: zod(s) })))
109
- export const MessagesInput = Schema.Struct({ sessionID: SessionID, limit: Schema.optional(NonNegativeInt) }).pipe(withStatics((s) => ({ zod: zod(s) })))
110
-
111
- export type ListInput = { directory?: string; scope?: "project"; path?: string; workspaceID?: WorkspaceID; roots?: boolean; start?: number; search?: string; limit?: number }
112
-
113
- const CreatedEventSchema = Schema.Struct({ sessionID: SessionID, info: Info })
114
- const UpdatedShare = Schema.Struct({ url: Schema.optional(Schema.NullOr(Schema.String)) })
115
- const UpdatedTime = Schema.Struct({ created: Schema.optional(Schema.NullOr(NonNegativeInt)), updated: Schema.optional(Schema.NullOr(NonNegativeInt)), compacting: Schema.optional(Schema.NullOr(NonNegativeInt)), archived: Schema.optional(Schema.NullOr(ArchivedTimestamp)) })
116
- const UpdatedInfo = Schema.Struct({
117
- id: Schema.optional(Schema.NullOr(SessionID)), slug: Schema.optional(Schema.NullOr(Schema.String)),
118
- projectID: Schema.optional(Schema.NullOr(ProjectID)), workspaceID: Schema.optional(Schema.NullOr(WorkspaceID)),
119
- directory: Schema.optional(Schema.NullOr(Schema.String)), path: Schema.optional(Schema.NullOr(Schema.String)),
120
- parentID: Schema.optional(Schema.NullOr(SessionID)), summary: Schema.optional(Schema.NullOr(Summary)),
121
- share: Schema.optional(UpdatedShare), title: Schema.optional(Schema.NullOr(Schema.String)),
122
- version: Schema.optional(Schema.NullOr(Schema.String)), time: Schema.optional(UpdatedTime),
123
- permission: Schema.optional(Schema.NullOr(Permission.Ruleset)), revert: Schema.optional(Schema.NullOr(Revert)),
124
- })
125
- const UpdatedEventSchema = Schema.Struct({ sessionID: SessionID, info: UpdatedInfo })
126
-
127
- export const Event = {
128
- Created: SyncEvent.define({ type: "session.created", version: 1, aggregate: "sessionID", schema: CreatedEventSchema }),
129
- Updated: SyncEvent.define({ type: "session.updated", version: 1, aggregate: "sessionID", schema: UpdatedEventSchema, busSchema: CreatedEventSchema }),
130
- Deleted: SyncEvent.define({ type: "session.deleted", version: 1, aggregate: "sessionID", schema: CreatedEventSchema }),
131
- Diff: BusEvent.define("session.diff", Schema.Struct({ sessionID: SessionID, diff: Schema.Array(Snapshot.FileDiff) })),
132
- Error: BusEvent.define("session.error", Schema.Struct({ sessionID: Schema.optional(SessionID), error: MessageV2.Assistant.fields.error })),
133
- TurnOpen: SaeeolSession.Event.TurnOpen,
134
- TurnClose: SaeeolSession.Event.TurnClose,
135
- }
136
-
137
- export function plan(input: { slug: string; time: { created: number } }, instance: any) {
138
- const base = instance.project.vcs ? path.join(instance.worktree, ".saeeol", "plans") : path.join(Global.Path.data, "plans")
139
- return path.join(base, [input.time.created, input.slug].join("-") + ".md")
140
- }
141
-
142
- export const getUsage = (input: { model: Provider.Model; usage: LanguageModelUsage; metadata?: ProviderMetadata; provider?: Provider.Info }) => {
143
- const safe = (v: number) => (Number.isFinite(v) ? v : 0)
144
- const inputTokens = safe(input.usage.inputTokens ?? 0)
145
- const outputTokens = safe(input.usage.outputTokens ?? 0)
146
- const reasoningTokens = safe(input.usage.outputTokenDetails?.reasoningTokens ?? input.usage.reasoningTokens ?? 0)
147
- const cacheRead = safe(input.usage.inputTokenDetails?.cacheReadTokens ?? input.usage.cachedInputTokens ?? 0)
148
- const cacheWrite = safe(Number(input.usage.inputTokenDetails?.cacheWriteTokens ?? input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ?? input.metadata?.["vertex"]?.["cacheCreationInputTokens"] ?? (input.metadata as any)?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ?? (input.metadata as any)?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ?? 0))
149
- const adjustedInput = safe(inputTokens - cacheRead - cacheWrite)
150
- const tokens = { total: input.usage.totalTokens, input: adjustedInput, output: safe(outputTokens - reasoningTokens), reasoning: reasoningTokens, cache: { write: cacheWrite, read: cacheRead } }
151
- const reported = SaeeolSession.providerCost({ metadata: input.metadata, usage: input.usage, provider: input.provider, providerID: input.model.providerID })
152
- if (reported !== undefined) return { cost: safe(reported), tokens }
153
- const costInfo = input.model.cost?.experimentalOver200K && tokens.input + tokens.cache.read > 200_000 ? input.model.cost.experimentalOver200K : input.model.cost
154
- return {
155
- cost: safe(new Decimal(0).add(new Decimal(tokens.input).mul(costInfo?.input ?? 0).div(1_000_000)).add(new Decimal(tokens.output).mul(costInfo?.output ?? 0).div(1_000_000)).add(new Decimal(tokens.cache.read).mul(costInfo?.cache?.read ?? 0).div(1_000_000)).add(new Decimal(tokens.cache.write).mul(costInfo?.cache?.write ?? 0).div(1_000_000)).add(new Decimal(tokens.reasoning).mul(costInfo?.output ?? 0).div(1_000_000)).toNumber()),
156
- tokens,
157
- }
158
- }
159
-
160
- export class BusyError extends Error { constructor(public readonly sessionID: string) { super(`Session ${sessionID} is busy`) } }
1
+ export * from "./core/session-types"
@@ -1,124 +1 @@
1
- import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core"
2
- import { ProjectTable } from "../project/project.sql"
3
- import type { MessageV2 } from "./message-v2"
4
- import type { SessionEntry } from "../v2/session-entry"
5
- import type { Snapshot } from "../snapshot"
6
- import type { Permission } from "../permission"
7
- import type { ProjectID } from "../project/schema"
8
- import type { SessionID, MessageID, PartID } from "./schema"
9
- import type { WorkspaceID } from "../control-plane/schema"
10
- import { Timestamps } from "../storage/schema.sql"
11
-
12
- type PartData = Omit<MessageV2.Part, "id" | "sessionID" | "messageID">
13
- type InfoData = Omit<MessageV2.Info, "id" | "sessionID">
14
-
15
- export const SessionTable = sqliteTable(
16
- "session",
17
- {
18
- id: text().$type<SessionID>().primaryKey(),
19
- project_id: text()
20
- .$type<ProjectID>()
21
- .notNull()
22
- .references(() => ProjectTable.id, { onDelete: "cascade" }),
23
- workspace_id: text().$type<WorkspaceID>(),
24
- parent_id: text().$type<SessionID>(),
25
- slug: text().notNull(),
26
- directory: text().notNull(),
27
- path: text(),
28
- title: text().notNull(),
29
- version: text().notNull(),
30
- share_url: text(),
31
- summary_additions: integer(),
32
- summary_deletions: integer(),
33
- summary_files: integer(),
34
- summary_diffs: text({ mode: "json" }).$type<Snapshot.SummaryFileDiff[]>(),
35
- revert: text({ mode: "json" }).$type<{ messageID: MessageID; partID?: PartID; snapshot?: string; diff?: string }>(),
36
- permission: text({ mode: "json" }).$type<Permission.Ruleset>(),
37
- ...Timestamps,
38
- time_compacting: integer(),
39
- time_archived: integer(),
40
- },
41
- (table) => [
42
- index("session_project_idx").on(table.project_id),
43
- index("session_workspace_idx").on(table.workspace_id),
44
- index("session_parent_idx").on(table.parent_id),
45
- ],
46
- )
47
-
48
- export const MessageTable = sqliteTable(
49
- "message",
50
- {
51
- id: text().$type<MessageID>().primaryKey(),
52
- session_id: text()
53
- .$type<SessionID>()
54
- .notNull()
55
- .references(() => SessionTable.id, { onDelete: "cascade" }),
56
- ...Timestamps,
57
- data: text({ mode: "json" }).notNull().$type<InfoData>(),
58
- },
59
- (table) => [index("message_session_time_created_id_idx").on(table.session_id, table.time_created, table.id)],
60
- )
61
-
62
- export const PartTable = sqliteTable(
63
- "part",
64
- {
65
- id: text().$type<PartID>().primaryKey(),
66
- message_id: text()
67
- .$type<MessageID>()
68
- .notNull()
69
- .references(() => MessageTable.id, { onDelete: "cascade" }),
70
- session_id: text().$type<SessionID>().notNull(),
71
- ...Timestamps,
72
- data: text({ mode: "json" }).notNull().$type<PartData>(),
73
- },
74
- (table) => [
75
- index("part_message_id_id_idx").on(table.message_id, table.id),
76
- index("part_session_idx").on(table.session_id),
77
- ],
78
- )
79
-
80
- export const TodoTable = sqliteTable(
81
- "todo",
82
- {
83
- session_id: text()
84
- .$type<SessionID>()
85
- .notNull()
86
- .references(() => SessionTable.id, { onDelete: "cascade" }),
87
- content: text().notNull(),
88
- status: text().notNull(),
89
- priority: text().notNull(),
90
- position: integer().notNull(),
91
- ...Timestamps,
92
- },
93
- (table) => [
94
- primaryKey({ columns: [table.session_id, table.position] }),
95
- index("todo_session_idx").on(table.session_id),
96
- ],
97
- )
98
-
99
- export const SessionEntryTable = sqliteTable(
100
- "session_entry",
101
- {
102
- id: text().$type<SessionEntry.ID>().primaryKey(),
103
- session_id: text()
104
- .$type<SessionID>()
105
- .notNull()
106
- .references(() => SessionTable.id, { onDelete: "cascade" }),
107
- type: text().$type<SessionEntry.Type>().notNull(),
108
- ...Timestamps,
109
- data: text({ mode: "json" }).notNull().$type<Omit<SessionEntry.Entry, "type" | "id">>(),
110
- },
111
- (table) => [
112
- index("session_entry_session_idx").on(table.session_id),
113
- index("session_entry_session_type_idx").on(table.session_id, table.type),
114
- index("session_entry_time_created_idx").on(table.time_created),
115
- ],
116
- )
117
-
118
- export const PermissionTable = sqliteTable("permission", {
119
- project_id: text()
120
- .primaryKey()
121
- .references(() => ProjectTable.id, { onDelete: "cascade" }),
122
- ...Timestamps,
123
- data: text({ mode: "json" }).notNull().$type<Permission.Ruleset>(),
124
- })
1
+ export * from "./core/session.sql"