itermbot 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 +15 -20
- package/.github/workflows/release.yml +32 -20
- package/README.md +11 -20
- package/cleanup-unused.patch +108 -0
- package/config/app.yaml +32 -13
- package/config/memory.yaml +38 -31
- package/config/model.yaml +33 -0
- package/config/skill.yaml +8 -0
- package/config/tool.yaml +50 -17
- package/config/tsconfig.json +4 -1
- package/dist/chat/builtin-commands.d.ts +8 -0
- package/dist/chat/builtin-commands.d.ts.map +1 -0
- package/dist/chat/builtin-commands.js +53 -0
- package/dist/chat/builtin-commands.js.map +1 -0
- package/dist/chat/progress.d.ts +3 -0
- package/dist/chat/progress.d.ts.map +1 -0
- package/dist/chat/progress.js +23 -0
- package/dist/chat/progress.js.map +1 -0
- package/dist/chat/response-safety.d.ts +8 -0
- package/dist/chat/response-safety.d.ts.map +1 -0
- package/dist/chat/response-safety.js +126 -0
- package/dist/chat/response-safety.js.map +1 -0
- package/dist/chat/step-display.d.ts +2 -0
- package/dist/chat/step-display.d.ts.map +1 -0
- package/dist/chat/step-display.js +50 -0
- package/dist/chat/step-display.js.map +1 -0
- package/dist/chat/tool-result.d.ts +4 -0
- package/dist/chat/tool-result.d.ts.map +1 -0
- package/dist/chat/tool-result.js +24 -0
- package/dist/chat/tool-result.js.map +1 -0
- package/dist/config.d.ts +11 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +26 -12
- package/dist/config.js.map +1 -1
- package/dist/index.js +308 -151
- package/dist/index.js.map +1 -1
- package/dist/iterm/direct-command-router.d.ts +24 -0
- package/dist/iterm/direct-command-router.d.ts.map +1 -0
- package/dist/iterm/direct-command-router.js +213 -0
- package/dist/iterm/direct-command-router.js.map +1 -0
- package/dist/iterm/session-hint.d.ts +10 -0
- package/dist/iterm/session-hint.d.ts.map +1 -0
- package/dist/iterm/session-hint.js +43 -0
- package/dist/iterm/session-hint.js.map +1 -0
- package/dist/iterm/target-panel-policy.d.ts +12 -0
- package/dist/iterm/target-panel-policy.d.ts.map +1 -0
- package/dist/iterm/target-panel-policy.js +287 -0
- package/dist/iterm/target-panel-policy.js.map +1 -0
- package/dist/runtime/text-tool-call-recovery.d.ts +23 -0
- package/dist/runtime/text-tool-call-recovery.d.ts.map +1 -0
- package/dist/runtime/text-tool-call-recovery.js +211 -0
- package/dist/runtime/text-tool-call-recovery.js.map +1 -0
- package/dist/startup/colors.d.ts +37 -0
- package/dist/startup/colors.d.ts.map +1 -0
- package/dist/{startup-colors.js → startup/colors.js} +30 -15
- package/dist/startup/colors.js.map +1 -0
- package/dist/startup/diagnostics.d.ts +8 -0
- package/dist/startup/diagnostics.d.ts.map +1 -0
- package/dist/startup/diagnostics.js +18 -0
- package/dist/startup/diagnostics.js.map +1 -0
- package/dist/startup/os.d.ts +10 -0
- package/dist/startup/os.d.ts.map +1 -0
- package/dist/startup/os.js +67 -0
- package/dist/startup/os.js.map +1 -0
- package/dist/startup/ui.d.ts +11 -0
- package/dist/startup/ui.d.ts.map +1 -0
- package/dist/startup/ui.js +49 -0
- package/dist/startup/ui.js.map +1 -0
- package/package.json +23 -13
- package/scripts/internal-package-refs.mjs +158 -0
- package/scripts/patch-buildin-cache.sh +1 -4
- package/scripts/resolve-deps.js +5 -0
- package/scripts/test-llm.mjs +11 -5
- package/skills/gpu-ssh-monitor/SKILL.md +22 -3
- package/src/chat/builtin-commands.ts +70 -0
- package/src/chat/progress.ts +26 -0
- package/src/chat/response-safety.ts +134 -0
- package/src/chat/step-display.ts +54 -0
- package/src/chat/tool-result.ts +22 -0
- package/src/config.ts +48 -21
- package/src/index.ts +377 -167
- package/src/iterm/direct-command-router.ts +274 -0
- package/src/iterm/session-hint.ts +49 -0
- package/src/iterm/target-panel-policy.ts +341 -0
- package/src/runtime/text-tool-call-recovery.ts +257 -0
- package/src/{startup-colors.ts → startup/colors.ts} +42 -27
- package/src/startup/diagnostics.ts +25 -0
- package/src/startup/os.ts +63 -0
- package/src/startup/ui.ts +56 -0
- package/src/types/marked-terminal.d.ts +3 -0
- package/test/builtin-commands.test.mjs +50 -0
- package/test/chat-flow.integration.test.mjs +235 -0
- package/test/chat-progress.test.mjs +83 -0
- package/test/config.test.mjs +22 -0
- package/test/diagnostics.test.mjs +45 -0
- package/test/direct-command-router.test.mjs +149 -0
- package/test/live-iterm-llm.integration.test.mjs +153 -0
- package/test/response-safety.test.mjs +44 -0
- package/test/session-hint.test.mjs +78 -0
- package/test/startup-colors.test.mjs +145 -0
- package/test/target-panel-policy.test.mjs +180 -0
- package/test/tool-call-recovery.test.mjs +199 -0
- package/config/agent.yaml +0 -121
- package/config/models.yaml +0 -36
- package/config/skills.yaml +0 -4
- package/dist/agent.d.ts +0 -14
- package/dist/agent.d.ts.map +0 -1
- package/dist/agent.js +0 -16
- package/dist/agent.js.map +0 -1
- package/dist/context.d.ts +0 -12
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -20
- package/dist/context.js.map +0 -1
- package/dist/session-hint.d.ts +0 -4
- package/dist/session-hint.d.ts.map +0 -1
- package/dist/session-hint.js +0 -25
- package/dist/session-hint.js.map +0 -1
- package/dist/startup-colors.d.ts +0 -26
- package/dist/startup-colors.d.ts.map +0 -1
- package/dist/startup-colors.js.map +0 -1
- package/dist/target-routing.d.ts +0 -15
- package/dist/target-routing.d.ts.map +0 -1
- package/dist/target-routing.js +0 -355
- package/dist/target-routing.js.map +0 -1
- package/src/agent.ts +0 -35
- package/src/context.ts +0 -35
- package/src/session-hint.ts +0 -28
- package/src/target-routing.ts +0 -419
package/src/config.ts
CHANGED
|
@@ -1,26 +1,53 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createRuntimeConfig,
|
|
3
|
-
getModelsConfigPath as getRuntimeModelsConfigPath,
|
|
4
|
-
getMemoryConfigPath as getRuntimeMemoryConfigPath,
|
|
5
|
-
getToolConfigPath as getRuntimeToolConfigPath,
|
|
6
|
-
type AgentRuntimeConfig,
|
|
7
|
-
} from "@easynet/agent-runtime";
|
|
1
|
+
import { resolveKindResourceFile, asObject } from "@easynet/agent-common/config";
|
|
8
2
|
|
|
9
|
-
export type
|
|
3
|
+
export type PromptTemplates = {
|
|
4
|
+
systemPrompt?: string;
|
|
5
|
+
};
|
|
10
6
|
|
|
11
|
-
export
|
|
12
|
-
const runtimeConfig = await createRuntimeConfig({ configPath });
|
|
13
|
-
return runtimeConfig;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function getModelsConfigPath(config: AppConfig, agentName?: string): string {
|
|
17
|
-
return getRuntimeModelsConfigPath(config, agentName);
|
|
18
|
-
}
|
|
7
|
+
export type ResponseSafetyMode = "off" | "balanced" | "strict";
|
|
19
8
|
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
9
|
+
export type AppConfig = {
|
|
10
|
+
printSteps?: boolean;
|
|
11
|
+
maxSteps?: number;
|
|
12
|
+
promptTemplates?: PromptTemplates;
|
|
13
|
+
responseSafetyMode?: ResponseSafetyMode;
|
|
14
|
+
};
|
|
23
15
|
|
|
24
|
-
export function
|
|
25
|
-
|
|
16
|
+
export async function loadAppConfig(configPath: string): Promise<AppConfig> {
|
|
17
|
+
try {
|
|
18
|
+
const resource = await resolveKindResourceFile<{
|
|
19
|
+
printSteps?: unknown;
|
|
20
|
+
maxSteps?: unknown;
|
|
21
|
+
promptTemplates?: PromptTemplates;
|
|
22
|
+
responseSafetyMode?: unknown;
|
|
23
|
+
}>(configPath, {
|
|
24
|
+
baseDir: process.cwd(),
|
|
25
|
+
expectedApiVersion: "easynet.world/v1",
|
|
26
|
+
expectedKind: "AppConfig",
|
|
27
|
+
});
|
|
28
|
+
const spec = asObject(resource.spec) as {
|
|
29
|
+
printSteps?: unknown;
|
|
30
|
+
maxSteps?: unknown;
|
|
31
|
+
promptTemplates?: PromptTemplates;
|
|
32
|
+
responseSafetyMode?: unknown;
|
|
33
|
+
} | undefined;
|
|
34
|
+
const modeRaw = typeof spec?.responseSafetyMode === "string" ? spec.responseSafetyMode.trim().toLowerCase() : "";
|
|
35
|
+
const responseSafetyMode =
|
|
36
|
+
modeRaw === "off" || modeRaw === "balanced" || modeRaw === "strict"
|
|
37
|
+
? (modeRaw as ResponseSafetyMode)
|
|
38
|
+
: undefined;
|
|
39
|
+
const printSteps = typeof spec?.printSteps === "boolean" ? spec.printSteps : undefined;
|
|
40
|
+
const maxSteps =
|
|
41
|
+
typeof spec?.maxSteps === "number" && Number.isFinite(spec.maxSteps)
|
|
42
|
+
? Math.max(1, Math.floor(spec.maxSteps))
|
|
43
|
+
: undefined;
|
|
44
|
+
return {
|
|
45
|
+
printSteps,
|
|
46
|
+
maxSteps,
|
|
47
|
+
promptTemplates: spec?.promptTemplates,
|
|
48
|
+
responseSafetyMode,
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
26
53
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,67 +1,105 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* iTermBot: ReAct agent
|
|
3
|
+
* iTermBot: ReAct agent powered by @easynet/agent-runtime.
|
|
4
4
|
* Usage:
|
|
5
|
-
* npm start
|
|
6
|
-
*
|
|
7
|
-
* npm run deep # use Deep agent
|
|
8
|
-
* node dist/index.js react "Your question"
|
|
9
|
-
* node dist/index.js deep "Your question"
|
|
5
|
+
* npm start # interactive REPL
|
|
6
|
+
* node dist/index.js "Your question" # single-turn
|
|
10
7
|
*/
|
|
8
|
+
import * as readline from "node:readline";
|
|
9
|
+
import { marked } from "marked";
|
|
10
|
+
import { markedTerminal } from "marked-terminal";
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "@easynet/agent-
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
12
|
+
createAgentEventBus,
|
|
13
|
+
type AgentEventListener,
|
|
14
|
+
} from "@easynet/agent-common";
|
|
15
|
+
import { resolveConfigPath } from "@easynet/agent-common/config";
|
|
16
|
+
import { createAgentReactRuntime } from "@easynet/agent-runtime";
|
|
17
|
+
import { createAgentModel } from "@easynet/agent-model";
|
|
18
|
+
import { createAgentMemory } from "@easynet/agent-memory";
|
|
19
|
+
import { createAgentTools } from "@easynet/agent-tool";
|
|
20
|
+
import { createAgentSkills } from "@easynet/agent-skill";
|
|
21
|
+
import { runWithTextToolCallRecovery } from "./runtime/text-tool-call-recovery.js";
|
|
22
|
+
import { applyStartupPanelColors, restoreSessionColorsSync } from "./startup/colors.js";
|
|
23
|
+
import { buildSystemPrompt } from "./iterm/session-hint.js";
|
|
24
|
+
import {
|
|
25
|
+
detectDirectTargetPanelCommand,
|
|
26
|
+
tryHandleDirectTargetPanelCommand,
|
|
27
|
+
} from "./iterm/direct-command-router.js";
|
|
28
|
+
import { enforceTargetPanelExecutionPolicy, setTargetPanelHint } from "./iterm/target-panel-policy.js";
|
|
29
|
+
import { runLlmHealthCheck } from "./startup/diagnostics.js";
|
|
30
|
+
import { detectStartupTargetOs } from "./startup/os.js";
|
|
31
|
+
import { createChatProgressEventListener } from "./chat/progress.js";
|
|
32
|
+
import { renderStepLine } from "./chat/step-display.js";
|
|
33
|
+
import { enforceResponseSafetyWithMode } from "./chat/response-safety.js";
|
|
34
|
+
import { tryHandleBuiltinReadCommand } from "./chat/builtin-commands.js";
|
|
35
|
+
import {
|
|
36
|
+
clearStartupNoise,
|
|
37
|
+
formatLogPath,
|
|
38
|
+
getAppVersion,
|
|
39
|
+
printStartupBanner,
|
|
40
|
+
printStartupSummary,
|
|
41
|
+
runStartupStep,
|
|
42
|
+
StartupProgressRenderer,
|
|
43
|
+
} from "./startup/ui.js";
|
|
44
|
+
import { loadAppConfig, type ResponseSafetyMode } from "./config.js";
|
|
23
45
|
|
|
24
46
|
let startupColorSnapshots: Parameters<typeof restoreSessionColorsSync>[0] = [];
|
|
25
|
-
let
|
|
47
|
+
let unpatchTargetPanelPolicy: (() => void) | null = null;
|
|
48
|
+
type AgentRuntime = Awaited<ReturnType<typeof createAgentReactRuntime>>;
|
|
49
|
+
|
|
50
|
+
marked.use(markedTerminal({
|
|
51
|
+
code: false,
|
|
52
|
+
width: Math.max(80, process.stdout.columns ?? 100),
|
|
53
|
+
reflowText: true,
|
|
54
|
+
heading: (text: string, level: number) => `\n${"#".repeat(level)} ${text}\n`,
|
|
55
|
+
}) as never);
|
|
56
|
+
|
|
57
|
+
function renderMarkdownForTerminal(text: string): string {
|
|
58
|
+
try {
|
|
59
|
+
const rendered = marked.parse(text) as string;
|
|
60
|
+
return rendered.trimEnd();
|
|
61
|
+
} catch {
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let conversationTurn = 0;
|
|
67
|
+
|
|
68
|
+
function supportsColor(): boolean {
|
|
69
|
+
return Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function colorize(text: string, code: string): string {
|
|
73
|
+
if (!supportsColor()) return text;
|
|
74
|
+
return `${code}${text}\x1b[0m`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function divider(char = "─"): string {
|
|
78
|
+
const width = Math.max(50, Math.min(process.stdout.columns ?? 100, 120));
|
|
79
|
+
return char.repeat(width);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function printUserMessage(turn: number, text: string): void {
|
|
83
|
+
console.log(`\n${colorize(divider("─"), "\x1b[2m")}`);
|
|
84
|
+
console.log(colorize(`[Turn ${turn}] USER`, "\x1b[1m\x1b[36m"));
|
|
85
|
+
console.log(text);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function printBotMessage(text: string, turn = conversationTurn): void {
|
|
89
|
+
const rendered = renderMarkdownForTerminal(text);
|
|
90
|
+
console.log(colorize(`[Turn ${turn}] iTermBot`, "\x1b[1m\x1b[32m"));
|
|
91
|
+
console.log(rendered);
|
|
92
|
+
console.log(colorize(divider("─"), "\x1b[2m"));
|
|
93
|
+
}
|
|
26
94
|
|
|
27
95
|
function sanitizeSessionId(input: string): string {
|
|
28
96
|
return input.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
29
97
|
}
|
|
30
98
|
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
| {
|
|
36
|
-
agent?: Record<string, { memory?: { namespace_mode?: "thread" | "fixed"; namespace?: string } }>;
|
|
37
|
-
}
|
|
38
|
-
| undefined;
|
|
39
|
-
const agents = appConfig?.agent;
|
|
40
|
-
const defaultBaseNamespace = "user:itermbot";
|
|
41
|
-
const baseNamespace =
|
|
42
|
-
agents?.react?.memory?.namespace ??
|
|
43
|
-
agents?.deep?.memory?.namespace ??
|
|
44
|
-
defaultBaseNamespace;
|
|
45
|
-
const mode = agents?.react?.memory?.namespace_mode ?? agents?.deep?.memory?.namespace_mode ?? "thread";
|
|
46
|
-
const seed = args.chatSessionId ? sanitizeSessionId(args.chatSessionId) : `local_${Date.now().toString(36)}`;
|
|
47
|
-
const threadNamespace = mode === "fixed" ? baseNamespace : `${baseNamespace}:thread:${seed}`;
|
|
48
|
-
|
|
49
|
-
if (!ctx.config.app) ctx.config.app = {};
|
|
50
|
-
if (!ctx.config.app.agent) ctx.config.app.agent = {};
|
|
51
|
-
|
|
52
|
-
for (const agentName of ["react", "deep"]) {
|
|
53
|
-
const current = ctx.config.app.agent[agentName] ?? {};
|
|
54
|
-
const currentMemory = current.memory ?? {};
|
|
55
|
-
ctx.config.app.agent[agentName] = {
|
|
56
|
-
...current,
|
|
57
|
-
memory: {
|
|
58
|
-
...currentMemory,
|
|
59
|
-
namespace: threadNamespace,
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return threadNamespace;
|
|
99
|
+
function buildNamespace(chatSessionId: string | null): string {
|
|
100
|
+
const base = "user:itermbot";
|
|
101
|
+
const seed = chatSessionId ? sanitizeSessionId(chatSessionId) : `local_${Date.now().toString(36)}`;
|
|
102
|
+
return `${base}:thread:${seed}`;
|
|
65
103
|
}
|
|
66
104
|
|
|
67
105
|
function ensureItermEnvironmentOrExit(): void {
|
|
@@ -69,135 +107,307 @@ function ensureItermEnvironmentOrExit(): void {
|
|
|
69
107
|
const itermSessionId = process.env.ITERM_SESSION_ID?.trim();
|
|
70
108
|
const isIterm = termProgram === "iTerm.app" || Boolean(itermSessionId);
|
|
71
109
|
if (isIterm) return;
|
|
72
|
-
|
|
73
110
|
console.error("iTermBot startup blocked: iTerm2 environment is required.");
|
|
74
111
|
console.error("Please run iTermBot inside iTerm2 and try again.");
|
|
75
112
|
process.exit(1);
|
|
76
113
|
}
|
|
77
114
|
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
115
|
+
async function buildRuntime(
|
|
116
|
+
systemPrompt: string,
|
|
117
|
+
namespace: string,
|
|
118
|
+
startup: Awaited<ReturnType<typeof applyStartupPanelColors>>,
|
|
119
|
+
maxSteps?: number,
|
|
120
|
+
): Promise<AgentRuntime> {
|
|
121
|
+
createAgentEventBus();
|
|
122
|
+
await createAgentModel({
|
|
123
|
+
configPath: resolveConfigPath("config/model.yaml", process.cwd()),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const memoryConfigPath = resolveConfigPath("config/memory.yaml", process.cwd());
|
|
127
|
+
await createAgentMemory({ configPath: memoryConfigPath });
|
|
128
|
+
const tools = createAgentTools({
|
|
129
|
+
configFilePath: resolveConfigPath("config/tool.yaml", process.cwd()),
|
|
130
|
+
coreTools: { sandboxRoot: process.cwd() },
|
|
131
|
+
});
|
|
132
|
+
setTargetPanelHint({
|
|
133
|
+
windowId: startup.windowId ?? undefined,
|
|
134
|
+
tabIndex: startup.tabIndex ?? undefined,
|
|
135
|
+
sessionId: startup.targetSessionId ?? undefined,
|
|
136
|
+
});
|
|
137
|
+
if (unpatchTargetPanelPolicy) unpatchTargetPanelPolicy();
|
|
138
|
+
unpatchTargetPanelPolicy = enforceTargetPanelExecutionPolicy(tools);
|
|
139
|
+
await createAgentSkills(resolveConfigPath("config/skill.yaml", process.cwd()));
|
|
140
|
+
|
|
141
|
+
return createAgentReactRuntime({
|
|
142
|
+
systemPrompt,
|
|
143
|
+
namespace,
|
|
144
|
+
...(typeof maxSteps === "number" ? { maxSteps } : {}),
|
|
145
|
+
});
|
|
90
146
|
}
|
|
91
147
|
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
148
|
+
async function runInteractive(runtime: AgentRuntime): Promise<void> {
|
|
149
|
+
const rl = readline.createInterface({
|
|
150
|
+
input: process.stdin,
|
|
151
|
+
output: process.stdout,
|
|
152
|
+
terminal: true,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const ask = (): void => {
|
|
156
|
+
rl.question("\nyou> ", async (input) => {
|
|
157
|
+
const trimmed = input.trim();
|
|
158
|
+
if (trimmed === "exit" || trimmed === "quit") {
|
|
159
|
+
rl.close();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (!trimmed) {
|
|
163
|
+
ask();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
conversationTurn += 1;
|
|
167
|
+
const turn = conversationTurn;
|
|
168
|
+
printUserMessage(turn, trimmed);
|
|
169
|
+
try {
|
|
170
|
+
const builtin = tryHandleBuiltinReadCommand(trimmed, runtime);
|
|
171
|
+
if (builtin !== null) {
|
|
172
|
+
printBotMessage(builtin, turn);
|
|
173
|
+
ask();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const directOutput = await tryHandleDirectCommandWithProgress(trimmed, runtime);
|
|
178
|
+
if (directOutput !== null) {
|
|
179
|
+
printBotMessage(directOutput, turn);
|
|
180
|
+
ask();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const recoveryWriter = printSteps ? writeStepLine : () => {};
|
|
185
|
+
const result = await runWithTextToolCallRecovery(runtime, trimmed, recoveryWriter, {
|
|
186
|
+
windowId: startup.windowId ?? undefined,
|
|
187
|
+
tabIndex: startup.tabIndex ?? undefined,
|
|
188
|
+
sessionId: startup.targetSessionId ?? undefined,
|
|
189
|
+
});
|
|
190
|
+
const safeText = enforceResponseSafetyWithMode(result.text, responseSafetyMode, {
|
|
191
|
+
evidenceText: result.evidenceText,
|
|
192
|
+
targetOs: startupTargetOs,
|
|
193
|
+
});
|
|
194
|
+
if (safeText) printBotMessage(safeText, turn);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
197
|
+
}
|
|
198
|
+
ask();
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
rl.on("close", () => {
|
|
203
|
+
shutdown();
|
|
204
|
+
process.exit(0);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
ask();
|
|
95
208
|
}
|
|
96
209
|
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
210
|
+
function shutdown(): void {
|
|
211
|
+
if (unpatchTargetPanelPolicy) {
|
|
212
|
+
unpatchTargetPanelPolicy();
|
|
213
|
+
unpatchTargetPanelPolicy = null;
|
|
214
|
+
}
|
|
215
|
+
setTargetPanelHint(null);
|
|
216
|
+
if (startupColorSnapshots.length > 0) {
|
|
217
|
+
restoreSessionColorsSync(startupColorSnapshots);
|
|
105
218
|
}
|
|
106
219
|
}
|
|
107
220
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}): void {
|
|
111
|
-
console.log("--------------------------------------------------------------------------------------------------");
|
|
112
|
-
console.log(`Version : v${args.version}`);
|
|
113
|
-
console.log(`Workspace : ${process.cwd()}`);
|
|
114
|
-
console.log("");
|
|
115
|
-
console.log("Commands : list tools, list skills, exit");
|
|
116
|
-
console.log("--------------------------------------------------------------------------------------------------\n");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
ensureItermEnvironmentOrExit();
|
|
120
|
-
clearStartupNoise();
|
|
121
|
-
printStartupBanner();
|
|
122
|
-
|
|
123
|
-
runAppCli({
|
|
124
|
-
appName: "iTermBot",
|
|
125
|
-
createBotContext,
|
|
126
|
-
ui: {
|
|
127
|
-
assistantLabel: "iTermBot",
|
|
128
|
-
useColor: true,
|
|
129
|
-
echoUserQuestion: false,
|
|
130
|
-
processingSpinner: true,
|
|
131
|
-
processingText: false,
|
|
132
|
-
loadingText: "Loading config, LLM, memory, tools...",
|
|
133
|
-
loadingSpinner: true,
|
|
134
|
-
readyText: false,
|
|
135
|
-
interactiveIntro: false,
|
|
136
|
-
},
|
|
137
|
-
eventListener: createStructuredRunEventListener((line) => console.log(line)),
|
|
138
|
-
onShutdown: () => {
|
|
139
|
-
if (stopDynamicTargetRouting) {
|
|
140
|
-
stopDynamicTargetRouting();
|
|
141
|
-
stopDynamicTargetRouting = null;
|
|
142
|
-
}
|
|
143
|
-
if (startupColorSnapshots.length > 0) {
|
|
144
|
-
restoreSessionColorsSync(startupColorSnapshots);
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
onReady: async (ctx) => {
|
|
148
|
-
const startup = await applyStartupPanelColors();
|
|
149
|
-
const routingState: TargetRoutingState = {
|
|
150
|
-
chatSessionId: startup.chatSessionId,
|
|
151
|
-
chatWindowId: startup.windowId,
|
|
152
|
-
chatTabIndex: startup.tabIndex,
|
|
153
|
-
targetSessionId: startup.targetSessionId,
|
|
154
|
-
windowId: startup.windowId,
|
|
155
|
-
tabIndex: startup.tabIndex,
|
|
156
|
-
};
|
|
157
|
-
stopDynamicTargetRouting = enableDynamicTargetRouting(ctx, routingState);
|
|
158
|
-
configureThreadMemoryNamespace(ctx, {
|
|
159
|
-
chatSessionId: startup.chatSessionId,
|
|
160
|
-
});
|
|
221
|
+
process.on("SIGINT", () => { shutdown(); process.exit(0); });
|
|
222
|
+
process.on("SIGTERM", () => { shutdown(); process.exit(0); });
|
|
161
223
|
|
|
162
|
-
|
|
224
|
+
let startup: Awaited<ReturnType<typeof applyStartupPanelColors>> = {
|
|
225
|
+
chatSessionId: null,
|
|
226
|
+
targetSessionId: null,
|
|
227
|
+
windowId: null,
|
|
228
|
+
tabIndex: null,
|
|
229
|
+
colorSnapshots: [],
|
|
230
|
+
};
|
|
231
|
+
let responseSafetyMode: ResponseSafetyMode = "balanced";
|
|
232
|
+
let startupTargetOs = "";
|
|
233
|
+
let printSteps = false;
|
|
234
|
+
let maxSteps = 16;
|
|
163
235
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
236
|
+
function formatStepNumber(stepNumber: number): string {
|
|
237
|
+
return String(Math.max(0, stepNumber)).padStart(2, "0");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function writeStepLine(line: string): void {
|
|
241
|
+
console.log(renderStepLine(line));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function tryHandleDirectCommandWithProgress(
|
|
245
|
+
input: string,
|
|
246
|
+
runtime: AgentRuntime,
|
|
247
|
+
): Promise<string | null> {
|
|
248
|
+
const verbose = process.env.ITB_VERBOSE_DIRECT_ROUTE?.trim() === "1" || printSteps;
|
|
249
|
+
const startedAt = Date.now();
|
|
250
|
+
const writeStep = (line: string): void => console.log(renderStepLine(line));
|
|
251
|
+
if (verbose) {
|
|
252
|
+
writeStep("");
|
|
253
|
+
writeStep("=== Steps: direct command routing ===");
|
|
254
|
+
writeStep(`[${formatStepNumber(1)}] ▶ detect/translate direct command`);
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const directIntent = detectDirectTargetPanelCommand(input);
|
|
258
|
+
if (verbose && directIntent?.command) {
|
|
259
|
+
const reason = directIntent.reason === "raw_shell_command"
|
|
260
|
+
? "Because your input is an executable shell command, execute it directly in the target panel."
|
|
261
|
+
: "Because you explicitly requested direct terminal execution, execute it directly in the target panel.";
|
|
262
|
+
writeStep(` reason: ${reason}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const directOutput = await tryHandleDirectTargetPanelCommand(input, runtime, {
|
|
266
|
+
windowId: startup.windowId ?? undefined,
|
|
267
|
+
tabIndex: startup.tabIndex ?? undefined,
|
|
268
|
+
sessionId: startup.targetSessionId ?? undefined,
|
|
172
269
|
});
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
description:
|
|
180
|
-
typeof tool.description === "string" && tool.description.trim().length > 0
|
|
181
|
-
? tool.description.trim()
|
|
182
|
-
: "No description",
|
|
183
|
-
}))
|
|
184
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
185
|
-
console.log(`Available tools (${tools.length}):`);
|
|
186
|
-
for (const tool of tools) {
|
|
187
|
-
console.log(`- ${tool.name}: ${tool.description}`);
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
"list skills": (ctx) => {
|
|
191
|
-
const skills = (ctx.skillSet?.list() ?? [])
|
|
192
|
-
.map((skill) => ({
|
|
193
|
-
name: skill.name,
|
|
194
|
-
description: (skill.description ?? "").trim() || "No description",
|
|
195
|
-
}))
|
|
196
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
197
|
-
console.log(`Available skills (${skills.length}):`);
|
|
198
|
-
for (const skill of skills) {
|
|
199
|
-
console.log(`- ${skill.name}: ${skill.description}`);
|
|
270
|
+
if (directOutput === null) {
|
|
271
|
+
if (verbose) {
|
|
272
|
+
writeStep(`[${formatStepNumber(1)}] ✓ detect/translate direct command (fallback to agent)`);
|
|
273
|
+
writeStep(" progress 1/1");
|
|
274
|
+
writeStep("=== Steps complete: 1 step(s) ===");
|
|
275
|
+
writeStep("");
|
|
200
276
|
}
|
|
201
|
-
|
|
202
|
-
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
if (verbose) {
|
|
280
|
+
const elapsed = Date.now() - startedAt;
|
|
281
|
+
writeStep(`[${formatStepNumber(1)}] ✓ detect/translate direct command (${elapsed}ms)`);
|
|
282
|
+
writeStep(" progress 1/1");
|
|
283
|
+
writeStep(`=== Steps complete: 1 step(s), ${elapsed}ms ===`);
|
|
284
|
+
writeStep("");
|
|
285
|
+
}
|
|
286
|
+
return directOutput;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
289
|
+
if (verbose) {
|
|
290
|
+
const elapsed = Date.now() - startedAt;
|
|
291
|
+
writeStep(`[${formatStepNumber(1)}] ✖ detect/translate direct command`);
|
|
292
|
+
writeStep(` error: ${message}`);
|
|
293
|
+
writeStep(" progress 1/1");
|
|
294
|
+
writeStep(`=== Steps complete: 1 step(s), ${elapsed}ms ===`);
|
|
295
|
+
writeStep("");
|
|
296
|
+
}
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function main(): Promise<void> {
|
|
302
|
+
ensureItermEnvironmentOrExit();
|
|
303
|
+
clearStartupNoise();
|
|
304
|
+
printStartupBanner();
|
|
305
|
+
|
|
306
|
+
const progress = new StartupProgressRenderer();
|
|
307
|
+
|
|
308
|
+
// 1. Load app config (prompt templates only)
|
|
309
|
+
const appConfigPath = resolveConfigPath("config/app.yaml", process.cwd());
|
|
310
|
+
const appConfig = await runStartupStep(
|
|
311
|
+
progress,
|
|
312
|
+
"init app config",
|
|
313
|
+
() => loadAppConfig(appConfigPath),
|
|
314
|
+
() => formatLogPath(appConfigPath),
|
|
315
|
+
);
|
|
316
|
+
printSteps = appConfig.printSteps === true;
|
|
317
|
+
maxSteps = appConfig.maxSteps ?? 16;
|
|
318
|
+
responseSafetyMode = appConfig.responseSafetyMode ?? "balanced";
|
|
319
|
+
|
|
320
|
+
// 2. Identify iTerm panels and apply colors
|
|
321
|
+
startup = await runStartupStep(
|
|
322
|
+
progress,
|
|
323
|
+
"init iTerm panels",
|
|
324
|
+
() => applyStartupPanelColors(),
|
|
325
|
+
);
|
|
326
|
+
startupColorSnapshots = startup.colorSnapshots;
|
|
327
|
+
|
|
328
|
+
// 3. Detect target panel OS early so command translation can be OS-aware.
|
|
329
|
+
const osInfo = await runStartupStep(
|
|
330
|
+
progress,
|
|
331
|
+
"detect target os",
|
|
332
|
+
() => detectStartupTargetOs({
|
|
333
|
+
windowId: startup.windowId ?? undefined,
|
|
334
|
+
tabIndex: startup.tabIndex ?? undefined,
|
|
335
|
+
sessionId: startup.targetSessionId ?? undefined,
|
|
336
|
+
}),
|
|
337
|
+
(info) => `${info.os} (${info.source})`,
|
|
338
|
+
);
|
|
339
|
+
startupTargetOs = osInfo.os;
|
|
340
|
+
process.env.ITB_TARGET_OS = osInfo.os;
|
|
341
|
+
|
|
342
|
+
// 4. Build system prompt and namespace
|
|
343
|
+
const namespace = buildNamespace(startup.chatSessionId);
|
|
344
|
+
const systemPrompt = buildSystemPrompt(appConfig.promptTemplates, startup, "", {
|
|
345
|
+
targetOs: startupTargetOs,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// 5. Create agent runtime (model/memory/tools registered by application)
|
|
349
|
+
let runtime!: AgentRuntime;
|
|
350
|
+
await runStartupStep(
|
|
351
|
+
progress,
|
|
352
|
+
"init agent runtime",
|
|
353
|
+
async () => { runtime = await buildRuntime(systemPrompt, namespace, startup, maxSteps); },
|
|
354
|
+
() => `namespace=${namespace}`,
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
// 6. Subscribe to agent events for UI output
|
|
358
|
+
if (printSteps) {
|
|
359
|
+
const listener: AgentEventListener = createChatProgressEventListener(console.log);
|
|
360
|
+
runtime.events.subscribe(listener);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 7. LLM health check — call LLM directly, not through agent loop
|
|
364
|
+
await runStartupStep(
|
|
365
|
+
progress,
|
|
366
|
+
"check llm health",
|
|
367
|
+
() => runLlmHealthCheck(runtime),
|
|
368
|
+
(label) => label,
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
printStartupSummary({ version: getAppVersion(), responseSafetyMode, targetOs: startupTargetOs });
|
|
372
|
+
|
|
373
|
+
// 8. Single-turn or interactive REPL
|
|
374
|
+
const args = process.argv.slice(2);
|
|
375
|
+
const question = args.find((a) => !a.startsWith("-") && a !== "react" && a !== "deep");
|
|
376
|
+
if (question) {
|
|
377
|
+
conversationTurn += 1;
|
|
378
|
+
const turn = conversationTurn;
|
|
379
|
+
printUserMessage(turn, question);
|
|
380
|
+
const builtin = tryHandleBuiltinReadCommand(question, runtime);
|
|
381
|
+
if (builtin !== null) {
|
|
382
|
+
printBotMessage(builtin, turn);
|
|
383
|
+
shutdown();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const directOutput = await tryHandleDirectCommandWithProgress(question, runtime);
|
|
388
|
+
if (directOutput !== null) {
|
|
389
|
+
printBotMessage(directOutput, turn);
|
|
390
|
+
} else {
|
|
391
|
+
const recoveryWriter = printSteps ? writeStepLine : () => {};
|
|
392
|
+
const result = await runWithTextToolCallRecovery(runtime, question, recoveryWriter, {
|
|
393
|
+
windowId: startup.windowId ?? undefined,
|
|
394
|
+
tabIndex: startup.tabIndex ?? undefined,
|
|
395
|
+
sessionId: startup.targetSessionId ?? undefined,
|
|
396
|
+
});
|
|
397
|
+
const safeText = enforceResponseSafetyWithMode(result.text, responseSafetyMode, {
|
|
398
|
+
evidenceText: result.evidenceText,
|
|
399
|
+
targetOs: startupTargetOs,
|
|
400
|
+
});
|
|
401
|
+
if (safeText) printBotMessage(safeText, turn);
|
|
402
|
+
}
|
|
403
|
+
shutdown();
|
|
404
|
+
} else {
|
|
405
|
+
await runInteractive(runtime);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
main().catch((error) => {
|
|
410
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
411
|
+
shutdown();
|
|
412
|
+
process.exit(1);
|
|
203
413
|
});
|