@withakay/opencode-autopilot 0.1.1 → 0.3.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/dist/index.js CHANGED
@@ -1,180 +1,236 @@
1
- // state/session-cache.ts
2
- function createSessionStore() {
3
- return {
4
- roles: new Map,
5
- agents: new Map,
6
- textParts: new Map
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
- const created = createSessionStore();
18
- this.sessions.set(sessionID, created);
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
- return [...session.textParts.values()].filter((part) => part.messageID === messageID).map((part) => part.text).join("");
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
- // state/factory.ts
64
- var DEFAULT_ALLOWED_TOOLS = ["bash", "read", "glob", "grep", "apply_patch"];
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
- steps: [],
74
- open_items: [],
75
- completed_items: [],
76
- blocked_items: [],
77
- dependencies: {},
78
- stale: false
19
+ lastAssistantMessageID: undefined,
20
+ lastUsage: undefined,
21
+ awaitingWorkerReply: false,
22
+ blockedByPermission: false,
23
+ permissionBlockMessage: undefined
79
24
  };
80
25
  }
81
- function createApprovalState() {
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 createTrustState(trustedPaths) {
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 createContextState(remainingBudget, threshold) {
101
- const resolvedBudget = remainingBudget ?? null;
102
- const compactionNeeded = resolvedBudget !== null ? resolvedBudget <= threshold : false;
103
- return {
104
- remaining_budget: resolvedBudget,
105
- threshold,
106
- compaction_needed: compactionNeeded,
107
- compacted_at: null,
108
- unsafe_to_continue: compactionNeeded
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
- function createRetryCounters(options) {
112
- return {
113
- step_retry_count: 0,
114
- global_retry_count: 0,
115
- no_progress_count: 0,
116
- recovery_attempt_count: 0,
117
- max_step_retries: options.maxStepRetries ?? DEFAULT_MAX_STEP_RETRIES,
118
- max_global_retries: options.maxGlobalRetries ?? DEFAULT_MAX_GLOBAL_RETRIES,
119
- max_no_progress: options.maxNoProgress ?? DEFAULT_MAX_NO_PROGRESS
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
- function createInitialState(goal, options = {}) {
123
- const sessionID = options.sessionID ?? "";
124
- const allowedPaths = options.allowedPaths ?? [];
125
- return {
126
- session_id: sessionID,
127
- mode: options.mode ?? "DISABLED",
128
- phase: options.phase ?? "STOPPED",
129
- goal,
130
- plan_state: createPlanState(),
131
- completion_evidence: [],
132
- allowed_tools: [...options.allowedTools ?? DEFAULT_ALLOWED_TOOLS],
133
- allowed_paths: [...allowedPaths],
134
- approval_state: createApprovalState(),
135
- trust_state: createTrustState(options.trustedPaths ?? allowedPaths),
136
- context_state: createContextState(options.remainingBudget ?? null, options.contextThreshold ?? DEFAULT_CONTEXT_THRESHOLD),
137
- foreground_action: null,
138
- background_tasks: [],
139
- retry_counters: createRetryCounters(options),
140
- stop_reason: null,
141
- latest_observations: {
142
- events: [],
143
- last_user_input: null,
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
- function createSessionState(sessionID, goal, options = {}) {
156
- return createInitialState(goal, {
157
- ...options,
158
- sessionID,
159
- mode: options.mode ?? "ENABLED",
160
- phase: options.phase ?? "OBSERVE"
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
165
202
  function buildContinuationPrompt(options) {
203
+ if (options.isValidation) {
204
+ return [
205
+ `Autopilot VALIDATION checkpoint ${options.continueCount}/${options.maxContinues}.`,
206
+ "You previously marked this task as potentially complete, but you MUST validate your work before marking it COMPLETE.",
207
+ `Original task: ${options.task}`,
208
+ "",
209
+ "VALIDATION CHECKLIST - Complete ALL of these:",
210
+ "1. Verify any files you created/modified actually exist and have correct content",
211
+ "2. Run any tests or check outputs if applicable",
212
+ "3. Confirm the task requirements are FULLY satisfied",
213
+ "4. Check for any errors, TODOs, or incomplete work",
214
+ "",
215
+ "IF validation passes and you're 100% confident: mark COMPLETE",
216
+ "IF you find issues: mark CONTINUE and fix them",
217
+ "IF uncertain: mark VALIDATE again after gathering more info"
218
+ ].join(`
219
+ `);
220
+ }
166
221
  return [
167
222
  `Autopilot continuation ${options.continueCount}/${options.maxContinues}.`,
168
223
  "Continue working autonomously on the same task.",
169
224
  `Original task: ${options.task}`,
170
225
  "Review your latest progress, choose the next concrete step, and keep moving without waiting for the user.",
171
- "If you are done, provide the result and mark the response complete.",
172
- "If you are blocked by missing information or denied permissions, explain the blocker and mark the response blocked."
226
+ "Use VALIDATE when you think you might be done but need to verify your work.",
227
+ "Use COMPLETE only when you are highly confident the task is fully done (gamble-your-house confident).",
228
+ "Use BLOCKED if missing information or denied permissions."
173
229
  ].join(`
174
230
  `);
175
231
  }
176
232
  // prompts/directives.ts
177
- var AUTOPILOT_MARKER_RE = /\n?<autopilot\s+status="(continue|complete|blocked)">([\s\S]*?)<\/autopilot>\s*$/i;
233
+ var AUTOPILOT_MARKER_RE = /\n?<autopilot\s+status="(continue|validate|complete|blocked)">([\s\S]*?)<\/autopilot>\s*$/i;
178
234
  var BLOCKED_HINT_RE = /(need (more|additional) information|cannot continue|can't continue|blocked|waiting for user|please provide|which option|what should i|what would you like)/i;
179
235
  function parseAutopilotMarker(text) {
180
236
  const match = text.match(AUTOPILOT_MARKER_RE);
@@ -224,6 +280,9 @@ function defaultReason(status) {
224
280
  if (status === "blocked") {
225
281
  return "Task is blocked.";
226
282
  }
283
+ if (status === "validate") {
284
+ return "Task needs validation before marking complete.";
285
+ }
227
286
  return "More work remains.";
228
287
  }
229
288
  // prompts/format.ts
@@ -262,9 +321,13 @@ function summarizeAutopilotState(state) {
262
321
  const status = [
263
322
  `phase=${state.phase}`,
264
323
  `mode=${state.mode}`,
324
+ `session_mode=${state.session_mode}`,
265
325
  `continues=${state.continuation_count}/${state.max_continues}`,
266
326
  `agent=${state.worker_agent}`
267
327
  ];
328
+ if (state.session_mode === "delegated-task") {
329
+ status.push(`task=${JSON.stringify(state.goal)}`);
330
+ }
268
331
  if (state.stop_reason) {
269
332
  status.push(`stop=${state.stop_reason}`);
270
333
  }
@@ -285,277 +348,299 @@ function normalizeMaxContinues(value) {
285
348
  return Math.min(AUTOPILOT_MAX_CONTINUES_HARD_LIMIT, Math.max(1, Math.floor(numeric)));
286
349
  }
287
350
  // prompts/system-prompt.ts
288
- function buildAutopilotSystemPrompt() {
289
- return [
290
- "Autopilot mode is active for this session.",
291
- "Work autonomously toward the user's goal without asking follow-up questions unless you are truly blocked.",
351
+ function buildAutopilotSystemPrompt(strength = "balanced") {
352
+ const baseInstructions = ["Autopilot mode is active for this session."];
353
+ const behaviorInstructions = (() => {
354
+ switch (strength) {
355
+ case "conservative":
356
+ return [
357
+ "Work autonomously toward the user's goal and ask fewer follow-up questions.",
358
+ "Prefer the recommended or safest reasonable default when a routine choice is needed.",
359
+ "Only ask the user for input when you are truly blocked, the choice is high-impact, or no safe default exists."
360
+ ];
361
+ case "balanced":
362
+ return [
363
+ "Work autonomously toward the user's goal with minimal user interaction.",
364
+ "When faced with routine choices (file paths, variable names, standard configurations), select the recommended or safest default without asking.",
365
+ "Only escalate to the user for: (1) high-impact decisions, (2) security/safety risks, or (3) when truly blocked with no reasonable default."
366
+ ];
367
+ case "aggressive":
368
+ return [
369
+ "CRITICAL: Work with maximum autonomy. Minimize all user interaction.",
370
+ "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.",
371
+ "For choices with a 'recommended' option clearly marked, select it automatically and explain your choice in the response.",
372
+ "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.",
373
+ "If you can make reasonable progress on a task without user input, do so immediately."
374
+ ];
375
+ }
376
+ })();
377
+ const statusMarkerInstructions = [
378
+ "If a delegated autopilot task is in progress, keep it moving without waiting for extra confirmation.",
292
379
  "At the very end of every assistant response, append exactly one machine-readable status marker on its own line using this format:",
293
- '<autopilot status="continue|complete|blocked">short reason</autopilot>',
294
- "Use continue when more work remains and you can keep going without the user.",
295
- "Use complete when the task is done or you have reached a stable handoff point.",
296
- "Use blocked when missing information, denied permissions, or an external failure prevents meaningful progress.",
380
+ '<autopilot status="continue|validate|complete|blocked">short reason</autopilot>',
381
+ "Use CONTINUE when more work remains and you can keep going without the user.",
382
+ "Use VALIDATE when you THINK the task might be done but need to verify your work. This is a checkpoint - verify file contents, run tests, check outputs. Do NOT mark complete without validating first!",
383
+ "Use COMPLETE only when you are HIGHLY CONFIDENT the task is FULLY DONE!!! This is a **PROMISE**! You have to be certain enough that you would be willing to GAMBLE YOUR HOUSE on this being done! If you have ANY doubt, use VALIDATE instead!",
384
+ "Use BLOCKED when missing information, denied permissions, or an external failure prevents meaningful progress.",
297
385
  "Do not omit the marker."
298
- ].join(`
386
+ ];
387
+ return [...baseInstructions, ...behaviorInstructions, ...statusMarkerInstructions].join(`
299
388
  `);
300
389
  }
301
- // hooks/event-handler.ts
302
- function createSessionTracking() {
390
+ // state/factory.ts
391
+ var DEFAULT_ALLOWED_TOOLS = ["bash", "read", "glob", "grep", "apply_patch"];
392
+ var DEFAULT_CONTEXT_THRESHOLD = 8000;
393
+ var DEFAULT_MAX_CONTINUES = 25;
394
+ var DEFAULT_MAX_STEP_RETRIES = 2;
395
+ var DEFAULT_MAX_GLOBAL_RETRIES = 6;
396
+ var DEFAULT_MAX_NO_PROGRESS = 3;
397
+ var DEFAULT_WORKER_AGENT = "pi";
398
+ var DEFAULT_AUTONOMOUS_STRENGTH = "balanced";
399
+ function createPlanState() {
303
400
  return {
304
- lastAssistantMessageID: undefined,
305
- lastUsage: undefined,
306
- awaitingWorkerReply: false,
307
- blockedByPermission: false,
308
- permissionBlockMessage: undefined
401
+ steps: [],
402
+ open_items: [],
403
+ completed_items: [],
404
+ blocked_items: [],
405
+ dependencies: {},
406
+ stale: false
309
407
  };
310
408
  }
311
- function isString(value) {
312
- return typeof value === "string";
313
- }
314
- function isObject(value) {
315
- return typeof value === "object" && value !== null;
409
+ function createApprovalState() {
410
+ return {
411
+ status: "idle",
412
+ pending_action: null,
413
+ pending_scope: null,
414
+ approved_scopes: [],
415
+ denied_scopes: [],
416
+ last_feedback: null
417
+ };
316
418
  }
317
- function normalizeError(error) {
318
- if (!isObject(error))
319
- return;
320
- const result = {};
321
- if (isString(error["name"])) {
322
- result.name = error["name"];
323
- }
324
- if (isObject(error["data"]) && isString(error["data"]["message"])) {
325
- result.data = { message: error["data"]["message"] };
326
- }
327
- return result;
419
+ function createTrustState(trustedPaths) {
420
+ return {
421
+ status: trustedPaths.length > 0 ? "trusted" : "untrusted",
422
+ trusted_paths: [...trustedPaths],
423
+ pending_path: null,
424
+ denied_paths: [],
425
+ last_feedback: null
426
+ };
328
427
  }
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
- }
428
+ function createContextState(remainingBudget, threshold) {
429
+ const resolvedBudget = remainingBudget ?? null;
430
+ const compactionNeeded = resolvedBudget === null ? false : resolvedBudget <= threshold;
431
+ return {
432
+ remaining_budget: resolvedBudget,
433
+ threshold,
434
+ compaction_needed: compactionNeeded,
435
+ compacted_at: null,
436
+ unsafe_to_continue: compactionNeeded
432
437
  };
433
438
  }
434
-
435
- // hooks/permission.ts
436
- function createPermissionHook(deps) {
437
- return async (input, output) => {
438
- const state = deps.getState(input.sessionID);
439
- if (!state || state.mode !== "ENABLED") {
440
- return;
441
- }
442
- const permissionMode = deps.getPermissionMode(input.sessionID);
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
- }
439
+ function createRetryCounters(options) {
440
+ return {
441
+ step_retry_count: 0,
442
+ global_retry_count: 0,
443
+ no_progress_count: 0,
444
+ recovery_attempt_count: 0,
445
+ max_step_retries: options.maxStepRetries ?? DEFAULT_MAX_STEP_RETRIES,
446
+ max_global_retries: options.maxGlobalRetries ?? DEFAULT_MAX_GLOBAL_RETRIES,
447
+ max_no_progress: options.maxNoProgress ?? DEFAULT_MAX_NO_PROGRESS
455
448
  };
456
449
  }
457
-
458
- // hooks/system-transform.ts
459
- function createSystemTransformHook(deps) {
460
- const {
461
- getState,
462
- getSuppressCount,
463
- decrementSuppressCount,
464
- buildSystemPrompt
465
- } = deps;
466
- return async (input, output) => {
467
- const { sessionID } = input;
468
- if (sessionID === undefined) {
469
- return;
470
- }
471
- const state = getState(sessionID);
472
- if (!state || state.mode !== "ENABLED") {
473
- return;
474
- }
475
- const suppressCount = getSuppressCount(sessionID);
476
- if (suppressCount > 0) {
477
- decrementSuppressCount(sessionID);
478
- return;
479
- }
480
- if (!Array.isArray(output.system)) {
481
- output.system = [];
482
- }
483
- output.system.push(buildSystemPrompt());
450
+ function createInitialState(goal, options = {}) {
451
+ const sessionID = options.sessionID ?? "";
452
+ const allowedPaths = options.allowedPaths ?? [];
453
+ return {
454
+ session_id: sessionID,
455
+ mode: options.mode ?? "DISABLED",
456
+ phase: options.phase ?? "STOPPED",
457
+ session_mode: options.sessionMode ?? "delegated-task",
458
+ goal,
459
+ plan_state: createPlanState(),
460
+ completion_evidence: [],
461
+ allowed_tools: [...options.allowedTools ?? DEFAULT_ALLOWED_TOOLS],
462
+ allowed_paths: [...allowedPaths],
463
+ approval_state: createApprovalState(),
464
+ trust_state: createTrustState(options.trustedPaths ?? allowedPaths),
465
+ context_state: createContextState(options.remainingBudget ?? null, options.contextThreshold ?? DEFAULT_CONTEXT_THRESHOLD),
466
+ foreground_action: null,
467
+ background_tasks: [],
468
+ retry_counters: createRetryCounters(options),
469
+ stop_reason: null,
470
+ latest_observations: {
471
+ events: [],
472
+ last_user_input: null,
473
+ last_tool_result: null,
474
+ last_tool_error: null,
475
+ last_interrupt: null
476
+ },
477
+ continuation_count: 0,
478
+ max_continues: options.maxContinues ?? DEFAULT_MAX_CONTINUES,
479
+ worker_agent: options.workerAgent ?? DEFAULT_WORKER_AGENT,
480
+ autonomous_strength: options.autonomousStrength ?? DEFAULT_AUTONOMOUS_STRENGTH,
481
+ last_updated_at: null,
482
+ resumable: true
484
483
  };
485
484
  }
485
+ function createSessionState(sessionID, goal, options = {}) {
486
+ return createInitialState(goal, {
487
+ ...options,
488
+ sessionID,
489
+ mode: options.mode ?? "ENABLED",
490
+ phase: options.phase ?? "OBSERVE",
491
+ sessionMode: options.sessionMode ?? "delegated-task"
492
+ });
493
+ }
486
494
 
487
- // hooks/chat-message.ts
488
- var CONTROL_AGENT = "autopilot";
489
- function createChatMessageHook(deps) {
490
- const { getState, incrementSuppressCount } = deps;
491
- return async (input, _output) => {
492
- const state = getState(input.sessionID);
493
- if (!state || state.mode !== "ENABLED") {
494
- return;
495
- }
496
- if (input.agent === CONTROL_AGENT) {
497
- incrementSuppressCount(input.sessionID);
498
- }
495
+ // state/session-cache.ts
496
+ function createSessionStore() {
497
+ return {
498
+ roles: new Map,
499
+ agents: new Map,
500
+ textParts: new Map
499
501
  };
500
502
  }
501
503
 
502
- // hooks/tool-after.ts
503
- var AUTOPILOT_STATUS_TOOL = "autopilot_status";
504
- function createToolAfterHook(deps) {
505
- return async (input, output) => {
506
- if (input.tool !== AUTOPILOT_STATUS_TOOL) {
507
- return;
504
+ class SessionCache {
505
+ sessions = new Map;
506
+ ensureSession(sessionID) {
507
+ const existing = this.sessions.get(sessionID);
508
+ if (existing) {
509
+ return existing;
508
510
  }
509
- output.output = deps.stripMarker(output.output);
510
- };
511
+ const created = createSessionStore();
512
+ this.sessions.set(sessionID, created);
513
+ return created;
514
+ }
515
+ setRole(sessionID, messageID, role) {
516
+ this.ensureSession(sessionID).roles.set(messageID, role);
517
+ }
518
+ getRole(sessionID, messageID) {
519
+ return this.sessions.get(sessionID)?.roles.get(messageID) ?? null;
520
+ }
521
+ setAgent(sessionID, messageID, agent) {
522
+ this.ensureSession(sessionID).agents.set(messageID, agent);
523
+ }
524
+ getAgent(sessionID, messageID) {
525
+ return this.sessions.get(sessionID)?.agents.get(messageID) ?? null;
526
+ }
527
+ setTextPart(sessionID, partID, messageID, text) {
528
+ this.ensureSession(sessionID).textParts.set(partID, {
529
+ id: partID,
530
+ messageID,
531
+ text
532
+ });
533
+ }
534
+ getTextPart(sessionID, partID) {
535
+ return this.sessions.get(sessionID)?.textParts.get(partID) ?? null;
536
+ }
537
+ getMessageText(sessionID, messageID) {
538
+ const session = this.sessions.get(sessionID);
539
+ if (!session) {
540
+ return "";
541
+ }
542
+ return [...session.textParts.values()].filter((part) => part.messageID === messageID).map((part) => part.text).join("");
543
+ }
544
+ snapshot(sessionID) {
545
+ const session = this.sessions.get(sessionID) ?? createSessionStore();
546
+ return {
547
+ roles: Object.fromEntries(session.roles),
548
+ agents: Object.fromEntries(session.agents),
549
+ textParts: [...session.textParts.values()]
550
+ };
551
+ }
552
+ cleanup(sessionID) {
553
+ this.sessions.delete(sessionID);
554
+ }
511
555
  }
512
556
 
513
- // tools/start.ts
557
+ // tools/autopilot.ts
514
558
  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
-
530
- Use the global \`Autopilot\` agent to control the autopilot plugin.
531
559
 
532
- **Start Autopilot:**
533
- Switch to the \`Autopilot\` agent, then send the task you want delegated.
534
-
535
- Examples:
536
- - \`Fix the failing tests\`
537
- - \`Use allow-all mode and build-high to refactor the reducer\`
560
+ // tools/usage.ts
561
+ function buildAutopilotUsage() {
562
+ return `
563
+ ## Autopilot Usage
538
564
 
539
- **Check Status:**
540
- - \`status\`
541
- - \`is autopilot running?\`
565
+ Primary workflow:
566
+ - \`/autopilot on\` — enable session autopilot defaults
567
+ - \`/autopilot off\` — disable autopilot for this session
568
+ - \`/autopilot status\` — show current status and recent events
569
+ - \`/autopilot <task>\` — enable autopilot and hand a long-running task to the delegate agent
542
570
 
543
- **Stop Autopilot:**
544
- - \`stop\`
545
- - \`stop because I want to inspect manually\`
571
+ Direct tool equivalents:
572
+ - \`autopilot(action="on")\`
573
+ - \`autopilot(action="off")\`
574
+ - \`autopilot(action="status")\`
575
+ - \`autopilot(task="Fix the failing tests")\`
576
+ - \`autopilot(action="on", autonomousStrength="aggressive")\`
546
577
 
547
578
  Defaults:
548
579
  - permission mode: \`limited\`
549
580
  - continuation limit: \`10\`
550
- - worker agent: \`pi\`
551
- `.trim();
581
+ - delegate agent: \`general\`
582
+ - autonomous strength: \`balanced\`
583
+
584
+ Autonomous strength modes:
585
+ - \`conservative\` — soft guidance to prefer defaults, asks when unsure (similar to previous behavior)
586
+ - \`balanced\` — stronger bias toward selecting recommended/safe defaults, minimal user interaction (default)
587
+ - \`aggressive\` — always pick recommended/safe defaults for routine choices, only escalate high-impact decisions
588
+
589
+ Notes:
590
+ - Session autopilot makes OpenCode act more autonomously and ask fewer questions based on the configured autonomous strength.
591
+ - Long-running delegated tasks run through the configured agent and continue until complete, blocked, or the continuation limit is reached.
592
+ - 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.
593
+ `.trim();
594
+ }
595
+
596
+ // tools/autopilot.ts
597
+ var AUTOPILOT_FALLBACK_AGENT = "pi";
598
+ function createAutopilotTool(deps) {
599
+ return tool({
600
+ description: "Control session autopilot: turn it on or off, check status, or start a long-running delegated task",
601
+ args: {
602
+ action: tool.schema.enum(["on", "off", "status", "help"]).optional().describe("Autopilot command: on, off, status, or help"),
603
+ task: tool.schema.string().optional().describe("Optional delegated task to hand to the configured agent"),
604
+ permissionMode: tool.schema.enum(["limited", "allow-all"]).optional().describe("How permissions should behave while autopilot is active"),
605
+ maxContinues: tool.schema.number().int().positive().optional().describe("Maximum number of autonomous continuation prompts"),
606
+ workerAgent: tool.schema.string().optional().describe("Delegate agent used for long-running autopilot tasks"),
607
+ 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)")
608
+ },
609
+ async execute(args, context) {
610
+ const task = args.task?.trim() ?? "";
611
+ const action = args.action ?? (task ? "on" : "help");
612
+ if (action === "help" || !args.action && task.toLowerCase() === "help") {
613
+ return buildAutopilotUsage();
614
+ }
615
+ if (action === "status") {
616
+ const state2 = deps.getState(context.sessionID);
617
+ if (!state2) {
618
+ return deps.summarizeState(state2);
619
+ }
620
+ const history = deps.getHistory(context.sessionID);
621
+ const historyStr = history.length > 0 ? `
622
+ Recent events:
623
+ - ${history.join(`
624
+ - `)}` : "";
625
+ return `${deps.summarizeState(state2)}${historyStr}`;
626
+ }
627
+ if (action === "off") {
628
+ const state2 = deps.getState(context.sessionID);
629
+ if (!state2 || state2.mode !== "ENABLED") {
630
+ return "Autopilot is not running in this session.";
631
+ }
632
+ deps.onStop(context.sessionID, task || undefined);
633
+ return task ? `Autopilot stopped: ${task}` : "Autopilot stopped for this session.";
552
634
  }
553
635
  const permissionMode = args.permissionMode ?? "limited";
554
636
  const maxContinues = deps.normalizeMaxContinues(args.maxContinues);
555
637
  const workerAgent = args.workerAgent?.trim() || deps.defaultWorkerAgent || AUTOPILOT_FALLBACK_AGENT;
556
- const state = deps.createSessionState(context.sessionID, args.task.trim(), {
638
+ const autonomousStrength = args.autonomousStrength ?? "balanced";
639
+ const state = deps.createSessionState(context.sessionID, task, {
557
640
  maxContinues,
558
- workerAgent
641
+ workerAgent,
642
+ autonomousStrength,
643
+ sessionMode: task ? "delegated-task" : "session-defaults"
559
644
  });
560
645
  Object.defineProperty(state, "permissionMode", {
561
646
  value: permissionMode,
@@ -566,138 +651,29 @@ Defaults:
566
651
  deps.setState(context.sessionID, state);
567
652
  deps.initSession(context.sessionID);
568
653
  context.metadata({
569
- title: "Autopilot armed",
654
+ title: task ? "Autopilot task started" : "Autopilot enabled",
570
655
  metadata: {
656
+ action,
571
657
  permissionMode,
572
658
  maxContinues,
573
- workerAgent
659
+ workerAgent,
660
+ autonomousStrength,
661
+ task: task || null
574
662
  }
575
663
  });
576
664
  await deps.onArmed(context.sessionID, state);
577
- return `Autopilot armed in ${permissionMode} mode with ${workerAgent}. It will start after this response and may continue up to ${maxContinues} times.`;
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.";
665
+ if (state.session_mode === "session-defaults") {
666
+ 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
667
  }
616
- deps.onStop(context.sessionID, args.reason);
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: \`general\`
653
- `.trim();
668
+ 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
669
  }
655
670
  });
656
671
  }
657
672
 
658
- // tools/prompt.ts
659
- import { tool as tool5 } from "@opencode-ai/plugin";
660
- var AUTOPILOT_PROMPT = `You are the Autopilot control agent.
661
-
662
- Your job is to operate the local autopilot plugin, not to do the coding work yourself.
663
- The plugin should handle the autonomous execution loop, while the worker agent (default \`pi\`) does the task work.
664
-
665
- Rules:
666
- - If the user asks for help, usage, or examples, call \`autopilot_help\` and return the tool result.
667
- - If the user asks for status, progress, whether autopilot is running, or similar, call \`autopilot_status\` and return the tool result.
668
- - If the user asks to stop, cancel, disable, or pause autopilot, call \`autopilot_stop\`. Pass through a short reason when one is provided. Return the tool result.
669
- - Otherwise, treat the user's message as a request to start autopilot.
670
- - On a start request, call \`autopilot_start\` immediately before doing anything else.
671
- - Default start settings: \`permissionMode: limited\`, \`maxContinues: 10\`, \`workerAgent: general\`.
672
- - If the user clearly requests \`allow-all\`, \`allow all\`, or similar, set \`permissionMode: allow-all\`.
673
- - If the user clearly requests \`limited\`, set \`permissionMode: limited\`.
674
- - If the user clearly gives a continuation cap such as \`max 3\`, \`max=3\`, or \`continue 3 times\`, pass it as \`maxContinues\`.
675
- - If the user clearly asks for a different worker agent, such as \`use build-high\`, \`worker agent build-high\`, or \`agent=build-high\`, pass it as \`workerAgent\`.
676
- - Use the user's full request as the \`task\` unless they clearly separate control options from the task.
677
- - Do not solve the task yourself after starting autopilot. Return the tool result so the session can continue under the plugin.
678
-
679
- Examples:
680
- - \`Fix the failing autopilot tests\` -> \`autopilot_start(task="Fix the failing autopilot tests")\`
681
- - \`Start autopilot in allow-all mode and use build-high to refactor the reducer\` -> \`autopilot_start(permissionMode="allow-all", workerAgent="build-high", task="refactor the reducer")\`
682
- - \`Use allow all and max 3 to debug the plugin startup failure\` -> \`autopilot_start(permissionMode="allow-all", maxContinues=3, task="debug the plugin startup failure")\`
683
- - \`status\` -> \`autopilot_status()\`
684
- - \`stop because I want to inspect manually\` -> \`autopilot_stop(reason="because I want to inspect manually")\``;
685
- function createPromptTool() {
686
- return tool5({
687
- description: "Get the autopilot control agent prompt. Call this at the start of a session to get your operating instructions.",
688
- args: {},
689
- execute: async () => AUTOPILOT_PROMPT
690
- });
691
- }
692
-
693
673
  // plugin.ts
694
674
  var AUTOPILOT_FALLBACK_AGENT2 = "general";
695
675
  var MAX_HISTORY_ENTRIES = 10;
696
- var AutopilotPlugin = async ({
697
- client,
698
- directory,
699
- worktree
700
- }) => {
676
+ var AutopilotPlugin = async ({ client, directory, worktree }) => {
701
677
  const stateBySession = new Map;
702
678
  const trackingBySession = new Map;
703
679
  const suppressCountBySession = new Map;
@@ -735,12 +711,12 @@ var AutopilotPlugin = async ({
735
711
  const safeToast = async (opts) => {
736
712
  try {
737
713
  await client.tui.showToast({
738
- directory,
739
- workspace: worktree,
740
- title: opts.title,
741
- message: opts.message,
742
- variant: opts.variant,
743
- duration: 3000
714
+ body: {
715
+ title: opts.title,
716
+ message: opts.message,
717
+ variant: opts.variant,
718
+ duration: 3000
719
+ }
744
720
  });
745
721
  } catch {}
746
722
  };
@@ -779,7 +755,7 @@ var AutopilotPlugin = async ({
779
755
  const tracking = getTracking(sessionID);
780
756
  if (!state || state.mode !== "ENABLED" || !tracking)
781
757
  return;
782
- if (state.phase === "OBSERVE" && state.continuation_count === 0 && !tracking.lastAssistantMessageID) {
758
+ if (state.phase === "OBSERVE" && state.continuation_count === 0 && !tracking.lastAssistantMessageID && state.session_mode === "delegated-task") {
783
759
  recordHistory(sessionID, `Starting task with ${state.worker_agent}`);
784
760
  await safeToast({
785
761
  title: "Autopilot armed",
@@ -809,6 +785,21 @@ var AutopilotPlugin = async ({
809
785
  await setStopped(sessionID, "Task blocked", directive.reason, "warning");
810
786
  return;
811
787
  }
788
+ if (directive.status === "validate") {
789
+ state.continuation_count += 1;
790
+ await safeToast({
791
+ title: "Autopilot validating",
792
+ message: "Verifying task completion before finalizing",
793
+ variant: "info"
794
+ });
795
+ await dispatchPrompt(sessionID, state, buildContinuationPrompt({
796
+ continueCount: state.continuation_count,
797
+ maxContinues: state.max_continues,
798
+ task: state.goal,
799
+ isValidation: true
800
+ }));
801
+ return;
802
+ }
812
803
  if (state.continuation_count >= state.max_continues) {
813
804
  await setStopped(sessionID, "Continuation limit reached", `Stopped after ${state.continuation_count} autonomous continuations.`, "warning");
814
805
  return;
@@ -876,50 +867,44 @@ var AutopilotPlugin = async ({
876
867
  const toolAfterHook = createToolAfterHook({
877
868
  stripMarker: stripAutopilotMarker
878
869
  });
879
- const startTool = createStartTool({
870
+ const stopSession = (sessionID, reason) => {
871
+ const state = getState(sessionID);
872
+ if (!state)
873
+ return;
874
+ state.mode = "DISABLED";
875
+ state.phase = "STOPPED";
876
+ state.stop_reason = "USER_STOP";
877
+ recordHistory(sessionID, reason ? `Cancelled by user: ${reason}` : "Cancelled by user");
878
+ };
879
+ const autopilotTool = createAutopilotTool({
880
880
  getState,
881
881
  setState,
882
882
  createSessionState,
883
883
  normalizeMaxContinues,
884
884
  initSession,
885
+ summarizeState: summarizeAutopilotState,
886
+ getHistory: (sessionID) => historyBySession.get(sessionID) ?? [],
887
+ onStop: stopSession,
885
888
  defaultWorkerAgent: AUTOPILOT_FALLBACK_AGENT2,
886
889
  onArmed: async (sessionID, state) => {
887
- const pm = state["permissionMode"];
890
+ const pm = state.permissionMode;
888
891
  if (pm === "allow-all" || pm === "limited") {
889
892
  permissionModeBySession.set(sessionID, pm);
890
893
  } else {
891
894
  permissionModeBySession.set(sessionID, "limited");
892
895
  }
893
- recordHistory(sessionID, `Armed in ${permissionModeBySession.get(sessionID)} mode with ${state.worker_agent}.`);
894
- recordHistory(sessionID, `Continuation limit: ${state.max_continues}.`);
895
- }
896
- });
897
- const statusTool = createStatusTool({
898
- getState,
899
- summarizeState: summarizeAutopilotState,
900
- getHistory: (sessionID) => historyBySession.get(sessionID) ?? []
901
- });
902
- const stopTool = createStopTool({
903
- getState,
904
- onStop: (sessionID, reason) => {
905
- const state = getState(sessionID);
906
- if (!state)
896
+ if (state.session_mode === "delegated-task") {
897
+ recordHistory(sessionID, `Delegated task armed in ${permissionModeBySession.get(sessionID)} mode with ${state.worker_agent}.`);
898
+ recordHistory(sessionID, `Continuation limit: ${state.max_continues}.`);
907
899
  return;
908
- state.mode = "DISABLED";
909
- state.phase = "STOPPED";
910
- state.stop_reason = "USER_STOP";
911
- recordHistory(sessionID, reason ? `Cancelled by user: ${reason}` : "Cancelled by user");
900
+ }
901
+ recordHistory(sessionID, `Session autopilot enabled in ${permissionModeBySession.get(sessionID)} mode.`);
902
+ recordHistory(sessionID, `Delegate agent ready: ${state.worker_agent}. Long-running tasks will use this agent.`);
912
903
  }
913
904
  });
914
- const helpTool = createHelpTool();
915
- const promptTool = createPromptTool();
916
905
  return {
917
906
  tool: {
918
- autopilot_start: startTool,
919
- autopilot_status: statusTool,
920
- autopilot_stop: stopTool,
921
- autopilot_help: helpTool,
922
- autopilot_prompt: promptTool
907
+ autopilot: autopilotTool
923
908
  },
924
909
  event: eventHandler,
925
910
  "permission.ask": permissionHook,
@@ -932,5 +917,5 @@ export {
932
917
  AutopilotPlugin
933
918
  };
934
919
 
935
- //# debugId=B16FACD3D9E9525064756E2164756E21
920
+ //# debugId=377FE34A3CD781F264756E2164756E21
936
921
  //# sourceMappingURL=index.js.map