@keyou007/agent-bot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +675 -0
- package/README.md +187 -0
- package/README.zh.md +187 -0
- package/SECURITY.md +13 -0
- package/config.example.yaml +56 -0
- package/dist/acp/AcpJsonRpcConnection.js +155 -0
- package/dist/acp/AcpJsonRpcConnection.js.map +1 -0
- package/dist/acp/AcpProcessManager.js +65 -0
- package/dist/acp/AcpProcessManager.js.map +1 -0
- package/dist/acp/AcpSessionManager.js +164 -0
- package/dist/acp/AcpSessionManager.js.map +1 -0
- package/dist/acp/acpTypes.js +10 -0
- package/dist/acp/acpTypes.js.map +1 -0
- package/dist/cli/FeishuAppConfiguration.js +349 -0
- package/dist/cli/FeishuAppConfiguration.js.map +1 -0
- package/dist/cli/FeishuAppRegistration.js +160 -0
- package/dist/cli/FeishuAppRegistration.js.map +1 -0
- package/dist/cli/Initializer.js +112 -0
- package/dist/cli/Initializer.js.map +1 -0
- package/dist/cli/LocalControlClient.js +47 -0
- package/dist/cli/LocalControlClient.js.map +1 -0
- package/dist/cli/LocalControlServer.js +66 -0
- package/dist/cli/LocalControlServer.js.map +1 -0
- package/dist/cli/SkillRegistry.js +126 -0
- package/dist/cli/SkillRegistry.js.map +1 -0
- package/dist/cli/controlProtocol.js +10 -0
- package/dist/cli/controlProtocol.js.map +1 -0
- package/dist/cli/taskChatRoute.js +23 -0
- package/dist/cli/taskChatRoute.js.map +1 -0
- package/dist/cli.js +616 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex/AppServerConnection.js +131 -0
- package/dist/codex/AppServerConnection.js.map +1 -0
- package/dist/codex/CodexEventMapper.js +296 -0
- package/dist/codex/CodexEventMapper.js.map +1 -0
- package/dist/codex/CodexLocalActivityDetector.js +124 -0
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -0
- package/dist/codex/CodexProcessManager.js +64 -0
- package/dist/codex/CodexProcessManager.js.map +1 -0
- package/dist/codex/CodexRuntime.js +825 -0
- package/dist/codex/CodexRuntime.js.map +1 -0
- package/dist/codex/ProjectlessWorkspace.js +74 -0
- package/dist/codex/ProjectlessWorkspace.js.map +1 -0
- package/dist/codex/protocol.js +10 -0
- package/dist/codex/protocol.js.map +1 -0
- package/dist/commands/CommandRouter.js +176 -0
- package/dist/commands/CommandRouter.js.map +1 -0
- package/dist/commands/commandTypes.js +2 -0
- package/dist/commands/commandTypes.js.map +1 -0
- package/dist/config/loadConfig.js +107 -0
- package/dist/config/loadConfig.js.map +1 -0
- package/dist/config/paths.js +32 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/schema.js +38 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/console/ConsoleConnector.js +39 -0
- package/dist/console/ConsoleConnector.js.map +1 -0
- package/dist/console/ConsoleTurnPresenter.js +60 -0
- package/dist/console/ConsoleTurnPresenter.js.map +1 -0
- package/dist/feishu/CardRenderer.js +972 -0
- package/dist/feishu/CardRenderer.js.map +1 -0
- package/dist/feishu/CardUpdateScheduler.js +148 -0
- package/dist/feishu/CardUpdateScheduler.js.map +1 -0
- package/dist/feishu/ConsoleFeishuClient.js +18 -0
- package/dist/feishu/ConsoleFeishuClient.js.map +1 -0
- package/dist/feishu/FeishuConnector.js +257 -0
- package/dist/feishu/FeishuConnector.js.map +1 -0
- package/dist/feishu/FeishuMarkdown.js +37 -0
- package/dist/feishu/FeishuMarkdown.js.map +1 -0
- package/dist/feishu/FeishuMessageClient.js +459 -0
- package/dist/feishu/FeishuMessageClient.js.map +1 -0
- package/dist/feishu/FeishuTurnPresenter.js +425 -0
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -0
- package/dist/feishu/GroupAvatarGenerator.js +86 -0
- package/dist/feishu/GroupAvatarGenerator.js.map +1 -0
- package/dist/feishu/LocalCardImage.js +13 -0
- package/dist/feishu/LocalCardImage.js.map +1 -0
- package/dist/feishu/LocalImageMarkdown.js +89 -0
- package/dist/feishu/LocalImageMarkdown.js.map +1 -0
- package/dist/feishu/contextKey.js +12 -0
- package/dist/feishu/contextKey.js.map +1 -0
- package/dist/feishu/transport.js +14 -0
- package/dist/feishu/transport.js.map +1 -0
- package/dist/feishu/types.js +2 -0
- package/dist/feishu/types.js.map +1 -0
- package/dist/index.js +213 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/logger.js +22 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/presentation/OutboundRouter.js +127 -0
- package/dist/presentation/OutboundRouter.js.map +1 -0
- package/dist/presentation/TurnStateReducer.js +307 -0
- package/dist/presentation/TurnStateReducer.js.map +1 -0
- package/dist/presentation/splitMarkdown.js +341 -0
- package/dist/presentation/splitMarkdown.js.map +1 -0
- package/dist/presentation/turnViewTypes.js +2 -0
- package/dist/presentation/turnViewTypes.js.map +1 -0
- package/dist/proxy/ProxySessionController.js +2819 -0
- package/dist/proxy/ProxySessionController.js.map +1 -0
- package/dist/runtime/AcpRuntimeAdapter.js +271 -0
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -0
- package/dist/runtime/AgentRuntimeRegistry.js +18 -0
- package/dist/runtime/AgentRuntimeRegistry.js.map +1 -0
- package/dist/runtime/agentEnvironment.js +11 -0
- package/dist/runtime/agentEnvironment.js.map +1 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/startup/SessionMetadataHydrator.js +46 -0
- package/dist/startup/SessionMetadataHydrator.js.map +1 -0
- package/dist/startup/StartupNotifier.js +78 -0
- package/dist/startup/StartupNotifier.js.map +1 -0
- package/dist/startup/startFeishu.js +6 -0
- package/dist/startup/startFeishu.js.map +1 -0
- package/dist/state/StateStore.js +893 -0
- package/dist/state/StateStore.js.map +1 -0
- package/dist/state/migrations.js +129 -0
- package/dist/state/migrations.js.map +1 -0
- package/dist/supervision/SafeRestartNotifier.js +116 -0
- package/dist/supervision/SafeRestartNotifier.js.map +1 -0
- package/dist/supervision/SafeRestartScheduler.js +123 -0
- package/dist/supervision/SafeRestartScheduler.js.map +1 -0
- package/dist/supervision/replacementSupervisor.js +8 -0
- package/dist/supervision/replacementSupervisor.js.map +1 -0
- package/dist/supervision/restartPolicy.js +16 -0
- package/dist/supervision/restartPolicy.js.map +1 -0
- package/dist/supervisor.js +94 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/utils/executeShellCommand.js +76 -0
- package/dist/utils/executeShellCommand.js.map +1 -0
- package/dist/utils/id.js +5 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/markdown.js +29 -0
- package/dist/utils/markdown.js.map +1 -0
- package/dist/utils/spawnCommand.js +21 -0
- package/dist/utils/spawnCommand.js.map +1 -0
- package/dist/utils/taskTitle.js +10 -0
- package/dist/utils/taskTitle.js.map +1 -0
- package/docs/technical-reference.md +294 -0
- package/docs/technical-reference.zh.md +294 -0
- package/npm-shrinkwrap.json +4525 -0
- package/package.json +87 -0
- package/skills/agent-bot/SKILL.md +141 -0
- package/skills/agent-bot/agents/openai.yaml +4 -0
- package/src/acp/AcpJsonRpcConnection.ts +212 -0
- package/src/acp/AcpProcessManager.ts +87 -0
- package/src/acp/AcpSessionManager.ts +227 -0
- package/src/acp/acpTypes.ts +99 -0
- package/src/cli/FeishuAppConfiguration.ts +473 -0
- package/src/cli/FeishuAppRegistration.ts +207 -0
- package/src/cli/Initializer.ts +159 -0
- package/src/cli/LocalControlClient.ts +48 -0
- package/src/cli/LocalControlServer.ts +67 -0
- package/src/cli/SkillRegistry.ts +151 -0
- package/src/cli/controlProtocol.ts +34 -0
- package/src/cli/taskChatRoute.ts +31 -0
- package/src/cli.ts +630 -0
- package/src/codex/AppServerConnection.ts +159 -0
- package/src/codex/CodexEventMapper.ts +351 -0
- package/src/codex/CodexLocalActivityDetector.ts +148 -0
- package/src/codex/CodexProcessManager.ts +63 -0
- package/src/codex/CodexRuntime.ts +1025 -0
- package/src/codex/ProjectlessWorkspace.ts +90 -0
- package/src/codex/protocol.ts +37 -0
- package/src/commands/CommandRouter.ts +179 -0
- package/src/commands/commandTypes.ts +28 -0
- package/src/config/loadConfig.ts +115 -0
- package/src/config/paths.ts +37 -0
- package/src/config/schema.ts +46 -0
- package/src/console/ConsoleConnector.ts +40 -0
- package/src/console/ConsoleTurnPresenter.ts +64 -0
- package/src/feishu/CardRenderer.ts +1157 -0
- package/src/feishu/CardUpdateScheduler.ts +165 -0
- package/src/feishu/ConsoleFeishuClient.ts +22 -0
- package/src/feishu/FeishuConnector.ts +277 -0
- package/src/feishu/FeishuMarkdown.ts +47 -0
- package/src/feishu/FeishuMessageClient.ts +649 -0
- package/src/feishu/FeishuTurnPresenter.ts +539 -0
- package/src/feishu/GroupAvatarGenerator.ts +102 -0
- package/src/feishu/LocalCardImage.ts +13 -0
- package/src/feishu/LocalImageMarkdown.ts +92 -0
- package/src/feishu/contextKey.ts +14 -0
- package/src/feishu/transport.ts +24 -0
- package/src/feishu/types.ts +83 -0
- package/src/index.ts +228 -0
- package/src/logging/logger.ts +28 -0
- package/src/presentation/OutboundRouter.ts +205 -0
- package/src/presentation/TurnStateReducer.ts +341 -0
- package/src/presentation/splitMarkdown.ts +358 -0
- package/src/presentation/turnViewTypes.ts +54 -0
- package/src/proxy/ProxySessionController.ts +3311 -0
- package/src/runtime/AcpRuntimeAdapter.ts +324 -0
- package/src/runtime/AgentRuntimeRegistry.ts +20 -0
- package/src/runtime/agentEnvironment.ts +14 -0
- package/src/runtime/types.ts +215 -0
- package/src/startup/SessionMetadataHydrator.ts +46 -0
- package/src/startup/StartupNotifier.ts +92 -0
- package/src/startup/startFeishu.ts +19 -0
- package/src/state/StateStore.ts +1249 -0
- package/src/state/migrations.ts +128 -0
- package/src/supervision/SafeRestartNotifier.ts +122 -0
- package/src/supervision/SafeRestartScheduler.ts +149 -0
- package/src/supervision/replacementSupervisor.ts +10 -0
- package/src/supervision/restartPolicy.ts +19 -0
- package/src/supervisor.ts +106 -0
- package/src/utils/executeShellCommand.ts +97 -0
- package/src/utils/id.ts +5 -0
- package/src/utils/markdown.ts +25 -0
- package/src/utils/spawnCommand.ts +35 -0
- package/src/utils/taskTitle.ts +9 -0
- package/tsconfig.json +19 -0
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@keyou007/agent-bot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Using Codex in Lark/Feishu feels just like using native Codex.",
|
|
6
|
+
"license": "GPL-3.0-only",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/keyou/agent-bot.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/keyou/agent-bot#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/keyou/agent-bot/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"agent",
|
|
17
|
+
"codex",
|
|
18
|
+
"acp",
|
|
19
|
+
"feishu",
|
|
20
|
+
"lark",
|
|
21
|
+
"cli"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"agent-bot": "dist/cli.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
".env.example",
|
|
28
|
+
"CHANGELOG.md",
|
|
29
|
+
"config.example.yaml",
|
|
30
|
+
"dist/",
|
|
31
|
+
"docs/technical-reference.md",
|
|
32
|
+
"docs/technical-reference.zh.md",
|
|
33
|
+
"npm-shrinkwrap.json",
|
|
34
|
+
"README.zh.md",
|
|
35
|
+
"SECURITY.md",
|
|
36
|
+
"skills/agent-bot/",
|
|
37
|
+
"src/",
|
|
38
|
+
"tsconfig.json"
|
|
39
|
+
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public",
|
|
42
|
+
"registry": "https://registry.npmjs.org/"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"dev": "tsx src/index.ts",
|
|
46
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
47
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
48
|
+
"start": "node dist/supervisor.js",
|
|
49
|
+
"start:once": "node dist/index.js",
|
|
50
|
+
"cli": "node dist/cli.js",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json",
|
|
53
|
+
"verify": "npm run typecheck && npm test",
|
|
54
|
+
"prepack": "npm run build && node scripts/check-package.mjs",
|
|
55
|
+
"prepublishOnly": "npm run verify",
|
|
56
|
+
"package:check": "npm run prepack",
|
|
57
|
+
"package:smoke": "node scripts/smoke-package.mjs"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@agentclientprotocol/sdk": "1.2.1",
|
|
61
|
+
"@larksuiteoapi/node-sdk": "1.56.1",
|
|
62
|
+
"@resvg/resvg-js": "2.6.2",
|
|
63
|
+
"better-sqlite3": "12.11.1",
|
|
64
|
+
"dotenv": "17.4.2",
|
|
65
|
+
"pino": "10.3.1",
|
|
66
|
+
"qrcode-terminal": "0.12.0",
|
|
67
|
+
"yaml": "2.9.0",
|
|
68
|
+
"zod": "4.4.3"
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@types/better-sqlite3": "7.6.13",
|
|
72
|
+
"@types/node": "22.18.11",
|
|
73
|
+
"@types/qrcode-terminal": "0.12.2",
|
|
74
|
+
"eslint": "10.6.0",
|
|
75
|
+
"prettier": "3.9.5",
|
|
76
|
+
"tsx": "4.23.0",
|
|
77
|
+
"typescript": "7.0.2",
|
|
78
|
+
"vitest": "4.1.10"
|
|
79
|
+
},
|
|
80
|
+
"overrides": {
|
|
81
|
+
"axios": "1.18.1"
|
|
82
|
+
},
|
|
83
|
+
"engines": {
|
|
84
|
+
"node": ">=22"
|
|
85
|
+
},
|
|
86
|
+
"packageManager": "npm@11.12.1"
|
|
87
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-bot
|
|
3
|
+
description: Initialize Agent Bot, detect and work safely inside its runtime, and manage the local service and its Codex or ACP sessions through the agent-bot CLI. Use when an agent needs to adapt its behavior while running under Agent Bot, or when a user asks to initialize, inspect, start, safely restart, immediately restart, or stop Agent Bot; open its console UI; list, inspect, stop, rename, or send a prompt to tasks; check a scheduled restart; or troubleshoot the bot without manually killing worker processes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent Bot
|
|
7
|
+
|
|
8
|
+
Use the `agent-bot` command as the supported control surface for Agent Bot. Prefer its structured status and control operations over direct process manipulation or database edits.
|
|
9
|
+
|
|
10
|
+
## Detect the Agent Bot runtime
|
|
11
|
+
|
|
12
|
+
Agent Bot injects `AGENT_BOT=1` into every Codex and ACP agent process it starts. Test for the exact value `1`:
|
|
13
|
+
|
|
14
|
+
```powershell
|
|
15
|
+
if ($env:AGENT_BOT -eq "1") { "running in Agent Bot" }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
if [ "${AGENT_BOT:-}" = "1" ]; then echo "running in Agent Bot"; fi
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
const runningInAgentBot = process.env.AGENT_BOT === "1";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
running_in_agent_bot = os.environ.get("AGENT_BOT") == "1"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Treat this environment marker as authoritative. Do not infer Agent Bot execution merely because the CLI is installed, `~/.agent-bot` exists, or an Agent Bot process is running elsewhere. Configured agent environment variables cannot override the marker. Child processes normally inherit it, so the marker identifies the Agent Bot-started process tree unless a child explicitly replaces its environment.
|
|
33
|
+
|
|
34
|
+
## Behave safely inside Agent Bot
|
|
35
|
+
|
|
36
|
+
When `AGENT_BOT=1`:
|
|
37
|
+
|
|
38
|
+
- Do not kill Agent Bot, its supervisor, or agent workers directly. For code changes that must be loaded by the running service, finish verification first and schedule a safe restart; never use an immediate restart from an active hosted task unless the user explicitly accepts interruption.
|
|
39
|
+
- Keep repository contents limited to source and examples. Read user configuration and runtime state from the root selected by `AGENT_BOT_HOME`, defaulting to `~/.agent-bot`.
|
|
40
|
+
|
|
41
|
+
## Initialize after installation
|
|
42
|
+
|
|
43
|
+
Run initialization once after installing or linking the CLI:
|
|
44
|
+
|
|
45
|
+
```powershell
|
|
46
|
+
agent-bot init
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Initialization creates the Agent Bot home, `config.yaml`, `.env`, `data/`, and `logs/` from the bundled examples. If Feishu credentials are absent, it starts Feishu one-click app registration, prints an authorization URL and QR code, waits for the user to approve the request, and stores the returned app ID and secret in `.env`. It then audits the app's currently published tenant permissions, event subscriptions, and callbacks. Core and optional missing items produce separate incremental-configuration URLs so optional approval cannot block core setup.
|
|
50
|
+
|
|
51
|
+
When running `init` on the user's behalf, relay every exact authorization or incremental-configuration URL and let the user complete it in their own browser. The command waits only for core message receipt and delivery capabilities. Optional capabilities such as group creation, group-title synchronization, reactions, images, and card actions do not block initialization; relay the final affected-feature warnings when they remain unavailable. Neither URL contains the app secret, and the secret itself is never printed.
|
|
52
|
+
|
|
53
|
+
Existing complete Feishu credentials are preserved but still audited and repaired when needed. Use `agent-bot init --skip-feishu` only for console-only initialization because it skips both registration and configuration auditing. Use `agent-bot init --reconfigure-feishu` only when the user explicitly wants to replace existing credentials. The command respects `AGENT_BOT_HOME`, `AGENT_BOT_CONFIG`, and `--config <path>`. Use `agent-bot init --json` when structured final output helps.
|
|
54
|
+
|
|
55
|
+
## Start with inspection
|
|
56
|
+
|
|
57
|
+
Verify the CLI and current service state before changing anything:
|
|
58
|
+
|
|
59
|
+
```powershell
|
|
60
|
+
agent-bot --help
|
|
61
|
+
agent-bot server status
|
|
62
|
+
agent-bot task list
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Add `--json` to status and list commands when machine-readable output helps. If `agent-bot` is unavailable, report that the CLI is not installed or linked instead of guessing process state.
|
|
66
|
+
|
|
67
|
+
By default Agent Bot stores user-owned configuration and runtime state in `~/.agent-bot`: config at `~/.agent-bot/config.yaml`, environment variables at `~/.agent-bot/.env`, SQLite state at `~/.agent-bot/data/agent-bot.sqlite`, logs at `~/.agent-bot/logs/agent-bot.log`, and inbound image cache next to the SQLite database. `AGENT_BOT_HOME` changes this root. Use `--config <path>` only when controlling a non-default configuration.
|
|
68
|
+
|
|
69
|
+
## Manage the service
|
|
70
|
+
|
|
71
|
+
Use these commands:
|
|
72
|
+
|
|
73
|
+
```powershell
|
|
74
|
+
agent-bot server start
|
|
75
|
+
agent-bot server status
|
|
76
|
+
agent-bot server restart --reason "reason for restart"
|
|
77
|
+
agent-bot server stop
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Treat `server restart` as the normal restart path. It schedules a safe restart and waits for active tasks, pending final-message delivery, and a quiet inbound-message window. The first safe-restart status card is delayed by three seconds so the active task's final response can usually be delivered first; this presentation delay does not postpone scheduler polling.
|
|
81
|
+
|
|
82
|
+
Use `agent-bot server restart --immediate` only when the user explicitly requests an immediate restart or accepts interruption. Never use `taskkill`, `Stop-Process`, or equivalent commands for routine Agent Bot restart management.
|
|
83
|
+
|
|
84
|
+
`agent-bot console` opens the console UI only when the service is stopped. Do not use `--force` while the service is live unless the user explicitly accepts concurrent state access.
|
|
85
|
+
|
|
86
|
+
## Manage tasks
|
|
87
|
+
|
|
88
|
+
List tasks before resolving a numeric reference because task numbers follow the current list order:
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
agent-bot task list
|
|
92
|
+
agent-bot task chat <number-or-task-id> [--json]
|
|
93
|
+
agent-bot task status <number-or-task-id>
|
|
94
|
+
agent-bot task stop <number-or-task-id>
|
|
95
|
+
agent-bot task title <number-or-task-id> <new-title>
|
|
96
|
+
agent-bot task prompt <number-or-task-id> <prompt>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Task IDs may be supplied in full or as an unambiguous prefix. Use `task stop` to ask the running worker to send the Codex Interrupt signal; leave child-process lifecycle management to Codex.
|
|
100
|
+
|
|
101
|
+
Use `task chat <number-or-task-id>` to read the Feishu chat ID associated with a task from local routing state. Plain output is only the chat ID for scripting. Add `--json` to include the stable task ID, complete context key, and the thread ID for a topic-bound task. This read-only command does not require the Agent Bot server to be running.
|
|
102
|
+
|
|
103
|
+
Use `task prompt` to send input to a specific task without changing any chat's current task. The bot first posts the Prompt text to the task's existing Feishu chat, thread, or private-chat route, then submits it to the task. If posting fails, the task is not started or steered. The thinking card and final response continue to the same route; the command line only reports whether the Prompt was accepted.
|
|
104
|
+
|
|
105
|
+
Use `/queue <prompt>` in Feishu to persist a separate follow-up Prompt instead of steering the active turn. Queued Prompts run in FIFO order after the current turn completes and survive Agent Bot restarts. `/nosteer <prompt>` is an equivalent compatibility spelling.
|
|
106
|
+
|
|
107
|
+
For a long-running objective in the active Feishu task, use `/goal <objective>`. Use `/goal` to inspect it, `/goal pause` or `/goal resume` to control automatic continuation, `/goal edit <objective>` to revise it, and `/goal clear` to remove it. Stopping a Goal turn through Agent Bot also pauses the Goal before sending Interrupt so it does not immediately continue.
|
|
108
|
+
|
|
109
|
+
Use `/newgroup [title]` in Feishu to create a private group named `[agent] [project directory] <title>`, invite the command sender, and immediately create and bind a new task in that group. The new task inherits the source chat's current task project directory, model, reasoning effort, and permission mode without interrupting or switching the source task. When the source chat has no current task, use the selected agent and its runtime defaults. An explicit title becomes both the group suffix and the task title. When the title is omitted, use task title `新任务` and group name `[agent] [project directory] 新任务 (mm-dd)`; an explicit title never gets a date suffix. Every new group gets a deterministic scheme-C Identicon avatar: Latin project names display their first word, Chinese names display up to their first four characters, and the normalized full project path hashes into the palette and symmetric node pattern. Projectless groups use the title as the stable seed. Replace the user's home-directory prefix with `~`. The bracketed project-directory value is limited to 15 characters. Longer paths keep the final two directories when they fit, otherwise keep the final directory, truncating the tail only when the final directory itself is overlong. Use `\` on Windows and `/` on macOS/Linux. Do not automatically send a Sessions or Status card to the new group. A later `/new --dir <cwd>` or `/new --nodir` in the group may explicitly replace the inherited project shape.
|
|
110
|
+
|
|
111
|
+
Group names created by `/newgroup` and `/forkgroup` are capped at 60 displayed characters. When the generated name would exceed that limit, truncate only the title portion in the Feishu group name; preserve the underlying Agent Bot task title.
|
|
112
|
+
|
|
113
|
+
Use `/forkgroup [title]` in Feishu to fork the current position into a newly created private group. In a Feishu thread, use the thread's original anchor turn when the thread has no bound task or its bound task has not completed a turn of its own. Once the bound thread task has completed a turn, use its latest locally persisted completed turn; exclude any newer active turn without interrupting it. Resolve this source before generic thread initialization so an unbound thread does not create an intermediate task. Outside a thread, fork the current Codex task's latest available completed turn. Agent Bot invites the command sender and binds the fork as the new group's current task, but does not automatically send a Sessions or Status card there. An explicit title is used directly. Without an explicit title, both the forked task and group name use the same persistent `source title(分支 N)` sequence as `/fork`. `/forkgroup` does not add a date suffix.
|
|
114
|
+
|
|
115
|
+
Use `/new [title] --nodir` to force a new Codex Projectless task even when the current task belongs to a project. `--nodir` and `--dir <cwd>` are mutually exclusive; omitting both preserves the normal project-shape inheritance behavior.
|
|
116
|
+
|
|
117
|
+
Every task in the Feishu `/sessions` card has a `New` action. It inherits the source task's project directory, model, reasoning effort, and permission mode, creates a new task, and switches the current chat to it without stopping or forking the source task.
|
|
118
|
+
|
|
119
|
+
Use `/fork [number-or-task-id]` in Feishu to branch from the current or specified Codex task and switch to the new branch. If the source task is running, Agent Bot forks from its latest completed turn and leaves the active turn running; it rejects only when the task has no completed turn yet.
|
|
120
|
+
|
|
121
|
+
Status cards include a `Refresh` action that reads the latest task state and updates the same card in place without switching tasks or sending another card.
|
|
122
|
+
|
|
123
|
+
Use `/model` in Feishu to show an interactive Card 2.0 model selector for the current task. Each non-current model has a blue link-style `Switch` action. After selecting a model, the same card advances to that model's reasoning-mode selector; the selector lists only mode names, has a `Back` action, and applies a selected mode from the next request. `/thinking` opens the reasoning selector directly. `/model <name>` and `/thinking <level>` remain available for direct text-based selection.
|
|
124
|
+
|
|
125
|
+
Keep action labels placed to the right of card content or at the bottom of a card in English. Buttons above the main card content, such as activity-history navigation, may remain in Chinese.
|
|
126
|
+
|
|
127
|
+
Messages whose trimmed text starts with `/` are always parsed as Agent Bot commands. Unknown slash commands are reported to the user with a `/help` hint and must never fall through to the model, including slash-prefixed messages that also contain images.
|
|
128
|
+
|
|
129
|
+
When a group body has a current task, renaming the Feishu group to `[agent-name] new title` also renames that current task if the prefix matches the task's configured agent. Malformed names, agent mismatches, empty groups, and thread-specific tasks are ignored.
|
|
130
|
+
|
|
131
|
+
Use `--context <key>` or `--status <status>` to narrow `task list`. Use `--config <path>` before the command when controlling a non-default Agent Bot configuration.
|
|
132
|
+
|
|
133
|
+
## Apply code changes safely
|
|
134
|
+
|
|
135
|
+
After changing Agent Bot code, run the relevant tests, typecheck, and build. If a running service must pick up the result, schedule:
|
|
136
|
+
|
|
137
|
+
```powershell
|
|
138
|
+
agent-bot server restart --reason "brief description of the completed change"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Do not replace the worker immediately while a task is active. A later incoming task must not cancel the pending safe restart; allow the scheduler to wait until the service becomes safely idle again.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
4
|
+
import type { Logger } from "pino";
|
|
5
|
+
import {
|
|
6
|
+
isJsonRpcNotification,
|
|
7
|
+
isJsonRpcRequest,
|
|
8
|
+
isJsonRpcResponse,
|
|
9
|
+
type JsonRpcMessage,
|
|
10
|
+
type JsonRpcResponse,
|
|
11
|
+
type JsonValue,
|
|
12
|
+
} from "./acpTypes.js";
|
|
13
|
+
|
|
14
|
+
type ClientMethodHandler = (params: JsonValue | undefined) => Promise<JsonValue>;
|
|
15
|
+
|
|
16
|
+
interface PendingRequest {
|
|
17
|
+
resolve: (value: JsonValue) => void;
|
|
18
|
+
reject: (error: Error) => void;
|
|
19
|
+
method: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AcpJsonRpcConnectionEvents {
|
|
23
|
+
notification: [method: string, params: JsonValue | undefined];
|
|
24
|
+
request: [method: string, params: JsonValue | undefined];
|
|
25
|
+
close: [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class AcpJsonRpcConnection extends EventEmitter {
|
|
29
|
+
private nextId = 1;
|
|
30
|
+
private readonly pending = new Map<string | number, PendingRequest>();
|
|
31
|
+
private readonly handlers = new Map<string, ClientMethodHandler>();
|
|
32
|
+
private closed = false;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
private readonly process: ChildProcessWithoutNullStreams,
|
|
36
|
+
private readonly logger: Logger,
|
|
37
|
+
) {
|
|
38
|
+
super();
|
|
39
|
+
this.bindOutput();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
on<K extends keyof AcpJsonRpcConnectionEvents>(
|
|
43
|
+
eventName: K,
|
|
44
|
+
listener: (...args: AcpJsonRpcConnectionEvents[K]) => void,
|
|
45
|
+
): this {
|
|
46
|
+
return super.on(eventName, listener);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
registerHandler(method: string, handler: ClientMethodHandler): void {
|
|
50
|
+
this.handlers.set(method, handler);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async request<T = JsonValue>(
|
|
54
|
+
method: string,
|
|
55
|
+
params?: JsonValue,
|
|
56
|
+
timeoutMs = 0,
|
|
57
|
+
): Promise<T> {
|
|
58
|
+
if (this.closed) {
|
|
59
|
+
throw new Error(`Cannot send ACP request after connection closed: ${method}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const id = this.nextId++;
|
|
63
|
+
const responsePromise = new Promise<JsonValue>((resolve, reject) => {
|
|
64
|
+
this.pending.set(id, { resolve, reject, method });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.write({
|
|
68
|
+
jsonrpc: "2.0",
|
|
69
|
+
id,
|
|
70
|
+
method,
|
|
71
|
+
params,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (timeoutMs <= 0) {
|
|
75
|
+
return (await responsePromise) as T;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (await Promise.race([
|
|
79
|
+
responsePromise,
|
|
80
|
+
new Promise<JsonValue>((_resolve, reject) => {
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
this.pending.delete(id);
|
|
83
|
+
reject(new Error(`ACP request timed out: ${method}`));
|
|
84
|
+
}, timeoutMs);
|
|
85
|
+
}),
|
|
86
|
+
])) as T;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
notify(method: string, params?: JsonValue): void {
|
|
90
|
+
if (this.closed) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.write({
|
|
95
|
+
jsonrpc: "2.0",
|
|
96
|
+
method,
|
|
97
|
+
params,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
close(): void {
|
|
102
|
+
this.closed = true;
|
|
103
|
+
for (const pending of this.pending.values()) {
|
|
104
|
+
pending.reject(new Error("ACP connection closed."));
|
|
105
|
+
}
|
|
106
|
+
this.pending.clear();
|
|
107
|
+
this.emit("close");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private bindOutput(): void {
|
|
111
|
+
const reader = readline.createInterface({
|
|
112
|
+
input: this.process.stdout,
|
|
113
|
+
crlfDelay: Infinity,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
reader.on("line", (line) => {
|
|
117
|
+
if (!line.trim()) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.handleLine(line);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.process.once("close", () => this.close());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private handleLine(line: string): void {
|
|
128
|
+
let message: JsonRpcMessage;
|
|
129
|
+
try {
|
|
130
|
+
message = JSON.parse(line) as JsonRpcMessage;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.logger.warn({ line, error }, "Ignoring non-JSON ACP stdout line.");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (isJsonRpcResponse(message)) {
|
|
137
|
+
this.handleResponse(message);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (isJsonRpcRequest(message)) {
|
|
142
|
+
void this.handleRequest(message.id, message.method, message.params);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (isJsonRpcNotification(message)) {
|
|
147
|
+
this.emit("notification", message.method, message.params);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.logger.warn({ message }, "Ignoring unknown ACP message shape.");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private handleResponse(message: JsonRpcResponse): void {
|
|
155
|
+
const pending = this.pending.get(message.id);
|
|
156
|
+
if (!pending) {
|
|
157
|
+
this.logger.warn({ message }, "Received ACP response for unknown request.");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this.pending.delete(message.id);
|
|
162
|
+
if ("error" in message) {
|
|
163
|
+
pending.reject(new Error(`${pending.method} failed: ${message.error.message}`));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
pending.resolve(message.result);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private async handleRequest(
|
|
171
|
+
id: string | number,
|
|
172
|
+
method: string,
|
|
173
|
+
params: JsonValue | undefined,
|
|
174
|
+
): Promise<void> {
|
|
175
|
+
this.emit("request", method, params);
|
|
176
|
+
const handler = this.handlers.get(method);
|
|
177
|
+
if (!handler) {
|
|
178
|
+
this.write({
|
|
179
|
+
jsonrpc: "2.0",
|
|
180
|
+
id,
|
|
181
|
+
error: {
|
|
182
|
+
code: -32601,
|
|
183
|
+
message: `Client method is not implemented by Agent Bot: ${method}`,
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const result = await handler(params);
|
|
191
|
+
this.write({
|
|
192
|
+
jsonrpc: "2.0",
|
|
193
|
+
id,
|
|
194
|
+
result,
|
|
195
|
+
});
|
|
196
|
+
} catch (error) {
|
|
197
|
+
this.write({
|
|
198
|
+
jsonrpc: "2.0",
|
|
199
|
+
id,
|
|
200
|
+
error: {
|
|
201
|
+
code: -32000,
|
|
202
|
+
message: error instanceof Error ? error.message : String(error),
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private write(message: JsonRpcMessage): void {
|
|
209
|
+
const serialized = JSON.stringify(message);
|
|
210
|
+
this.process.stdin.write(`${serialized}\n`, "utf8");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
import type { Logger } from "pino";
|
|
4
|
+
import type { AgentConfig } from "../config/schema.js";
|
|
5
|
+
import { AcpJsonRpcConnection } from "./AcpJsonRpcConnection.js";
|
|
6
|
+
import { agentBotEnvironment } from "../runtime/agentEnvironment.js";
|
|
7
|
+
import { spawnStdioCommand } from "../utils/spawnCommand.js";
|
|
8
|
+
|
|
9
|
+
export interface ManagedAcpProcess {
|
|
10
|
+
processKey: string;
|
|
11
|
+
child: ChildProcessWithoutNullStreams;
|
|
12
|
+
connection: AcpJsonRpcConnection;
|
|
13
|
+
stop: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class AcpProcessManager {
|
|
17
|
+
private readonly processes = new Map<string, ManagedAcpProcess>();
|
|
18
|
+
|
|
19
|
+
constructor(private readonly logger: Logger) {}
|
|
20
|
+
|
|
21
|
+
start(processKey: string, agentName: string, agent: AgentConfig): ManagedAcpProcess {
|
|
22
|
+
if (this.processes.has(processKey)) {
|
|
23
|
+
throw new Error(`ACP process already exists: ${processKey}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const child = spawnStdioCommand(agent.command, agent.args, agentBotEnvironment(process.env, agent.env));
|
|
27
|
+
|
|
28
|
+
const childLogger = this.logger.child({ processKey, agentName });
|
|
29
|
+
const connection = new AcpJsonRpcConnection(child, childLogger);
|
|
30
|
+
const managed: ManagedAcpProcess = {
|
|
31
|
+
processKey,
|
|
32
|
+
child,
|
|
33
|
+
connection,
|
|
34
|
+
stop: () => this.stop(processKey),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
this.processes.set(processKey, managed);
|
|
38
|
+
this.bindLifecycle(managed, childLogger);
|
|
39
|
+
|
|
40
|
+
childLogger.info({ command: agent.command, args: agent.args }, "Started ACP agent process.");
|
|
41
|
+
return managed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get(processKey: string): ManagedAcpProcess | undefined {
|
|
45
|
+
return this.processes.get(processKey);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
stop(processKey: string): void {
|
|
49
|
+
const managed = this.processes.get(processKey);
|
|
50
|
+
if (!managed) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.processes.delete(processKey);
|
|
55
|
+
managed.connection.close();
|
|
56
|
+
|
|
57
|
+
if (!managed.child.killed) {
|
|
58
|
+
managed.child.kill();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
stopAll(): void {
|
|
63
|
+
for (const processKey of this.processes.keys()) {
|
|
64
|
+
this.stop(processKey);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private bindLifecycle(managed: ManagedAcpProcess, logger: Logger): void {
|
|
69
|
+
const stderr = readline.createInterface({
|
|
70
|
+
input: managed.child.stderr,
|
|
71
|
+
crlfDelay: Infinity,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
stderr.on("line", (line) => {
|
|
75
|
+
logger.debug({ stderr: line }, "ACP agent stderr.");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
managed.child.once("error", (error) => {
|
|
79
|
+
logger.error({ error }, "ACP agent process error.");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
managed.child.once("exit", (code, signal) => {
|
|
83
|
+
this.processes.delete(managed.processKey);
|
|
84
|
+
logger.info({ code, signal }, "ACP agent process exited.");
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|