saeeol 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/package.json +14 -14
  2. package/src/session/compaction-helpers.ts +1 -169
  3. package/src/session/compaction.ts +1 -712
  4. package/src/session/core/compaction/compaction-helpers.ts +169 -0
  5. package/src/session/core/compaction/compaction.ts +712 -0
  6. package/src/session/core/compaction/overflow.ts +28 -0
  7. package/src/session/core/instruction.ts +234 -0
  8. package/src/session/core/llm.ts +504 -0
  9. package/src/session/core/network.ts +392 -0
  10. package/src/session/core/processor.ts +731 -0
  11. package/src/session/core/projectors.ts +139 -0
  12. package/src/session/core/resolve-tools.ts +241 -0
  13. package/src/session/core/retry.ts +149 -0
  14. package/src/session/core/revert.ts +173 -0
  15. package/src/session/core/run-state.ts +110 -0
  16. package/src/session/core/schema.ts +35 -0
  17. package/src/session/core/session-types.ts +160 -0
  18. package/src/session/core/session.sql.ts +124 -0
  19. package/src/session/core/session.ts +948 -0
  20. package/src/session/core/shell-exec.ts +205 -0
  21. package/src/session/core/status.ts +100 -0
  22. package/src/session/core/subtask.ts +268 -0
  23. package/src/session/core/summary.ts +173 -0
  24. package/src/session/core/system.ts +114 -0
  25. package/src/session/core/todo.ts +86 -0
  26. package/src/session/core/user-part.ts +293 -0
  27. package/src/session/instruction.ts +1 -234
  28. package/src/session/llm.ts +1 -504
  29. package/src/session/message/message-errors.ts +83 -0
  30. package/src/session/message/message-parts.ts +89 -0
  31. package/src/session/message/message-query.ts +107 -0
  32. package/src/session/message/message-transform.ts +156 -0
  33. package/src/session/message/message-types.ts +68 -0
  34. package/src/session/message/message-v2.ts +73 -0
  35. package/src/session/message/message.ts +192 -0
  36. package/src/session/message-errors.ts +1 -83
  37. package/src/session/message-parts.ts +1 -89
  38. package/src/session/message-query.ts +1 -107
  39. package/src/session/message-transform.ts +1 -156
  40. package/src/session/message-types.ts +1 -68
  41. package/src/session/message-v2.ts +1 -73
  42. package/src/session/message.ts +1 -192
  43. package/src/session/network.ts +1 -392
  44. package/src/session/overflow.ts +1 -28
  45. package/src/session/processor.ts +1 -731
  46. package/src/session/projectors.ts +2 -139
  47. package/src/session/prompt/prompt-command.ts +93 -0
  48. package/src/session/prompt/prompt-loop.ts +299 -0
  49. package/src/session/prompt/prompt-model.ts +44 -0
  50. package/src/session/prompt/prompt-reminders.ts +120 -0
  51. package/src/session/prompt/prompt-resolve.ts +42 -0
  52. package/src/session/prompt/prompt-schemas.ts +128 -0
  53. package/src/session/prompt/prompt-title.ts +55 -0
  54. package/src/session/prompt/prompt-types.ts +47 -0
  55. package/src/session/prompt/prompt-user-msg.ts +80 -0
  56. package/src/session/prompt/prompt.ts +211 -0
  57. package/src/session/prompt-command.ts +1 -93
  58. package/src/session/prompt-loop.ts +1 -299
  59. package/src/session/prompt-model.ts +1 -44
  60. package/src/session/prompt-reminders.ts +1 -120
  61. package/src/session/prompt-resolve.ts +1 -42
  62. package/src/session/prompt-schemas.ts +1 -128
  63. package/src/session/prompt-title.ts +1 -55
  64. package/src/session/prompt-types.ts +1 -47
  65. package/src/session/prompt-user-msg.ts +1 -80
  66. package/src/session/prompt.ts +1 -211
  67. package/src/session/resolve-tools.ts +1 -241
  68. package/src/session/retry.ts +1 -149
  69. package/src/session/revert.ts +1 -173
  70. package/src/session/run-state.ts +1 -110
  71. package/src/session/schema.ts +1 -35
  72. package/src/session/session-types.ts +1 -160
  73. package/src/session/session.sql.ts +1 -124
  74. package/src/session/session.ts +1 -948
  75. package/src/session/shell-exec.ts +1 -205
  76. package/src/session/status.ts +1 -100
  77. package/src/session/subtask.ts +1 -268
  78. package/src/session/summary.ts +1 -173
  79. package/src/session/system.ts +1 -114
  80. package/src/session/todo.ts +1 -86
  81. package/src/session/user-part.ts +1 -293
