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.
- package/package.json +14 -14
- package/src/session/compaction-helpers.ts +1 -169
- package/src/session/compaction.ts +1 -712
- package/src/session/core/compaction/compaction-helpers.ts +169 -0
- package/src/session/core/compaction/compaction.ts +712 -0
- package/src/session/core/compaction/overflow.ts +28 -0
- package/src/session/core/instruction.ts +234 -0
- package/src/session/core/llm.ts +504 -0
- package/src/session/core/network.ts +392 -0
- package/src/session/core/processor.ts +731 -0
- package/src/session/core/projectors.ts +139 -0
- package/src/session/core/resolve-tools.ts +241 -0
- package/src/session/core/retry.ts +149 -0
- package/src/session/core/revert.ts +173 -0
- package/src/session/core/run-state.ts +110 -0
- package/src/session/core/schema.ts +35 -0
- package/src/session/core/session-types.ts +160 -0
- package/src/session/core/session.sql.ts +124 -0
- package/src/session/core/session.ts +948 -0
- package/src/session/core/shell-exec.ts +205 -0
- package/src/session/core/status.ts +100 -0
- package/src/session/core/subtask.ts +268 -0
- package/src/session/core/summary.ts +173 -0
- package/src/session/core/system.ts +114 -0
- package/src/session/core/todo.ts +86 -0
- package/src/session/core/user-part.ts +293 -0
- package/src/session/instruction.ts +1 -234
- package/src/session/llm.ts +1 -504
- package/src/session/message/message-errors.ts +83 -0
- package/src/session/message/message-parts.ts +89 -0
- package/src/session/message/message-query.ts +107 -0
- package/src/session/message/message-transform.ts +156 -0
- package/src/session/message/message-types.ts +68 -0
- package/src/session/message/message-v2.ts +73 -0
- package/src/session/message/message.ts +192 -0
- package/src/session/message-errors.ts +1 -83
- package/src/session/message-parts.ts +1 -89
- package/src/session/message-query.ts +1 -107
- package/src/session/message-transform.ts +1 -156
- package/src/session/message-types.ts +1 -68
- package/src/session/message-v2.ts +1 -73
- package/src/session/message.ts +1 -192
- package/src/session/network.ts +1 -392
- package/src/session/overflow.ts +1 -28
- package/src/session/processor.ts +1 -731
- package/src/session/projectors.ts +2 -139
- package/src/session/prompt/prompt-command.ts +93 -0
- package/src/session/prompt/prompt-loop.ts +299 -0
- package/src/session/prompt/prompt-model.ts +44 -0
- package/src/session/prompt/prompt-reminders.ts +120 -0
- package/src/session/prompt/prompt-resolve.ts +42 -0
- package/src/session/prompt/prompt-schemas.ts +128 -0
- package/src/session/prompt/prompt-title.ts +55 -0
- package/src/session/prompt/prompt-types.ts +47 -0
- package/src/session/prompt/prompt-user-msg.ts +80 -0
- package/src/session/prompt/prompt.ts +211 -0
- package/src/session/prompt-command.ts +1 -93
- package/src/session/prompt-loop.ts +1 -299
- package/src/session/prompt-model.ts +1 -44
- package/src/session/prompt-reminders.ts +1 -120
- package/src/session/prompt-resolve.ts +1 -42
- package/src/session/prompt-schemas.ts +1 -128
- package/src/session/prompt-title.ts +1 -55
- package/src/session/prompt-types.ts +1 -47
- package/src/session/prompt-user-msg.ts +1 -80
- package/src/session/prompt.ts +1 -211
- package/src/session/resolve-tools.ts +1 -241
- package/src/session/retry.ts +1 -149
- package/src/session/revert.ts +1 -173
- package/src/session/run-state.ts +1 -110
- package/src/session/schema.ts +1 -35
- package/src/session/session-types.ts +1 -160
- package/src/session/session.sql.ts +1 -124
- package/src/session/session.ts +1 -948
- package/src/session/shell-exec.ts +1 -205
- package/src/session/status.ts +1 -100
- package/src/session/subtask.ts +1 -268
- package/src/session/summary.ts +1 -173
- package/src/session/system.ts +1 -114
- package/src/session/todo.ts +1 -86
- package/src/session/user-part.ts +1 -293
|
@@ -1,234 +1 @@
|
|
|
1
|
-
|
|
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"
|