grix-connector 3.8.3 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,74 +1,88 @@
1
1
  ---
2
2
  name: tailnet-file-share
3
- description: 将本地文件转为 tailnet 链接,图片自动内嵌预览,其他文件给下载链接
3
+ description: Turn a local file into a tailnet link — images render as inline previews, everything else gets a download link. Trigger when the user asks to view, send, share, download, or export any file on this machine.
4
4
  trigger: 当用户要求查看、发送、分享、下载、导出本机上的任何文件时
5
5
  ---
6
6
 
7
- 当用户要查看或获取本机上的文件时(图片、日志、报告、构建产物、导出数据等),你必须:
7
+ # Tailnet File Share
8
8
 
9
- 1. 调用 grix_file_link 工具,传入文件的绝对路径
10
- 2. 将工具返回的 `markdown` 字段原样写入你的回复,不要修改
11
- - 图片文件(jpg/png/gif/webp/svg 等):工具返回 `![文件名](url)`,在聊天中直接渲染为图片预览
12
- - 其他文件:工具返回 `[文件名](url)`,用户点击即可下载
13
- 3. 不要输出原始文件路径,不要尝试粘贴或转述文件内容
14
- 4. 链接只绑本机 tailnet 内网地址、外网访问不到,用户随时点开回看都可以,正常情况下不需要关心也不要提任何有效期问题,直接把链接发给用户即可
9
+ When the user wants to view or obtain a file on this machine (images, logs, reports, build artifacts, exported data, etc.), you must:
15
10
 
16
- ## 何时改用 grix_file_upload(原生附件)
11
+ 1. Call the `grix_file_link` tool with the file's absolute path.
12
+ 2. Copy the tool's `markdown` field into your reply verbatim — do not modify it.
13
+ - Image files (jpg/png/gif/webp/svg, etc.): the tool returns `![filename](url)`, which renders as an inline image preview in chat.
14
+ - Other files: the tool returns `[filename](url)`, which the user clicks to download.
15
+ 3. Do not print the raw file path, and do not paste or paraphrase the file contents.
16
+ 4. The link is bound to this machine's tailnet-internal address and is unreachable from the public internet. The user can reopen it any time, so under normal circumstances there is no expiry to worry about and none to mention — just send the link.
17
17
 
18
- grix_file_link 外还有一个 `grix_file_upload` 工具:把本地文件上传到 Grix 平台,
19
- 在目标会话里以**原生附件消息**出现(图片/视频在聊天里直接内联展示,无需 tailnet、
20
- 无需装证书)。支持图片/视频/文档/压缩包,单文件最大 50 MB。按场景选择:
18
+ ## When to use `grix_file_upload` (native attachment) instead
21
19
 
22
- - 文件应作为聊天里的正式附件(尤其发图、发视频给用户看)→ `grix_file_upload`
23
- - 文件较大(>50 MB)、只需给个可点的下载链接、或要发的是任意本机路径产物 `grix_file_link`
20
+ Besides `grix_file_link` there is a `grix_file_upload` tool: it uploads a local
21
+ file to the Grix platform, where it appears in the target session as a **native
22
+ attachment message** (images/videos display inline in chat — no tailnet, no
23
+ certificate install needed). It supports images, videos, documents, and
24
+ archives, up to 50 MB per file. Choose by scenario:
24
25
 
25
- ## HTTPS 与首次安装信任证书
26
+ - The file should be a proper attachment in the chat (especially sending images or videos for the user to look at) → use `grix_file_upload`.
27
+ - The file is large (>50 MB), a clickable download link is all that's needed, or it's an arbitrary local-path artifact → use `grix_file_link`.
26
28
 
27
- 下载链接是 HTTPS(`https://…`),由本机内置的一个 10 年有效期自签 CA 现签发。
28
- 工具结果里还会返回 `ca_install_url` —— 这是根 CA 证书的安装链接。
29
+ ## HTTPS and the one-time trust-certificate install
29
30
 
30
- - **第一次给某个用户发文件链接时**(或用户反馈"链接打不开 / 证书不受信任 / 不安全"时),
31
- `ca_install_url` 作为"安装信任证书"链接一并发给他,并附上下面的安装步骤。
32
- - 用户在**每台设备上只需安装一次**,之后 10 年内这台机器发出的所有下载链接都不再有任何警告,
33
- tailnet IP 变了也不用重装。
31
+ Download links are HTTPS (`https://…`), signed on the fly by a self-signed CA
32
+ built into this machine with a 10-year validity. The tool result also returns
33
+ `ca_install_url` the install link for the root CA certificate.
34
34
 
35
- 发给用户的安装引导(按其设备选其一):
35
+ - **The first time you send a file link to a given user** (or whenever they report "the link won't open / certificate not trusted / not secure"), send them `ca_install_url` as the "install trust certificate" link along with the steps below.
36
+ - Each device only needs this **once**; afterwards, for 10 years, every download link from this machine is warning-free, and no reinstall is needed even if the tailnet IP changes.
36
37
 
37
- - **iPhone / iPad**:用 Safari 打开 `ca_install_url`,会直接弹出"此网站正尝试下载一个描述文件" 允许
38
- 到「设置」顶部出现"已下载描述文件",点进去安装 →
39
- 再到「设置 → 通用 → 关于本机 → 证书信任设置」,把该证书开关打开(开启完全信任)。这一步必须做,否则系统仍不信任。
40
- (iOS 必须用 Safari 打开,其他浏览器不会触发描述文件安装。服务端已按设备自动返回 .mobileconfig,无需手动加参数。)
41
- - **Mac**:点链接下载 `.crt` → 双击用「钥匙串访问」打开 → 找到该证书 → 双击 →「信任」展开 →「使用此证书时」选"始终信任"。
42
- - **Android**:点 `ca_install_url` 下载 `.crt` →「设置 → 安全 → 加密与凭据 → 安装证书 → CA 证书」选择刚下载的文件安装
43
- (新版安卓不支持浏览器内一键安装,必须走系统设置这一步)。
44
- - **Windows**:双击 `.crt` →「安装证书」→ 选存储位置 →「将所有证书放入下列存储」→ 选"受信任的根证书颁发机构"。
38
+ Install guidance to send the user (pick the one matching their device):
45
39
 
46
- 如果 grix_file_link 调用失败(如未连接 Tailscale),告诉用户文件的本地路径,让他们自行获取。
40
+ - **iPhone / iPad**: open `ca_install_url` in Safari — it prompts "This website is trying to download a configuration profile" → Allow → a "Profile Downloaded" entry appears at the top of Settings; tap it to install → then go to Settings → General → About → Certificate Trust Settings and turn the switch on for that certificate (enable full trust). This step is mandatory, otherwise the system still won't trust it. (iOS must use Safari; other browsers won't trigger profile installation. The server already returns a `.mobileconfig` based on the device, so no manual parameters are needed.)
41
+ - **Mac**: click the link to download the `.crt` → double-click to open it in Keychain Access → find the certificate → double-click → expand "Trust" → set "When using this certificate" to "Always Trust".
42
+ - **Android**: tap `ca_install_url` to download the `.crt` → Settings → Security → Encryption & credentials → Install a certificate → CA certificate, and pick the downloaded file (recent Android versions do not support one-tap install from the browser; the Settings route is required).
43
+ - **Windows**: double-click the `.crt` → "Install Certificate" → choose the store location → "Place all certificates in the following store" → select "Trusted Root Certification Authorities".
47
44
 
48
- ## 适用范围(重要):这套证书只管 grix_file_link 自己的服务
45
+ If the `grix_file_link` call fails (e.g. Tailscale is not connected), tell the
46
+ user the local file path and let them retrieve it themselves.
49
47
 
50
- 上面这张内置 CA 和它签发的证书,**只让 grix_file_link 起的下载服务(本机 tailnet 地址)被设备信任**。它不负责、也解决不了用户自己另起的其它服务。
48
+ ## Scope (important): this CA only covers `grix_file_link`'s own service
51
49
 
