backeer-opencode 0.2.0 → 0.2.2
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/CHANGELOG.md +17 -0
- package/README.md +34 -18
- package/dist/backeer-permission.js +343 -71
- package/package.json +1 -1
- package/src/config.js +1 -0
- package/src/diagnostics.js +148 -0
- package/src/event-publishers.js +6 -0
- package/src/lifecycle-controller.js +12 -2
- package/src/lifecycle-publishers.js +9 -5
- package/src/management.js +18 -16
- package/src/permission-controller.js +9 -0
- package/src/plugin.js +25 -6
- package/src/reporter.js +50 -0
- package/src/session-messages.js +122 -0
- package/src/updates.js +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { limit, rememberBounded } from "./utilities.js"
|
|
2
|
+
|
|
3
|
+
const FEEDBACK_SYMBOLS = {
|
|
4
|
+
info: "•",
|
|
5
|
+
success: "✓",
|
|
6
|
+
applied: "✓",
|
|
7
|
+
warning: "!",
|
|
8
|
+
error: "×",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function sendSessionMessage(client, sessionID, text) {
|
|
12
|
+
if (typeof client?.session?.prompt !== "function") {
|
|
13
|
+
throw new Error("OpenCode session messaging is unavailable")
|
|
14
|
+
}
|
|
15
|
+
await client.session.prompt({
|
|
16
|
+
path: { id: sessionID },
|
|
17
|
+
body: {
|
|
18
|
+
noReply: true,
|
|
19
|
+
parts: [{ type: "text", text, ignored: true }],
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatBackeerFeedback(kind, message) {
|
|
25
|
+
const symbol = FEEDBACK_SYMBOLS[kind] ?? FEEDBACK_SYMBOLS.info
|
|
26
|
+
return `> **Backeer** ${symbol} ${limit(message, 300)}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createConversationFeedback({ client, reporter }) {
|
|
30
|
+
const sessions = new Map()
|
|
31
|
+
let disposed = false
|
|
32
|
+
|
|
33
|
+
function sessionState(sessionID) {
|
|
34
|
+
let state = sessions.get(sessionID)
|
|
35
|
+
if (!state) {
|
|
36
|
+
state = { idle: false, flushing: false, queue: [], queued: new Set() }
|
|
37
|
+
rememberBounded(sessions, sessionID, state)
|
|
38
|
+
}
|
|
39
|
+
return state
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function flush(sessionID, state) {
|
|
43
|
+
if (disposed || state.flushing || !state.idle || state.queue.length === 0) return
|
|
44
|
+
state.flushing = true
|
|
45
|
+
try {
|
|
46
|
+
while (!disposed && state.idle && state.queue.length > 0) {
|
|
47
|
+
const item = state.queue[0]
|
|
48
|
+
try {
|
|
49
|
+
await sendSessionMessage(
|
|
50
|
+
client,
|
|
51
|
+
sessionID,
|
|
52
|
+
formatBackeerFeedback(item.kind, item.message),
|
|
53
|
+
)
|
|
54
|
+
state.queue.shift()
|
|
55
|
+
state.queued.delete(item.key)
|
|
56
|
+
} catch (error) {
|
|
57
|
+
state.idle = false
|
|
58
|
+
reporter.debug("Conversation feedback could not be displayed", error)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
state.flushing = false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function enqueue(kind, sessionID, message) {
|
|
67
|
+
if (disposed || typeof sessionID !== "string" || !sessionID || !message) return
|
|
68
|
+
const state = sessionState(sessionID)
|
|
69
|
+
const key = `${sessionID}:${kind}:${message}`
|
|
70
|
+
if (state.queued.has(key)) return
|
|
71
|
+
|
|
72
|
+
state.queue.push({ kind, message: limit(message, 300), key })
|
|
73
|
+
state.queued.add(key)
|
|
74
|
+
while (state.queue.length > 20) {
|
|
75
|
+
const removed = state.queue.shift()
|
|
76
|
+
state.queued.delete(removed.key)
|
|
77
|
+
}
|
|
78
|
+
if (state.idle) void flush(sessionID, state)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function setIdle(sessionID, idle) {
|
|
82
|
+
if (typeof sessionID !== "string" || !sessionID) return
|
|
83
|
+
const state = sessionState(sessionID)
|
|
84
|
+
state.idle = idle
|
|
85
|
+
if (idle) void flush(sessionID, state)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
info: (sessionID, message) => enqueue("info", sessionID, message),
|
|
90
|
+
success: (sessionID, message) => enqueue("success", sessionID, message),
|
|
91
|
+
applied: (sessionID, message) => enqueue("applied", sessionID, message),
|
|
92
|
+
warning: (sessionID, message) => enqueue("warning", sessionID, message),
|
|
93
|
+
error: (sessionID, message) => enqueue("error", sessionID, message),
|
|
94
|
+
|
|
95
|
+
handle(event) {
|
|
96
|
+
const sessionID = event?.properties?.sessionID
|
|
97
|
+
if (typeof sessionID !== "string") return
|
|
98
|
+
|
|
99
|
+
if (event.type === "session.status") {
|
|
100
|
+
const status = event.properties?.status?.type
|
|
101
|
+
if (status === "idle") setIdle(sessionID, true)
|
|
102
|
+
else if (status === "busy" || status === "retry") setIdle(sessionID, false)
|
|
103
|
+
} else if (event.type === "session.idle") {
|
|
104
|
+
setIdle(sessionID, true)
|
|
105
|
+
} else if (
|
|
106
|
+
event.type === "permission.asked" ||
|
|
107
|
+
event.type === "question.asked" ||
|
|
108
|
+
event.type === "question.v2.asked" ||
|
|
109
|
+
event.type === "todo.updated"
|
|
110
|
+
) {
|
|
111
|
+
setIdle(sessionID, false)
|
|
112
|
+
} else if (event.type === "session.deleted") {
|
|
113
|
+
sessions.delete(sessionID)
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
dispose() {
|
|
118
|
+
disposed = true
|
|
119
|
+
sessions.clear()
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
}
|
package/src/updates.js
CHANGED