@withakay/opencode-autopilot 0.1.0 → 0.2.0
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/README.md +48 -25
- package/dist/index.js +481 -497
- package/dist/index.js.map +17 -19
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -1,164 +1,201 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class SessionCache {
|
|
11
|
-
sessions = new Map;
|
|
12
|
-
ensureSession(sessionID) {
|
|
13
|
-
const existing = this.sessions.get(sessionID);
|
|
14
|
-
if (existing) {
|
|
15
|
-
return existing;
|
|
1
|
+
// hooks/chat-message.ts
|
|
2
|
+
var CONTROL_AGENT = "autopilot";
|
|
3
|
+
function createChatMessageHook(deps) {
|
|
4
|
+
const { getState, incrementSuppressCount } = deps;
|
|
5
|
+
return async (input, _output) => {
|
|
6
|
+
const state = getState(input.sessionID);
|
|
7
|
+
if (!state || state.mode !== "ENABLED") {
|
|
8
|
+
return;
|
|
16
9
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return created;
|
|
20
|
-
}
|
|
21
|
-
setRole(sessionID, messageID, role) {
|
|
22
|
-
this.ensureSession(sessionID).roles.set(messageID, role);
|
|
23
|
-
}
|
|
24
|
-
getRole(sessionID, messageID) {
|
|
25
|
-
return this.sessions.get(sessionID)?.roles.get(messageID) ?? null;
|
|
26
|
-
}
|
|
27
|
-
setAgent(sessionID, messageID, agent) {
|
|
28
|
-
this.ensureSession(sessionID).agents.set(messageID, agent);
|
|
29
|
-
}
|
|
30
|
-
getAgent(sessionID, messageID) {
|
|
31
|
-
return this.sessions.get(sessionID)?.agents.get(messageID) ?? null;
|
|
32
|
-
}
|
|
33
|
-
setTextPart(sessionID, partID, messageID, text) {
|
|
34
|
-
this.ensureSession(sessionID).textParts.set(partID, {
|
|
35
|
-
id: partID,
|
|
36
|
-
messageID,
|
|
37
|
-
text
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
getTextPart(sessionID, partID) {
|
|
41
|
-
return this.sessions.get(sessionID)?.textParts.get(partID) ?? null;
|
|
42
|
-
}
|
|
43
|
-
getMessageText(sessionID, messageID) {
|
|
44
|
-
const session = this.sessions.get(sessionID);
|
|
45
|
-
if (!session) {
|
|
46
|
-
return "";
|
|
10
|
+
if (input.agent === CONTROL_AGENT) {
|
|
11
|
+
incrementSuppressCount(input.sessionID);
|
|
47
12
|
}
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
snapshot(sessionID) {
|
|
51
|
-
const session = this.sessions.get(sessionID) ?? createSessionStore();
|
|
52
|
-
return {
|
|
53
|
-
roles: Object.fromEntries(session.roles),
|
|
54
|
-
agents: Object.fromEntries(session.agents),
|
|
55
|
-
textParts: [...session.textParts.values()]
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
cleanup(sessionID) {
|
|
59
|
-
this.sessions.delete(sessionID);
|
|
60
|
-
}
|
|
13
|
+
};
|
|
61
14
|
}
|
|
62
15
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
var DEFAULT_CONTEXT_THRESHOLD = 8000;
|
|
66
|
-
var DEFAULT_MAX_CONTINUES = 25;
|
|
67
|
-
var DEFAULT_MAX_STEP_RETRIES = 2;
|
|
68
|
-
var DEFAULT_MAX_GLOBAL_RETRIES = 6;
|
|
69
|
-
var DEFAULT_MAX_NO_PROGRESS = 3;
|
|
70
|
-
var DEFAULT_WORKER_AGENT = "pi";
|
|
71
|
-
function createPlanState() {
|
|
16
|
+
// hooks/event-handler.ts
|
|
17
|
+
function createSessionTracking() {
|
|
72
18
|
return {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
stale: false
|
|
19
|
+
lastAssistantMessageID: undefined,
|
|
20
|
+
lastUsage: undefined,
|
|
21
|
+
awaitingWorkerReply: false,
|
|
22
|
+
blockedByPermission: false,
|
|
23
|
+
permissionBlockMessage: undefined
|
|
79
24
|
};
|
|
80
25
|
}
|
|
81
|
-
function
|
|
82
|
-
return
|
|
83
|
-
status: "idle",
|
|
84
|
-
pending_action: null,
|
|
85
|
-
pending_scope: null,
|
|
86
|
-
approved_scopes: [],
|
|
87
|
-
denied_scopes: [],
|
|
88
|
-
last_feedback: null
|
|
89
|
-
};
|
|
26
|
+
function isString(value) {
|
|
27
|
+
return typeof value === "string";
|
|
90
28
|
}
|
|
91
|
-
function
|
|
92
|
-
return
|
|
93
|
-
status: trustedPaths.length > 0 ? "trusted" : "untrusted",
|
|
94
|
-
trusted_paths: [...trustedPaths],
|
|
95
|
-
pending_path: null,
|
|
96
|
-
denied_paths: [],
|
|
97
|
-
last_feedback: null
|
|
98
|
-
};
|
|
29
|
+
function isObject(value) {
|
|
30
|
+
return typeof value === "object" && value !== null;
|
|
99
31
|
}
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
32
|
+
function normalizeError(error) {
|
|
33
|
+
if (!isObject(error))
|
|
34
|
+
return;
|
|
35
|
+
const result = {};
|
|
36
|
+
if (isString(error.name)) {
|
|
37
|
+
result.name = error.name;
|
|
38
|
+
}
|
|
39
|
+
if (isObject(error.data) && isString(error.data.message)) {
|
|
40
|
+
result.data = { message: error.data.message };
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
function createEventHandler(deps) {
|
|
45
|
+
const { getState, deleteState, sessionCache, getTracking, onSessionIdle, onSessionError } = deps;
|
|
46
|
+
return async function handleEvent(input) {
|
|
47
|
+
const { event } = input;
|
|
48
|
+
switch (event.type) {
|
|
49
|
+
case "message.updated": {
|
|
50
|
+
const info = event.properties.info;
|
|
51
|
+
if (!isObject(info))
|
|
52
|
+
return;
|
|
53
|
+
const sessionID = info.sessionID;
|
|
54
|
+
const messageID = info.id;
|
|
55
|
+
const role = info.role;
|
|
56
|
+
if (!isString(sessionID) || !isString(messageID) || !isString(role)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (role === "user" || role === "assistant") {
|
|
60
|
+
sessionCache.setRole(sessionID, messageID, role);
|
|
61
|
+
}
|
|
62
|
+
if ("agent" in info && isString(info.agent)) {
|
|
63
|
+
sessionCache.setAgent(sessionID, messageID, info.agent);
|
|
64
|
+
}
|
|
65
|
+
if (role === "assistant") {
|
|
66
|
+
const state = getState(sessionID);
|
|
67
|
+
const tracking = getTracking(sessionID);
|
|
68
|
+
if (state && tracking?.awaitingWorkerReply) {
|
|
69
|
+
const agent = "agent" in info && isString(info.agent) ? info.agent : undefined;
|
|
70
|
+
if (agent === state.worker_agent) {
|
|
71
|
+
tracking.lastAssistantMessageID = messageID;
|
|
72
|
+
tracking.lastUsage = {
|
|
73
|
+
tokens: info.tokens,
|
|
74
|
+
cost: info.cost
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
case "message.part.updated": {
|
|
82
|
+
const part = event.properties.part;
|
|
83
|
+
if (!isObject(part))
|
|
84
|
+
return;
|
|
85
|
+
if (part.type !== "text" || !isString(part.sessionID) || !isString(part.messageID) || !isString(part.id) || !isString(part.text)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const state = getState(part.sessionID);
|
|
89
|
+
if (!state)
|
|
90
|
+
return;
|
|
91
|
+
const cachedRole = sessionCache.getRole(part.sessionID, part.messageID);
|
|
92
|
+
const cachedAgent = sessionCache.getAgent(part.sessionID, part.messageID);
|
|
93
|
+
if (cachedRole === "assistant" && cachedAgent === state.worker_agent) {
|
|
94
|
+
sessionCache.setTextPart(part.sessionID, part.id, part.messageID, part.text);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
case "session.idle": {
|
|
99
|
+
const sessionID = event.properties.sessionID;
|
|
100
|
+
if (!isString(sessionID))
|
|
101
|
+
return;
|
|
102
|
+
await onSessionIdle(sessionID);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
case "session.error": {
|
|
106
|
+
const sessionID = event.properties.sessionID;
|
|
107
|
+
if (!isString(sessionID))
|
|
108
|
+
return;
|
|
109
|
+
const state = getState(sessionID);
|
|
110
|
+
if (!state || state.mode !== "ENABLED")
|
|
111
|
+
return;
|
|
112
|
+
const error = normalizeError(event.properties.error);
|
|
113
|
+
await onSessionError(sessionID, error);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
case "session.deleted": {
|
|
117
|
+
const info = event.properties.info;
|
|
118
|
+
if (!isObject(info) || !isString(info.id))
|
|
119
|
+
return;
|
|
120
|
+
sessionCache.cleanup(info.id);
|
|
121
|
+
deleteState(info.id);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
case "permission.updated": {
|
|
125
|
+
const sessionID = event.properties.sessionID;
|
|
126
|
+
if (!isString(sessionID))
|
|
127
|
+
return;
|
|
128
|
+
const state = getState(sessionID);
|
|
129
|
+
const tracking = getTracking(sessionID);
|
|
130
|
+
if (!state || state.mode !== "ENABLED" || !tracking)
|
|
131
|
+
return;
|
|
132
|
+
tracking.blockedByPermission = true;
|
|
133
|
+
const permType = isString(event.properties.type) ? event.properties.type : "unknown";
|
|
134
|
+
const patterns = event.properties.pattern;
|
|
135
|
+
const patternStr = Array.isArray(patterns) ? patterns.filter(isString).join(", ") : isString(patterns) ? patterns : "";
|
|
136
|
+
tracking.permissionBlockMessage = `Denied ${permType} ${patternStr}`.trim();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
109
140
|
};
|
|
110
141
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
142
|
+
|
|
143
|
+
// hooks/permission.ts
|
|
144
|
+
function createPermissionHook(deps) {
|
|
145
|
+
return async (input, output) => {
|
|
146
|
+
const state = deps.getState(input.sessionID);
|
|
147
|
+
if (!state || state.mode !== "ENABLED") {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const permissionMode = deps.getPermissionMode(input.sessionID);
|
|
151
|
+
if (permissionMode === "allow-all") {
|
|
152
|
+
output.status = "allow";
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (permissionMode === "limited") {
|
|
156
|
+
output.status = "deny";
|
|
157
|
+
deps.onPermissionDenied?.(input.sessionID, {
|
|
158
|
+
type: input.type,
|
|
159
|
+
pattern: input.pattern,
|
|
160
|
+
title: input.title
|
|
161
|
+
});
|
|
162
|
+
}
|
|
120
163
|
};
|
|
121
164
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
last_tool_result: null,
|
|
145
|
-
last_tool_error: null,
|
|
146
|
-
last_interrupt: null
|
|
147
|
-
},
|
|
148
|
-
continuation_count: 0,
|
|
149
|
-
max_continues: options.maxContinues ?? DEFAULT_MAX_CONTINUES,
|
|
150
|
-
worker_agent: options.workerAgent ?? DEFAULT_WORKER_AGENT,
|
|
151
|
-
last_updated_at: null,
|
|
152
|
-
resumable: true
|
|
165
|
+
|
|
166
|
+
// hooks/system-transform.ts
|
|
167
|
+
function createSystemTransformHook(deps) {
|
|
168
|
+
const { getState, getSuppressCount, decrementSuppressCount, buildSystemPrompt } = deps;
|
|
169
|
+
return async (input, output) => {
|
|
170
|
+
const { sessionID } = input;
|
|
171
|
+
if (sessionID === undefined) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const state = getState(sessionID);
|
|
175
|
+
if (!state || state.mode !== "ENABLED") {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const suppressCount = getSuppressCount(sessionID);
|
|
179
|
+
if (suppressCount > 0) {
|
|
180
|
+
decrementSuppressCount(sessionID);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (!Array.isArray(output.system)) {
|
|
184
|
+
output.system = [];
|
|
185
|
+
}
|
|
186
|
+
output.system.push(buildSystemPrompt(state.autonomous_strength));
|
|
153
187
|
};
|
|
154
188
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
189
|
+
|
|
190
|
+
// hooks/tool-after.ts
|
|
191
|
+
var AUTOPILOT_TOOL = "autopilot";
|
|
192
|
+
function createToolAfterHook(deps) {
|
|
193
|
+
return async (input, output) => {
|
|
194
|
+
if (input.tool !== AUTOPILOT_TOOL) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
output.output = deps.stripMarker(output.output);
|
|
198
|
+
};
|
|
162
199
|
}
|
|
163
200
|
|
|
164
201
|
// prompts/continuation.ts
|
|
@@ -262,9 +299,13 @@ function summarizeAutopilotState(state) {
|
|
|
262
299
|
const status = [
|
|
263
300
|
`phase=${state.phase}`,
|
|
264
301
|
`mode=${state.mode}`,
|
|
302
|
+
`session_mode=${state.session_mode}`,
|
|
265
303
|
`continues=${state.continuation_count}/${state.max_continues}`,
|
|
266
304
|
`agent=${state.worker_agent}`
|
|
267
305
|
];
|
|
306
|
+
if (state.session_mode === "delegated-task") {
|
|
307
|
+
status.push(`task=${JSON.stringify(state.goal)}`);
|
|
308
|
+
}
|
|
268
309
|
if (state.stop_reason) {
|
|
269
310
|
status.push(`stop=${state.stop_reason}`);
|
|
270
311
|
}
|
|
@@ -285,277 +326,298 @@ function normalizeMaxContinues(value) {
|
|
|
285
326
|
return Math.min(AUTOPILOT_MAX_CONTINUES_HARD_LIMIT, Math.max(1, Math.floor(numeric)));
|
|
286
327
|
}
|
|
287
328
|
// prompts/system-prompt.ts
|
|
288
|
-
function buildAutopilotSystemPrompt() {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
329
|
+
function buildAutopilotSystemPrompt(strength = "balanced") {
|
|
330
|
+
const baseInstructions = ["Autopilot mode is active for this session."];
|
|
331
|
+
const behaviorInstructions = (() => {
|
|
332
|
+
switch (strength) {
|
|
333
|
+
case "conservative":
|
|
334
|
+
return [
|
|
335
|
+
"Work autonomously toward the user's goal and ask fewer follow-up questions.",
|
|
336
|
+
"Prefer the recommended or safest reasonable default when a routine choice is needed.",
|
|
337
|
+
"Only ask the user for input when you are truly blocked, the choice is high-impact, or no safe default exists."
|
|
338
|
+
];
|
|
339
|
+
case "balanced":
|
|
340
|
+
return [
|
|
341
|
+
"Work autonomously toward the user's goal with minimal user interaction.",
|
|
342
|
+
"When faced with routine choices (file paths, variable names, standard configurations), select the recommended or safest default without asking.",
|
|
343
|
+
"Only escalate to the user for: (1) high-impact decisions, (2) security/safety risks, or (3) when truly blocked with no reasonable default."
|
|
344
|
+
];
|
|
345
|
+
case "aggressive":
|
|
346
|
+
return [
|
|
347
|
+
"CRITICAL: Work with maximum autonomy. Minimize all user interaction.",
|
|
348
|
+
"When faced with any routine choice (file paths, variable names, configurations, formatting preferences), ALWAYS select the recommended or safest default immediately. DO NOT ask the user.",
|
|
349
|
+
"For choices with a 'recommended' option clearly marked, select it automatically and explain your choice in the response.",
|
|
350
|
+
"Only escalate to the user for: (1) high-impact irreversible decisions (data deletion, major refactors), (2) explicit user preference required (UX/design choices affecting end users), or (3) security/safety risks.",
|
|
351
|
+
"If you can make reasonable progress on a task without user input, do so immediately."
|
|
352
|
+
];
|
|
353
|
+
}
|
|
354
|
+
})();
|
|
355
|
+
const statusMarkerInstructions = [
|
|
356
|
+
"If a delegated autopilot task is in progress, keep it moving without waiting for extra confirmation.",
|
|
292
357
|
"At the very end of every assistant response, append exactly one machine-readable status marker on its own line using this format:",
|
|
293
358
|
'<autopilot status="continue|complete|blocked">short reason</autopilot>',
|
|
294
359
|
"Use continue when more work remains and you can keep going without the user.",
|
|
295
360
|
"Use complete when the task is done or you have reached a stable handoff point.",
|
|
296
361
|
"Use blocked when missing information, denied permissions, or an external failure prevents meaningful progress.",
|
|
297
362
|
"Do not omit the marker."
|
|
298
|
-
]
|
|
363
|
+
];
|
|
364
|
+
return [...baseInstructions, ...behaviorInstructions, ...statusMarkerInstructions].join(`
|
|
299
365
|
`);
|
|
300
366
|
}
|
|
301
|
-
//
|
|
302
|
-
|
|
367
|
+
// state/factory.ts
|
|
368
|
+
var DEFAULT_ALLOWED_TOOLS = ["bash", "read", "glob", "grep", "apply_patch"];
|
|
369
|
+
var DEFAULT_CONTEXT_THRESHOLD = 8000;
|
|
370
|
+
var DEFAULT_MAX_CONTINUES = 25;
|
|
371
|
+
var DEFAULT_MAX_STEP_RETRIES = 2;
|
|
372
|
+
var DEFAULT_MAX_GLOBAL_RETRIES = 6;
|
|
373
|
+
var DEFAULT_MAX_NO_PROGRESS = 3;
|
|
374
|
+
var DEFAULT_WORKER_AGENT = "pi";
|
|
375
|
+
var DEFAULT_AUTONOMOUS_STRENGTH = "balanced";
|
|
376
|
+
function createPlanState() {
|
|
303
377
|
return {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
378
|
+
steps: [],
|
|
379
|
+
open_items: [],
|
|
380
|
+
completed_items: [],
|
|
381
|
+
blocked_items: [],
|
|
382
|
+
dependencies: {},
|
|
383
|
+
stale: false
|
|
309
384
|
};
|
|
310
385
|
}
|
|
311
|
-
function
|
|
312
|
-
return
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
function createEventHandler(deps) {
|
|
330
|
-
const {
|
|
331
|
-
getState,
|
|
332
|
-
deleteState,
|
|
333
|
-
sessionCache,
|
|
334
|
-
getTracking,
|
|
335
|
-
onSessionIdle,
|
|
336
|
-
onSessionError
|
|
337
|
-
} = deps;
|
|
338
|
-
return async function handleEvent(input) {
|
|
339
|
-
const { event } = input;
|
|
340
|
-
switch (event.type) {
|
|
341
|
-
case "message.updated": {
|
|
342
|
-
const info = event.properties["info"];
|
|
343
|
-
if (!isObject(info))
|
|
344
|
-
return;
|
|
345
|
-
const sessionID = info["sessionID"];
|
|
346
|
-
const messageID = info["id"];
|
|
347
|
-
const role = info["role"];
|
|
348
|
-
if (!isString(sessionID) || !isString(messageID) || !isString(role)) {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
if (role === "user" || role === "assistant") {
|
|
352
|
-
sessionCache.setRole(sessionID, messageID, role);
|
|
353
|
-
}
|
|
354
|
-
if ("agent" in info && isString(info["agent"])) {
|
|
355
|
-
sessionCache.setAgent(sessionID, messageID, info["agent"]);
|
|
356
|
-
}
|
|
357
|
-
if (role === "assistant") {
|
|
358
|
-
const state = getState(sessionID);
|
|
359
|
-
const tracking = getTracking(sessionID);
|
|
360
|
-
if (state && tracking && tracking.awaitingWorkerReply) {
|
|
361
|
-
const agent = "agent" in info && isString(info["agent"]) ? info["agent"] : undefined;
|
|
362
|
-
if (agent === state.worker_agent) {
|
|
363
|
-
tracking.lastAssistantMessageID = messageID;
|
|
364
|
-
tracking.lastUsage = {
|
|
365
|
-
tokens: info["tokens"],
|
|
366
|
-
cost: info["cost"]
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
case "message.part.updated": {
|
|
374
|
-
const part = event.properties["part"];
|
|
375
|
-
if (!isObject(part))
|
|
376
|
-
return;
|
|
377
|
-
if (part["type"] !== "text" || !isString(part["sessionID"]) || !isString(part["messageID"]) || !isString(part["id"]) || !isString(part["text"])) {
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
const state = getState(part["sessionID"]);
|
|
381
|
-
if (!state)
|
|
382
|
-
return;
|
|
383
|
-
const cachedRole = sessionCache.getRole(part["sessionID"], part["messageID"]);
|
|
384
|
-
const cachedAgent = sessionCache.getAgent(part["sessionID"], part["messageID"]);
|
|
385
|
-
if (cachedRole === "assistant" && cachedAgent === state.worker_agent) {
|
|
386
|
-
sessionCache.setTextPart(part["sessionID"], part["id"], part["messageID"], part["text"]);
|
|
387
|
-
}
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
case "session.idle": {
|
|
391
|
-
const sessionID = event.properties["sessionID"];
|
|
392
|
-
if (!isString(sessionID))
|
|
393
|
-
return;
|
|
394
|
-
await onSessionIdle(sessionID);
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
case "session.error": {
|
|
398
|
-
const sessionID = event.properties["sessionID"];
|
|
399
|
-
if (!isString(sessionID))
|
|
400
|
-
return;
|
|
401
|
-
const state = getState(sessionID);
|
|
402
|
-
if (!state || state.mode !== "ENABLED")
|
|
403
|
-
return;
|
|
404
|
-
const error = normalizeError(event.properties["error"]);
|
|
405
|
-
await onSessionError(sessionID, error);
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
case "session.deleted": {
|
|
409
|
-
const info = event.properties["info"];
|
|
410
|
-
if (!isObject(info) || !isString(info["id"]))
|
|
411
|
-
return;
|
|
412
|
-
sessionCache.cleanup(info["id"]);
|
|
413
|
-
deleteState(info["id"]);
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
case "permission.updated": {
|
|
417
|
-
const sessionID = event.properties["sessionID"];
|
|
418
|
-
if (!isString(sessionID))
|
|
419
|
-
return;
|
|
420
|
-
const state = getState(sessionID);
|
|
421
|
-
const tracking = getTracking(sessionID);
|
|
422
|
-
if (!state || state.mode !== "ENABLED" || !tracking)
|
|
423
|
-
return;
|
|
424
|
-
tracking.blockedByPermission = true;
|
|
425
|
-
const permType = isString(event.properties["type"]) ? event.properties["type"] : "unknown";
|
|
426
|
-
const patterns = event.properties["pattern"];
|
|
427
|
-
const patternStr = Array.isArray(patterns) ? patterns.filter(isString).join(", ") : isString(patterns) ? patterns : "";
|
|
428
|
-
tracking.permissionBlockMessage = `Denied ${permType} ${patternStr}`.trim();
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
386
|
+
function createApprovalState() {
|
|
387
|
+
return {
|
|
388
|
+
status: "idle",
|
|
389
|
+
pending_action: null,
|
|
390
|
+
pending_scope: null,
|
|
391
|
+
approved_scopes: [],
|
|
392
|
+
denied_scopes: [],
|
|
393
|
+
last_feedback: null
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function createTrustState(trustedPaths) {
|
|
397
|
+
return {
|
|
398
|
+
status: trustedPaths.length > 0 ? "trusted" : "untrusted",
|
|
399
|
+
trusted_paths: [...trustedPaths],
|
|
400
|
+
pending_path: null,
|
|
401
|
+
denied_paths: [],
|
|
402
|
+
last_feedback: null
|
|
432
403
|
};
|
|
433
404
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
return
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
if (permissionMode === "allow-all") {
|
|
444
|
-
output.status = "allow";
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
if (permissionMode === "limited") {
|
|
448
|
-
output.status = "deny";
|
|
449
|
-
deps.onPermissionDenied?.(input.sessionID, {
|
|
450
|
-
type: input.type,
|
|
451
|
-
pattern: input.pattern,
|
|
452
|
-
title: input.title
|
|
453
|
-
});
|
|
454
|
-
}
|
|
405
|
+
function createContextState(remainingBudget, threshold) {
|
|
406
|
+
const resolvedBudget = remainingBudget ?? null;
|
|
407
|
+
const compactionNeeded = resolvedBudget === null ? false : resolvedBudget <= threshold;
|
|
408
|
+
return {
|
|
409
|
+
remaining_budget: resolvedBudget,
|
|
410
|
+
threshold,
|
|
411
|
+
compaction_needed: compactionNeeded,
|
|
412
|
+
compacted_at: null,
|
|
413
|
+
unsafe_to_continue: compactionNeeded
|
|
455
414
|
};
|
|
456
415
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
416
|
+
function createRetryCounters(options) {
|
|
417
|
+
return {
|
|
418
|
+
step_retry_count: 0,
|
|
419
|
+
global_retry_count: 0,
|
|
420
|
+
no_progress_count: 0,
|
|
421
|
+
recovery_attempt_count: 0,
|
|
422
|
+
max_step_retries: options.maxStepRetries ?? DEFAULT_MAX_STEP_RETRIES,
|
|
423
|
+
max_global_retries: options.maxGlobalRetries ?? DEFAULT_MAX_GLOBAL_RETRIES,
|
|
424
|
+
max_no_progress: options.maxNoProgress ?? DEFAULT_MAX_NO_PROGRESS
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function createInitialState(goal, options = {}) {
|
|
428
|
+
const sessionID = options.sessionID ?? "";
|
|
429
|
+
const allowedPaths = options.allowedPaths ?? [];
|
|
430
|
+
return {
|
|
431
|
+
session_id: sessionID,
|
|
432
|
+
mode: options.mode ?? "DISABLED",
|
|
433
|
+
phase: options.phase ?? "STOPPED",
|
|
434
|
+
session_mode: options.sessionMode ?? "delegated-task",
|
|
435
|
+
goal,
|
|
436
|
+
plan_state: createPlanState(),
|
|
437
|
+
completion_evidence: [],
|
|
438
|
+
allowed_tools: [...options.allowedTools ?? DEFAULT_ALLOWED_TOOLS],
|
|
439
|
+
allowed_paths: [...allowedPaths],
|
|
440
|
+
approval_state: createApprovalState(),
|
|
441
|
+
trust_state: createTrustState(options.trustedPaths ?? allowedPaths),
|
|
442
|
+
context_state: createContextState(options.remainingBudget ?? null, options.contextThreshold ?? DEFAULT_CONTEXT_THRESHOLD),
|
|
443
|
+
foreground_action: null,
|
|
444
|
+
background_tasks: [],
|
|
445
|
+
retry_counters: createRetryCounters(options),
|
|
446
|
+
stop_reason: null,
|
|
447
|
+
latest_observations: {
|
|
448
|
+
events: [],
|
|
449
|
+
last_user_input: null,
|
|
450
|
+
last_tool_result: null,
|
|
451
|
+
last_tool_error: null,
|
|
452
|
+
last_interrupt: null
|
|
453
|
+
},
|
|
454
|
+
continuation_count: 0,
|
|
455
|
+
max_continues: options.maxContinues ?? DEFAULT_MAX_CONTINUES,
|
|
456
|
+
worker_agent: options.workerAgent ?? DEFAULT_WORKER_AGENT,
|
|
457
|
+
autonomous_strength: options.autonomousStrength ?? DEFAULT_AUTONOMOUS_STRENGTH,
|
|
458
|
+
last_updated_at: null,
|
|
459
|
+
resumable: true
|
|
484
460
|
};
|
|
485
461
|
}
|
|
462
|
+
function createSessionState(sessionID, goal, options = {}) {
|
|
463
|
+
return createInitialState(goal, {
|
|
464
|
+
...options,
|
|
465
|
+
sessionID,
|
|
466
|
+
mode: options.mode ?? "ENABLED",
|
|
467
|
+
phase: options.phase ?? "OBSERVE",
|
|
468
|
+
sessionMode: options.sessionMode ?? "delegated-task"
|
|
469
|
+
});
|
|
470
|
+
}
|
|
486
471
|
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (!state || state.mode !== "ENABLED") {
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
if (input.agent === CONTROL_AGENT) {
|
|
497
|
-
incrementSuppressCount(input.sessionID);
|
|
498
|
-
}
|
|
472
|
+
// state/session-cache.ts
|
|
473
|
+
function createSessionStore() {
|
|
474
|
+
return {
|
|
475
|
+
roles: new Map,
|
|
476
|
+
agents: new Map,
|
|
477
|
+
textParts: new Map
|
|
499
478
|
};
|
|
500
479
|
}
|
|
501
480
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (
|
|
507
|
-
return;
|
|
481
|
+
class SessionCache {
|
|
482
|
+
sessions = new Map;
|
|
483
|
+
ensureSession(sessionID) {
|
|
484
|
+
const existing = this.sessions.get(sessionID);
|
|
485
|
+
if (existing) {
|
|
486
|
+
return existing;
|
|
508
487
|
}
|
|
509
|
-
|
|
510
|
-
|
|
488
|
+
const created = createSessionStore();
|
|
489
|
+
this.sessions.set(sessionID, created);
|
|
490
|
+
return created;
|
|
491
|
+
}
|
|
492
|
+
setRole(sessionID, messageID, role) {
|
|
493
|
+
this.ensureSession(sessionID).roles.set(messageID, role);
|
|
494
|
+
}
|
|
495
|
+
getRole(sessionID, messageID) {
|
|
496
|
+
return this.sessions.get(sessionID)?.roles.get(messageID) ?? null;
|
|
497
|
+
}
|
|
498
|
+
setAgent(sessionID, messageID, agent) {
|
|
499
|
+
this.ensureSession(sessionID).agents.set(messageID, agent);
|
|
500
|
+
}
|
|
501
|
+
getAgent(sessionID, messageID) {
|
|
502
|
+
return this.sessions.get(sessionID)?.agents.get(messageID) ?? null;
|
|
503
|
+
}
|
|
504
|
+
setTextPart(sessionID, partID, messageID, text) {
|
|
505
|
+
this.ensureSession(sessionID).textParts.set(partID, {
|
|
506
|
+
id: partID,
|
|
507
|
+
messageID,
|
|
508
|
+
text
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
getTextPart(sessionID, partID) {
|
|
512
|
+
return this.sessions.get(sessionID)?.textParts.get(partID) ?? null;
|
|
513
|
+
}
|
|
514
|
+
getMessageText(sessionID, messageID) {
|
|
515
|
+
const session = this.sessions.get(sessionID);
|
|
516
|
+
if (!session) {
|
|
517
|
+
return "";
|
|
518
|
+
}
|
|
519
|
+
return [...session.textParts.values()].filter((part) => part.messageID === messageID).map((part) => part.text).join("");
|
|
520
|
+
}
|
|
521
|
+
snapshot(sessionID) {
|
|
522
|
+
const session = this.sessions.get(sessionID) ?? createSessionStore();
|
|
523
|
+
return {
|
|
524
|
+
roles: Object.fromEntries(session.roles),
|
|
525
|
+
agents: Object.fromEntries(session.agents),
|
|
526
|
+
textParts: [...session.textParts.values()]
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
cleanup(sessionID) {
|
|
530
|
+
this.sessions.delete(sessionID);
|
|
531
|
+
}
|
|
511
532
|
}
|
|
512
533
|
|
|
513
|
-
// tools/
|
|
534
|
+
// tools/autopilot.ts
|
|
514
535
|
import { tool } from "@opencode-ai/plugin";
|
|
515
|
-
var AUTOPILOT_FALLBACK_AGENT = "pi";
|
|
516
|
-
function createStartTool(deps) {
|
|
517
|
-
return tool({
|
|
518
|
-
description: "Arm autopilot mode for the current session",
|
|
519
|
-
args: {
|
|
520
|
-
task: tool.schema.string().min(1).describe("Task for the autonomous worker to execute"),
|
|
521
|
-
permissionMode: tool.schema.enum(["limited", "allow-all"]).optional().describe("How permissions should behave while autopilot is active"),
|
|
522
|
-
maxContinues: tool.schema.number().int().positive().optional().describe("Maximum number of autonomous continuation prompts after the initial task prompt"),
|
|
523
|
-
workerAgent: tool.schema.string().optional().describe("Agent used for autonomous follow-up turns (defaults to pi)")
|
|
524
|
-
},
|
|
525
|
-
async execute(args, context) {
|
|
526
|
-
if (args.task.trim().toLowerCase() === "help") {
|
|
527
|
-
return `
|
|
528
|
-
## Autopilot Usage
|
|
529
536
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
Examples:
|
|
536
|
-
- \`Fix the failing tests\`
|
|
537
|
-
- \`Use allow-all mode and build-high to refactor the reducer\`
|
|
537
|
+
// tools/usage.ts
|
|
538
|
+
function buildAutopilotUsage() {
|
|
539
|
+
return `
|
|
540
|
+
## Autopilot Usage
|
|
538
541
|
|
|
539
|
-
|
|
540
|
-
- \`
|
|
541
|
-
- \`
|
|
542
|
+
Primary workflow:
|
|
543
|
+
- \`/autopilot on\` — enable session autopilot defaults
|
|
544
|
+
- \`/autopilot off\` — disable autopilot for this session
|
|
545
|
+
- \`/autopilot status\` — show current status and recent events
|
|
546
|
+
- \`/autopilot <task>\` — enable autopilot and hand a long-running task to the delegate agent
|
|
542
547
|
|
|
543
|
-
|
|
544
|
-
- \`
|
|
545
|
-
- \`
|
|
548
|
+
Direct tool equivalents:
|
|
549
|
+
- \`autopilot(action="on")\`
|
|
550
|
+
- \`autopilot(action="off")\`
|
|
551
|
+
- \`autopilot(action="status")\`
|
|
552
|
+
- \`autopilot(task="Fix the failing tests")\`
|
|
553
|
+
- \`autopilot(action="on", autonomousStrength="aggressive")\`
|
|
546
554
|
|
|
547
555
|
Defaults:
|
|
548
556
|
- permission mode: \`limited\`
|
|
549
557
|
- continuation limit: \`10\`
|
|
550
|
-
-
|
|
551
|
-
|
|
558
|
+
- delegate agent: \`general\`
|
|
559
|
+
- autonomous strength: \`balanced\`
|
|
560
|
+
|
|
561
|
+
Autonomous strength modes:
|
|
562
|
+
- \`conservative\` — soft guidance to prefer defaults, asks when unsure (similar to previous behavior)
|
|
563
|
+
- \`balanced\` — stronger bias toward selecting recommended/safe defaults, minimal user interaction (default)
|
|
564
|
+
- \`aggressive\` — always pick recommended/safe defaults for routine choices, only escalate high-impact decisions
|
|
565
|
+
|
|
566
|
+
Notes:
|
|
567
|
+
- Session autopilot makes OpenCode act more autonomously and ask fewer questions based on the configured autonomous strength.
|
|
568
|
+
- Long-running delegated tasks run through the configured agent and continue until complete, blocked, or the continuation limit is reached.
|
|
569
|
+
- OpenCode does not currently expose a general question-timeout hook, so autopilot can only auto-handle permission prompts directly. For everything else, the injected system guidance tells the active agent to prefer recommended defaults when safe. The autonomous strength parameter controls how strongly this guidance is worded.
|
|
570
|
+
`.trim();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// tools/autopilot.ts
|
|
574
|
+
var AUTOPILOT_FALLBACK_AGENT = "pi";
|
|
575
|
+
function createAutopilotTool(deps) {
|
|
576
|
+
return tool({
|
|
577
|
+
description: "Control session autopilot: turn it on or off, check status, or start a long-running delegated task",
|
|
578
|
+
args: {
|
|
579
|
+
action: tool.schema.enum(["on", "off", "status", "help"]).optional().describe("Autopilot command: on, off, status, or help"),
|
|
580
|
+
task: tool.schema.string().optional().describe("Optional delegated task to hand to the configured agent"),
|
|
581
|
+
permissionMode: tool.schema.enum(["limited", "allow-all"]).optional().describe("How permissions should behave while autopilot is active"),
|
|
582
|
+
maxContinues: tool.schema.number().int().positive().optional().describe("Maximum number of autonomous continuation prompts"),
|
|
583
|
+
workerAgent: tool.schema.string().optional().describe("Delegate agent used for long-running autopilot tasks"),
|
|
584
|
+
autonomousStrength: tool.schema.enum(["conservative", "balanced", "aggressive"]).optional().describe("How strongly autopilot prefers defaults: conservative (soft guidance), balanced (default, stronger guidance), aggressive (always pick recommended/safe defaults)")
|
|
585
|
+
},
|
|
586
|
+
async execute(args, context) {
|
|
587
|
+
const task = args.task?.trim() ?? "";
|
|
588
|
+
const action = args.action ?? (task ? "on" : "help");
|
|
589
|
+
if (action === "help" || !args.action && task.toLowerCase() === "help") {
|
|
590
|
+
return buildAutopilotUsage();
|
|
591
|
+
}
|
|
592
|
+
if (action === "status") {
|
|
593
|
+
const state2 = deps.getState(context.sessionID);
|
|
594
|
+
if (!state2) {
|
|
595
|
+
return deps.summarizeState(state2);
|
|
596
|
+
}
|
|
597
|
+
const history = deps.getHistory(context.sessionID);
|
|
598
|
+
const historyStr = history.length > 0 ? `
|
|
599
|
+
Recent events:
|
|
600
|
+
- ${history.join(`
|
|
601
|
+
- `)}` : "";
|
|
602
|
+
return `${deps.summarizeState(state2)}${historyStr}`;
|
|
603
|
+
}
|
|
604
|
+
if (action === "off") {
|
|
605
|
+
const state2 = deps.getState(context.sessionID);
|
|
606
|
+
if (!state2 || state2.mode !== "ENABLED") {
|
|
607
|
+
return "Autopilot is not running in this session.";
|
|
608
|
+
}
|
|
609
|
+
deps.onStop(context.sessionID, task || undefined);
|
|
610
|
+
return task ? `Autopilot stopped: ${task}` : "Autopilot stopped for this session.";
|
|
552
611
|
}
|
|
553
612
|
const permissionMode = args.permissionMode ?? "limited";
|
|
554
613
|
const maxContinues = deps.normalizeMaxContinues(args.maxContinues);
|
|
555
614
|
const workerAgent = args.workerAgent?.trim() || deps.defaultWorkerAgent || AUTOPILOT_FALLBACK_AGENT;
|
|
556
|
-
const
|
|
615
|
+
const autonomousStrength = args.autonomousStrength ?? "balanced";
|
|
616
|
+
const state = deps.createSessionState(context.sessionID, task, {
|
|
557
617
|
maxContinues,
|
|
558
|
-
workerAgent
|
|
618
|
+
workerAgent,
|
|
619
|
+
autonomousStrength,
|
|
620
|
+
sessionMode: task ? "delegated-task" : "session-defaults"
|
|
559
621
|
});
|
|
560
622
|
Object.defineProperty(state, "permissionMode", {
|
|
561
623
|
value: permissionMode,
|
|
@@ -566,103 +628,29 @@ Defaults:
|
|
|
566
628
|
deps.setState(context.sessionID, state);
|
|
567
629
|
deps.initSession(context.sessionID);
|
|
568
630
|
context.metadata({
|
|
569
|
-
title: "Autopilot
|
|
631
|
+
title: task ? "Autopilot task started" : "Autopilot enabled",
|
|
570
632
|
metadata: {
|
|
633
|
+
action,
|
|
571
634
|
permissionMode,
|
|
572
635
|
maxContinues,
|
|
573
|
-
workerAgent
|
|
636
|
+
workerAgent,
|
|
637
|
+
autonomousStrength,
|
|
638
|
+
task: task || null
|
|
574
639
|
}
|
|
575
640
|
});
|
|
576
641
|
await deps.onArmed(context.sessionID, state);
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// tools/status.ts
|
|
583
|
-
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
584
|
-
function createStatusTool(deps) {
|
|
585
|
-
return tool2({
|
|
586
|
-
description: "Show autopilot status for the current session",
|
|
587
|
-
args: {},
|
|
588
|
-
async execute(_args, context) {
|
|
589
|
-
const state = deps.getState(context.sessionID);
|
|
590
|
-
if (!state) {
|
|
591
|
-
return deps.summarizeState(state);
|
|
592
|
-
}
|
|
593
|
-
const history = deps.getHistory(context.sessionID);
|
|
594
|
-
const historyStr = history.length > 0 ? `
|
|
595
|
-
Recent events:
|
|
596
|
-
- ${history.join(`
|
|
597
|
-
- `)}` : "";
|
|
598
|
-
return `${deps.summarizeState(state)}${historyStr}`;
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// tools/stop.ts
|
|
604
|
-
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
605
|
-
function createStopTool(deps) {
|
|
606
|
-
return tool3({
|
|
607
|
-
description: "Stop autopilot mode for the current session",
|
|
608
|
-
args: {
|
|
609
|
-
reason: tool3.schema.string().optional().describe("Optional reason to include in the stop message")
|
|
610
|
-
},
|
|
611
|
-
async execute(args, context) {
|
|
612
|
-
const state = deps.getState(context.sessionID);
|
|
613
|
-
if (!state || state.mode !== "ENABLED") {
|
|
614
|
-
return "Autopilot is not running in this session.";
|
|
642
|
+
if (state.session_mode === "session-defaults") {
|
|
643
|
+
return `Autopilot is enabled in ${permissionMode} mode for this session. OpenCode will prefer reasonable defaults, ask fewer questions, and keep using ${workerAgent} for delegated work when you hand it a task.`;
|
|
615
644
|
}
|
|
616
|
-
|
|
617
|
-
return args.reason ? `Autopilot stopped: ${args.reason}` : "Autopilot stopped for this session.";
|
|
618
|
-
}
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// tools/help.ts
|
|
623
|
-
import { tool as tool4 } from "@opencode-ai/plugin";
|
|
624
|
-
function createHelpTool() {
|
|
625
|
-
return tool4({
|
|
626
|
-
description: "Show autopilot usage instructions",
|
|
627
|
-
args: {},
|
|
628
|
-
execute: async () => {
|
|
629
|
-
return `
|
|
630
|
-
## Autopilot Usage
|
|
631
|
-
|
|
632
|
-
Use the global \`Autopilot\` agent to control the autopilot plugin.
|
|
633
|
-
|
|
634
|
-
**Start Autopilot:**
|
|
635
|
-
Switch to the \`Autopilot\` agent, then send the task you want delegated.
|
|
636
|
-
|
|
637
|
-
Examples:
|
|
638
|
-
- \`Fix the failing tests\`
|
|
639
|
-
- \`Use allow-all mode and build-high to refactor the reducer\`
|
|
640
|
-
|
|
641
|
-
**Check Status:**
|
|
642
|
-
- \`status\`
|
|
643
|
-
- \`is autopilot running?\`
|
|
644
|
-
|
|
645
|
-
**Stop Autopilot:**
|
|
646
|
-
- \`stop\`
|
|
647
|
-
- \`stop because I want to inspect manually\`
|
|
648
|
-
|
|
649
|
-
Defaults:
|
|
650
|
-
- permission mode: \`limited\`
|
|
651
|
-
- continuation limit: \`10\`
|
|
652
|
-
- worker agent: \`pi\`
|
|
653
|
-
`.trim();
|
|
645
|
+
return `Autopilot enabled in ${permissionMode} mode with ${workerAgent}. It will start the delegated task after this response and may continue up to ${maxContinues} times.`;
|
|
654
646
|
}
|
|
655
647
|
});
|
|
656
648
|
}
|
|
657
649
|
|
|
658
650
|
// plugin.ts
|
|
659
|
-
var AUTOPILOT_FALLBACK_AGENT2 = "
|
|
651
|
+
var AUTOPILOT_FALLBACK_AGENT2 = "general";
|
|
660
652
|
var MAX_HISTORY_ENTRIES = 10;
|
|
661
|
-
var AutopilotPlugin = async ({
|
|
662
|
-
client,
|
|
663
|
-
directory,
|
|
664
|
-
worktree
|
|
665
|
-
}) => {
|
|
653
|
+
var AutopilotPlugin = async ({ client, directory, worktree }) => {
|
|
666
654
|
const stateBySession = new Map;
|
|
667
655
|
const trackingBySession = new Map;
|
|
668
656
|
const suppressCountBySession = new Map;
|
|
@@ -700,12 +688,12 @@ var AutopilotPlugin = async ({
|
|
|
700
688
|
const safeToast = async (opts) => {
|
|
701
689
|
try {
|
|
702
690
|
await client.tui.showToast({
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
691
|
+
body: {
|
|
692
|
+
title: opts.title,
|
|
693
|
+
message: opts.message,
|
|
694
|
+
variant: opts.variant,
|
|
695
|
+
duration: 3000
|
|
696
|
+
}
|
|
709
697
|
});
|
|
710
698
|
} catch {}
|
|
711
699
|
};
|
|
@@ -744,7 +732,7 @@ var AutopilotPlugin = async ({
|
|
|
744
732
|
const tracking = getTracking(sessionID);
|
|
745
733
|
if (!state || state.mode !== "ENABLED" || !tracking)
|
|
746
734
|
return;
|
|
747
|
-
if (state.phase === "OBSERVE" && state.continuation_count === 0 && !tracking.lastAssistantMessageID) {
|
|
735
|
+
if (state.phase === "OBSERVE" && state.continuation_count === 0 && !tracking.lastAssistantMessageID && state.session_mode === "delegated-task") {
|
|
748
736
|
recordHistory(sessionID, `Starting task with ${state.worker_agent}`);
|
|
749
737
|
await safeToast({
|
|
750
738
|
title: "Autopilot armed",
|
|
@@ -841,48 +829,44 @@ var AutopilotPlugin = async ({
|
|
|
841
829
|
const toolAfterHook = createToolAfterHook({
|
|
842
830
|
stripMarker: stripAutopilotMarker
|
|
843
831
|
});
|
|
844
|
-
const
|
|
832
|
+
const stopSession = (sessionID, reason) => {
|
|
833
|
+
const state = getState(sessionID);
|
|
834
|
+
if (!state)
|
|
835
|
+
return;
|
|
836
|
+
state.mode = "DISABLED";
|
|
837
|
+
state.phase = "STOPPED";
|
|
838
|
+
state.stop_reason = "USER_STOP";
|
|
839
|
+
recordHistory(sessionID, reason ? `Cancelled by user: ${reason}` : "Cancelled by user");
|
|
840
|
+
};
|
|
841
|
+
const autopilotTool = createAutopilotTool({
|
|
845
842
|
getState,
|
|
846
843
|
setState,
|
|
847
844
|
createSessionState,
|
|
848
845
|
normalizeMaxContinues,
|
|
849
846
|
initSession,
|
|
847
|
+
summarizeState: summarizeAutopilotState,
|
|
848
|
+
getHistory: (sessionID) => historyBySession.get(sessionID) ?? [],
|
|
849
|
+
onStop: stopSession,
|
|
850
850
|
defaultWorkerAgent: AUTOPILOT_FALLBACK_AGENT2,
|
|
851
851
|
onArmed: async (sessionID, state) => {
|
|
852
|
-
const pm = state
|
|
852
|
+
const pm = state.permissionMode;
|
|
853
853
|
if (pm === "allow-all" || pm === "limited") {
|
|
854
854
|
permissionModeBySession.set(sessionID, pm);
|
|
855
855
|
} else {
|
|
856
856
|
permissionModeBySession.set(sessionID, "limited");
|
|
857
857
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
});
|
|
862
|
-
const statusTool = createStatusTool({
|
|
863
|
-
getState,
|
|
864
|
-
summarizeState: summarizeAutopilotState,
|
|
865
|
-
getHistory: (sessionID) => historyBySession.get(sessionID) ?? []
|
|
866
|
-
});
|
|
867
|
-
const stopTool = createStopTool({
|
|
868
|
-
getState,
|
|
869
|
-
onStop: (sessionID, reason) => {
|
|
870
|
-
const state = getState(sessionID);
|
|
871
|
-
if (!state)
|
|
858
|
+
if (state.session_mode === "delegated-task") {
|
|
859
|
+
recordHistory(sessionID, `Delegated task armed in ${permissionModeBySession.get(sessionID)} mode with ${state.worker_agent}.`);
|
|
860
|
+
recordHistory(sessionID, `Continuation limit: ${state.max_continues}.`);
|
|
872
861
|
return;
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
state.
|
|
876
|
-
recordHistory(sessionID, reason ? `Cancelled by user: ${reason}` : "Cancelled by user");
|
|
862
|
+
}
|
|
863
|
+
recordHistory(sessionID, `Session autopilot enabled in ${permissionModeBySession.get(sessionID)} mode.`);
|
|
864
|
+
recordHistory(sessionID, `Delegate agent ready: ${state.worker_agent}. Long-running tasks will use this agent.`);
|
|
877
865
|
}
|
|
878
866
|
});
|
|
879
|
-
const helpTool = createHelpTool();
|
|
880
867
|
return {
|
|
881
868
|
tool: {
|
|
882
|
-
|
|
883
|
-
autopilot_status: statusTool,
|
|
884
|
-
autopilot_stop: stopTool,
|
|
885
|
-
autopilot_help: helpTool
|
|
869
|
+
autopilot: autopilotTool
|
|
886
870
|
},
|
|
887
871
|
event: eventHandler,
|
|
888
872
|
"permission.ask": permissionHook,
|
|
@@ -895,5 +879,5 @@ export {
|
|
|
895
879
|
AutopilotPlugin
|
|
896
880
|
};
|
|
897
881
|
|
|
898
|
-
//# debugId=
|
|
882
|
+
//# debugId=4E4C5F5E313D2F9664756E2164756E21
|
|
899
883
|
//# sourceMappingURL=index.js.map
|