snow-flow 10.0.185 → 10.0.186-dev.682
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/bin/index.js.map +9 -9
- package/bin/worker.js.map +7 -7
- package/mcp/servicenow-unified.js +116 -116
- package/package.json +1 -1
- package/parsers-config.ts +2 -1
- package/src/bun/index.ts +10 -9
- package/src/cli/cmd/agent.ts +3 -3
- package/src/cli/cmd/auth.ts +46 -0
- package/src/cli/cmd/import.ts +2 -2
- package/src/cli/cmd/session.ts +9 -12
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +2 -1
- package/src/cli/cmd/tui/component/prompt/index.tsx +19 -6
- package/src/cli/cmd/tui/component/spinner.tsx +24 -0
- package/src/cli/cmd/tui/context/exit.tsx +1 -1
- package/src/cli/cmd/tui/routes/home.tsx +16 -2
- package/src/cli/cmd/tui/routes/session/index.tsx +122 -53
- package/src/cli/cmd/tui/routes/session/permission.tsx +9 -1
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +9 -1
- package/src/cli/cmd/tui/thread.ts +4 -1
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +1 -1
- package/src/cli/cmd/tui/util/clipboard.ts +3 -3
- package/src/cli/cmd/tui/worker.ts +6 -1
- package/src/config/config.ts +28 -0
- package/src/context/context-db.ts +437 -0
- package/src/format/formatter.ts +14 -5
- package/src/global/index.ts +3 -4
- package/src/mcp/index.ts +7 -2
- package/src/mcp/oauth-callback.ts +7 -15
- package/src/mcp/oauth-provider.ts +34 -3
- package/src/project/project.ts +8 -4
- package/src/provider/models.ts +1 -1
- package/src/provider/provider.ts +88 -9
- package/src/provider/transform.ts +7 -2
- package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_capacity_plan.ts +20 -7
- package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_retrospective.ts +6 -8
- package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_sprint_manage.ts +46 -28
- package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_team_manage.ts +53 -41
- package/src/servicenow/servicenow-mcp-unified/tools/agile/snow_agile_velocity_report.ts +8 -1
- package/src/servicenow/servicenow-mcp-unified/tools/automation/snow_schedule_script_job.ts +388 -243
- package/src/session/compaction.ts +126 -23
- package/src/session/message-v2.ts +33 -10
- package/src/session/processor.ts +29 -17
- package/src/session/prompt.ts +34 -6
- package/src/share/share-next.ts +2 -2
- package/src/shell/shell.ts +2 -1
- package/src/tool/edit.ts +15 -1
- package/src/tool/registry.ts +9 -1
- package/src/tool/truncation.ts +17 -0
- package/src/tool/websearch.ts +1 -1
- package/src/tool/websearch.txt +2 -2
- package/src/tool/write.ts +3 -4
- package/src/util/filesystem.ts +36 -7
- package/src/util/keybind.ts +1 -1
- package/src/util/log.ts +8 -5
- package/src/util/token.ts +28 -0
- package/test/cli/plugin-auth-picker.test.ts +120 -0
- package/test/fixture/fixture.ts +3 -0
- package/test/mcp/oauth-auto-connect.test.ts +197 -0
- package/test/project/project.test.ts +47 -0
- package/test/provider/provider.test.ts +2 -0
- package/test/provider/transform.test.ts +32 -0
- package/test/tool/edit.test.ts +679 -0
|
@@ -14,6 +14,7 @@ import { fn } from "@/util/fn"
|
|
|
14
14
|
import { Agent } from "@/agent/agent"
|
|
15
15
|
import { Plugin } from "@/plugin"
|
|
16
16
|
import { Config } from "@/config/config"
|
|
17
|
+
import { ContextDB } from "@/context/context-db"
|
|
17
18
|
|
|
18
19
|
export namespace SessionCompaction {
|
|
19
20
|
const log = Log.create({ service: "session.compaction" })
|
|
@@ -121,8 +122,31 @@ export namespace SessionCompaction {
|
|
|
121
122
|
sessionID: string
|
|
122
123
|
abort: AbortSignal
|
|
123
124
|
auto: boolean
|
|
125
|
+
overflow?: boolean
|
|
124
126
|
}) {
|
|
125
127
|
const userMessage = input.messages.findLast((m) => m.info.id === input.parentID)!.info as MessageV2.User
|
|
128
|
+
|
|
129
|
+
let messages = input.messages
|
|
130
|
+
let replay: MessageV2.WithParts | undefined
|
|
131
|
+
if (input.overflow) {
|
|
132
|
+
const idx = input.messages.findIndex((m) => m.info.id === input.parentID)
|
|
133
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
134
|
+
const msg = input.messages[i]
|
|
135
|
+
if (msg.info.role === "user" && !msg.parts.some((p) => p.type === "compaction")) {
|
|
136
|
+
replay = msg
|
|
137
|
+
messages = input.messages.slice(0, i)
|
|
138
|
+
break
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const hasContent = replay && messages.some(
|
|
142
|
+
(m) => m.info.role === "user" && !m.parts.some((p) => p.type === "compaction"),
|
|
143
|
+
)
|
|
144
|
+
if (!hasContent) {
|
|
145
|
+
replay = undefined
|
|
146
|
+
messages = input.messages
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
126
150
|
const agent = await Agent.get("compaction")
|
|
127
151
|
const model = agent.model
|
|
128
152
|
? await Provider.getModel(agent.model.providerID, agent.model.modelID)
|
|
@@ -175,7 +199,7 @@ export namespace SessionCompaction {
|
|
|
175
199
|
tools: {},
|
|
176
200
|
system: [],
|
|
177
201
|
messages: [
|
|
178
|
-
...MessageV2.toModelMessages(
|
|
202
|
+
...MessageV2.toModelMessages(messages, model, { stripMedia: true }),
|
|
179
203
|
{
|
|
180
204
|
role: "user",
|
|
181
205
|
content: [
|
|
@@ -189,32 +213,82 @@ export namespace SessionCompaction {
|
|
|
189
213
|
model,
|
|
190
214
|
})
|
|
191
215
|
|
|
216
|
+
if (result === "compact") {
|
|
217
|
+
processor.message.error = new MessageV2.ContextOverflowError({
|
|
218
|
+
message: replay
|
|
219
|
+
? "Conversation history too large to compact - exceeds model context limit"
|
|
220
|
+
: "Session too large to compact - context exceeds model limit even after stripping media",
|
|
221
|
+
}).toObject()
|
|
222
|
+
processor.message.finish = "error"
|
|
223
|
+
await Session.updateMessage(processor.message)
|
|
224
|
+
return "stop"
|
|
225
|
+
}
|
|
226
|
+
|
|
192
227
|
if (result === "continue" && input.auto) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
228
|
+
if (replay) {
|
|
229
|
+
const original = replay.info as MessageV2.User
|
|
230
|
+
const replayMsg = await Session.updateMessage({
|
|
231
|
+
id: Identifier.ascending("message"),
|
|
232
|
+
role: "user",
|
|
233
|
+
sessionID: input.sessionID,
|
|
234
|
+
time: { created: Date.now() },
|
|
235
|
+
agent: original.agent,
|
|
236
|
+
model: original.model,
|
|
237
|
+
tools: original.tools,
|
|
238
|
+
system: original.system,
|
|
239
|
+
variant: original.variant,
|
|
240
|
+
})
|
|
241
|
+
for (const part of replay.parts) {
|
|
242
|
+
if (part.type === "compaction") continue
|
|
243
|
+
const replayPart =
|
|
244
|
+
part.type === "file" && MessageV2.isMedia(part.mime)
|
|
245
|
+
? { type: "text" as const, text: `[Attached ${part.mime}: ${part.filename ?? "file"}]` }
|
|
246
|
+
: part
|
|
247
|
+
await Session.updatePart({
|
|
248
|
+
...replayPart,
|
|
249
|
+
id: Identifier.ascending("part"),
|
|
250
|
+
messageID: replayMsg.id,
|
|
251
|
+
sessionID: input.sessionID,
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
const continueMsg = await Session.updateMessage({
|
|
256
|
+
id: Identifier.ascending("message"),
|
|
257
|
+
role: "user",
|
|
258
|
+
sessionID: input.sessionID,
|
|
259
|
+
time: { created: Date.now() },
|
|
260
|
+
agent: userMessage.agent,
|
|
261
|
+
model: userMessage.model,
|
|
262
|
+
})
|
|
263
|
+
const text =
|
|
264
|
+
(input.overflow
|
|
265
|
+
? "The previous request exceeded the provider's size limit due to large media attachments. The conversation was compacted and media files were removed from context. If the user was asking about attached images or files, explain that the attachments were too large to process and suggest they try again with smaller or fewer files.\n\n"
|
|
266
|
+
: "") + "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed."
|
|
267
|
+
await Session.updatePart({
|
|
268
|
+
id: Identifier.ascending("part"),
|
|
269
|
+
messageID: continueMsg.id,
|
|
270
|
+
sessionID: input.sessionID,
|
|
271
|
+
type: "text",
|
|
272
|
+
synthetic: true,
|
|
273
|
+
text,
|
|
274
|
+
time: {
|
|
275
|
+
start: Date.now(),
|
|
276
|
+
end: Date.now(),
|
|
277
|
+
},
|
|
278
|
+
})
|
|
279
|
+
}
|
|
215
280
|
}
|
|
216
281
|
if (processor.message.error) return "stop"
|
|
217
282
|
Bus.publish(Event.Compacted, { sessionID: input.sessionID })
|
|
283
|
+
|
|
284
|
+
// Capture Instance context before fire-and-forget (AsyncLocalStorage won't
|
|
285
|
+
// be available once the promise escapes the current execution context).
|
|
286
|
+
const projectID = Instance.project.id
|
|
287
|
+
const directory = Instance.directory
|
|
288
|
+
persistSummary(input.sessionID, msg.id, projectID, directory).catch((e) => {
|
|
289
|
+
log.warn("failed to persist compaction summary", { error: e })
|
|
290
|
+
})
|
|
291
|
+
|
|
218
292
|
return "continue"
|
|
219
293
|
}
|
|
220
294
|
|
|
@@ -227,6 +301,7 @@ export namespace SessionCompaction {
|
|
|
227
301
|
modelID: z.string(),
|
|
228
302
|
}),
|
|
229
303
|
auto: z.boolean(),
|
|
304
|
+
overflow: z.boolean().optional(),
|
|
230
305
|
}),
|
|
231
306
|
async (input) => {
|
|
232
307
|
const msg = await Session.updateMessage({
|
|
@@ -245,7 +320,35 @@ export namespace SessionCompaction {
|
|
|
245
320
|
sessionID: msg.sessionID,
|
|
246
321
|
type: "compaction",
|
|
247
322
|
auto: input.auto,
|
|
323
|
+
overflow: input.overflow,
|
|
248
324
|
})
|
|
249
325
|
},
|
|
250
326
|
)
|
|
327
|
+
|
|
328
|
+
async function persistSummary(sessionID: string, messageID: string, projectID: string, directory: string) {
|
|
329
|
+
const config = await Config.get()
|
|
330
|
+
if (config.contextdb?.persist_summaries === false) return
|
|
331
|
+
|
|
332
|
+
const db = await ContextDB.get()
|
|
333
|
+
if (!db) return
|
|
334
|
+
|
|
335
|
+
const msgs = await Session.messages({ sessionID })
|
|
336
|
+
const compactionMsg = msgs.find((m) => m.info.id === messageID)
|
|
337
|
+
if (!compactionMsg) return
|
|
338
|
+
|
|
339
|
+
const summaryText = compactionMsg.parts
|
|
340
|
+
.filter((p) => p.type === "text")
|
|
341
|
+
.map((p) => (p as MessageV2.TextPart).text)
|
|
342
|
+
.join("\n")
|
|
343
|
+
|
|
344
|
+
if (!summaryText.trim()) return
|
|
345
|
+
|
|
346
|
+
ContextDB.storeSummary({
|
|
347
|
+
sessionID,
|
|
348
|
+
projectID,
|
|
349
|
+
directory,
|
|
350
|
+
summaryText,
|
|
351
|
+
filesMentioned: ContextDB.extractFileReferences(summaryText),
|
|
352
|
+
})
|
|
353
|
+
}
|
|
251
354
|
}
|
|
@@ -16,6 +16,10 @@ import type { Provider } from "@/provider/provider"
|
|
|
16
16
|
import { Instance } from "@/project/instance"
|
|
17
17
|
|
|
18
18
|
export namespace MessageV2 {
|
|
19
|
+
export function isMedia(mime: string) {
|
|
20
|
+
return mime.startsWith("image/") || mime === "application/pdf"
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
|
|
20
24
|
export const AbortedError = NamedError.create("MessageAbortedError", z.object({ message: z.string() }))
|
|
21
25
|
export const AuthError = NamedError.create(
|
|
@@ -37,6 +41,10 @@ export namespace MessageV2 {
|
|
|
37
41
|
}),
|
|
38
42
|
)
|
|
39
43
|
export type APIError = z.infer<typeof APIError.Schema>
|
|
44
|
+
export const ContextOverflowError = NamedError.create(
|
|
45
|
+
"ContextOverflowError",
|
|
46
|
+
z.object({ message: z.string(), responseBody: z.string().optional() }),
|
|
47
|
+
)
|
|
40
48
|
|
|
41
49
|
const PartBase = z.object({
|
|
42
50
|
id: z.string(),
|
|
@@ -161,6 +169,7 @@ export namespace MessageV2 {
|
|
|
161
169
|
export const CompactionPart = PartBase.extend({
|
|
162
170
|
type: z.literal("compaction"),
|
|
163
171
|
auto: z.boolean(),
|
|
172
|
+
overflow: z.boolean().optional(),
|
|
164
173
|
}).meta({
|
|
165
174
|
ref: "CompactionPart",
|
|
166
175
|
})
|
|
@@ -362,6 +371,7 @@ export namespace MessageV2 {
|
|
|
362
371
|
OutputLengthError.Schema,
|
|
363
372
|
AbortedError.Schema,
|
|
364
373
|
APIError.Schema,
|
|
374
|
+
ContextOverflowError.Schema,
|
|
365
375
|
])
|
|
366
376
|
.optional(),
|
|
367
377
|
parentID: z.string(),
|
|
@@ -529,7 +539,11 @@ export namespace MessageV2 {
|
|
|
529
539
|
return result
|
|
530
540
|
}
|
|
531
541
|
|
|
532
|
-
export function toModelMessages(
|
|
542
|
+
export function toModelMessages(
|
|
543
|
+
input: WithParts[],
|
|
544
|
+
model: Provider.Model,
|
|
545
|
+
options?: { stripMedia?: boolean },
|
|
546
|
+
): ModelMessage[] {
|
|
533
547
|
const result: UIMessage[] = []
|
|
534
548
|
const toolNames = new Set<string>()
|
|
535
549
|
|
|
@@ -583,13 +597,21 @@ export namespace MessageV2 {
|
|
|
583
597
|
text: part.text,
|
|
584
598
|
})
|
|
585
599
|
// text/plain and directory files are converted into text parts, ignore them
|
|
586
|
-
if (part.type === "file" && part.mime !== "text/plain" && part.mime !== "application/x-directory")
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
600
|
+
if (part.type === "file" && part.mime !== "text/plain" && part.mime !== "application/x-directory") {
|
|
601
|
+
if (options?.stripMedia && isMedia(part.mime)) {
|
|
602
|
+
userMessage.parts.push({
|
|
603
|
+
type: "text",
|
|
604
|
+
text: `[Attached ${part.mime}: ${part.filename ?? "file"}]`,
|
|
605
|
+
})
|
|
606
|
+
} else {
|
|
607
|
+
userMessage.parts.push({
|
|
608
|
+
type: "file",
|
|
609
|
+
url: part.url,
|
|
610
|
+
mediaType: part.mime,
|
|
611
|
+
filename: part.filename,
|
|
612
|
+
})
|
|
613
|
+
}
|
|
614
|
+
}
|
|
593
615
|
|
|
594
616
|
if (part.type === "compaction") {
|
|
595
617
|
userMessage.parts.push({
|
|
@@ -638,7 +660,7 @@ export namespace MessageV2 {
|
|
|
638
660
|
toolNames.add(part.tool)
|
|
639
661
|
if (part.state.status === "completed") {
|
|
640
662
|
const outputText = part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output
|
|
641
|
-
const attachments = part.state.time.compacted ? [] : (part.state.attachments ?? [])
|
|
663
|
+
const attachments = part.state.time.compacted || options?.stripMedia ? [] : (part.state.attachments ?? [])
|
|
642
664
|
const output =
|
|
643
665
|
attachments.length > 0
|
|
644
666
|
? {
|
|
@@ -746,7 +768,8 @@ export namespace MessageV2 {
|
|
|
746
768
|
msg.parts.some((part) => part.type === "compaction")
|
|
747
769
|
)
|
|
748
770
|
break
|
|
749
|
-
if (msg.info.role === "assistant" && msg.info.summary && msg.info.finish
|
|
771
|
+
if (msg.info.role === "assistant" && msg.info.summary && msg.info.finish && !msg.info.error)
|
|
772
|
+
completed.add(msg.info.parentID)
|
|
750
773
|
}
|
|
751
774
|
result.reverse()
|
|
752
775
|
return result
|
package/src/session/processor.ts
CHANGED
|
@@ -271,7 +271,10 @@ export namespace SessionProcessor {
|
|
|
271
271
|
sessionID: input.sessionID,
|
|
272
272
|
messageID: input.assistantMessage.parentID,
|
|
273
273
|
})
|
|
274
|
-
if (
|
|
274
|
+
if (
|
|
275
|
+
!input.assistantMessage.summary &&
|
|
276
|
+
(await SessionCompaction.isOverflow({ tokens: usage.tokens, model: input.model }))
|
|
277
|
+
) {
|
|
275
278
|
needsCompaction = true
|
|
276
279
|
}
|
|
277
280
|
break
|
|
@@ -342,24 +345,33 @@ export namespace SessionProcessor {
|
|
|
342
345
|
stack: JSON.stringify(e.stack),
|
|
343
346
|
})
|
|
344
347
|
const error = MessageV2.fromError(e, { providerID: input.model.providerID })
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
348
|
+
if (MessageV2.ContextOverflowError.isInstance(error)) {
|
|
349
|
+
needsCompaction = true
|
|
350
|
+
Bus.publish(Session.Event.Error, {
|
|
351
|
+
sessionID: input.sessionID,
|
|
352
|
+
error,
|
|
353
|
+
})
|
|
354
|
+
} else {
|
|
355
|
+
const retry = SessionRetry.retryable(error)
|
|
356
|
+
if (retry !== undefined) {
|
|
357
|
+
attempt++
|
|
358
|
+
const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
|
|
359
|
+
SessionStatus.set(input.sessionID, {
|
|
360
|
+
type: "retry",
|
|
361
|
+
attempt,
|
|
362
|
+
message: retry,
|
|
363
|
+
next: Date.now() + delay,
|
|
364
|
+
})
|
|
365
|
+
await SessionRetry.sleep(delay, input.abort).catch(() => {})
|
|
366
|
+
continue
|
|
367
|
+
}
|
|
368
|
+
input.assistantMessage.error = error
|
|
369
|
+
Bus.publish(Session.Event.Error, {
|
|
370
|
+
sessionID: input.assistantMessage.sessionID,
|
|
371
|
+
error: input.assistantMessage.error,
|
|
354
372
|
})
|
|
355
|
-
|
|
356
|
-
continue
|
|
373
|
+
SessionStatus.set(input.sessionID, { type: "idle" })
|
|
357
374
|
}
|
|
358
|
-
input.assistantMessage.error = error
|
|
359
|
-
Bus.publish(Session.Event.Error, {
|
|
360
|
-
sessionID: input.assistantMessage.sessionID,
|
|
361
|
-
error: input.assistantMessage.error,
|
|
362
|
-
})
|
|
363
375
|
}
|
|
364
376
|
if (snapshot) {
|
|
365
377
|
const patch = await Snapshot.patch(snapshot)
|
package/src/session/prompt.ts
CHANGED
|
@@ -26,7 +26,6 @@ import { ToolRegistry } from "../tool/registry"
|
|
|
26
26
|
import { MCP } from "../mcp"
|
|
27
27
|
import { LSP } from "../lsp"
|
|
28
28
|
import { ReadTool } from "../tool/read"
|
|
29
|
-
import { ListTool } from "../tool/ls"
|
|
30
29
|
import { FileTime } from "../file/time"
|
|
31
30
|
import { Flag } from "../flag/flag"
|
|
32
31
|
import { ulid } from "ulid"
|
|
@@ -48,6 +47,7 @@ import { iife } from "@/util/iife"
|
|
|
48
47
|
import { Shell } from "@/shell/shell"
|
|
49
48
|
import { Truncate } from "@/tool/truncation"
|
|
50
49
|
import { UsageReporter, ActivityReporter, AnonymousTelemetry } from "@/usage"
|
|
50
|
+
import { ContextDB } from "@/context/context-db"
|
|
51
51
|
|
|
52
52
|
// @ts-ignore
|
|
53
53
|
globalThis.AI_SDK_LOG_WARNINGS = false
|
|
@@ -277,6 +277,7 @@ export namespace SessionPrompt {
|
|
|
277
277
|
using _ = defer(() => cancel(sessionID))
|
|
278
278
|
|
|
279
279
|
let step = 0
|
|
280
|
+
let l2RetrievedForUser: string | undefined
|
|
280
281
|
const session = await Session.get(sessionID)
|
|
281
282
|
while (true) {
|
|
282
283
|
SessionStatus.set(sessionID, { type: "busy" })
|
|
@@ -501,6 +502,7 @@ export namespace SessionPrompt {
|
|
|
501
502
|
abort,
|
|
502
503
|
sessionID,
|
|
503
504
|
auto: task.auto,
|
|
505
|
+
overflow: task.overflow,
|
|
504
506
|
})
|
|
505
507
|
if (result === "stop") break
|
|
506
508
|
continue
|
|
@@ -610,12 +612,32 @@ export namespace SessionPrompt {
|
|
|
610
612
|
|
|
611
613
|
await Plugin.trigger("experimental.chat.messages.transform", {}, { messages: sessionMessages })
|
|
612
614
|
|
|
615
|
+
// L2: cross-session context retrieval via ContextDB.
|
|
616
|
+
// Retrieve once per user turn (tracked by lastUser.id) to avoid
|
|
617
|
+
// redundant FTS queries during multi-step tool-call loops.
|
|
618
|
+
const needsL2 = lastUser.id !== l2RetrievedForUser
|
|
619
|
+
let l2Context: string[] = []
|
|
620
|
+
if (needsL2) {
|
|
621
|
+
const userQuery =
|
|
622
|
+
sessionMessages
|
|
623
|
+
.findLast((m) => m.info.role === "user")
|
|
624
|
+
?.parts.filter((p) => p.type === "text" && !("synthetic" in p && (p as any).synthetic))
|
|
625
|
+
.map((p) => (p as MessageV2.TextPart).text)
|
|
626
|
+
.join(" ") ?? ""
|
|
627
|
+
l2Context = await ContextDB.retrieveContext({
|
|
628
|
+
sessionID,
|
|
629
|
+
projectID: Instance.project.id,
|
|
630
|
+
userQuery,
|
|
631
|
+
})
|
|
632
|
+
l2RetrievedForUser = lastUser.id
|
|
633
|
+
}
|
|
634
|
+
|
|
613
635
|
const result = await processor.process({
|
|
614
636
|
user: lastUser,
|
|
615
637
|
agent,
|
|
616
638
|
abort,
|
|
617
639
|
sessionID,
|
|
618
|
-
system: [...(await SystemPrompt.environment(model)), ...(await InstructionPrompt.system())],
|
|
640
|
+
system: [...(await SystemPrompt.environment(model)), ...(await InstructionPrompt.system()), ...l2Context],
|
|
619
641
|
messages: [
|
|
620
642
|
...MessageV2.toModelMessages(sessionMessages, model),
|
|
621
643
|
...(isLastStep
|
|
@@ -637,6 +659,7 @@ export namespace SessionPrompt {
|
|
|
637
659
|
agent: lastUser.agent,
|
|
638
660
|
model: lastUser.model,
|
|
639
661
|
auto: true,
|
|
662
|
+
overflow: !processor.message.finish,
|
|
640
663
|
})
|
|
641
664
|
}
|
|
642
665
|
continue
|
|
@@ -832,7 +855,12 @@ export namespace SessionPrompt {
|
|
|
832
855
|
title: "",
|
|
833
856
|
metadata,
|
|
834
857
|
output: truncated.content,
|
|
835
|
-
attachments
|
|
858
|
+
attachments: attachments.map((attachment) => ({
|
|
859
|
+
...attachment,
|
|
860
|
+
id: Identifier.ascending("part"),
|
|
861
|
+
sessionID: ctx.sessionID,
|
|
862
|
+
messageID: input.processor.message.id,
|
|
863
|
+
})),
|
|
836
864
|
content: result.content, // directly return content to preserve ordering when outputting to model
|
|
837
865
|
}
|
|
838
866
|
}
|
|
@@ -1086,7 +1114,7 @@ export namespace SessionPrompt {
|
|
|
1086
1114
|
}
|
|
1087
1115
|
|
|
1088
1116
|
if (part.mime === "application/x-directory") {
|
|
1089
|
-
const args = {
|
|
1117
|
+
const args = { filePath: filepath }
|
|
1090
1118
|
const listCtx: Tool.Context = {
|
|
1091
1119
|
sessionID: input.sessionID,
|
|
1092
1120
|
abort: new AbortController().signal,
|
|
@@ -1097,7 +1125,7 @@ export namespace SessionPrompt {
|
|
|
1097
1125
|
metadata: async () => {},
|
|
1098
1126
|
ask: async () => {},
|
|
1099
1127
|
}
|
|
1100
|
-
const result = await
|
|
1128
|
+
const result = await ReadTool.init().then((t) => t.execute(args, listCtx))
|
|
1101
1129
|
return [
|
|
1102
1130
|
{
|
|
1103
1131
|
id: Identifier.ascending("part"),
|
|
@@ -1105,7 +1133,7 @@ export namespace SessionPrompt {
|
|
|
1105
1133
|
sessionID: input.sessionID,
|
|
1106
1134
|
type: "text",
|
|
1107
1135
|
synthetic: true,
|
|
1108
|
-
text: `Called the
|
|
1136
|
+
text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
|
|
1109
1137
|
},
|
|
1110
1138
|
{
|
|
1111
1139
|
id: Identifier.ascending("part"),
|
package/src/share/share-next.ts
CHANGED
|
@@ -31,7 +31,7 @@ export namespace ShareNext {
|
|
|
31
31
|
await sync(evt.properties.info.sessionID, [
|
|
32
32
|
{
|
|
33
33
|
type: "message",
|
|
34
|
-
data: evt.properties.info,
|
|
34
|
+
data: evt.properties.info as any,
|
|
35
35
|
},
|
|
36
36
|
])
|
|
37
37
|
if (evt.properties.info.role === "user") {
|
|
@@ -184,7 +184,7 @@ export namespace ShareNext {
|
|
|
184
184
|
},
|
|
185
185
|
...messages.map((x) => ({
|
|
186
186
|
type: "message" as const,
|
|
187
|
-
data: x.info,
|
|
187
|
+
data: x.info as any,
|
|
188
188
|
})),
|
|
189
189
|
...messages.flatMap((x) => x.parts.map((y) => ({ type: "part" as const, data: y }))),
|
|
190
190
|
{
|
package/src/shell/shell.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Flag } from "@/flag/flag"
|
|
2
2
|
import { lazy } from "@/util/lazy"
|
|
3
|
+
import { statSync } from "fs"
|
|
3
4
|
import path from "path"
|
|
4
5
|
import { spawn, type ChildProcess } from "child_process"
|
|
5
6
|
|
|
@@ -43,7 +44,7 @@ export namespace Shell {
|
|
|
43
44
|
// git.exe is typically at: C:\Program Files\Git\cmd\git.exe
|
|
44
45
|
// bash.exe is at: C:\Program Files\Git\bin\bash.exe
|
|
45
46
|
const bash = path.join(git, "..", "..", "bin", "bash.exe")
|
|
46
|
-
if (
|
|
47
|
+
try { if (statSync(bash).size) return bash } catch {}
|
|
47
48
|
}
|
|
48
49
|
return process.env.COMSPEC || "cmd.exe"
|
|
49
50
|
}
|
package/src/tool/edit.ts
CHANGED
|
@@ -24,6 +24,15 @@ function normalizeLineEndings(text: string): string {
|
|
|
24
24
|
return text.replaceAll("\r\n", "\n")
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function detectLineEnding(text: string): "\n" | "\r\n" {
|
|
28
|
+
return text.includes("\r\n") ? "\r\n" : "\n"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function convertToLineEnding(text: string, ending: "\n" | "\r\n"): string {
|
|
32
|
+
if (ending === "\n") return text
|
|
33
|
+
return text.replaceAll("\n", "\r\n")
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
export const EditTool = Tool.define("edit", {
|
|
28
37
|
description: DESCRIPTION,
|
|
29
38
|
parameters: z.object({
|
|
@@ -79,7 +88,12 @@ export const EditTool = Tool.define("edit", {
|
|
|
79
88
|
if (stats.isDirectory()) throw new Error(`Path is a directory, not a file: ${filePath}`)
|
|
80
89
|
await FileTime.assert(ctx.sessionID, filePath)
|
|
81
90
|
contentOld = await file.text()
|
|
82
|
-
|
|
91
|
+
|
|
92
|
+
const ending = detectLineEnding(contentOld)
|
|
93
|
+
const old = convertToLineEnding(normalizeLineEndings(params.oldString), ending)
|
|
94
|
+
const next = convertToLineEnding(normalizeLineEndings(params.newString), ending)
|
|
95
|
+
|
|
96
|
+
contentNew = replace(contentOld, old, next, params.replaceAll)
|
|
83
97
|
|
|
84
98
|
diff = trimDiff(
|
|
85
99
|
createTwoFilesPatch(filePath, filePath, normalizeLineEndings(contentOld), normalizeLineEndings(contentNew)),
|
package/src/tool/registry.ts
CHANGED
|
@@ -157,9 +157,17 @@ export namespace ToolRegistry {
|
|
|
157
157
|
})
|
|
158
158
|
.map(async (t) => {
|
|
159
159
|
using _ = log.time(t.id)
|
|
160
|
+
const tool = await t.init({ agent })
|
|
161
|
+
const output = {
|
|
162
|
+
description: tool.description,
|
|
163
|
+
parameters: tool.parameters,
|
|
164
|
+
}
|
|
165
|
+
await Plugin.trigger("tool.definition", { toolID: t.id }, output)
|
|
160
166
|
return {
|
|
161
167
|
id: t.id,
|
|
162
|
-
...
|
|
168
|
+
...tool,
|
|
169
|
+
description: output.description,
|
|
170
|
+
parameters: output.parameters,
|
|
163
171
|
}
|
|
164
172
|
}),
|
|
165
173
|
)
|
package/src/tool/truncation.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Identifier } from "../id/id"
|
|
|
5
5
|
import { PermissionNext } from "../permission/next"
|
|
6
6
|
import type { Agent } from "../agent/agent"
|
|
7
7
|
import { Scheduler } from "../scheduler"
|
|
8
|
+
import { ContextDB } from "@/context/context-db"
|
|
8
9
|
|
|
9
10
|
export namespace Truncate {
|
|
10
11
|
export const MAX_LINES = 2000
|
|
@@ -106,4 +107,20 @@ export namespace Truncate {
|
|
|
106
107
|
|
|
107
108
|
return { content: message, truncated: true, outputPath: filepath }
|
|
108
109
|
}
|
|
110
|
+
|
|
111
|
+
export function indexOutput(input: { sessionID: string; toolName: string; toolInput: string; summary: string; outputPath?: string }) {
|
|
112
|
+
const hash = new Bun.CryptoHasher("sha256").update(input.toolName + ":" + input.toolInput).digest("hex")
|
|
113
|
+
ContextDB.get()
|
|
114
|
+
.then((db) => {
|
|
115
|
+
if (!db) return
|
|
116
|
+
ContextDB.storeToolOutput({
|
|
117
|
+
sessionID: input.sessionID,
|
|
118
|
+
toolName: input.toolName,
|
|
119
|
+
inputHash: hash,
|
|
120
|
+
outputSummary: input.summary.slice(0, 2000),
|
|
121
|
+
outputPath: input.outputPath,
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
.catch(() => {})
|
|
125
|
+
}
|
|
109
126
|
}
|
package/src/tool/websearch.ts
CHANGED
|
@@ -39,7 +39,7 @@ interface McpSearchResponse {
|
|
|
39
39
|
export const WebSearchTool = Tool.define("websearch", async () => {
|
|
40
40
|
return {
|
|
41
41
|
get description() {
|
|
42
|
-
return DESCRIPTION.replace("{{
|
|
42
|
+
return DESCRIPTION.replace("{{year}}", new Date().getFullYear().toString())
|
|
43
43
|
},
|
|
44
44
|
parameters: z.object({
|
|
45
45
|
query: z.string().describe("Websearch query"),
|
package/src/tool/websearch.txt
CHANGED
|
@@ -10,5 +10,5 @@ Usage notes:
|
|
|
10
10
|
- Configurable context length for optimal LLM integration
|
|
11
11
|
- Domain filtering and advanced search options available
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
- Example: If
|
|
13
|
+
The current year is {{year}}. You MUST use this year when searching for recent information or current events
|
|
14
|
+
- Example: If the current year is 2026 and the user asks for "latest AI news", search for "AI news 2026", NOT "AI news 2025"
|
package/src/tool/write.ts
CHANGED
|
@@ -26,9 +26,8 @@ export const WriteTool = Tool.define("write", {
|
|
|
26
26
|
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(Instance.directory, params.filePath)
|
|
27
27
|
await assertExternalDirectory(ctx, filepath)
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const contentOld = exists ? await file.text() : ""
|
|
29
|
+
const exists = await Filesystem.exists(filepath)
|
|
30
|
+
const contentOld = exists ? await Filesystem.readText(filepath) : ""
|
|
32
31
|
if (exists) await FileTime.assert(ctx.sessionID, filepath)
|
|
33
32
|
|
|
34
33
|
const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, params.content))
|
|
@@ -42,7 +41,7 @@ export const WriteTool = Tool.define("write", {
|
|
|
42
41
|
},
|
|
43
42
|
})
|
|
44
43
|
|
|
45
|
-
await
|
|
44
|
+
await Filesystem.write(filepath, params.content)
|
|
46
45
|
await Bus.publish(File.Event.Edited, {
|
|
47
46
|
file: filepath,
|
|
48
47
|
})
|