@royalcat/opencode-dcp-rc 0.0.0-stage → 4.0.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/LICENSE +619 -0
- package/README.md +348 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8129 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/commands/compression-targets.d.ts +13 -0
- package/dist/lib/commands/compression-targets.d.ts.map +1 -0
- package/dist/lib/commands/context.d.ts +67 -0
- package/dist/lib/commands/context.d.ts.map +1 -0
- package/dist/lib/commands/decompress.d.ts +12 -0
- package/dist/lib/commands/decompress.d.ts.map +1 -0
- package/dist/lib/commands/help.d.ts +18 -0
- package/dist/lib/commands/help.d.ts.map +1 -0
- package/dist/lib/commands/index.d.ts +8 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/manual.d.ts +23 -0
- package/dist/lib/commands/manual.d.ts.map +1 -0
- package/dist/lib/commands/recompress.d.ts +12 -0
- package/dist/lib/commands/recompress.d.ts.map +1 -0
- package/dist/lib/commands/stats.d.ts +27 -0
- package/dist/lib/commands/stats.d.ts.map +1 -0
- package/dist/lib/commands/sweep.d.ts +23 -0
- package/dist/lib/commands/sweep.d.ts.map +1 -0
- package/dist/lib/compress/index.d.ts +3 -0
- package/dist/lib/compress/index.d.ts.map +1 -0
- package/dist/lib/compress/pipeline.d.ts +29 -0
- package/dist/lib/compress/pipeline.d.ts.map +1 -0
- package/dist/lib/compress/protected-content.d.ts +6 -0
- package/dist/lib/compress/protected-content.d.ts.map +1 -0
- package/dist/lib/compress/rc.d.ts +15 -0
- package/dist/lib/compress/rc.d.ts.map +1 -0
- package/dist/lib/compress/search.d.ts +12 -0
- package/dist/lib/compress/search.d.ts.map +1 -0
- package/dist/lib/compress/state.d.ts +10 -0
- package/dist/lib/compress/state.d.ts.map +1 -0
- package/dist/lib/compress/summary.d.ts +68 -0
- package/dist/lib/compress/summary.d.ts.map +1 -0
- package/dist/lib/compress/timing.d.ts +18 -0
- package/dist/lib/compress/timing.d.ts.map +1 -0
- package/dist/lib/compress/types.d.ts +64 -0
- package/dist/lib/compress/types.d.ts.map +1 -0
- package/dist/lib/compress/usage.d.ts +45 -0
- package/dist/lib/compress/usage.d.ts.map +1 -0
- package/dist/lib/compress-permission.d.ts +4 -0
- package/dist/lib/compress-permission.d.ts.map +1 -0
- package/dist/lib/config.d.ts +83 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/hooks.d.ts +22 -0
- package/dist/lib/hooks.d.ts.map +1 -0
- package/dist/lib/logger.d.ts +31 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/message-ids.d.ts +20 -0
- package/dist/lib/message-ids.d.ts.map +1 -0
- package/dist/lib/messages/index.d.ts +8 -0
- package/dist/lib/messages/index.d.ts.map +1 -0
- package/dist/lib/messages/inject/inject.d.ts +7 -0
- package/dist/lib/messages/inject/inject.d.ts.map +1 -0
- package/dist/lib/messages/inject/subagent-results.d.ts +4 -0
- package/dist/lib/messages/inject/subagent-results.d.ts.map +1 -0
- package/dist/lib/messages/inject/utils.d.ts +23 -0
- package/dist/lib/messages/inject/utils.d.ts.map +1 -0
- package/dist/lib/messages/prune.d.ts +4 -0
- package/dist/lib/messages/prune.d.ts.map +1 -0
- package/dist/lib/messages/query.d.ts +7 -0
- package/dist/lib/messages/query.d.ts.map +1 -0
- package/dist/lib/messages/reasoning-strip.d.ts +8 -0
- package/dist/lib/messages/reasoning-strip.d.ts.map +1 -0
- package/dist/lib/messages/shape.d.ts +5 -0
- package/dist/lib/messages/shape.d.ts.map +1 -0
- package/dist/lib/messages/sync.d.ts +4 -0
- package/dist/lib/messages/sync.d.ts.map +1 -0
- package/dist/lib/messages/utils.d.ts +27 -0
- package/dist/lib/messages/utils.d.ts.map +1 -0
- package/dist/lib/prompts/compress-rc.d.ts +4 -0
- package/dist/lib/prompts/compress-rc.d.ts.map +1 -0
- package/dist/lib/prompts/context-limit-nudge.d.ts +2 -0
- package/dist/lib/prompts/context-limit-nudge.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/nudge.d.ts +4 -0
- package/dist/lib/prompts/extensions/nudge.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/system.d.ts +4 -0
- package/dist/lib/prompts/extensions/system.d.ts.map +1 -0
- package/dist/lib/prompts/extensions/tool.d.ts +3 -0
- package/dist/lib/prompts/extensions/tool.d.ts.map +1 -0
- package/dist/lib/prompts/index.d.ts +4 -0
- package/dist/lib/prompts/index.d.ts.map +1 -0
- package/dist/lib/prompts/iteration-nudge.d.ts +2 -0
- package/dist/lib/prompts/iteration-nudge.d.ts.map +1 -0
- package/dist/lib/prompts/store.d.ts +26 -0
- package/dist/lib/prompts/store.d.ts.map +1 -0
- package/dist/lib/prompts/system.d.ts +4 -0
- package/dist/lib/prompts/system.d.ts.map +1 -0
- package/dist/lib/prompts/turn-nudge.d.ts +2 -0
- package/dist/lib/prompts/turn-nudge.d.ts.map +1 -0
- package/dist/lib/protected-patterns.d.ts +6 -0
- package/dist/lib/protected-patterns.d.ts.map +1 -0
- package/dist/lib/state/index.d.ts +5 -0
- package/dist/lib/state/index.d.ts.map +1 -0
- package/dist/lib/state/persistence.d.ts +47 -0
- package/dist/lib/state/persistence.d.ts.map +1 -0
- package/dist/lib/state/state.d.ts +9 -0
- package/dist/lib/state/state.d.ts.map +1 -0
- package/dist/lib/state/tool-cache.d.ts +13 -0
- package/dist/lib/state/tool-cache.d.ts.map +1 -0
- package/dist/lib/state/types.d.ts +105 -0
- package/dist/lib/state/types.d.ts.map +1 -0
- package/dist/lib/state/utils.d.ts +22 -0
- package/dist/lib/state/utils.d.ts.map +1 -0
- package/dist/lib/strategies/deduplication.d.ts +10 -0
- package/dist/lib/strategies/deduplication.d.ts.map +1 -0
- package/dist/lib/strategies/index.d.ts +3 -0
- package/dist/lib/strategies/index.d.ts.map +1 -0
- package/dist/lib/strategies/purge-errors.d.ts +13 -0
- package/dist/lib/strategies/purge-errors.d.ts.map +1 -0
- package/dist/lib/subagents/subagent-results.d.ts +5 -0
- package/dist/lib/subagents/subagent-results.d.ts.map +1 -0
- package/dist/lib/token-utils.d.ts +19 -0
- package/dist/lib/token-utils.d.ts.map +1 -0
- package/dist/lib/tui/dialogs.d.ts +29 -0
- package/dist/lib/tui/dialogs.d.ts.map +1 -0
- package/dist/lib/tui/format.d.ts +4 -0
- package/dist/lib/tui/format.d.ts.map +1 -0
- package/dist/lib/tui/types.d.ts +18 -0
- package/dist/lib/tui/types.d.ts.map +1 -0
- package/dist/lib/tui/ui.d.ts +48 -0
- package/dist/lib/tui/ui.d.ts.map +1 -0
- package/dist/lib/ui/notification.d.ts +17 -0
- package/dist/lib/ui/notification.d.ts.map +1 -0
- package/dist/lib/ui/utils.d.ts +10 -0
- package/dist/lib/ui/utils.d.ts.map +1 -0
- package/dist/lib/v2/index.d.ts +5 -0
- package/dist/lib/v2/index.d.ts.map +1 -0
- package/dist/lib/v2/messages.d.ts +13 -0
- package/dist/lib/v2/messages.d.ts.map +1 -0
- package/dist/lib/v2/rpc.d.ts +75 -0
- package/dist/lib/v2/rpc.d.ts.map +1 -0
- package/dist/lib/v2/theme.d.ts +55 -0
- package/dist/lib/v2/theme.d.ts.map +1 -0
- package/dist/lib/v2/tui.d.ts +4 -0
- package/dist/lib/v2/tui.d.ts.map +1 -0
- package/dist/lib/v2/usage.d.ts +44 -0
- package/dist/lib/v2/usage.d.ts.map +1 -0
- package/dist/tui.d.ts +7 -0
- package/dist/tui.d.ts.map +1 -0
- package/lib/commands/compression-targets.ts +54 -0
- package/lib/commands/context.ts +305 -0
- package/lib/commands/decompress.ts +273 -0
- package/lib/commands/help.ts +76 -0
- package/lib/commands/index.ts +11 -0
- package/lib/commands/manual.ts +126 -0
- package/lib/commands/recompress.ts +222 -0
- package/lib/commands/stats.ts +180 -0
- package/lib/commands/sweep.ts +266 -0
- package/lib/compress/index.ts +2 -0
- package/lib/compress/pipeline.ts +116 -0
- package/lib/compress/protected-content.ts +163 -0
- package/lib/compress/rc.ts +494 -0
- package/lib/compress/search.ts +277 -0
- package/lib/compress/state.ts +272 -0
- package/lib/compress/summary.ts +320 -0
- package/lib/compress/timing.ts +77 -0
- package/lib/compress/types.ts +70 -0
- package/lib/compress/usage.ts +168 -0
- package/lib/compress-permission.ts +9 -0
- package/lib/config.ts +995 -0
- package/lib/hooks.ts +205 -0
- package/lib/logger.ts +226 -0
- package/lib/message-ids.ts +181 -0
- package/lib/messages/index.ts +7 -0
- package/lib/messages/inject/inject.ts +215 -0
- package/lib/messages/inject/subagent-results.ts +84 -0
- package/lib/messages/inject/utils.ts +304 -0
- package/lib/messages/prune.ts +238 -0
- package/lib/messages/query.ts +71 -0
- package/lib/messages/reasoning-strip.ts +40 -0
- package/lib/messages/shape.ts +50 -0
- package/lib/messages/sync.ts +124 -0
- package/lib/messages/utils.ts +190 -0
- package/lib/prompts/compress-rc.ts +25 -0
- package/lib/prompts/context-limit-nudge.ts +18 -0
- package/lib/prompts/extensions/nudge.ts +34 -0
- package/lib/prompts/extensions/system.ts +32 -0
- package/lib/prompts/extensions/tool.ts +21 -0
- package/lib/prompts/index.ts +29 -0
- package/lib/prompts/iteration-nudge.ts +6 -0
- package/lib/prompts/store.ts +452 -0
- package/lib/prompts/system.ts +41 -0
- package/lib/prompts/turn-nudge.ts +10 -0
- package/lib/protected-patterns.ts +159 -0
- package/lib/state/index.ts +4 -0
- package/lib/state/persistence.ts +321 -0
- package/lib/state/state.ts +214 -0
- package/lib/state/tool-cache.ts +99 -0
- package/lib/state/types.ts +117 -0
- package/lib/state/utils.ts +345 -0
- package/lib/strategies/deduplication.ts +125 -0
- package/lib/strategies/index.ts +2 -0
- package/lib/strategies/purge-errors.ts +86 -0
- package/lib/subagents/subagent-results.ts +75 -0
- package/lib/token-utils.ts +164 -0
- package/lib/tui/dialogs.tsx +261 -0
- package/lib/tui/format.ts +25 -0
- package/lib/tui/types.ts +25 -0
- package/lib/tui/ui.tsx +245 -0
- package/lib/ui/notification.ts +347 -0
- package/lib/ui/utils.ts +304 -0
- package/lib/v2/index.ts +448 -0
- package/lib/v2/messages.ts +252 -0
- package/lib/v2/rpc.ts +60 -0
- package/lib/v2/theme.ts +67 -0
- package/lib/v2/tui.tsx +115 -0
- package/lib/v2/usage.ts +423 -0
- package/package.json +91 -5
- package/server.js +1 -0
- package/tui.tsx +7 -0
package/lib/v2/index.ts
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode/plugin"
|
|
2
|
+
import { tool, type ToolDefinition } from "@opencode-ai/plugin"
|
|
3
|
+
import { getConfig } from "../config"
|
|
4
|
+
import { Logger } from "../logger"
|
|
5
|
+
import { PromptStore } from "../prompts/store"
|
|
6
|
+
import { createRcCompressTool } from "../compress"
|
|
7
|
+
import { extractSummaryCallId, hasSummaryMarker } from "../compress/summary"
|
|
8
|
+
import { attachCompressionDuration } from "../compress/state"
|
|
9
|
+
import {
|
|
10
|
+
createCompressionUsageTracker,
|
|
11
|
+
installCompressionUsageHook,
|
|
12
|
+
installHttpUsageHook,
|
|
13
|
+
} from "./usage"
|
|
14
|
+
import { createCommandExecuteHandler, createSystemPromptHandler } from "../hooks"
|
|
15
|
+
import {
|
|
16
|
+
createSessionState,
|
|
17
|
+
ensureSessionInitialized,
|
|
18
|
+
checkSession,
|
|
19
|
+
saveSessionState,
|
|
20
|
+
syncToolCache,
|
|
21
|
+
type SessionState,
|
|
22
|
+
} from "../state"
|
|
23
|
+
import { assignMessageRefs } from "../message-ids"
|
|
24
|
+
import { applyPendingManualTrigger } from "../commands/manual"
|
|
25
|
+
import {
|
|
26
|
+
buildToolIdList,
|
|
27
|
+
injectCompressNudges,
|
|
28
|
+
injectMessageIds,
|
|
29
|
+
injectExtendedSubAgentResults,
|
|
30
|
+
prune,
|
|
31
|
+
stripHallucinations,
|
|
32
|
+
syncCompressionBlocks,
|
|
33
|
+
} from "../messages"
|
|
34
|
+
import { countTokens } from "../token-utils"
|
|
35
|
+
import { matchesGlob } from "../protected-patterns"
|
|
36
|
+
import { history, project } from "./messages"
|
|
37
|
+
import { analyzeContextTokens } from "../commands/context"
|
|
38
|
+
import { buildStatsReport } from "../commands/stats"
|
|
39
|
+
import { rpc } from "./rpc"
|
|
40
|
+
|
|
41
|
+
// Extension point for future model-invisible V2 reports. Never use synthetic()
|
|
42
|
+
// here: its text would enter the model's context, unlike V1 ignored messages.
|
|
43
|
+
export async function report(logger: Logger, text: string, sessionID?: string) {
|
|
44
|
+
logger.debug("V2 report (display pending)", { sessionID, text })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function setup(ctx: Plugin.Context) {
|
|
48
|
+
const warnings = {
|
|
49
|
+
tui: {
|
|
50
|
+
showToast: async (input: { body: { message: string } }) => {
|
|
51
|
+
console.warn(`DCP: ${input.body.message}`)
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
const config = getConfig({ directory: ctx.location.directory, client: warnings })
|
|
56
|
+
if (!config.enabled) {
|
|
57
|
+
await ctx.rpc.register(
|
|
58
|
+
{ ...rpc, methods: { status: rpc.methods.status } },
|
|
59
|
+
{ status: async () => ({ enabled: false }) },
|
|
60
|
+
)
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
const logger = new Logger(config.debug)
|
|
64
|
+
const usageTracker = createCompressionUsageTracker()
|
|
65
|
+
await installCompressionUsageHook(ctx, usageTracker, logger)
|
|
66
|
+
await installHttpUsageHook(ctx, usageTracker, logger)
|
|
67
|
+
const prompts = new PromptStore(
|
|
68
|
+
logger,
|
|
69
|
+
ctx.location.directory,
|
|
70
|
+
config.experimental.customPrompts,
|
|
71
|
+
"compact",
|
|
72
|
+
)
|
|
73
|
+
const sessions = new Map<string, SessionState>()
|
|
74
|
+
const queues = new Map<string, Promise<unknown>>()
|
|
75
|
+
const limits = new Map<string, number>()
|
|
76
|
+
const aliases: Record<string, string> = {
|
|
77
|
+
task: "subagent",
|
|
78
|
+
bash: "shell",
|
|
79
|
+
apply_patch: "patch",
|
|
80
|
+
}
|
|
81
|
+
for (const list of [
|
|
82
|
+
config.compress.protectedTools,
|
|
83
|
+
config.commands.protectedTools,
|
|
84
|
+
config.strategies.deduplication.protectedTools,
|
|
85
|
+
config.strategies.purgeErrors.protectedTools,
|
|
86
|
+
]) {
|
|
87
|
+
for (const name of [...list])
|
|
88
|
+
if (aliases[name] && !list.includes(aliases[name]!)) list.push(aliases[name]!)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function serial<T>(sessionID: string, operation: () => Promise<T>): Promise<T> {
|
|
92
|
+
const pending = (queues.get(sessionID) ?? Promise.resolve()).catch(() => {}).then(operation)
|
|
93
|
+
queues.set(sessionID, pending)
|
|
94
|
+
void pending
|
|
95
|
+
.finally(() => {
|
|
96
|
+
if (queues.get(sessionID) === pending) queues.delete(sessionID)
|
|
97
|
+
})
|
|
98
|
+
.catch(() => {})
|
|
99
|
+
return pending
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const client = {
|
|
103
|
+
session: {
|
|
104
|
+
get: async ({ path }: { path: { id: string } }) => ({
|
|
105
|
+
data: await ctx.session.get({ sessionID: path.id }),
|
|
106
|
+
}),
|
|
107
|
+
messages: async ({ path }: { path: { id: string } }) => {
|
|
108
|
+
const [entries, session] = await Promise.all([
|
|
109
|
+
ctx.session.context({ sessionID: path.id }),
|
|
110
|
+
ctx.session.get({ sessionID: path.id }),
|
|
111
|
+
])
|
|
112
|
+
return { data: history(entries, session) }
|
|
113
|
+
},
|
|
114
|
+
prompt: async (input: {
|
|
115
|
+
path: { id: string }
|
|
116
|
+
body: { parts: Array<{ text: string }> }
|
|
117
|
+
}) =>
|
|
118
|
+
report(logger, input.body.parts.map((part) => part.text).join("\n"), input.path.id),
|
|
119
|
+
},
|
|
120
|
+
tui: {
|
|
121
|
+
showToast: async (input: { body: { message: string } }) =>
|
|
122
|
+
report(logger, input.body.message),
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function load(sessionID: string, agentID?: string) {
|
|
127
|
+
const [session, entries] = await Promise.all([
|
|
128
|
+
ctx.session.get({ sessionID }),
|
|
129
|
+
ctx.session.context({ sessionID }),
|
|
130
|
+
])
|
|
131
|
+
const selected =
|
|
132
|
+
agentID ??
|
|
133
|
+
session.agent ??
|
|
134
|
+
entries.findLast((entry) => entry.type === "assistant")?.agent
|
|
135
|
+
if (!selected) throw new Error("DCP commands require a session with a selected agent")
|
|
136
|
+
const { data: agent } = await ctx.agent.get({ agentID: selected })
|
|
137
|
+
let state = sessions.get(sessionID)
|
|
138
|
+
if (!state) {
|
|
139
|
+
state = createSessionState("compact")
|
|
140
|
+
sessions.set(sessionID, state)
|
|
141
|
+
}
|
|
142
|
+
const messages = history(entries, session)
|
|
143
|
+
await ensureSessionInitialized(
|
|
144
|
+
client,
|
|
145
|
+
state,
|
|
146
|
+
sessionID,
|
|
147
|
+
logger,
|
|
148
|
+
messages,
|
|
149
|
+
config.manualMode.enabled,
|
|
150
|
+
)
|
|
151
|
+
await checkSession(client, state, logger, messages, config.manualMode.enabled)
|
|
152
|
+
const rule = [...agent.permissions, ...(session.permissions ?? [])].findLast(
|
|
153
|
+
(rule) => matchesGlob("compress", rule.action) && matchesGlob("*", rule.resource),
|
|
154
|
+
)
|
|
155
|
+
state.compressPermission =
|
|
156
|
+
config.compress.permission === "deny"
|
|
157
|
+
? "deny"
|
|
158
|
+
: rule?.effect === "deny"
|
|
159
|
+
? "deny"
|
|
160
|
+
: config.compress.permission === "ask" || rule?.effect === "ask"
|
|
161
|
+
? "ask"
|
|
162
|
+
: "allow"
|
|
163
|
+
return { state, entries, session, messages }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function allowed(state: SessionState) {
|
|
167
|
+
if (state.isSubAgent && !config.experimental.allowSubAgents)
|
|
168
|
+
throw new Error("DCP compression is disabled in subagents")
|
|
169
|
+
if (state.compressPermission === "deny") throw new Error("DCP compression is denied")
|
|
170
|
+
if (state.compressPermission === "ask")
|
|
171
|
+
throw new Error(
|
|
172
|
+
"DCP: compress permission 'ask' is not supported by OpenCode V2's public plugin API yet. Compression was not performed.",
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
await ctx.model.transform((editor) => {
|
|
177
|
+
limits.clear()
|
|
178
|
+
for (const model of editor.list())
|
|
179
|
+
limits.set(`${model.providerID}/${model.id}`, model.limit.context)
|
|
180
|
+
})
|
|
181
|
+
for (const kind of ["context", "compaction"] as const)
|
|
182
|
+
await ctx.session.hook(kind, (event) =>
|
|
183
|
+
serial(event.sessionID, async () => {
|
|
184
|
+
const { state, entries, session, messages } = await load(
|
|
185
|
+
event.sessionID,
|
|
186
|
+
event.agent,
|
|
187
|
+
)
|
|
188
|
+
if (state.isSubAgent && !config.experimental.allowSubAgents) {
|
|
189
|
+
delete event.tools.compress
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
if (state.compressPermission === "deny") delete event.tools.compress
|
|
193
|
+
state.modelContextLimit = limits.get(`${event.model.providerID}/${event.model.id}`)
|
|
194
|
+
state.systemPromptTokens = countTokens(
|
|
195
|
+
event.system.map((part) => part.text).join("\n"),
|
|
196
|
+
)
|
|
197
|
+
const view = project(event.messages, entries, {
|
|
198
|
+
...session,
|
|
199
|
+
agent: event.agent,
|
|
200
|
+
model: event.model,
|
|
201
|
+
})
|
|
202
|
+
stripHallucinations(view.messages, state.idFormat)
|
|
203
|
+
assignMessageRefs(state, view.messages)
|
|
204
|
+
// Compaction may select only a prefix; block origins can be in the retained tail.
|
|
205
|
+
syncCompressionBlocks(state, logger, messages)
|
|
206
|
+
syncToolCache(state, config, logger, view.messages)
|
|
207
|
+
buildToolIdList(state, view.messages)
|
|
208
|
+
prune(state, logger, view.messages, view.summaryBase)
|
|
209
|
+
await injectExtendedSubAgentResults(
|
|
210
|
+
client,
|
|
211
|
+
state,
|
|
212
|
+
logger,
|
|
213
|
+
view.messages,
|
|
214
|
+
config.experimental.allowSubAgents,
|
|
215
|
+
)
|
|
216
|
+
prompts.reload()
|
|
217
|
+
injectCompressNudges(
|
|
218
|
+
state,
|
|
219
|
+
config,
|
|
220
|
+
logger,
|
|
221
|
+
view.messages,
|
|
222
|
+
prompts.getRuntimePrompts(),
|
|
223
|
+
kind === "context",
|
|
224
|
+
)
|
|
225
|
+
injectMessageIds(state, config, view.messages)
|
|
226
|
+
applyPendingManualTrigger(state, view.messages, logger)
|
|
227
|
+
event.messages = view.restore()
|
|
228
|
+
const system = { system: event.system.map((part) => part.text) }
|
|
229
|
+
await createSystemPromptHandler(
|
|
230
|
+
state,
|
|
231
|
+
logger,
|
|
232
|
+
config,
|
|
233
|
+
prompts,
|
|
234
|
+
)(
|
|
235
|
+
{
|
|
236
|
+
sessionID: event.sessionID,
|
|
237
|
+
model: { limit: { context: state.modelContextLimit ?? 0 } },
|
|
238
|
+
},
|
|
239
|
+
system,
|
|
240
|
+
)
|
|
241
|
+
event.system = system.system.map((text, index) => ({
|
|
242
|
+
...event.system[index],
|
|
243
|
+
type: "text",
|
|
244
|
+
text,
|
|
245
|
+
}))
|
|
246
|
+
await logger.saveContext(event.sessionID, view.messages)
|
|
247
|
+
}),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
// Hidden summary generation for rc mode: when our marker is present, drop
|
|
251
|
+
// the session context and every tool so the transient request contains
|
|
252
|
+
// only the prompt we built. This keeps the summarization request invisible
|
|
253
|
+
// and independent of any injected metadata.
|
|
254
|
+
await ctx.session.hook("generate", (event) => {
|
|
255
|
+
const own = event.messages.filter(
|
|
256
|
+
(message) =>
|
|
257
|
+
Array.isArray(message.content) &&
|
|
258
|
+
message.content.some(
|
|
259
|
+
(part: any) =>
|
|
260
|
+
part?.type === "text" &&
|
|
261
|
+
typeof part.text === "string" &&
|
|
262
|
+
hasSummaryMarker(part.text),
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
if (own.length === 0) {
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
event.messages = own as typeof event.messages
|
|
269
|
+
for (const name of Object.keys(event.tools ?? {})) {
|
|
270
|
+
delete (event.tools as Record<string, unknown>)[name]
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Local fallback estimate of the request input, used only when the
|
|
274
|
+
// provider usage cannot be captured from the language model wrapper.
|
|
275
|
+
const callId = own
|
|
276
|
+
.flatMap((message) =>
|
|
277
|
+
Array.isArray(message.content) ? (message.content as any[]) : [],
|
|
278
|
+
)
|
|
279
|
+
.filter((part) => part?.type === "text" && typeof part.text === "string")
|
|
280
|
+
.map((part) => extractSummaryCallId(part.text))
|
|
281
|
+
.find((id): id is string => !!id)
|
|
282
|
+
if (callId) {
|
|
283
|
+
const texts: string[] = []
|
|
284
|
+
for (const part of event.system ?? []) {
|
|
285
|
+
if (typeof (part as any)?.text === "string") {
|
|
286
|
+
texts.push((part as any).text)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
for (const message of own) {
|
|
290
|
+
for (const part of (message.content as any[]) ?? []) {
|
|
291
|
+
if (part?.type === "text" && typeof part.text === "string") {
|
|
292
|
+
texts.push(part.text)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
usageTracker.recordEstimatedInput(callId, countTokens(texts.join("\n")))
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
if (config.compress.permission !== "deny") {
|
|
301
|
+
const define = (state: SessionState): ToolDefinition => {
|
|
302
|
+
const context = {
|
|
303
|
+
client,
|
|
304
|
+
state,
|
|
305
|
+
logger,
|
|
306
|
+
config,
|
|
307
|
+
prompts,
|
|
308
|
+
generate: async (input: { sessionID: string; prompt: string; callId?: string }) => {
|
|
309
|
+
try {
|
|
310
|
+
const result = await ctx.session.generate({
|
|
311
|
+
sessionID: input.sessionID,
|
|
312
|
+
prompt: input.prompt,
|
|
313
|
+
})
|
|
314
|
+
const text = result?.text ?? ""
|
|
315
|
+
const usage = input.callId
|
|
316
|
+
? usageTracker.resolve(input.callId, text)
|
|
317
|
+
: undefined
|
|
318
|
+
return { text, usage }
|
|
319
|
+
} finally {
|
|
320
|
+
if (input.callId) {
|
|
321
|
+
usageTracker.discard(input.callId)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
}
|
|
326
|
+
return createRcCompressTool(context)
|
|
327
|
+
}
|
|
328
|
+
const definition = define(createSessionState("compact"))
|
|
329
|
+
await ctx.tool.transform((editor) =>
|
|
330
|
+
editor.add({
|
|
331
|
+
name: "compress",
|
|
332
|
+
description: definition.description,
|
|
333
|
+
input: tool.schema.object(definition.args),
|
|
334
|
+
options: { codemode: false, permission: "compress" },
|
|
335
|
+
execute: (input, context) =>
|
|
336
|
+
serial(context.sessionID, async () => {
|
|
337
|
+
const { state } = await load(context.sessionID, context.agent)
|
|
338
|
+
allowed(state)
|
|
339
|
+
const started = Date.now()
|
|
340
|
+
const legacy = define(state)
|
|
341
|
+
const content = await legacy.execute(input, {
|
|
342
|
+
sessionID: context.sessionID,
|
|
343
|
+
messageID: context.messageID,
|
|
344
|
+
callID: context.id,
|
|
345
|
+
agent: context.agent,
|
|
346
|
+
directory: ctx.location.directory,
|
|
347
|
+
worktree: ctx.location.directory,
|
|
348
|
+
abort: new AbortController().signal,
|
|
349
|
+
ask: async () => allowed(state),
|
|
350
|
+
metadata: ({ title }: { title?: string }) => {
|
|
351
|
+
void context.progress({ title })
|
|
352
|
+
},
|
|
353
|
+
} as Parameters<typeof legacy.execute>[1])
|
|
354
|
+
attachCompressionDuration(
|
|
355
|
+
state.prune.messages,
|
|
356
|
+
context.messageID,
|
|
357
|
+
context.id,
|
|
358
|
+
Date.now() - started,
|
|
359
|
+
)
|
|
360
|
+
await saveSessionState(state, logger)
|
|
361
|
+
// The rc executor returns the tool result text.
|
|
362
|
+
return { content: content as string }
|
|
363
|
+
}),
|
|
364
|
+
}),
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
if (config.commands.enabled)
|
|
368
|
+
await ctx.command.transform((editor) => {
|
|
369
|
+
for (const name of ["dcp", "dcp-compress"])
|
|
370
|
+
editor.add({
|
|
371
|
+
name,
|
|
372
|
+
description: name === "dcp" ? "DCP commands" : "Trigger DCP manual compression",
|
|
373
|
+
execute: async (invocation) => {
|
|
374
|
+
const prompt = await serial(invocation.sessionID, async () => {
|
|
375
|
+
const { state } = await load(invocation.sessionID)
|
|
376
|
+
const permission =
|
|
377
|
+
state.compressPermission ?? config.compress.permission
|
|
378
|
+
if (
|
|
379
|
+
name === "dcp-compress" ||
|
|
380
|
+
invocation.prompt.text.trim().split(/\s+/)[0] === "compress"
|
|
381
|
+
)
|
|
382
|
+
allowed(state)
|
|
383
|
+
const output = { parts: [] }
|
|
384
|
+
await createCommandExecuteHandler(
|
|
385
|
+
client,
|
|
386
|
+
state,
|
|
387
|
+
logger,
|
|
388
|
+
config,
|
|
389
|
+
ctx.location.directory,
|
|
390
|
+
)(
|
|
391
|
+
{
|
|
392
|
+
command: name,
|
|
393
|
+
sessionID: invocation.sessionID,
|
|
394
|
+
arguments: invocation.prompt.text,
|
|
395
|
+
},
|
|
396
|
+
output,
|
|
397
|
+
)
|
|
398
|
+
state.compressPermission = permission
|
|
399
|
+
const pending = state.pendingManualTrigger
|
|
400
|
+
if (!pending) return
|
|
401
|
+
allowed(state)
|
|
402
|
+
state.pendingManualTrigger = null
|
|
403
|
+
return pending.prompt
|
|
404
|
+
})
|
|
405
|
+
if (prompt)
|
|
406
|
+
await ctx.session.prompt({
|
|
407
|
+
sessionID: invocation.sessionID,
|
|
408
|
+
text: prompt,
|
|
409
|
+
delivery: invocation.delivery,
|
|
410
|
+
})
|
|
411
|
+
},
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
await ctx.rpc.register(rpc, {
|
|
415
|
+
status: async () => ({ enabled: config.commands.enabled }),
|
|
416
|
+
snapshot: ({ sessionID }) =>
|
|
417
|
+
serial(sessionID, async () => {
|
|
418
|
+
const { state, messages } = await load(sessionID)
|
|
419
|
+
syncCompressionBlocks(state, logger, messages)
|
|
420
|
+
return {
|
|
421
|
+
manualMode: !!state.manualMode,
|
|
422
|
+
canCompress:
|
|
423
|
+
state.compressPermission === "allow" &&
|
|
424
|
+
(!state.isSubAgent || config.experimental.allowSubAgents),
|
|
425
|
+
...(state.compressPermission === "ask"
|
|
426
|
+
? {
|
|
427
|
+
blockedReason:
|
|
428
|
+
"Permission 'ask' is not supported by the V2 plugin API yet.",
|
|
429
|
+
}
|
|
430
|
+
: {}),
|
|
431
|
+
context: analyzeContextTokens(state, messages),
|
|
432
|
+
stats: await buildStatsReport(state, logger),
|
|
433
|
+
}
|
|
434
|
+
}),
|
|
435
|
+
manual: ({ sessionID, enabled }) =>
|
|
436
|
+
serial(sessionID, async () => {
|
|
437
|
+
const { state } = await load(sessionID)
|
|
438
|
+
state.manualMode = enabled ? "active" : false
|
|
439
|
+
await saveSessionState(state, logger)
|
|
440
|
+
return {}
|
|
441
|
+
}),
|
|
442
|
+
})
|
|
443
|
+
logger.info("DCP V2 initialized")
|
|
444
|
+
return () => {
|
|
445
|
+
sessions.clear()
|
|
446
|
+
limits.clear()
|
|
447
|
+
}
|
|
448
|
+
}
|