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,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC mode: Headless operation with JSON stdin/stdout protocol.
|
|
3
|
+
*
|
|
4
|
+
* Used for embedding the agent in other applications.
|
|
5
|
+
* Receives commands as JSON on stdin, outputs events and responses as JSON on stdout.
|
|
6
|
+
*
|
|
7
|
+
* Protocol:
|
|
8
|
+
* - Commands: JSON objects with `type` field, optional `id` for correlation
|
|
9
|
+
* - Responses: JSON objects with `type: "response"`, `command`, `success`, and optional `data`/`error`
|
|
10
|
+
* - Events: AgentSessionEvent objects streamed as they occur
|
|
11
|
+
* - Extension UI: Extension UI requests are emitted, client responds with extension_ui_response
|
|
12
|
+
*/
|
|
13
|
+
import * as crypto from "node:crypto";
|
|
14
|
+
import * as readline from "readline";
|
|
15
|
+
import { theme } from "../interactive/theme/theme.js";
|
|
16
|
+
/**
|
|
17
|
+
* Run in RPC mode.
|
|
18
|
+
* Listens for JSON commands on stdin, outputs events and responses on stdout.
|
|
19
|
+
*/
|
|
20
|
+
export async function runRpcMode(session) {
|
|
21
|
+
const output = (obj) => {
|
|
22
|
+
console.log(JSON.stringify(obj));
|
|
23
|
+
};
|
|
24
|
+
const success = (id, command, data) => {
|
|
25
|
+
if (data === undefined) {
|
|
26
|
+
return { id, type: "response", command, success: true };
|
|
27
|
+
}
|
|
28
|
+
return { id, type: "response", command, success: true, data };
|
|
29
|
+
};
|
|
30
|
+
const error = (id, command, message) => {
|
|
31
|
+
return { id, type: "response", command, success: false, error: message };
|
|
32
|
+
};
|
|
33
|
+
// Pending extension UI requests waiting for response
|
|
34
|
+
const pendingExtensionRequests = new Map();
|
|
35
|
+
// Shutdown request flag
|
|
36
|
+
let shutdownRequested = false;
|
|
37
|
+
/** Helper for dialog methods with signal/timeout support */
|
|
38
|
+
function createDialogPromise(opts, defaultValue, request, parseResponse) {
|
|
39
|
+
if (opts?.signal?.aborted)
|
|
40
|
+
return Promise.resolve(defaultValue);
|
|
41
|
+
const id = crypto.randomUUID();
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
let timeoutId;
|
|
44
|
+
const cleanup = () => {
|
|
45
|
+
if (timeoutId)
|
|
46
|
+
clearTimeout(timeoutId);
|
|
47
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
48
|
+
pendingExtensionRequests.delete(id);
|
|
49
|
+
};
|
|
50
|
+
const onAbort = () => {
|
|
51
|
+
cleanup();
|
|
52
|
+
resolve(defaultValue);
|
|
53
|
+
};
|
|
54
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
55
|
+
if (opts?.timeout) {
|
|
56
|
+
timeoutId = setTimeout(() => {
|
|
57
|
+
cleanup();
|
|
58
|
+
resolve(defaultValue);
|
|
59
|
+
}, opts.timeout);
|
|
60
|
+
}
|
|
61
|
+
pendingExtensionRequests.set(id, {
|
|
62
|
+
resolve: (response) => {
|
|
63
|
+
cleanup();
|
|
64
|
+
resolve(parseResponse(response));
|
|
65
|
+
},
|
|
66
|
+
reject,
|
|
67
|
+
});
|
|
68
|
+
output({ type: "extension_ui_request", id, ...request });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create an extension UI context that uses the RPC protocol.
|
|
73
|
+
*/
|
|
74
|
+
const createExtensionUIContext = () => ({
|
|
75
|
+
select: (title, options, opts) => createDialogPromise(opts, undefined, { method: "select", title, options, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? undefined : "value" in r ? r.value : undefined),
|
|
76
|
+
confirm: (title, message, opts) => createDialogPromise(opts, false, { method: "confirm", title, message, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? false : "confirmed" in r ? r.confirmed : false),
|
|
77
|
+
input: (title, placeholder, opts) => createDialogPromise(opts, undefined, { method: "input", title, placeholder, timeout: opts?.timeout }, (r) => "cancelled" in r && r.cancelled ? undefined : "value" in r ? r.value : undefined),
|
|
78
|
+
notify(message, type) {
|
|
79
|
+
// Fire and forget - no response needed
|
|
80
|
+
output({
|
|
81
|
+
type: "extension_ui_request",
|
|
82
|
+
id: crypto.randomUUID(),
|
|
83
|
+
method: "notify",
|
|
84
|
+
message,
|
|
85
|
+
notifyType: type,
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
setStatus(key, text) {
|
|
89
|
+
// Fire and forget - no response needed
|
|
90
|
+
output({
|
|
91
|
+
type: "extension_ui_request",
|
|
92
|
+
id: crypto.randomUUID(),
|
|
93
|
+
method: "setStatus",
|
|
94
|
+
statusKey: key,
|
|
95
|
+
statusText: text,
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
setWorkingMessage(_message) {
|
|
99
|
+
// Working message not supported in RPC mode - requires TUI loader access
|
|
100
|
+
},
|
|
101
|
+
setWidget(key, content, options) {
|
|
102
|
+
// Only support string arrays in RPC mode - factory functions are ignored
|
|
103
|
+
if (content === undefined || Array.isArray(content)) {
|
|
104
|
+
output({
|
|
105
|
+
type: "extension_ui_request",
|
|
106
|
+
id: crypto.randomUUID(),
|
|
107
|
+
method: "setWidget",
|
|
108
|
+
widgetKey: key,
|
|
109
|
+
widgetLines: content,
|
|
110
|
+
widgetPlacement: options?.placement,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Component factories are not supported in RPC mode - would need TUI access
|
|
114
|
+
},
|
|
115
|
+
setFooter(_factory) {
|
|
116
|
+
// Custom footer not supported in RPC mode - requires TUI access
|
|
117
|
+
},
|
|
118
|
+
setHeader(_factory) {
|
|
119
|
+
// Custom header not supported in RPC mode - requires TUI access
|
|
120
|
+
},
|
|
121
|
+
setTitle(title) {
|
|
122
|
+
// Fire and forget - host can implement terminal title control
|
|
123
|
+
output({
|
|
124
|
+
type: "extension_ui_request",
|
|
125
|
+
id: crypto.randomUUID(),
|
|
126
|
+
method: "setTitle",
|
|
127
|
+
title,
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
async custom() {
|
|
131
|
+
// Custom UI not supported in RPC mode
|
|
132
|
+
return undefined;
|
|
133
|
+
},
|
|
134
|
+
setEditorText(text) {
|
|
135
|
+
// Fire and forget - host can implement editor control
|
|
136
|
+
output({
|
|
137
|
+
type: "extension_ui_request",
|
|
138
|
+
id: crypto.randomUUID(),
|
|
139
|
+
method: "set_editor_text",
|
|
140
|
+
text,
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
getEditorText() {
|
|
144
|
+
// Synchronous method can't wait for RPC response
|
|
145
|
+
// Host should track editor state locally if needed
|
|
146
|
+
return "";
|
|
147
|
+
},
|
|
148
|
+
async editor(title, prefill) {
|
|
149
|
+
const id = crypto.randomUUID();
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
pendingExtensionRequests.set(id, {
|
|
152
|
+
resolve: (response) => {
|
|
153
|
+
if ("cancelled" in response && response.cancelled) {
|
|
154
|
+
resolve(undefined);
|
|
155
|
+
}
|
|
156
|
+
else if ("value" in response) {
|
|
157
|
+
resolve(response.value);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
resolve(undefined);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
reject,
|
|
164
|
+
});
|
|
165
|
+
output({ type: "extension_ui_request", id, method: "editor", title, prefill });
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
setEditorComponent() {
|
|
169
|
+
// Custom editor components not supported in RPC mode
|
|
170
|
+
},
|
|
171
|
+
get theme() {
|
|
172
|
+
return theme;
|
|
173
|
+
},
|
|
174
|
+
getAllThemes() {
|
|
175
|
+
return [];
|
|
176
|
+
},
|
|
177
|
+
getTheme(_name) {
|
|
178
|
+
return undefined;
|
|
179
|
+
},
|
|
180
|
+
setTheme(_theme) {
|
|
181
|
+
// Theme switching not supported in RPC mode
|
|
182
|
+
return { success: false, error: "Theme switching not supported in RPC mode" };
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
// Set up extensions with RPC-based UI context
|
|
186
|
+
const extensionRunner = session.extensionRunner;
|
|
187
|
+
if (extensionRunner) {
|
|
188
|
+
await session.bindExtensions({
|
|
189
|
+
uiContext: createExtensionUIContext(),
|
|
190
|
+
commandContextActions: {
|
|
191
|
+
waitForIdle: () => session.agent.waitForIdle(),
|
|
192
|
+
newSession: async (options) => {
|
|
193
|
+
const success = await session.newSession({ parentSession: options?.parentSession });
|
|
194
|
+
if (success && options?.setup) {
|
|
195
|
+
await options.setup(session.sessionManager);
|
|
196
|
+
}
|
|
197
|
+
return { cancelled: !success };
|
|
198
|
+
},
|
|
199
|
+
fork: async (entryId) => {
|
|
200
|
+
const result = await session.fork(entryId);
|
|
201
|
+
return { cancelled: result.cancelled };
|
|
202
|
+
},
|
|
203
|
+
navigateTree: async (targetId, options) => {
|
|
204
|
+
const result = await session.navigateTree(targetId, {
|
|
205
|
+
summarize: options?.summarize,
|
|
206
|
+
customInstructions: options?.customInstructions,
|
|
207
|
+
replaceInstructions: options?.replaceInstructions,
|
|
208
|
+
label: options?.label,
|
|
209
|
+
});
|
|
210
|
+
return { cancelled: result.cancelled };
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
shutdownHandler: () => {
|
|
214
|
+
shutdownRequested = true;
|
|
215
|
+
},
|
|
216
|
+
onError: (err) => {
|
|
217
|
+
output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error });
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// Output all agent events as JSON
|
|
222
|
+
session.subscribe((event) => {
|
|
223
|
+
output(event);
|
|
224
|
+
});
|
|
225
|
+
// Handle a single command
|
|
226
|
+
const handleCommand = async (command) => {
|
|
227
|
+
const id = command.id;
|
|
228
|
+
switch (command.type) {
|
|
229
|
+
// =================================================================
|
|
230
|
+
// Prompting
|
|
231
|
+
// =================================================================
|
|
232
|
+
case "prompt": {
|
|
233
|
+
// Don't await - events will stream
|
|
234
|
+
// Extension commands are executed immediately, file prompt templates are expanded
|
|
235
|
+
// If streaming and streamingBehavior specified, queues via steer/followUp
|
|
236
|
+
session
|
|
237
|
+
.prompt(command.message, {
|
|
238
|
+
images: command.images,
|
|
239
|
+
streamingBehavior: command.streamingBehavior,
|
|
240
|
+
source: "rpc",
|
|
241
|
+
})
|
|
242
|
+
.catch((e) => output(error(id, "prompt", e.message)));
|
|
243
|
+
return success(id, "prompt");
|
|
244
|
+
}
|
|
245
|
+
case "steer": {
|
|
246
|
+
await session.steer(command.message);
|
|
247
|
+
return success(id, "steer");
|
|
248
|
+
}
|
|
249
|
+
case "follow_up": {
|
|
250
|
+
await session.followUp(command.message);
|
|
251
|
+
return success(id, "follow_up");
|
|
252
|
+
}
|
|
253
|
+
case "abort": {
|
|
254
|
+
await session.abort();
|
|
255
|
+
return success(id, "abort");
|
|
256
|
+
}
|
|
257
|
+
case "new_session": {
|
|
258
|
+
const options = command.parentSession ? { parentSession: command.parentSession } : undefined;
|
|
259
|
+
const cancelled = !(await session.newSession(options));
|
|
260
|
+
return success(id, "new_session", { cancelled });
|
|
261
|
+
}
|
|
262
|
+
// =================================================================
|
|
263
|
+
// State
|
|
264
|
+
// =================================================================
|
|
265
|
+
case "get_state": {
|
|
266
|
+
const state = {
|
|
267
|
+
model: session.model,
|
|
268
|
+
thinkingLevel: session.thinkingLevel,
|
|
269
|
+
isStreaming: session.isStreaming,
|
|
270
|
+
isCompacting: session.isCompacting,
|
|
271
|
+
steeringMode: session.steeringMode,
|
|
272
|
+
followUpMode: session.followUpMode,
|
|
273
|
+
sessionFile: session.sessionFile,
|
|
274
|
+
sessionId: session.sessionId,
|
|
275
|
+
autoCompactionEnabled: session.autoCompactionEnabled,
|
|
276
|
+
messageCount: session.messages.length,
|
|
277
|
+
pendingMessageCount: session.pendingMessageCount,
|
|
278
|
+
};
|
|
279
|
+
return success(id, "get_state", state);
|
|
280
|
+
}
|
|
281
|
+
// =================================================================
|
|
282
|
+
// Model
|
|
283
|
+
// =================================================================
|
|
284
|
+
case "set_model": {
|
|
285
|
+
const models = await session.modelRegistry.getAvailable();
|
|
286
|
+
const model = models.find((m) => m.provider === command.provider && m.id === command.modelId);
|
|
287
|
+
if (!model) {
|
|
288
|
+
return error(id, "set_model", `Model not found: ${command.provider}/${command.modelId}`);
|
|
289
|
+
}
|
|
290
|
+
await session.setModel(model);
|
|
291
|
+
return success(id, "set_model", model);
|
|
292
|
+
}
|
|
293
|
+
case "cycle_model": {
|
|
294
|
+
const result = await session.cycleModel();
|
|
295
|
+
if (!result) {
|
|
296
|
+
return success(id, "cycle_model", null);
|
|
297
|
+
}
|
|
298
|
+
return success(id, "cycle_model", result);
|
|
299
|
+
}
|
|
300
|
+
case "get_available_models": {
|
|
301
|
+
const models = await session.modelRegistry.getAvailable();
|
|
302
|
+
return success(id, "get_available_models", { models });
|
|
303
|
+
}
|
|
304
|
+
// =================================================================
|
|
305
|
+
// Thinking
|
|
306
|
+
// =================================================================
|
|
307
|
+
case "set_thinking_level": {
|
|
308
|
+
session.setThinkingLevel(command.level);
|
|
309
|
+
return success(id, "set_thinking_level");
|
|
310
|
+
}
|
|
311
|
+
case "cycle_thinking_level": {
|
|
312
|
+
const level = session.cycleThinkingLevel();
|
|
313
|
+
if (!level) {
|
|
314
|
+
return success(id, "cycle_thinking_level", null);
|
|
315
|
+
}
|
|
316
|
+
return success(id, "cycle_thinking_level", { level });
|
|
317
|
+
}
|
|
318
|
+
// =================================================================
|
|
319
|
+
// Queue Modes
|
|
320
|
+
// =================================================================
|
|
321
|
+
case "set_steering_mode": {
|
|
322
|
+
session.setSteeringMode(command.mode);
|
|
323
|
+
return success(id, "set_steering_mode");
|
|
324
|
+
}
|
|
325
|
+
case "set_follow_up_mode": {
|
|
326
|
+
session.setFollowUpMode(command.mode);
|
|
327
|
+
return success(id, "set_follow_up_mode");
|
|
328
|
+
}
|
|
329
|
+
// =================================================================
|
|
330
|
+
// Compaction
|
|
331
|
+
// =================================================================
|
|
332
|
+
case "compact": {
|
|
333
|
+
const result = await session.compact(command.customInstructions);
|
|
334
|
+
return success(id, "compact", result);
|
|
335
|
+
}
|
|
336
|
+
case "set_auto_compaction": {
|
|
337
|
+
session.setAutoCompactionEnabled(command.enabled);
|
|
338
|
+
return success(id, "set_auto_compaction");
|
|
339
|
+
}
|
|
340
|
+
// =================================================================
|
|
341
|
+
// Retry
|
|
342
|
+
// =================================================================
|
|
343
|
+
case "set_auto_retry": {
|
|
344
|
+
session.setAutoRetryEnabled(command.enabled);
|
|
345
|
+
return success(id, "set_auto_retry");
|
|
346
|
+
}
|
|
347
|
+
case "abort_retry": {
|
|
348
|
+
session.abortRetry();
|
|
349
|
+
return success(id, "abort_retry");
|
|
350
|
+
}
|
|
351
|
+
// =================================================================
|
|
352
|
+
// Bash
|
|
353
|
+
// =================================================================
|
|
354
|
+
case "bash": {
|
|
355
|
+
const result = await session.executeBash(command.command);
|
|
356
|
+
return success(id, "bash", result);
|
|
357
|
+
}
|
|
358
|
+
case "abort_bash": {
|
|
359
|
+
session.abortBash();
|
|
360
|
+
return success(id, "abort_bash");
|
|
361
|
+
}
|
|
362
|
+
// =================================================================
|
|
363
|
+
// Session
|
|
364
|
+
// =================================================================
|
|
365
|
+
case "get_session_stats": {
|
|
366
|
+
const stats = session.getSessionStats();
|
|
367
|
+
return success(id, "get_session_stats", stats);
|
|
368
|
+
}
|
|
369
|
+
case "export_html": {
|
|
370
|
+
const path = await session.exportToHtml(command.outputPath);
|
|
371
|
+
return success(id, "export_html", { path });
|
|
372
|
+
}
|
|
373
|
+
case "switch_session": {
|
|
374
|
+
const cancelled = !(await session.switchSession(command.sessionPath));
|
|
375
|
+
return success(id, "switch_session", { cancelled });
|
|
376
|
+
}
|
|
377
|
+
case "fork": {
|
|
378
|
+
const result = await session.fork(command.entryId);
|
|
379
|
+
return success(id, "fork", { text: result.selectedText, cancelled: result.cancelled });
|
|
380
|
+
}
|
|
381
|
+
case "get_fork_messages": {
|
|
382
|
+
const messages = session.getUserMessagesForForking();
|
|
383
|
+
return success(id, "get_fork_messages", { messages });
|
|
384
|
+
}
|
|
385
|
+
case "get_last_assistant_text": {
|
|
386
|
+
const text = session.getLastAssistantText();
|
|
387
|
+
return success(id, "get_last_assistant_text", { text });
|
|
388
|
+
}
|
|
389
|
+
// =================================================================
|
|
390
|
+
// Messages
|
|
391
|
+
// =================================================================
|
|
392
|
+
case "get_messages": {
|
|
393
|
+
return success(id, "get_messages", { messages: session.messages });
|
|
394
|
+
}
|
|
395
|
+
default: {
|
|
396
|
+
const unknownCommand = command;
|
|
397
|
+
return error(undefined, unknownCommand.type, `Unknown command: ${unknownCommand.type}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
/**
|
|
402
|
+
* Check if shutdown was requested and perform shutdown if so.
|
|
403
|
+
* Called after handling each command when waiting for the next command.
|
|
404
|
+
*/
|
|
405
|
+
async function checkShutdownRequested() {
|
|
406
|
+
if (!shutdownRequested)
|
|
407
|
+
return;
|
|
408
|
+
const currentRunner = session.extensionRunner;
|
|
409
|
+
if (currentRunner?.hasHandlers("session_shutdown")) {
|
|
410
|
+
await currentRunner.emit({ type: "session_shutdown" });
|
|
411
|
+
}
|
|
412
|
+
// Close readline interface to stop waiting for input
|
|
413
|
+
rl.close();
|
|
414
|
+
process.exit(0);
|
|
415
|
+
}
|
|
416
|
+
// Listen for JSON input
|
|
417
|
+
const rl = readline.createInterface({
|
|
418
|
+
input: process.stdin,
|
|
419
|
+
output: process.stdout,
|
|
420
|
+
terminal: false,
|
|
421
|
+
});
|
|
422
|
+
rl.on("line", async (line) => {
|
|
423
|
+
try {
|
|
424
|
+
const parsed = JSON.parse(line);
|
|
425
|
+
// Handle extension UI responses
|
|
426
|
+
if (parsed.type === "extension_ui_response") {
|
|
427
|
+
const response = parsed;
|
|
428
|
+
const pending = pendingExtensionRequests.get(response.id);
|
|
429
|
+
if (pending) {
|
|
430
|
+
pendingExtensionRequests.delete(response.id);
|
|
431
|
+
pending.resolve(response);
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
// Handle regular commands
|
|
436
|
+
const command = parsed;
|
|
437
|
+
const response = await handleCommand(command);
|
|
438
|
+
output(response);
|
|
439
|
+
// Check for deferred shutdown request (idle between commands)
|
|
440
|
+
await checkShutdownRequested();
|
|
441
|
+
}
|
|
442
|
+
catch (e) {
|
|
443
|
+
output(error(undefined, "parse", `Failed to parse command: ${e.message}`));
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
// Keep process alive forever
|
|
447
|
+
return new Promise(() => { });
|
|
448
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
/**
|
|
3
|
+
* Parse changelog entries from CHANGELOG.md
|
|
4
|
+
* Scans for ## lines and collects content until next ## or EOF
|
|
5
|
+
*/
|
|
6
|
+
export function parseChangelog(changelogPath) {
|
|
7
|
+
if (!existsSync(changelogPath)) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const content = readFileSync(changelogPath, "utf-8");
|
|
12
|
+
const lines = content.split("\n");
|
|
13
|
+
const entries = [];
|
|
14
|
+
let currentLines = [];
|
|
15
|
+
let currentVersion = null;
|
|
16
|
+
for (const line of lines) {
|
|
17
|
+
// Check if this is a version header (## [x.y.z] ...)
|
|
18
|
+
if (line.startsWith("## ")) {
|
|
19
|
+
// Save previous entry if exists
|
|
20
|
+
if (currentVersion && currentLines.length > 0) {
|
|
21
|
+
entries.push({
|
|
22
|
+
...currentVersion,
|
|
23
|
+
content: currentLines.join("\n").trim(),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
// Try to parse version from this line
|
|
27
|
+
const versionMatch = line.match(/##\s+\[?(\d+)\.(\d+)\.(\d+)\]?/);
|
|
28
|
+
if (versionMatch) {
|
|
29
|
+
currentVersion = {
|
|
30
|
+
major: Number.parseInt(versionMatch[1], 10),
|
|
31
|
+
minor: Number.parseInt(versionMatch[2], 10),
|
|
32
|
+
patch: Number.parseInt(versionMatch[3], 10),
|
|
33
|
+
};
|
|
34
|
+
currentLines = [line];
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Reset if we can't parse version
|
|
38
|
+
currentVersion = null;
|
|
39
|
+
currentLines = [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (currentVersion) {
|
|
43
|
+
// Collect lines for current version
|
|
44
|
+
currentLines.push(line);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Save last entry
|
|
48
|
+
if (currentVersion && currentLines.length > 0) {
|
|
49
|
+
entries.push({
|
|
50
|
+
...currentVersion,
|
|
51
|
+
content: currentLines.join("\n").trim(),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return entries;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error(`Warning: Could not parse changelog: ${error}`);
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Compare versions. Returns: -1 if v1 < v2, 0 if v1 === v2, 1 if v1 > v2
|
|
63
|
+
*/
|
|
64
|
+
export function compareVersions(v1, v2) {
|
|
65
|
+
if (v1.major !== v2.major)
|
|
66
|
+
return v1.major - v2.major;
|
|
67
|
+
if (v1.minor !== v2.minor)
|
|
68
|
+
return v1.minor - v2.minor;
|
|
69
|
+
return v1.patch - v2.patch;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get entries newer than lastVersion
|
|
73
|
+
*/
|
|
74
|
+
export function getNewEntries(entries, lastVersion) {
|
|
75
|
+
// Parse lastVersion
|
|
76
|
+
const parts = lastVersion.split(".").map(Number);
|
|
77
|
+
const last = {
|
|
78
|
+
major: parts[0] || 0,
|
|
79
|
+
minor: parts[1] || 0,
|
|
80
|
+
patch: parts[2] || 0,
|
|
81
|
+
content: "",
|
|
82
|
+
};
|
|
83
|
+
return entries.filter((entry) => compareVersions(entry, last) > 0);
|
|
84
|
+
}
|
|
85
|
+
// Re-export getChangelogPath from paths.ts for convenience
|
|
86
|
+
export { getChangelogPath } from "../config.js";
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import Clipboard from "@mariozechner/clipboard";
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
3
|
+
const PREFERRED_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
4
|
+
const DEFAULT_LIST_TIMEOUT_MS = 1000;
|
|
5
|
+
const DEFAULT_READ_TIMEOUT_MS = 3000;
|
|
6
|
+
const DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
7
|
+
export function isWaylandSession(env = process.env) {
|
|
8
|
+
return Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === "wayland";
|
|
9
|
+
}
|
|
10
|
+
function baseMimeType(mimeType) {
|
|
11
|
+
return mimeType.split(";")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
export function extensionForImageMimeType(mimeType) {
|
|
14
|
+
switch (baseMimeType(mimeType)) {
|
|
15
|
+
case "image/png":
|
|
16
|
+
return "png";
|
|
17
|
+
case "image/jpeg":
|
|
18
|
+
return "jpg";
|
|
19
|
+
case "image/webp":
|
|
20
|
+
return "webp";
|
|
21
|
+
case "image/gif":
|
|
22
|
+
return "gif";
|
|
23
|
+
default:
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function selectPreferredImageMimeType(mimeTypes) {
|
|
28
|
+
const normalized = mimeTypes
|
|
29
|
+
.map((t) => t.trim())
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.map((t) => ({ raw: t, base: baseMimeType(t) }));
|
|
32
|
+
for (const preferred of PREFERRED_IMAGE_MIME_TYPES) {
|
|
33
|
+
const match = normalized.find((t) => t.base === preferred);
|
|
34
|
+
if (match) {
|
|
35
|
+
return match.raw;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const anyImage = normalized.find((t) => t.base.startsWith("image/"));
|
|
39
|
+
return anyImage?.raw ?? null;
|
|
40
|
+
}
|
|
41
|
+
function runCommand(command, args, options) {
|
|
42
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;
|
|
43
|
+
const maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
44
|
+
const result = spawnSync(command, args, {
|
|
45
|
+
timeout: timeoutMs,
|
|
46
|
+
maxBuffer: maxBufferBytes,
|
|
47
|
+
});
|
|
48
|
+
if (result.error) {
|
|
49
|
+
return { ok: false, stdout: Buffer.alloc(0) };
|
|
50
|
+
}
|
|
51
|
+
if (result.status !== 0) {
|
|
52
|
+
return { ok: false, stdout: Buffer.alloc(0) };
|
|
53
|
+
}
|
|
54
|
+
const stdout = Buffer.isBuffer(result.stdout)
|
|
55
|
+
? result.stdout
|
|
56
|
+
: Buffer.from(result.stdout ?? "", typeof result.stdout === "string" ? "utf-8" : undefined);
|
|
57
|
+
return { ok: true, stdout };
|
|
58
|
+
}
|
|
59
|
+
function readClipboardImageViaWlPaste() {
|
|
60
|
+
const list = runCommand("wl-paste", ["--list-types"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });
|
|
61
|
+
if (!list.ok) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const types = list.stdout
|
|
65
|
+
.toString("utf-8")
|
|
66
|
+
.split(/\r?\n/)
|
|
67
|
+
.map((t) => t.trim())
|
|
68
|
+
.filter(Boolean);
|
|
69
|
+
const selectedType = selectPreferredImageMimeType(types);
|
|
70
|
+
if (!selectedType) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const data = runCommand("wl-paste", ["--type", selectedType, "--no-newline"]);
|
|
74
|
+
if (!data.ok || data.stdout.length === 0) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return { bytes: data.stdout, mimeType: baseMimeType(selectedType) };
|
|
78
|
+
}
|
|
79
|
+
function readClipboardImageViaXclip() {
|
|
80
|
+
const targets = runCommand("xclip", ["-selection", "clipboard", "-t", "TARGETS", "-o"], {
|
|
81
|
+
timeoutMs: DEFAULT_LIST_TIMEOUT_MS,
|
|
82
|
+
});
|
|
83
|
+
let candidateTypes = [];
|
|
84
|
+
if (targets.ok) {
|
|
85
|
+
candidateTypes = targets.stdout
|
|
86
|
+
.toString("utf-8")
|
|
87
|
+
.split(/\r?\n/)
|
|
88
|
+
.map((t) => t.trim())
|
|
89
|
+
.filter(Boolean);
|
|
90
|
+
}
|
|
91
|
+
const preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;
|
|
92
|
+
const tryTypes = preferred ? [preferred, ...PREFERRED_IMAGE_MIME_TYPES] : [...PREFERRED_IMAGE_MIME_TYPES];
|
|
93
|
+
for (const mimeType of tryTypes) {
|
|
94
|
+
const data = runCommand("xclip", ["-selection", "clipboard", "-t", mimeType, "-o"]);
|
|
95
|
+
if (data.ok && data.stdout.length > 0) {
|
|
96
|
+
return { bytes: data.stdout, mimeType: baseMimeType(mimeType) };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
export async function readClipboardImage(options) {
|
|
102
|
+
const env = options?.env ?? process.env;
|
|
103
|
+
const platform = options?.platform ?? process.platform;
|
|
104
|
+
if (platform === "linux" && isWaylandSession(env)) {
|
|
105
|
+
return readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();
|
|
106
|
+
}
|
|
107
|
+
if (!Clipboard.hasImage()) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const imageData = await Clipboard.getImageBinary();
|
|
111
|
+
if (!imageData || imageData.length === 0) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);
|
|
115
|
+
return { bytes, mimeType: "image/png" };
|
|
116
|
+
}
|