52
- 当用户在 tailnet 上**自行启动了别的 HTTPS 服务**(自建网站 / API / 媒体服务等),用浏览器打开报证书错误时,要分清这是另一回事:
50
+ The built-in CA above and the certificates it issues **only make the download
51
+ service started by `grix_file_link` (this machine's tailnet address) trusted by
52
+ the device**. It does not cover — and cannot fix — other services the user
53
+ starts themselves.
53
54
 
54
- - 设备只信任「它亲自装过的那张 CA 签出来的证书」。自建服务用的是它自己的 CA(如 mkcert),设备没装过,所以报 `不受信任 / 冒充 / NET::ERR_CERT_AUTHORITY_INVALID`——这跟有没有装 Grix 的 CA 无关。
55
- - 想让自建服务也被信任,只有两条路:
56
- 1. 让该服务改用一张设备已信任的 CA 来签证书,并且叶子证书有效期 **≤ 398 天**(否则即使信任了 CA,仍会报 `NET::ERR_CERT_VALIDITY_TOO_LONG`);
57
- 2. 或者干脆别让用户开自己的 HTTPS,把要分享的文件改走 grix_file_link,由本机内置服务发出来,自动被信任、有效期也合规。
58
- - 注意:Grix 内置 CA 的**私钥只留在跑连接器的这台机器上、不对外发**(`ca_install_url` 只给公钥证书,不给私钥)。因此只有**与连接器同一台机器**上的服务能借这张 CA 来签证书;其它机器上的服务必须自带 CA,并在每台设备各装一次。
55
+ When the user has **started their own HTTPS service on the tailnet** (a
56
+ self-hosted site / API / media server, etc.) and the browser reports a
57
+ certificate error, be clear that this is a separate matter:
59
58
 
60
- 简而言之:能用 grix_file_link 发的就用它发,最省事;自建服务的证书信任问题不在本技能职责内,需在那个服务侧自行解决。
59
+ - A device only trusts "certificates issued by a CA it has personally installed". A self-hosted service uses its own CA (e.g. mkcert), which the device never installed, so it reports `not trusted / impersonation / NET::ERR_CERT_AUTHORITY_INVALID` — this has nothing to do with whether the Grix CA is installed.
60
+ - There are only two ways to make a self-hosted service trusted:
61
+ 1. Have that service sign its certificate with a CA the device already trusts, and keep the leaf certificate's validity **≤ 398 days** (otherwise, even with the CA trusted, it reports `NET::ERR_CERT_VALIDITY_TOO_LONG`);
62
+ 2. Or simply don't have the user run their own HTTPS — share the files via `grix_file_link` instead, served by the built-in service, automatically trusted and with compliant validity.
63
+ - Note: the Grix built-in CA's **private key stays only on the machine running the connector and is never handed out** (`ca_install_url` serves the public certificate only, not the private key). So only services **on the same machine as the connector** can borrow this CA for signing; services on other machines must bring their own CA and install it once per device.
61
64
 
62
- ## 两条信任路:Grix app vs 系统浏览器(排障先分清)
65
+ In short: if it can be sent via `grix_file_link`, send it that way — it's the
66
+ least trouble. Certificate trust for self-hosted services is outside this
67
+ skill's responsibility and must be solved on that service's side.
63
68
 
64
- 同一个链接,在 **Grix app 里打开** 和在 **系统浏览器(Safari / Chrome)里打开**,走的是两套完全独立的证书信任机制,报错和解法都不同。用户反馈"打不开 / 证书报错"时,先问清他是在哪条路上看的。
69
+ ## Two trust paths: inside the Grix app vs. the system browser (diagnose this first)
65
70
 
66
- - **Grix app 内**(图片预览、应用内下载等走 app 自带的网络栈):app 内置了一条信任规则——只要链接是 tailnet 地址(`100.64.0.0/10` 段)、且证书签发者名字里含 `Grix Tailnet Local CA`,就直接放行,**用户不需要在设备上安装任何证书**。所以 grix_file_link 的链接在 app 里通常开箱即用、零安装。app 内若仍打不开,多半是没走 tailnet IP、或证书签发者名字不符,而不是"没装证书"。
67
- - **系统浏览器(Safari / Chrome)**:走的是手机 / 电脑的系统信任库,跟 app 那条规则毫无关系。必须按上面《HTTPS 与首次安装信任证书》把根 CA 装好(iOS 装 .mobileconfig 并开完全信任,安卓走系统设置,等等)。用户截图里出现的 `NET::ERR_CERT_AUTHORITY_INVALID`、`冒充`、`NET::ERR_CERT_VALIDITY_TOO_LONG`,几乎都是系统浏览器这条路,引导他装 CA 即可。
71
+ The same link opened **in the Grix app** and **in a system browser (Safari /
72
+ Chrome)** goes through two entirely independent certificate-trust mechanisms,
73
+ with different errors and different fixes. When a user reports "won't open /
74
+ certificate error", first find out which path they were on.
68
75
 
69
- 对**自建服务**同理,按用户要在哪看来定:
76
+ - **Inside the Grix app** (image previews, in-app downloads, etc. use the app's own network stack): the app has a built-in trust rule — if the link is a tailnet address (`100.64.0.0/10` range) and the certificate's issuer name contains `Grix Tailnet Local CA`, it is allowed through, and **the user does not need to install any certificate on the device**. So `grix_file_link` links generally work out of the box in the app with zero install. If it still won't open in the app, it's most likely not a tailnet IP, or the issuer name doesn't match — not a "certificate not installed" problem.
77
+ - **System browser (Safari / Chrome)**: this goes through the phone's / computer's system trust store, which has nothing to do with the app's rule. The root CA must be installed as described in *HTTPS and the one-time trust-certificate install* above (on iOS, install the `.mobileconfig` and enable full trust; on Android, go through Settings; and so on). `NET::ERR_CERT_AUTHORITY_INVALID`, "impersonation", and `NET::ERR_CERT_VALIDITY_TOO_LONG` in the user's screenshots are almost always this path — guide them to install the CA.
70
78
 
71
- - 只需要在 **Grix app 内**展示:自建服务无需向用户分发私钥、也无需装任何证书——只要①服务挂在 tailnet IP 上、②把它那张 CA 的签发者名字带上 `Grix Tailnet Local CA`,app 就会信任。
72
- - 需要在 **系统浏览器** 里打开:app 那条规则不起作用,仍需在每台设备装上该服务自己的 CA(见上一节《适用范围》)。
79
+ The same distinction applies to **self-hosted services**, decided by where the user wants to view it:
73
80
 
74
- 注意:app 内这条是**按签发者名字字符串匹配**,不是按某把 CA 的公钥指纹绑定。它仅作为私有 tailnet 内的便利取舍,**不是强安全边界**——同一 tailnet 内任何人只要把 CA 命名成相同前缀就会被 app 信任。不要据此把它当作可对抗攻击者的信任根。
81
+ - Only needs to display **inside the Grix app**: the self-hosted service does not have to distribute a private key to the user, and no certificate install is needed — it only needs to ① be served on a tailnet IP, and ② have its CA's issuer name carry `Grix Tailnet Local CA`, and the app will trust it.
82
+ - Needs to open in a **system browser**: the app's rule does not apply; that service's own CA must still be installed on each device (see *Scope* above).
83
+
84
+ Note: the in-app rule is **a string match on the issuer name**, not a binding to
85
+ a specific CA's public-key fingerprint. It is a convenience trade-off within a
86
+ private tailnet only and **is not a hard security boundary** — anyone on the
87
+ same tailnet can have the app trust them simply by naming their CA with the same
88
+ prefix. Do not treat it as a trust root that stands up to an attacker.
package/dist/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- import{readFileSync as L,readdirSync as Q,writeFileSync as W}from"node:fs";import{join as S}from"node:path";import{AgentInstance as T}from"./bridge/bridge.js";import{buildSharedInstanceConfig as K,diffSharedOwners as z,sharedInstanceKey as G,SharedOwnersCache as J}from"./manager-share-config.js";import{isAgentDeletedError as M}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as l}from"./core/log/index.js";import{resolveClientVersion as V}from"./core/util/client-version.js";import{UpgradeChecker as X}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as Y}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as Z}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ee,logDefaultSkillsCheck as te,cleanupProjectedSkills as j}from"./default-skills/index.js";import{supportsDirectProviderConfig as ne}from"./core/config/provider-env.js";import{resolveCopilotCommand as D}from"./core/runtime/copilot-resolve.js";import{getCliVersion as ae,resolveCliPath as re}from"./core/util/cli-probe.js";import{AgentInstaller as se}from"./core/installer/installer.js";import{reportInstallFailure as oe}from"./core/observability/sentry.js";const ie=8e3;function ce(){const n=D();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:"agent"},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"kimi",command:"kimi"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function le(){return Promise.all(ce().map(async n=>{const e=await re(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await ae(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function de(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:"agent"};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"kimi":return{adapterType:"acp",command:"kimi",args:["acp"],enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=D();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":return{adapterType:"acp",command:"hermes",args:["acp"],enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function P(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function pe(n,e){return S(A.data,`session-bindings-${P(n,e)}.json`)}function me(n,e){return S(A.data,`active-events-${P(n,e)}.json`)}function ue(...n){const e=[],t=new Set;for(const r of n)for(const a of r??[]){const s=String(a??"").trim(),o=s.toLowerCase();!s||t.has(o)||(t.add(o),e.push(s))}return e.length>0?e:void 0}function ge(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},r=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??r.maxConcurrent??1,maxQueued:e?.max_queued??r.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??r.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function x(n){const e=V(),t=String(n.client_type??"").trim().toLowerCase(),r=de(t),a=String(n.ws_url??"").trim(),s="get_session_usage",o="get_rate_limits",c="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!a)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const i=r.adapterType,p=i==="acp",g=t==="qwen",f={...r.options??{}},w=i==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_service_tier","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",s,o]}:null,d=i==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",s,o]}:null,u=g?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s],adapterHint:"qwen/base"}:null,h=i==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",s]}:null,m=i==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,_=i==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",s,o]}:null,y=i==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,b=i==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s]}:null,C=i==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",s]}:null,H=p&&!g?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",s]}:null,q=t==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",s,o]}:null,R=i==="codex"||i==="claude"||t==="gemini"?["session_control","set_model","set_mode"]:void 0,N=[s,o];p&&f.raw_transport===void 0&&(f.raw_transport=t==="gemini");const F=`${t}/base`,I=Z(i,t),B=I==="kiro"?void 0:process.cwd(),k=ee({mode:I,projectDir:B}),E=k.filter($=>$.source==="connector").map($=>$.name);E.length>0&&l.info("manager",`[${n.name}] injecting connector skills: [${E.join(", ")}]`);let U=k.length>0?k:void 0;return{name:n.name,adapterType:i,aibot:{url:a,agentId:n.agent_id,apiKey:n.api_key,clientType:t,clientVersion:e,adapterHint:r.adapterHint??u?.adapterHint??h?.adapterHint??m?.adapterHint??_?.adapterHint??b?.adapterHint??C?.adapterHint??F,capabilities:w?.capabilities??y?.capabilities??h?.capabilities??m?.capabilities??_?.capabilities??b?.capabilities??C?.capabilities??u?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:ue(w?.localActions??y?.localActions??d?.localActions??h?.localActions??m?.localActions??_?.localActions??b?.localActions??C?.localActions??u?.localActions??q?.localActions??H?.localActions??["exec_approve","exec_reject"],R,N,["connector_rollback","connector_upgrade_push",c,"configure_gateway_provider"]),skills:U},agent:{command:r.command,args:[...r.args??[],...n.args??[]],env:n.env,clientType:t,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:f,acpAuthMethod:f.auth_method,acpInitialMode:f.initial_mode,acpMcpTools:f.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:pe(n.name,n.agent_id),activeEventStorePath:me(n.name,n.agent_id),...r.enableSessionBinding||p?{enableSessionBinding:!0}:{},...r.autoInjectArgs||t==="reasonix"&&n.provider?.model?.trim()?{autoInjectArgs:{...r.autoInjectArgs??{},...t==="reasonix"&&n.provider?.model?.trim()?{model:n.provider.model.trim()}:{}}}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:ge(i,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function he(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):ie}function O(n){const e=[],t=[],r=Q(n).filter(a=>a.endsWith(".json")).sort();for(const a of r)try{const s=JSON.parse(L(S(n,a),"utf-8"));if(Array.isArray(s.agents)){if(s.agents.length===0){t.push({file:a,error:"agents array is empty"});continue}for(const o of s.agents)e.push({entry:o,file:a})}else t.push({file:a,error:"unrecognized config format"})}catch(s){t.push({file:a,error:s instanceof Error?s.message:String(s)})}return{entries:e,fileErrors:t}}function v(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(v).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${v(e[r])}`).join(",")}}`}function fe(n,e){return v(n)===v(e)}class Oe{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new J(A.data);installer=new se;async start(e){const t=e??A.config;this.configDir=t,l.info("manager",`Loading configs from ${t}`),te(),j(),this.globalConfigStore=new Y(S(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:a}=O(t);for(const i of a)l.error("manager",`Failed to load ${i.file}: ${i.error}`);if(r.length===0&&a.length===0)throw new Error(`No config files found in ${t}`);const s=[];let o=a.length;for(const{entry:i,file:p}of r)try{const g=x(i);s.push({config:g,entry:i,file:p}),l.info("manager",`Loaded ${g.name} (${g.adapterType??"acp"}) from ${p}`)}catch(g){const f=typeof i?.name=="string"?i.name:"<unknown>";l.error("manager",`Invalid agent config in ${p} (name=${f}): ${g}`),o++}let c=0;if(s.length>0){const i=he();l.info("manager",`Starting ${s.length} agent(s), startup wait=${i}ms`);const p=()=>this.upgradeChecker?.triggerCheck(),g=d=>{this.instances=this.instances.filter(u=>u!==d)},f=s.map(({config:d,entry:u,file:h})=>{const m=new T(d,this.globalConfigStore);return m.setUpgradeTrigger(p),m.setShareSetHandler(_=>this.onShareSet(d,_)),m.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(d.name)),this.instances.push(m),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:u,file:h}),{config:d,instance:m,startPromise:m.start()}}),w=await Promise.all(f.map(async d=>{const u=await new Promise(h=>{let m=!1;const _=setTimeout(()=>{m||(m=!0,h({kind:"timeout"}))},i);d.startPromise.then(()=>{m||(m=!0,clearTimeout(_),h({kind:"started"}))}).catch(y=>{m||(m=!0,clearTimeout(_),h({kind:"failed",error:y}))})});return{task:d,outcome:u}}));for(const{task:d,outcome:u}of w){if(u.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(u.kind==="failed"){if(M(u.error)){this.onAgentDeletedByPlatform(d.config.name);continue}g(d.instance),l.error("manager",`Failed to start ${d.config.name}: ${u.error}`);continue}c++,l.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{l.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(h=>{if(M(h)){this.onAgentDeletedByPlatform(d.config.name);return}g(d.instance),l.error("manager",`Delayed start failed: ${d.config.name}: ${h}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-c);l.info("manager",`${d}/${s.length} agent(s) running now`)}c>0&&l.warn("manager",`${c} agent(s) still connecting in background`)}if(this.instances.length===0&&s.length>0)throw new Error("All agent configurations failed to start");s.length>0&&(this.upgradeChecker=new X(s.map(({config:i})=>({apiKey:i.aibot.apiKey,wsUrl:i.aibot.url})),()=>this.instances.some(i=>i.hasPendingWork())||[...this.sharedInstances.values()].some(i=>i.hasPendingWork())),await this.upgradeChecker.start())}async stop(){l.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],j(),l.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const a=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,a)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:r,toRemove:a}=z(e.name,this.sharedInstances.keys(),t);for(const s of r){if(this.stopping)break;const o=G(e.name,s);try{const c=K(e,s),i=new T(c,this.globalConfigStore);this.sharedInstances.set(o,i),await i.start(),l.info("manager",`shared instance started: ${o}`)}catch(c){this.sharedInstances.delete(o);const i=c instanceof Error?c.message:String(c);/Auth failed/i.test(i)?(this.sharedOwnersCache.remove(e.name,s),l.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):l.error("manager",`start shared instance failed ${o}: ${c}`)}}for(const{key:s}of a){const o=this.sharedInstances.get(s);o&&(this.sharedInstances.delete(s),o.stop().catch(c=>l.error("manager",`stop shared instance failed ${s}: ${c}`)),l.info("manager",`shared instance stopped: ${s}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(l.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),l.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const r=x(e);if(this.instances.some(a=>a.name===r.name))throw new Error(`Agent "${r.name}" already exists`);await this.startInstanceFromConfig(r,e,t)}async startInstanceFromConfig(e,t,r){const a=new T(e,this.globalConfigStore);a.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),a.setShareSetHandler(s=>this.onShareSet(e,s)),a.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),a.setProviderConfigHandler(s=>this.setAgentProvider(e.name,s)),a.setRelayEnvSettledHandler(()=>this.relayEnvSettledHandler?.()),await a.start(),this.instances.push(a),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:r}),this.relayEnvSettledHandler?.()}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Removed agent: ${e}`),this.relayEnvSettledHandler?.()}async removeAgentInternal(e,t){const r=this.instances.findIndex(c=>c.name===e),a=r===-1?void 0:this.instances[r];r!==-1&&this.instances.splice(r,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const s=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),s&&await s.catch(()=>{});const o=`${e}#shared:`;for(const[c,i]of[...this.sharedInstances.entries()])c.startsWith(o)&&(this.sharedInstances.delete(c),i.stop().catch(p=>l.error("manager",`stop shared instance failed ${c}: ${p}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),a&&await a.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(r=>r.name===e)))return;l.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){l.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=S(this.configDir,"agents.json");try{const t=[];for(const[,a]of this.configMap){const s=this.rawConfigMap.get(a.name)?.entry.provider;t.push({name:a.name,ws_url:a.aibot.url,agent_id:a.aibot.agentId,api_key:a.aibot.apiKey,client_type:a.aibot.clientType,...s?{provider:s}:{}})}W(e,JSON.stringify({agents:t},null,4)+`
2
- `,"utf-8")}catch(t){l.error("manager",`Failed to persist agents config: ${t}`)}}isAgentBusy(e){return this.instances.find(r=>r.name===e)?.hasPendingWork()??!1}getAgentsWithStaleRelayEnv(){return this.instances.filter(e=>e.hasStaleRelayEnv()).map(e=>e.name)}markAgentRelayEnvStale(e){const t=this.instances.find(r=>r.name===e);return t?(t.markRelayEnvStale(),!0):!1}relayEnvSettledHandler;setRelayEnvSettledHandler(e){this.relayEnvSettledHandler=e}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),l.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const r=this.rawConfigMap.get(e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const a={...r.entry,provider:t};await this.replaceInstance(e,a,r.file),this.persistAgentsConfig(),l.info("manager",`Updated provider config for agent: ${e}`)}async clearDirectProviderConfigs(){const e=[...this.rawConfigMap.entries()].filter(([,r])=>!!r.entry.provider&&ne(r.entry.client_type)).map(([r])=>r),t=[];for(const r of e)try{await this.setAgentProvider(r,void 0),t.push(r)}catch(a){l.warn("manager",`Failed to clear provider config for "${r}": ${a instanceof Error?a.message:String(a)}`)}return t}async replaceInstance(e,t,r){const a=x(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(a,t,r),this.restoreCachedSharedInstances(a)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t}=O(this.configDir);if(t.length>0){const o=t.map(c=>`${c.file}: ${c.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${o}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const r=new Map;for(const o of e){const c=String(o.entry?.name??"").trim();c&&(r.has(c)&&l.warn("manager",`reload: duplicate agent name "${c}", last one wins`),r.set(c,o))}const a={added:[],removed:[],restarted:[],unchanged:[],failed:[]},s=new Set(this.rawConfigMap.keys());for(const o of s)if(!r.has(o))try{await this.removeAgentInternal(o),a.removed.push(o)}catch(c){a.failed.push({name:o,error:c instanceof Error?c.message:String(c)})}for(const[o,c]of r){const i=this.rawConfigMap.get(o);if(i)if(fe(i.entry,c.entry))this.rawConfigMap.set(o,c),a.unchanged.push(o);else try{await this.replaceInstance(o,c.entry,c.file),a.restarted.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}else try{await this.addAgentInternal(c.entry,c.file),a.added.push(o)}catch(p){a.failed.push({name:o,error:p instanceof Error?p.message:String(p)})}}return l.info("manager",`reload done: +${a.added.length} -${a.removed.length} ~${a.restarted.length} =${a.unchanged.length} !${a.failed.length}`),a}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return _e(this.instances,e)}async probeOne(e,t={}){return we(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return oe(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function _e(n,e){const t=e.concurrency??4,r=Date.now(),a=new Array(n.length);await new Promise(p=>{let g=0,f=0;const w=n.length;if(w===0){p();return}function d(m){const _=n[m];_.probe(e).then(y=>{a[m]=y,u()},y=>{a[m]={agent_name:_.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},u()})}function u(){f++,g<w?d(g++):f===w&&p()}const h=Math.min(t,w);for(let m=0;m<h;m++)d(g++)});const s=a.filter(p=>p.status==="healthy").length,o=a.filter(p=>p.status==="degraded").length,c=a.filter(p=>p.status==="unavailable").length,i=await le();return{ok:s===a.length&&a.length>0,total:a.length,healthy:s,degraded:o,unavailable:c,installed_clients:i,agents:a,probed_at:r,duration_ms:Date.now()-r}}async function we(n,e,t){const r=n.find(a=>a.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.probe(t)}export{Oe as Manager,le as probeInstalledClientCommands,_e as probeInstances,we as probeOneInstance};
1
+ import{readFileSync as W,readdirSync as K,writeFileSync as G}from"node:fs";import{join as S}from"node:path";import{AgentInstance as I}from"./bridge/bridge.js";import{buildSharedInstanceConfig as z,diffSharedOwners as J,sharedInstanceKey as V,SharedOwnersCache as X}from"./manager-share-config.js";import{isAgentDeletedError as j}from"./core/aibot/errors.js";import{GRIX_PATHS as A,log as l}from"./core/log/index.js";import{resolveClientVersion as Y}from"./core/util/client-version.js";import{UpgradeChecker as Z}from"./core/upgrade/upgrade-checker.js";import{AgentGlobalConfigStore as ee}from"./core/persistence/agent-global-config-store.js";import{resolveSkillScanMode as te}from"./adapter/claude/skill-scanner.js";import{buildReportedSkills as ne,logDefaultSkillsCheck as ae,cleanupProjectedSkills as D}from"./default-skills/index.js";import{supportsDirectProviderConfig as re}from"./core/config/provider-env.js";import{resolveCopilotCommand as P}from"./core/runtime/copilot-resolve.js";import{resolveCursorCommand as O}from"./core/runtime/cursor-resolve.js";import{getCliVersion as se,resolveCliPath as oe}from"./core/util/cli-probe.js";import{AgentInstaller as ie}from"./core/installer/installer.js";import{reportInstallFailure as ce}from"./core/observability/sentry.js";const le=8e3;function de(){const n=P();return[{clientType:"openclaw",command:"openclaw"},{clientType:"claude",command:"claude"},{clientType:"codex",command:"codex"},{clientType:"gemini",command:"gemini"},{clientType:"qwen",command:"qwen"},{clientType:"hermes",command:"hermes"},{clientType:"reasonix",command:"reasonix"},{clientType:"codewhale",command:"codewhale"},{clientType:"opencode",command:"opencode"},{clientType:"cursor",command:O()},{clientType:"pi",command:"pi"},{clientType:"openhuman",command:"openhuman-core"},{clientType:"kiro",command:"kiro-cli"},{clientType:"kimi",command:"kimi"},{clientType:"copilot",command:n.command},{clientType:"agy",command:"agy"}]}async function pe(){return Promise.all(de().map(async n=>{const e=await oe(n.command);if(!e)return{client_type:n.clientType,command:n.command,installed:!1,path:null,version:null};const t=await se(e,n.versionArgs??["--version"]);return{client_type:n.clientType,command:n.command,installed:!0,path:e,version:t.version,error:t.error}}))}function me(n){switch(n){case"claude":return{adapterType:"claude",command:"claude"};case"codex":return{adapterType:"codex",command:"codex",options:{sandboxMode:"danger-full-access"}};case"gemini":return{adapterType:"acp",command:"gemini",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"qwen":return{adapterType:"acp",command:"qwen",adapterHint:"qwen/base",autoInjectArgs:{acp:!0},enableSessionBinding:!0};case"pi":return{adapterType:"pi",command:"pi"};case"cursor":return{adapterType:"cursor",command:O()};case"reasonix":return{adapterType:"acp",command:"reasonix",args:["acp"],enableSessionBinding:!0};case"codewhale":return{adapterType:"codewhale",command:"codewhale",enableSessionBinding:!0};case"openhuman":return{adapterType:"openhuman",command:"openhuman-core",enableSessionBinding:!0};case"kiro":return{adapterType:"acp",command:"kiro-cli",args:["acp"],enableSessionBinding:!0};case"kimi":return{adapterType:"acp",command:"kimi",args:["acp"],options:{initial_mode:"yolo"},enableSessionBinding:!0};case"opencode":return{adapterType:"opencode",command:"opencode",args:["serve"],enableSessionBinding:!0};case"copilot":{const e=P();return{adapterType:"acp",command:e.command,args:[...e.prefixArgs,"--acp"],enableSessionBinding:!0}}case"agy":return{adapterType:"agy",command:"agy",enableSessionBinding:!0};case"hermes":return{adapterType:"acp",command:"hermes",args:["acp"],enableSessionBinding:!0};default:throw new Error(`Unsupported client_type: ${n}`)}}function q(n,e){return String(n??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||String(e??"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")||"default"}function ue(n,e){return S(A.data,`session-bindings-${q(n,e)}.json`)}function ge(n,e){return S(A.data,`active-events-${q(n,e)}.json`)}function he(...n){const e=[],t=new Set;for(const r of n)for(const s of r??[]){const i=String(s??"").trim(),o=i.toLowerCase();!i||t.has(o)||(t.add(o),e.push(i))}return e.length>0?e:void 0}function fe(n,e){const t={claude:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codex:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},cursor:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},acp:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},pi:{maxConcurrent:1,maxQueued:5,queueTimeoutMs:0},codewhale:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},openhuman:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},opencode:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0},agy:{maxConcurrent:1,maxQueued:3,queueTimeoutMs:0}},r=t[n]??t.acp;return{maxConcurrent:e?.max_concurrent??r.maxConcurrent??1,maxQueued:e?.max_queued??r.maxQueued??3,queueTimeoutMs:e?.queue_timeout_ms??r.queueTimeoutMs??0,cancelableQueued:!0,cancelableRunning:!0}}function b(n,e={}){const t=Y(),r=String(n.client_type??"").trim().toLowerCase(),s=me(r),i=String(n.ws_url??"").trim(),o="get_session_usage",p="get_rate_limits",c="get_agent_global_config";if(!n.name?.trim())throw new Error("agent name is required");if(!i)throw new Error(`agent ${n.name}: ws_url is required`);if(!n.agent_id?.trim())throw new Error(`agent ${n.name}: agent_id is required`);if(!n.api_key?.trim())throw new Error(`agent ${n.name}: api_key is required`);const a=s.adapterType,u=a==="acp",m=r==="qwen",_={...s.options??{}},w=a==="codex"?{capabilities:["local_action_v1","agent_invoke"],localActions:["session_control","get_context","set_model","set_mode","set_reasoning_effort","set_service_tier","set_sandbox_mode","exec_approve","exec_reject","file_list","create_folder","turn_interrupt","permission_approve","permission_reject","thread_compact",o,p]}:null,d=a==="claude"?{localActions:["session_control","set_mode","set_model","claude_interaction_reply","exec_approve","exec_reject","file_list","create_folder","thread_compact",o,p]}:null,f=m?{capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o],adapterHint:"qwen/base"}:null,h=a==="pi"?{adapterHint:"pi/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","get_context","file_list","create_folder",o]}:null,g=a==="openhuman"?{adapterHint:"openhuman/base",capabilities:["local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,y=a==="cursor"?{adapterHint:"cursor/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","set_mode","get_context","file_list","create_folder",o,p]}:null,v=a==="codewhale"?{capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,k=a==="opencode"?{adapterHint:"opencode/base",capabilities:["stream_chunk","local_action_v1"],localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,$=a==="agy"?{adapterHint:"agy/base",capabilities:["stream_chunk","local_action_v1"],localActions:["session_control","set_model","file_list","create_folder",o]}:null,N=u&&!m?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder",o]}:null,R=r==="kiro"?{localActions:["exec_approve","exec_reject","permission_approve","permission_reject","session_control","set_model","set_mode","file_list","create_folder","thread_compact",o,p]}:null,B=a==="codex"||a==="claude"||r==="gemini"?["session_control","set_model","set_mode"]:void 0,F=[o,p];u&&_.raw_transport===void 0&&(_.raw_transport=r==="gemini");const U=`${r}/base`,E=te(a,r),L=E==="kiro"?void 0:process.cwd(),T=ne({mode:E,projectDir:L}),M=T.filter(x=>x.source==="connector").map(x=>x.name);M.length>0&&!e.quiet&&l.info("manager",`[${n.name}] injecting connector skills: [${M.join(", ")}]`);let Q=T.length>0?T:void 0;return{name:n.name,adapterType:a,aibot:{url:i,agentId:n.agent_id,apiKey:n.api_key,clientType:r,clientVersion:t,adapterHint:s.adapterHint??f?.adapterHint??h?.adapterHint??g?.adapterHint??y?.adapterHint??k?.adapterHint??$?.adapterHint??U,capabilities:w?.capabilities??v?.capabilities??h?.capabilities??g?.capabilities??y?.capabilities??k?.capabilities??$?.capabilities??f?.capabilities??["stream_chunk","local_action_v1","connector_upgrade"],localActions:he(w?.localActions??v?.localActions??d?.localActions??h?.localActions??g?.localActions??y?.localActions??k?.localActions??$?.localActions??f?.localActions??R?.localActions??N?.localActions??["exec_approve","exec_reject"],B,F,["connector_rollback","connector_upgrade_push",c,"configure_gateway_provider"]),skills:Q},agent:{command:s.command,args:[...s.args??[],...n.args??[]],env:n.env,clientType:r,...n.provider?{provider:{...n.provider.base_url?.trim()?{baseUrl:n.provider.base_url.trim()}:{},...n.provider.api_key?.trim()?{apiKey:n.provider.api_key.trim()}:{},...n.provider.model?.trim()?{model:n.provider.model.trim()}:{}}}:{}},adapterOptions:_,acpAuthMethod:_.auth_method,acpInitialMode:_.initial_mode,acpMcpTools:_.acp_mcp_tools,promptTimeoutMs:n.prompt_timeout_ms,bindingsPath:ue(n.name,n.agent_id),activeEventStorePath:ge(n.name,n.agent_id),...s.enableSessionBinding||u?{enableSessionBinding:!0}:{},...s.autoInjectArgs||r==="reasonix"&&n.provider?.model?.trim()?{autoInjectArgs:{...s.autoInjectArgs??{},...r==="reasonix"&&n.provider?.model?.trim()?{model:n.provider.model.trim()}:{}}}:{},poolMaxSize:n.pool?.maxSize,poolIdleTimeoutMs:n.pool?.idleTimeoutMs,eventQueue:fe(a,n.event_queue),logDir:A.log,providerBaseUrl:n.provider_base_url?.trim()||void 0,providerApiKey:n.provider_api_key?.trim()||void 0}}function _e(){const n=process.env.GRIX_AGENT_STARTUP_WAIT_MS,e=Number(n);return Number.isFinite(e)&&e>=500?Math.floor(e):le}function H(n){const e=[],t=[],r=[],s=K(n).filter(i=>i.endsWith(".json")).sort();for(const i of s)try{const o=JSON.parse(W(S(n,i),"utf-8"));if(o===null||typeof o!="object"||Array.isArray(o)||!("agents"in o)){r.push(i);continue}if(!Array.isArray(o.agents)){t.push({file:i,error:"agents is not an array"});continue}if(o.agents.length===0){t.push({file:i,error:"agents array is empty"});continue}for(const p of o.agents)e.push({entry:p,file:i})}catch(o){t.push({file:i,error:o instanceof Error?o.message:String(o)})}return{entries:e,fileErrors:t,skipped:r}}function C(n){if(n===null||typeof n!="object")return JSON.stringify(n)??"null";if(Array.isArray(n))return`[${n.map(C).join(",")}]`;const e=n;return`{${Object.keys(e).sort().map(r=>`${JSON.stringify(r)}:${C(e[r])}`).join(",")}}`}function ye(n,e){return C(n)===C(e)}class Ne{instances=[];configMap=new Map;rawConfigMap=new Map;reloadChain=Promise.resolve();sharedInstances=new Map;shareSyncChains=new Map;stopping=!1;deletedAgentCleanups=new Set;upgradeChecker=null;globalConfigStore;configDir=A.config;sharedOwnersCache=new X(A.data);installer=new ie;async start(e){const t=e??A.config;this.configDir=t,l.info("manager",`Loading configs from ${t}`),ae(),D(),this.globalConfigStore=new ee(S(A.data,"agent-global-configs.json")),this.globalConfigStore.load();const{entries:r,fileErrors:s,skipped:i}=H(t);for(const a of s)l.error("manager",`Failed to load ${a.file}: ${a.error}`);for(const a of i)l.info("manager",`Skipped ${a}: not an agent config (no "agents" key)`);if(r.length===0&&s.length===0&&i.length===0)throw new Error(`No config files found in ${t}`);const o=[];let p=s.length;for(const{entry:a,file:u}of r)try{const m=b(a);o.push({config:m,entry:a,file:u}),l.info("manager",`Loaded ${m.name} (${m.adapterType??"acp"}) from ${u}`)}catch(m){const _=typeof a?.name=="string"?a.name:"<unknown>";l.error("manager",`Invalid agent config in ${u} (name=${_}): ${m}`),p++}let c=0;if(o.length>0){const a=_e();l.info("manager",`Starting ${o.length} agent(s), startup wait=${a}ms`);const u=()=>this.upgradeChecker?.triggerCheck(),m=d=>{this.instances=this.instances.filter(f=>f!==d)},_=o.map(({config:d,entry:f,file:h})=>{const g=new I(d,this.globalConfigStore);return g.setUpgradeTrigger(u),g.setShareSetHandler(y=>this.onShareSet(d,y)),g.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(d.name)),this.instances.push(g),this.configMap.set(d.name,d),this.rawConfigMap.set(d.name,{entry:f,file:h}),{config:d,instance:g,startPromise:g.start()}}),w=await Promise.all(_.map(async d=>{const f=await new Promise(h=>{let g=!1;const y=setTimeout(()=>{g||(g=!0,h({kind:"timeout"}))},a);d.startPromise.then(()=>{g||(g=!0,clearTimeout(y),h({kind:"started"}))}).catch(v=>{g||(g=!0,clearTimeout(y),h({kind:"failed",error:v}))})});return{task:d,outcome:f}}));for(const{task:d,outcome:f}of w){if(f.kind==="started"){this.restoreCachedSharedInstances(d.config);continue}if(f.kind==="failed"){if(j(f.error)){this.onAgentDeletedByPlatform(d.config.name);continue}m(d.instance),l.error("manager",`Failed to start ${d.config.name}: ${f.error}`);continue}c++,l.warn("manager",`Startup pending for ${d.config.name}, continue retrying in background`),d.startPromise.then(()=>{l.info("manager",`Delayed start succeeded: ${d.config.name}`),this.restoreCachedSharedInstances(d.config)}).catch(h=>{if(j(h)){this.onAgentDeletedByPlatform(d.config.name);return}m(d.instance),l.error("manager",`Delayed start failed: ${d.config.name}: ${h}`)})}if(this.instances.length>0){const d=Math.max(0,this.instances.length-c);l.info("manager",`${d}/${o.length} agent(s) running now`)}c>0&&l.warn("manager",`${c} agent(s) still connecting in background`)}if(this.instances.length===0&&o.length>0)throw new Error("All agent configurations failed to start");o.length>0&&(this.upgradeChecker=new Z(o.map(({config:a})=>({apiKey:a.aibot.apiKey,wsUrl:a.aibot.url})),()=>this.instances.some(a=>a.hasPendingOrBackgroundWork())||[...this.sharedInstances.values()].some(a=>a.hasPendingOrBackgroundWork())),await this.upgradeChecker.start())}async stop(){l.info("manager","Stopping all agents..."),this.stopping=!0,this.upgradeChecker?.stop(),await Promise.allSettled([...this.shareSyncChains.values()]),this.shareSyncChains.clear();const e=[...this.sharedInstances.values()];this.sharedInstances.clear(),await Promise.allSettled([...this.instances.map(t=>t.stop()),...e.map(t=>t.stop())]),await this.globalConfigStore?.flush(),this.instances=[],D(),l.info("manager","All stopped")}onShareSet(e,t){this.sharedOwnersCache.save(e.name,t);const s=(this.shareSyncChains.get(e.name)??Promise.resolve()).catch(()=>{}).then(()=>this.syncSharedInstances(e,t));this.shareSyncChains.set(e.name,s)}async syncSharedInstances(e,t){if(this.stopping)return;const{toAdd:r,toRemove:s}=J(e.name,this.sharedInstances.keys(),t);for(const i of r){if(this.stopping)break;const o=V(e.name,i);try{const p=z(e,i),c=new I(p,this.globalConfigStore);this.sharedInstances.set(o,c),await c.start(),l.info("manager",`shared instance started: ${o}`)}catch(p){this.sharedInstances.delete(o);const c=p instanceof Error?p.message:String(p);/Auth failed/i.test(c)?(this.sharedOwnersCache.remove(e.name,i),l.warn("manager",`shared instance auth rejected, removed from cache: ${o}`)):l.error("manager",`start shared instance failed ${o}: ${p}`)}}for(const{key:i}of s){const o=this.sharedInstances.get(i);o&&(this.sharedInstances.delete(i),o.stop().catch(p=>l.error("manager",`stop shared instance failed ${i}: ${p}`)),l.info("manager",`shared instance stopped: ${i}`))}}restoreCachedSharedInstances(e){const t=this.sharedOwnersCache.load(e.name);t.length!==0&&(l.info("manager",`restoring ${t.length} cached shared instance(s) for ${e.name}`),this.onShareSet(e,t))}getAgentsStatus(){return this.instances.map(e=>e.getStatus())}async addAgent(e){await this.addAgentInternal(e,"agents.json"),this.persistAgentsConfig(),l.info("manager",`Added agent: ${e.name}`)}async addAgentInternal(e,t){const r=b(e);if(this.instances.some(s=>s.name===r.name))throw new Error(`Agent "${r.name}" already exists`);await this.startInstanceFromConfig(r,e,t)}async startInstanceFromConfig(e,t,r){const s=new I(e,this.globalConfigStore);s.setUpgradeTrigger(()=>this.upgradeChecker?.triggerCheck()),s.setShareSetHandler(i=>this.onShareSet(e,i)),s.setAgentDeletedHandler(()=>this.onAgentDeletedByPlatform(e.name)),s.setProviderConfigHandler(i=>this.setAgentProvider(e.name,i)),s.setRelayEnvSettledHandler(()=>this.relayEnvSettledHandler?.()),await s.start(),this.instances.push(s),this.configMap.set(e.name,e),this.rawConfigMap.set(e.name,{entry:t,file:r}),this.relayEnvSettledHandler?.()}async removeAgent(e){if(!this.instances.some(t=>t.name===e))throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Removed agent: ${e}`),this.relayEnvSettledHandler?.()}async removeAgentInternal(e,t){const r=this.instances.findIndex(p=>p.name===e),s=r===-1?void 0:this.instances[r];r!==-1&&this.instances.splice(r,1),this.configMap.delete(e),this.rawConfigMap.delete(e);const i=this.shareSyncChains.get(e);this.shareSyncChains.delete(e),i&&await i.catch(()=>{});const o=`${e}#shared:`;for(const[p,c]of[...this.sharedInstances.entries()])p.startsWith(o)&&(this.sharedInstances.delete(p),c.stop().catch(a=>l.error("manager",`stop shared instance failed ${p}: ${a}`)));t?.keepShareCache||this.sharedOwnersCache.delete(e),s&&await s.stop()}onAgentDeletedByPlatform(e){this.stopping||this.deletedAgentCleanups.has(e)||(this.deletedAgentCleanups.add(e),(async()=>{try{if(!(this.rawConfigMap.has(e)||this.instances.some(r=>r.name===e)))return;l.warn("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u505C\u6B62\u5B9E\u4F8B\u5E76\u6E05\u7406\u672C\u5730\u914D\u7F6E`),await this.removeAgentInternal(e),this.persistAgentsConfig(),l.info("manager",`Agent "${e}" \u5DF2\u5728\u5E73\u53F0\u5220\u9664\uFF0C\u672C\u5730\u914D\u7F6E\u5DF2\u6E05\u7406\uFF08agents.json \u5DF2\u79FB\u9664\u8BE5\u6761\u76EE\uFF09`)}catch(t){l.error("manager",`cleanup for deleted agent "${e}" failed: ${t}`)}finally{this.deletedAgentCleanups.delete(e)}})())}persistAgentsConfig(){const e=S(this.configDir,"agents.json");try{const t=[];for(const[,s]of this.configMap){const i=this.rawConfigMap.get(s.name)?.entry.provider;t.push({name:s.name,ws_url:s.aibot.url,agent_id:s.aibot.agentId,api_key:s.aibot.apiKey,client_type:s.aibot.clientType,...i?{provider:i}:{}})}G(e,JSON.stringify({agents:t},null,4)+`
2
+ `,"utf-8")}catch(t){l.error("manager",`Failed to persist agents config: ${t}`)}}isAgentBusy(e){return this.instances.find(r=>r.name===e)?.hasPendingWork()??!1}getAgentsWithStaleRelayEnv(){return this.instances.filter(e=>e.hasStaleRelayEnv()).map(e=>e.name)}markAgentRelayEnvStale(e){const t=this.instances.find(r=>r.name===e);return t?(t.markRelayEnvStale(),!0):!1}relayEnvSettledHandler;setRelayEnvSettledHandler(e){this.relayEnvSettledHandler=e}async restartAgent(e){const t=this.rawConfigMap.get(e);if(!t)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});await this.replaceInstance(e,t.entry,t.file),l.info("manager",`Restarted agent: ${e}`)}async setAgentProvider(e,t){const r=this.rawConfigMap.get(e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});const s={...r.entry,provider:t};await this.replaceInstance(e,s,r.file),this.persistAgentsConfig(),l.info("manager",`Updated provider config for agent: ${e}`)}async clearDirectProviderConfigs(){const e=[...this.rawConfigMap.entries()].filter(([,r])=>!!r.entry.provider&&re(r.entry.client_type)).map(([r])=>r),t=[];for(const r of e)try{await this.setAgentProvider(r,void 0),t.push(r)}catch(s){l.warn("manager",`Failed to clear provider config for "${r}": ${s instanceof Error?s.message:String(s)}`)}return t}async replaceInstance(e,t,r){const s=b(t);await this.removeAgentInternal(e,{keepShareCache:!0}),await this.startInstanceFromConfig(s,t,r),this.restoreCachedSharedInstances(s)}async reload(){const e=this.reloadChain.catch(()=>{}).then(()=>this.doReload());return this.reloadChain=e.catch(()=>{}),e}async doReload(){if(this.stopping)throw Object.assign(new Error("manager is stopping"),{code:"RELOAD_UNSAFE"});const{entries:e,fileErrors:t,skipped:r}=H(this.configDir);for(const c of r)l.info("manager",`reload: skipped ${c}: not an agent config (no "agents" key)`);if(t.length>0){const c=t.map(a=>`${a.file}: ${a.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${t.length} config file(s) failed to parse [${c}]`),{code:"RELOAD_UNSAFE"})}if(e.length===0)throw Object.assign(new Error("reload aborted: no valid agent config found"),{code:"RELOAD_UNSAFE"});const s=[];for(const{entry:c}of e)try{b(c,{quiet:!0})}catch(a){const u=c?.name,m=typeof u=="string"&&u.trim()?u:"<unknown>";s.push({name:m,error:a instanceof Error?a.message:String(a)})}if(s.length>0){const c=s.map(a=>`${a.name}: ${a.error}`).join("; ");throw Object.assign(new Error(`reload aborted: ${s.length} agent config(s) invalid [${c}]`),{code:"RELOAD_UNSAFE"})}const i=new Map;for(const c of e){const a=String(c.entry?.name??"").trim();a&&(i.has(a)&&l.warn("manager",`reload: duplicate agent name "${a}", last one wins`),i.set(a,c))}const o={added:[],removed:[],restarted:[],unchanged:[],failed:[]},p=new Set(this.rawConfigMap.keys());for(const c of p)if(!i.has(c))try{await this.removeAgentInternal(c),o.removed.push(c)}catch(a){o.failed.push({name:c,error:a instanceof Error?a.message:String(a)})}for(const[c,a]of i){const u=this.rawConfigMap.get(c);if(u)if(ye(u.entry,a.entry))this.rawConfigMap.set(c,a),o.unchanged.push(c);else try{await this.replaceInstance(c,a.entry,a.file),o.restarted.push(c)}catch(m){o.failed.push({name:c,error:m instanceof Error?m.message:String(m)})}else try{await this.addAgentInternal(a.entry,a.file),o.added.push(c)}catch(m){o.failed.push({name:c,error:m instanceof Error?m.message:String(m)})}}return l.info("manager",`reload done: +${o.added.length} -${o.removed.length} ~${o.restarted.length} =${o.unchanged.length} !${o.failed.length}`),o}async checkUpgrade(){return this.upgradeChecker?this.upgradeChecker.checkForUpdate():{available:!1}}triggerUpgrade(){this.upgradeChecker?.triggerCheck()}async probeAll(e={}){return we(this.instances,e)}async probeOne(e,t={}){return Ae(this.instances,e,t)}listInstallable(){return this.installer.listInstallable()}async installAgent(e){const t=await this.installer.install(e);return ce(t),t}getInstallProgress(e){return this.installer.getProgress(e)??null}}async function we(n,e){const t=e.concurrency??4,r=Date.now(),s=new Array(n.length);await new Promise(a=>{let u=0,m=0;const _=n.length;if(_===0){a();return}function w(h){const g=n[h];g.probe(e).then(y=>{s[h]=y,d()},y=>{s[h]={agent_name:g.name,client_type:"unknown",adapter_type:"acp",ok:!1,status:"error",probed_at:Date.now(),duration_ms:0,cached:!1,cli:{command:"",installed:!1,path:null,version:null,error:{code:"internal",message:y?.message??String(y)}},conversation:{attempted:!1,ok:!1,latency_ms:null},config:{model:null,base_url:null,source:{model:"unknown",base_url:"unknown"}},process:{started:!1,alive:!1,busy:!1}},d()})}function d(){m++,u<_?w(u++):m===_&&a()}const f=Math.min(t,_);for(let h=0;h<f;h++)w(u++)});const i=s.filter(a=>a.status==="healthy").length,o=s.filter(a=>a.status==="degraded").length,p=s.filter(a=>a.status==="unavailable").length,c=await pe();return{ok:i===s.length&&s.length>0,total:s.length,healthy:i,degraded:o,unavailable:p,installed_clients:c,agents:s,probed_at:r,duration_ms:Date.now()-r}}async function Ae(n,e,t){const r=n.find(s=>s.name===e);if(!r)throw Object.assign(new Error(`Agent "${e}" not found`),{code:"NOT_FOUND"});return r.probe(t)}export{Ne as Manager,pe as probeInstalledClientCommands,we as probeInstances,Ae as probeOneInstance};
@@ -1 +1 @@
1
- function a(o){const e=new Set([`http://127.0.0.1:${o.serverPort}`,`http://localhost:${o.serverPort}`,...o.allowedOrigins]),t=new Set([`127.0.0.1:${o.serverPort}`,`localhost:${o.serverPort}`,...o.allowedHosts]);return{validateRequest(s){const r=i(s,e);if(!r.ok)return r;const n=l(s,t);return n.ok?{ok:!0}:n}}}function i(o,e){const t=o.headers.origin;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${t}`}:{ok:!0}}function l(o,e){const t=o.headers.host;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${t}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
1
+ function a(e){const t=new Set([`http://127.0.0.1:${e.serverPort}`,`http://localhost:${e.serverPort}`,...e.allowedOrigins]),o=new Set([`127.0.0.1:${e.serverPort}`,`localhost:${e.serverPort}`,...e.allowedHosts]);return{validateRequest(s){const r=i(s,t);if(!r.ok)return r;const n=l(s,o);return n.ok?{ok:!0}:n}}}function i(e,t){const o=e.headers.origin;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${o}`}:{ok:!0}}function l(e,t){const o=e.headers.host;return o?t.has(o)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${o}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
@@ -1 +1 @@
1
- import{EventEmitter as S}from"events";import{log as l}from"../core/log/index.js";import{AgentEventType as c}from"../types/events.js";import{mapSessionUpdate as M}from"./event-mapper.js";class h extends Error{authMethods;constructor(e){super("ACP authentication required"),this.name="AcpAuthRequiredError",this.authMethods=e}}function p(a){if(!a||typeof a!="object")return!1;const e=a;return e.code===-32e3?!0:e.code===-32603&&e.data?.details?/\b(401|token\s*expired|access\s*token)\b/i.test(e.data.details):!1}function m(a){return!a||typeof a!="object"?[{id:"oauth"}]:a.data?.authMethods??[{id:"oauth"}]}class u extends S{acpSessionId="";alive=!1;pendingPermissions=new Map;availableModes=[];availableModels=[];currentMode="";currentModel="";listSupported=!1;loadSupported=!1;_supportsCommandsExecute=!1;_availableCommands=[];transport;settleTimer=null;static SETTLE_MS=3e3;pendingToolCallIds=new Set;settleSuppressedSince=0;static SETTLE_SUPPRESS_MAX_MS=900*1e3;constructor(){super(),this.transport=null}get sessionId(){return this.acpSessionId}get isAlive(){return this.alive}get modes(){return[...this.availableModes]}get mode(){return this.currentMode}get models(){return[...this.availableModels]}get model(){return this.currentModel}get sessionOptions(){return{modes:[...this.availableModes],currentModeId:this.currentMode,models:[...this.availableModels],currentModelId:this.currentModel}}get supportsCommandsExecute(){return this._supportsCommandsExecute}get availableCommands(){return[...this._availableCommands]}async connect(e){this.transport=e.transport,this.transport.on("close",()=>{this.alive&&(this.alive=!1,this.emit("session-lost"))}),this.transport.setHandlers((n,r)=>{n==="session/update"?this.handleSessionUpdate(r):n==="_kiro.dev/metadata"?this.handleKiroMetadata(r):n==="_kiro.dev/commands/available"?this.handleCommandsAvailable(r):n==="_kiro.dev/compaction/status"?this.emit("compactionStatus",r):n==="_kiro.dev/clear/status"&&this.emit("clearStatus",r)},(n,r,d)=>{n==="session/request_permission"?this.handlePermissionRequest(r,d):n.startsWith("cursor/")?this.transport.respondSuccess(r,{}).catch(()=>{}):this.transport.respondError(r,-32601,"method not implemented").catch(()=>{})});const t=await this.initialize();this.listSupported=!!t.agentCapabilities?.sessionCapabilities?.list,this.loadSupported=!!t.agentCapabilities?.loadSession;const i=t.agentCapabilities??{};if(this._supportsCommandsExecute=!!(i.extensions?.["_kiro.dev/commands"]||i.commands),e.authMethod)try{await this.transport.call("authenticate",{methodId:e.authMethod})}catch(n){throw p(n)?new h(m(n)):n}const s=e.sessionId&&e.sessionId!=="__continue__";let o=!1;if(s){if(!this.loadSupported)throw new Error(`session/load not supported by agent, cannot resume session ${e.sessionId}`);try{await this.loadSession(e.sessionId,e)&&(o=!0)}catch(n){throw p(n)?new h(m(n)):n}if(!o)throw new Error(`session/load failed for session ${e.sessionId}`)}else try{await this.newSession(e)}catch(n){throw p(n)?new h(m(n)):n}e.initialMode&&await this.setLiveMode(e.initialMode).catch(()=>{}),e.initialModel&&await this.setModel(e.initialModel).catch(()=>{}),this.alive=!0}async initialize(){return await this.transport.call("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1},clientInfo:{name:"grix-connector-acp",version:"0.2.0"}})}buildMcpEntries(e){return(e??[]).map(t=>{const i={name:t.name,command:t.command};return t.args&&(i.args=t.args),t.env&&(i.env=Object.entries(t.env).map(([s,o])=>({name:s,value:o}))),i})}async newSession(e){const t=this.buildMcpEntries(e.mcpServers),i={cwd:e.cwd||process.cwd(),mcpServers:t};e.additionalDirectories&&e.additionalDirectories.length>0&&(i.additionalDirectories=e.additionalDirectories);const s=await this.transport.call("session/new",i);if(!s?.sessionId)throw new Error("session/new returned empty sessionId");this.acpSessionId=s.sessionId,this.absorbModes(s.modes),this.absorbModels(s.models)}async loadSession(e,t){const i=this.buildMcpEntries(t.mcpServers),s={sessionId:e,cwd:t.cwd||process.cwd(),mcpServers:i};t.additionalDirectories&&t.additionalDirectories.length>0&&(s.additionalDirectories=t.additionalDirectories);const o=await this.transport.call("session/load",s);return o?.sessionId?(this.acpSessionId=o.sessionId,this.absorbModes(o.modes),this.absorbModels(o.models),!0):o&&(o.modes||o.models)?(this.acpSessionId=e,this.absorbModes(o.modes),this.absorbModels(o.models),!0):(this.acpSessionId=e,!0)}absorbModes(e){e?.availableModes?.length&&(this.availableModes=[...e.availableModes],e.currentModeId&&(this.currentMode=e.currentModeId))}absorbModels(e){e?.availableModels?.length&&(this.availableModels=[...e.availableModels],e.currentModelId&&(this.currentModel=e.currentModelId))}async send(e,t,i){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const s=[{type:"text",text:e}];if(t)for(const o of t)s.push({type:"image",data:o.data.toString("base64"),mimeType:o.mimeType});this.pendingToolCallIds.clear(),this.settleSuppressedSince=0,await this.transport.call("session/prompt",{sessionId:this.acpSessionId,prompt:s,...this.currentModel?{modelId:this.currentModel}:{}}),this.scheduleSettledResult()}scheduleSettledResult(){this.settleTimer&&clearTimeout(this.settleTimer),this.settleTimer=setTimeout(()=>{if(this.settleTimer=null,!!this.alive){if(this.pendingToolCallIds.size>0){if(this.settleSuppressedSince===0&&(this.settleSuppressedSince=Date.now()),Date.now()-this.settleSuppressedSince<u.SETTLE_SUPPRESS_MAX_MS){l.info("acp-client",`settle suppressed: ${this.pendingToolCallIds.size} pending tool call(s) for session ${this.acpSessionId}`),this.scheduleSettledResult();return}l.warn("acp-client",`settle suppression exceeded ${u.SETTLE_SUPPRESS_MAX_MS/6e4}min with ${this.pendingToolCallIds.size} pending tool call(s), emitting Result anyway`)}this.settleSuppressedSince=0,l.info("acp-client",`settle timer fired, emitting Result for session ${this.acpSessionId}`),this.emit("event",{type:c.Result,sessionId:this.acpSessionId,done:!0})}},u.SETTLE_MS),this.settleTimer.unref()}clearSettleTimer(){this.settleTimer&&(clearTimeout(this.settleTimer),this.settleTimer=null),this.settleSuppressedSince=0}async cancel(){if(this.acpSessionId){this.pendingToolCallIds.clear(),this.settleSuppressedSince=0;try{await this.transport.notify("session/cancel",{sessionId:this.acpSessionId})}catch{}}}async authenticate(e){await this.transport.call("authenticate",{methodId:e})}async respondPermission(e,t){const i=this.pendingPermissions.get(e);if(!i)throw new Error(`unknown permission request: ${e}`);this.pendingPermissions.delete(e);const s=this.pickPermissionOptionId(t.behavior,i.options),o=this.buildPermissionResult(t.behavior,s);await this.transport.respondSuccess(i.rpcId,o)}async ping(e=1e4){if(!this.alive)return!1;try{const t=new AbortController,i=setTimeout(()=>t.abort(),e);return await this.transport.call("session/list",{},t.signal),clearTimeout(i),!0}catch(t){return t?.code===-32601}}async setLiveMode(e){if(!this.acpSessionId)return l.warn("acp-client",`setLiveMode("${e}") skipped: no active session`),!1;const t=this.matchAvailableMode(e);if(!t)return l.warn("acp-client",`setLiveMode("${e}") failed: mode not found in available [${this.availableModes.map(i=>i.id).join(",")}]`),!1;try{const i=new AbortController,s=setTimeout(()=>i.abort(),8e3);return await this.transport.call("session/set_mode",{sessionId:this.acpSessionId,modeId:t},i.signal),clearTimeout(s),this.currentMode=t,!0}catch(i){return l.warn("acp-client",`setLiveMode("${t}") RPC failed: ${i instanceof Error?i.message:i}`),!1}}async setModel(e){if(!this.acpSessionId)return l.warn("acp-client",`setModel("${e}") skipped: no active session`),!1;try{const t=new AbortController,i=setTimeout(()=>t.abort(),8e3);return await this.transport.call("session/set_model",{sessionId:this.acpSessionId,modelId:e},t.signal),clearTimeout(i),this.currentModel=e,!0}catch(t){return l.warn("acp-client",`setModel("${e}") RPC failed: ${t instanceof Error?t.message:t}`),!1}}handleSessionUpdate(e){this.emit("activity");const t=e?.update?.sessionUpdate,i=t==="usage_update";if(t==="tool_call"||t==="tool_call_update"){const o=e?.update??{},n=String(o.toolCallId??""),r=String(o.status??"").toLowerCase().trim();n&&(r==="completed"||r==="failed"?this.pendingToolCallIds.delete(n):t==="tool_call"&&this.pendingToolCallIds.add(n))}this.settleTimer&&!i&&this.scheduleSettledResult();const s=M(this.acpSessionId,e);for(const o of s)o.sessionId||(o.sessionId=this.acpSessionId),o.type===c.Result&&(this.pendingToolCallIds.clear(),this.settleTimer&&this.clearSettleTimer()),this.emit("event",o)}handleKiroMetadata(e){const i=e?.contextUsagePercentage;typeof i!="number"||!Number.isFinite(i)||this.emit("event",{type:c.ContextWindowUpdate,sessionId:this.acpSessionId,contextWindow:{usedPercentage:Math.min(100,Math.max(0,i))}})}handleCommandsAvailable(e){const t=e,i=Array.isArray(t?.commands)?t.commands:[];this._availableCommands=i.map(s=>({name:String(s.name??""),description:s.description?String(s.description):void 0,args:s.args?String(s.args):void 0})),this._supportsCommandsExecute=!0,this.emit("commandsAvailable",this._availableCommands)}async executeCommand(e,t){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const i=new AbortController,s=setTimeout(()=>i.abort(),2e4);try{const o=await this.transport.call("_kiro.dev/commands/execute",{sessionId:this.acpSessionId,command:e,...t?{args:t}:{}},i.signal);return clearTimeout(s),{status:o?.status??"ok",message:o?.message,options:o?.options,data:o?.data}}catch(o){clearTimeout(s);const n=o instanceof Error?o.message:String(o);if(o?.code===-32601)throw this._supportsCommandsExecute=!1,o;return{status:"failed",message:n}}}handlePermissionRequest(e,t){const i=t,s=i?.toolCall??{},o=String(e),n=Array.isArray(i?.options)?i.options:[];this.pendingPermissions.set(o,{rpcId:e,options:n});const r=s.title||s.kind||"permission",d=s.toolCallId||o,f=s.title||"",w={type:c.PermissionRequest,requestId:o,toolName:r,toolInput:f||d,sessionId:this.acpSessionId,permissionRequest:{requestId:o,toolCallId:d,toolName:r,toolTitle:f,options:n,rawParams:t}};this.emit("event",w)}matchAvailableMode(e){const t=e.toLowerCase();for(const i of this.availableModes)if(i.id.toLowerCase()===t||i.name.toLowerCase()===t)return i.id;return""}pickPermissionOptionId(e,t){if(t.length===0)return"";if(e==="deny"){for(const s of t)if(s.kind==="reject_once"||s.kind==="reject_always")return s.optionId;for(const s of t)if(s.kind.toLowerCase().includes("reject")||s.kind.toLowerCase().includes("deny"))return s.optionId;for(const s of t)if(s.name.toLowerCase().includes("reject")||s.name.toLowerCase().includes("deny"))return s.optionId;return t[t.length-1].optionId}const i=e==="allow-always"?"allow_always":e==="allow-once"?"allow_once":null;if(i){for(const s of t)if(s.kind===i)return s.optionId}for(const s of t)if(s.kind.toLowerCase().includes("allow"))return s.optionId;for(const s of t)if(s.name.toLowerCase().includes("allow"))return s.optionId;return t[0].optionId}buildPermissionResult(e,t){return e==="deny"?t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}:t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}}}export{h as AcpAuthRequiredError,u as AcpClient,p as isAuthRequiredError};
1
+ import{EventEmitter as w}from"events";import{log as a}from"../core/log/index.js";import{AgentEventType as c}from"../types/events.js";import{mapSessionUpdate as M}from"./event-mapper.js";class h extends Error{authMethods;constructor(e){super("ACP authentication required"),this.name="AcpAuthRequiredError",this.authMethods=e}}function p(l){if(!l||typeof l!="object")return!1;const e=l;return e.code===-32e3?!0:e.code===-32603&&e.data?.details?/\b(401|token\s*expired|access\s*token)\b/i.test(e.data.details):!1}function m(l){return!l||typeof l!="object"?[{id:"oauth"}]:l.data?.authMethods??[{id:"oauth"}]}class u extends w{acpSessionId="";alive=!1;pendingPermissions=new Map;availableModes=[];availableModels=[];currentMode="";currentModel="";listSupported=!1;loadSupported=!1;_supportsCommandsExecute=!1;_availableCommands=[];transport;settleTimer=null;static SETTLE_MS=3e3;pendingToolCallIds=new Set;settleSuppressedSince=0;static SETTLE_SUPPRESS_MAX_MS=900*1e3;constructor(){super(),this.transport=null}get sessionId(){return this.acpSessionId}get isAlive(){return this.alive}get modes(){return[...this.availableModes]}get mode(){return this.currentMode}get models(){return[...this.availableModels]}get model(){return this.currentModel}get sessionOptions(){return{modes:[...this.availableModes],currentModeId:this.currentMode,models:[...this.availableModels],currentModelId:this.currentModel}}get supportsCommandsExecute(){return this._supportsCommandsExecute}get availableCommands(){return[...this._availableCommands]}async connect(e){this.transport=e.transport,this.transport.on("close",()=>{this.alive&&(this.alive=!1,this.emit("session-lost"))}),this.transport.setHandlers((n,r)=>{n==="session/update"?this.handleSessionUpdate(r):n==="_kiro.dev/metadata"?this.handleKiroMetadata(r):n==="_kiro.dev/commands/available"?this.handleCommandsAvailable(r):n==="_kiro.dev/compaction/status"?this.emit("compactionStatus",r):n==="_kiro.dev/clear/status"&&this.emit("clearStatus",r)},(n,r,d)=>{n==="session/request_permission"?this.handlePermissionRequest(r,d):n.startsWith("cursor/")?this.transport.respondSuccess(r,{}).catch(()=>{}):this.transport.respondError(r,-32601,"method not implemented").catch(()=>{})});const t=await this.initialize();this.listSupported=!!t.agentCapabilities?.sessionCapabilities?.list,this.loadSupported=!!t.agentCapabilities?.loadSession;const s=t.agentCapabilities??{};if(this._supportsCommandsExecute=!!(s.extensions?.["_kiro.dev/commands"]||s.commands),e.authMethod)try{await this.transport.call("authenticate",{methodId:e.authMethod})}catch(n){throw p(n)?new h(m(n)):n}const i=e.sessionId&&e.sessionId!=="__continue__";let o=!1;if(i){if(!this.loadSupported)throw new Error(`session/load not supported by agent, cannot resume session ${e.sessionId}`);try{await this.loadSession(e.sessionId,e)&&(o=!0)}catch(n){throw p(n)?new h(m(n)):n}if(!o)throw new Error(`session/load failed for session ${e.sessionId}`)}else try{await this.newSession(e)}catch(n){throw p(n)?new h(m(n)):n}e.initialMode&&await this.setLiveMode(e.initialMode).catch(()=>{}),e.initialModel&&await this.setModel(e.initialModel).catch(()=>{}),this.alive=!0}async initialize(){return await this.transport.call("initialize",{protocolVersion:1,clientCapabilities:{fs:{readTextFile:!1,writeTextFile:!1},terminal:!1},clientInfo:{name:"grix-connector-acp",version:"0.2.0"}})}buildMcpEntries(e){return(e??[]).map(t=>{const s={name:t.name,command:t.command};return t.args&&(s.args=t.args),t.env&&(s.env=Object.entries(t.env).map(([i,o])=>({name:i,value:o}))),s})}async newSession(e){const t=this.buildMcpEntries(e.mcpServers),s={cwd:e.cwd||process.cwd(),mcpServers:t};e.additionalDirectories&&e.additionalDirectories.length>0&&(s.additionalDirectories=e.additionalDirectories);const i=await this.transport.call("session/new",s);if(!i?.sessionId)throw new Error("session/new returned empty sessionId");this.acpSessionId=i.sessionId,this.absorbModes(i.modes),a.info("acp-client",`[handshake] session/new models block: ${JSON.stringify(i.models)}`),this.absorbModels(i.models),this.absorbConfigOptions(i.configOptions)}async loadSession(e,t){const s=this.buildMcpEntries(t.mcpServers),i={sessionId:e,cwd:t.cwd||process.cwd(),mcpServers:s};t.additionalDirectories&&t.additionalDirectories.length>0&&(i.additionalDirectories=t.additionalDirectories);const o=await this.transport.call("session/load",i);return o?.sessionId?(this.acpSessionId=o.sessionId,this.absorbModes(o.modes),a.info("acp-client",`[handshake] session/load models block: ${JSON.stringify(o.models)}`),this.absorbModels(o.models),this.absorbConfigOptions(o.configOptions),!0):o&&(o.modes||o.models||o.configOptions)?(this.acpSessionId=e,this.absorbModes(o.modes),a.info("acp-client",`[handshake] session/load (compat) models block: ${JSON.stringify(o.models)}`),this.absorbModels(o.models),this.absorbConfigOptions(o.configOptions),!0):(a.info("acp-client",`[handshake] session/load returned empty result for ${e}`),this.acpSessionId=e,!0)}absorbModes(e){e?.availableModes?.length&&(this.availableModes=[...e.availableModes],e.currentModeId&&(this.currentMode=e.currentModeId))}absorbConfigOptions(e){if(Array.isArray(e))for(const t of e){const s=t,i=Array.isArray(s?.options)?s.options.filter(n=>typeof n?.value=="string"&&n.value):[];if(i.length===0)continue;const o=s.category||s.id;o==="mode"&&this.availableModes.length===0?(this.availableModes=i.map(n=>({id:n.value,name:n.name||n.value,...n.description?{description:n.description}:{}})),typeof s.currentValue=="string"&&s.currentValue&&(this.currentMode=s.currentValue),a.info("acp-client",`[handshake] absorbConfigOptions modes: count=${this.availableModes.length} ids=${JSON.stringify(this.availableModes.map(n=>n.id))} current=${s.currentValue??"<none>"}`)):o==="model"&&this.availableModels.length===0&&(this.availableModels=i.map(n=>({modelId:n.value,name:n.name||n.value})),typeof s.currentValue=="string"&&s.currentValue&&(this.currentModel=s.currentValue),a.info("acp-client",`[handshake] absorbConfigOptions models: count=${this.availableModels.length} ids=${JSON.stringify(this.availableModels.map(n=>n.modelId))} current=${s.currentValue??"<none>"}`))}}absorbModels(e){if(!e?.availableModels?.length){a.info("acp-client","[handshake] absorbModels skipped: block empty");return}this.availableModels=[...e.availableModels],a.info("acp-client",`[handshake] absorbModels: count=${this.availableModels.length} ids=${JSON.stringify(this.availableModels.map(t=>t.modelId))} current=${e.currentModelId??"<none>"}`),e.currentModelId&&(this.currentModel=e.currentModelId)}async send(e,t,s){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const i=[{type:"text",text:e}];if(t)for(const o of t)i.push({type:"image",data:o.data.toString("base64"),mimeType:o.mimeType});this.pendingToolCallIds.clear(),this.settleSuppressedSince=0,await this.transport.call("session/prompt",{sessionId:this.acpSessionId,prompt:i,...this.currentModel?{modelId:this.currentModel}:{}}),this.scheduleSettledResult()}scheduleSettledResult(){this.settleTimer&&clearTimeout(this.settleTimer),this.settleTimer=setTimeout(()=>{if(this.settleTimer=null,!!this.alive){if(this.pendingToolCallIds.size>0){if(this.settleSuppressedSince===0&&(this.settleSuppressedSince=Date.now()),Date.now()-this.settleSuppressedSince<u.SETTLE_SUPPRESS_MAX_MS){a.info("acp-client",`settle suppressed: ${this.pendingToolCallIds.size} pending tool call(s) for session ${this.acpSessionId}`),this.scheduleSettledResult();return}a.warn("acp-client",`settle suppression exceeded ${u.SETTLE_SUPPRESS_MAX_MS/6e4}min with ${this.pendingToolCallIds.size} pending tool call(s), emitting Result anyway`)}this.settleSuppressedSince=0,a.info("acp-client",`settle timer fired, emitting Result for session ${this.acpSessionId}`),this.emit("event",{type:c.Result,sessionId:this.acpSessionId,done:!0})}},u.SETTLE_MS),this.settleTimer.unref()}clearSettleTimer(){this.settleTimer&&(clearTimeout(this.settleTimer),this.settleTimer=null),this.settleSuppressedSince=0}async cancel(){if(this.acpSessionId){this.pendingToolCallIds.clear(),this.settleSuppressedSince=0;try{await this.transport.notify("session/cancel",{sessionId:this.acpSessionId})}catch{}}}async authenticate(e){await this.transport.call("authenticate",{methodId:e})}async respondPermission(e,t){const s=this.pendingPermissions.get(e);if(!s)throw new Error(`unknown permission request: ${e}`);this.pendingPermissions.delete(e);const i=this.pickPermissionOptionId(t.behavior,s.options),o=this.buildPermissionResult(t.behavior,i);await this.transport.respondSuccess(s.rpcId,o)}async ping(e=1e4){if(!this.alive)return!1;try{const t=new AbortController,s=setTimeout(()=>t.abort(),e);return await this.transport.call("session/list",{},t.signal),clearTimeout(s),!0}catch(t){return t?.code===-32601}}async setLiveMode(e){if(!this.acpSessionId)return a.warn("acp-client",`setLiveMode("${e}") skipped: no active session`),!1;const t=this.matchAvailableMode(e);if(!t)return a.warn("acp-client",`setLiveMode("${e}") failed: mode not found in available [${this.availableModes.map(s=>s.id).join(",")}]`),!1;try{const s=new AbortController,i=setTimeout(()=>s.abort(),8e3);return await this.transport.call("session/set_mode",{sessionId:this.acpSessionId,modeId:t},s.signal),clearTimeout(i),this.currentMode=t,!0}catch(s){return a.warn("acp-client",`setLiveMode("${t}") RPC failed: ${s instanceof Error?s.message:s}`),!1}}async setModel(e){if(!this.acpSessionId)return a.warn("acp-client",`setModel("${e}") skipped: no active session`),!1;try{const t=new AbortController,s=setTimeout(()=>t.abort(),8e3);return await this.transport.call("session/set_model",{sessionId:this.acpSessionId,modelId:e},t.signal),clearTimeout(s),this.currentModel=e,!0}catch(t){return a.warn("acp-client",`setModel("${e}") RPC failed: ${t instanceof Error?t.message:t}`),!1}}handleSessionUpdate(e){this.emit("activity");const t=e?.update?.sessionUpdate,s=t==="usage_update";if(t==="tool_call"||t==="tool_call_update"){const o=e?.update??{},n=String(o.toolCallId??""),r=String(o.status??"").toLowerCase().trim();n&&(r==="completed"||r==="failed"?this.pendingToolCallIds.delete(n):t==="tool_call"&&this.pendingToolCallIds.add(n))}this.settleTimer&&!s&&this.scheduleSettledResult();const i=M(this.acpSessionId,e);for(const o of i)o.sessionId||(o.sessionId=this.acpSessionId),o.type===c.Result&&(this.pendingToolCallIds.clear(),this.settleTimer&&this.clearSettleTimer()),this.emit("event",o)}handleKiroMetadata(e){const s=e?.contextUsagePercentage;typeof s!="number"||!Number.isFinite(s)||this.emit("event",{type:c.ContextWindowUpdate,sessionId:this.acpSessionId,contextWindow:{usedPercentage:Math.min(100,Math.max(0,s))}})}handleCommandsAvailable(e){const t=e,s=Array.isArray(t?.commands)?t.commands:[];this._availableCommands=s.map(i=>({name:String(i.name??""),description:i.description?String(i.description):void 0,args:i.args?String(i.args):void 0})),this._supportsCommandsExecute=!0,this.emit("commandsAvailable",this._availableCommands)}async executeCommand(e,t){if(!this.alive)throw new Error("session not active");if(!this.acpSessionId)throw new Error("no agent session id");const s=new AbortController,i=setTimeout(()=>s.abort(),2e4);try{const o=await this.transport.call("_kiro.dev/commands/execute",{sessionId:this.acpSessionId,command:e,...t?{args:t}:{}},s.signal);return clearTimeout(i),{status:o?.status??"ok",message:o?.message,options:o?.options,data:o?.data}}catch(o){clearTimeout(i);const n=o instanceof Error?o.message:String(o);if(o?.code===-32601)throw this._supportsCommandsExecute=!1,o;return{status:"failed",message:n}}}handlePermissionRequest(e,t){const s=t,i=s?.toolCall??{},o=String(e),n=Array.isArray(s?.options)?s.options:[];this.pendingPermissions.set(o,{rpcId:e,options:n});const r=i.title||i.kind||"permission",d=i.toolCallId||o,f=i.title||"",S={type:c.PermissionRequest,requestId:o,toolName:r,toolInput:f||d,sessionId:this.acpSessionId,permissionRequest:{requestId:o,toolCallId:d,toolName:r,toolTitle:f,options:n,rawParams:t}};this.emit("event",S)}matchAvailableMode(e){const t=e.toLowerCase();for(const s of this.availableModes)if(s.id.toLowerCase()===t||s.name.toLowerCase()===t)return s.id;return""}pickPermissionOptionId(e,t){if(t.length===0)return"";if(e==="deny"){for(const i of t)if(i.kind==="reject_once"||i.kind==="reject_always")return i.optionId;for(const i of t)if(i.kind.toLowerCase().includes("reject")||i.kind.toLowerCase().includes("deny"))return i.optionId;for(const i of t)if(i.name.toLowerCase().includes("reject")||i.name.toLowerCase().includes("deny"))return i.optionId;return t[t.length-1].optionId}const s=e==="allow-always"?"allow_always":e==="allow-once"?"allow_once":null;if(s){for(const i of t)if(i.kind===s)return i.optionId}for(const i of t)if(i.kind.toLowerCase().includes("allow"))return i.optionId;for(const i of t)if(i.name.toLowerCase().includes("allow"))return i.optionId;return t[0].optionId}buildPermissionResult(e,t){return e==="deny"?t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}:t?{outcome:{outcome:"selected",optionId:t}}:{outcome:{outcome:"cancelled"}}}}export{h as AcpAuthRequiredError,u as AcpClient,p as isAuthRequiredError};
@@ -1,5 +1,5 @@
1
- import{AgentEventType as i}from"../types/events.js";import{log as c}from"../core/log/index.js";function k(o,t){if(!t||typeof t!="object")return[];const e=t,n=e.update;if(!n||typeof n!="object")return[];const s=n.sessionUpdate,r=e.sessionId||o;switch(s){case"agent_message_chunk":return a(r,n);case"tool_call":return l(r,n);case"tool_call_update":return p(r,n);case"plan":return f(r,n);case"user_message_chunk":return[];case"usage_update":return m(r,n);case"agent_message_end":case"response_complete":case"prompt_done":return c.info("event-mapper",`completion event received: kind=${s} sid=${r}`),[{type:i.Result,sessionId:r,done:!0}];default:return d(r,s,n)}}function a(o,t){const e=t?.content?.text;return e?[{type:i.Text,content:e,sessionId:o}]:[]}function l(o,t){const e=t?.title||t?.kind||"tool",n=y(t?.kind,t?.rawInput)||e;return[{type:i.ToolUse,toolName:e,toolInput:n,sessionId:o}]}function p(o,t){const e=t?.title||t?.toolCallId||"tool",n=g(t?.content),s=(t?.status??"").toLowerCase().trim();return s==="completed"||s==="failed"?s==="completed"&&!n?[]:[{type:i.ToolResult,toolName:e,content:s==="failed"&&!n?"(failed)":u(n,800),sessionId:o}]:n?[{type:i.ToolProgress,toolName:e,content:u(n,800),sessionId:o}]:[]}function f(o,t){const e=t?.entries;if(!Array.isArray(e)||e.length===0)return[];const n=e.map(s=>{const r=s.content??"";return s.status?`[${s.status}] ${r}`:r}).join(`
2
- `);return[{type:i.Thinking,content:n,sessionId:o}]}function d(o,t,e){const n=(t??"").toLowerCase();if(n==="reasoning"||n==="reasoning_chunk"||n==="thinking"||n==="agent_thinking_chunk"||n==="agent_thought_chunk"){const s=e?.content?.text||e?.text;return s?[{type:i.Thinking,content:s,sessionId:o}]:[]}return t&&c.info("event-mapper",`unmapped sessionUpdate kind="${t}" sid=${o}`),[]}function m(o,t){const e=t?.used,n=t?.size;return typeof e!="number"||typeof n!="number"||n<=0?[]:[{type:i.ContextWindowUpdate,sessionId:o,contextWindow:{used:e,size:n}}]}function g(o){return Array.isArray(o)?o.map(t=>t?.content?.text??"").filter(Boolean).join(`
3
- `):""}function y(o,t){if(!t||typeof t!="object")return"";const e=t;if(Object.keys(e).length===0)return"";const n=(o??"").toLowerCase();if(["bash","shell","terminal","execute"].includes(n)){const s=e.command,r=e.description;if(typeof s=="string")return typeof r=="string"&&r?`# ${r}
4
- ${s}`:s}if(["read","write","edit"].includes(n))return e.file_path??e.path??"";if(typeof e.command=="string"){const s=e.description;return typeof s=="string"&&s?`# ${s}
5
- ${e.command}`:e.command}return JSON.stringify(e,null,2)}function u(o,t){return t<=0||o.length<=t?o:o.slice(0,t)+"..."}export{k as mapSessionUpdate,y as summarizeToolInput};
1
+ import{AgentEventType as i}from"../types/events.js";import{log as c}from"../core/log/index.js";function _(s,t){if(!t||typeof t!="object")return[];const e=t,n=e.update;if(!n||typeof n!="object")return[];const o=n.sessionUpdate,r=e.sessionId||s;switch(o){case"agent_message_chunk":return a(r,n);case"tool_call":return u(r,n);case"tool_call_update":return f(r,n);case"plan":return d(r,n);case"user_message_chunk":return[];case"usage_update":return m(r,n);case"agent_message_end":case"response_complete":case"prompt_done":return c.info("event-mapper",`completion event received: kind=${o} sid=${r}`),[{type:i.Result,sessionId:r,done:!0}];default:return p(r,o,n)}}function a(s,t){const e=t?.content?.text;return e?[{type:i.Text,content:e,sessionId:s}]:[]}function u(s,t){const e=t?.title||t?.kind||"tool",n=y(t?.kind,t?.rawInput)||e,o=typeof t?.toolCallId=="string"?t.toolCallId:void 0;return[{type:i.ToolUse,toolName:e,toolCallId:o,toolInput:n,sessionId:s}]}function f(s,t){const e=t?.title||t?.toolCallId||"tool",n=typeof t?.toolCallId=="string"?t.toolCallId:void 0,o=g(t?.content),r=(t?.status??"").toLowerCase().trim();return r==="completed"||r==="failed"?r==="completed"&&!o?[]:[{type:i.ToolResult,toolName:e,toolCallId:n,content:r==="failed"&&!o?"(failed)":l(o,800),sessionId:s}]:o?[{type:i.ToolProgress,toolName:e,toolCallId:n,content:l(o,800),sessionId:s}]:[]}function d(s,t){const e=t?.entries;if(!Array.isArray(e)||e.length===0)return[];const n=e.map(o=>{const r=o.content??"";return o.status?`[${o.status}] ${r}`:r}).join(`
2
+ `);return[{type:i.Thinking,content:n,sessionId:s}]}function p(s,t,e){const n=(t??"").toLowerCase();if(n==="reasoning"||n==="reasoning_chunk"||n==="thinking"||n==="agent_thinking_chunk"||n==="agent_thought_chunk"){const o=e?.content?.text||e?.text;return o?[{type:i.Thinking,content:o,sessionId:s}]:[]}return t&&c.info("event-mapper",`unmapped sessionUpdate kind="${t}" sid=${s}`),[]}function m(s,t){const e=t?.used,n=t?.size;return typeof e!="number"||typeof n!="number"||n<=0?[]:[{type:i.ContextWindowUpdate,sessionId:s,contextWindow:{used:e,size:n}}]}function g(s){return Array.isArray(s)?s.map(t=>t?.content?.text??"").filter(Boolean).join(`
3
+ `):""}function y(s,t){if(!t||typeof t!="object")return"";const e=t;if(Object.keys(e).length===0)return"";const n=(s??"").toLowerCase();if(["bash","shell","terminal","execute"].includes(n)){const o=e.command,r=e.description;if(typeof o=="string")return typeof r=="string"&&r?`# ${r}
4
+ ${o}`:o}if(["read","write","edit"].includes(n))return e.file_path??e.path??"";if(typeof e.command=="string"){const o=e.description;return typeof o=="string"&&o?`# ${o}
5
+ ${e.command}`:e.command}return JSON.stringify(e,null,2)}function l(s,t){return t<=0||s.length<=t?s:s.slice(0,t)+"..."}export{_ as mapSessionUpdate,y as summarizeToolInput};