icopilot 2.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.
Files changed (203) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/LICENSE +21 -0
  3. package/README.md +214 -0
  4. package/bin/icopilot.js +6 -0
  5. package/dist/acp/router.js +123 -0
  6. package/dist/acp/schema.js +53 -0
  7. package/dist/agents/aggregator.js +187 -0
  8. package/dist/agents/custom-agents.js +97 -0
  9. package/dist/agents/goal-driven.js +411 -0
  10. package/dist/agents/multi-repo.js +350 -0
  11. package/dist/agents/parallel-runner.js +181 -0
  12. package/dist/agents/router.js +144 -0
  13. package/dist/agents/self-heal.js +481 -0
  14. package/dist/agents/tdd-agent.js +278 -0
  15. package/dist/api/github-models.js +158 -0
  16. package/dist/bridge/ide-bridge.js +479 -0
  17. package/dist/cloud/routine-executor.js +34 -0
  18. package/dist/cloud/routine-scheduler.js +67 -0
  19. package/dist/cloud/routine-storage.js +297 -0
  20. package/dist/commands/acp-cmd.js +143 -0
  21. package/dist/commands/actions-cmd.js +624 -0
  22. package/dist/commands/agent-cmd.js +144 -0
  23. package/dist/commands/alias-cmd.js +132 -0
  24. package/dist/commands/bookmark-cmd.js +77 -0
  25. package/dist/commands/changelog-cmd.js +99 -0
  26. package/dist/commands/changes-cmd.js +120 -0
  27. package/dist/commands/clipboard-cmd.js +217 -0
  28. package/dist/commands/cloud-routine-cmd.js +265 -0
  29. package/dist/commands/codegen-cmd.js +544 -0
  30. package/dist/commands/compare-cmd.js +116 -0
  31. package/dist/commands/context-cmd.js +247 -0
  32. package/dist/commands/context-viz-cmd.js +43 -0
  33. package/dist/commands/conventions-cmd.js +116 -0
  34. package/dist/commands/cost-cmd.js +51 -0
  35. package/dist/commands/deps-cmd.js +294 -0
  36. package/dist/commands/diagram-cmd.js +658 -0
  37. package/dist/commands/diff-review-cmd.js +92 -0
  38. package/dist/commands/doc-cmd.js +412 -0
  39. package/dist/commands/doctor-cmd.js +152 -0
  40. package/dist/commands/editor-cmd.js +49 -0
  41. package/dist/commands/env-cmd.js +86 -0
  42. package/dist/commands/explain-cmd.js +78 -0
  43. package/dist/commands/explain-shell-cmd.js +22 -0
  44. package/dist/commands/explore-cmd.js +231 -0
  45. package/dist/commands/feedback-cmd.js +98 -0
  46. package/dist/commands/fix-cmd.js +17 -0
  47. package/dist/commands/generate-cmd.js +38 -0
  48. package/dist/commands/git-extra.js +197 -0
  49. package/dist/commands/git-log-cmd.js +98 -0
  50. package/dist/commands/git-undo-cmd.js +137 -0
  51. package/dist/commands/git.js +155 -0
  52. package/dist/commands/history-cmd.js +122 -0
  53. package/dist/commands/index-cmd.js +65 -0
  54. package/dist/commands/init-cmd.js +73 -0
  55. package/dist/commands/lint-cmd.js +133 -0
  56. package/dist/commands/memory-cmd.js +98 -0
  57. package/dist/commands/metrics-cmd.js +97 -0
  58. package/dist/commands/mode-prefix.js +30 -0
  59. package/dist/commands/multi-cmd.js +44 -0
  60. package/dist/commands/notify-cmd.js +204 -0
  61. package/dist/commands/profile-cmd.js +101 -0
  62. package/dist/commands/prompts.js +17 -0
  63. package/dist/commands/rag-cmd.js +60 -0
  64. package/dist/commands/readme-cmd.js +564 -0
  65. package/dist/commands/reasoning-cmd.js +34 -0
  66. package/dist/commands/refactor-cmd.js +96 -0
  67. package/dist/commands/release-cmd.js +450 -0
  68. package/dist/commands/repo-cmd.js +195 -0
  69. package/dist/commands/route-cmd.js +21 -0
  70. package/dist/commands/schedule-cmd.js +109 -0
  71. package/dist/commands/search-cmd.js +47 -0
  72. package/dist/commands/security-cmd.js +156 -0
  73. package/dist/commands/settings-cmd.js +238 -0
  74. package/dist/commands/skill-cmd.js +338 -0
  75. package/dist/commands/slash.js +2721 -0
  76. package/dist/commands/snippets-cmd.js +83 -0
  77. package/dist/commands/space-cmd.js +92 -0
  78. package/dist/commands/stash-cmd.js +156 -0
  79. package/dist/commands/stats-cmd.js +36 -0
  80. package/dist/commands/style-cmd.js +85 -0
  81. package/dist/commands/suggest-cmd.js +40 -0
  82. package/dist/commands/summary-cmd.js +138 -0
  83. package/dist/commands/task-cmd.js +58 -0
  84. package/dist/commands/team-memory-cmd.js +97 -0
  85. package/dist/commands/template-cmd.js +475 -0
  86. package/dist/commands/test-cmd.js +146 -0
  87. package/dist/commands/todo-cmd.js +172 -0
  88. package/dist/commands/tokens-cmd.js +277 -0
  89. package/dist/commands/trigger-cmd.js +147 -0
  90. package/dist/commands/undo-cmd.js +18 -0
  91. package/dist/commands/voice-cmd.js +89 -0
  92. package/dist/commands/watch-cmd.js +110 -0
  93. package/dist/commands/web-cmd.js +183 -0
  94. package/dist/commands/worktree-cmd.js +119 -0
  95. package/dist/config-profile.js +66 -0
  96. package/dist/config.js +288 -0
  97. package/dist/context/compactor.js +53 -0
  98. package/dist/context/dep-context.js +329 -0
  99. package/dist/context/file-refs.js +54 -0
  100. package/dist/context/git-context.js +229 -0
  101. package/dist/context/image-input.js +66 -0
  102. package/dist/context/memory.js +55 -0
  103. package/dist/context/persistent-memory.js +104 -0
  104. package/dist/context/pinned.js +96 -0
  105. package/dist/context/priority.js +150 -0
  106. package/dist/context/read-only.js +48 -0
  107. package/dist/context/smart-files.js +286 -0
  108. package/dist/context/team-memory.js +156 -0
  109. package/dist/extensions/loader.js +149 -0
  110. package/dist/extensions/marketplace.js +49 -0
  111. package/dist/extensions/slack-provider.js +181 -0
  112. package/dist/extensions/team.js +56 -0
  113. package/dist/extensions/teams-provider.js +222 -0
  114. package/dist/extensions/voice.js +18 -0
  115. package/dist/hooks/lifecycle.js +215 -0
  116. package/dist/hooks/precommit.js +463 -0
  117. package/dist/index/embeddings.js +23 -0
  118. package/dist/index/indexer.js +86 -0
  119. package/dist/index/retrieve.js +20 -0
  120. package/dist/index/store.js +95 -0
  121. package/dist/index.js +286 -0
  122. package/dist/intelligence/dead-code.js +457 -0
  123. package/dist/intelligence/error-watch.js +263 -0
  124. package/dist/intelligence/navigation.js +141 -0
  125. package/dist/intelligence/stack-trace.js +210 -0
  126. package/dist/intelligence/symbol-index.js +410 -0
  127. package/dist/knowledge/auto-memory.js +412 -0
  128. package/dist/knowledge/conventions.js +475 -0
  129. package/dist/knowledge/corrections.js +213 -0
  130. package/dist/knowledge/rag.js +450 -0
  131. package/dist/knowledge/style-learner.js +324 -0
  132. package/dist/logger.js +35 -0
  133. package/dist/mcp/client.js +144 -0
  134. package/dist/mcp/config.js +24 -0
  135. package/dist/mcp/index.js +89 -0
  136. package/dist/modes/auto-compact.js +20 -0
  137. package/dist/modes/autopilot.js +157 -0
  138. package/dist/modes/background.js +82 -0
  139. package/dist/modes/interactive.js +187 -0
  140. package/dist/modes/oneshot.js +36 -0
  141. package/dist/modes/tui.js +265 -0
  142. package/dist/modes/turn.js +342 -0
  143. package/dist/notifications/manager.js +107 -0
  144. package/dist/plugins/marketplace.js +244 -0
  145. package/dist/providers/custom-provider.js +298 -0
  146. package/dist/providers/local-model.js +121 -0
  147. package/dist/routing/profiles.js +44 -0
  148. package/dist/routing/router.js +18 -0
  149. package/dist/sandbox/container.js +151 -0
  150. package/dist/security/audit.js +237 -0
  151. package/dist/security/content-filter.js +449 -0
  152. package/dist/security/proxy.js +301 -0
  153. package/dist/security/retention.js +281 -0
  154. package/dist/security/roles.js +252 -0
  155. package/dist/server/api-server.js +679 -0
  156. package/dist/session/bookmarks.js +72 -0
  157. package/dist/session/cloud-session.js +291 -0
  158. package/dist/session/handoff.js +405 -0
  159. package/dist/session/manager.js +35 -0
  160. package/dist/session/session.js +296 -0
  161. package/dist/session/share.js +313 -0
  162. package/dist/session/undo-journal.js +91 -0
  163. package/dist/snippets/store.js +60 -0
  164. package/dist/spaces/space-config.js +156 -0
  165. package/dist/spaces/space.js +220 -0
  166. package/dist/stats/store.js +101 -0
  167. package/dist/tools/apply-patch.js +134 -0
  168. package/dist/tools/auto-check.js +218 -0
  169. package/dist/tools/diff-edit.js +150 -0
  170. package/dist/tools/diff-prompt.js +36 -0
  171. package/dist/tools/edit-file.js +66 -0
  172. package/dist/tools/file-ops.js +205 -0
  173. package/dist/tools/glob.js +17 -0
  174. package/dist/tools/grep.js +56 -0
  175. package/dist/tools/image.js +194 -0
  176. package/dist/tools/list-directory.js +228 -0
  177. package/dist/tools/memory.js +17 -0
  178. package/dist/tools/multi-edit.js +299 -0
  179. package/dist/tools/policy.js +95 -0
  180. package/dist/tools/registry.js +484 -0
  181. package/dist/tools/retry.js +74 -0
  182. package/dist/tools/run-in-terminal.js +162 -0
  183. package/dist/tools/safety.js +64 -0
  184. package/dist/tools/sandbox.js +15 -0
  185. package/dist/tools/search-symbols.js +212 -0
  186. package/dist/tools/shell.js +118 -0
  187. package/dist/tools/web.js +167 -0
  188. package/dist/ui/prompt.js +37 -0
  189. package/dist/ui/render.js +96 -0
  190. package/dist/ui/screen.js +13 -0
  191. package/dist/ui/theme.js +56 -0
  192. package/dist/util/browser.js +34 -0
  193. package/dist/util/completion.js +350 -0
  194. package/dist/util/cost.js +28 -0
  195. package/dist/util/keybindings.js +113 -0
  196. package/dist/util/lazy.js +26 -0
  197. package/dist/util/perf.js +25 -0
  198. package/dist/util/token-worker.js +11 -0
  199. package/dist/util/tokens.js +50 -0
  200. package/dist/workflows/builtins.js +128 -0
  201. package/dist/workflows/engine.js +496 -0
  202. package/dist/workflows/file-trigger.js +197 -0
  203. package/package.json +79 -0
