@otto-assistant/bridge 0.4.93 → 0.4.97
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/dist/anthropic-account-identity.js +62 -0
- package/dist/anthropic-account-identity.test.js +38 -0
- package/dist/anthropic-auth-plugin.js +88 -16
- package/dist/anthropic-auth-state.js +28 -3
- package/dist/{anthropic-auth-plugin.test.js → anthropic-auth-state.test.js} +21 -2
- package/dist/cli-parsing.test.js +12 -9
- package/dist/cli.js +23 -10
- package/dist/context-awareness-plugin.js +1 -1
- package/dist/context-awareness-plugin.test.js +2 -2
- package/dist/discord-command-registration.js +2 -2
- package/dist/discord-utils.js +5 -2
- package/dist/kimaki-opencode-plugin.js +1 -0
- package/dist/logger.js +8 -2
- package/dist/session-handler/thread-session-runtime.js +18 -1
- package/dist/system-message.js +1 -1
- package/dist/system-message.test.js +1 -1
- package/dist/system-prompt-drift-plugin.js +251 -0
- package/dist/utils.js +5 -1
- package/dist/worktrees.js +0 -33
- package/package.json +2 -1
- package/src/anthropic-account-identity.test.ts +52 -0
- package/src/anthropic-account-identity.ts +77 -0
- package/src/anthropic-auth-plugin.ts +97 -16
- package/src/{anthropic-auth-plugin.test.ts → anthropic-auth-state.test.ts} +23 -1
- package/src/anthropic-auth-state.ts +36 -3
- package/src/cli-parsing.test.ts +16 -9
- package/src/cli.ts +29 -11
- package/src/context-awareness-plugin.test.ts +2 -2
- package/src/context-awareness-plugin.ts +1 -1
- package/src/discord-command-registration.ts +2 -2
- package/src/discord-utils.ts +19 -17
- package/src/kimaki-opencode-plugin.ts +1 -0
- package/src/logger.ts +9 -2
- package/src/session-handler/thread-session-runtime.ts +21 -1
- package/src/system-message.test.ts +1 -1
- package/src/system-message.ts +1 -1
- package/src/system-prompt-drift-plugin.ts +379 -0
- package/src/utils.ts +5 -1
- package/src/worktrees.test.ts +1 -0
- package/src/worktrees.ts +1 -47
|
@@ -137,6 +137,17 @@ import { extractLeadingOpencodeCommand } from '../opencode-command-detection.js'
|
|
|
137
137
|
const logger = createLogger(LogPrefix.SESSION)
|
|
138
138
|
const discordLogger = createLogger(LogPrefix.DISCORD)
|
|
139
139
|
const DETERMINISTIC_CONTEXT_LIMIT = 100_000
|
|
140
|
+
const TOAST_SESSION_ID_REGEX = /\b(ses_[A-Za-z0-9]+)\b\s*$/u
|
|
141
|
+
|
|
142
|
+
function extractToastSessionId({ message }: { message: string }): string | undefined {
|
|
143
|
+
const match = message.match(TOAST_SESSION_ID_REGEX)
|
|
144
|
+
return match?.[1]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function stripToastSessionId({ message }: { message: string }): string {
|
|
148
|
+
return message.replace(TOAST_SESSION_ID_REGEX, '').trimEnd()
|
|
149
|
+
}
|
|
150
|
+
|
|
140
151
|
const shouldLogSessionEvents =
|
|
141
152
|
process.env['KIMAKI_LOG_SESSION_EVENTS'] === '1' ||
|
|
142
153
|
process.env['KIMAKI_VITEST'] === '1'
|
|
@@ -1381,6 +1392,9 @@ export class ThreadSessionRuntime {
|
|
|
1381
1392
|
const sessionId = this.state?.sessionId
|
|
1382
1393
|
|
|
1383
1394
|
const eventSessionId = getOpencodeEventSessionId(event)
|
|
1395
|
+
const toastSessionId = event.type === 'tui.toast.show'
|
|
1396
|
+
? extractToastSessionId({ message: event.properties.message })
|
|
1397
|
+
: undefined
|
|
1384
1398
|
|
|
1385
1399
|
if (shouldLogSessionEvents) {
|
|
1386
1400
|
const eventDetails = (() => {
|
|
@@ -1412,6 +1426,7 @@ export class ThreadSessionRuntime {
|
|
|
1412
1426
|
}
|
|
1413
1427
|
|
|
1414
1428
|
const isGlobalEvent = event.type === 'tui.toast.show'
|
|
1429
|
+
const isScopedToastEvent = Boolean(toastSessionId)
|
|
1415
1430
|
|
|
1416
1431
|
// Drop events that don't match current session (stale events from
|
|
1417
1432
|
// previous sessions), unless it's a global event or a subtask session.
|
|
@@ -1420,6 +1435,11 @@ export class ThreadSessionRuntime {
|
|
|
1420
1435
|
return // stale event from previous session
|
|
1421
1436
|
}
|
|
1422
1437
|
}
|
|
1438
|
+
if (isScopedToastEvent && toastSessionId !== sessionId) {
|
|
1439
|
+
if (!this.getSubtaskInfoForSession(toastSessionId!)) {
|
|
1440
|
+
return
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1423
1443
|
|
|
1424
1444
|
if (isOpencodeSessionEventLogEnabled()) {
|
|
1425
1445
|
const eventLogResult = await appendOpencodeSessionEventLog({
|
|
@@ -2763,7 +2783,7 @@ export class ThreadSessionRuntime {
|
|
|
2763
2783
|
if (properties.variant === 'warning') {
|
|
2764
2784
|
return
|
|
2765
2785
|
}
|
|
2766
|
-
const toastMessage = properties.message.trim()
|
|
2786
|
+
const toastMessage = stripToastSessionId({ message: properties.message }).trim()
|
|
2767
2787
|
if (!toastMessage) {
|
|
2768
2788
|
return
|
|
2769
2789
|
}
|
|
@@ -224,7 +224,7 @@ describe('system-message', () => {
|
|
|
224
224
|
|
|
225
225
|
This creates a new Discord thread with an isolated git worktree and starts a session in it. The worktree name should be kebab-case and descriptive of the task.
|
|
226
226
|
|
|
227
|
-
By default, worktrees are created from \`
|
|
227
|
+
By default, worktrees are created from \`HEAD\`, which means whatever commit or branch the current checkout is on. If you want a different base, pass \`--base-branch\` or use the slash command option explicitly.
|
|
228
228
|
|
|
229
229
|
Critical recursion guard:
|
|
230
230
|
- If you already are in a worktree thread, do not create another worktree unless the user explicitly asks for a nested worktree.
|
package/src/system-message.ts
CHANGED
|
@@ -559,7 +559,7 @@ kimaki send --channel ${channelId} --prompt "your task description" --worktree w
|
|
|
559
559
|
|
|
560
560
|
This creates a new Discord thread with an isolated git worktree and starts a session in it. The worktree name should be kebab-case and descriptive of the task.
|
|
561
561
|
|
|
562
|
-
By default, worktrees are created from \`
|
|
562
|
+
By default, worktrees are created from \`HEAD\`, which means whatever commit or branch the current checkout is on. If you want a different base, pass \`--base-branch\` or use the slash command option explicitly.
|
|
563
563
|
|
|
564
564
|
Critical recursion guard:
|
|
565
565
|
- If you already are in a worktree thread, do not create another worktree unless the user explicitly asks for a nested worktree.
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
// OpenCode plugin that detects per-session system prompt drift across turns.
|
|
2
|
+
// When the effective system prompt changes after the first user message, it
|
|
3
|
+
// writes a debug diff file and shows a toast because prompt-cache invalidation
|
|
4
|
+
// increases rate-limit usage and usually means another plugin is mutating the
|
|
5
|
+
// system prompt unexpectedly.
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs'
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import type { Plugin } from '@opencode-ai/plugin'
|
|
10
|
+
import { createPatch, diffLines } from 'diff'
|
|
11
|
+
import * as errore from 'errore'
|
|
12
|
+
import { createPluginLogger, formatPluginErrorWithStack, setPluginLogFilePath } from './plugin-logger.js'
|
|
13
|
+
import { initSentry, notifyError } from './sentry.js'
|
|
14
|
+
import { abbreviatePath } from './utils.js'
|
|
15
|
+
|
|
16
|
+
const logger = createPluginLogger('OPENCODE')
|
|
17
|
+
const TOAST_SESSION_MARKER_SEPARATOR = ' '
|
|
18
|
+
|
|
19
|
+
type PluginHooks = Awaited<ReturnType<Plugin>>
|
|
20
|
+
type SystemTransformHook = NonNullable<PluginHooks['experimental.chat.system.transform']>
|
|
21
|
+
type SystemTransformInput = Parameters<SystemTransformHook>[0]
|
|
22
|
+
type SystemTransformOutput = Parameters<SystemTransformHook>[1]
|
|
23
|
+
type PluginEventHook = NonNullable<PluginHooks['event']>
|
|
24
|
+
type PluginEvent = Parameters<PluginEventHook>[0]['event']
|
|
25
|
+
type ChatMessageHook = NonNullable<PluginHooks['chat.message']>
|
|
26
|
+
type ChatMessageInput = Parameters<ChatMessageHook>[0]
|
|
27
|
+
|
|
28
|
+
type SessionState = {
|
|
29
|
+
userTurnCount: number
|
|
30
|
+
previousTurnPrompt: string | undefined
|
|
31
|
+
latestTurnPrompt: string | undefined
|
|
32
|
+
latestTurnPromptTurn: number
|
|
33
|
+
comparedTurn: number
|
|
34
|
+
previousTurnContext: TurnContext | undefined
|
|
35
|
+
currentTurnContext: TurnContext | undefined
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type SystemPromptDiff = {
|
|
39
|
+
additions: number
|
|
40
|
+
deletions: number
|
|
41
|
+
patch: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type TurnContext = {
|
|
45
|
+
agent: string | undefined
|
|
46
|
+
model: string | undefined
|
|
47
|
+
directory: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getSystemPromptDiffDir({ dataDir }: { dataDir: string }): string {
|
|
51
|
+
return path.join(dataDir, 'system-prompt-diffs')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeSystemPrompt({ system }: { system: string[] }): string {
|
|
55
|
+
return system.join('\n')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function appendToastSessionMarker({
|
|
59
|
+
message,
|
|
60
|
+
sessionId,
|
|
61
|
+
}: {
|
|
62
|
+
message: string
|
|
63
|
+
sessionId: string
|
|
64
|
+
}): string {
|
|
65
|
+
return `${message}${TOAST_SESSION_MARKER_SEPARATOR}${sessionId}`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildTurnContext({
|
|
69
|
+
input,
|
|
70
|
+
directory,
|
|
71
|
+
}: {
|
|
72
|
+
input: ChatMessageInput
|
|
73
|
+
directory: string
|
|
74
|
+
}): TurnContext {
|
|
75
|
+
const model = input.model
|
|
76
|
+
? `${input.model.providerID}/${input.model.modelID}${input.variant ? `:${input.variant}` : ''}`
|
|
77
|
+
: undefined
|
|
78
|
+
return {
|
|
79
|
+
agent: input.agent,
|
|
80
|
+
model,
|
|
81
|
+
directory,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function shouldSuppressDiffNotice({
|
|
86
|
+
previousContext,
|
|
87
|
+
currentContext,
|
|
88
|
+
}: {
|
|
89
|
+
previousContext: TurnContext | undefined
|
|
90
|
+
currentContext: TurnContext | undefined
|
|
91
|
+
}): boolean {
|
|
92
|
+
if (!previousContext || !currentContext) {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
return (
|
|
96
|
+
previousContext.agent !== currentContext.agent
|
|
97
|
+
|| previousContext.model !== currentContext.model
|
|
98
|
+
|| previousContext.directory !== currentContext.directory
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function buildPatch({
|
|
103
|
+
beforeText,
|
|
104
|
+
afterText,
|
|
105
|
+
beforeLabel,
|
|
106
|
+
afterLabel,
|
|
107
|
+
}: {
|
|
108
|
+
beforeText: string
|
|
109
|
+
afterText: string
|
|
110
|
+
beforeLabel: string
|
|
111
|
+
afterLabel: string
|
|
112
|
+
}): SystemPromptDiff {
|
|
113
|
+
const changes = diffLines(beforeText, afterText)
|
|
114
|
+
const additions = changes.reduce((count, change) => {
|
|
115
|
+
if (!change.added) {
|
|
116
|
+
return count
|
|
117
|
+
}
|
|
118
|
+
return count + change.count
|
|
119
|
+
}, 0)
|
|
120
|
+
const deletions = changes.reduce((count, change) => {
|
|
121
|
+
if (!change.removed) {
|
|
122
|
+
return count
|
|
123
|
+
}
|
|
124
|
+
return count + change.count
|
|
125
|
+
}, 0)
|
|
126
|
+
const patch = createPatch(afterLabel, beforeText, afterText, beforeLabel, afterLabel)
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
additions,
|
|
130
|
+
deletions,
|
|
131
|
+
patch,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function writeSystemPromptDiffFile({
|
|
136
|
+
dataDir,
|
|
137
|
+
sessionId,
|
|
138
|
+
beforePrompt,
|
|
139
|
+
afterPrompt,
|
|
140
|
+
}: {
|
|
141
|
+
dataDir: string
|
|
142
|
+
sessionId: string
|
|
143
|
+
beforePrompt: string
|
|
144
|
+
afterPrompt: string
|
|
145
|
+
}): Error | {
|
|
146
|
+
additions: number
|
|
147
|
+
deletions: number
|
|
148
|
+
filePath: string
|
|
149
|
+
latestPromptPath: string
|
|
150
|
+
} {
|
|
151
|
+
const diff = buildPatch({
|
|
152
|
+
beforeText: beforePrompt,
|
|
153
|
+
afterText: afterPrompt,
|
|
154
|
+
beforeLabel: 'system-before.txt',
|
|
155
|
+
afterLabel: 'system-after.txt',
|
|
156
|
+
})
|
|
157
|
+
const timestamp = new Date().toISOString().replaceAll(':', '-')
|
|
158
|
+
const sessionDir = path.join(getSystemPromptDiffDir({ dataDir }), sessionId)
|
|
159
|
+
const filePath = path.join(sessionDir, `${timestamp}.diff`)
|
|
160
|
+
const latestPromptPath = path.join(sessionDir, `${timestamp}.md`)
|
|
161
|
+
const fileContent = [
|
|
162
|
+
`Session: ${sessionId}`,
|
|
163
|
+
`Created: ${new Date().toISOString()}`,
|
|
164
|
+
`Additions: ${diff.additions}`,
|
|
165
|
+
`Deletions: ${diff.deletions}`,
|
|
166
|
+
'',
|
|
167
|
+
diff.patch,
|
|
168
|
+
].join('\n')
|
|
169
|
+
|
|
170
|
+
return errore.try({
|
|
171
|
+
try: () => {
|
|
172
|
+
fs.mkdirSync(sessionDir, { recursive: true })
|
|
173
|
+
fs.writeFileSync(filePath, fileContent)
|
|
174
|
+
// fs.writeFileSync(latestPromptPath, afterPrompt)
|
|
175
|
+
return {
|
|
176
|
+
additions: diff.additions,
|
|
177
|
+
deletions: diff.deletions,
|
|
178
|
+
filePath,
|
|
179
|
+
latestPromptPath,
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
catch: (error) => {
|
|
183
|
+
return new Error('Failed to write system prompt diff file', { cause: error })
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getOrCreateSessionState({
|
|
189
|
+
sessions,
|
|
190
|
+
sessionId,
|
|
191
|
+
}: {
|
|
192
|
+
sessions: Map<string, SessionState>
|
|
193
|
+
sessionId: string
|
|
194
|
+
}): SessionState {
|
|
195
|
+
const existing = sessions.get(sessionId)
|
|
196
|
+
if (existing) {
|
|
197
|
+
return existing
|
|
198
|
+
}
|
|
199
|
+
const state = {
|
|
200
|
+
userTurnCount: 0,
|
|
201
|
+
previousTurnPrompt: undefined,
|
|
202
|
+
latestTurnPrompt: undefined,
|
|
203
|
+
latestTurnPromptTurn: 0,
|
|
204
|
+
comparedTurn: 0,
|
|
205
|
+
previousTurnContext: undefined,
|
|
206
|
+
currentTurnContext: undefined,
|
|
207
|
+
}
|
|
208
|
+
sessions.set(sessionId, state)
|
|
209
|
+
return state
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function handleSystemTransform({
|
|
213
|
+
input,
|
|
214
|
+
output,
|
|
215
|
+
sessions,
|
|
216
|
+
dataDir,
|
|
217
|
+
client,
|
|
218
|
+
}: {
|
|
219
|
+
input: SystemTransformInput
|
|
220
|
+
output: SystemTransformOutput
|
|
221
|
+
sessions: Map<string, SessionState>
|
|
222
|
+
dataDir: string | undefined
|
|
223
|
+
client: Parameters<Plugin>[0]['client']
|
|
224
|
+
}): Promise<void> {
|
|
225
|
+
const sessionId = input.sessionID
|
|
226
|
+
if (!sessionId) {
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const currentPrompt = normalizeSystemPrompt({ system: output.system })
|
|
231
|
+
const state = getOrCreateSessionState({
|
|
232
|
+
sessions,
|
|
233
|
+
sessionId,
|
|
234
|
+
})
|
|
235
|
+
const currentTurn = state.userTurnCount
|
|
236
|
+
state.latestTurnPrompt = currentPrompt
|
|
237
|
+
state.latestTurnPromptTurn = currentTurn
|
|
238
|
+
|
|
239
|
+
if (currentTurn <= 1) {
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
if (state.comparedTurn === currentTurn) {
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
const previousPrompt = state.previousTurnPrompt
|
|
246
|
+
state.comparedTurn = currentTurn
|
|
247
|
+
if (!previousPrompt || previousPrompt === currentPrompt) {
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
if (
|
|
251
|
+
shouldSuppressDiffNotice({
|
|
252
|
+
previousContext: state.previousTurnContext,
|
|
253
|
+
currentContext: state.currentTurnContext,
|
|
254
|
+
})
|
|
255
|
+
) {
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!dataDir) {
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const diffFileResult = writeSystemPromptDiffFile({
|
|
264
|
+
dataDir,
|
|
265
|
+
sessionId,
|
|
266
|
+
beforePrompt: previousPrompt,
|
|
267
|
+
afterPrompt: currentPrompt,
|
|
268
|
+
})
|
|
269
|
+
if (diffFileResult instanceof Error) {
|
|
270
|
+
throw diffFileResult
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
await client.tui.showToast({
|
|
274
|
+
body: {
|
|
275
|
+
variant: 'info',
|
|
276
|
+
title: 'Context cache discarded',
|
|
277
|
+
message: appendToastSessionMarker({
|
|
278
|
+
sessionId,
|
|
279
|
+
message:
|
|
280
|
+
`system prompt changed since the previous message (+${diffFileResult.additions} / -${diffFileResult.deletions}). ` +
|
|
281
|
+
`Diff: \`${abbreviatePath(diffFileResult.filePath)}\`. ` +
|
|
282
|
+
`Latest prompt: \`${abbreviatePath(diffFileResult.latestPromptPath)}\``,
|
|
283
|
+
}),
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const systemPromptDriftPlugin: Plugin = async ({ client, directory }) => {
|
|
289
|
+
initSentry()
|
|
290
|
+
|
|
291
|
+
const dataDir = process.env.KIMAKI_DATA_DIR
|
|
292
|
+
if (dataDir) {
|
|
293
|
+
setPluginLogFilePath(dataDir)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const sessions = new Map<string, SessionState>()
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
'chat.message': async (input) => {
|
|
300
|
+
const sessionId = input.sessionID
|
|
301
|
+
if (!sessionId) {
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
const state = getOrCreateSessionState({ sessions, sessionId })
|
|
305
|
+
if (
|
|
306
|
+
state.userTurnCount > 0
|
|
307
|
+
&& state.latestTurnPromptTurn === state.userTurnCount
|
|
308
|
+
) {
|
|
309
|
+
state.previousTurnPrompt = state.latestTurnPrompt
|
|
310
|
+
state.previousTurnContext = state.currentTurnContext
|
|
311
|
+
}
|
|
312
|
+
state.currentTurnContext = buildTurnContext({ input, directory })
|
|
313
|
+
state.userTurnCount += 1
|
|
314
|
+
},
|
|
315
|
+
'experimental.chat.system.transform': async (input, output) => {
|
|
316
|
+
const result = await errore.tryAsync({
|
|
317
|
+
try: async () => {
|
|
318
|
+
await handleSystemTransform({
|
|
319
|
+
input,
|
|
320
|
+
output,
|
|
321
|
+
sessions,
|
|
322
|
+
dataDir,
|
|
323
|
+
client,
|
|
324
|
+
})
|
|
325
|
+
},
|
|
326
|
+
catch: (error) => {
|
|
327
|
+
return new Error('system prompt drift transform hook failed', {
|
|
328
|
+
cause: error,
|
|
329
|
+
})
|
|
330
|
+
},
|
|
331
|
+
})
|
|
332
|
+
if (result instanceof Error) {
|
|
333
|
+
logger.warn(
|
|
334
|
+
`[system-prompt-drift-plugin] ${formatPluginErrorWithStack(result)}`,
|
|
335
|
+
)
|
|
336
|
+
void notifyError(result, 'system prompt drift plugin transform hook failed')
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
event: async ({ event }) => {
|
|
340
|
+
const result = await errore.tryAsync({
|
|
341
|
+
try: async () => {
|
|
342
|
+
if (event.type !== 'session.deleted') {
|
|
343
|
+
return
|
|
344
|
+
}
|
|
345
|
+
const deletedSessionId = getDeletedSessionId({ event })
|
|
346
|
+
if (!deletedSessionId) {
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
sessions.delete(deletedSessionId)
|
|
350
|
+
},
|
|
351
|
+
catch: (error) => {
|
|
352
|
+
return new Error('system prompt drift event hook failed', {
|
|
353
|
+
cause: error,
|
|
354
|
+
})
|
|
355
|
+
},
|
|
356
|
+
})
|
|
357
|
+
if (result instanceof Error) {
|
|
358
|
+
logger.warn(
|
|
359
|
+
`[system-prompt-drift-plugin] ${formatPluginErrorWithStack(result)}`,
|
|
360
|
+
)
|
|
361
|
+
void notifyError(result, 'system prompt drift plugin event hook failed')
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function getDeletedSessionId({ event }: { event: PluginEvent }): string | undefined {
|
|
368
|
+
if (event.type !== 'session.deleted') {
|
|
369
|
+
return undefined
|
|
370
|
+
}
|
|
371
|
+
const sessionInfo = event.properties?.info
|
|
372
|
+
if (!sessionInfo || typeof sessionInfo !== 'object') {
|
|
373
|
+
return undefined
|
|
374
|
+
}
|
|
375
|
+
const id = 'id' in sessionInfo ? sessionInfo.id : undefined
|
|
376
|
+
return typeof id === 'string' ? id : undefined
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export { systemPromptDriftPlugin }
|
package/src/utils.ts
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
// abort error detection, and date/time formatting helpers.
|
|
4
4
|
|
|
5
5
|
import os from 'node:os'
|
|
6
|
-
import
|
|
6
|
+
// Use namespace import for CJS interop — discord.js is CJS and its named
|
|
7
|
+
// exports aren't detectable by all ESM loaders (e.g. tsx/esbuild) because
|
|
8
|
+
// discord.js uses tslib's __exportStar which is opaque to static analysis.
|
|
9
|
+
import * as discord from 'discord.js'
|
|
10
|
+
const { PermissionsBitField } = discord
|
|
7
11
|
import type { BotMode } from './database.js'
|
|
8
12
|
import * as errore from 'errore'
|
|
9
13
|
|
package/src/worktrees.test.ts
CHANGED
package/src/worktrees.ts
CHANGED
|
@@ -527,52 +527,6 @@ type WorktreeResult = {
|
|
|
527
527
|
async function resolveDefaultWorktreeTarget(
|
|
528
528
|
directory: string,
|
|
529
529
|
): Promise<string> {
|
|
530
|
-
const remoteHead = await execAsync(
|
|
531
|
-
'git symbolic-ref refs/remotes/origin/HEAD',
|
|
532
|
-
{
|
|
533
|
-
cwd: directory,
|
|
534
|
-
},
|
|
535
|
-
).catch(() => {
|
|
536
|
-
return null
|
|
537
|
-
})
|
|
538
|
-
|
|
539
|
-
const remoteRef = remoteHead?.stdout.trim()
|
|
540
|
-
if (remoteRef?.startsWith('refs/remotes/')) {
|
|
541
|
-
return remoteRef.replace('refs/remotes/', '')
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
const hasMain = await execAsync(
|
|
545
|
-
'git show-ref --verify --quiet refs/heads/main',
|
|
546
|
-
{
|
|
547
|
-
cwd: directory,
|
|
548
|
-
},
|
|
549
|
-
)
|
|
550
|
-
.then(() => {
|
|
551
|
-
return true
|
|
552
|
-
})
|
|
553
|
-
.catch(() => {
|
|
554
|
-
return false
|
|
555
|
-
})
|
|
556
|
-
if (hasMain) {
|
|
557
|
-
return 'main'
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
const hasMaster = await execAsync(
|
|
561
|
-
'git show-ref --verify --quiet refs/heads/master',
|
|
562
|
-
{
|
|
563
|
-
cwd: directory,
|
|
564
|
-
},
|
|
565
|
-
)
|
|
566
|
-
.then(() => {
|
|
567
|
-
return true
|
|
568
|
-
})
|
|
569
|
-
.catch(() => {
|
|
570
|
-
return false
|
|
571
|
-
})
|
|
572
|
-
if (hasMaster) {
|
|
573
|
-
return 'master'
|
|
574
|
-
}
|
|
575
|
-
|
|
576
530
|
return 'HEAD'
|
|
577
531
|
}
|
|
578
532
|
|
|
@@ -608,7 +562,7 @@ export async function createWorktreeWithSubmodules({
|
|
|
608
562
|
}: {
|
|
609
563
|
directory: string
|
|
610
564
|
name: string
|
|
611
|
-
/** Override the base branch to create the worktree from. Defaults to
|
|
565
|
+
/** Override the base branch to create the worktree from. Defaults to HEAD. */
|
|
612
566
|
baseBranch?: string
|
|
613
567
|
/** Called with a short phase label so callers can update UI (e.g. Discord status message). */
|
|
614
568
|
onProgress?: (phase: string) => void
|