opencode-planpilot 0.1.1 → 0.1.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/package.json +2 -2
- package/src/index.ts +98 -3
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export const PlanpilotPlugin: Plugin = async (ctx) => {
|
|
|
16
16
|
"Planpilot guidance:",
|
|
17
17
|
"- Do not read plan files from disk or follow plan file placeholders.",
|
|
18
18
|
"- Use the planpilot tool for plan/step/goal info (plan show-active, step show-next, goal list <step_id>).",
|
|
19
|
+
"- If you cannot continue or need human input, insert a new step with executor `human` before the next pending step using planpilot so auto-continue pauses.",
|
|
19
20
|
].join("\n")
|
|
20
21
|
|
|
21
22
|
const log = async (level: "debug" | "info" | "warn" | "error", message: string, extra?: Record<string, any>) => {
|
|
@@ -33,6 +34,86 @@ export const PlanpilotPlugin: Plugin = async (ctx) => {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
type SessionMessage = {
|
|
38
|
+
info?: {
|
|
39
|
+
role?: string
|
|
40
|
+
agent?: string
|
|
41
|
+
model?: {
|
|
42
|
+
providerID: string
|
|
43
|
+
modelID: string
|
|
44
|
+
}
|
|
45
|
+
modelID?: string
|
|
46
|
+
providerID?: string
|
|
47
|
+
variant?: string
|
|
48
|
+
time?: {
|
|
49
|
+
created?: number
|
|
50
|
+
}
|
|
51
|
+
error?: {
|
|
52
|
+
name?: string
|
|
53
|
+
}
|
|
54
|
+
finish?: string
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const loadRecentMessages = async (sessionID: string, limit = 200): Promise<SessionMessage[]> => {
|
|
59
|
+
const response = await ctx.client.session.messages({
|
|
60
|
+
path: { id: sessionID },
|
|
61
|
+
query: { limit },
|
|
62
|
+
})
|
|
63
|
+
const data = (response as { data?: unknown }).data ?? response
|
|
64
|
+
if (!Array.isArray(data)) return []
|
|
65
|
+
return data as SessionMessage[]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const findLastMessage = (messages: SessionMessage[], predicate: (message: SessionMessage) => boolean) => {
|
|
69
|
+
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
70
|
+
const message = messages[idx]
|
|
71
|
+
if (predicate(message)) return message
|
|
72
|
+
}
|
|
73
|
+
return undefined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const findLastMessageByRole = (messages: SessionMessage[], role: "user" | "assistant") =>
|
|
77
|
+
findLastMessage(messages, (message) => message?.info?.role === role)
|
|
78
|
+
|
|
79
|
+
const resolveAutoContext = async (sessionID: string) => {
|
|
80
|
+
const messages = await loadRecentMessages(sessionID)
|
|
81
|
+
if (!messages.length) return null
|
|
82
|
+
|
|
83
|
+
const sortedMessages = [...messages].sort((left, right) => {
|
|
84
|
+
const leftTime = left?.info?.time?.created ?? 0
|
|
85
|
+
const rightTime = right?.info?.time?.created ?? 0
|
|
86
|
+
return leftTime - rightTime
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const lastUser = findLastMessage(sortedMessages, (message) => message?.info?.role === "user")
|
|
90
|
+
if (!lastUser) return { missingUser: true }
|
|
91
|
+
|
|
92
|
+
const lastAssistant = findLastMessageByRole(sortedMessages, "assistant")
|
|
93
|
+
const error = lastAssistant?.info?.error
|
|
94
|
+
const aborted = typeof error === "object" && error?.name === "MessageAbortedError"
|
|
95
|
+
const finish = lastAssistant?.info?.finish
|
|
96
|
+
const ready =
|
|
97
|
+
!lastAssistant || (typeof finish === "string" && finish !== "tool-calls" && finish !== "unknown")
|
|
98
|
+
|
|
99
|
+
const model =
|
|
100
|
+
lastUser?.info?.model ??
|
|
101
|
+
(lastUser?.info?.providerID && lastUser?.info?.modelID
|
|
102
|
+
? { providerID: lastUser.info.providerID, modelID: lastUser.info.modelID }
|
|
103
|
+
: lastAssistant?.info?.providerID && lastAssistant?.info?.modelID
|
|
104
|
+
? { providerID: lastAssistant.info.providerID, modelID: lastAssistant.info.modelID }
|
|
105
|
+
: undefined)
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
agent: lastUser?.info?.agent ?? lastAssistant?.info?.agent,
|
|
109
|
+
model,
|
|
110
|
+
variant: lastUser?.info?.variant,
|
|
111
|
+
aborted,
|
|
112
|
+
ready,
|
|
113
|
+
missingUser: false,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
36
117
|
const handleSessionIdle = async (sessionID: string) => {
|
|
37
118
|
if (inFlight.has(sessionID)) return
|
|
38
119
|
if (skipNextAuto.has(sessionID)) {
|
|
@@ -55,6 +136,13 @@ export const PlanpilotPlugin: Plugin = async (ctx) => {
|
|
|
55
136
|
const detail = formatStepDetail(next, goals)
|
|
56
137
|
if (!detail.trim()) return
|
|
57
138
|
|
|
139
|
+
const autoContext = await resolveAutoContext(sessionID)
|
|
140
|
+
if (autoContext?.missingUser) {
|
|
141
|
+
await log("warn", "auto-continue stopped: missing user context", { sessionID })
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
if (autoContext?.aborted || autoContext?.ready === false) return
|
|
145
|
+
|
|
58
146
|
const message =
|
|
59
147
|
"Planpilot (auto):\n" +
|
|
60
148
|
"Before acting, think through the next step and its goals. Record implementation details using Planpilot comments (plan/step/goal --comment or comment commands). Continue with the next step (executor: ai). Do not ask for confirmation; proceed and report results.\n\n" +
|
|
@@ -62,11 +150,18 @@ export const PlanpilotPlugin: Plugin = async (ctx) => {
|
|
|
62
150
|
"\n\n" +
|
|
63
151
|
detail.trimEnd()
|
|
64
152
|
|
|
153
|
+
const promptBody: any = {
|
|
154
|
+
agent: autoContext?.agent ?? undefined,
|
|
155
|
+
model: autoContext?.model ?? undefined,
|
|
156
|
+
parts: [{ type: "text" as const, text: message }],
|
|
157
|
+
}
|
|
158
|
+
if (autoContext?.variant) {
|
|
159
|
+
promptBody.variant = autoContext.variant
|
|
160
|
+
}
|
|
161
|
+
|
|
65
162
|
await ctx.client.session.promptAsync({
|
|
66
163
|
path: { id: sessionID },
|
|
67
|
-
body:
|
|
68
|
-
parts: [{ type: "text", text: message }],
|
|
69
|
-
},
|
|
164
|
+
body: promptBody,
|
|
70
165
|
})
|
|
71
166
|
} catch (err) {
|
|
72
167
|
await log("warn", "failed to auto-continue plan", { error: err instanceof Error ? err.message : String(err) })
|