@@ -0,0 +1,157 @@
1
+ import { Session } from '../session/session.js';
2
+ import { hookManager } from '../hooks/lifecycle.js';
3
+ import { theme } from '../ui/theme.js';
4
+ import { runTurn } from './turn.js';
5
+ export const AUTOPILOT_MAX_STEPS = 10;
6
+ const AUTOPILOT_REQUIRE_APPROVAL_DEFAULT = true;
7
+ const AUTOPILOT_COMPLETE_TOKEN = 'AUTOPILOT_COMPLETE:';
8
+ export function buildAutopilotSystemPrompt(goal) {
9
+ const normalizedGoal = goal.trim();
10
+ return [
11
+ 'You are operating in autopilot mode.',
12
+ `Goal: ${normalizedGoal || 'No goal provided.'}`,
13
+ '',
14
+ 'Follow this process:',
15
+ '1. Break the goal into numbered steps.',
16
+ '2. Execute each step using the available tools.',
17
+ '3. Verify each step succeeded before moving to the next step.',
18
+ '4. Report a final summary with results, blockers, and follow-up items.',
19
+ '',
20
+ `Constraints: keep the plan within ${AUTOPILOT_MAX_STEPS} steps unless the task clearly requires fewer.`,
21
+ `Approval: assume destructive actions require explicit user approval when requireApproval=${String(AUTOPILOT_REQUIRE_APPROVAL_DEFAULT)}.`,
22
+ ].join('\n');
23
+ }
24
+ export function parseAutopilotPlan(response) {
25
+ const normalizedResponse = response.trim();
26
+ if (!normalizedResponse) {
27
+ return { goal: '', steps: [] };
28
+ }
29
+ const lines = normalizedResponse.split(/\r?\n/);
30
+ const steps = [];
31
+ const goalLines = [];
32
+ for (const rawLine of lines) {
33
+ const line = rawLine.trim();
34
+ if (!line) {
35
+ continue;
36
+ }
37
+ const match = line.match(/^(\d+)[.)]\s+(.*)$/);
38
+ if (match) {
39
+ const description = match[2]?.trim();
40
+ if (description) {
41
+ steps.push({ description, status: 'pending' });
42
+ }
43
+ continue;
44
+ }
45
+ if (steps.length > 0) {
46
+ const currentStep = steps[steps.length - 1];
47
+ currentStep.description = `${currentStep.description} ${line}`.trim();
48
+ continue;
49
+ }
50
+ goalLines.push(line);
51
+ }
52
+ const goal = goalLines.join(' ').trim();
53
+ if (steps.length > 0) {
54
+ return { goal, steps };
55
+ }
56
+ return {
57
+ goal: normalizedResponse,
58
+ steps: [],
59
+ };
60
+ }
61
+ export async function runAutopilot(goal, opts = {}) {
62
+ const normalizedGoal = goal.trim();
63
+ if (!normalizedGoal) {
64
+ throw new Error('autopilot requires a goal.');
65
+ }
66
+ const maxSteps = Math.max(1, Math.min(opts.maxSteps ?? AUTOPILOT_MAX_STEPS, AUTOPILOT_MAX_STEPS));
67
+ const signal = opts.signal ?? new AbortController().signal;
68
+ const session = opts.session ??
69
+ new Session({
70
+ model: opts.model,
71
+ cwd: opts.cwd,
72
+ mode: 'ask',
73
+ });
74
+ if (!opts.session) {
75
+ await session.initializeGitContext();
76
+ await hookManager.emit('sessionStart', {
77
+ sessionId: session.state.id,
78
+ cwd: session.state.cwd,
79
+ mode: session.state.mode,
80
+ model: session.state.model,
81
+ });
82
+ }
83
+ const previousMode = session.state.mode;
84
+ const previousPrompt = session.state.systemPrompt;
85
+ const previousAutopilotEnabled = Boolean(session.state.autopilotEnabled);
86
+ session.setAutopilotEnabled(false);
87
+ session.setMode('ask');
88
+ session.setSystemPrompt(buildAutopilotSystemPrompt(normalizedGoal));
89
+ try {
90
+ for (let step = 1; step <= maxSteps; step++) {
91
+ process.stdout.write(theme.dim(`\n[autopilot] step ${step} of ${maxSteps}\n`));
92
+ await runTurn({
93
+ session,
94
+ userInput: buildAutopilotTurnPrompt(normalizedGoal, step, maxSteps),
95
+ signal,
96
+ });
97
+ if (isAutopilotComplete(findLastAssistantMessage(session.state.messages))) {
98
+ return session;
99
+ }
100
+ }
101
+ process.stdout.write(theme.warn(`\n⚠ autopilot stopped after ${maxSteps} steps.\n`));
102
+ return session;
103
+ }
104
+ finally {
105
+ session.setSystemPrompt(previousPrompt);
106
+ session.setMode(previousMode);
107
+ session.setAutopilotEnabled(previousAutopilotEnabled);
108
+ if (!opts.session) {
109
+ await hookManager.emit('sessionEnd', {
110
+ sessionId: session.state.id,
111
+ cwd: session.state.cwd,
112
+ mode: session.state.mode,
113
+ model: session.state.model,
114
+ });
115
+ }
116
+ }
117
+ }
118
+ function buildAutopilotTurnPrompt(goal, step, maxSteps) {
119
+ const progress = `Step ${step} of ${maxSteps}.`;
120
+ if (step === 1) {
121
+ return [
122
+ progress,
123
+ `Goal: ${goal}`,
124
+ `Start working now. When the goal is fully complete, begin your response with "${AUTOPILOT_COMPLETE_TOKEN}" followed by a concise summary.`,
125
+ ].join('\n');
126
+ }
127
+ return [
128
+ progress,
129
+ 'Continue from the current session state and finish the next best action.',
130
+ `If the goal is fully complete, begin your response with "${AUTOPILOT_COMPLETE_TOKEN}" followed by a concise summary.`,
131
+ ].join('\n');
132
+ }
133
+ function findLastAssistantMessage(messages) {
134
+ for (let index = messages.length - 1; index >= 0; index--) {
135
+ const message = messages[index];
136
+ if (message?.role === 'assistant') {
137
+ return contentToText(message.content);
138
+ }
139
+ }
140
+ return '';
141
+ }
142
+ function isAutopilotComplete(content) {
143
+ return content.trimStart().startsWith(AUTOPILOT_COMPLETE_TOKEN);
144
+ }
145
+ function contentToText(content) {
146
+ if (typeof content === 'string')
147
+ return content;
148
+ if (!Array.isArray(content))
149
+ return '';
150
+ return content
151
+ .map((part) => {
152
+ if (typeof part === 'string')
153
+ return part;
154
+ return 'text' in part && typeof part.text === 'string' ? part.text : '';
155
+ })
156
+ .join('\n');
157
+ }
@@ -0,0 +1,82 @@
1
+ import crypto from 'node:crypto';
2
+ import { theme } from '../ui/theme.js';
3
+ export class TaskManager {
4
+ tasks = new Map();
5
+ startTask(goal) {
6
+ const task = {
7
+ id: crypto.randomUUID(),
8
+ goal: goal.trim(),
9
+ status: 'running',
10
+ startedAt: new Date().toISOString(),
11
+ };
12
+ this.tasks.set(task.id, task);
13
+ return task.id;
14
+ }
15
+ completeTask(id, result) {
16
+ const task = this.tasks.get(id);
17
+ if (!task)
18
+ return;
19
+ task.status = 'done';
20
+ task.completedAt = new Date().toISOString();
21
+ task.result = result;
22
+ delete task.error;
23
+ }
24
+ failTask(id, error) {
25
+ const task = this.tasks.get(id);
26
+ if (!task)
27
+ return;
28
+ task.status = 'failed';
29
+ task.completedAt = new Date().toISOString();
30
+ task.error = error;
31
+ delete task.result;
32
+ }
33
+ getTask(id) {
34
+ const task = this.tasks.get(id);
35
+ return task ? cloneTask(task) : undefined;
36
+ }
37
+ listTasks() {
38
+ return [...this.tasks.values()].map(cloneTask);
39
+ }
40
+ formatTaskList() {
41
+ const tasks = this.listTasks();
42
+ if (tasks.length === 0)
43
+ return `${theme.brand('Background tasks')}\n ${theme.dim('No tasks.')}\n`;
44
+ const lines = tasks.map((task) => ` ${statusMarker(task.status)} ${theme.hl(shortId(task.id))} ${task.goal} ${theme.dim(`(${task.status})`)}`);
45
+ return `${theme.brand('Background tasks')}\n${lines.join('\n')}\n`;
46
+ }
47
+ formatTaskResult(id) {
48
+ const task = this.getTask(id);
49
+ if (!task)
50
+ return `${theme.warn(`No background task matches "${id}".`)}\n`;
51
+ const lines = [
52
+ `${theme.brand('Background task')} ${theme.hl(shortId(task.id))}`,
53
+ ` goal: ${task.goal}`,
54
+ ` status: ${task.status}`,
55
+ ` started: ${task.startedAt}`,
56
+ ];
57
+ if (task.completedAt)
58
+ lines.push(` completed: ${task.completedAt}`);
59
+ if (task.result)
60
+ lines.push(` result: ${task.result}`);
61
+ if (task.error)
62
+ lines.push(` error: ${task.error}`);
63
+ return `${lines.join('\n')}\n`;
64
+ }
65
+ }
66
+ export const backgroundTaskManager = new TaskManager();
67
+ function cloneTask(task) {
68
+ return { ...task };
69
+ }
70
+ function shortId(id) {
71
+ return id.slice(0, 8);
72
+ }
73
+ function statusMarker(status) {
74
+ switch (status) {
75
+ case 'done':
76
+ return theme.ok('✓');
77
+ case 'failed':
78
+ return theme.err('✗');
79
+ default:
80
+ return theme.dim('…');
81
+ }
82
+ }
@@ -0,0 +1,187 @@
1
+ import { createRequire } from 'node:module';
2
+ import { Session } from '../session/session.js';
3
+ import { theme, banner } from '../ui/theme.js';
4
+ import { createPrompt, prefix } from '../ui/prompt.js';
5
+ import { handleSlash } from '../commands/slash.js';
6
+ import { loadAliases, resolveAlias } from '../commands/alias-cmd.js';
7
+ import { MetricsCollector } from '../commands/metrics-cmd.js';
8
+ import { runAutopilot } from './autopilot.js';
9
+ import { handlePostTurnContextBudget } from './auto-compact.js';
10
+ import { runTurn } from './turn.js';
11
+ import { config } from '../config.js';
12
+ import { backgroundTaskManager } from './background.js';
13
+ import { hookManager } from '../hooks/lifecycle.js';
14
+ import { applyKeybindingConfig, getKeybindingHelp } from '../util/keybindings.js';
15
+ import { getCloudRoutineScheduler } from '../cloud/routine-scheduler.js';
16
+ import { createCloudRoutineExecutor } from '../cloud/routine-executor.js';
17
+ const require = createRequire(import.meta.url);
18
+ const VERSION = require('../../package.json').version;
19
+ export async function runInteractive(initialMode = 'ask', opts = {}) {
20
+ const session = new Session({ mode: initialMode });
21
+ await session.initializeGitContext();
22
+ await hookManager.emit('sessionStart', {
23
+ sessionId: session.state.id,
24
+ cwd: session.state.cwd,
25
+ mode: session.state.mode,
26
+ model: session.state.model,
27
+ });
28
+ const metrics = new MetricsCollector();
29
+ const scheduler = getCloudRoutineScheduler();
30
+ if (config.cloudRoutines?.enabled) {
31
+ const executor = createCloudRoutineExecutor();
32
+ scheduler.setExecutor(executor);
33
+ scheduler.start();
34
+ }
35
+ // Apply keybinding configuration
36
+ const keybindingMode = applyKeybindingConfig();
37
+ if (!config.quiet) {
38
+ process.stdout.write(banner(VERSION, session.state.model));
39
+ if (keybindingMode !== 'default') {
40
+ process.stdout.write(getKeybindingHelp(keybindingMode));
41
+ }
42
+ }
43
+ const rl = createPrompt(keybindingMode);
44
+ let running = true;
45
+ let processing = false;
46
+ let currentAbort = null;
47
+ const pendingInputs = [];
48
+ // SIGINT: abort streaming, but never exit (unless pressed at idle twice).
49
+ let lastSigintAt = 0;
50
+ const onSigint = () => {
51
+ if (currentAbort && !currentAbort.signal.aborted) {
52
+ currentAbort.abort();
53
+ process.stdout.write(theme.warn('\n⏸ interrupted.\n'));
54
+ return;
55
+ }
56
+ const now = Date.now();
57
+ if (now - lastSigintAt < 1500) {
58
+ process.stdout.write(theme.dim('\nbye.\n'));
59
+ running = false;
60
+ rl.close();
61
+ return;
62
+ }
63
+ lastSigintAt = now;
64
+ process.stdout.write(theme.dim('\n(press Ctrl-C again to exit)\n'));
65
+ };
66
+ process.on('SIGINT', onSigint);
67
+ const enqueueInput = (line, scheduled = false) => {
68
+ pendingInputs.push({ line, scheduled });
69
+ void processQueue();
70
+ };
71
+ const processQueue = async () => {
72
+ if (processing)
73
+ return;
74
+ processing = true;
75
+ try {
76
+ while (running && pendingInputs.length) {
77
+ const next = pendingInputs.shift();
78
+ if (!next)
79
+ continue;
80
+ const resolvedLine = resolveAlias(next.line, loadAliases()) ?? next.line;
81
+ currentAbort = new AbortController();
82
+ try {
83
+ if (next.scheduled) {
84
+ process.stdout.write(theme.dim(`\n[schedule] ${next.line}\n`));
85
+ }
86
+ const slash = await handleSlash(resolvedLine, {
87
+ session,
88
+ abort: currentAbort,
89
+ metrics,
90
+ schedulePrompt: (prompt) => enqueueInput(prompt, true),
91
+ exit: () => {
92
+ running = false;
93
+ },
94
+ });
95
+ if (slash.consumed)
96
+ continue;
97
+ const input = slash.forwardInput ?? resolvedLine;
98
+ if (slash.handled && slash.forwardInput !== undefined) {
99
+ process.stdout.write(formatMessagePreview(slash.forwardInput));
100
+ }
101
+ const trimmedInput = input.trim();
102
+ if (trimmedInput.endsWith('&')) {
103
+ const goal = trimmedInput.slice(0, -1).trim();
104
+ if (!goal) {
105
+ process.stdout.write(theme.warn('\nusage: <prompt> &\n'));
106
+ continue;
107
+ }
108
+ const id = backgroundTaskManager.startTask(goal);
109
+ process.stdout.write(theme.ok(`\n↳ started background task ${id.slice(0, 8)} for: ${goal}\n`));
110
+ continue;
111
+ }
112
+ const explicitTurnMode = slash
113
+ .turnMode;
114
+ const effectiveTurnMode = explicitTurnMode ?? opts.defaultTurnMode;
115
+ if (session.state.autopilotEnabled && !effectiveTurnMode) {
116
+ await runAutopilot(input, {
117
+ session,
118
+ signal: currentAbort.signal,
119
+ });
120
+ }
121
+ else {
122
+ await runTurn({
123
+ session,
124
+ userInput: input,
125
+ metrics,
126
+ signal: currentAbort.signal,
127
+ turnMode: effectiveTurnMode ?? undefined,
128
+ });
129
+ await handlePostTurnContextBudget(session, currentAbort.signal);
130
+ }
131
+ }
132
+ catch (e) {
133
+ if (e?.name === 'AbortError' || currentAbort.signal.aborted) {
134
+ // already messaged
135
+ }
136
+ else {
137
+ await hookManager.emit('errorOccurred', {
138
+ scope: 'interactive',
139
+ sessionId: session.state.id,
140
+ message: e?.message || String(e),
141
+ });
142
+ process.stdout.write(theme.err(`\nerror: ${e?.message || e}\n`));
143
+ }
144
+ }
145
+ finally {
146
+ currentAbort = null;
147
+ }
148
+ }
149
+ }
150
+ finally {
151
+ processing = false;
152
+ }
153
+ };
154
+ try {
155
+ while (running) {
156
+ let line;
157
+ try {
158
+ line = await rl.read(prefix(session.state.mode));
159
+ }
160
+ catch {
161
+ break;
162
+ }
163
+ if (!line || !line.trim())
164
+ continue;
165
+ enqueueInput(line);
166
+ }
167
+ }
168
+ finally {
169
+ scheduler.stop();
170
+ process.off('SIGINT', onSigint);
171
+ rl.close();
172
+ await hookManager.emit('sessionEnd', {
173
+ sessionId: session.state.id,
174
+ cwd: session.state.cwd,
175
+ mode: session.state.mode,
176
+ model: session.state.model,
177
+ });
178
+ }
179
+ }
180
+ function formatMessagePreview(message) {
181
+ const lines = message.trim().split(/\r?\n/);
182
+ const preview = lines.slice(0, 8);
183
+ const suffix = lines.length > preview.length
184
+ ? `\n${theme.dim(`… ${lines.length - preview.length} more line(s)`)}`
185
+ : '';
186
+ return `\n${theme.brand('Message preview')}\n${preview.join('\n')}${suffix}\n\n`;
187
+ }
@@ -0,0 +1,36 @@
1
+ import { Session } from '../session/session.js';
2
+ import { hookManager } from '../hooks/lifecycle.js';
3
+ import { runTurn } from './turn.js';
4
+ import { theme } from '../ui/theme.js';
5
+ export async function runOneShot(prompt, opts = {}) {
6
+ const session = new Session({
7
+ model: opts.model,
8
+ mode: opts.plan ? 'plan' : 'ask',
9
+ });
10
+ await session.initializeGitContext();
11
+ await hookManager.emit('sessionStart', {
12
+ sessionId: session.state.id,
13
+ cwd: session.state.cwd,
14
+ mode: session.state.mode,
15
+ model: session.state.model,
16
+ });
17
+ const ac = new AbortController();
18
+ const onSigint = () => {
19
+ ac.abort();
20
+ process.stdout.write(theme.warn('\n⏸ interrupted.\n'));
21
+ process.exit(130);
22
+ };
23
+ process.on('SIGINT', onSigint);
24
+ try {
25
+ await runTurn({ session, userInput: prompt, signal: ac.signal, turnMode: opts.turnMode });
26
+ }
27
+ finally {
28
+ process.off('SIGINT', onSigint);
29
+ await hookManager.emit('sessionEnd', {
30
+ sessionId: session.state.id,
31
+ cwd: session.state.cwd,
32
+ mode: session.state.mode,
33
+ model: session.state.model,
34
+ });
35
+ }
36
+ }