indusagi-coding-agent 0.50.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,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOOM Engine - WebAssembly wrapper for doomgeneric
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
export interface DoomModule {
|
|
11
|
+
_doomgeneric_Create: (argc: number, argv: number) => void;
|
|
12
|
+
_doomgeneric_Tick: () => void;
|
|
13
|
+
_DG_GetFrameBuffer: () => number;
|
|
14
|
+
_DG_GetScreenWidth: () => number;
|
|
15
|
+
_DG_GetScreenHeight: () => number;
|
|
16
|
+
_DG_PushKeyEvent: (pressed: number, key: number) => void;
|
|
17
|
+
_malloc: (size: number) => number;
|
|
18
|
+
_free: (ptr: number) => void;
|
|
19
|
+
HEAPU8: Uint8Array;
|
|
20
|
+
HEAPU32: Uint32Array;
|
|
21
|
+
FS_createDataFile: (parent: string, name: string, data: number[], canRead: boolean, canWrite: boolean) => void;
|
|
22
|
+
FS_createPath: (parent: string, path: string, canRead: boolean, canWrite: boolean) => string;
|
|
23
|
+
setValue: (ptr: number, value: number, type: string) => void;
|
|
24
|
+
getValue: (ptr: number, type: string) => number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class DoomEngine {
|
|
28
|
+
private module: DoomModule | null = null;
|
|
29
|
+
private frameBufferPtr: number = 0;
|
|
30
|
+
private initialized = false;
|
|
31
|
+
private wadPath: string;
|
|
32
|
+
private _width = 640;
|
|
33
|
+
private _height = 400;
|
|
34
|
+
|
|
35
|
+
constructor(wadPath: string) {
|
|
36
|
+
this.wadPath = wadPath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get width(): number {
|
|
40
|
+
return this._width;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get height(): number {
|
|
44
|
+
return this._height;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async init(): Promise<void> {
|
|
48
|
+
// Locate WASM build
|
|
49
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
50
|
+
const buildDir = join(__dirname, "doom", "build");
|
|
51
|
+
const doomJsPath = join(buildDir, "doom.js");
|
|
52
|
+
|
|
53
|
+
if (!existsSync(doomJsPath)) {
|
|
54
|
+
throw new Error(`WASM not found at ${doomJsPath}. Run ./doom/build.sh first`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Read WAD file
|
|
58
|
+
const wadData = readFileSync(this.wadPath);
|
|
59
|
+
const wadArray = Array.from(new Uint8Array(wadData));
|
|
60
|
+
|
|
61
|
+
// Load WASM module - eval to bypass jiti completely
|
|
62
|
+
const doomJsCode = readFileSync(doomJsPath, "utf-8");
|
|
63
|
+
const moduleExports: { exports: unknown } = { exports: {} };
|
|
64
|
+
const nativeRequire = createRequire(doomJsPath);
|
|
65
|
+
const moduleFunc = new Function("module", "exports", "__dirname", "__filename", "require", doomJsCode);
|
|
66
|
+
moduleFunc(moduleExports, moduleExports.exports, buildDir, doomJsPath, nativeRequire);
|
|
67
|
+
const createDoomModule = moduleExports.exports as (config: unknown) => Promise<DoomModule>;
|
|
68
|
+
|
|
69
|
+
const moduleConfig = {
|
|
70
|
+
locateFile: (path: string) => {
|
|
71
|
+
if (path.endsWith(".wasm")) {
|
|
72
|
+
return join(buildDir, path);
|
|
73
|
+
}
|
|
74
|
+
return path;
|
|
75
|
+
},
|
|
76
|
+
print: () => {},
|
|
77
|
+
printErr: () => {},
|
|
78
|
+
preRun: [
|
|
79
|
+
(module: DoomModule) => {
|
|
80
|
+
// Create /doom directory and add WAD
|
|
81
|
+
module.FS_createPath("/", "doom", true, true);
|
|
82
|
+
module.FS_createDataFile("/doom", "doom1.wad", wadArray, true, false);
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
this.module = await createDoomModule(moduleConfig);
|
|
88
|
+
if (!this.module) {
|
|
89
|
+
throw new Error("Failed to initialize DOOM module");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Initialize DOOM
|
|
93
|
+
this.initDoom();
|
|
94
|
+
|
|
95
|
+
// Get framebuffer info
|
|
96
|
+
this.frameBufferPtr = this.module._DG_GetFrameBuffer();
|
|
97
|
+
this._width = this.module._DG_GetScreenWidth();
|
|
98
|
+
this._height = this.module._DG_GetScreenHeight();
|
|
99
|
+
this.initialized = true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private initDoom(): void {
|
|
103
|
+
if (!this.module) return;
|
|
104
|
+
|
|
105
|
+
const args = ["doom", "-iwad", "/doom/doom1.wad"];
|
|
106
|
+
const argPtrs: number[] = [];
|
|
107
|
+
|
|
108
|
+
for (const arg of args) {
|
|
109
|
+
const ptr = this.module._malloc(arg.length + 1);
|
|
110
|
+
for (let i = 0; i < arg.length; i++) {
|
|
111
|
+
this.module.setValue(ptr + i, arg.charCodeAt(i), "i8");
|
|
112
|
+
}
|
|
113
|
+
this.module.setValue(ptr + arg.length, 0, "i8");
|
|
114
|
+
argPtrs.push(ptr);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const argvPtr = this.module._malloc(argPtrs.length * 4);
|
|
118
|
+
for (let i = 0; i < argPtrs.length; i++) {
|
|
119
|
+
this.module.setValue(argvPtr + i * 4, argPtrs[i]!, "i32");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.module._doomgeneric_Create(args.length, argvPtr);
|
|
123
|
+
|
|
124
|
+
for (const ptr of argPtrs) {
|
|
125
|
+
this.module._free(ptr);
|
|
126
|
+
}
|
|
127
|
+
this.module._free(argvPtr);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Run one game tick
|
|
132
|
+
*/
|
|
133
|
+
tick(): void {
|
|
134
|
+
if (!this.module || !this.initialized) return;
|
|
135
|
+
this.module._doomgeneric_Tick();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get current frame as RGBA pixel data
|
|
140
|
+
* DOOM outputs ARGB, we convert to RGBA
|
|
141
|
+
*/
|
|
142
|
+
getFrameRGBA(): Uint8Array {
|
|
143
|
+
if (!this.module || !this.initialized) {
|
|
144
|
+
return new Uint8Array(this._width * this._height * 4);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const pixels = this._width * this._height;
|
|
148
|
+
const buffer = new Uint8Array(pixels * 4);
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < pixels; i++) {
|
|
151
|
+
const argb = this.module.getValue(this.frameBufferPtr + i * 4, "i32");
|
|
152
|
+
const offset = i * 4;
|
|
153
|
+
buffer[offset + 0] = (argb >> 16) & 0xff; // R
|
|
154
|
+
buffer[offset + 1] = (argb >> 8) & 0xff; // G
|
|
155
|
+
buffer[offset + 2] = argb & 0xff; // B
|
|
156
|
+
buffer[offset + 3] = 255; // A
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return buffer;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Push a key event
|
|
164
|
+
*/
|
|
165
|
+
pushKey(pressed: boolean, key: number): void {
|
|
166
|
+
if (!this.module || !this.initialized) return;
|
|
167
|
+
this.module._DG_PushKeyEvent(pressed ? 1 : 0, key);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
isInitialized(): boolean {
|
|
171
|
+
return this.initialized;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOOM key codes (from doomkeys.h)
|
|
3
|
+
*/
|
|
4
|
+
export const DoomKeys = {
|
|
5
|
+
KEY_RIGHTARROW: 0xae,
|
|
6
|
+
KEY_LEFTARROW: 0xac,
|
|
7
|
+
KEY_UPARROW: 0xad,
|
|
8
|
+
KEY_DOWNARROW: 0xaf,
|
|
9
|
+
KEY_STRAFE_L: 0xa0,
|
|
10
|
+
KEY_STRAFE_R: 0xa1,
|
|
11
|
+
KEY_USE: 0xa2,
|
|
12
|
+
KEY_FIRE: 0xa3,
|
|
13
|
+
KEY_ESCAPE: 27,
|
|
14
|
+
KEY_ENTER: 13,
|
|
15
|
+
KEY_TAB: 9,
|
|
16
|
+
KEY_F1: 0x80 + 0x3b,
|
|
17
|
+
KEY_F2: 0x80 + 0x3c,
|
|
18
|
+
KEY_F3: 0x80 + 0x3d,
|
|
19
|
+
KEY_F4: 0x80 + 0x3e,
|
|
20
|
+
KEY_F5: 0x80 + 0x3f,
|
|
21
|
+
KEY_F6: 0x80 + 0x40,
|
|
22
|
+
KEY_F7: 0x80 + 0x41,
|
|
23
|
+
KEY_F8: 0x80 + 0x42,
|
|
24
|
+
KEY_F9: 0x80 + 0x43,
|
|
25
|
+
KEY_F10: 0x80 + 0x44,
|
|
26
|
+
KEY_F11: 0x80 + 0x57,
|
|
27
|
+
KEY_F12: 0x80 + 0x58,
|
|
28
|
+
KEY_BACKSPACE: 127,
|
|
29
|
+
KEY_PAUSE: 0xff,
|
|
30
|
+
KEY_EQUALS: 0x3d,
|
|
31
|
+
KEY_MINUS: 0x2d,
|
|
32
|
+
KEY_RSHIFT: 0x80 + 0x36,
|
|
33
|
+
KEY_RCTRL: 0x80 + 0x1d,
|
|
34
|
+
KEY_RALT: 0x80 + 0x38,
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
37
|
+
import { Key, matchesKey, parseKey } from "indusagi/tui";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Map terminal key input to DOOM key codes
|
|
41
|
+
* Supports both raw terminal input and Kitty protocol sequences
|
|
42
|
+
*/
|
|
43
|
+
export function mapKeyToDoom(data: string): number[] {
|
|
44
|
+
// Arrow keys
|
|
45
|
+
if (matchesKey(data, Key.up)) return [DoomKeys.KEY_UPARROW];
|
|
46
|
+
if (matchesKey(data, Key.down)) return [DoomKeys.KEY_DOWNARROW];
|
|
47
|
+
if (matchesKey(data, Key.right)) return [DoomKeys.KEY_RIGHTARROW];
|
|
48
|
+
if (matchesKey(data, Key.left)) return [DoomKeys.KEY_LEFTARROW];
|
|
49
|
+
|
|
50
|
+
// WASD - check both raw char and Kitty sequences
|
|
51
|
+
if (data === "w" || matchesKey(data, "w")) return [DoomKeys.KEY_UPARROW];
|
|
52
|
+
if (data === "W" || matchesKey(data, Key.shift("w"))) return [DoomKeys.KEY_UPARROW, DoomKeys.KEY_RSHIFT];
|
|
53
|
+
if (data === "s" || matchesKey(data, "s")) return [DoomKeys.KEY_DOWNARROW];
|
|
54
|
+
if (data === "S" || matchesKey(data, Key.shift("s"))) return [DoomKeys.KEY_DOWNARROW, DoomKeys.KEY_RSHIFT];
|
|
55
|
+
if (data === "a" || matchesKey(data, "a")) return [DoomKeys.KEY_STRAFE_L];
|
|
56
|
+
if (data === "A" || matchesKey(data, Key.shift("a"))) return [DoomKeys.KEY_STRAFE_L, DoomKeys.KEY_RSHIFT];
|
|
57
|
+
if (data === "d" || matchesKey(data, "d")) return [DoomKeys.KEY_STRAFE_R];
|
|
58
|
+
if (data === "D" || matchesKey(data, Key.shift("d"))) return [DoomKeys.KEY_STRAFE_R, DoomKeys.KEY_RSHIFT];
|
|
59
|
+
|
|
60
|
+
// Fire - F key
|
|
61
|
+
if (data === "f" || data === "F" || matchesKey(data, "f") || matchesKey(data, Key.shift("f"))) {
|
|
62
|
+
return [DoomKeys.KEY_FIRE];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Use/Open
|
|
66
|
+
if (data === " " || matchesKey(data, Key.space)) return [DoomKeys.KEY_USE];
|
|
67
|
+
|
|
68
|
+
// Menu/UI keys
|
|
69
|
+
if (matchesKey(data, Key.enter)) return [DoomKeys.KEY_ENTER];
|
|
70
|
+
if (matchesKey(data, Key.escape)) return [DoomKeys.KEY_ESCAPE];
|
|
71
|
+
if (matchesKey(data, Key.tab)) return [DoomKeys.KEY_TAB];
|
|
72
|
+
if (matchesKey(data, Key.backspace)) return [DoomKeys.KEY_BACKSPACE];
|
|
73
|
+
|
|
74
|
+
// Ctrl keys (except Ctrl+C) = fire (legacy support)
|
|
75
|
+
const parsed = parseKey(data);
|
|
76
|
+
if (parsed?.startsWith("ctrl+") && parsed !== "ctrl+c") {
|
|
77
|
+
return [DoomKeys.KEY_FIRE];
|
|
78
|
+
}
|
|
79
|
+
if (data.length === 1 && data.charCodeAt(0) < 32 && data !== "\x03") {
|
|
80
|
+
return [DoomKeys.KEY_FIRE];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Weapon selection (0-9)
|
|
84
|
+
if (data >= "0" && data <= "9") return [data.charCodeAt(0)];
|
|
85
|
+
|
|
86
|
+
// Plus/minus for screen size
|
|
87
|
+
if (data === "+" || data === "=") return [DoomKeys.KEY_EQUALS];
|
|
88
|
+
if (data === "-") return [DoomKeys.KEY_MINUS];
|
|
89
|
+
|
|
90
|
+
// Y/N for prompts
|
|
91
|
+
if (data === "y" || data === "Y" || matchesKey(data, "y") || matchesKey(data, Key.shift("y"))) {
|
|
92
|
+
return ["y".charCodeAt(0)];
|
|
93
|
+
}
|
|
94
|
+
if (data === "n" || data === "N" || matchesKey(data, "n") || matchesKey(data, Key.shift("n"))) {
|
|
95
|
+
return ["n".charCodeAt(0)];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Other printable characters (for cheats)
|
|
99
|
+
if (data.length === 1 && data.charCodeAt(0) >= 32) {
|
|
100
|
+
return [data.toLowerCase().charCodeAt(0)];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOOM Overlay Demo - Play DOOM as an overlay
|
|
3
|
+
*
|
|
4
|
+
* Usage: indusagi --extension ./examples/extensions/doom-overlay
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* /doom-overlay - Play DOOM in an overlay (Q to pause/exit)
|
|
8
|
+
*
|
|
9
|
+
* This demonstrates that overlays can handle real-time game rendering at 35 FPS.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ExtensionAPI } from "indusagi-coding-agent";
|
|
13
|
+
import { DoomOverlayComponent } from "./doom-component.js";
|
|
14
|
+
import { DoomEngine } from "./doom-engine.js";
|
|
15
|
+
import { ensureWadFile } from "./wad-finder.js";
|
|
16
|
+
|
|
17
|
+
// Persistent engine instance - survives between invocations
|
|
18
|
+
let activeEngine: DoomEngine | null = null;
|
|
19
|
+
let activeWadPath: string | null = null;
|
|
20
|
+
|
|
21
|
+
export default function (indusagi: ExtensionAPI) {
|
|
22
|
+
indusagi.registerCommand("doom-overlay", {
|
|
23
|
+
description: "Play DOOM as an overlay. Q to pause and exit.",
|
|
24
|
+
|
|
25
|
+
handler: async (args, ctx) => {
|
|
26
|
+
if (!ctx.hasUI) {
|
|
27
|
+
ctx.ui.notify("DOOM requires interactive mode", "error");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Auto-download WAD if not present
|
|
32
|
+
ctx.ui.notify("Loading DOOM...", "info");
|
|
33
|
+
const wad = args?.trim() ? args.trim() : await ensureWadFile();
|
|
34
|
+
|
|
35
|
+
if (!wad) {
|
|
36
|
+
ctx.ui.notify("Failed to download DOOM WAD file. Check your internet connection.", "error");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
// Reuse existing engine if same WAD, otherwise create new
|
|
42
|
+
let isResume = false;
|
|
43
|
+
if (activeEngine && activeWadPath === wad) {
|
|
44
|
+
ctx.ui.notify("Resuming DOOM...", "info");
|
|
45
|
+
isResume = true;
|
|
46
|
+
} else {
|
|
47
|
+
ctx.ui.notify(`Loading DOOM from ${wad}...`, "info");
|
|
48
|
+
activeEngine = new DoomEngine(wad);
|
|
49
|
+
await activeEngine.init();
|
|
50
|
+
activeWadPath = wad;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await ctx.ui.custom(
|
|
54
|
+
(tui, _theme, _keybindings, done) => {
|
|
55
|
+
return new DoomOverlayComponent(tui, activeEngine!, () => done(undefined), isResume);
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
overlay: true,
|
|
59
|
+
overlayOptions: {
|
|
60
|
+
width: "75%",
|
|
61
|
+
maxHeight: "95%",
|
|
62
|
+
anchor: "center",
|
|
63
|
+
margin: { top: 1 },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
ctx.ui.notify(`Failed to load DOOM: ${error}`, "error");
|
|
69
|
+
activeEngine = null;
|
|
70
|
+
activeWadPath = null;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
// Get the bundled WAD path (relative to this module)
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const BUNDLED_WAD = join(__dirname, "doom1.wad");
|
|
8
|
+
const WAD_URL = "https://distro.ibiblio.org/slitaz/sources/packages/d/doom1.wad";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_WAD_PATHS = ["./doom1.wad", "./DOOM1.WAD", "~/doom1.wad", "~/.doom/doom1.wad"];
|
|
11
|
+
|
|
12
|
+
export function findWadFile(customPath?: string): string | null {
|
|
13
|
+
if (customPath) {
|
|
14
|
+
const resolved = resolve(customPath.replace(/^~/, process.env.HOME || ""));
|
|
15
|
+
if (existsSync(resolved)) return resolved;
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check bundled WAD first
|
|
20
|
+
if (existsSync(BUNDLED_WAD)) {
|
|
21
|
+
return BUNDLED_WAD;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Fall back to default paths
|
|
25
|
+
for (const p of DEFAULT_WAD_PATHS) {
|
|
26
|
+
const resolved = resolve(p.replace(/^~/, process.env.HOME || ""));
|
|
27
|
+
if (existsSync(resolved)) return resolved;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Download the shareware WAD if not present. Returns path or null on failure. */
|
|
34
|
+
export async function ensureWadFile(): Promise<string | null> {
|
|
35
|
+
// Check if already exists
|
|
36
|
+
const existing = findWadFile();
|
|
37
|
+
if (existing) return existing;
|
|
38
|
+
|
|
39
|
+
// Download to bundled location
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch(WAD_URL);
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw new Error(`HTTP ${response.status}`);
|
|
44
|
+
}
|
|
45
|
+
const buffer = await response.arrayBuffer();
|
|
46
|
+
writeFileSync(BUNDLED_WAD, Buffer.from(buffer));
|
|
47
|
+
return BUNDLED_WAD;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inter-extension event bus example.
|
|
3
|
+
*
|
|
4
|
+
* Shows indusagi.events for communication between extensions. One extension
|
|
5
|
+
* can emit events that other extensions listen to.
|
|
6
|
+
*
|
|
7
|
+
* Usage: /emit [event-name] [data] - emit an event on the bus
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ExtensionAPI, ExtensionContext } from "indusagi-coding-agent";
|
|
11
|
+
|
|
12
|
+
export default function (indusagi: ExtensionAPI) {
|
|
13
|
+
// Store ctx for use in event handler
|
|
14
|
+
let currentCtx: ExtensionContext | undefined;
|
|
15
|
+
|
|
16
|
+
indusagi.on("session_start", async (_event, ctx) => {
|
|
17
|
+
currentCtx = ctx;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Listen for events from other extensions
|
|
21
|
+
indusagi.events.on("my:notification", (data) => {
|
|
22
|
+
const { message, from } = data as { message: string; from: string };
|
|
23
|
+
currentCtx?.ui.notify(`Event from ${from}: ${message}`, "info");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Command to emit events (emits "my:notification" which the listener above receives)
|
|
27
|
+
indusagi.registerCommand("emit", {
|
|
28
|
+
description: "Emit my:notification event (usage: /emit message)",
|
|
29
|
+
handler: async (args, _ctx) => {
|
|
30
|
+
const message = args.trim() || "hello";
|
|
31
|
+
indusagi.events.emit("my:notification", { message, from: "/emit command" });
|
|
32
|
+
// Listener above will show the notification
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Example: emit on session start
|
|
37
|
+
indusagi.on("session_start", async () => {
|
|
38
|
+
indusagi.events.emit("my:notification", {
|
|
39
|
+
message: "Session started",
|
|
40
|
+
from: "event-bus-example",
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Trigger Extension
|
|
3
|
+
*
|
|
4
|
+
* Watches a trigger file and injects its contents into the conversation.
|
|
5
|
+
* Useful for external systems to send messages to the agent.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* echo "Run the tests" > /tmp/agent-trigger.txt
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import type { ExtensionAPI } from "indusagi-coding-agent";
|
|
13
|
+
|
|
14
|
+
export default function (indusagi: ExtensionAPI) {
|
|
15
|
+
indusagi.on("session_start", async (_event, ctx) => {
|
|
16
|
+
const triggerFile = "/tmp/agent-trigger.txt";
|
|
17
|
+
|
|
18
|
+
fs.watch(triggerFile, () => {
|
|
19
|
+
try {
|
|
20
|
+
const content = fs.readFileSync(triggerFile, "utf-8").trim();
|
|
21
|
+
if (content) {
|
|
22
|
+
indusagi.sendMessage(
|
|
23
|
+
{
|
|
24
|
+
customType: "file-trigger",
|
|
25
|
+
content: `External trigger: ${content}`,
|
|
26
|
+
display: true,
|
|
27
|
+
},
|
|
28
|
+
{ triggerTurn: true }, // triggerTurn - get LLM to respond
|
|
29
|
+
);
|
|
30
|
+
fs.writeFileSync(triggerFile, ""); // Clear after reading
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// File might not exist yet
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (ctx.hasUI) {
|
|
38
|
+
ctx.ui.notify(`Watching ${triggerFile}`, "info");
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Checkpoint Extension
|
|
3
|
+
*
|
|
4
|
+
* Creates git stash checkpoints at each turn so /fork can restore code state.
|
|
5
|
+
* When forking, offers to restore code to that point in history.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionAPI } from "indusagi-coding-agent";
|
|
9
|
+
|
|
10
|
+
export default function (indusagi: ExtensionAPI) {
|
|
11
|
+
const checkpoints = new Map<string, string>();
|
|
12
|
+
let currentEntryId: string | undefined;
|
|
13
|
+
|
|
14
|
+
// Track the current entry ID when user messages are saved
|
|
15
|
+
indusagi.on("tool_result", async (_event, ctx) => {
|
|
16
|
+
const leaf = ctx.sessionManager.getLeafEntry();
|
|
17
|
+
if (leaf) currentEntryId = leaf.id;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
indusagi.on("turn_start", async () => {
|
|
21
|
+
// Create a git stash entry before LLM makes changes
|
|
22
|
+
const { stdout } = await indusagi.exec("git", ["stash", "create"]);
|
|
23
|
+
const ref = stdout.trim();
|
|
24
|
+
if (ref && currentEntryId) {
|
|
25
|
+
checkpoints.set(currentEntryId, ref);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
indusagi.on("session_before_fork", async (event, ctx) => {
|
|
30
|
+
const ref = checkpoints.get(event.entryId);
|
|
31
|
+
if (!ref) return;
|
|
32
|
+
|
|
33
|
+
if (!ctx.hasUI) {
|
|
34
|
+
// In non-interactive mode, don't restore automatically
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const choice = await ctx.ui.select("Restore code state?", [
|
|
39
|
+
"Yes, restore code to that point",
|
|
40
|
+
"No, keep current code",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
if (choice?.startsWith("Yes")) {
|
|
44
|
+
await indusagi.exec("git", ["stash", "apply", ref]);
|
|
45
|
+
ctx.ui.notify("Code restored to checkpoint", "info");
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
indusagi.on("agent_end", async () => {
|
|
50
|
+
// Clear checkpoints after agent completes
|
|
51
|
+
checkpoints.clear();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handoff extension - transfer context to a new focused session
|
|
3
|
+
*
|
|
4
|
+
* Instead of compacting (which is lossy), handoff extracts what matters
|
|
5
|
+
* for your next task and creates a new session with a generated prompt.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* /handoff now implement this for teams as well
|
|
9
|
+
* /handoff execute phase one of the plan
|
|
10
|
+
* /handoff check other places that need this fix
|
|
11
|
+
*
|
|
12
|
+
* The generated prompt appears as a draft in the editor for review/editing.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { complete, type Message } from "indusagi/ai";
|
|
16
|
+
import type { ExtensionAPI, SessionEntry } from "indusagi-coding-agent";
|
|
17
|
+
import { BorderedLoader, convertToLlm, serializeConversation } from "indusagi-coding-agent";
|
|
18
|
+
|
|
19
|
+
const SYSTEM_PROMPT = `You are a context transfer assistant. Given a conversation history and the user's goal for a new thread, generate a focused prompt that:
|
|
20
|
+
|
|
21
|
+
1. Summarizes relevant context from the conversation (decisions made, approaches taken, key findings)
|
|
22
|
+
2. Lists any relevant files that were discussed or modified
|
|
23
|
+
3. Clearly states the next task based on the user's goal
|
|
24
|
+
4. Is self-contained - the new thread should be able to proceed without the old conversation
|
|
25
|
+
|
|
26
|
+
Format your response as a prompt the user can send to start the new thread. Be concise but include all necessary context. Do not include any preamble like "Here's the prompt" - just output the prompt itself.
|
|
27
|
+
|
|
28
|
+
Example output format:
|
|
29
|
+
## Context
|
|
30
|
+
We've been working on X. Key decisions:
|
|
31
|
+
- Decision 1
|
|
32
|
+
- Decision 2
|
|
33
|
+
|
|
34
|
+
Files involved:
|
|
35
|
+
- path/to/file1.ts
|
|
36
|
+
- path/to/file2.ts
|
|
37
|
+
|
|
38
|
+
## Task
|
|
39
|
+
[Clear description of what to do next based on user's goal]`;
|
|
40
|
+
|
|
41
|
+
export default function (indusagi: ExtensionAPI) {
|
|
42
|
+
indusagi.registerCommand("handoff", {
|
|
43
|
+
description: "Transfer context to a new focused session",
|
|
44
|
+
handler: async (args, ctx) => {
|
|
45
|
+
if (!ctx.hasUI) {
|
|
46
|
+
ctx.ui.notify("handoff requires interactive mode", "error");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!ctx.model) {
|
|
51
|
+
ctx.ui.notify("No model selected", "error");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const goal = args.trim();
|
|
56
|
+
if (!goal) {
|
|
57
|
+
ctx.ui.notify("Usage: /handoff <goal for new thread>", "error");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Gather conversation context from current branch
|
|
62
|
+
const branch = ctx.sessionManager.getBranch();
|
|
63
|
+
const messages = branch
|
|
64
|
+
.filter((entry): entry is SessionEntry & { type: "message" } => entry.type === "message")
|
|
65
|
+
.map((entry) => entry.message);
|
|
66
|
+
|
|
67
|
+
if (messages.length === 0) {
|
|
68
|
+
ctx.ui.notify("No conversation to hand off", "error");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Convert to LLM format and serialize
|
|
73
|
+
const llmMessages = convertToLlm(messages);
|
|
74
|
+
const conversationText = serializeConversation(llmMessages);
|
|
75
|
+
const currentSessionFile = ctx.sessionManager.getSessionFile();
|
|
76
|
+
|
|
77
|
+
// Generate the handoff prompt with loader UI
|
|
78
|
+
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
79
|
+
const loader = new BorderedLoader(tui, theme, `Generating handoff prompt...`);
|
|
80
|
+
loader.onAbort = () => done(null);
|
|
81
|
+
|
|
82
|
+
const doGenerate = async () => {
|
|
83
|
+
const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!);
|
|
84
|
+
|
|
85
|
+
const userMessage: Message = {
|
|
86
|
+
role: "user",
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: `## Conversation History\n\n${conversationText}\n\n## User's Goal for New Thread\n\n${goal}`,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const response = await complete(
|
|
97
|
+
ctx.model!,
|
|
98
|
+
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
|
99
|
+
{ apiKey, signal: loader.signal },
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (response.stopReason === "aborted") {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return response.content
|
|
107
|
+
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
108
|
+
.map((c) => c.text)
|
|
109
|
+
.join("\n");
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
doGenerate()
|
|
113
|
+
.then(done)
|
|
114
|
+
.catch((err) => {
|
|
115
|
+
console.error("Handoff generation failed:", err);
|
|
116
|
+
done(null);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return loader;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (result === null) {
|
|
123
|
+
ctx.ui.notify("Cancelled", "info");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Let user edit the generated prompt
|
|
128
|
+
const editedPrompt = await ctx.ui.editor("Edit handoff prompt", result);
|
|
129
|
+
|
|
130
|
+
if (editedPrompt === undefined) {
|
|
131
|
+
ctx.ui.notify("Cancelled", "info");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Create new session with parent tracking
|
|
136
|
+
const newSessionResult = await ctx.newSession({
|
|
137
|
+
parentSession: currentSessionFile,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (newSessionResult.cancelled) {
|
|
141
|
+
ctx.ui.notify("New session cancelled", "info");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Set the edited prompt in the main editor for submission
|
|
146
|
+
ctx.ui.setEditorText(editedPrompt);
|
|
147
|
+
ctx.ui.notify("Handoff ready. Submit when ready.", "info");
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|