zoe-agent 0.3.1
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 +154 -0
- package/LICENSE +96 -0
- package/README.md +568 -0
- package/dist/adapters/cli/agent.d.ts +59 -0
- package/dist/adapters/cli/agent.js +232 -0
- package/dist/adapters/cli/bootstrap.d.ts +25 -0
- package/dist/adapters/cli/bootstrap.js +204 -0
- package/dist/adapters/cli/commands/build-registry.d.ts +14 -0
- package/dist/adapters/cli/commands/build-registry.js +88 -0
- package/dist/adapters/cli/commands/clear.d.ts +7 -0
- package/dist/adapters/cli/commands/clear.js +10 -0
- package/dist/adapters/cli/commands/compact.d.ts +13 -0
- package/dist/adapters/cli/commands/compact.js +96 -0
- package/dist/adapters/cli/commands/exit.d.ts +7 -0
- package/dist/adapters/cli/commands/exit.js +9 -0
- package/dist/adapters/cli/commands/gateway.d.ts +7 -0
- package/dist/adapters/cli/commands/gateway.js +152 -0
- package/dist/adapters/cli/commands/help.d.ts +9 -0
- package/dist/adapters/cli/commands/help.js +12 -0
- package/dist/adapters/cli/commands/models.d.ts +10 -0
- package/dist/adapters/cli/commands/models.js +32 -0
- package/dist/adapters/cli/commands/registry.d.ts +70 -0
- package/dist/adapters/cli/commands/registry.js +111 -0
- package/dist/adapters/cli/commands/settings-utils.d.ts +38 -0
- package/dist/adapters/cli/commands/settings-utils.js +182 -0
- package/dist/adapters/cli/commands/settings.d.ts +9 -0
- package/dist/adapters/cli/commands/settings.js +395 -0
- package/dist/adapters/cli/commands/skills.d.ts +7 -0
- package/dist/adapters/cli/commands/skills.js +21 -0
- package/dist/adapters/cli/config-loader.d.ts +27 -0
- package/dist/adapters/cli/config-loader.js +48 -0
- package/dist/adapters/cli/docker-utils.d.ts +37 -0
- package/dist/adapters/cli/docker-utils.js +90 -0
- package/dist/adapters/cli/index.d.ts +2 -0
- package/dist/adapters/cli/index.js +88 -0
- package/dist/adapters/cli/repl.d.ts +22 -0
- package/dist/adapters/cli/repl.js +256 -0
- package/dist/adapters/cli/setup.d.ts +19 -0
- package/dist/adapters/cli/setup.js +613 -0
- package/dist/adapters/cli/system-prompts.d.ts +56 -0
- package/dist/adapters/cli/system-prompts.js +131 -0
- package/dist/adapters/cli/tui/app.d.ts +58 -0
- package/dist/adapters/cli/tui/app.js +314 -0
- package/dist/adapters/cli/tui/components/assistant-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/assistant-message.js +9 -0
- package/dist/adapters/cli/tui/components/autocomplete.d.ts +19 -0
- package/dist/adapters/cli/tui/components/autocomplete.js +75 -0
- package/dist/adapters/cli/tui/components/command-palette.d.ts +15 -0
- package/dist/adapters/cli/tui/components/command-palette.js +50 -0
- package/dist/adapters/cli/tui/components/diff-viewer.d.ts +5 -0
- package/dist/adapters/cli/tui/components/diff-viewer.js +109 -0
- package/dist/adapters/cli/tui/components/error-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/error-message.js +8 -0
- package/dist/adapters/cli/tui/components/footer.d.ts +20 -0
- package/dist/adapters/cli/tui/components/footer.js +19 -0
- package/dist/adapters/cli/tui/components/goal-status.d.ts +12 -0
- package/dist/adapters/cli/tui/components/goal-status.js +22 -0
- package/dist/adapters/cli/tui/components/info-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/info-message.js +8 -0
- package/dist/adapters/cli/tui/components/logo-banner.d.ts +7 -0
- package/dist/adapters/cli/tui/components/logo-banner.js +33 -0
- package/dist/adapters/cli/tui/components/markdown.d.ts +9 -0
- package/dist/adapters/cli/tui/components/markdown.js +92 -0
- package/dist/adapters/cli/tui/components/message-area.d.ts +19 -0
- package/dist/adapters/cli/tui/components/message-area.js +55 -0
- package/dist/adapters/cli/tui/components/permission-prompt.d.ts +13 -0
- package/dist/adapters/cli/tui/components/permission-prompt.js +32 -0
- package/dist/adapters/cli/tui/components/prompt-area.d.ts +22 -0
- package/dist/adapters/cli/tui/components/prompt-area.js +68 -0
- package/dist/adapters/cli/tui/components/text-input.d.ts +27 -0
- package/dist/adapters/cli/tui/components/text-input.js +142 -0
- package/dist/adapters/cli/tui/components/tool-call-block.d.ts +11 -0
- package/dist/adapters/cli/tui/components/tool-call-block.js +68 -0
- package/dist/adapters/cli/tui/components/user-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/user-message.js +8 -0
- package/dist/adapters/cli/tui/diff/file-write-meta.d.ts +11 -0
- package/dist/adapters/cli/tui/diff/file-write-meta.js +11 -0
- package/dist/adapters/cli/tui/diff/line-diff.d.ts +17 -0
- package/dist/adapters/cli/tui/diff/line-diff.js +44 -0
- package/dist/adapters/cli/tui/feed-serializer.d.ts +29 -0
- package/dist/adapters/cli/tui/feed-serializer.js +70 -0
- package/dist/adapters/cli/tui/file-index.d.ts +8 -0
- package/dist/adapters/cli/tui/file-index.js +41 -0
- package/dist/adapters/cli/tui/hooks/use-agent.d.ts +54 -0
- package/dist/adapters/cli/tui/hooks/use-agent.js +177 -0
- package/dist/adapters/cli/tui/hooks/use-feed.d.ts +16 -0
- package/dist/adapters/cli/tui/hooks/use-feed.js +25 -0
- package/dist/adapters/cli/tui/hooks/use-file-watcher.d.ts +10 -0
- package/dist/adapters/cli/tui/hooks/use-file-watcher.js +43 -0
- package/dist/adapters/cli/tui/hooks/use-keybindings.d.ts +16 -0
- package/dist/adapters/cli/tui/hooks/use-keybindings.js +25 -0
- package/dist/adapters/cli/tui/hooks/use-theme.d.ts +8 -0
- package/dist/adapters/cli/tui/hooks/use-theme.js +12 -0
- package/dist/adapters/cli/tui/index.d.ts +19 -0
- package/dist/adapters/cli/tui/index.js +206 -0
- package/dist/adapters/cli/tui/ink-reset.d.ts +29 -0
- package/dist/adapters/cli/tui/ink-reset.js +57 -0
- package/dist/adapters/cli/tui/layout.d.ts +15 -0
- package/dist/adapters/cli/tui/layout.js +15 -0
- package/dist/adapters/cli/tui/logo/gradient.d.ts +11 -0
- package/dist/adapters/cli/tui/logo/gradient.js +31 -0
- package/dist/adapters/cli/tui/overlays/help-dialog.d.ts +4 -0
- package/dist/adapters/cli/tui/overlays/help-dialog.js +26 -0
- package/dist/adapters/cli/tui/overlays/model-selector.d.ts +14 -0
- package/dist/adapters/cli/tui/overlays/model-selector.js +43 -0
- package/dist/adapters/cli/tui/overlays/session-selector.d.ts +35 -0
- package/dist/adapters/cli/tui/overlays/session-selector.js +162 -0
- package/dist/adapters/cli/tui/overlays/settings-overlay.d.ts +24 -0
- package/dist/adapters/cli/tui/overlays/settings-overlay.js +126 -0
- package/dist/adapters/cli/tui/session-export.d.ts +21 -0
- package/dist/adapters/cli/tui/session-export.js +63 -0
- package/dist/adapters/cli/tui/theme.d.ts +23 -0
- package/dist/adapters/cli/tui/theme.js +22 -0
- package/dist/adapters/cli/tui/types.d.ts +52 -0
- package/dist/adapters/cli/tui/types.js +12 -0
- package/dist/adapters/sdk/agent.d.ts +20 -0
- package/dist/adapters/sdk/agent.js +356 -0
- package/dist/adapters/sdk/http.d.ts +43 -0
- package/dist/adapters/sdk/http.js +61 -0
- package/dist/adapters/sdk/index.d.ts +58 -0
- package/dist/adapters/sdk/index.js +209 -0
- package/dist/adapters/sdk/settings.d.ts +18 -0
- package/dist/adapters/sdk/settings.js +57 -0
- package/dist/adapters/sdk/tools.d.ts +7 -0
- package/dist/adapters/sdk/tools.js +13 -0
- package/dist/adapters/server/auth.d.ts +53 -0
- package/dist/adapters/server/auth.js +168 -0
- package/dist/adapters/server/index.d.ts +40 -0
- package/dist/adapters/server/index.js +255 -0
- package/dist/adapters/server/rest-gateway.d.ts +13 -0
- package/dist/adapters/server/rest-gateway.js +218 -0
- package/dist/adapters/server/rest.d.ts +37 -0
- package/dist/adapters/server/rest.js +341 -0
- package/dist/adapters/server/server-core.d.ts +55 -0
- package/dist/adapters/server/server-core.js +121 -0
- package/dist/adapters/server/session-store.d.ts +81 -0
- package/dist/adapters/server/session-store.js +272 -0
- package/dist/adapters/server/settings-handlers.d.ts +24 -0
- package/dist/adapters/server/settings-handlers.js +360 -0
- package/dist/adapters/server/standalone.d.ts +19 -0
- package/dist/adapters/server/standalone.js +113 -0
- package/dist/adapters/server/websocket.d.ts +26 -0
- package/dist/adapters/server/websocket.js +68 -0
- package/dist/adapters/server/ws-handlers.d.ts +32 -0
- package/dist/adapters/server/ws-handlers.js +523 -0
- package/dist/adapters/server/ws-types.d.ts +304 -0
- package/dist/adapters/server/ws-types.js +7 -0
- package/dist/core/agent-loop.d.ts +68 -0
- package/dist/core/agent-loop.js +423 -0
- package/dist/core/config.d.ts +115 -0
- package/dist/core/config.js +189 -0
- package/dist/core/errors.d.ts +58 -0
- package/dist/core/errors.js +88 -0
- package/dist/core/hooks.d.ts +35 -0
- package/dist/core/hooks.js +49 -0
- package/dist/core/index.d.ts +23 -0
- package/dist/core/index.js +29 -0
- package/dist/core/message-convert.d.ts +41 -0
- package/dist/core/message-convert.js +94 -0
- package/dist/core/middleware/auth.d.ts +24 -0
- package/dist/core/middleware/auth.js +28 -0
- package/dist/core/middleware/logging.d.ts +23 -0
- package/dist/core/middleware/logging.js +28 -0
- package/dist/core/middleware/rate-limit.d.ts +27 -0
- package/dist/core/middleware/rate-limit.js +38 -0
- package/dist/core/middleware/semantic-tools.d.ts +10 -0
- package/dist/core/middleware/semantic-tools.js +43 -0
- package/dist/core/middleware.d.ts +48 -0
- package/dist/core/middleware.js +38 -0
- package/dist/core/permission.d.ts +25 -0
- package/dist/core/permission.js +50 -0
- package/dist/core/provider-config.d.ts +129 -0
- package/dist/core/provider-config.js +273 -0
- package/dist/core/provider-env.d.ts +39 -0
- package/dist/core/provider-env.js +142 -0
- package/dist/core/provider-resolver.d.ts +12 -0
- package/dist/core/provider-resolver.js +12 -0
- package/dist/core/session-store.d.ts +75 -0
- package/dist/core/session-store.js +245 -0
- package/dist/core/settings-manager.d.ts +57 -0
- package/dist/core/settings-manager.js +359 -0
- package/dist/core/settings-schema.d.ts +38 -0
- package/dist/core/settings-schema.js +171 -0
- package/dist/core/skill-catalog.d.ts +6 -0
- package/dist/core/skill-catalog.js +17 -0
- package/dist/core/skill-invoker.d.ts +127 -0
- package/dist/core/skill-invoker.js +182 -0
- package/dist/core/stream-accumulator.d.ts +21 -0
- package/dist/core/stream-accumulator.js +51 -0
- package/dist/core/stream-manager.d.ts +58 -0
- package/dist/core/stream-manager.js +212 -0
- package/dist/core/tool-executor.d.ts +84 -0
- package/dist/core/tool-executor.js +256 -0
- package/dist/core/types.d.ts +259 -0
- package/dist/core/types.js +11 -0
- package/dist/gateway/gateway.d.ts +52 -0
- package/dist/gateway/gateway.js +537 -0
- package/dist/gateway/index.d.ts +21 -0
- package/dist/gateway/index.js +31 -0
- package/dist/gateway/openapi-importer.d.ts +15 -0
- package/dist/gateway/openapi-importer.js +66 -0
- package/dist/gateway/semantic-scorer.d.ts +7 -0
- package/dist/gateway/semantic-scorer.js +24 -0
- package/dist/gateway/settings-adapter.d.ts +49 -0
- package/dist/gateway/settings-adapter.js +137 -0
- package/dist/gateway/tool-factory.d.ts +9 -0
- package/dist/gateway/tool-factory.js +414 -0
- package/dist/gateway/types.d.ts +68 -0
- package/dist/gateway/types.js +7 -0
- package/dist/models-catalog.js +46 -0
- package/dist/providers/anthropic.d.ts +22 -0
- package/dist/providers/anthropic.js +148 -0
- package/dist/providers/factory.d.ts +10 -0
- package/dist/providers/factory.js +25 -0
- package/dist/providers/openai.d.ts +15 -0
- package/dist/providers/openai.js +71 -0
- package/dist/providers/types.d.ts +48 -0
- package/dist/providers/types.js +1 -0
- package/dist/skills/args.d.ts +37 -0
- package/dist/skills/args.js +99 -0
- package/dist/skills/index.d.ts +11 -0
- package/dist/skills/index.js +23 -0
- package/dist/skills/loader.d.ts +3 -0
- package/dist/skills/loader.js +59 -0
- package/dist/skills/parser.d.ts +7 -0
- package/dist/skills/parser.js +152 -0
- package/dist/skills/registry.d.ts +13 -0
- package/dist/skills/registry.js +74 -0
- package/dist/skills/resolver.d.ts +19 -0
- package/dist/skills/resolver.js +116 -0
- package/dist/skills/types.d.ts +74 -0
- package/dist/skills/types.js +50 -0
- package/dist/tools/browser.d.ts +2 -0
- package/dist/tools/browser.js +68 -0
- package/dist/tools/core.d.ts +20 -0
- package/dist/tools/core.js +244 -0
- package/dist/tools/email.d.ts +2 -0
- package/dist/tools/email.js +61 -0
- package/dist/tools/image.d.ts +2 -0
- package/dist/tools/image.js +257 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +88 -0
- package/dist/tools/interface.d.ts +22 -0
- package/dist/tools/interface.js +1 -0
- package/dist/tools/notify.d.ts +2 -0
- package/dist/tools/notify.js +100 -0
- package/dist/tools/prompt-optimizer.d.ts +2 -0
- package/dist/tools/prompt-optimizer.js +65 -0
- package/dist/tools/screenshot.d.ts +2 -0
- package/dist/tools/screenshot.js +184 -0
- package/dist/tools/search.d.ts +2 -0
- package/dist/tools/search.js +78 -0
- package/dist/tools/todos.d.ts +10 -0
- package/dist/tools/todos.js +50 -0
- package/package.json +119 -0
- package/skills/docker-ops/SKILL.md +329 -0
- package/skills/k8s-deploy/SKILL.md +397 -0
- package/skills/log-analyzer/SKILL.md +331 -0
- package/skills/speckit-analyze/SKILL.md +260 -0
- package/skills/speckit-checklist/SKILL.md +374 -0
- package/skills/speckit-clarify/SKILL.md +286 -0
- package/skills/speckit-constitution/SKILL.md +157 -0
- package/skills/speckit-implement/SKILL.md +224 -0
- package/skills/speckit-plan/SKILL.md +171 -0
- package/skills/speckit-specify/SKILL.md +346 -0
- package/skills/speckit-tasks/SKILL.md +215 -0
- package/skills/speckit-taskstoissues/SKILL.md +107 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface TextInputProps {
|
|
2
|
+
value: string;
|
|
3
|
+
onChange: (value: string) => void;
|
|
4
|
+
onSubmit: (value: string) => void;
|
|
5
|
+
placeholder?: string;
|
|
6
|
+
/** When true, ↑/↓ are left to the parent (e.g. autocomplete dropdown nav). */
|
|
7
|
+
ignoreArrows?: boolean;
|
|
8
|
+
/** When true, Enter does not submit — the parent owns it (autocomplete accept). */
|
|
9
|
+
ignoreReturn?: boolean;
|
|
10
|
+
/** Called when ↑ is pressed on the top line (recall previous history). */
|
|
11
|
+
onHistoryUp?: () => void;
|
|
12
|
+
/** Called when ↓ is pressed on the bottom line (recall next history). */
|
|
13
|
+
onHistoryDown?: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Multi-line controlled text input with cursor control + history.
|
|
17
|
+
*
|
|
18
|
+
* Newline: Shift+Enter / Alt+Enter / Ctrl+J (terminal-dependent — all three are
|
|
19
|
+
* bound for max compatibility). Plain Enter submits. ↑/↓ move between lines;
|
|
20
|
+
* at the top line ↑ recalls previous history, at the bottom line ↓ recalls next
|
|
21
|
+
* (via onHistoryUp/Down). Tab is left to the parent.
|
|
22
|
+
*
|
|
23
|
+
* External value changes (history recall, post-submit clear) move the cursor to
|
|
24
|
+
* the end; user keystrokes move it to the insertion point.
|
|
25
|
+
*/
|
|
26
|
+
export declare function TextInput({ value, onChange, onSubmit, placeholder, ignoreArrows, ignoreReturn, onHistoryUp, onHistoryDown, }: TextInputProps): import("react").JSX.Element;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { useTheme } from '../hooks/use-theme.js';
|
|
5
|
+
// ── line/column math (value may contain '\n') ───────────────────────────
|
|
6
|
+
function lineColOf(value, cursor) {
|
|
7
|
+
let line = 0;
|
|
8
|
+
let col = 0;
|
|
9
|
+
for (let i = 0; i < cursor && i < value.length; i++) {
|
|
10
|
+
if (value[i] === '\n') {
|
|
11
|
+
line++;
|
|
12
|
+
col = 0;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
col++;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return { line, col };
|
|
19
|
+
}
|
|
20
|
+
function lineStarts(value) {
|
|
21
|
+
const starts = [0];
|
|
22
|
+
for (let i = 0; i < value.length; i++) {
|
|
23
|
+
if (value[i] === '\n')
|
|
24
|
+
starts.push(i + 1);
|
|
25
|
+
}
|
|
26
|
+
return starts;
|
|
27
|
+
}
|
|
28
|
+
/** Move the cursor up/down one line, keeping the column; returns the same cursor at an edge. */
|
|
29
|
+
function moveVertical(value, cursor, dir) {
|
|
30
|
+
const starts = lineStarts(value);
|
|
31
|
+
const { line, col } = lineColOf(value, cursor);
|
|
32
|
+
const target = line + dir;
|
|
33
|
+
if (target < 0 || target >= starts.length)
|
|
34
|
+
return cursor;
|
|
35
|
+
const targetStart = starts[target];
|
|
36
|
+
const targetEnd = target < starts.length - 1 ? starts[target + 1] - 1 : value.length; // exclude '\n'
|
|
37
|
+
const targetCol = Math.min(col, targetEnd - targetStart);
|
|
38
|
+
return targetStart + targetCol;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Multi-line controlled text input with cursor control + history.
|
|
42
|
+
*
|
|
43
|
+
* Newline: Shift+Enter / Alt+Enter / Ctrl+J (terminal-dependent — all three are
|
|
44
|
+
* bound for max compatibility). Plain Enter submits. ↑/↓ move between lines;
|
|
45
|
+
* at the top line ↑ recalls previous history, at the bottom line ↓ recalls next
|
|
46
|
+
* (via onHistoryUp/Down). Tab is left to the parent.
|
|
47
|
+
*
|
|
48
|
+
* External value changes (history recall, post-submit clear) move the cursor to
|
|
49
|
+
* the end; user keystrokes move it to the insertion point.
|
|
50
|
+
*/
|
|
51
|
+
export function TextInput({ value, onChange, onSubmit, placeholder, ignoreArrows, ignoreReturn, onHistoryUp, onHistoryDown, }) {
|
|
52
|
+
const theme = useTheme();
|
|
53
|
+
const [cursor, setCursor] = useState(value.length);
|
|
54
|
+
const selfUpdate = useRef(false);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (selfUpdate.current) {
|
|
57
|
+
selfUpdate.current = false;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
setCursor(value.length); // external change → cursor to end
|
|
61
|
+
}, [value]);
|
|
62
|
+
const at = Math.min(cursor, value.length);
|
|
63
|
+
const insert = (text) => {
|
|
64
|
+
selfUpdate.current = true;
|
|
65
|
+
onChange(value.slice(0, at) + text + value.slice(at));
|
|
66
|
+
setCursor(at + text.length);
|
|
67
|
+
};
|
|
68
|
+
useInput((inputChar, key) => {
|
|
69
|
+
// Newline before submit. Ink doesn't parse the modified-return CSI a
|
|
70
|
+
// terminal sends for Shift+Enter/Alt+Enter (`\x1B[27;<modifier>;13~`, where
|
|
71
|
+
// 13=return); detect that raw sequence too, plus the key-flag paths + Ctrl+J.
|
|
72
|
+
const isModifiedReturn = key.return && (key.shift || key.meta || key.ctrl);
|
|
73
|
+
const isCtrlJ = !key.return && (inputChar === '\n' || inputChar === '\x0a' || (key.ctrl && inputChar === 'j'));
|
|
74
|
+
const isModifiedReturnCSI = /\x1b?\[27;\d*;?13~/.test(inputChar);
|
|
75
|
+
if (isModifiedReturn || isCtrlJ || isModifiedReturnCSI) {
|
|
76
|
+
insert('\n');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (key.return) {
|
|
80
|
+
if (!ignoreReturn)
|
|
81
|
+
onSubmit(value);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (key.backspace || key.delete) {
|
|
85
|
+
if (at === 0)
|
|
86
|
+
return;
|
|
87
|
+
selfUpdate.current = true;
|
|
88
|
+
onChange(value.slice(0, at - 1) + value.slice(at));
|
|
89
|
+
setCursor(at - 1);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!ignoreArrows) {
|
|
93
|
+
if (key.upArrow) {
|
|
94
|
+
const { line } = lineColOf(value, at);
|
|
95
|
+
if (line === 0) {
|
|
96
|
+
onHistoryUp?.();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
setCursor(moveVertical(value, at, -1));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (key.downArrow) {
|
|
103
|
+
const last = lineStarts(value).length - 1;
|
|
104
|
+
const { line } = lineColOf(value, at);
|
|
105
|
+
if (line === last) {
|
|
106
|
+
onHistoryDown?.();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
setCursor(moveVertical(value, at, 1));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (key.leftArrow) {
|
|
114
|
+
setCursor(Math.max(0, at - 1));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (key.rightArrow) {
|
|
118
|
+
setCursor(Math.min(value.length, at + 1));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Insert printable text (multi-char = paste), but never raw CSI escape
|
|
122
|
+
// sequences (e.g. leftover `\x1B[27;2;13~` from an unparsed modified key).
|
|
123
|
+
const isCsi = /\x1b?\[\d[\d;]*[~A-Za-z]/.test(inputChar);
|
|
124
|
+
if (inputChar && !key.ctrl && !key.meta && inputChar.length >= 1 && inputChar >= ' ' && !isCsi) {
|
|
125
|
+
insert(inputChar);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// Render — one <Text> per line; the cursor line shows the block cursor.
|
|
129
|
+
if (value.length === 0) {
|
|
130
|
+
return _jsx(Text, { color: theme.fgDim, children: placeholder ?? '' });
|
|
131
|
+
}
|
|
132
|
+
const { line: curLine, col: curCol } = lineColOf(value, at);
|
|
133
|
+
const lines = value.split('\n');
|
|
134
|
+
return (_jsx(Box, { flexDirection: "column", children: lines.map((line, i) => {
|
|
135
|
+
if (i !== curLine)
|
|
136
|
+
return _jsx(Text, { children: line || ' ' }, i);
|
|
137
|
+
const before = line.slice(0, curCol);
|
|
138
|
+
const c = line.slice(curCol, curCol + 1);
|
|
139
|
+
const after = line.slice(curCol + 1);
|
|
140
|
+
return (_jsxs(Text, { children: [before, _jsx(Text, { backgroundColor: theme.fg, color: theme.bg, children: c || ' ' }), after] }, i));
|
|
141
|
+
}) }));
|
|
142
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToolCallEntry } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Bordered tool-execution block: shared header, then the output buffer (or an
|
|
4
|
+
* inline diff for `write_file`). Collapsed by default (truncated output + hint);
|
|
5
|
+
* `expanded` shows the full output. Ctrl+O (handled in app.tsx) toggles
|
|
6
|
+
* expand-all and bumps the `<Static>` key so this re-renders.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ToolCallBlock({ entry, expanded }: {
|
|
9
|
+
entry: ToolCallEntry;
|
|
10
|
+
expanded: boolean;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { useTheme } from '../hooks/use-theme.js';
|
|
4
|
+
import { Markdown } from './markdown.js';
|
|
5
|
+
import { GoalStatus } from './goal-status.js';
|
|
6
|
+
import { DiffViewer } from './diff-viewer.js';
|
|
7
|
+
import { isFileWriteMetadata } from '../diff/file-write-meta.js';
|
|
8
|
+
const STATUS_GLYPH = {
|
|
9
|
+
running: '~',
|
|
10
|
+
ok: '✓',
|
|
11
|
+
fail: '✗',
|
|
12
|
+
};
|
|
13
|
+
/** Tools whose output is markdown-formatted (render via Markdown component). */
|
|
14
|
+
const MARKDOWN_TOOLS = new Set(['web_search', 'read_website', 'optimize_prompt']);
|
|
15
|
+
/** One-line preview of a tool's args — `command` shown verbatim, else JSON. */
|
|
16
|
+
function formatArgs(args) {
|
|
17
|
+
if (typeof args.command === 'string' && args.command.length > 0)
|
|
18
|
+
return args.command;
|
|
19
|
+
const json = JSON.stringify(args);
|
|
20
|
+
return json === '{}' ? '' : json;
|
|
21
|
+
}
|
|
22
|
+
function truncate(text, max) {
|
|
23
|
+
if (text.length <= max)
|
|
24
|
+
return text;
|
|
25
|
+
return `${text.slice(0, max)} … (${text.length - max} more chars)`;
|
|
26
|
+
}
|
|
27
|
+
/** Status glyph + tool name + args preview + duration — shared by every block. */
|
|
28
|
+
function BlockHeader({ entry, theme }) {
|
|
29
|
+
const glyph = STATUS_GLYPH[entry.status];
|
|
30
|
+
const glyphColor = entry.status === 'fail' ? theme.red : entry.status === 'running' ? theme.yellow : theme.green;
|
|
31
|
+
const argsPreview = formatArgs(entry.args);
|
|
32
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: glyphColor, bold: true, children: [glyph, " "] }), _jsx(Text, { color: theme.purple, bold: true, children: entry.name }), argsPreview ? _jsxs(Text, { color: theme.fgDim, children: [" ", truncate(argsPreview, 120)] }) : null, entry.durationMs != null ? _jsxs(Text, { color: theme.fgDim, children: [" (", entry.durationMs, "ms)"] }) : null] }));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Bordered tool-execution block: shared header, then the output buffer (or an
|
|
36
|
+
* inline diff for `write_file`). Collapsed by default (truncated output + hint);
|
|
37
|
+
* `expanded` shows the full output. Ctrl+O (handled in app.tsx) toggles
|
|
38
|
+
* expand-all and bumps the `<Static>` key so this re-renders.
|
|
39
|
+
*/
|
|
40
|
+
export function ToolCallBlock({ entry, expanded }) {
|
|
41
|
+
const theme = useTheme();
|
|
42
|
+
// manage_todos renders as a GoalStatus (task list with glyphs), not a
|
|
43
|
+
// generic tool block.
|
|
44
|
+
if (entry.name === 'manage_todos' && entry.output) {
|
|
45
|
+
try {
|
|
46
|
+
const todos = JSON.parse(entry.output);
|
|
47
|
+
if (Array.isArray(todos) && todos.length > 0)
|
|
48
|
+
return _jsx(GoalStatus, { todos: todos });
|
|
49
|
+
}
|
|
50
|
+
catch { /* fall through to generic block */ }
|
|
51
|
+
}
|
|
52
|
+
// write_file with captured diff metadata → inline unified diff (collapsed by
|
|
53
|
+
// default; expanded via the global Ctrl+O toggle). Falls through to the
|
|
54
|
+
// generic block when there's no metadata (e.g. on session resume) or when the
|
|
55
|
+
// write was oversized (diffSkipped) — in which case the plain output shows.
|
|
56
|
+
if (entry.name === 'write_file') {
|
|
57
|
+
const meta = isFileWriteMetadata(entry.metadata) ? entry.metadata : null;
|
|
58
|
+
if (meta && !meta.diffSkipped && meta.newContent !== undefined) {
|
|
59
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.fgGutter, paddingLeft: 1, paddingRight: 1, children: [_jsx(BlockHeader, { entry: entry, theme: theme }), _jsx(DiffViewer, { oldContent: meta.oldContent ?? null, newContent: meta.newContent, expanded: expanded })] }));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const output = entry.output ?? '';
|
|
63
|
+
const isMarkdown = MARKDOWN_TOOLS.has(entry.name);
|
|
64
|
+
const limit = expanded ? 50000 : isMarkdown ? 1000 : 400;
|
|
65
|
+
const shown = truncate(output, limit);
|
|
66
|
+
const hasMore = output.length > limit;
|
|
67
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.fgGutter, paddingLeft: 1, paddingRight: 1, children: [_jsx(BlockHeader, { entry: entry, theme: theme }), output ? (MARKDOWN_TOOLS.has(entry.name) ? (_jsx(Markdown, { content: shown })) : (_jsx(Text, { color: theme.fgDim, children: shown }))) : null, hasMore ? (_jsxs(Text, { color: theme.fgDim, children: [" \u2026 ", output.length - limit, " more chars (Ctrl+O to expand)"] })) : output && expanded ? (_jsx(Text, { color: theme.fgDim, children: " (Ctrl+O to collapse)" })) : null] }));
|
|
68
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { useTheme } from '../hooks/use-theme.js';
|
|
4
|
+
/** A user input entry — green speaker token + content. */
|
|
5
|
+
export function UserMessage({ entry }) {
|
|
6
|
+
const theme = useTheme();
|
|
7
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: theme.green, bold: true, children: "You \u203A " }), _jsx(Text, { color: theme.fg, children: entry.content })] }));
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boundary parse guard for `write_file`'s tool-result metadata.
|
|
3
|
+
*
|
|
4
|
+
* `StepResult.metadata` is an opaque `Record<string, unknown>` at the core
|
|
5
|
+
* level; the TUI must parse it before use (constitution: parse, don't cast).
|
|
6
|
+
* The `FileWriteMetadata` type is owned by the producer (`src/tools/core.ts`)
|
|
7
|
+
* and imported here type-only — Adapter→Infrastructure is the allowed direction
|
|
8
|
+
* and the import is erased at runtime.
|
|
9
|
+
*/
|
|
10
|
+
import type { FileWriteMetadata } from '../../../../tools/core.js';
|
|
11
|
+
export declare function isFileWriteMetadata(u: unknown): u is FileWriteMetadata;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function isFileWriteMetadata(u) {
|
|
2
|
+
if (typeof u !== 'object' || u === null)
|
|
3
|
+
return false;
|
|
4
|
+
const m = u;
|
|
5
|
+
return (typeof m.path === 'string' &&
|
|
6
|
+
typeof m.isNewFile === 'boolean' &&
|
|
7
|
+
typeof m.byteDelta === 'number' &&
|
|
8
|
+
// oldContent, when present, is a string or null; newContent is a string.
|
|
9
|
+
(m.oldContent === undefined || m.oldContent === null || typeof m.oldContent === 'string') &&
|
|
10
|
+
(m.newContent === undefined || typeof m.newContent === 'string'));
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type DiffViewLine = {
|
|
2
|
+
kind: 'added';
|
|
3
|
+
newLineNo: number;
|
|
4
|
+
text: string;
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'removed';
|
|
7
|
+
oldLineNo: number;
|
|
8
|
+
text: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'context';
|
|
11
|
+
oldLineNo: number;
|
|
12
|
+
newLineNo: number;
|
|
13
|
+
text: string;
|
|
14
|
+
};
|
|
15
|
+
/** Compute a line-level diff view-model. `oldContent === null` ⇒ new file
|
|
16
|
+
* (every line added). */
|
|
17
|
+
export declare function computeDiffLines(oldContent: string | null, newContent: string): DiffViewLine[];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure line-diff view-model for the inline diff viewer.
|
|
3
|
+
*
|
|
4
|
+
* Uses the `diff` package's `diffLines` (the same engine the Pi coding agent
|
|
5
|
+
* uses — see specs/006-inline-diff-viewer/research.md R1). CRLF/CR are
|
|
6
|
+
* normalized to LF before comparison so a CRLF file doesn't diff as
|
|
7
|
+
* fully-changed; the on-disk write is unaffected (research.md R5).
|
|
8
|
+
*/
|
|
9
|
+
import { diffLines } from 'diff';
|
|
10
|
+
const toLF = (s) => s.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
11
|
+
/** Split a diff part's value into lines, dropping the trailing empty segment
|
|
12
|
+
* produced by a final newline (diff parts always end with \n). */
|
|
13
|
+
function splitLines(value) {
|
|
14
|
+
const segs = value.split('\n');
|
|
15
|
+
if (segs.length > 0 && segs[segs.length - 1] === '')
|
|
16
|
+
segs.pop();
|
|
17
|
+
return segs;
|
|
18
|
+
}
|
|
19
|
+
/** Compute a line-level diff view-model. `oldContent === null` ⇒ new file
|
|
20
|
+
* (every line added). */
|
|
21
|
+
export function computeDiffLines(oldContent, newContent) {
|
|
22
|
+
const parts = diffLines(oldContent === null ? '' : toLF(oldContent), toLF(newContent));
|
|
23
|
+
const out = [];
|
|
24
|
+
let oldNo = 1;
|
|
25
|
+
let newNo = 1;
|
|
26
|
+
for (const part of parts) {
|
|
27
|
+
for (const text of splitLines(part.value)) {
|
|
28
|
+
if (part.added) {
|
|
29
|
+
out.push({ kind: 'added', newLineNo: newNo, text });
|
|
30
|
+
newNo++;
|
|
31
|
+
}
|
|
32
|
+
else if (part.removed) {
|
|
33
|
+
out.push({ kind: 'removed', oldLineNo: oldNo, text });
|
|
34
|
+
oldNo++;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
out.push({ kind: 'context', oldLineNo: oldNo, newLineNo: newNo, text });
|
|
38
|
+
oldNo++;
|
|
39
|
+
newNo++;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feed-serializer — rebuild the TUI feed from persisted messages.
|
|
3
|
+
*
|
|
4
|
+
* When a session is resumed, its `Message[]` history must be projected back
|
|
5
|
+
* into the `FeedEntry` union the feed renders. During a live run, entries are
|
|
6
|
+
* built imperatively from `StepResult` callbacks (see use-agent.ts); on replay
|
|
7
|
+
* there is no loop, so this pure function does the equivalent mapping in one
|
|
8
|
+
* pass.
|
|
9
|
+
*
|
|
10
|
+
* `manage_todos` is handled exactly like the live path: it updates the
|
|
11
|
+
* persistent todo panel, NOT the feed. The most-recent `manage_todos` result is
|
|
12
|
+
* returned as `latestTodos` so the caller (session resume) can restore the
|
|
13
|
+
* pinned panel — otherwise the todos would only appear as a scrolling box.
|
|
14
|
+
*
|
|
15
|
+
* Tool results are NOT stored on the assistant message's toolCalls[].result —
|
|
16
|
+
* runAgentLoop emits them as separate role:"tool" messages linked by
|
|
17
|
+
* toolCallId (agent-loop.ts:459). This serializer joins the two so replayed
|
|
18
|
+
* tool blocks show their output. Tool durationMs is not persisted, so replayed
|
|
19
|
+
* blocks render without duration (cosmetic only).
|
|
20
|
+
*/
|
|
21
|
+
import type { Message } from '../../../core/types.js';
|
|
22
|
+
import type { FeedEntryInput } from './types.js';
|
|
23
|
+
import type { Todo } from './components/goal-status.js';
|
|
24
|
+
export interface RebuiltFeed {
|
|
25
|
+
entries: FeedEntryInput[];
|
|
26
|
+
/** The most-recent manage_todos result (the current todo list), or null. */
|
|
27
|
+
latestTodos: Todo[] | null;
|
|
28
|
+
}
|
|
29
|
+
export declare function messagesToFeedEntries(messages: Message[]): RebuiltFeed;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feed-serializer — rebuild the TUI feed from persisted messages.
|
|
3
|
+
*
|
|
4
|
+
* When a session is resumed, its `Message[]` history must be projected back
|
|
5
|
+
* into the `FeedEntry` union the feed renders. During a live run, entries are
|
|
6
|
+
* built imperatively from `StepResult` callbacks (see use-agent.ts); on replay
|
|
7
|
+
* there is no loop, so this pure function does the equivalent mapping in one
|
|
8
|
+
* pass.
|
|
9
|
+
*
|
|
10
|
+
* `manage_todos` is handled exactly like the live path: it updates the
|
|
11
|
+
* persistent todo panel, NOT the feed. The most-recent `manage_todos` result is
|
|
12
|
+
* returned as `latestTodos` so the caller (session resume) can restore the
|
|
13
|
+
* pinned panel — otherwise the todos would only appear as a scrolling box.
|
|
14
|
+
*
|
|
15
|
+
* Tool results are NOT stored on the assistant message's toolCalls[].result —
|
|
16
|
+
* runAgentLoop emits them as separate role:"tool" messages linked by
|
|
17
|
+
* toolCallId (agent-loop.ts:459). This serializer joins the two so replayed
|
|
18
|
+
* tool blocks show their output. Tool durationMs is not persisted, so replayed
|
|
19
|
+
* blocks render without duration (cosmetic only).
|
|
20
|
+
*/
|
|
21
|
+
export function messagesToFeedEntries(messages) {
|
|
22
|
+
// Index tool results by toolCallId for O(1) lookup when rendering tool calls.
|
|
23
|
+
const toolResults = new Map();
|
|
24
|
+
for (const m of messages) {
|
|
25
|
+
if (m.role === 'tool' && m.toolCallId) {
|
|
26
|
+
toolResults.set(m.toolCallId, m.content);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const entries = [];
|
|
30
|
+
let latestTodos = null;
|
|
31
|
+
for (const m of messages) {
|
|
32
|
+
if (m.role === 'system')
|
|
33
|
+
continue;
|
|
34
|
+
if (m.role === 'user') {
|
|
35
|
+
entries.push({ kind: 'user', content: m.content });
|
|
36
|
+
}
|
|
37
|
+
else if (m.role === 'assistant') {
|
|
38
|
+
if (m.content)
|
|
39
|
+
entries.push({ kind: 'assistant', content: m.content });
|
|
40
|
+
if (m.toolCalls) {
|
|
41
|
+
for (const tc of m.toolCalls) {
|
|
42
|
+
const result = tc.result ?? toolResults.get(tc.id);
|
|
43
|
+
// manage_todos feeds the persistent panel, not the scrollback (mirrors
|
|
44
|
+
// the live intercept in use-agent). Track the latest; skip the feed.
|
|
45
|
+
if (tc.name === 'manage_todos') {
|
|
46
|
+
if (result) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(result);
|
|
49
|
+
if (Array.isArray(parsed))
|
|
50
|
+
latestTodos = parsed;
|
|
51
|
+
}
|
|
52
|
+
catch { /* ignore parse error */ }
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
entries.push({
|
|
57
|
+
kind: 'tool',
|
|
58
|
+
name: tc.name,
|
|
59
|
+
args: tc.arguments,
|
|
60
|
+
status: result != null ? 'ok' : 'fail',
|
|
61
|
+
output: result,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// role: 'tool' messages are consumed above via the toolResults index —
|
|
67
|
+
// they are provider-facing duplicates, not feed entries.
|
|
68
|
+
}
|
|
69
|
+
return { entries, latestTodos };
|
|
70
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively list project files as relative posix paths, skipping dependency
|
|
3
|
+
* / build / VCS directories. Pure (no cache): the caller (`prompt-area`) owns
|
|
4
|
+
* the result in React state and refreshes it on each idle mount and whenever
|
|
5
|
+
* `@` is entered — so files an agent created during a run show up immediately
|
|
6
|
+
* on the next prompt, with no restart.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getFileIndex(root?: string): string[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
// Dirs we never want in `@file` completion (deps, build output, VCS, caches).
|
|
4
|
+
const IGNORED_DIRS = new Set([
|
|
5
|
+
'node_modules', '.git', 'dist', 'build', 'out', 'coverage',
|
|
6
|
+
'.next', '.cache', '.turbo', '.verdaccio', 'tmp',
|
|
7
|
+
]);
|
|
8
|
+
const MAX_FILES = 5000;
|
|
9
|
+
/**
|
|
10
|
+
* Recursively list project files as relative posix paths, skipping dependency
|
|
11
|
+
* / build / VCS directories. Pure (no cache): the caller (`prompt-area`) owns
|
|
12
|
+
* the result in React state and refreshes it on each idle mount and whenever
|
|
13
|
+
* `@` is entered — so files an agent created during a run show up immediately
|
|
14
|
+
* on the next prompt, with no restart.
|
|
15
|
+
*/
|
|
16
|
+
export function getFileIndex(root = process.cwd()) {
|
|
17
|
+
const files = [];
|
|
18
|
+
const stack = [root];
|
|
19
|
+
while (stack.length > 0 && files.length < MAX_FILES) {
|
|
20
|
+
const dir = stack.pop();
|
|
21
|
+
let entries;
|
|
22
|
+
try {
|
|
23
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
for (const e of entries) {
|
|
29
|
+
if (e.isDirectory()) {
|
|
30
|
+
if (IGNORED_DIRS.has(e.name) || e.name.startsWith('.'))
|
|
31
|
+
continue;
|
|
32
|
+
stack.push(path.join(dir, e.name));
|
|
33
|
+
}
|
|
34
|
+
else if (e.isFile()) {
|
|
35
|
+
files.push(path.relative(root, path.join(dir, e.name)).split(path.sep).join('/'));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
files.sort();
|
|
40
|
+
return files;
|
|
41
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* use-agent — agent run state for the TUI.
|
|
3
|
+
*
|
|
4
|
+
* Drives `Agent.chat(input, signal, approveTool, permissionLevel, onStep)`,
|
|
5
|
+
* which (in TUI mode) opts into token streaming — the loop emits `text_delta`
|
|
6
|
+
* steps as tokens arrive. Those accumulate into `streamingText` (rendered live
|
|
7
|
+
* in the message area, since Ink `<Static>` freezes completed entries); on a
|
|
8
|
+
* tool call or turn end the accumulated text is committed to the feed history.
|
|
9
|
+
* ESC/Ctrl+C calls `agent.abort()`.
|
|
10
|
+
*
|
|
11
|
+
* `approveTool` runs inside the detached `runAgentLoop` promise, so it must
|
|
12
|
+
* pause and wait for the user to press y/n in `<PermissionPrompt>`. This hook
|
|
13
|
+
* owns that bridge: it stores the pending resolver in a ref (stable across
|
|
14
|
+
* renders) and the pending prompt's view in state (so the component re-renders).
|
|
15
|
+
*/
|
|
16
|
+
import { Agent } from '../../agent.js';
|
|
17
|
+
import type { PermissionLevel, CumulativeUsage } from '../../../../core/types.js';
|
|
18
|
+
import type { Todo } from '../components/goal-status.js';
|
|
19
|
+
import type { FeedApi } from './use-feed.js';
|
|
20
|
+
export interface PendingPermissionView {
|
|
21
|
+
toolName: string;
|
|
22
|
+
args: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface StreamingToolView {
|
|
25
|
+
name: string;
|
|
26
|
+
args: Record<string, unknown>;
|
|
27
|
+
output: string;
|
|
28
|
+
}
|
|
29
|
+
export interface AgentApi {
|
|
30
|
+
isRunning: boolean;
|
|
31
|
+
pendingPermission: PendingPermissionView | null;
|
|
32
|
+
/** Live, accumulating assistant text while streaming (empty when idle). */
|
|
33
|
+
streamingText: string;
|
|
34
|
+
/** Live, accumulating tool output while a tool runs (null when idle). */
|
|
35
|
+
streamingTool: StreamingToolView | null;
|
|
36
|
+
/** Cumulative token/cost usage across the session (for the footer). */
|
|
37
|
+
usage: CumulativeUsage;
|
|
38
|
+
/** Last turn's input size in tokens — the current context-window usage. */
|
|
39
|
+
contextTokens: number;
|
|
40
|
+
/** Persistent todo list (updated by manage_todos tool; null when none). */
|
|
41
|
+
latestTodos: Todo[] | null;
|
|
42
|
+
submit: (input: string) => Promise<void>;
|
|
43
|
+
resolvePermission: (approve: boolean) => void;
|
|
44
|
+
abort: () => void;
|
|
45
|
+
resetTodos: () => void;
|
|
46
|
+
/** Restore the persistent todo panel (e.g. from a resumed session). */
|
|
47
|
+
restoreTodos: (todos: Todo[] | null) => void;
|
|
48
|
+
}
|
|
49
|
+
export interface UseAgentArgs {
|
|
50
|
+
agent: Agent;
|
|
51
|
+
feed: FeedApi;
|
|
52
|
+
permissionLevel?: PermissionLevel;
|
|
53
|
+
}
|
|
54
|
+
export declare function useAgent({ agent, feed, permissionLevel }: UseAgentArgs): AgentApi;
|