indusagi-coding-agent 0.1.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/CHANGELOG.md +2249 -0
- package/README.md +546 -0
- package/dist/cli/args.js +282 -0
- package/dist/cli/config-selector.js +30 -0
- package/dist/cli/file-processor.js +78 -0
- package/dist/cli/list-models.js +91 -0
- package/dist/cli/session-picker.js +31 -0
- package/dist/cli.js +10 -0
- package/dist/config.js +158 -0
- package/dist/core/agent-session.js +2097 -0
- package/dist/core/auth-storage.js +278 -0
- package/dist/core/bash-executor.js +211 -0
- package/dist/core/compaction/branch-summarization.js +241 -0
- package/dist/core/compaction/compaction.js +606 -0
- package/dist/core/compaction/index.js +6 -0
- package/dist/core/compaction/utils.js +137 -0
- package/dist/core/diagnostics.js +1 -0
- package/dist/core/event-bus.js +24 -0
- package/dist/core/exec.js +70 -0
- package/dist/core/export-html/ansi-to-html.js +248 -0
- package/dist/core/export-html/index.js +221 -0
- package/dist/core/export-html/template.css +905 -0
- package/dist/core/export-html/template.html +54 -0
- package/dist/core/export-html/template.js +1549 -0
- package/dist/core/export-html/tool-renderer.js +56 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked.min.js +6 -0
- package/dist/core/extensions/index.js +8 -0
- package/dist/core/extensions/loader.js +395 -0
- package/dist/core/extensions/runner.js +499 -0
- package/dist/core/extensions/types.js +31 -0
- package/dist/core/extensions/wrapper.js +101 -0
- package/dist/core/footer-data-provider.js +133 -0
- package/dist/core/index.js +8 -0
- package/dist/core/keybindings.js +140 -0
- package/dist/core/messages.js +122 -0
- package/dist/core/model-registry.js +454 -0
- package/dist/core/model-resolver.js +309 -0
- package/dist/core/package-manager.js +1142 -0
- package/dist/core/prompt-templates.js +250 -0
- package/dist/core/resource-loader.js +569 -0
- package/dist/core/sdk.js +225 -0
- package/dist/core/session-manager.js +1078 -0
- package/dist/core/settings-manager.js +430 -0
- package/dist/core/skills.js +339 -0
- package/dist/core/system-prompt.js +136 -0
- package/dist/core/timings.js +24 -0
- package/dist/core/tools/bash.js +226 -0
- package/dist/core/tools/edit-diff.js +242 -0
- package/dist/core/tools/edit.js +145 -0
- package/dist/core/tools/find.js +205 -0
- package/dist/core/tools/grep.js +238 -0
- package/dist/core/tools/index.js +60 -0
- package/dist/core/tools/ls.js +117 -0
- package/dist/core/tools/path-utils.js +52 -0
- package/dist/core/tools/read.js +165 -0
- package/dist/core/tools/truncate.js +204 -0
- package/dist/core/tools/write.js +77 -0
- package/dist/index.js +41 -0
- package/dist/main.js +565 -0
- package/dist/migrations.js +260 -0
- package/dist/modes/index.js +7 -0
- package/dist/modes/interactive/components/armin.js +328 -0
- package/dist/modes/interactive/components/assistant-message.js +86 -0
- package/dist/modes/interactive/components/bash-execution.js +155 -0
- package/dist/modes/interactive/components/bordered-loader.js +47 -0
- package/dist/modes/interactive/components/branch-summary-message.js +41 -0
- package/dist/modes/interactive/components/compaction-summary-message.js +42 -0
- package/dist/modes/interactive/components/config-selector.js +458 -0
- package/dist/modes/interactive/components/countdown-timer.js +27 -0
- package/dist/modes/interactive/components/custom-editor.js +61 -0
- package/dist/modes/interactive/components/custom-message.js +80 -0
- package/dist/modes/interactive/components/diff.js +132 -0
- package/dist/modes/interactive/components/dynamic-border.js +19 -0
- package/dist/modes/interactive/components/extension-editor.js +96 -0
- package/dist/modes/interactive/components/extension-input.js +54 -0
- package/dist/modes/interactive/components/extension-selector.js +70 -0
- package/dist/modes/interactive/components/footer.js +213 -0
- package/dist/modes/interactive/components/index.js +31 -0
- package/dist/modes/interactive/components/keybinding-hints.js +60 -0
- package/dist/modes/interactive/components/login-dialog.js +138 -0
- package/dist/modes/interactive/components/model-selector.js +253 -0
- package/dist/modes/interactive/components/oauth-selector.js +91 -0
- package/dist/modes/interactive/components/scoped-models-selector.js +262 -0
- package/dist/modes/interactive/components/session-selector-search.js +145 -0
- package/dist/modes/interactive/components/session-selector.js +698 -0
- package/dist/modes/interactive/components/settings-selector.js +250 -0
- package/dist/modes/interactive/components/show-images-selector.js +33 -0
- package/dist/modes/interactive/components/skill-invocation-message.js +44 -0
- package/dist/modes/interactive/components/theme-selector.js +43 -0
- package/dist/modes/interactive/components/thinking-selector.js +45 -0
- package/dist/modes/interactive/components/tool-execution.js +608 -0
- package/dist/modes/interactive/components/tree-selector.js +892 -0
- package/dist/modes/interactive/components/user-message-selector.js +109 -0
- package/dist/modes/interactive/components/user-message.js +15 -0
- package/dist/modes/interactive/components/visual-truncate.js +32 -0
- package/dist/modes/interactive/interactive-mode.js +3576 -0
- package/dist/modes/interactive/theme/dark.json +85 -0
- package/dist/modes/interactive/theme/light.json +84 -0
- package/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/dist/modes/interactive/theme/theme.js +938 -0
- package/dist/modes/print-mode.js +96 -0
- package/dist/modes/rpc/rpc-client.js +390 -0
- package/dist/modes/rpc/rpc-mode.js +448 -0
- package/dist/modes/rpc/rpc-types.js +7 -0
- package/dist/utils/changelog.js +86 -0
- package/dist/utils/clipboard-image.js +116 -0
- package/dist/utils/clipboard.js +58 -0
- package/dist/utils/frontmatter.js +25 -0
- package/dist/utils/git.js +5 -0
- package/dist/utils/image-convert.js +34 -0
- package/dist/utils/image-resize.js +180 -0
- package/dist/utils/mime.js +25 -0
- package/dist/utils/photon.js +120 -0
- package/dist/utils/shell.js +164 -0
- package/dist/utils/sleep.js +16 -0
- package/dist/utils/tools-manager.js +186 -0
- package/docs/compaction.md +390 -0
- package/docs/custom-provider.md +538 -0
- package/docs/development.md +69 -0
- package/docs/extensions.md +1733 -0
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/json.md +79 -0
- package/docs/keybindings.md +162 -0
- package/docs/models.md +193 -0
- package/docs/packages.md +163 -0
- package/docs/prompt-templates.md +67 -0
- package/docs/providers.md +147 -0
- package/docs/rpc.md +1048 -0
- package/docs/sdk.md +957 -0
- package/docs/session.md +412 -0
- package/docs/settings.md +216 -0
- package/docs/shell-aliases.md +13 -0
- package/docs/skills.md +226 -0
- package/docs/terminal-setup.md +65 -0
- package/docs/themes.md +295 -0
- package/docs/tree.md +219 -0
- package/docs/tui.md +887 -0
- package/docs/windows.md +17 -0
- package/examples/README.md +25 -0
- package/examples/extensions/README.md +192 -0
- package/examples/extensions/antigravity-image-gen.ts +414 -0
- package/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/examples/extensions/bookmark.ts +50 -0
- package/examples/extensions/claude-rules.ts +86 -0
- package/examples/extensions/confirm-destructive.ts +59 -0
- package/examples/extensions/custom-compaction.ts +115 -0
- package/examples/extensions/custom-footer.ts +65 -0
- package/examples/extensions/custom-header.ts +73 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +605 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +350 -0
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +83 -0
- package/examples/extensions/dirty-repo-guard.ts +56 -0
- package/examples/extensions/doom-overlay/README.md +46 -0
- package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
- package/examples/extensions/doom-overlay/doom-component.ts +133 -0
- package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/examples/extensions/doom-overlay/doom-keys.ts +105 -0
- package/examples/extensions/doom-overlay/index.ts +74 -0
- package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
- package/examples/extensions/event-bus.ts +43 -0
- package/examples/extensions/file-trigger.ts +41 -0
- package/examples/extensions/git-checkpoint.ts +53 -0
- package/examples/extensions/handoff.ts +151 -0
- package/examples/extensions/hello.ts +25 -0
- package/examples/extensions/inline-bash.ts +94 -0
- package/examples/extensions/input-transform.ts +43 -0
- package/examples/extensions/interactive-shell.ts +196 -0
- package/examples/extensions/mac-system-theme.ts +47 -0
- package/examples/extensions/message-renderer.ts +60 -0
- package/examples/extensions/modal-editor.ts +86 -0
- package/examples/extensions/model-status.ts +31 -0
- package/examples/extensions/notify.ts +25 -0
- package/examples/extensions/overlay-qa-tests.ts +882 -0
- package/examples/extensions/overlay-test.ts +151 -0
- package/examples/extensions/permission-gate.ts +34 -0
- package/examples/extensions/pirate.ts +47 -0
- package/examples/extensions/plan-mode/README.md +65 -0
- package/examples/extensions/plan-mode/index.ts +341 -0
- package/examples/extensions/plan-mode/utils.ts +168 -0
- package/examples/extensions/preset.ts +399 -0
- package/examples/extensions/protected-paths.ts +30 -0
- package/examples/extensions/qna.ts +120 -0
- package/examples/extensions/question.ts +265 -0
- package/examples/extensions/questionnaire.ts +428 -0
- package/examples/extensions/rainbow-editor.ts +88 -0
- package/examples/extensions/sandbox/index.ts +318 -0
- package/examples/extensions/sandbox/package-lock.json +92 -0
- package/examples/extensions/sandbox/package.json +19 -0
- package/examples/extensions/send-user-message.ts +97 -0
- package/examples/extensions/session-name.ts +27 -0
- package/examples/extensions/shutdown-command.ts +63 -0
- package/examples/extensions/snake.ts +344 -0
- package/examples/extensions/space-invaders.ts +561 -0
- package/examples/extensions/ssh.ts +220 -0
- package/examples/extensions/status-line.ts +40 -0
- package/examples/extensions/subagent/README.md +172 -0
- package/examples/extensions/subagent/agents/planner.md +37 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/subagent/agents/scout.md +50 -0
- package/examples/extensions/subagent/agents/worker.md +24 -0
- package/examples/extensions/subagent/agents.ts +127 -0
- package/examples/extensions/subagent/index.ts +964 -0
- package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/examples/extensions/subagent/prompts/implement.md +10 -0
- package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/examples/extensions/summarize.ts +196 -0
- package/examples/extensions/timed-confirm.ts +70 -0
- package/examples/extensions/todo.ts +300 -0
- package/examples/extensions/tool-override.ts +144 -0
- package/examples/extensions/tools.ts +147 -0
- package/examples/extensions/trigger-compact.ts +40 -0
- package/examples/extensions/truncated-tool.ts +193 -0
- package/examples/extensions/widget-placement.ts +17 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +22 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +50 -0
- package/examples/sdk/03-custom-prompt.ts +55 -0
- package/examples/sdk/04-skills.ts +46 -0
- package/examples/sdk/05-tools.ts +56 -0
- package/examples/sdk/06-extensions.ts +88 -0
- package/examples/sdk/07-context-files.ts +40 -0
- package/examples/sdk/08-prompt-templates.ts +47 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +82 -0
- package/examples/sdk/13-codex-oauth.ts +37 -0
- package/examples/sdk/README.md +144 -0
- package/package.json +85 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension runner - executes extensions and manages their lifecycle.
|
|
3
|
+
*/
|
|
4
|
+
import { theme } from "../../modes/interactive/theme/theme.js";
|
|
5
|
+
// Keybindings for these actions cannot be overridden by extensions
|
|
6
|
+
const RESERVED_ACTIONS_FOR_EXTENSION_CONFLICTS = [
|
|
7
|
+
"interrupt",
|
|
8
|
+
"clear",
|
|
9
|
+
"exit",
|
|
10
|
+
"suspend",
|
|
11
|
+
"cycleThinkingLevel",
|
|
12
|
+
"cycleModelForward",
|
|
13
|
+
"cycleModelBackward",
|
|
14
|
+
"selectModel",
|
|
15
|
+
"expandTools",
|
|
16
|
+
"toggleThinking",
|
|
17
|
+
"externalEditor",
|
|
18
|
+
"followUp",
|
|
19
|
+
"submit",
|
|
20
|
+
"selectConfirm",
|
|
21
|
+
"selectCancel",
|
|
22
|
+
"copy",
|
|
23
|
+
"deleteToLineEnd",
|
|
24
|
+
];
|
|
25
|
+
const buildBuiltinKeybindings = (effectiveKeybindings) => {
|
|
26
|
+
const builtinKeybindings = {};
|
|
27
|
+
for (const [action, keys] of Object.entries(effectiveKeybindings)) {
|
|
28
|
+
const keyAction = action;
|
|
29
|
+
const keyList = Array.isArray(keys) ? keys : [keys];
|
|
30
|
+
const restrictOverride = RESERVED_ACTIONS_FOR_EXTENSION_CONFLICTS.includes(keyAction);
|
|
31
|
+
for (const key of keyList) {
|
|
32
|
+
const normalizedKey = key.toLowerCase();
|
|
33
|
+
builtinKeybindings[normalizedKey] = {
|
|
34
|
+
action: keyAction,
|
|
35
|
+
restrictOverride: restrictOverride,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return builtinKeybindings;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Helper function to emit session_shutdown event to extensions.
|
|
43
|
+
* Returns true if the event was emitted, false if there were no handlers.
|
|
44
|
+
*/
|
|
45
|
+
export async function emitSessionShutdownEvent(extensionRunner) {
|
|
46
|
+
if (extensionRunner?.hasHandlers("session_shutdown")) {
|
|
47
|
+
await extensionRunner.emit({
|
|
48
|
+
type: "session_shutdown",
|
|
49
|
+
});
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const noOpUIContext = {
|
|
55
|
+
select: async () => undefined,
|
|
56
|
+
confirm: async () => false,
|
|
57
|
+
input: async () => undefined,
|
|
58
|
+
notify: () => { },
|
|
59
|
+
setStatus: () => { },
|
|
60
|
+
setWorkingMessage: () => { },
|
|
61
|
+
setWidget: () => { },
|
|
62
|
+
setFooter: () => { },
|
|
63
|
+
setHeader: () => { },
|
|
64
|
+
setTitle: () => { },
|
|
65
|
+
custom: async () => undefined,
|
|
66
|
+
setEditorText: () => { },
|
|
67
|
+
getEditorText: () => "",
|
|
68
|
+
editor: async () => undefined,
|
|
69
|
+
setEditorComponent: () => { },
|
|
70
|
+
get theme() {
|
|
71
|
+
return theme;
|
|
72
|
+
},
|
|
73
|
+
getAllThemes: () => [],
|
|
74
|
+
getTheme: () => undefined,
|
|
75
|
+
setTheme: (_theme) => ({ success: false, error: "UI not available" }),
|
|
76
|
+
};
|
|
77
|
+
export class ExtensionRunner {
|
|
78
|
+
constructor(extensions, runtime, cwd, sessionManager, modelRegistry) {
|
|
79
|
+
this.errorListeners = new Set();
|
|
80
|
+
this.getModel = () => undefined;
|
|
81
|
+
this.isIdleFn = () => true;
|
|
82
|
+
this.waitForIdleFn = async () => { };
|
|
83
|
+
this.abortFn = () => { };
|
|
84
|
+
this.hasPendingMessagesFn = () => false;
|
|
85
|
+
this.getContextUsageFn = () => undefined;
|
|
86
|
+
this.compactFn = () => { };
|
|
87
|
+
this.newSessionHandler = async () => ({ cancelled: false });
|
|
88
|
+
this.forkHandler = async () => ({ cancelled: false });
|
|
89
|
+
this.navigateTreeHandler = async () => ({ cancelled: false });
|
|
90
|
+
this.shutdownHandler = () => { };
|
|
91
|
+
this.shortcutDiagnostics = [];
|
|
92
|
+
this.extensions = extensions;
|
|
93
|
+
this.runtime = runtime;
|
|
94
|
+
this.uiContext = noOpUIContext;
|
|
95
|
+
this.cwd = cwd;
|
|
96
|
+
this.sessionManager = sessionManager;
|
|
97
|
+
this.modelRegistry = modelRegistry;
|
|
98
|
+
}
|
|
99
|
+
bindCore(actions, contextActions) {
|
|
100
|
+
// Copy actions into the shared runtime (all extension APIs reference this)
|
|
101
|
+
this.runtime.sendMessage = actions.sendMessage;
|
|
102
|
+
this.runtime.sendUserMessage = actions.sendUserMessage;
|
|
103
|
+
this.runtime.appendEntry = actions.appendEntry;
|
|
104
|
+
this.runtime.setSessionName = actions.setSessionName;
|
|
105
|
+
this.runtime.getSessionName = actions.getSessionName;
|
|
106
|
+
this.runtime.setLabel = actions.setLabel;
|
|
107
|
+
this.runtime.getActiveTools = actions.getActiveTools;
|
|
108
|
+
this.runtime.getAllTools = actions.getAllTools;
|
|
109
|
+
this.runtime.setActiveTools = actions.setActiveTools;
|
|
110
|
+
this.runtime.setModel = actions.setModel;
|
|
111
|
+
this.runtime.getThinkingLevel = actions.getThinkingLevel;
|
|
112
|
+
this.runtime.setThinkingLevel = actions.setThinkingLevel;
|
|
113
|
+
// Context actions (required)
|
|
114
|
+
this.getModel = contextActions.getModel;
|
|
115
|
+
this.isIdleFn = contextActions.isIdle;
|
|
116
|
+
this.abortFn = contextActions.abort;
|
|
117
|
+
this.hasPendingMessagesFn = contextActions.hasPendingMessages;
|
|
118
|
+
this.shutdownHandler = contextActions.shutdown;
|
|
119
|
+
this.getContextUsageFn = contextActions.getContextUsage;
|
|
120
|
+
this.compactFn = contextActions.compact;
|
|
121
|
+
// Process provider registrations queued during extension loading
|
|
122
|
+
for (const { name, config } of this.runtime.pendingProviderRegistrations) {
|
|
123
|
+
this.modelRegistry.registerProvider(name, config);
|
|
124
|
+
}
|
|
125
|
+
this.runtime.pendingProviderRegistrations = [];
|
|
126
|
+
}
|
|
127
|
+
bindCommandContext(actions) {
|
|
128
|
+
if (actions) {
|
|
129
|
+
this.waitForIdleFn = actions.waitForIdle;
|
|
130
|
+
this.newSessionHandler = actions.newSession;
|
|
131
|
+
this.forkHandler = actions.fork;
|
|
132
|
+
this.navigateTreeHandler = actions.navigateTree;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.waitForIdleFn = async () => { };
|
|
136
|
+
this.newSessionHandler = async () => ({ cancelled: false });
|
|
137
|
+
this.forkHandler = async () => ({ cancelled: false });
|
|
138
|
+
this.navigateTreeHandler = async () => ({ cancelled: false });
|
|
139
|
+
}
|
|
140
|
+
setUIContext(uiContext) {
|
|
141
|
+
this.uiContext = uiContext ?? noOpUIContext;
|
|
142
|
+
}
|
|
143
|
+
getUIContext() {
|
|
144
|
+
return this.uiContext;
|
|
145
|
+
}
|
|
146
|
+
hasUI() {
|
|
147
|
+
return this.uiContext !== noOpUIContext;
|
|
148
|
+
}
|
|
149
|
+
getExtensionPaths() {
|
|
150
|
+
return this.extensions.map((e) => e.path);
|
|
151
|
+
}
|
|
152
|
+
/** Get all registered tools from all extensions. */
|
|
153
|
+
getAllRegisteredTools() {
|
|
154
|
+
const tools = [];
|
|
155
|
+
for (const ext of this.extensions) {
|
|
156
|
+
for (const tool of ext.tools.values()) {
|
|
157
|
+
tools.push(tool);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return tools;
|
|
161
|
+
}
|
|
162
|
+
/** Get a tool definition by name. Returns undefined if not found. */
|
|
163
|
+
getToolDefinition(toolName) {
|
|
164
|
+
for (const ext of this.extensions) {
|
|
165
|
+
const tool = ext.tools.get(toolName);
|
|
166
|
+
if (tool) {
|
|
167
|
+
return tool.definition;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
getFlags() {
|
|
173
|
+
const allFlags = new Map();
|
|
174
|
+
for (const ext of this.extensions) {
|
|
175
|
+
for (const [name, flag] of ext.flags) {
|
|
176
|
+
allFlags.set(name, flag);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return allFlags;
|
|
180
|
+
}
|
|
181
|
+
setFlagValue(name, value) {
|
|
182
|
+
this.runtime.flagValues.set(name, value);
|
|
183
|
+
}
|
|
184
|
+
getFlagValues() {
|
|
185
|
+
return new Map(this.runtime.flagValues);
|
|
186
|
+
}
|
|
187
|
+
getShortcuts(effectiveKeybindings) {
|
|
188
|
+
this.shortcutDiagnostics = [];
|
|
189
|
+
const builtinKeybindings = buildBuiltinKeybindings(effectiveKeybindings);
|
|
190
|
+
const extensionShortcuts = new Map();
|
|
191
|
+
const addDiagnostic = (message, extensionPath) => {
|
|
192
|
+
this.shortcutDiagnostics.push({ type: "warning", message, path: extensionPath });
|
|
193
|
+
if (!this.hasUI()) {
|
|
194
|
+
console.warn(message);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
for (const ext of this.extensions) {
|
|
198
|
+
for (const [key, shortcut] of ext.shortcuts) {
|
|
199
|
+
const normalizedKey = key.toLowerCase();
|
|
200
|
+
const builtInKeybinding = builtinKeybindings[normalizedKey];
|
|
201
|
+
if (builtInKeybinding?.restrictOverride === true) {
|
|
202
|
+
addDiagnostic(`Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`, shortcut.extensionPath);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (builtInKeybinding?.restrictOverride === false) {
|
|
206
|
+
addDiagnostic(`Extension shortcut conflict: '${key}' is built-in shortcut for ${builtInKeybinding.action} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`, shortcut.extensionPath);
|
|
207
|
+
}
|
|
208
|
+
const existingExtensionShortcut = extensionShortcuts.get(normalizedKey);
|
|
209
|
+
if (existingExtensionShortcut) {
|
|
210
|
+
addDiagnostic(`Extension shortcut conflict: '${key}' registered by both ${existingExtensionShortcut.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`, shortcut.extensionPath);
|
|
211
|
+
}
|
|
212
|
+
extensionShortcuts.set(normalizedKey, shortcut);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return extensionShortcuts;
|
|
216
|
+
}
|
|
217
|
+
getShortcutDiagnostics() {
|
|
218
|
+
return this.shortcutDiagnostics;
|
|
219
|
+
}
|
|
220
|
+
onError(listener) {
|
|
221
|
+
this.errorListeners.add(listener);
|
|
222
|
+
return () => this.errorListeners.delete(listener);
|
|
223
|
+
}
|
|
224
|
+
emitError(error) {
|
|
225
|
+
for (const listener of this.errorListeners) {
|
|
226
|
+
listener(error);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
hasHandlers(eventType) {
|
|
230
|
+
for (const ext of this.extensions) {
|
|
231
|
+
const handlers = ext.handlers.get(eventType);
|
|
232
|
+
if (handlers && handlers.length > 0) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
getMessageRenderer(customType) {
|
|
239
|
+
for (const ext of this.extensions) {
|
|
240
|
+
const renderer = ext.messageRenderers.get(customType);
|
|
241
|
+
if (renderer) {
|
|
242
|
+
return renderer;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
getRegisteredCommands() {
|
|
248
|
+
const commands = [];
|
|
249
|
+
for (const ext of this.extensions) {
|
|
250
|
+
for (const command of ext.commands.values()) {
|
|
251
|
+
commands.push(command);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return commands;
|
|
255
|
+
}
|
|
256
|
+
getCommand(name) {
|
|
257
|
+
for (const ext of this.extensions) {
|
|
258
|
+
const command = ext.commands.get(name);
|
|
259
|
+
if (command) {
|
|
260
|
+
return command;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Request a graceful shutdown. Called by extension tools and event handlers.
|
|
267
|
+
* The actual shutdown behavior is provided by the mode via bindExtensions().
|
|
268
|
+
*/
|
|
269
|
+
shutdown() {
|
|
270
|
+
this.shutdownHandler();
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Create an ExtensionContext for use in event handlers and tool execution.
|
|
274
|
+
* Context values are resolved at call time, so changes via bindCore/bindUI are reflected.
|
|
275
|
+
*/
|
|
276
|
+
createContext() {
|
|
277
|
+
const getModel = this.getModel;
|
|
278
|
+
return {
|
|
279
|
+
ui: this.uiContext,
|
|
280
|
+
hasUI: this.hasUI(),
|
|
281
|
+
cwd: this.cwd,
|
|
282
|
+
sessionManager: this.sessionManager,
|
|
283
|
+
modelRegistry: this.modelRegistry,
|
|
284
|
+
get model() {
|
|
285
|
+
return getModel();
|
|
286
|
+
},
|
|
287
|
+
isIdle: () => this.isIdleFn(),
|
|
288
|
+
abort: () => this.abortFn(),
|
|
289
|
+
hasPendingMessages: () => this.hasPendingMessagesFn(),
|
|
290
|
+
shutdown: () => this.shutdownHandler(),
|
|
291
|
+
getContextUsage: () => this.getContextUsageFn(),
|
|
292
|
+
compact: (options) => this.compactFn(options),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
createCommandContext() {
|
|
296
|
+
return {
|
|
297
|
+
...this.createContext(),
|
|
298
|
+
waitForIdle: () => this.waitForIdleFn(),
|
|
299
|
+
newSession: (options) => this.newSessionHandler(options),
|
|
300
|
+
fork: (entryId) => this.forkHandler(entryId),
|
|
301
|
+
navigateTree: (targetId, options) => this.navigateTreeHandler(targetId, options),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
isSessionBeforeEvent(type) {
|
|
305
|
+
return (type === "session_before_switch" ||
|
|
306
|
+
type === "session_before_fork" ||
|
|
307
|
+
type === "session_before_compact" ||
|
|
308
|
+
type === "session_before_tree");
|
|
309
|
+
}
|
|
310
|
+
async emit(event) {
|
|
311
|
+
const ctx = this.createContext();
|
|
312
|
+
let result;
|
|
313
|
+
for (const ext of this.extensions) {
|
|
314
|
+
const handlers = ext.handlers.get(event.type);
|
|
315
|
+
if (!handlers || handlers.length === 0)
|
|
316
|
+
continue;
|
|
317
|
+
for (const handler of handlers) {
|
|
318
|
+
try {
|
|
319
|
+
const handlerResult = await handler(event, ctx);
|
|
320
|
+
if (this.isSessionBeforeEvent(event.type) && handlerResult) {
|
|
321
|
+
result = handlerResult;
|
|
322
|
+
if (result.cancel) {
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (event.type === "tool_result" && handlerResult) {
|
|
327
|
+
result = handlerResult;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
332
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
333
|
+
this.emitError({
|
|
334
|
+
extensionPath: ext.path,
|
|
335
|
+
event: event.type,
|
|
336
|
+
error: message,
|
|
337
|
+
stack,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
async emitToolCall(event) {
|
|
345
|
+
const ctx = this.createContext();
|
|
346
|
+
let result;
|
|
347
|
+
for (const ext of this.extensions) {
|
|
348
|
+
const handlers = ext.handlers.get("tool_call");
|
|
349
|
+
if (!handlers || handlers.length === 0)
|
|
350
|
+
continue;
|
|
351
|
+
for (const handler of handlers) {
|
|
352
|
+
const handlerResult = await handler(event, ctx);
|
|
353
|
+
if (handlerResult) {
|
|
354
|
+
result = handlerResult;
|
|
355
|
+
if (result.block) {
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
async emitUserBash(event) {
|
|
364
|
+
const ctx = this.createContext();
|
|
365
|
+
for (const ext of this.extensions) {
|
|
366
|
+
const handlers = ext.handlers.get("user_bash");
|
|
367
|
+
if (!handlers || handlers.length === 0)
|
|
368
|
+
continue;
|
|
369
|
+
for (const handler of handlers) {
|
|
370
|
+
try {
|
|
371
|
+
const handlerResult = await handler(event, ctx);
|
|
372
|
+
if (handlerResult) {
|
|
373
|
+
return handlerResult;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
378
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
379
|
+
this.emitError({
|
|
380
|
+
extensionPath: ext.path,
|
|
381
|
+
event: "user_bash",
|
|
382
|
+
error: message,
|
|
383
|
+
stack,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
async emitContext(messages) {
|
|
391
|
+
const ctx = this.createContext();
|
|
392
|
+
let currentMessages = structuredClone(messages);
|
|
393
|
+
for (const ext of this.extensions) {
|
|
394
|
+
const handlers = ext.handlers.get("context");
|
|
395
|
+
if (!handlers || handlers.length === 0)
|
|
396
|
+
continue;
|
|
397
|
+
for (const handler of handlers) {
|
|
398
|
+
try {
|
|
399
|
+
const event = { type: "context", messages: currentMessages };
|
|
400
|
+
const handlerResult = await handler(event, ctx);
|
|
401
|
+
if (handlerResult && handlerResult.messages) {
|
|
402
|
+
currentMessages = handlerResult.messages;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
407
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
408
|
+
this.emitError({
|
|
409
|
+
extensionPath: ext.path,
|
|
410
|
+
event: "context",
|
|
411
|
+
error: message,
|
|
412
|
+
stack,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return currentMessages;
|
|
418
|
+
}
|
|
419
|
+
async emitBeforeAgentStart(prompt, images, systemPrompt) {
|
|
420
|
+
const ctx = this.createContext();
|
|
421
|
+
const messages = [];
|
|
422
|
+
let currentSystemPrompt = systemPrompt;
|
|
423
|
+
let systemPromptModified = false;
|
|
424
|
+
for (const ext of this.extensions) {
|
|
425
|
+
const handlers = ext.handlers.get("before_agent_start");
|
|
426
|
+
if (!handlers || handlers.length === 0)
|
|
427
|
+
continue;
|
|
428
|
+
for (const handler of handlers) {
|
|
429
|
+
try {
|
|
430
|
+
const event = {
|
|
431
|
+
type: "before_agent_start",
|
|
432
|
+
prompt,
|
|
433
|
+
images,
|
|
434
|
+
systemPrompt: currentSystemPrompt,
|
|
435
|
+
};
|
|
436
|
+
const handlerResult = await handler(event, ctx);
|
|
437
|
+
if (handlerResult) {
|
|
438
|
+
const result = handlerResult;
|
|
439
|
+
if (result.message) {
|
|
440
|
+
messages.push(result.message);
|
|
441
|
+
}
|
|
442
|
+
if (result.systemPrompt !== undefined) {
|
|
443
|
+
currentSystemPrompt = result.systemPrompt;
|
|
444
|
+
systemPromptModified = true;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch (err) {
|
|
449
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
450
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
451
|
+
this.emitError({
|
|
452
|
+
extensionPath: ext.path,
|
|
453
|
+
event: "before_agent_start",
|
|
454
|
+
error: message,
|
|
455
|
+
stack,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (messages.length > 0 || systemPromptModified) {
|
|
461
|
+
return {
|
|
462
|
+
messages: messages.length > 0 ? messages : undefined,
|
|
463
|
+
systemPrompt: systemPromptModified ? currentSystemPrompt : undefined,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
/** Emit input event. Transforms chain, "handled" short-circuits. */
|
|
469
|
+
async emitInput(text, images, source) {
|
|
470
|
+
const ctx = this.createContext();
|
|
471
|
+
let currentText = text;
|
|
472
|
+
let currentImages = images;
|
|
473
|
+
for (const ext of this.extensions) {
|
|
474
|
+
for (const handler of ext.handlers.get("input") ?? []) {
|
|
475
|
+
try {
|
|
476
|
+
const event = { type: "input", text: currentText, images: currentImages, source };
|
|
477
|
+
const result = (await handler(event, ctx));
|
|
478
|
+
if (result?.action === "handled")
|
|
479
|
+
return result;
|
|
480
|
+
if (result?.action === "transform") {
|
|
481
|
+
currentText = result.text;
|
|
482
|
+
currentImages = result.images ?? currentImages;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
catch (err) {
|
|
486
|
+
this.emitError({
|
|
487
|
+
extensionPath: ext.path,
|
|
488
|
+
event: "input",
|
|
489
|
+
error: err instanceof Error ? err.message : String(err),
|
|
490
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return currentText !== text || currentImages !== images
|
|
496
|
+
? { action: "transform", text: currentText, images: currentImages }
|
|
497
|
+
: { action: "continue" };
|
|
498
|
+
}
|
|
499
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension system types.
|
|
3
|
+
*
|
|
4
|
+
* Extensions are TypeScript modules that can:
|
|
5
|
+
* - Subscribe to agent lifecycle events
|
|
6
|
+
* - Register LLM-callable tools
|
|
7
|
+
* - Register commands, keyboard shortcuts, and CLI flags
|
|
8
|
+
* - Interact with the user via UI primitives
|
|
9
|
+
*/
|
|
10
|
+
// Type guards
|
|
11
|
+
export function isBashToolResult(e) {
|
|
12
|
+
return e.toolName === "bash";
|
|
13
|
+
}
|
|
14
|
+
export function isReadToolResult(e) {
|
|
15
|
+
return e.toolName === "read";
|
|
16
|
+
}
|
|
17
|
+
export function isEditToolResult(e) {
|
|
18
|
+
return e.toolName === "edit";
|
|
19
|
+
}
|
|
20
|
+
export function isWriteToolResult(e) {
|
|
21
|
+
return e.toolName === "write";
|
|
22
|
+
}
|
|
23
|
+
export function isGrepToolResult(e) {
|
|
24
|
+
return e.toolName === "grep";
|
|
25
|
+
}
|
|
26
|
+
export function isFindToolResult(e) {
|
|
27
|
+
return e.toolName === "find";
|
|
28
|
+
}
|
|
29
|
+
export function isLsToolResult(e) {
|
|
30
|
+
return e.toolName === "ls";
|
|
31
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool wrappers for extensions.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Wrap a RegisteredTool into an AgentTool.
|
|
6
|
+
* Uses the runner's createContext() for consistent context across tools and event handlers.
|
|
7
|
+
*/
|
|
8
|
+
export function wrapRegisteredTool(registeredTool, runner) {
|
|
9
|
+
const { definition } = registeredTool;
|
|
10
|
+
return {
|
|
11
|
+
name: definition.name,
|
|
12
|
+
label: definition.label,
|
|
13
|
+
description: definition.description,
|
|
14
|
+
parameters: definition.parameters,
|
|
15
|
+
execute: (toolCallId, params, signal, onUpdate) => definition.execute(toolCallId, params, onUpdate, runner.createContext(), signal),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Wrap all registered tools into AgentTools.
|
|
20
|
+
* Uses the runner's createContext() for consistent context across tools and event handlers.
|
|
21
|
+
*/
|
|
22
|
+
export function wrapRegisteredTools(registeredTools, runner) {
|
|
23
|
+
return registeredTools.map((rt) => wrapRegisteredTool(rt, runner));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Wrap a tool with extension callbacks for interception.
|
|
27
|
+
* - Emits tool_call event before execution (can block)
|
|
28
|
+
* - Emits tool_result event after execution (can modify result)
|
|
29
|
+
*/
|
|
30
|
+
export function wrapToolWithExtensions(tool, runner) {
|
|
31
|
+
return {
|
|
32
|
+
...tool,
|
|
33
|
+
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
34
|
+
// Emit tool_call event - extensions can block execution
|
|
35
|
+
if (runner.hasHandlers("tool_call")) {
|
|
36
|
+
try {
|
|
37
|
+
const callResult = (await runner.emitToolCall({
|
|
38
|
+
type: "tool_call",
|
|
39
|
+
toolName: tool.name,
|
|
40
|
+
toolCallId,
|
|
41
|
+
input: params,
|
|
42
|
+
}));
|
|
43
|
+
if (callResult?.block) {
|
|
44
|
+
const reason = callResult.reason || "Tool execution was blocked by an extension";
|
|
45
|
+
throw new Error(reason);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (err instanceof Error) {
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Extension failed, blocking execution: ${String(err)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Execute the actual tool
|
|
56
|
+
try {
|
|
57
|
+
const result = await tool.execute(toolCallId, params, signal, onUpdate);
|
|
58
|
+
// Emit tool_result event - extensions can modify the result
|
|
59
|
+
if (runner.hasHandlers("tool_result")) {
|
|
60
|
+
const resultResult = (await runner.emit({
|
|
61
|
+
type: "tool_result",
|
|
62
|
+
toolName: tool.name,
|
|
63
|
+
toolCallId,
|
|
64
|
+
input: params,
|
|
65
|
+
content: result.content,
|
|
66
|
+
details: result.details,
|
|
67
|
+
isError: false,
|
|
68
|
+
}));
|
|
69
|
+
if (resultResult) {
|
|
70
|
+
return {
|
|
71
|
+
content: resultResult.content ?? result.content,
|
|
72
|
+
details: (resultResult.details ?? result.details),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
// Emit tool_result event for errors
|
|
80
|
+
if (runner.hasHandlers("tool_result")) {
|
|
81
|
+
await runner.emit({
|
|
82
|
+
type: "tool_result",
|
|
83
|
+
toolName: tool.name,
|
|
84
|
+
toolCallId,
|
|
85
|
+
input: params,
|
|
86
|
+
content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
|
|
87
|
+
details: undefined,
|
|
88
|
+
isError: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Wrap all tools with extension callbacks.
|
|
98
|
+
*/
|
|
99
|
+
export function wrapToolsWithExtensions(tools, runner) {
|
|
100
|
+
return tools.map((tool) => wrapToolWithExtensions(tool, runner));
|
|
101
|
+
}
|