@keyou007/agent-bot 0.1.17-alpha.0 → 0.1.17-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +3 -1
- package/README.zh.md +3 -1
- package/config.example.yaml +12 -0
- package/dist/codex/CodexProcessManager.js +2 -1
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +22 -2
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/config/loadConfig.js +10 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +6 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +61 -5
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuMarkdown.js +42 -13
- package/dist/feishu/FeishuMarkdown.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +35 -12
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/LocalImageMarkdown.js +16 -2
- package/dist/feishu/LocalImageMarkdown.js.map +1 -1
- package/dist/index.js +33 -5
- package/dist/index.js.map +1 -1
- package/dist/local-files/LocalFileViewerServer.js +706 -0
- package/dist/local-files/LocalFileViewerServer.js.map +1 -0
- package/dist/local-files/NetworkAddressSelector.js +97 -0
- package/dist/local-files/NetworkAddressSelector.js.map +1 -0
- package/dist/proxy/ProxySessionController.js +43 -29
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +15 -2
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/utils/spawnCommand.js +2 -1
- package/dist/utils/spawnCommand.js.map +1 -1
- package/docs/technical-reference.md +8 -0
- package/docs/technical-reference.zh.md +8 -0
- package/npm-shrinkwrap.json +9 -9
- package/package.json +2 -2
- package/skills/agent-bot/SKILL.md +1 -1
- package/src/codex/CodexProcessManager.ts +3 -1
- package/src/codex/CodexRuntime.ts +25 -2
- package/src/config/loadConfig.ts +10 -0
- package/src/config/schema.ts +9 -0
- package/src/feishu/FeishuConnector.ts +70 -5
- package/src/feishu/FeishuMarkdown.ts +51 -12
- package/src/feishu/FeishuTurnPresenter.ts +43 -13
- package/src/feishu/LocalImageMarkdown.ts +23 -2
- package/src/index.ts +33 -4
- package/src/local-files/LocalFileViewerServer.ts +774 -0
- package/src/local-files/NetworkAddressSelector.ts +111 -0
- package/src/proxy/ProxySessionController.ts +33 -20
- package/src/supervision/SafeRestartScheduler.ts +23 -2
- package/src/utils/spawnCommand.ts +2 -0
|
@@ -8,6 +8,7 @@ export class SafeRestartScheduler {
|
|
|
8
8
|
idleSince;
|
|
9
9
|
idleInboundAt;
|
|
10
10
|
scheduleId = 0;
|
|
11
|
+
restartImmediatelyIfIdle = false;
|
|
11
12
|
constructor(options) {
|
|
12
13
|
this.options = options;
|
|
13
14
|
}
|
|
@@ -17,10 +18,14 @@ export class SafeRestartScheduler {
|
|
|
17
18
|
get pendingReason() {
|
|
18
19
|
return this.reason;
|
|
19
20
|
}
|
|
20
|
-
schedule(reason, notificationTarget) {
|
|
21
|
+
schedule(reason, notificationTarget, options = {}) {
|
|
21
22
|
const newlyScheduled = !this.reason;
|
|
22
23
|
if (newlyScheduled) {
|
|
23
24
|
this.notificationTargets = [];
|
|
25
|
+
this.restartImmediatelyIfIdle = options.restartImmediatelyIfIdle === true;
|
|
26
|
+
}
|
|
27
|
+
else if (options.restartImmediatelyIfIdle) {
|
|
28
|
+
this.restartImmediatelyIfIdle = true;
|
|
24
29
|
}
|
|
25
30
|
this.scheduleId += 1;
|
|
26
31
|
this.addNotificationTarget(notificationTarget, reason);
|
|
@@ -65,6 +70,7 @@ export class SafeRestartScheduler {
|
|
|
65
70
|
this.notificationTargets = [];
|
|
66
71
|
this.idleSince = undefined;
|
|
67
72
|
this.idleInboundAt = undefined;
|
|
73
|
+
this.restartImmediatelyIfIdle = false;
|
|
68
74
|
}
|
|
69
75
|
async poll() {
|
|
70
76
|
const reason = this.reason;
|
|
@@ -84,6 +90,10 @@ export class SafeRestartScheduler {
|
|
|
84
90
|
});
|
|
85
91
|
return;
|
|
86
92
|
}
|
|
93
|
+
if (this.restartImmediatelyIfIdle) {
|
|
94
|
+
await this.restart(reason, scheduleId, state);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
87
97
|
const quietPeriodMs = this.options.quietPeriodMs ?? 15_000;
|
|
88
98
|
const latestInboundAt = state.latestInboundAt ?? "";
|
|
89
99
|
if (this.idleSince === undefined || this.idleInboundAt !== latestInboundAt) {
|
|
@@ -138,6 +148,9 @@ export class SafeRestartScheduler {
|
|
|
138
148
|
}
|
|
139
149
|
return;
|
|
140
150
|
}
|
|
151
|
+
await this.restart(reason, scheduleId, confirmed);
|
|
152
|
+
}
|
|
153
|
+
async restart(reason, scheduleId, activity) {
|
|
141
154
|
const notificationTargets = this.notificationTargetsSnapshot();
|
|
142
155
|
this.clear();
|
|
143
156
|
await this.emitStatus({
|
|
@@ -145,7 +158,7 @@ export class SafeRestartScheduler {
|
|
|
145
158
|
reason,
|
|
146
159
|
notificationTargets,
|
|
147
160
|
phase: "restarting",
|
|
148
|
-
activity
|
|
161
|
+
activity,
|
|
149
162
|
remainingMs: 0,
|
|
150
163
|
});
|
|
151
164
|
await this.options.onReady(reason, notificationTargets);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SafeRestartScheduler.js","sourceRoot":"","sources":["../../src/supervision/SafeRestartScheduler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SafeRestartScheduler.js","sourceRoot":"","sources":["../../src/supervision/SafeRestartScheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,9 +5,10 @@ export function prepareCommand(command, args, platform = process.platform, comsp
|
|
|
5
5
|
const commandLine = [command, ...args].map(quoteCmdToken).join(" ");
|
|
6
6
|
return { command: comspec, args: ["/d", "/s", "/c", commandLine] };
|
|
7
7
|
}
|
|
8
|
-
export function spawnStdioCommand(command, args, env) {
|
|
8
|
+
export function spawnStdioCommand(command, args, env, cwd) {
|
|
9
9
|
const prepared = prepareCommand(command, args);
|
|
10
10
|
return spawn(prepared.command, prepared.args, {
|
|
11
|
+
cwd,
|
|
11
12
|
env,
|
|
12
13
|
stdio: ["pipe", "pipe", "pipe"],
|
|
13
14
|
windowsHide: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawnCommand.js","sourceRoot":"","sources":["../../src/utils/spawnCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAuC,MAAM,oBAAoB,CAAC;AAOhF,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,IAAc,EACd,QAAQ,GAAoB,OAAO,CAAC,QAAQ,EAC5C,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,SAAS;IAEjE,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACnD,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,OAAe,EACf,IAAc,EACd,GAAsB;
|
|
1
|
+
{"version":3,"file":"spawnCommand.js","sourceRoot":"","sources":["../../src/utils/spawnCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAuC,MAAM,oBAAoB,CAAC;AAOhF,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,IAAc,EACd,QAAQ,GAAoB,OAAO,CAAC,QAAQ,EAC5C,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,SAAS;IAEjE,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACnD,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,OAAe,EACf,IAAc,EACd,GAAsB,EACtB,GAAY;IAEZ,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE;QAC5C,GAAG;QACH,GAAG;QACH,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;QAC/B,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACpE,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;AAC5C,CAAC"}
|
|
@@ -156,6 +156,12 @@ feishu:
|
|
|
156
156
|
console:
|
|
157
157
|
enabled: true
|
|
158
158
|
|
|
159
|
+
fileViewer:
|
|
160
|
+
enabled: true
|
|
161
|
+
host: "127.0.0.1"
|
|
162
|
+
port: 0
|
|
163
|
+
# publicBaseUrl: "https://agentbot.example.com/files"
|
|
164
|
+
|
|
159
165
|
agents:
|
|
160
166
|
codex:
|
|
161
167
|
kind: "app-server"
|
|
@@ -187,6 +193,8 @@ logging:
|
|
|
187
193
|
path: "./logs/agent-bot.log"
|
|
188
194
|
```
|
|
189
195
|
|
|
196
|
+
`fileViewer` turns local non-image files and directories in final replies into signed, read-only HTTP links. The default `host: "127.0.0.1"` accepts connections only from the Agent Bot machine. With `0.0.0.0` or `::`, the server listens on every interface and automatically chooses a non-internal IPv4 link address in wired, Wi-Fi, other physical, then VPN order. Docker, Hyper-V, VMware, VirtualBox, WSL, loopback, and link-local interfaces are excluded; if no candidate remains, links fall back to loopback. `publicBaseUrl` has the highest priority and overrides automatic selection for domains, HTTPS reverse proxies, NAT, or port mappings. With `port: 0`, the first startup selects a free port and stores it in `<data>/file-viewer/port`; later restarts reuse it, while separate Profiles avoid a shared fixed port. The signing key is stored beside it as `secret`. File URLs can read only their signed absolute path. Directory URLs list direct children and allow downward navigation without exposing a parent-directory action. Text identity is detected from a content sample, and text pages preview the first 2 MiB with line anchors; images, PDFs, audio, and video use native browser previews; other binary files expose raw and download actions. Treat each signed URL as a bearer credential and do not share it publicly.
|
|
197
|
+
|
|
190
198
|
`feishu.respondToOwnerOnly` defaults to `true`. It compares each message sender and card-action operator Open ID with `feishu.userOpenId`, ignoring non-owner input before durable event claims, reactions, downloads, commands, or Agent work. Set it to `false` to allow other users. If it is enabled without a configured owner Open ID, all Feishu messages and card actions are ignored; configure `FEISHU_USER_OPEN_ID` or temporarily disable the restriction to bootstrap from a private message.
|
|
191
199
|
|
|
192
200
|
`feishu.respondToAllGroupMessages` defaults to `true` in the template, while first initialization and `init --reset` write the user's selected mode. Set it to `false` to ignore owner group messages unless they mention the current bot. The worker resolves the bot's Open ID at startup so mentioning another member does not trigger it. Owner private messages are unaffected. Initialization requests all-user group-message delivery only when this value is `true`; changing a mention-only Profile to `true` later requires rerunning `agentbot init` to add the permission.
|
|
@@ -156,6 +156,12 @@ feishu:
|
|
|
156
156
|
console:
|
|
157
157
|
enabled: true
|
|
158
158
|
|
|
159
|
+
fileViewer:
|
|
160
|
+
enabled: true
|
|
161
|
+
host: "127.0.0.1"
|
|
162
|
+
port: 0
|
|
163
|
+
# publicBaseUrl: "https://agentbot.example.com/files"
|
|
164
|
+
|
|
159
165
|
agents:
|
|
160
166
|
codex:
|
|
161
167
|
kind: "app-server"
|
|
@@ -187,6 +193,8 @@ logging:
|
|
|
187
193
|
path: "./logs/agent-bot.log"
|
|
188
194
|
```
|
|
189
195
|
|
|
196
|
+
`fileViewer` 为最终回答中的本地非图片文件和目录生成带签名的只读 HTTP 链接。默认 `host: "127.0.0.1"`,只能从运行 Agent Bot 的机器访问。设为 `0.0.0.0` 或 `::` 时,服务会监听所有网卡,并从非内部 IPv4 地址中按有线、Wi-Fi、其他物理网卡、VPN 的顺序自动选择链接地址;Docker、Hyper-V、VMware、VirtualBox、WSL、回环和链路本地地址会被排除,没有候选地址时回退到本机环回地址。`publicBaseUrl` 的优先级最高,用于域名、HTTPS 反向代理、NAT 或端口映射。`port: 0` 会在第一次启动时选择空闲端口,并保存到 `<data>/file-viewer/port`,以后重启继续使用,同一机器上的多个 Profile 不会争用固定端口。签名密钥保存在同目录的 `secret` 文件中。文件 URL 只能读取签发时对应的绝对路径;目录 URL 会列出其直接子项并允许继续向下浏览,但不提供向父目录跳转。文本身份由内容采样识别,页面显示带行号的开头 2 MiB;图片、PDF、音视频使用浏览器原生预览,其他二进制文件提供原始打开和下载入口。签名 URL 是持有者凭证,不应公开分享。
|
|
197
|
+
|
|
190
198
|
`feishu.respondToOwnerOnly` 默认为 `true`。每条消息的发送者及卡片操作人的 Open ID 都会与 `feishu.userOpenId` 比较;非拥有者输入会在持久化事件、添加 reaction、下载图片、执行命令或启动 Agent 前被忽略。设为 `false` 可允许其他用户。开启后若未配置拥有者 Open ID,所有飞书消息和卡片操作都会被忽略;需先配置 `FEISHU_USER_OPEN_ID`,或临时关闭限制后通过私聊完成补全。
|
|
191
199
|
|
|
192
200
|
`feishu.respondToAllGroupMessages` 在模板中默认为 `true`,第一次初始化和 `init --reset` 会按用户选择写入实际值。设为 `false` 后,拥有者未 @ 当前机器人的群消息也会被忽略;Worker 启动时会解析机器人的 Open ID,因此 @ 其他成员不会误触发。拥有者私聊不受影响。初始化只在该值为 `true` 时申请接收全部群消息的权限;仅 @ 响应的 Profile 后续改为 `true` 时,需要重新运行 `agentbot init` 补充权限。
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keyou007/agent-bot",
|
|
3
|
-
"version": "0.1.17-alpha.
|
|
3
|
+
"version": "0.1.17-alpha.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@keyou007/agent-bot",
|
|
9
|
-
"version": "0.1.17-alpha.
|
|
9
|
+
"version": "0.1.17-alpha.2",
|
|
10
10
|
"license": "GPL-3.0-only",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
13
13
|
"@clack/prompts": "1.7.0",
|
|
14
|
-
"@larksuiteoapi/node-sdk": "1.
|
|
14
|
+
"@larksuiteoapi/node-sdk": "1.73.0",
|
|
15
15
|
"@resvg/resvg-js": "2.6.2",
|
|
16
16
|
"better-sqlite3": "12.11.1",
|
|
17
17
|
"dotenv": "17.4.2",
|
|
@@ -730,18 +730,18 @@
|
|
|
730
730
|
"license": "MIT"
|
|
731
731
|
},
|
|
732
732
|
"node_modules/@larksuiteoapi/node-sdk": {
|
|
733
|
-
"version": "1.
|
|
734
|
-
"resolved": "https://registry.npmjs.org/@larksuiteoapi/node-sdk/-/node-sdk-1.
|
|
735
|
-
"integrity": "sha512
|
|
733
|
+
"version": "1.73.0",
|
|
734
|
+
"resolved": "https://registry.npmjs.org/@larksuiteoapi/node-sdk/-/node-sdk-1.73.0.tgz",
|
|
735
|
+
"integrity": "sha512-gs4EiupJ6RdYAEJGtuYrCVsB7wSMBwtUDK+ilpmOHD6RTvbtciW6AWciQO9coOQVuBZfh7hvAi25dPfCsny/nA==",
|
|
736
736
|
"license": "MIT",
|
|
737
737
|
"dependencies": {
|
|
738
|
-
"axios": "
|
|
738
|
+
"axios": "^1.16.0",
|
|
739
739
|
"lodash.identity": "^3.0.0",
|
|
740
740
|
"lodash.merge": "^4.6.2",
|
|
741
741
|
"lodash.pickby": "^4.6.0",
|
|
742
742
|
"protobufjs": "^7.2.6",
|
|
743
|
-
"qs": "^6.
|
|
744
|
-
"ws": "^8.
|
|
743
|
+
"qs": "^6.14.2",
|
|
744
|
+
"ws": "^8.19.0"
|
|
745
745
|
}
|
|
746
746
|
},
|
|
747
747
|
"node_modules/@napi-rs/wasm-runtime": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keyou007/agent-bot",
|
|
3
|
-
"version": "0.1.17-alpha.
|
|
3
|
+
"version": "0.1.17-alpha.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Use Codex, TraeX, and compatible coding agents from Lark/Feishu.",
|
|
6
6
|
"license": "GPL-3.0-only",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
71
71
|
"@clack/prompts": "1.7.0",
|
|
72
|
-
"@larksuiteoapi/node-sdk": "1.
|
|
72
|
+
"@larksuiteoapi/node-sdk": "1.73.0",
|
|
73
73
|
"@resvg/resvg-js": "2.6.2",
|
|
74
74
|
"better-sqlite3": "12.11.1",
|
|
75
75
|
"dotenv": "17.4.2",
|
|
@@ -144,7 +144,7 @@ agentbot server restart --task <task> --reason "<reason>"
|
|
|
144
144
|
agentbot server stop
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
`update` is only for npm-installed Agent Bot packages. It verifies the new package
|
|
147
|
+
`update` is only for npm-installed Agent Bot packages. It verifies the new package, sends a safe-restart card, waits for active tasks, and restarts immediately when the service is already idle. It automatically restores the prior version if activation fails. It refuses source checkouts and `npm link` installations.
|
|
148
148
|
|
|
149
149
|
Autostart is Profile-specific. Use `server autostart enable` for login startup, `server autostart enable --linger` on Linux only when the user explicitly requests startup before login, and `server autostart disable` to remove registration without stopping the current Server. Disabling Agent Bot autostart must not disable Linux user lingering because other services may use it.
|
|
150
150
|
|
|
@@ -29,10 +29,12 @@ export class CodexProcessManager implements AppServerClientProvider {
|
|
|
29
29
|
async getClient(): Promise<AppServerClient> {
|
|
30
30
|
if (this.client) return this.client;
|
|
31
31
|
this.version = undefined;
|
|
32
|
+
const environmentContext = this.environmentContext();
|
|
32
33
|
const child = spawnStdioCommand(
|
|
33
34
|
this.command,
|
|
34
35
|
this.args,
|
|
35
|
-
agentBotEnvironment(process.env, this.env,
|
|
36
|
+
agentBotEnvironment(process.env, this.env, environmentContext),
|
|
37
|
+
environmentContext.profilePath ?? os.homedir(),
|
|
36
38
|
);
|
|
37
39
|
this.child = child;
|
|
38
40
|
const client = new AppServerConnection(child, this.logger.child({ component: "codex-app-server" }));
|
|
@@ -191,7 +191,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
191
191
|
const session = this.requireSession(sessionId);
|
|
192
192
|
const client = await this.client();
|
|
193
193
|
await this.ensureSessionResumed(session, client);
|
|
194
|
-
const
|
|
194
|
+
const start = () => client.request<{ turn: { id: string } }>("turn/start", {
|
|
195
195
|
threadId: session.remoteSessionId,
|
|
196
196
|
input: codexUserInput(prompt),
|
|
197
197
|
cwd: session.cwd,
|
|
@@ -200,6 +200,19 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
200
200
|
summary: "auto",
|
|
201
201
|
approvalPolicy: session.permissionMode === "auto" ? "never" : "on-request",
|
|
202
202
|
}, SESSION_REQUEST_TIMEOUT_MS);
|
|
203
|
+
let response: { turn: { id: string } };
|
|
204
|
+
try {
|
|
205
|
+
response = await start();
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (!isInvalidWorkingDirectoryError(error)) throw error;
|
|
208
|
+
this.logger.warn(
|
|
209
|
+
{ error, sessionId: session.localSessionId, cwd: session.cwd },
|
|
210
|
+
"App Server rejected the task working directory; refreshing the current thread before retrying.",
|
|
211
|
+
);
|
|
212
|
+
await this.resumeAppServerSession(session, client);
|
|
213
|
+
session.needsResume = false;
|
|
214
|
+
response = await start();
|
|
215
|
+
}
|
|
203
216
|
if (session.activeTurnId !== response.turn.id) {
|
|
204
217
|
this.adoptTurn(session, response.turn.id, Date.now());
|
|
205
218
|
}
|
|
@@ -746,6 +759,11 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
746
759
|
|
|
747
760
|
private async ensureSessionResumed(session: CodexSession, client: AppServerClient): Promise<void> {
|
|
748
761
|
if (!session.needsResume) return;
|
|
762
|
+
await this.resumeAppServerSession(session, client);
|
|
763
|
+
session.needsResume = false;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private async resumeAppServerSession(session: CodexSession, client: AppServerClient): Promise<void> {
|
|
749
767
|
await client.request("thread/resume", {
|
|
750
768
|
threadId: session.remoteSessionId,
|
|
751
769
|
excludeTurns: true,
|
|
@@ -755,7 +773,6 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
755
773
|
...threadLifecycleParams(session.cwd),
|
|
756
774
|
...permissionParams(session.permissionMode),
|
|
757
775
|
}, SESSION_REQUEST_TIMEOUT_MS);
|
|
758
|
-
session.needsResume = false;
|
|
759
776
|
}
|
|
760
777
|
|
|
761
778
|
private async synchronizeSessionNow(sessionId: string): Promise<RuntimeSession> {
|
|
@@ -1084,6 +1101,12 @@ function isUnsupportedExcludeTurnsError(error: unknown): boolean {
|
|
|
1084
1101
|
|| /unknown|unrecognized|unexpected|unsupported|not supported|experimental/i.test(details);
|
|
1085
1102
|
}
|
|
1086
1103
|
|
|
1104
|
+
function isInvalidWorkingDirectoryError(error: unknown): boolean {
|
|
1105
|
+
return error instanceof AppServerRequestError
|
|
1106
|
+
&& error.method === "turn/start"
|
|
1107
|
+
&& /invalid cwd/i.test(`${error.serverMessage} ${error.data === undefined ? "" : JSON.stringify(error.data)}`);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1087
1110
|
function isUnsupportedRecencySortError(error: unknown): boolean {
|
|
1088
1111
|
if (!(error instanceof AppServerRequestError) || error.method !== "thread/list") return false;
|
|
1089
1112
|
const data = typeof error.data === "string"
|
package/src/config/loadConfig.ts
CHANGED
|
@@ -26,6 +26,16 @@ const DEFAULT_CONFIG = `feishu:
|
|
|
26
26
|
console:
|
|
27
27
|
enabled: true
|
|
28
28
|
|
|
29
|
+
fileViewer:
|
|
30
|
+
enabled: true
|
|
31
|
+
# Use 0.0.0.0 to accept LAN connections and automatically prefer
|
|
32
|
+
# wired, Wi-Fi, other physical, then VPN addresses in generated links.
|
|
33
|
+
host: "127.0.0.1"
|
|
34
|
+
# 0 selects a free port once and reuses it for this Profile.
|
|
35
|
+
port: 0
|
|
36
|
+
# Overrides the automatically selected URL for domains or reverse proxies.
|
|
37
|
+
# publicBaseUrl: "https://agentbot.example.com/files"
|
|
38
|
+
|
|
29
39
|
agents:
|
|
30
40
|
codex:
|
|
31
41
|
kind: "app-server"
|
package/src/config/schema.ts
CHANGED
|
@@ -62,6 +62,15 @@ export const appConfigSchema = z.object({
|
|
|
62
62
|
enabled: z.boolean().default(true),
|
|
63
63
|
})
|
|
64
64
|
.default({ enabled: true }),
|
|
65
|
+
fileViewer: z.object({
|
|
66
|
+
enabled: z.boolean().default(true),
|
|
67
|
+
host: z.string().min(1).default("127.0.0.1"),
|
|
68
|
+
port: z.number().int().min(0).max(65_535).default(0),
|
|
69
|
+
publicBaseUrl: z.string().url().refine(
|
|
70
|
+
(value) => ["http:", "https:"].includes(new URL(value).protocol),
|
|
71
|
+
"fileViewer.publicBaseUrl must use http or https.",
|
|
72
|
+
).optional(),
|
|
73
|
+
}).default({ enabled: true, host: "127.0.0.1", port: 0 }),
|
|
65
74
|
feishu: z.object({
|
|
66
75
|
transport: z.enum(["auto", "sdk", "console"]).default("auto"),
|
|
67
76
|
appId: z.string().optional(),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Logger } from "pino";
|
|
2
|
+
import type { WSClient as LarkWsClient, WSConnectionStatus } from "@larksuiteoapi/node-sdk";
|
|
2
3
|
import type { AppConfig } from "../config/schema.js";
|
|
3
4
|
import { threadContextKey } from "./contextKey.js";
|
|
4
5
|
import { resolveFeishuBotOpenId } from "./FeishuBotIdentity.js";
|
|
@@ -9,7 +10,12 @@ import type { CardAction, ChatUpdatedEvent, FeishuEventHandler, IncomingMessage
|
|
|
9
10
|
type BotOpenIdResolver = (appId: string, appSecret: string) => Promise<string>;
|
|
10
11
|
type BotOpenIdListener = (botOpenId: string) => void;
|
|
11
12
|
|
|
13
|
+
const LARK_WS_PING_TIMEOUT_SECONDS = 60;
|
|
14
|
+
const LARK_WS_HANDSHAKE_TIMEOUT_MS = 60_000;
|
|
15
|
+
|
|
12
16
|
export class FeishuConnector {
|
|
17
|
+
private wsClient?: LarkWsClient;
|
|
18
|
+
|
|
13
19
|
constructor(
|
|
14
20
|
private readonly config: AppConfig,
|
|
15
21
|
private readonly handler: FeishuEventHandler,
|
|
@@ -39,7 +45,12 @@ export class FeishuConnector {
|
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
stop(): void {
|
|
42
|
-
|
|
48
|
+
this.wsClient?.close();
|
|
49
|
+
this.wsClient = undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getConnectionStatus(): WSConnectionStatus | undefined {
|
|
53
|
+
return this.wsClient?.getConnectionStatus();
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
private async startFeishuWs(
|
|
@@ -48,7 +59,7 @@ export class FeishuConnector {
|
|
|
48
59
|
botOpenId?: string,
|
|
49
60
|
requireMention = false,
|
|
50
61
|
): Promise<void> {
|
|
51
|
-
const lark =
|
|
62
|
+
const lark = await import("@larksuiteoapi/node-sdk");
|
|
52
63
|
const eventDispatcher = new lark.EventDispatcher({}).register({
|
|
53
64
|
"im.message.receive_v1": async (data: unknown) => {
|
|
54
65
|
const senderOpenId = getFeishuEvent(data)?.sender?.sender_id?.open_id;
|
|
@@ -117,12 +128,66 @@ export class FeishuConnector {
|
|
|
117
128
|
};
|
|
118
129
|
},
|
|
119
130
|
});
|
|
120
|
-
|
|
131
|
+
let resolveInitialConnection!: () => void;
|
|
132
|
+
let rejectInitialConnection!: (error: Error) => void;
|
|
133
|
+
let initialConnectionSettled = false;
|
|
134
|
+
const initialConnection = new Promise<void>((resolve, reject) => {
|
|
135
|
+
resolveInitialConnection = resolve;
|
|
136
|
+
rejectInitialConnection = reject;
|
|
137
|
+
});
|
|
138
|
+
let wsClient: LarkWsClient | undefined;
|
|
139
|
+
wsClient = new lark.WSClient({
|
|
121
140
|
appId,
|
|
122
141
|
appSecret,
|
|
142
|
+
handshakeTimeoutMs: LARK_WS_HANDSHAKE_TIMEOUT_MS,
|
|
143
|
+
wsConfig: {
|
|
144
|
+
pingTimeout: LARK_WS_PING_TIMEOUT_SECONDS,
|
|
145
|
+
},
|
|
146
|
+
onReady: () => {
|
|
147
|
+
if (initialConnectionSettled) return;
|
|
148
|
+
initialConnectionSettled = true;
|
|
149
|
+
resolveInitialConnection();
|
|
150
|
+
},
|
|
151
|
+
onError: (error) => {
|
|
152
|
+
this.logger.error(
|
|
153
|
+
{ error, connectionStatus: wsClient?.getConnectionStatus() },
|
|
154
|
+
"Feishu WebSocket connection failed.",
|
|
155
|
+
);
|
|
156
|
+
if (initialConnectionSettled) return;
|
|
157
|
+
initialConnectionSettled = true;
|
|
158
|
+
rejectInitialConnection(error);
|
|
159
|
+
},
|
|
160
|
+
onReconnecting: () => {
|
|
161
|
+
this.logger.warn(
|
|
162
|
+
{ connectionStatus: wsClient?.getConnectionStatus() },
|
|
163
|
+
"Feishu WebSocket connection lost; reconnecting.",
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
onReconnected: () => {
|
|
167
|
+
this.logger.info("Feishu WebSocket connection restored.");
|
|
168
|
+
},
|
|
123
169
|
});
|
|
124
|
-
|
|
125
|
-
|
|
170
|
+
this.wsClient = wsClient;
|
|
171
|
+
try {
|
|
172
|
+
await wsClient.start({ eventDispatcher });
|
|
173
|
+
const initialStatus = wsClient.getConnectionStatus();
|
|
174
|
+
if ((initialStatus.state === "idle" || initialStatus.state === "failed") && !initialConnectionSettled) {
|
|
175
|
+
throw new Error(`Feishu WebSocket failed to start (state: ${initialStatus.state}).`);
|
|
176
|
+
}
|
|
177
|
+
await initialConnection;
|
|
178
|
+
const connectedStatus = wsClient.getConnectionStatus();
|
|
179
|
+
if (connectedStatus.state !== "connected") {
|
|
180
|
+
throw new Error(`Feishu WebSocket failed to become ready (state: ${connectedStatus.state}).`);
|
|
181
|
+
}
|
|
182
|
+
this.logger.info(
|
|
183
|
+
{ connectionStatus: connectedStatus },
|
|
184
|
+
"Feishu WebSocket connector started.",
|
|
185
|
+
);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
wsClient.close({ force: true });
|
|
188
|
+
if (this.wsClient === wsClient) this.wsClient = undefined;
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
126
191
|
}
|
|
127
192
|
|
|
128
193
|
private deferCardAction(action: CardAction, receivedAt: number): void {
|
|
@@ -8,10 +8,17 @@ interface FenceState {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const FENCE_OPENER = /^([ \t]*)((?:`{3,})|(?:~{3,}))(.*)$/;
|
|
11
|
-
const MARKDOWN_LINK = /(!?)\[([^\]\r\n]*)\]\((
|
|
11
|
+
const MARKDOWN_LINK = /(!?)\[([^\]\r\n]*)\]\((<[^>\r\n]+>|[^)\r\n]+)\)/g;
|
|
12
12
|
const LOCAL_FILE_LINE_REFERENCE = /(:\d+(?::\d+)?)$/;
|
|
13
|
+
const LOCAL_IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".tiff", ".bmp", ".ico"]);
|
|
13
14
|
|
|
14
|
-
export
|
|
15
|
+
export type LocalFileUrlResolver = (filePath: string, reference?: string) => string | undefined;
|
|
16
|
+
|
|
17
|
+
export function normalizeFeishuMarkdown(
|
|
18
|
+
markdown: string,
|
|
19
|
+
projectCwd?: string,
|
|
20
|
+
localFileUrl?: LocalFileUrlResolver,
|
|
21
|
+
): string {
|
|
15
22
|
const lines = markdown.match(/[^\r\n]*(?:\r\n|\n)|[^\r\n]+$/g) ?? [];
|
|
16
23
|
let fence: FenceState | undefined;
|
|
17
24
|
|
|
@@ -21,7 +28,7 @@ export function normalizeFeishuMarkdown(markdown: string, projectCwd?: string):
|
|
|
21
28
|
|
|
22
29
|
if (!fence) {
|
|
23
30
|
const opener = FENCE_OPENER.exec(body);
|
|
24
|
-
if (!opener?.[2]) return `${includeLocalFileLineReferences(body, projectCwd)}${ending}`;
|
|
31
|
+
if (!opener?.[2]) return `${includeLocalFileLineReferences(body, projectCwd, localFileUrl)}${ending}`;
|
|
25
32
|
|
|
26
33
|
const indent = opener[1] ?? "";
|
|
27
34
|
const delimiter = opener[2];
|
|
@@ -42,18 +49,38 @@ export function normalizeFeishuMarkdown(markdown: string, projectCwd?: string):
|
|
|
42
49
|
}).join("");
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
function includeLocalFileLineReferences(
|
|
52
|
+
function includeLocalFileLineReferences(
|
|
53
|
+
markdown: string,
|
|
54
|
+
projectCwd?: string,
|
|
55
|
+
localFileUrl?: LocalFileUrlResolver,
|
|
56
|
+
): string {
|
|
46
57
|
return markdown.replace(MARKDOWN_LINK, (link, imageMarker: string, label: string, rawTarget: string) => {
|
|
47
58
|
if (imageMarker) return link;
|
|
48
59
|
|
|
49
60
|
const target = unwrapMarkdownTarget(rawTarget.trim());
|
|
50
61
|
if (!isLocalFileTarget(target)) return link;
|
|
62
|
+
if (isLocalImageTarget(target)) return link;
|
|
51
63
|
|
|
52
64
|
const reference = LOCAL_FILE_LINE_REFERENCE.exec(target)?.[1];
|
|
65
|
+
const targetWithoutReference = reference ? target.slice(0, -reference.length) : target;
|
|
66
|
+
const filePath = localFilePath(targetWithoutReference);
|
|
67
|
+
const viewerUrl = filePath ? localFileUrl?.(filePath, reference) : undefined;
|
|
53
68
|
const projectPathLabel = projectCwd ? projectFilePathLabel(target, projectCwd, reference) : undefined;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
if (!projectPathLabel) {
|
|
70
|
+
const normalizedLabel = reference && !label.endsWith(reference) ? `${label}${reference}` : label;
|
|
71
|
+
if (viewerUrl) return `[${normalizedLabel}](${viewerUrl})`;
|
|
72
|
+
return normalizedLabel === label ? link : `[${normalizedLabel}](${rawTarget})`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (projectPathLabel === label) return viewerUrl ? `[${label}](${viewerUrl})` : link;
|
|
76
|
+
if (reference && projectPathLabel === `${label}${reference}`) {
|
|
77
|
+
return `[${projectPathLabel}](${viewerUrl ?? rawTarget})`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!label) return viewerUrl ? `[${projectPathLabel}](${viewerUrl})` : inlineCode(projectPathLabel);
|
|
81
|
+
return viewerUrl
|
|
82
|
+
? `[${label}](${viewerUrl})(${inlineCode(projectPathLabel)})`
|
|
83
|
+
: `${label}(${inlineCode(projectPathLabel)})`;
|
|
57
84
|
});
|
|
58
85
|
}
|
|
59
86
|
|
|
@@ -71,11 +98,7 @@ function projectFilePathLabel(
|
|
|
71
98
|
|
|
72
99
|
const pathApi = usesWindowsPaths(filePath, projectCwd) ? path.win32 : path.posix;
|
|
73
100
|
const parsed = pathApi.parse(filePath);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const parentName = pathApi.basename(pathApi.dirname(filePath));
|
|
77
|
-
if (!parentName) return undefined;
|
|
78
|
-
return `${parentName}${pathApi.sep}${parsed.base}${reference ?? ""}`;
|
|
101
|
+
return `${parsed.base}${reference ?? ""}`;
|
|
79
102
|
}
|
|
80
103
|
|
|
81
104
|
function localFilePath(target: string): string | undefined {
|
|
@@ -108,10 +131,26 @@ function displayAbsolutePath(filePath: string): string {
|
|
|
108
131
|
return path.win32.normalize(filePath);
|
|
109
132
|
}
|
|
110
133
|
|
|
134
|
+
function inlineCode(value: string): string {
|
|
135
|
+
const longestDelimiter = Math.max(0, ...(value.match(/`+/g) ?? []).map((run) => run.length));
|
|
136
|
+
const delimiter = "`".repeat(longestDelimiter + 1);
|
|
137
|
+
const padding = value.startsWith("`") || value.endsWith("`") ? " " : "";
|
|
138
|
+
return `${delimiter}${padding}${value}${padding}${delimiter}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
111
141
|
function usesWindowsPaths(...values: string[]): boolean {
|
|
112
142
|
return values.some((value) => /^\/?[a-z]:[\\/]/i.test(value) || /^\\\\/.test(value));
|
|
113
143
|
}
|
|
114
144
|
|
|
145
|
+
function isLocalImageTarget(target: string): boolean {
|
|
146
|
+
const reference = LOCAL_FILE_LINE_REFERENCE.exec(target)?.[1];
|
|
147
|
+
const targetWithoutReference = reference ? target.slice(0, -reference.length) : target;
|
|
148
|
+
const filePath = localFilePath(targetWithoutReference);
|
|
149
|
+
if (!filePath) return false;
|
|
150
|
+
const pathApi = usesWindowsPaths(filePath) ? path.win32 : path.posix;
|
|
151
|
+
return LOCAL_IMAGE_EXTENSIONS.has(pathApi.extname(filePath).toLowerCase());
|
|
152
|
+
}
|
|
153
|
+
|
|
115
154
|
function unwrapMarkdownTarget(target: string): string {
|
|
116
155
|
return target.startsWith("<") && target.endsWith(">") ? target.slice(1, -1) : target;
|
|
117
156
|
}
|