@@ -1,234 +1 @@
1
- import path from "path"
2
- import { Effect, Layer, Context } from "effect"
3
- import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
4
- import { Config } from "@/config/config"
5
- import { InstanceState } from "@/effect/instance-state"
6
- import { Flag } from "@saeeol/core/flag/flag"
7
- import { AppFileSystem } from "@saeeol/core/filesystem"
8
- import { withTransientReadRetry } from "@/util/effect-http-client"
9
- import { Global } from "@saeeol/core/global"
10
- import { SaeeolInstruction } from "@/saeeol/session/instruction"
11
- import type { MessageV2 } from "./message-v2"
12
- import type { MessageID } from "./schema"
13
-
14
- const FILES = [
15
- "AGENTS.md",
16
- ...(Flag.SAEEOL_DISABLE_CLAUDE_CODE_PROMPT ? [] : ["CLAUDE.md"]),
17
- "CONTEXT.md", // deprecated
18
- ]
19
-
20
- function extract(messages: MessageV2.WithParts[]) {
21
- const paths = new Set<string>()
22
- for (const msg of messages) {
23
- for (const part of msg.parts) {
24
- if (part.type === "tool" && part.tool === "read" && part.state.status === "completed") {
25
- if (part.state.time.compacted) continue
26
- const loaded = part.state.metadata?.loaded
27
- if (!loaded || !Array.isArray(loaded)) continue
28
- for (const p of loaded) {
29
- if (typeof p === "string") paths.add(p)
30
- }
31
- }
32
- }
33
- }
34
- return paths
35
- }
36
-
37
- export interface Interface {
38
- readonly clear: (messageID: MessageID) => Effect.Effect<void>
39
- readonly systemPaths: () => Effect.Effect<Set<string>, AppFileSystem.Error>
40
- readonly system: () => Effect.Effect<string[], AppFileSystem.Error>
41
- readonly find: (dir: string) => Effect.Effect<string | undefined, AppFileSystem.Error>
42
- readonly resolve: (
43
- messages: MessageV2.WithParts[],
44
- filepath: string,
45
- messageID: MessageID,
46
- ) => Effect.Effect<{ filepath: string; content: string }[], AppFileSystem.Error>
47
- }
48
-
49
- export class Service extends Context.Service<Service, Interface>()("@saeeol/Instruction") {}
50
-
51
- export const layer: Layer.Layer<
52
- Service,
53
- never,
54
- AppFileSystem.Service | Config.Service | Global.Service | HttpClient.HttpClient
55
- > = Layer.effect(
56
- Service,
57
- Effect.gen(function* () {
58
- const cfg = yield* Config.Service
59
- const fs = yield* AppFileSystem.Service
60
- const global = yield* Global.Service
61
- const http = HttpClient.filterStatusOk(withTransientReadRetry(yield* HttpClient.HttpClient))
62
- const globalFiles = [
63
- ...(Flag.SAEEOL_CONFIG_DIR ? [path.join(Flag.SAEEOL_CONFIG_DIR, "AGENTS.md")] : []),
64
- path.join(global.config, "AGENTS.md"),
65
- ...(!Flag.SAEEOL_DISABLE_CLAUDE_CODE_PROMPT ? [path.join(global.home, ".claude", "CLAUDE.md")] : []),
66
- ]
67
-
68
- const state = yield* InstanceState.make(
69
- Effect.fn("Instruction.state")(() =>
70
- Effect.succeed({
71
- // Track which instruction files have already been attached for a given assistant message.
72
- claims: new Map<MessageID, Set<string>>(),
73
- }),
74
- ),
75
- )
76
-
77
- const relative = Effect.fnUntraced(function* (instruction: string) {
78
- const ctx = yield* InstanceState.context
79
- if (!Flag.SAEEOL_DISABLE_PROJECT_CONFIG) {
80
- return yield* fs
81
- .globUp(instruction, ctx.directory, ctx.worktree)
82
- .pipe(Effect.catch(() => Effect.succeed([] as string[])))
83
- }
84
- const root = Flag.SAEEOL_CONFIG_DIR ?? global.config
85
- return yield* fs.globUp(instruction, root, root).pipe(Effect.catch(() => Effect.succeed([] as string[])))
86
- })
87
-
88
- const read = Effect.fnUntraced(function* (filepath: string) {
89
- const content = yield* fs.readFileString(filepath).pipe(Effect.catch(() => Effect.succeed("")))
90
- return yield* Effect.promise(() => SaeeolInstruction.content(content, filepath))
91
- })
92
-
93
- const fetch = Effect.fnUntraced(function* (url: string) {
94
- const res = yield* http.execute(HttpClientRequest.get(url)).pipe(
95
- Effect.timeout(5000),
96
- Effect.catch(() => Effect.succeed(null)),
97
- )
98
- if (!res) return ""
99
- const body = yield* res.arrayBuffer.pipe(Effect.catch(() => Effect.succeed(new ArrayBuffer(0))))
100
- return new TextDecoder().decode(body)
101
- })
102
-
103
- const clear = Effect.fn("Instruction.clear")(function* (messageID: MessageID) {
104
- const s = yield* InstanceState.get(state)
105
- s.claims.delete(messageID)
106
- })
107
-
108
- const systemPaths = Effect.fn("Instruction.systemPaths")(function* () {
109
- const config = yield* cfg.get()
110
- const ctx = yield* InstanceState.context
111
- const paths = new Set<string>()
112
-
113
- for (const file of globalFiles) {
114
- if (yield* fs.existsSafe(file)) {
115
- paths.add(path.resolve(file))
116
- break
117
- }
118
- }
119
-
120
- // The first project-level match wins so we don't stack AGENTS.md/CLAUDE.md from every ancestor.
121
- if (!Flag.SAEEOL_DISABLE_PROJECT_CONFIG) {
122
- for (const file of FILES) {
123
- const matches = yield* fs.findUp(file, ctx.directory, ctx.worktree)
124
- if (matches.length > 0) {
125
- matches.forEach((item) => paths.add(path.resolve(item)))
126
- break
127
- }
128
- }
129
- }
130
-
131
- if (config.instructions) {
132
- for (const raw of config.instructions) {
133
- if (raw.startsWith("https://") || raw.startsWith("http://")) continue
134
- const instruction = raw.startsWith("~/") ? path.join(global.home, raw.slice(2)) : raw
135
- const matches = yield* (
136
- path.isAbsolute(instruction)
137
- ? fs.glob(path.basename(instruction), {
138
- cwd: path.dirname(instruction),
139
- absolute: true,
140
- include: "file",
141
- })
142
- : relative(instruction)
143
- ).pipe(Effect.catch(() => Effect.succeed([] as string[])))
144
- matches.forEach((item) => paths.add(path.resolve(item)))
145
- }
146
- }
147
-
148
- return paths
149
- })
150
-
151
- const system = Effect.fn("Instruction.system")(function* () {
152
- const config = yield* cfg.get()
153
- const paths = yield* systemPaths()
154
- const urls = (config.instructions ?? []).filter(
155
- (item) => item.startsWith("https://") || item.startsWith("http://"),
156
- )
157
-
158
- const files = yield* Effect.forEach(Array.from(paths), read, { concurrency: 8 })
159
- const remote = yield* Effect.forEach(urls, fetch, { concurrency: 4 })
160
-
161
- return [
162
- ...Array.from(paths).flatMap((item, i) => (files[i] ? [`Instructions from: ${item}\n${files[i]}`] : [])),
163
- ...urls.flatMap((item, i) => (remote[i] ? [`Instructions from: ${item}\n${remote[i]}`] : [])),
164
- ]
165
- })
166
-
167
- const find = Effect.fn("Instruction.find")(function* (dir: string) {
168
- for (const file of FILES) {
169
- const filepath = path.resolve(path.join(dir, file))
170
- if (yield* fs.existsSafe(filepath)) return filepath
171
- }
172
- return undefined
173
- })
174
-
175
- const resolve = Effect.fn("Instruction.resolve")(function* (
176
- messages: MessageV2.WithParts[],
177
- filepath: string,
178
- messageID: MessageID,
179
- ) {
180
- const sys = yield* systemPaths()
181
- const already = extract(messages)
182
- const results: { filepath: string; content: string }[] = []
183
- const s = yield* InstanceState.get(state)
184
- const root = path.resolve(yield* InstanceState.directory)
185
-
186
- const target = path.resolve(filepath)
187
- let current = path.dirname(target)
188
-
189
- // Walk upward from the file being read and attach nearby instruction files once per message.
190
- while (current.startsWith(root) && current !== root) {
191
- const found = yield* find(current)
192
- if (!found || found === target || sys.has(found) || already.has(found)) {
193
- current = path.dirname(current)
194
- continue
195
- }
196
-
197
- let set = s.claims.get(messageID)
198
- if (!set) {
199
- set = new Set()
200
- s.claims.set(messageID, set)
201
- }
202
- if (set.has(found)) {
203
- current = path.dirname(current)
204
- continue
205
- }
206
-
207
- set.add(found)
208
- const content = yield* read(found)
209
- if (content) {
210
- results.push({ filepath: found, content: `Instructions from: ${found}\n${content}` })
211
- }
212
-
213
- current = path.dirname(current)
214
- }
215
-
216
- return results
217
- })
218
-
219
- return Service.of({ clear, systemPaths, system, find, resolve })
220
- }),
221
- )
222
-
223
- export const defaultLayer = layer.pipe(
224
- Layer.provide(Config.defaultLayer),
225
- Layer.provide(Global.layer),
226
- Layer.provide(AppFileSystem.defaultLayer),
227
- Layer.provide(FetchHttpClient.layer),
228
- )
229
-
230
- export function loaded(messages: MessageV2.WithParts[]) {
231
- return extract(messages)
232
- }
233
-
234
- export * as Instruction from "./instruction"
1
+ export * from "./core/instruction"