@easynet/agent-runtime 1.0.2 → 1.0.4
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/.github/workflows/ci.yml +9 -24
- package/.github/workflows/release.yml +38 -26
- package/agent-runtime/.github/workflows/ci.yml +69 -0
- package/agent-runtime/.github/workflows/release.yml +118 -0
- package/agent-runtime/.releaserc.cjs +26 -0
- package/agent-runtime/config/agent.deep.yaml +25 -0
- package/agent-runtime/config/agent.react.yaml +24 -0
- package/agent-runtime/example/basic-usage.ts +49 -0
- package/agent-runtime/package-lock.json +7740 -0
- package/agent-runtime/package.json +49 -0
- package/agent-runtime/pnpm-lock.yaml +3712 -0
- package/agent-runtime/scripts/resolve-deps.js +54 -0
- package/agent-runtime/src/agents/deep-agent.ts +165 -0
- package/agent-runtime/src/agents/react-agent.helpers.ts +227 -0
- package/agent-runtime/src/agents/react-agent.ts +584 -0
- package/{src → agent-runtime/src/agents}/sub-agent.ts +2 -2
- package/agent-runtime/src/cli/args.ts +15 -0
- package/agent-runtime/src/cli/event-listener.ts +162 -0
- package/agent-runtime/src/cli/interactive.ts +144 -0
- package/agent-runtime/src/cli/runtime.ts +31 -0
- package/agent-runtime/src/cli/spinner.ts +23 -0
- package/agent-runtime/src/cli/terminal-render.ts +322 -0
- package/agent-runtime/src/cli/types.ts +33 -0
- package/agent-runtime/src/cli.ts +134 -0
- package/agent-runtime/src/config/helpers.ts +179 -0
- package/agent-runtime/src/config/index.ts +245 -0
- package/agent-runtime/src/config/types.ts +62 -0
- package/agent-runtime/src/core/context.ts +266 -0
- package/agent-runtime/src/index.ts +55 -0
- package/agent-runtime/tsconfig.json +18 -0
- package/apps/imessagebot/README.md +38 -0
- package/apps/imessagebot/config/.agent/cache/easynet/agent-tool-buildin/0.0.45/README.md +33 -0
- package/apps/imessagebot/config/.agent/cache/easynet/agent-tool-buildin/0.0.45/package-lock.json +15257 -0
- package/apps/imessagebot/config/.agent/cache/easynet/agent-tool-buildin/0.0.45/package.json +55 -0
- package/apps/imessagebot/config/agents/deep/agent.yaml +31 -0
- package/apps/imessagebot/config/agents/react/agent.yaml +58 -0
- package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.43/README.md +33 -0
- package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.43/package-lock.json +15457 -0
- package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.43/package.json +55 -0
- package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.46/README.md +33 -0
- package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.46/package-lock.json +15257 -0
- package/apps/imessagebot/config/agents/shared/.agent/cache/easynet/agent-tool-buildin/0.0.46/package.json +62 -0
- package/apps/imessagebot/config/agents/shared/memory.yaml +31 -0
- package/apps/imessagebot/config/agents/shared/model.yaml +23 -0
- package/apps/imessagebot/config/agents/shared/tool.yaml +13 -0
- package/apps/imessagebot/config/app.yaml +14 -0
- package/apps/imessagebot/package-lock.json +53695 -0
- package/apps/imessagebot/package.json +41 -0
- package/apps/imessagebot/pnpm-lock.yaml +1589 -0
- package/apps/imessagebot/scripts/resolve-deps.js +41 -0
- package/apps/imessagebot/scripts/test-llm.mjs +27 -0
- package/apps/imessagebot/scripts/validate-tools-config.mjs +174 -0
- package/apps/imessagebot/src/config.ts +76 -0
- package/apps/imessagebot/src/context.ts +35 -0
- package/apps/imessagebot/src/index.ts +17 -0
- package/apps/imessagebot/tsconfig.json +18 -0
- package/apps/itermbot/.github/workflows/ci.yml +61 -0
- package/apps/itermbot/.github/workflows/release.yml +80 -0
- package/apps/itermbot/.releaserc.cjs +26 -0
- package/apps/itermbot/README.md +82 -0
- package/apps/itermbot/config/app.yaml +29 -0
- package/apps/itermbot/config/tsconfig.json +18 -0
- package/apps/itermbot/macos_disk_usage_agent_plan.md +244 -0
- package/apps/itermbot/package-lock.json +53697 -0
- package/apps/itermbot/package.json +57 -0
- package/apps/itermbot/pnpm-lock.yaml +3966 -0
- package/apps/itermbot/scripts/patch-buildin-cache.sh +25 -0
- package/apps/itermbot/scripts/resolve-deps.js +41 -0
- package/apps/itermbot/scripts/test-llm.mjs +32 -0
- package/apps/itermbot/skills/command-explain-and-guard/SKILL.md +39 -0
- package/apps/itermbot/skills/command-explain-and-guard/handler.js +86 -0
- package/apps/itermbot/skills/disk-usage-investigate/SKILL.md +44 -0
- package/apps/itermbot/skills/disk-usage-investigate/handler.js +12 -0
- package/apps/itermbot/skills/gpu-ssh-monitor/SKILL.md +64 -0
- package/apps/itermbot/skills/repo-triage/SKILL.md +40 -0
- package/apps/itermbot/skills/repo-triage/handler.js +56 -0
- package/apps/itermbot/skills/test-failure-diagnose/SKILL.md +43 -0
- package/apps/itermbot/skills/test-failure-diagnose/handler.js +107 -0
- package/apps/itermbot/src/config.ts +95 -0
- package/apps/itermbot/src/context.ts +35 -0
- package/apps/itermbot/src/index.ts +223 -0
- package/apps/itermbot/src/iterm/session-hint.ts +40 -0
- package/apps/itermbot/src/iterm/target-routing.ts +419 -0
- package/apps/itermbot/src/startup/colors.ts +317 -0
- package/apps/itermbot/src/startup/diagnostics.ts +97 -0
- package/apps/itermbot/src/startup/ui.ts +141 -0
- package/config/agent.deep.yaml +25 -0
- package/config/agent.react.yaml +24 -0
- package/dist/agents/deep-agent.d.ts +37 -0
- package/dist/agents/deep-agent.d.ts.map +1 -0
- package/dist/agents/deep-agent.js +115 -0
- package/dist/agents/deep-agent.js.map +1 -0
- package/dist/agents/react-agent.d.ts +40 -0
- package/dist/agents/react-agent.d.ts.map +1 -0
- package/dist/agents/react-agent.helpers.d.ts +40 -0
- package/dist/agents/react-agent.helpers.d.ts.map +1 -0
- package/dist/agents/react-agent.helpers.js +196 -0
- package/dist/agents/react-agent.helpers.js.map +1 -0
- package/dist/agents/react-agent.js +400 -0
- package/dist/agents/react-agent.js.map +1 -0
- package/dist/agents/sub-agent.d.ts +34 -0
- package/dist/agents/sub-agent.d.ts.map +1 -0
- package/dist/agents/sub-agent.js +53 -0
- package/dist/agents/sub-agent.js.map +1 -0
- package/dist/cli/args.d.ts +8 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +9 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/event-listener.d.ts +3 -0
- package/dist/cli/event-listener.d.ts.map +1 -0
- package/dist/cli/event-listener.js +131 -0
- package/dist/cli/event-listener.js.map +1 -0
- package/dist/cli/interactive.d.ts +4 -0
- package/dist/cli/interactive.d.ts.map +1 -0
- package/dist/cli/interactive.js +118 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli/runtime.d.ts +8 -0
- package/dist/cli/runtime.d.ts.map +1 -0
- package/dist/cli/runtime.js +27 -0
- package/dist/cli/runtime.js.map +1 -0
- package/dist/cli/spinner.d.ts +2 -0
- package/dist/cli/spinner.d.ts.map +1 -0
- package/dist/cli/spinner.js +22 -0
- package/dist/cli/spinner.js.map +1 -0
- package/dist/cli/terminal-render.d.ts +7 -0
- package/dist/cli/terminal-render.d.ts.map +1 -0
- package/dist/cli/terminal-render.js +282 -0
- package/dist/cli/terminal-render.js.map +1 -0
- package/dist/cli/types.d.ts +29 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +3 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli.d.ts +4 -41
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +84 -588
- package/dist/cli.js.map +1 -1
- package/dist/config/helpers.d.ts +6 -0
- package/dist/config/helpers.d.ts.map +1 -0
- package/dist/config/helpers.js +164 -0
- package/dist/config/helpers.js.map +1 -0
- package/dist/config/index.d.ts +15 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +160 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +57 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/context.d.ts +8 -69
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +44 -24
- package/dist/context.js.map +1 -1
- package/dist/core/context.d.ts +66 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +149 -0
- package/dist/core/context.js.map +1 -0
- package/dist/deep-agent.d.ts +5 -2
- package/dist/deep-agent.d.ts.map +1 -1
- package/dist/deep-agent.js +44 -11
- package/dist/deep-agent.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/middleware/malformed-tool-call-middleware.d.ts +8 -0
- package/dist/middleware/malformed-tool-call-middleware.d.ts.map +1 -0
- package/dist/middleware/malformed-tool-call-middleware.js +191 -0
- package/dist/middleware/malformed-tool-call-middleware.js.map +1 -0
- package/dist/react-agent.d.ts +2 -2
- package/dist/react-agent.d.ts.map +1 -1
- package/dist/react-agent.js +28 -9
- package/dist/react-agent.js.map +1 -1
- package/package.json +1 -1
- package/scripts/resolve-deps.js +54 -0
- package/src/agents/deep-agent.ts +165 -0
- package/src/agents/react-agent.helpers.ts +227 -0
- package/src/agents/react-agent.ts +584 -0
- package/src/agents/sub-agent.ts +82 -0
- package/src/cli/args.ts +15 -0
- package/src/cli/event-listener.ts +162 -0
- package/src/cli/interactive.ts +144 -0
- package/src/cli/runtime.ts +31 -0
- package/src/cli/spinner.ts +23 -0
- package/src/cli/terminal-render.ts +322 -0
- package/src/cli/types.ts +33 -0
- package/src/cli.ts +91 -702
- package/src/config/helpers.ts +179 -0
- package/src/config/index.ts +245 -0
- package/src/config/types.ts +62 -0
- package/src/core/context.ts +266 -0
- package/src/index.ts +13 -11
- package/src/middleware/malformed-tool-call-middleware.ts +239 -0
- package/src/types/markdown-it-terminal.d.ts +4 -0
- package/src/types/marked-terminal.d.ts +16 -0
- package/dist/config.d.ts +0 -86
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -84
- package/dist/config.js.map +0 -1
- package/src/config.ts +0 -177
- package/src/context.ts +0 -247
- package/src/deep-agent.ts +0 -104
- package/src/react-agent.ts +0 -576
- /package/{src → agent-runtime/src/middleware}/malformed-tool-call-middleware.ts +0 -0
- /package/{src → agent-runtime/src/types}/markdown-it-terminal.d.ts +0 -0
- /package/{src → agent-runtime/src/types}/marked-terminal.d.ts +0 -0
package/src/cli.ts
CHANGED
|
@@ -2,741 +2,130 @@
|
|
|
2
2
|
* Shared CLI runner for agent apps.
|
|
3
3
|
* Provides: arg parsing, single-shot mode, interactive REPL, event logging.
|
|
4
4
|
*/
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
renderMarkdown?: boolean;
|
|
33
|
-
/** Echo the user question in the response section. Default: true. */
|
|
34
|
-
echoUserQuestion?: boolean;
|
|
35
|
-
/** Startup loading line. Set false to suppress. */
|
|
36
|
-
loadingText?: string | false;
|
|
37
|
-
/** Show animated loading progress while creating bot context. */
|
|
38
|
-
loadingSpinner?: boolean;
|
|
39
|
-
/** Startup ready line. Set false to suppress. */
|
|
40
|
-
readyText?: string | false;
|
|
41
|
-
/** Interactive intro line before prompt. Set false to suppress. */
|
|
42
|
-
interactiveIntro?: string | false;
|
|
43
|
-
/** Show animated spinner while a user request is being processed. */
|
|
44
|
-
processingSpinner?: boolean;
|
|
45
|
-
/** Spinner label shown while processing. Set false to suppress label/spinner. */
|
|
46
|
-
processingText?: string | false;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface AppCliOptions {
|
|
50
|
-
appName: string;
|
|
51
|
-
createBotContext: () => Promise<BotContext>;
|
|
52
|
-
/** Extra interactive commands, e.g. { "list tools": (ctx) => ... } */
|
|
53
|
-
interactiveCommands?: Record<string, (ctx: BotContext) => void | Promise<void>>;
|
|
54
|
-
/** Extra event listener(s) for structured runtime logs. */
|
|
55
|
-
eventListener?: AgentEventListener | AgentEventListener[];
|
|
56
|
-
/** Called after context is ready, before the REPL starts. Failures are logged but do not crash. */
|
|
57
|
-
onReady?: (ctx: BotContext) => void | Promise<void>;
|
|
58
|
-
/** Called during shutdown (exit/signal). Should be synchronous and fast. */
|
|
59
|
-
onShutdown?: (ctx: BotContext) => void;
|
|
60
|
-
/** Interactive UI style options. */
|
|
61
|
-
ui?: AppCliUiOptions;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function parseArgs(): { kind: AgentKind; query?: string } {
|
|
65
|
-
const args = process.argv.slice(2);
|
|
66
|
-
const first = args[0]?.toLowerCase();
|
|
67
|
-
const kind: AgentKind = first === DEEP ? DEEP : REACT;
|
|
68
|
-
const query =
|
|
69
|
-
first === REACT || first === DEEP ? (args[1] ?? undefined) : (first ?? undefined);
|
|
70
|
-
return { kind, query };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function escapeRegExp(value: string): string {
|
|
74
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function createRuntime(
|
|
78
|
-
ctx: BotContext,
|
|
79
|
-
kind: AgentKind
|
|
80
|
-
): Promise<ReactAgentRuntime | DeepAgentRuntime> {
|
|
81
|
-
if (kind === REACT) {
|
|
82
|
-
return createReactAgent(ctx, {
|
|
83
|
-
middleware: [malformedToolCallMiddleware()],
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
return createDeepAgent(ctx);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function runOne(ctx: BotContext, kind: AgentKind, query: string): Promise<string> {
|
|
90
|
-
const runtime = await createRuntime(ctx, kind);
|
|
91
|
-
const { text } = await runtime.run(query);
|
|
92
|
-
return text;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function createAnsi(useColor: boolean): Record<string, string> {
|
|
96
|
-
return {
|
|
97
|
-
reset: useColor ? "\x1b[0m" : "",
|
|
98
|
-
dim: useColor ? "\x1b[2m" : "",
|
|
99
|
-
heading: useColor ? "\x1b[1;38;5;45m" : "",
|
|
100
|
-
bullet: useColor ? "\x1b[38;5;81m" : "",
|
|
101
|
-
code: useColor ? "\x1b[38;5;221m" : "",
|
|
102
|
-
quote: useColor ? "\x1b[38;5;245m" : "",
|
|
103
|
-
hr: useColor ? "\x1b[38;5;240m" : "",
|
|
5
|
+
import { createConsoleAgentEventListener } from "@easynet/agent-common";
|
|
6
|
+
import { parseArgs } from "./cli/args.js";
|
|
7
|
+
import { createStructuredRunEventListener } from "./cli/event-listener.js";
|
|
8
|
+
import { interactive } from "./cli/interactive.js";
|
|
9
|
+
import { readDefaultAgentKindFromConfig, runOne } from "./cli/runtime.js";
|
|
10
|
+
import { startLoadingSpinner } from "./cli/spinner.js";
|
|
11
|
+
import { renderForTerminal } from "./cli/terminal-render.js";
|
|
12
|
+
import { REACT, type AgentKind, type AppCliOptions, type AppCliUiOptions } from "./cli/types.js";
|
|
13
|
+
|
|
14
|
+
export type { AppCliOptions, AppCliUiOptions };
|
|
15
|
+
export { createStructuredRunEventListener };
|
|
16
|
+
|
|
17
|
+
function createExitHandler(
|
|
18
|
+
appName: string,
|
|
19
|
+
onShutdown: AppCliOptions["onShutdown"],
|
|
20
|
+
ctx: Awaited<ReturnType<AppCliOptions["createBotContext"]>>,
|
|
21
|
+
): (code: number) => never {
|
|
22
|
+
let didCleanup = false;
|
|
23
|
+
|
|
24
|
+
const cleanup = () => {
|
|
25
|
+
if (didCleanup) return;
|
|
26
|
+
didCleanup = true;
|
|
27
|
+
try {
|
|
28
|
+
onShutdown?.(ctx);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(`${appName}: onShutdown hook failed:`, err instanceof Error ? err.message : err);
|
|
31
|
+
}
|
|
104
32
|
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function styleInlineMarkdown(line: string, ansi: Record<string, string>): string {
|
|
108
|
-
return line
|
|
109
|
-
.replace(/`([^`]+)`/g, `${ansi.code}\`$1\`${ansi.reset}`)
|
|
110
|
-
.replace(/\*\*([^*]+)\*\*/g, `${ansi.heading}$1${ansi.reset}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function parseTableRow(line: string): string[] {
|
|
114
|
-
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
115
|
-
return trimmed.split("|").map((cell) => cell.trim());
|
|
116
|
-
}
|
|
117
33
|
|
|
118
|
-
|
|
119
|
-
|
|
34
|
+
process.once("exit", cleanup);
|
|
35
|
+
const exitApp = (code: number): never => {
|
|
36
|
+
cleanup();
|
|
37
|
+
process.exit(code);
|
|
38
|
+
};
|
|
39
|
+
process.once("SIGINT", () => exitApp(130));
|
|
40
|
+
process.once("SIGTERM", () => exitApp(143));
|
|
41
|
+
return exitApp;
|
|
120
42
|
}
|
|
121
43
|
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
for (const row of normalized) {
|
|
126
|
-
while (row.length < colCount) row.push("");
|
|
44
|
+
function subscribeEventListeners(options: AppCliOptions, ctx: Awaited<ReturnType<AppCliOptions["createBotContext"]>>): void {
|
|
45
|
+
if (process.env.AGENT_EVENT_STDERR === "1") {
|
|
46
|
+
ctx.events.subscribe(createConsoleAgentEventListener());
|
|
127
47
|
}
|
|
128
48
|
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
normalized.forEach((row, idx) => {
|
|
137
|
-
const body = row
|
|
138
|
-
.map((cell, i) => ` ${(cell ?? "").padEnd(widths[i] ?? 0)} `)
|
|
139
|
-
.join("|");
|
|
140
|
-
const styledBody =
|
|
141
|
-
idx === 0
|
|
142
|
-
? `${ansi.heading}|${styleInlineMarkdown(body, ansi)}|${ansi.reset}`
|
|
143
|
-
: `|${styleInlineMarkdown(body, ansi)}|`;
|
|
144
|
-
out.push(styledBody);
|
|
145
|
-
if (idx === 0) out.push(`${ansi.hr}${line}${ansi.reset}`);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
out.push(`${ansi.hr}${line}${ansi.reset}`);
|
|
149
|
-
return out;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function formatMarkdownForTerminal(
|
|
153
|
-
markdown: string,
|
|
154
|
-
options: { useColor: boolean } = { useColor: true },
|
|
155
|
-
): string {
|
|
156
|
-
const ansi = createAnsi(options.useColor);
|
|
157
|
-
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
158
|
-
const out: string[] = [];
|
|
159
|
-
let inCodeBlock = false;
|
|
160
|
-
let codeFenceLang = "";
|
|
161
|
-
let codeBoxWidth = 0;
|
|
162
|
-
|
|
163
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
164
|
-
const rawLine = lines[i];
|
|
165
|
-
const line = rawLine ?? "";
|
|
166
|
-
|
|
167
|
-
if (line.trim().startsWith("```")) {
|
|
168
|
-
if (!inCodeBlock) {
|
|
169
|
-
inCodeBlock = true;
|
|
170
|
-
codeFenceLang = line.trim().slice(3).trim();
|
|
171
|
-
const title = codeFenceLang ? ` code:${codeFenceLang} ` : " code ";
|
|
172
|
-
codeBoxWidth = Math.max(16, title.length + 4);
|
|
173
|
-
out.push(`${ansi.hr}┌${"─".repeat(codeBoxWidth)}┐${ansi.reset}`);
|
|
174
|
-
out.push(`${ansi.hr}│${ansi.reset}${ansi.dim}${title.padEnd(codeBoxWidth)}${ansi.reset}${ansi.hr}│${ansi.reset}`);
|
|
175
|
-
} else {
|
|
176
|
-
inCodeBlock = false;
|
|
177
|
-
codeFenceLang = "";
|
|
178
|
-
out.push(`${ansi.hr}└${"─".repeat(codeBoxWidth)}┘${ansi.reset}`);
|
|
179
|
-
}
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (inCodeBlock) {
|
|
184
|
-
const content = line.length > codeBoxWidth ? line.slice(0, codeBoxWidth) : line.padEnd(codeBoxWidth);
|
|
185
|
-
out.push(`${ansi.hr}│${ansi.reset}${ansi.code}${content}${ansi.reset}${ansi.hr}│${ansi.reset}`);
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Markdown table: header row + separator row + data rows
|
|
190
|
-
if (
|
|
191
|
-
line.includes("|") &&
|
|
192
|
-
i + 1 < lines.length &&
|
|
193
|
-
(lines[i + 1] ?? "").includes("|")
|
|
194
|
-
) {
|
|
195
|
-
const header = parseTableRow(line);
|
|
196
|
-
const sep = parseTableRow(lines[i + 1] ?? "");
|
|
197
|
-
if (header.length > 1 && header.length === sep.length && isTableSeparatorRow(sep)) {
|
|
198
|
-
const rows: string[][] = [header];
|
|
199
|
-
let j = i + 2;
|
|
200
|
-
while (j < lines.length && (lines[j] ?? "").includes("|")) {
|
|
201
|
-
rows.push(parseTableRow(lines[j] ?? ""));
|
|
202
|
-
j += 1;
|
|
203
|
-
}
|
|
204
|
-
out.push(...renderTable(rows, ansi));
|
|
205
|
-
i = j - 1;
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (/^\s*#{1,6}\s+/.test(line)) {
|
|
211
|
-
const headingText = line.replace(/^\s*#{1,6}\s+/, "").trim();
|
|
212
|
-
out.push(`${ansi.heading}${headingText}${ansi.reset}`);
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (/^\s*>\s?/.test(line)) {
|
|
217
|
-
out.push(`${ansi.quote}${line}${ansi.reset}`);
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (/^\s*([-*]|\d+\.)\s+/.test(line)) {
|
|
222
|
-
out.push(`${ansi.bullet}${styleInlineMarkdown(line, ansi)}${ansi.reset}`);
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (/^\s*---+\s*$/.test(line)) {
|
|
227
|
-
out.push(`${ansi.hr}${"─".repeat(56)}${ansi.reset}`);
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
out.push(styleInlineMarkdown(line, ansi));
|
|
49
|
+
const listeners = Array.isArray(options.eventListener)
|
|
50
|
+
? options.eventListener
|
|
51
|
+
: options.eventListener
|
|
52
|
+
? [options.eventListener]
|
|
53
|
+
: [];
|
|
54
|
+
for (const listener of listeners) {
|
|
55
|
+
ctx.events.subscribe(listener);
|
|
232
56
|
}
|
|
233
|
-
|
|
234
|
-
return out.join("\n");
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const markdownRenderers = new Map<"color" | "plain", MarkdownIt>();
|
|
238
|
-
|
|
239
|
-
function getMarkdownRenderer(useColor: boolean): MarkdownIt {
|
|
240
|
-
const key = useColor ? "color" : "plain";
|
|
241
|
-
const cached = markdownRenderers.get(key);
|
|
242
|
-
if (cached) return cached;
|
|
243
|
-
|
|
244
|
-
const md = new MarkdownIt({
|
|
245
|
-
html: false,
|
|
246
|
-
linkify: true,
|
|
247
|
-
typographer: true,
|
|
248
|
-
breaks: false,
|
|
249
|
-
});
|
|
250
|
-
md.use(markdownItTerminal, {
|
|
251
|
-
indent: "",
|
|
252
|
-
...(useColor
|
|
253
|
-
? {}
|
|
254
|
-
: {
|
|
255
|
-
styleOptions: {
|
|
256
|
-
code: (s: string) => s,
|
|
257
|
-
blockquote: (s: string) => s,
|
|
258
|
-
html: (s: string) => s,
|
|
259
|
-
heading: (s: string) => s,
|
|
260
|
-
firstHeading: (s: string) => s,
|
|
261
|
-
hr: (s: string) => s,
|
|
262
|
-
listitem: (s: string) => s,
|
|
263
|
-
table: (s: string) => s,
|
|
264
|
-
paragraph: (s: string) => s,
|
|
265
|
-
strong: (s: string) => s,
|
|
266
|
-
em: (s: string) => s,
|
|
267
|
-
codespan: (s: string) => s,
|
|
268
|
-
del: (s: string) => s,
|
|
269
|
-
link: (s: string) => s,
|
|
270
|
-
href: (s: string) => s,
|
|
271
|
-
},
|
|
272
|
-
}),
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
markdownRenderers.set(key, md);
|
|
276
|
-
return md;
|
|
277
57
|
}
|
|
278
58
|
|
|
279
|
-
function
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// Remove stray inline fence markers that frequently break markdown rendering.
|
|
286
|
-
if (next.includes("```") && !trimmed.startsWith("```")) {
|
|
287
|
-
next = next.replace(/```+/g, "").replace(/\s+$/, "");
|
|
288
|
-
}
|
|
289
|
-
// Keep headings stable and readable in terminal output.
|
|
290
|
-
if (/^\s*#{3,}\s+/.test(next)) {
|
|
291
|
-
next = next.replace(/^\s*#{3,}\s+/, "## ");
|
|
292
|
-
}
|
|
293
|
-
return next;
|
|
294
|
-
};
|
|
295
|
-
const normalizeListLine = (line: string): string => {
|
|
296
|
-
if (/^(\s{2,}|\t+)([*-])\s+/.test(line)) {
|
|
297
|
-
return line.replace(/^(\s{2,}|\t+)([*-])\s+/, "- ");
|
|
298
|
-
}
|
|
299
|
-
if (/^(\s{2,}|\t+)(\d+\.)\s+/.test(line)) {
|
|
300
|
-
return line.replace(/^(\s{2,}|\t+)(\d+\.)\s+/, "$2 ");
|
|
301
|
-
}
|
|
302
|
-
return line;
|
|
303
|
-
};
|
|
304
|
-
const lines = source
|
|
305
|
-
.split("\n")
|
|
306
|
-
.map(sanitizeLine)
|
|
307
|
-
.map(normalizeListLine);
|
|
308
|
-
|
|
309
|
-
const titled = lines.map((line) => {
|
|
310
|
-
const trimmed = line.trim();
|
|
311
|
-
const bulletHeading = trimmed.match(/^[-*]\s+\*\*([^*]+)\*\*:?\s*$/);
|
|
312
|
-
if (bulletHeading?.[1]) {
|
|
313
|
-
return `### ${bulletHeading[1].trim()}`;
|
|
314
|
-
}
|
|
315
|
-
const plainBulletHeading = trimmed.match(/^[-*]\s+([^`].+):\s*$/);
|
|
316
|
-
if (plainBulletHeading?.[1] && plainBulletHeading[1].length <= 48) {
|
|
317
|
-
return `### ${plainBulletHeading[1].trim()}`;
|
|
318
|
-
}
|
|
319
|
-
if (hasMarkdownHeadings) return trimmed === "Summary (3‑8 bullets)" ? "## Summary" : line;
|
|
320
|
-
const unwrapped = trimmed.replace(/^\*\*(.+)\*\*$/, "$1").trim();
|
|
321
|
-
if (/^(Key terminal output.*|Current terminal buffer.*|Summary.*|Next steps.*)$/i.test(unwrapped)) {
|
|
322
|
-
return `## ${unwrapped}`;
|
|
323
|
-
}
|
|
324
|
-
if (/^(Key observations|Findings|Analysis|Conclusion)$/i.test(unwrapped)) {
|
|
325
|
-
return `## ${unwrapped}`;
|
|
326
|
-
}
|
|
327
|
-
return line;
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// Merge accidental line breaks inside list items so markdown renders as one bullet.
|
|
331
|
-
const merged: string[] = [];
|
|
332
|
-
for (let i = 0; i < titled.length; i += 1) {
|
|
333
|
-
const current = titled[i] ?? "";
|
|
334
|
-
const next = titled[i + 1] ?? "";
|
|
335
|
-
const currentTrim = current.trim();
|
|
336
|
-
const nextTrim = next.trim();
|
|
337
|
-
const isListLine = /^([-*]|\d+\.)\s+/.test(currentTrim);
|
|
338
|
-
const nextStartsNewBlock =
|
|
339
|
-
nextTrim.length === 0 ||
|
|
340
|
-
/^([-*]|\d+\.)\s+/.test(nextTrim) ||
|
|
341
|
-
/^#{1,6}\s+/.test(nextTrim) ||
|
|
342
|
-
/^```/.test(nextTrim) ||
|
|
343
|
-
/^---+$/.test(nextTrim);
|
|
344
|
-
const looksLikeWrappedContinuation =
|
|
345
|
-
isListLine &&
|
|
346
|
-
!nextStartsNewBlock &&
|
|
347
|
-
/^[0-9./~]/.test(nextTrim);
|
|
348
|
-
|
|
349
|
-
if (looksLikeWrappedContinuation) {
|
|
350
|
-
merged.push(`${current.replace(/\s+$/, "")} ${nextTrim}`);
|
|
351
|
-
i += 1;
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
merged.push(current);
|
|
59
|
+
async function runOnReady(options: AppCliOptions, appName: string, ctx: Awaited<ReturnType<AppCliOptions["createBotContext"]>>): Promise<void> {
|
|
60
|
+
if (!options.onReady) return;
|
|
61
|
+
try {
|
|
62
|
+
await options.onReady(ctx);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error(`${appName}: onReady hook failed:`, err instanceof Error ? err.message : err);
|
|
355
65
|
}
|
|
66
|
+
}
|
|
356
67
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const heading = line.trim();
|
|
363
|
-
const isOutputHeading = /^##\s*(Key terminal output|Current terminal buffer|Terminal output)/i.test(heading);
|
|
364
|
-
if (!isOutputHeading) continue;
|
|
365
|
-
if ((merged[i + 1] ?? "").trim().startsWith("```")) continue;
|
|
366
|
-
|
|
367
|
-
let j = i + 1;
|
|
368
|
-
const block: string[] = [];
|
|
369
|
-
while (j < merged.length) {
|
|
370
|
-
const current = merged[j] ?? "";
|
|
371
|
-
if (!current.trim()) {
|
|
372
|
-
if (block.length === 0) {
|
|
373
|
-
j += 1;
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
378
|
-
if (!/^\s{2,}\S/.test(current)) break;
|
|
379
|
-
block.push(current.replace(/^\s+/, ""));
|
|
380
|
-
j += 1;
|
|
381
|
-
}
|
|
68
|
+
async function createContextWithLoading(options: AppCliOptions): Promise<Awaited<ReturnType<AppCliOptions["createBotContext"]>>> {
|
|
69
|
+
const loadingText = options.ui?.loadingText ?? `${options.appName}: loading config, LLM, memory, tools...`;
|
|
70
|
+
const useLoadingSpinner = options.ui?.loadingSpinner ?? false;
|
|
71
|
+
let stopLoadingSpinner: (() => void) | null = null;
|
|
382
72
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
73
|
+
if (loadingText !== false) {
|
|
74
|
+
if (useLoadingSpinner && Boolean(process.stderr.isTTY)) {
|
|
75
|
+
stopLoadingSpinner = startLoadingSpinner(loadingText);
|
|
76
|
+
} else {
|
|
77
|
+
console.error(loadingText);
|
|
388
78
|
}
|
|
389
79
|
}
|
|
390
80
|
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return out.join("\n");
|
|
81
|
+
const ctx = await options.createBotContext();
|
|
82
|
+
stopLoadingSpinner?.();
|
|
83
|
+
return ctx;
|
|
397
84
|
}
|
|
398
85
|
|
|
399
|
-
function
|
|
400
|
-
|
|
401
|
-
options
|
|
402
|
-
)
|
|
403
|
-
|
|
404
|
-
const normalizedText = normalizeAssistantMarkdown(text);
|
|
405
|
-
try {
|
|
406
|
-
return getMarkdownRenderer(options.useColor).render(normalizedText, {});
|
|
407
|
-
} catch {
|
|
408
|
-
return formatMarkdownForTerminal(normalizedText, { useColor: options.useColor });
|
|
86
|
+
function printReady(options: AppCliOptions, kind: AgentKind): void {
|
|
87
|
+
const agentLabel = kind === REACT ? "ReAct (LangChain)" : "Deep (DeepAgents)";
|
|
88
|
+
const readyText = options.ui?.readyText ?? `Ready. Agent: ${agentLabel}`;
|
|
89
|
+
if (readyText !== false) {
|
|
90
|
+
console.error(readyText);
|
|
409
91
|
}
|
|
410
92
|
}
|
|
411
93
|
|
|
412
|
-
function
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
let lastLength = 0;
|
|
416
|
-
const render = () => {
|
|
417
|
-
const frame = frames[frameIndex % frames.length] ?? "";
|
|
418
|
-
const trimmed = message.trim();
|
|
419
|
-
const base = trimmed.startsWith("⏳") ? trimmed : `⏳ ${trimmed}`;
|
|
420
|
-
const line = `${base}${frame}`;
|
|
421
|
-
const padded = lastLength > line.length ? line.padEnd(lastLength, " ") : line;
|
|
422
|
-
process.stderr.write(`\r${padded}\r`);
|
|
423
|
-
lastLength = padded.length;
|
|
424
|
-
frameIndex += 1;
|
|
425
|
-
};
|
|
426
|
-
render();
|
|
427
|
-
const timer = setInterval(render, 90);
|
|
428
|
-
return () => {
|
|
429
|
-
clearInterval(timer);
|
|
430
|
-
process.stderr.write(`\r${" ".repeat(lastLength)}\r`);
|
|
431
|
-
};
|
|
94
|
+
function resolveAgentKind(parsed: ReturnType<typeof parseArgs>, ctx: Awaited<ReturnType<AppCliOptions["createBotContext"]>>): AgentKind {
|
|
95
|
+
if (parsed.explicitKind) return parsed.kind;
|
|
96
|
+
return readDefaultAgentKindFromConfig(ctx) ?? parsed.kind;
|
|
432
97
|
}
|
|
433
98
|
|
|
434
|
-
async function
|
|
435
|
-
ctx: BotContext,
|
|
436
|
-
kind: AgentKind,
|
|
99
|
+
async function runSingleQuery(
|
|
437
100
|
options: AppCliOptions,
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
const userLabel = options.ui?.userLabel ?? `${userInfo.username}`;
|
|
444
|
-
const assistantLabel = options.ui?.assistantLabel ?? name;
|
|
101
|
+
ctx: Awaited<ReturnType<AppCliOptions["createBotContext"]>>,
|
|
102
|
+
kind: AgentKind,
|
|
103
|
+
query: string,
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
const text = await runOne(ctx, kind, query);
|
|
445
106
|
const useColor = options.ui?.useColor ?? (Boolean(process.stdout.isTTY) && !process.env.NO_COLOR);
|
|
446
107
|
const renderMarkdown = options.ui?.renderMarkdown ?? true;
|
|
447
|
-
|
|
448
|
-
const showProcessingSpinner =
|
|
449
|
-
options.ui?.processingSpinner ?? Boolean(process.stderr.isTTY);
|
|
450
|
-
const processingText = options.ui?.processingText ?? "Processing";
|
|
451
|
-
|
|
452
|
-
const color = {
|
|
453
|
-
reset: useColor ? "\x1b[0m" : "",
|
|
454
|
-
dim: useColor ? "\x1b[2m" : "",
|
|
455
|
-
user: useColor ? "\x1b[38;5;39m" : "",
|
|
456
|
-
bot: useColor ? "\x1b[38;5;48m" : "",
|
|
457
|
-
prompt: useColor ? "\x1b[38;5;245m" : "",
|
|
458
|
-
promptUser: useColor ? "\x1b[1;38;5;45m" : "",
|
|
459
|
-
};
|
|
460
|
-
const hr = `${color.dim}${"-".repeat(56)}${color.reset}`;
|
|
461
|
-
const promptText = `${color.prompt}[${color.promptUser}${userLabel}${color.reset}${color.prompt}]${color.reset} `;
|
|
462
|
-
|
|
463
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
464
|
-
const userLabelPrefixPattern = new RegExp(`^(?:\\[${escapeRegExp(userLabel)}\\]\\s*)+`, "i");
|
|
465
|
-
const prompt = () =>
|
|
466
|
-
rl.question(promptText, async (line) => {
|
|
467
|
-
const qRaw = line?.trim();
|
|
468
|
-
const q = qRaw?.replace(userLabelPrefixPattern, "").trim();
|
|
469
|
-
if (!q) {
|
|
470
|
-
prompt();
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
if (q === "exit" || q === "quit") {
|
|
474
|
-
rl.close();
|
|
475
|
-
exitApp(0);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const handler = options.interactiveCommands?.[q.toLowerCase()];
|
|
479
|
-
if (handler) {
|
|
480
|
-
await handler(ctx);
|
|
481
|
-
prompt();
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
let stopProcessingSpinner: (() => void) | null = null;
|
|
486
|
-
try {
|
|
487
|
-
console.log(`\n${hr}`);
|
|
488
|
-
if (echoUserQuestion) {
|
|
489
|
-
console.log(`${color.user}[${userLabel}]${color.reset}`);
|
|
490
|
-
console.log(`> ${q}`);
|
|
491
|
-
console.log("");
|
|
492
|
-
}
|
|
493
|
-
console.log(`${color.bot}[${assistantLabel}]${color.reset}`);
|
|
494
|
-
stopProcessingSpinner = showProcessingSpinner
|
|
495
|
-
? startLoadingSpinner(processingText === false ? "⏳" : `⏳ ${processingText}`)
|
|
496
|
-
: null;
|
|
497
|
-
const { text } = await runtime.run(q);
|
|
498
|
-
if (stopProcessingSpinner) {
|
|
499
|
-
stopProcessingSpinner();
|
|
500
|
-
stopProcessingSpinner = null;
|
|
501
|
-
}
|
|
502
|
-
console.log(renderForTerminal(text, { renderMarkdown, useColor }));
|
|
503
|
-
console.log(`${hr}\n`);
|
|
504
|
-
} catch (err) {
|
|
505
|
-
if (stopProcessingSpinner) {
|
|
506
|
-
stopProcessingSpinner();
|
|
507
|
-
stopProcessingSpinner = null;
|
|
508
|
-
}
|
|
509
|
-
console.error("Error:", err instanceof Error ? err.message : err);
|
|
510
|
-
} finally {
|
|
511
|
-
if (stopProcessingSpinner) {
|
|
512
|
-
stopProcessingSpinner();
|
|
513
|
-
stopProcessingSpinner = null;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
prompt();
|
|
517
|
-
});
|
|
518
|
-
const introText =
|
|
519
|
-
options.ui?.interactiveIntro ??
|
|
520
|
-
`${options.appName} (${name} agent). Type your message or "exit" to quit.`;
|
|
521
|
-
if (introText !== false) {
|
|
522
|
-
console.log(`${introText}\n`);
|
|
523
|
-
}
|
|
524
|
-
prompt();
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
export function createStructuredRunEventListener(
|
|
528
|
-
writer: (line: string) => void = console.error
|
|
529
|
-
): AgentEventListener {
|
|
530
|
-
let step = 0;
|
|
531
|
-
let lastCommandSignature = "";
|
|
532
|
-
const stepActionByNumber = new Map<number, string>();
|
|
533
|
-
const okMark = process.stderr.isTTY && !process.env.NO_COLOR ? "\x1b[32m✔\x1b[0m" : "✔";
|
|
534
|
-
|
|
535
|
-
const asRecord = (value: unknown): Record<string, unknown> | null =>
|
|
536
|
-
typeof value === "object" && value !== null ? (value as Record<string, unknown>) : null;
|
|
537
|
-
|
|
538
|
-
const asNumber = (value: unknown): number | null =>
|
|
539
|
-
typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
540
|
-
|
|
541
|
-
const asString = (value: unknown): string | null =>
|
|
542
|
-
typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
543
|
-
const asAnyString = (value: unknown): string | null =>
|
|
544
|
-
typeof value === "string" ? value : null;
|
|
545
|
-
|
|
546
|
-
const parseJsonObject = (value: unknown): Record<string, unknown> | null => {
|
|
547
|
-
if (typeof value !== "string") return null;
|
|
548
|
-
try {
|
|
549
|
-
const parsed = JSON.parse(value) as unknown;
|
|
550
|
-
return asRecord(parsed);
|
|
551
|
-
} catch {
|
|
552
|
-
return null;
|
|
553
|
-
}
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
const extractCommandMeta = (args: unknown) => {
|
|
557
|
-
const rec = asRecord(args) ?? parseJsonObject(args);
|
|
558
|
-
if (!rec) return null;
|
|
559
|
-
const commandRaw = asAnyString(rec.command);
|
|
560
|
-
if (commandRaw === null) return null;
|
|
561
|
-
const command = commandRaw.trim();
|
|
562
|
-
return {
|
|
563
|
-
command,
|
|
564
|
-
windowId: asNumber(rec.windowId),
|
|
565
|
-
tabIndex: asNumber(rec.tabIndex),
|
|
566
|
-
sessionId: asString(rec.sessionId),
|
|
567
|
-
};
|
|
568
|
-
};
|
|
569
|
-
|
|
570
|
-
const isNoopCaptureCommand = (command: string): boolean => {
|
|
571
|
-
const normalized = command.trim().replace(/\s+/g, " ");
|
|
572
|
-
return (
|
|
573
|
-
normalized === "" ||
|
|
574
|
-
normalized === ":" ||
|
|
575
|
-
normalized === "true" ||
|
|
576
|
-
normalized === "printf ''" ||
|
|
577
|
-
normalized === "printf \"\""
|
|
578
|
-
);
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
const extractToolResultMeta = (payload: unknown) => {
|
|
582
|
-
const payloadRec = asRecord(payload);
|
|
583
|
-
const rawResult = payloadRec ? (payloadRec.result ?? null) : null;
|
|
584
|
-
const resultRec = asRecord(rawResult) ?? parseJsonObject(rawResult);
|
|
585
|
-
const nestedResult = resultRec ? asRecord(resultRec.result) : null;
|
|
586
|
-
const output =
|
|
587
|
-
asString((nestedResult ?? resultRec ?? {}).output) ??
|
|
588
|
-
asString((nestedResult ?? resultRec ?? {}).result as unknown);
|
|
589
|
-
const outputLines = output ? output.split(/\r?\n/).filter((line) => line.length > 0).length : null;
|
|
590
|
-
const error = asString((nestedResult ?? resultRec ?? {}).error);
|
|
591
|
-
return { outputLines, error };
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
return (event: AgentEvent) => {
|
|
595
|
-
switch (event.name) {
|
|
596
|
-
case "agent.react.run.start":
|
|
597
|
-
case "agent.deep.run.start":
|
|
598
|
-
step = 0;
|
|
599
|
-
lastCommandSignature = "";
|
|
600
|
-
stepActionByNumber.clear();
|
|
601
|
-
writer("Analyzing started");
|
|
602
|
-
return;
|
|
603
|
-
case "agent.react.skill.matched": {
|
|
604
|
-
const payload = (event.payload ?? {}) as { skill?: string; score?: number };
|
|
605
|
-
const score = typeof payload.score === "number" ? payload.score.toFixed(3) : "?";
|
|
606
|
-
writer(`[skill] matched ${payload.skill ?? "unknown"} (score ${score})`);
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
case "agent.react.tool.invoke.start": {
|
|
610
|
-
step += 1;
|
|
611
|
-
const payload = (event.payload ?? {}) as { args?: unknown };
|
|
612
|
-
const commandMeta = extractCommandMeta(payload.args);
|
|
613
|
-
if (commandMeta) {
|
|
614
|
-
const signature = `${commandMeta.command}|${commandMeta.windowId ?? ""}|${commandMeta.tabIndex ?? ""}|${commandMeta.sessionId ?? ""}`;
|
|
615
|
-
const isRepeat = signature === lastCommandSignature;
|
|
616
|
-
lastCommandSignature = signature;
|
|
617
|
-
const action = isNoopCaptureCommand(commandMeta.command)
|
|
618
|
-
? "capture current screen"
|
|
619
|
-
: commandMeta.command;
|
|
620
|
-
stepActionByNumber.set(step, `${action}${isRepeat ? " (repeat)" : ""}`);
|
|
621
|
-
} else {
|
|
622
|
-
stepActionByNumber.set(step, `tool: ${event.to}`);
|
|
623
|
-
}
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
case "agent.react.tool.invoke.done": {
|
|
627
|
-
const resultMeta = extractToolResultMeta(event.payload);
|
|
628
|
-
const action = stepActionByNumber.get(step) ?? `tool: ${event.to}`;
|
|
629
|
-
if (resultMeta.error) {
|
|
630
|
-
writer(`[step ${step}] ${action} ✖ (${resultMeta.error})`);
|
|
631
|
-
stepActionByNumber.delete(step);
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
writer(`[step ${step}] ${action} ${okMark}`);
|
|
635
|
-
stepActionByNumber.delete(step);
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
case "agent.react.tool.invoke.error": {
|
|
639
|
-
const payload = (event.payload ?? {}) as { error?: unknown };
|
|
640
|
-
const action = stepActionByNumber.get(step) ?? `tool: ${event.to}`;
|
|
641
|
-
const message =
|
|
642
|
-
typeof payload.error === "string"
|
|
643
|
-
? payload.error
|
|
644
|
-
: payload.error instanceof Error
|
|
645
|
-
? payload.error.message
|
|
646
|
-
: "unknown error";
|
|
647
|
-
writer(`[step ${step}] ${action} ✖ (${message})`);
|
|
648
|
-
stepActionByNumber.delete(step);
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
case "agent.react.run.done":
|
|
652
|
-
case "agent.deep.run.done":
|
|
653
|
-
writer("completed");
|
|
654
|
-
return;
|
|
655
|
-
default:
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
};
|
|
108
|
+
console.log(renderForTerminal(text, { renderMarkdown, useColor }));
|
|
659
109
|
}
|
|
660
110
|
|
|
661
111
|
export function runAppCli(options: AppCliOptions): void {
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
const loadingText =
|
|
668
|
-
options.ui?.loadingText ?? `${appName}: loading config, LLM, memory, tools...`;
|
|
669
|
-
const useLoadingSpinner = options.ui?.loadingSpinner ?? false;
|
|
670
|
-
let stopLoadingSpinner: (() => void) | null = null;
|
|
671
|
-
if (loadingText !== false) {
|
|
672
|
-
if (useLoadingSpinner && Boolean(process.stderr.isTTY)) {
|
|
673
|
-
stopLoadingSpinner = startLoadingSpinner(loadingText);
|
|
674
|
-
} else {
|
|
675
|
-
console.error(loadingText);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
const ctx = await createBotContext();
|
|
679
|
-
if (stopLoadingSpinner) {
|
|
680
|
-
stopLoadingSpinner();
|
|
681
|
-
}
|
|
682
|
-
let didCleanup = false;
|
|
683
|
-
const cleanup = () => {
|
|
684
|
-
if (didCleanup) return;
|
|
685
|
-
didCleanup = true;
|
|
686
|
-
try {
|
|
687
|
-
options.onShutdown?.(ctx);
|
|
688
|
-
} catch (err) {
|
|
689
|
-
console.error(
|
|
690
|
-
`${appName}: onShutdown hook failed:`,
|
|
691
|
-
err instanceof Error ? err.message : err,
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
};
|
|
695
|
-
const exitApp = (code: number): never => {
|
|
696
|
-
cleanup();
|
|
697
|
-
process.exit(code);
|
|
698
|
-
};
|
|
699
|
-
process.once("exit", cleanup);
|
|
700
|
-
process.once("SIGINT", () => exitApp(130));
|
|
701
|
-
process.once("SIGTERM", () => exitApp(143));
|
|
112
|
+
const main = async () => {
|
|
113
|
+
const parsed = parseArgs();
|
|
114
|
+
const ctx = await createContextWithLoading(options);
|
|
115
|
+
const kind = resolveAgentKind(parsed, ctx);
|
|
116
|
+
const exitApp = createExitHandler(options.appName, options.onShutdown, ctx);
|
|
702
117
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const eventListeners = Array.isArray(options.eventListener)
|
|
707
|
-
? options.eventListener
|
|
708
|
-
: options.eventListener
|
|
709
|
-
? [options.eventListener]
|
|
710
|
-
: [];
|
|
711
|
-
for (const listener of eventListeners) {
|
|
712
|
-
ctx.events.subscribe(listener);
|
|
713
|
-
}
|
|
714
|
-
if (options.onReady) {
|
|
715
|
-
try {
|
|
716
|
-
await options.onReady(ctx);
|
|
717
|
-
} catch (err) {
|
|
718
|
-
console.error(
|
|
719
|
-
`${appName}: onReady hook failed:`,
|
|
720
|
-
err instanceof Error ? err.message : err,
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
const readyText = options.ui?.readyText ?? `Ready. Agent: ${agentLabel}`;
|
|
725
|
-
if (readyText !== false) {
|
|
726
|
-
console.error(readyText);
|
|
727
|
-
}
|
|
118
|
+
subscribeEventListeners(options, ctx);
|
|
119
|
+
await runOnReady(options, options.appName, ctx);
|
|
120
|
+
printReady(options, kind);
|
|
728
121
|
|
|
729
|
-
if (query) {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
const renderMarkdown = options.ui?.renderMarkdown ?? true;
|
|
733
|
-
console.log(renderForTerminal(text, { renderMarkdown, useColor }));
|
|
734
|
-
cleanup();
|
|
735
|
-
return;
|
|
122
|
+
if (parsed.query) {
|
|
123
|
+
await runSingleQuery(options, ctx, kind, parsed.query);
|
|
124
|
+
exitApp(0);
|
|
736
125
|
}
|
|
737
126
|
|
|
738
127
|
await interactive(ctx, kind, options, exitApp);
|
|
739
|
-
}
|
|
128
|
+
};
|
|
740
129
|
|
|
741
130
|
main().catch((err) => {
|
|
742
131
|
console.error(err);
|