@lensagent/core 0.1.1 → 0.1.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/README.md +63 -4
- package/config/lens.example.jsonc +36 -13
- package/dist/adapters/executor-router.d.ts +20 -0
- package/dist/adapters/executor-router.js +86 -0
- package/dist/adapters/executor.d.ts +9 -3
- package/dist/adapters/executor.js +196 -61
- package/dist/adapters/lsp.d.ts +2 -1
- package/dist/adapters/lsp.js +98 -41
- package/dist/adapters/mcp.js +52 -17
- package/dist/adapters/revert.d.ts +5 -4
- package/dist/adapters/revert.js +6 -64
- package/dist/adapters/web.d.ts +9 -0
- package/dist/adapters/web.js +244 -0
- package/dist/config/files.d.ts +14 -0
- package/dist/config/files.js +57 -0
- package/dist/config/runtime.d.ts +3 -4
- package/dist/config/runtime.js +116 -97
- package/dist/config/schema.d.ts +56 -10
- package/dist/config/schema.js +48 -8
- package/dist/contracts/capability.d.ts +27 -1
- package/dist/contracts/domain.d.ts +1 -0
- package/dist/contracts/model.d.ts +33 -6
- package/dist/contracts/review.d.ts +17 -0
- package/dist/contracts/review.js +1 -0
- package/dist/core/access-policy.d.ts +40 -0
- package/dist/core/access-policy.js +68 -0
- package/dist/core/access.d.ts +30 -2
- package/dist/core/access.js +159 -26
- package/dist/core/context-boundaries.d.ts +25 -0
- package/dist/core/context-boundaries.js +106 -0
- package/dist/core/context.d.ts +3 -0
- package/dist/core/context.js +11 -0
- package/dist/core/host.js +261 -173
- package/dist/entry/pair.d.ts +1 -0
- package/dist/entry/pair.js +14 -0
- package/dist/entry/run.js +1 -1
- package/dist/execution/lsp-process.d.ts +3 -2
- package/dist/execution/lsp-process.js +46 -28
- package/dist/http/server.d.ts +5 -0
- package/dist/http/server.js +262 -80
- package/dist/runtime/agent.d.ts +9 -4
- package/dist/runtime/agent.js +361 -286
- package/dist/runtime/artifacts.d.ts +13 -1
- package/dist/runtime/artifacts.js +137 -10
- package/dist/runtime/cache.d.ts +59 -16
- package/dist/runtime/cache.js +124 -43
- package/dist/runtime/calls.d.ts +1 -1
- package/dist/runtime/calls.js +164 -48
- package/dist/runtime/capabilities.d.ts +2 -2
- package/dist/runtime/capabilities.js +4 -5
- package/dist/runtime/cleanup.d.ts +5 -2
- package/dist/runtime/cleanup.js +48 -17
- package/dist/runtime/compaction.d.ts +9 -0
- package/dist/runtime/compaction.js +52 -0
- package/dist/runtime/context.d.ts +8 -4
- package/dist/runtime/context.js +115 -128
- package/dist/runtime/events.d.ts +7 -0
- package/dist/runtime/events.js +83 -0
- package/dist/runtime/images.d.ts +14 -0
- package/dist/runtime/images.js +82 -0
- package/dist/runtime/kernel-capabilities.d.ts +7 -1
- package/dist/runtime/kernel-capabilities.js +146 -68
- package/dist/runtime/model-audit.d.ts +9 -0
- package/dist/runtime/model-audit.js +39 -0
- package/dist/runtime/model.d.ts +109 -4
- package/dist/runtime/model.js +368 -143
- package/dist/runtime/observability.d.ts +1 -0
- package/dist/runtime/observability.js +60 -15
- package/dist/runtime/policy.d.ts +1 -1
- package/dist/runtime/policy.js +2 -2
- package/dist/runtime/profile.d.ts +2 -1
- package/dist/runtime/profile.js +12 -15
- package/dist/runtime/projects.d.ts +3 -1
- package/dist/runtime/projects.js +38 -17
- package/dist/runtime/ptc-worker.d.ts +1 -0
- package/dist/runtime/ptc-worker.js +97 -0
- package/dist/runtime/ptc.d.ts +16 -0
- package/dist/runtime/ptc.js +128 -0
- package/dist/runtime/results.d.ts +5 -4
- package/dist/runtime/results.js +29 -51
- package/dist/runtime/review.d.ts +4 -1
- package/dist/runtime/review.js +101 -22
- package/dist/runtime/scheduler.js +1 -0
- package/dist/runtime/skills.js +34 -5
- package/dist/runtime/task-audit.d.ts +21 -0
- package/dist/runtime/task-audit.js +122 -0
- package/dist/runtime/time.d.ts +9 -0
- package/dist/runtime/time.js +18 -0
- package/dist/runtime/tokenizer.d.ts +2 -0
- package/dist/runtime/tokenizer.js +26 -1
- package/dist/store/database.js +26 -1
- package/dist/store/event-log.d.ts +1 -0
- package/dist/store/event-log.js +22 -5
- package/dist/store/schema.d.ts +1 -1
- package/dist/store/schema.js +50 -1
- package/dist/store/store.d.ts +65 -19
- package/dist/store/store.js +986 -188
- package/package.json +14 -6
- package/proto/executor.proto +69 -55
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 仓库与运行
|
|
6
6
|
|
|
7
|
-
仓库根目录本身就是可安装、构建和运行的 Node.js
|
|
7
|
+
仓库根目录本身就是可安装、构建和运行的 Node.js 项目。文件和终端能力由独立仓库 `lens-remote-executor` 提供;本仓库通过 gRPC 与其通信。两侧各自维护一份 `proto/executor.proto`,修改 RPC 契约时需要同步。
|
|
8
8
|
|
|
9
9
|
要求 Node.js 24 或更高版本。
|
|
10
10
|
|
|
@@ -13,12 +13,18 @@ pnpm install
|
|
|
13
13
|
pnpm run typecheck
|
|
14
14
|
pnpm run lint
|
|
15
15
|
pnpm run build
|
|
16
|
-
pnpm run secret-scan
|
|
17
16
|
pnpm run dev
|
|
18
17
|
```
|
|
19
18
|
|
|
20
19
|
默认配置模板位于 `config/lens.example.jsonc`。本地运行时复制为 `config/lens.jsonc`。配置与 `config/pair.json` 的相对路径均以进程 `cwd` 为根。
|
|
21
20
|
|
|
21
|
+
客户端首次连接会登记未批准身份。本机管理员使用 `pnpm run pair:approve -- --client <client-id> --strong-trust` 批准并签发凭据;已安装包可执行 `node dist/entry/pair.js --client <client-id> --strong-trust`。省略 `--strong-trust` 签发只读客户端凭据。命令只返回一次凭据,管理员应通过受保护渠道交给客户端;再次签发会撤销旧凭据。
|
|
22
|
+
|
|
23
|
+
在 Facet 侧栏输入凭据。凭据只保存在当前页面内存中,HTTP、Artifact 和 SSE 请求统一携带认证。Pair 记录的 `strongTrust` 默认关闭;单独设置 `isVerified` 或复制设备身份字段不能获得访问权限。强信任客户端可在 General 中编辑受管理配置文件原文,包括 Pair 信任记录;普通客户端不允许配置原文读取或修改,也不能启动 Agent/工具等可绕过配置权限的操作。配置原文不进入请求日志。
|
|
24
|
+
|
|
25
|
+
配置文件接口列出 Core 主配置、Pair 文件,以及主配置所在目录中现有的非模板 JSON/JSONC 配置文件。其他配置保存后由其 owner 在重启时读取;API 不接受任意路径。远端组件的独立配置由该组件的本机管理员管理,不通过 Core 猜测远端文件路径。LAN 上传输凭据应使用 HTTPS。
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
已安装包的入口与构建产物入口:
|
|
23
29
|
|
|
24
30
|
```bash
|
|
@@ -28,6 +34,56 @@ node dist/entry/node.js
|
|
|
28
34
|
|
|
29
35
|
Executor 需要独立启动;`pnpm run dev` 不会自动启动它。
|
|
30
36
|
|
|
37
|
+
## 图片输入
|
|
38
|
+
|
|
39
|
+
`view_image({ "path": "截图.png" })` 通过 Executor 读取工作区图片,支持 PNG、JPEG、GIF、WebP,单张不超过 5 MiB;路径权限与 `read_file` 一致。新 Agent 默认加载该工具,已有 Agent 可通过 Capability 分配和加载启用。需要同时更新 Core 和 Executor(`core-executor-rpc: 8`),并选择支持图片输入的模型。
|
|
40
|
+
|
|
41
|
+
工具和 MCP 返回的图片保存为 Artifact,在下一次模型请求中作为图片内容发送;PTC 子调用的图片也会随根结果传递。图片只以 Artifact 文件持久化;Full Context 仅保存 Artifact ID,Working Context 的图片项仅保存 projectId/artifactId,MIME 和 SHA-256 留在 Artifact 元数据中。Working Context 的 Snip 可以移除较早的图片,手动 Include Result 会重新附带图片。请求快照也只保存 Artifact ID 引用;发送模型请求前才在内存中加载、校验并编码图片字节,不把 base64 或 data URL 回写到 Context、数据库或请求诊断。图片预算目前按每张约 4096 token 估算,实际消耗由模型决定。
|
|
42
|
+
|
|
43
|
+
本阶段提供工具图片输入,不包含聊天附件上传入口、图片生成、音频或视频。
|
|
44
|
+
|
|
45
|
+
## 文件工具
|
|
46
|
+
|
|
47
|
+
`read_file` 可读取 UTF-8 文件或列出目录。目录包含隐藏项,名称按序排列,子目录带 `/` 后缀;响应的 `kind` 为 `file` 或 `directory`。`start_line` 从 1 开始选择行或目录项,`max_lines` 限定返回数量;`offset` 和 `next_offset` 始终表示 UTF-8 字节位置,配合 `eof` 分页。目录在分页期间发生变化时应重新读取。
|
|
48
|
+
|
|
49
|
+
`edit_file` 合并创建、整文件重写和批量字符串替换:
|
|
50
|
+
|
|
51
|
+
- `create`:提供 `content`,目标必须不存在;创建父目录并原子发布,拒绝覆盖已有目标。
|
|
52
|
+
- `rewrite`:提供 `content`,目标必须为现有 UTF-8 常规文件。
|
|
53
|
+
- `edit`:提供非空 `edits` 数组,省略 `content`。每项包含 `old_string`、`new_string` 和可选 `replace_all`,按顺序在内存中替换;默认要求唯一精确匹配,全部成功后提交一次。
|
|
54
|
+
|
|
55
|
+
`patch_file` 接受单个 `path` 与 `patch` 更新块正文。块头为 `@@` 或 `@@ 精确行锚点`,正文分别用空格、`+`、`-` 标记上下文、新增、删除行。所有块基于同一份原文件,必须唯一命中且区间不重叠;`*** End of File` 限定匹配到文件末尾。没有旧内容的纯新增块仅用于空文件,且只能有一个块。未修改区段保持原字节;新增行采用附近的行尾风格。
|
|
56
|
+
|
|
57
|
+
edit 最多 128 个替换项,patch 最多 128 个块;输入文本总量、原文件和最终文件分别不超过 8 MiB。成功结果的 `structured_content` 为 `{path, changed, units_applied, bytes_written}`,未发生内容变化时 `bytes_written` 为 0。两个工具共用文件历史、调用重放、原子提交和 `revert_call`。原 `write_file` 与旧的单替换 `edit_file` 输入已移除;调用方需使用协议 8 的新合同,在途调用的原始参数不会自动改写。
|
|
58
|
+
|
|
59
|
+
## 历史读取
|
|
60
|
+
|
|
61
|
+
`history_search` 默认检索当前 Project 内各 Session、Task 和 Agent 的可见 Full Context,包括子代理和 Compact 之前的记录。可用 `agent_id`、`session_id`、`task_id` 筛选。结果携带来源身份与 `context_item_id`;分页使用返回的 `next_after_context_item_id` 作为下一次的 `after_context_item_id`,保持原筛选条件,避免不同代理重复的 ordinal 导致漏读。
|
|
62
|
+
|
|
63
|
+
`history_read` 根据 `context_item_id` 读取上述范围内的完整条目,用 UTF-8 字节 `offset`/`limit` 分页,并用 `digest` 检查连续读取期间的变化。用户隐藏的记录和其他 Project 的记录不开放;历史消息和工具调用仅作为证据,不自动成为当前指令或待执行调用。
|
|
64
|
+
|
|
65
|
+
## Skill 工具
|
|
66
|
+
|
|
67
|
+
`skill({})` 列出分配给当前 Agent 的 Skill 名称、描述和加载状态;`skill({"name":"名称"})` 返回完整指令、资源基目录和最多 10 个资源文件样本,并持久化加载状态,后续模型请求继续包含这些指令。目录中的符号链接不作为资源遍历。
|
|
68
|
+
|
|
69
|
+
工作区 Skill 可位于 `skills/`、`.agents/skills/` 或 `.codex/skills/` 下的子目录。`SKILL.md` 可用 YAML 前置元数据声明 `name` 和 `description`;没有元数据时沿用目录名称和正文标题。文件资源仅在实际加载该 Skill 时枚举,目录刷新不重复扫描所有资源。工作区仍须通过共享文件系统验证。
|
|
70
|
+
|
|
71
|
+
## 网页工具
|
|
72
|
+
|
|
73
|
+
`web_search` 接受 `query`,并支持 `num_results`(1–50)、`livecrawl`(`fallback` / `preferred`)、`type`(`auto` / `fast` / `deep`)和 `context_max_characters`(1–1048576)。搜索使用公共搜索服务,返回带来源的检索文本;总超时 25 秒。
|
|
74
|
+
|
|
75
|
+
`web_fetch` 接受 HTTP(S) `url`、`format`(默认 `markdown`,也可为 `text` / `html`)和秒数 `timeout`(默认 30,最大 120)。超时覆盖域名解析、最多 5 次重定向、响应头与响应体;流式读取和解压后的响应均限制为 5 MiB。Markdown 中的相对链接按最终 URL 解析,二进制响应作为 Artifact 保存。
|
|
76
|
+
|
|
77
|
+
默认只允许公共网络地址;持有 `full_access` 的调用可访问私有地址。URL 不接受内嵌凭据,每次重定向重新校验目标并固定连接到已验证的解析地址。网页和搜索结果作为外部证据处理,工具返回内容不授予新的指令权限。
|
|
78
|
+
|
|
79
|
+
搜索与 `full_access` 网页获取遵循 Node 的标准代理设置,可通过 `NODE_USE_ENV_PROXY=1` 或 `--use-env-proxy` 启用。仅公共地址的网页获取使用直接连接,保证连接地址与本地校验结果一致。
|
|
80
|
+
|
|
81
|
+
## 时间工具
|
|
82
|
+
|
|
83
|
+
`get_system_time({})` 返回 `unix_ms`、`utc` 和 `local`,三个字段表示同一时刻。UTC 使用 `YYYY-MM-DDTHH:mm:ssZ`;本地时间使用 `YYYY-MM-DDTHH:mm:ss±HH:mm`,仅包含偏移,不包含地区或时区名称。
|
|
84
|
+
|
|
85
|
+
`sleep_milliseconds({"milliseconds":1000})` 在当前工具调用中等待固定毫秒数,然后返回 `started_at_unix_ms` 和 `ended_at_unix_ms`。支持取消;`0` 立即返回,最大值为 `2147483647`。实际结束时刻可能因事件循环调度延后。
|
|
86
|
+
|
|
31
87
|
## 文档
|
|
32
88
|
|
|
33
89
|
2026-09-19 之前的架构设计、Context、计划、研究、审查、日期快照及相关生成物已归档到 `waste/2026-09-19/`,不再作为当前规范。
|
|
@@ -37,8 +93,9 @@ Executor 需要独立启动;`pnpm run dev` 不会自动启动它。
|
|
|
37
93
|
- [AGENTS.md](./AGENTS.md) — 仓库级工程与 Agent 工作规则。
|
|
38
94
|
- [CLAUDE.md](./CLAUDE.md) — Claude Code 的最小入口。
|
|
39
95
|
- [CONTEXT.md](./CONTEXT.md) — 当前术语、事实边界与工程共享语境。
|
|
40
|
-
- [docs/2026-09-19/current-system-architecture.md](./docs/2026-09-19/current-system-architecture.md) —
|
|
41
|
-
- [docs/
|
|
96
|
+
- [docs/2026-09-19-01/current-system-architecture.md](./docs/2026-09-19-01/current-system-architecture.md) — 基于 `5195e0a` 源码与 CodeGraph 重建的系统架构快照。
|
|
97
|
+
- [docs/2026-09-21-01/code-quality-review.md](./docs/2026-09-21-01/code-quality-review.md) — 基于 `e7325fd` 的模型、上下文、工具 schema、外部接入及 HTTP 质量审查、根因和修复建议;建议尚未实施。
|
|
98
|
+
- [docs/document-lifecycle.md](./docs/document-lifecycle.md) — 文档命名、治理与归档规则。
|
|
42
99
|
- [waste/manifest.md](./waste/manifest.md) — 历史归档清单。
|
|
43
100
|
|
|
44
101
|
不要根据 `waste/` 中的历史资料推断当前行为;需要确认时直接检查当前代码和可执行证据。
|
|
@@ -46,3 +103,5 @@ Executor 需要独立启动;`pnpm run dev` 不会自动启动它。
|
|
|
46
103
|
## 许可证
|
|
47
104
|
|
|
48
105
|
本项目使用 [MIT License](./LICENSE)。
|
|
106
|
+
|
|
107
|
+
本地 LSP 和工作区 Skills 要求在 `executor` 中显式设置 `sharedFilesystem: true`。Core 使用短暂的随机文件探针验证 Executor 能读取同一工作区,探针随后删除;未通过验证的路径不用于本地 Skills,LSP 请求明确失败。默认按远端文件系统处理。文件读、写、任意命令和 revert 在单 Core 调度器内使用保守的执行域锁,写操作串行、读操作可并发,不根据 Core 的 OS 猜测远端路径。该锁不约束其他 Core 或外部进程;未引入跨进程分布式锁或新的 RPC。
|
|
@@ -8,7 +8,22 @@
|
|
|
8
8
|
// -- a Worker ignoring cancellation, an LSP child not exiting -- stops holding the process past
|
|
9
9
|
// this point; anything left unconfirmed is resolved by Recovery on the next start.
|
|
10
10
|
"shutdown": { "graceMs": 30000 },
|
|
11
|
+
// For Executor per-Core directory policy, also set identityPath and credentialPath to private files
|
|
12
|
+
// relative to this Core's root. credentialPath contains the service token, not a browser Pair token.
|
|
13
|
+
// Remote service credentials require executor.tls.enabled=true. Restart Core after changing these.
|
|
11
14
|
"executor": { "address": "127.0.0.1:50051" },
|
|
15
|
+
// Optional second Executor on the Core computer (loopback only). Start it separately on port
|
|
16
|
+
// 50052 with its own pairing, history, audit and evidence storage. Restart Core after configuring.
|
|
17
|
+
// Existing Workspaces remain bound to executor ("remote"); new ones explicitly select local/remote.
|
|
18
|
+
// Local Workspace Skills require a shared-filesystem byte probe; remote paths are never read by Core.
|
|
19
|
+
// "localExecutor": { "address": "127.0.0.1:50052", "identityPath": "config/local-executor-identity.json" },
|
|
20
|
+
// Additional named connections, managed by Launcher. Workspace executor_id selects the exact ID.
|
|
21
|
+
// Remote paths are entered in Facet and validated on that server; registration grants no access.
|
|
22
|
+
// "executors": {
|
|
23
|
+
// "procurement": { "name": "Procurement server", "address": "server.example:50051",
|
|
24
|
+
// "identityPath": "config/executor-procurement-identity.json", "credentialPath": "config/procurement-token.txt",
|
|
25
|
+
// "tls": { "enabled": true, "caFile": "config/company-ca.pem" } }
|
|
26
|
+
// },
|
|
12
27
|
"artifacts": { "directory": "artifacts" },
|
|
13
28
|
"persistence": {
|
|
14
29
|
"eventLog": { "enabled": false, "path": "data/lens.events.jsonl", "fsync": false },
|
|
@@ -23,6 +38,15 @@
|
|
|
23
38
|
"providers": {
|
|
24
39
|
"openai": {
|
|
25
40
|
"adapter": "@ai-sdk/openai",
|
|
41
|
+
// Declare the exact effort strings accepted by your endpoint. Lens sends the selected
|
|
42
|
+
// value unchanged, including "none"; it does not infer thinking modes or budgets.
|
|
43
|
+
// A Model reasoningEfforts list replaces this Provider list. The first entry is
|
|
44
|
+
// the model picker's initial choice; roles below explicitly select their own effort.
|
|
45
|
+
"reasoningEfforts": ["medium", "none", "minimal", "low", "high"],
|
|
46
|
+
// Model promptCache overrides this Provider default. Explicit modes require Provider/model support.
|
|
47
|
+
"promptCache": { "mode": "implicit" },
|
|
48
|
+
// Optional compactionThresholdTokens here sets a Provider default. A Model value overrides it.
|
|
49
|
+
// When both are omitted: max(contextWindowTokens - 25000, contextWindowTokens * 0.95).
|
|
26
50
|
"options": { "apiKey": { "env": "OPENAI_API_KEY", "optional": true } },
|
|
27
51
|
"models": { "gpt-5": { "contextWindowTokens": 400000 } }
|
|
28
52
|
}
|
|
@@ -33,7 +57,7 @@
|
|
|
33
57
|
"model": "openai/gpt-5",
|
|
34
58
|
"modelReasoningEffort": "high",
|
|
35
59
|
"policyPreset": "Write",
|
|
36
|
-
"capabilities": ["capability_search", "programmatic_tool_run", "system_echo", "read_file", "
|
|
60
|
+
"capabilities": ["capability_search", "get_system_time", "sleep_milliseconds", "skill", "history_search", "history_read", "programmatic_tool_run", "system_echo", "read_file", "view_image", "patch_file", "edit_file", "glob", "grep", "terminal_command_run", "web_search", "web_fetch", "todo", "ask_user", "subagent_create", "subagent_message", "subagent_broadcast", "subagent_wait"],
|
|
37
61
|
"contextStrategy": "default",
|
|
38
62
|
"providerRouting": { "retryMaxAttempts": 2, "fallback": [] }
|
|
39
63
|
},
|
|
@@ -41,7 +65,7 @@
|
|
|
41
65
|
"model": "openai/gpt-5",
|
|
42
66
|
"modelReasoningEffort": "medium",
|
|
43
67
|
"policyPreset": "Write",
|
|
44
|
-
"capabilities": ["capability_search", "programmatic_tool_run", "system_echo", "read_file", "
|
|
68
|
+
"capabilities": ["capability_search", "get_system_time", "sleep_milliseconds", "skill", "history_search", "history_read", "programmatic_tool_run", "system_echo", "read_file", "view_image", "patch_file", "edit_file", "glob", "grep", "terminal_command_run", "web_search", "web_fetch", "todo", "ask_user", "subagent_wait"],
|
|
45
69
|
"contextStrategy": "default",
|
|
46
70
|
"providerRouting": { "retryMaxAttempts": 2, "fallback": [] }
|
|
47
71
|
},
|
|
@@ -65,27 +89,26 @@
|
|
|
65
89
|
"tokenizer": { "mode": "universal_tel", "version": 1 },
|
|
66
90
|
"policyPresets": {
|
|
67
91
|
"Read": { "capabilities": {
|
|
68
|
-
"capability_search": "read_only", "
|
|
92
|
+
"capability_search": "read_only", "get_system_time": "read_only", "sleep_milliseconds": "read_only", "skill": "read_only", "history_search": "read_only", "history_read": "read_only", "programmatic_tool_run": "full_access", "system_echo": "read_only", "read_file": "read_only", "view_image": "read_only", "glob": "read_only", "grep": "read_only", "subagent_wait": "read_only"
|
|
69
93
|
} },
|
|
70
94
|
"Write": { "capabilities": {
|
|
71
|
-
"capability_search": "read_only", "
|
|
95
|
+
"capability_search": "read_only", "get_system_time": "read_only", "sleep_milliseconds": "read_only", "skill": "read_only", "programmatic_tool_run": "full_access", "system_echo": "read_only", "read_file": "read_only", "view_image": "read_only", "patch_file": "workspace_write", "edit_file": "workspace_write", "glob": "read_only", "grep": "read_only", "terminal_command_run": "external", "web_search": "external", "web_fetch": "external", "todo": "workspace_write", "ask_user": "read_only", "subagent_create": "workspace_write", "subagent_message": "workspace_write", "subagent_broadcast": "workspace_write", "subagent_wait": "read_only", "slash_command": "ask"
|
|
72
96
|
} }
|
|
73
97
|
},
|
|
74
98
|
"concurrency": { "instanceMax": 8, "perStepMax": 4, "perAdapterMax": 4 },
|
|
75
99
|
"worker": { "timeoutMs": 120000, "outputLimitBytes": 1048576 },
|
|
76
100
|
"contextBudget": { "outputReserveTokens": 8192, "safetyMarginTokens": 2048 },
|
|
101
|
+
// User controls which slash commands the LLM may discover and invoke.
|
|
102
|
+
"localPtc": { "timeoutMs": 30000, "maxToolCalls": 32 },
|
|
103
|
+
"slashCommands": { "llmVisible": [] },
|
|
77
104
|
"capabilityInput": { "catalogMaxTokens": 4096, "searchResultMaxTokens": 4096 },
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"contextCollapse": { "triggerTokens": 128000, "projectedTokens": 64000 },
|
|
83
|
-
"stageVersion": "v1"
|
|
84
|
-
},
|
|
85
|
-
"compaction": { "preservedSegmentTokens": 16000, "preservedSegmentBytes": 262144, "summaryMaxTokens": 8192 },
|
|
105
|
+
// Working Context Snip starts at 60% of the model window. Keep the newest 15 tool results;
|
|
106
|
+
// older results over 1500 estimated tokens retain 800 head + 400 tail tokens.
|
|
107
|
+
// Compaction retains at least 5000 tokens at whole-message/tool-exchange boundaries.
|
|
108
|
+
"compaction": { "summaryMaxTokens": 8192 },
|
|
86
109
|
"autoReview": { "timeoutMs": 30000, "goalMaxBytes": 32768 },
|
|
87
110
|
"executive": { "verification": [] },
|
|
88
|
-
"providerPromptCache": { "enabled":
|
|
111
|
+
"providerPromptCache": { "enabled": true, "minimumEligiblePrefixTokens": 1024, "maximumBreakpoints": 4 },
|
|
89
112
|
"historyRetention": { "purgeMode": "manual", "purgeArchivedAfterDays": null },
|
|
90
113
|
"snapshot": { "maxRuntimeEntities": 50000, "pageSizeDefault": 100, "pageSizeMax": 500 },
|
|
91
114
|
"sse": { "heartbeatIntervalMs": 15000, "maxBufferedEvents": 1024, "maxBufferedBytes": 1048576 }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type CapabilityAdapter, type CapabilityInvocation, type CapabilityOutput, type Json } from '../core/domain.js';
|
|
2
|
+
import type { Store } from '../store/store.js';
|
|
3
|
+
import { type ExecutorClient } from './executor.js';
|
|
4
|
+
import type { RegisteredCapability } from '../runtime/capabilities.js';
|
|
5
|
+
export type ExecutorId = string;
|
|
6
|
+
/** Workspace bindings, not path syntax or model declarations, select the machine. */
|
|
7
|
+
export declare class ExecutorRouter implements CapabilityAdapter {
|
|
8
|
+
readonly store: Store;
|
|
9
|
+
readonly remote: ExecutorClient;
|
|
10
|
+
readonly local?: ExecutorClient | undefined;
|
|
11
|
+
readonly additional: ReadonlyMap<string, ExecutorClient>;
|
|
12
|
+
readonly id = "executor";
|
|
13
|
+
constructor(store: Store, remote: ExecutorClient, local?: ExecutorClient | undefined, additional?: ReadonlyMap<string, ExecutorClient>);
|
|
14
|
+
endpoint(id: ExecutorId): ExecutorClient;
|
|
15
|
+
workspace(projectId: string, workspaceId: string): ExecutorClient;
|
|
16
|
+
execute(invocation: CapabilityInvocation): Promise<CapabilityOutput>;
|
|
17
|
+
capabilities(): readonly RegisteredCapability[];
|
|
18
|
+
health(): Promise<Json>;
|
|
19
|
+
deleteCallHistory(workspaceId: string, callIds: readonly string[]): Promise<Json>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { digest } from '../core/codec.js';
|
|
2
|
+
import { fail } from '../core/domain.js';
|
|
3
|
+
import { executorCapabilities } from './executor.js';
|
|
4
|
+
import { capabilityManifest } from '../runtime/manifests.js';
|
|
5
|
+
/** Workspace bindings, not path syntax or model declarations, select the machine. */
|
|
6
|
+
export class ExecutorRouter {
|
|
7
|
+
store;
|
|
8
|
+
remote;
|
|
9
|
+
local;
|
|
10
|
+
additional;
|
|
11
|
+
id = 'executor';
|
|
12
|
+
constructor(store, remote, local, additional = new Map()) {
|
|
13
|
+
this.store = store;
|
|
14
|
+
this.remote = remote;
|
|
15
|
+
this.local = local;
|
|
16
|
+
this.additional = additional;
|
|
17
|
+
}
|
|
18
|
+
endpoint(id) {
|
|
19
|
+
if (id === 'remote')
|
|
20
|
+
return this.remote;
|
|
21
|
+
const configured = this.additional.get(id);
|
|
22
|
+
if (configured)
|
|
23
|
+
return configured;
|
|
24
|
+
if (id !== 'local')
|
|
25
|
+
fail('EXECUTOR_NOT_CONFIGURED', 409, 'The Workspace Executor is not configured. Its files were not routed elsewhere.');
|
|
26
|
+
if (!this.local)
|
|
27
|
+
fail('LOCAL_EXECUTOR_NOT_CONFIGURED', 409, 'Configure a loopback local Executor before selecting local folders.');
|
|
28
|
+
return this.local;
|
|
29
|
+
}
|
|
30
|
+
workspace(projectId, workspaceId) {
|
|
31
|
+
const id = this.store.getWorkspace(projectId, workspaceId).executor_id;
|
|
32
|
+
if (typeof id !== 'string')
|
|
33
|
+
fail('EXECUTOR_NOT_CONFIGURED', 409, 'Workspace Executor binding is invalid.');
|
|
34
|
+
return this.endpoint(id);
|
|
35
|
+
}
|
|
36
|
+
async execute(invocation) {
|
|
37
|
+
if (invocation.capabilityId === 'list_workspaces')
|
|
38
|
+
return { content: { workspaces: this.store.listWorkspaces(invocation.projectId) } };
|
|
39
|
+
const input = invocation.arguments;
|
|
40
|
+
const targetId = typeof input.source_workspace_id === 'string' ? input.source_workspace_id : invocation.workspaceId;
|
|
41
|
+
const workspace = this.store.getWorkspace(invocation.projectId, targetId);
|
|
42
|
+
if (targetId !== invocation.workspaceId && !['read_file', 'view_image', 'glob', 'grep'].includes(invocation.capabilityId)) {
|
|
43
|
+
fail('WORKSPACE_WRITE_DENIED', 403, 'Cross-Workspace operations are read-only.');
|
|
44
|
+
}
|
|
45
|
+
const paths = Array.isArray(workspace.paths) ? workspace.paths.filter((value) => typeof value === 'string') : [];
|
|
46
|
+
return this.workspace(invocation.projectId, targetId).execute({ ...invocation, workspaceId: targetId, workspacePaths: paths,
|
|
47
|
+
// A mount never inherits the active Workspace's full-access escape hatch.
|
|
48
|
+
fullAccess: targetId === invocation.workspaceId && invocation.fullAccess === true });
|
|
49
|
+
}
|
|
50
|
+
capabilities() {
|
|
51
|
+
const discovery = {
|
|
52
|
+
id: 'list_workspaces', title: 'List Project Workspaces', description: 'List Project Workspace IDs, execution endpoints and paths. Use source_workspace_id on read_file/glob/grep to retrieve source material from another Workspace. Write reports in the active local Workspace. Listing does not guarantee access or complete search coverage.',
|
|
53
|
+
adapterId: 'executor', effect: 'read_only', loadMode: 'eager', outputLimitBytes: 65536, defaultAssignment: 'all_new_agents',
|
|
54
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false }
|
|
55
|
+
};
|
|
56
|
+
return [{ definition: discovery, adapter: this, manifest: capabilityManifest(discovery), claims: () => [] }, ...executorCapabilities(this.remote).map(capability => {
|
|
57
|
+
const readable = ['read_file', 'view_image', 'glob', 'grep'].includes(capability.definition.id);
|
|
58
|
+
const schema = capability.definition.inputSchema;
|
|
59
|
+
const definition = readable ? { ...capability.definition,
|
|
60
|
+
description: `${capability.definition.description} Defaults to the active Workspace. To read a different Workspace in this Project, set source_workspace_id from list_workspaces; never infer the machine from a path.`,
|
|
61
|
+
inputSchema: { ...schema, properties: { ...schema.properties, source_workspace_id: { type: 'string', minLength: 1 } } }
|
|
62
|
+
} : capability.definition;
|
|
63
|
+
return { ...capability, definition, adapter: this, manifest: capabilityManifest(definition) };
|
|
64
|
+
})];
|
|
65
|
+
}
|
|
66
|
+
async health() {
|
|
67
|
+
const remote = await this.remote.health();
|
|
68
|
+
if (!this.local && this.additional.size === 0)
|
|
69
|
+
return remote;
|
|
70
|
+
const state = (value) => {
|
|
71
|
+
if (typeof value !== 'object' || value === null || !('state_id' in value) || typeof value.state_id !== 'string')
|
|
72
|
+
fail('EXECUTOR_IDENTITY_REQUIRED', 503, 'Executor state identity is unavailable.');
|
|
73
|
+
return value.state_id;
|
|
74
|
+
};
|
|
75
|
+
const states = { remote: state(remote) };
|
|
76
|
+
if (this.local)
|
|
77
|
+
states.local = state(await this.local.health());
|
|
78
|
+
for (const [id, client] of this.additional)
|
|
79
|
+
states[id] = state(await client.health());
|
|
80
|
+
return { state_id: digest(states) };
|
|
81
|
+
}
|
|
82
|
+
async deleteCallHistory(workspaceId, callIds) {
|
|
83
|
+
const workspace = this.store.workspaceOwner(workspaceId);
|
|
84
|
+
return this.workspace(workspace.projectId, workspaceId).deleteCallHistory(workspaceId, callIds);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import * as grpc from '@grpc/grpc-js';
|
|
1
2
|
import { type CapabilityAdapter, type CapabilityInvocation, type CapabilityOutput, type Json } from '../core/domain.js';
|
|
2
3
|
import type { ConfigRuntime } from '../config/runtime.js';
|
|
4
|
+
import type { HarnessConfig } from '../config/schema.js';
|
|
3
5
|
import type { RegisteredCapability } from '../runtime/capabilities.js';
|
|
4
6
|
/**
|
|
5
7
|
* Carries the Executor's gRPC status alongside the message. Without it every failure looked the
|
|
@@ -16,20 +18,24 @@ type ObjectValue = Readonly<Record<string, unknown>>;
|
|
|
16
18
|
export declare class ExecutorClient implements CapabilityAdapter {
|
|
17
19
|
#private;
|
|
18
20
|
readonly config: ConfigRuntime;
|
|
21
|
+
readonly endpoint: HarnessConfig['executor'];
|
|
19
22
|
readonly id = "executor";
|
|
20
23
|
private constructor();
|
|
21
|
-
static create(root: string, runtime: ConfigRuntime): Promise<ExecutorClient>;
|
|
24
|
+
static create(root: string, runtime: ConfigRuntime, endpoint?: HarnessConfig['executor']): Promise<ExecutorClient>;
|
|
22
25
|
close(): void;
|
|
23
26
|
health(signal?: AbortSignal): Promise<Json>;
|
|
27
|
+
taskAudit(projectId: string, taskId: string, limit: number, cursor?: string): Promise<Json>;
|
|
24
28
|
browse(path: string, signal?: AbortSignal): Promise<Json>;
|
|
25
29
|
chooseFolder(_initialPath?: string, signal?: AbortSignal): Promise<Json>;
|
|
26
30
|
open(workspacePaths: readonly string[], path: string, signal?: AbortSignal): Promise<Json>;
|
|
27
31
|
validatePaths(paths: readonly string[], signal?: AbortSignal, createIfMissing?: boolean): Promise<Json>;
|
|
32
|
+
sharedWorkspace(path: string): boolean;
|
|
33
|
+
verifySharedPaths(paths: readonly string[], signal?: AbortSignal): Promise<void>;
|
|
28
34
|
deleteCallHistory(workspaceId: string, callIds: readonly string[], signal?: AbortSignal): Promise<Json>;
|
|
29
35
|
describeCallHistory(workspaceId: string, callIds: readonly string[], workspacePaths: readonly string[], fullAccess: boolean, signal?: AbortSignal): Promise<Json>;
|
|
30
36
|
execute(invocation: CapabilityInvocation): Promise<CapabilityOutput>;
|
|
31
|
-
call(method: string, request: ObjectValue, timeoutMs: number, signal?: AbortSignal): Promise<Json>;
|
|
37
|
+
call(method: string, request: ObjectValue, timeoutMs: number, signal?: AbortSignal, audit?: grpc.Metadata): Promise<Json>;
|
|
32
38
|
}
|
|
33
39
|
export declare function executorCapabilities(adapter: ExecutorClient): readonly RegisteredCapability[];
|
|
34
|
-
export declare function
|
|
40
|
+
export declare function verifySharedWorkspace(root: string, read: (path: string, expected: string) => Promise<boolean>): Promise<void>;
|
|
35
41
|
export {};
|