@epoch-agent/server 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +165 -7
- package/dist/index.js +148 -12
- package/dist/web/assets/index-B0ky2TY1.css +1 -0
- package/dist/web/assets/index-C8ef1aOs.js +376 -0
- package/dist/web/index.html +25 -3
- package/package.json +3 -3
- package/dist/web/assets/index-DqVr3JKp.css +0 -1
- package/dist/web/assets/index-LrChVcRS.js +0 -375
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Lang, WireSkillImportFailure, WireSkillImportForm, WireSkillRemoveFailure, WireCapabilitiesResponse, WireSkillBodyResponse, EpochUserContent, AgentEvent, WireEnvelope, CustomCommandDef, ExpandedCommand, ModelRef, SetSelectionResult, WireCommandExpansion, WireCustomCommand, WireGoalPhase, WireGoalRefusal, WireTurnState, WireSessionFinish, ApprovalOutcome, QuestionAnswer, SessionSurfaceView, WireSessionReference, WireFileReference, ProviderType, ModelSelectionOrigin, WireModelCaveat, WireModelRejection, ContextBreakdown, PermissionLevel, WirePermissionRememberFailure, WirePluginSourceType, WirePluginRefuseReason, WirePluginEntry, WireModelSource, WireModelProbeStatus, ScheduleDefinition, ScheduleRun, ScheduleBackendKind, OperationType, ScheduleTrigger, WireScheduleIssue, ScheduleRunStatus, TrustLevel, WireSandboxReason, WireAuditOutcome, WireAuditCode, WireCachedApproval, WireCachedDecision, WireToolVerdict, WireToolGateAxis, WireSecurityResponse, WireRewindScope, WireRewindRequest, WireCheckpointSummary, WireRewindPreview, WireRewindResponse, ActiveAgentRole, WireSettingValue, WireSettingLayer, WireSettingWriteLayer, WireSettingFutile, WireSettingApply, WireSettingValueKind, WireSettingsResponse, EpochConfig, TokenUsage, WireCompressionLine, WireProviderSummary, WireToolSummary, Diagnostic, WireReplayMessage, WireFileChange, WireAuthGuard, WireWorkspaceDiffResponse, BackgroundTaskInfo, WireTasksResponse, WireBackgroundTask, WireScheduleRow } from '@epoch-agent/protocol';
|
|
1
|
+
import { Lang, WireSkillImportFailure, WireSkillImportForm, WireSkillRemoveFailure, WireCapabilitiesResponse, WireSkillBodyResponse, EpochUserContent, AgentEvent, WireEnvelope, CustomCommandDef, ExpandedCommand, ModelRef, SetSelectionResult, WireCommandExpansion, WireCustomCommand, WireGoalPhase, WireGoalRefusal, WireTurnState, WireSessionFinish, ApprovalOutcome, QuestionAnswer, SessionSurfaceView, WireSessionReference, WireFileReference, ProviderType, ModelSelectionOrigin, WireModelCaveat, WireModelRejection, ContextBreakdown, PermissionLevel, WirePermissionRememberFailure, WirePluginSourceType, WirePluginVisibility, WirePluginStatus, WirePluginRefuseReason, WirePluginUploadFieldName, WirePluginUploadFields, WirePluginEntry, WireModelSource, WireModelProbeStatus, ScheduleDefinition, ScheduleRun, ScheduleBackendKind, OperationType, ScheduleTrigger, WireScheduleIssue, ScheduleRunStatus, TrustLevel, WireSandboxReason, WireAuditOutcome, WireAuditCode, WireCachedApproval, WireCachedDecision, WireToolVerdict, WireToolGateAxis, WireSecurityResponse, WireRewindScope, WireRewindRequest, WireCheckpointSummary, WireRewindPreview, WireRewindResponse, ActiveAgentRole, WireSettingValue, WireSettingLayer, WireSettingWriteLayer, WireSettingFutile, WireSettingApply, WireSettingValueKind, WireSettingsResponse, EpochConfig, TokenUsage, WireCompressionLine, WireProviderSummary, WireToolSummary, Diagnostic, WireReplayMessage, WireFileChange, WireAuthGuard, WireWorkspaceDiffResponse, BackgroundTaskInfo, WireTasksResponse, WireBackgroundTask, WireScheduleRow } from '@epoch-agent/protocol';
|
|
2
2
|
import { SerializableApprovalRequest, SerializableQuestionRequest, ApprovalRevokeResult, LevelChangeResult, WorkspaceFilesView } from '@epoch-agent/runtime';
|
|
3
3
|
import { ServerResponse } from 'node:http';
|
|
4
4
|
|
|
@@ -1510,6 +1510,34 @@ declare class SessionHub {
|
|
|
1510
1510
|
* 调用方回 404,同审批。
|
|
1511
1511
|
*/
|
|
1512
1512
|
respondQuestion(requestId: string, answer: QuestionAnswer): boolean;
|
|
1513
|
+
/**
|
|
1514
|
+
* 广播「这个会话的后台任务清单变了」(方案 36 PR-3)。
|
|
1515
|
+
*
|
|
1516
|
+
* 唯一的调用方是 `index.ts` 里那条对作业表的订阅 —— Hub 自己不认识任务表
|
|
1517
|
+
* (那是 runtime 转出来的一张进程级的表),它在这条链上只负责发帧。
|
|
1518
|
+
*
|
|
1519
|
+
* ## ⚠️ **不查 `registry` 有没有这个会话**
|
|
1520
|
+
*
|
|
1521
|
+
* 作业表是**进程级**的,而 Hub 那张只装此刻活跃的那几段。两者对不上有两种
|
|
1522
|
+
* 真实情况,各自都该发帧:
|
|
1523
|
+
*
|
|
1524
|
+
* - **被冷却的会话**(`onCooled` 把它从 registry 里摘了)它的 `pnpm dev`
|
|
1525
|
+
* 照旧在跑,而正看着它的那个标签页照旧要知道它跑完了没有;
|
|
1526
|
+
* - **不经过 Hub 的会话**(定时任务、嵌入宿主自己起的那一段)—— 那些帧没有
|
|
1527
|
+
* 客户端认领,`reducer.ts` 的 `parkFrame` 会把它们折进 `parked` 里,
|
|
1528
|
+
* 代价是一格没人读的对象。
|
|
1529
|
+
*
|
|
1530
|
+
* 查一下会便宜一点,但那条判据是错的:**它把「Hub 认不认识」当成了「值不值得
|
|
1531
|
+
* 广播」,而这一帧的宾语是任务表不是 Hub**。收窄的代价是第一种情况静默失效,
|
|
1532
|
+
* 而那正好是这一帧最该管的那一档(冷却的会话没有回合状态跃迁,
|
|
1533
|
+
* 它的任务数除了这一帧之外没有任何东西会推)。
|
|
1534
|
+
*
|
|
1535
|
+
* ⚠️ 代价如实记在这儿:每一帧占一个全局序号、进那 512 格的环形缓冲。
|
|
1536
|
+
* 这一族事件很稀(起一格 / 结束一格,**不含输出**,判据在 `infra` 的
|
|
1537
|
+
* `notifyJobs` 上),所以它挤不动重连游标 —— 那条前提一旦破了(比如有人
|
|
1538
|
+
* 把这条通道接到 `append` 上),先回来看这一段。
|
|
1539
|
+
*/
|
|
1540
|
+
notifyTasksChanged(sessionId: string): void;
|
|
1513
1541
|
/**
|
|
1514
1542
|
* 收摊:撤掉倒计时、放弃所有挂起的审批、中止所有在跑的回合。
|
|
1515
1543
|
*
|
|
@@ -2263,11 +2291,12 @@ interface RuntimeModel {
|
|
|
2263
2291
|
}
|
|
2264
2292
|
|
|
2265
2293
|
/**
|
|
2266
|
-
*
|
|
2267
|
-
* 补,判据在 protocol 的 `wire-plugin.ts`
|
|
2294
|
+
* 插件那一页的十三条端点(方案 59 §六 E2,2026-08-21;本地源与市场管理 2026-09-04
|
|
2295
|
+
* 补,判据在 protocol 的 `wire-plugin.ts` 文件头第一节那次**翻案**;上传 2026-09-08
|
|
2296
|
+
* 补,判据在那份文件头第一节之二;刷新 2026-09-10 补):
|
|
2268
2297
|
*
|
|
2269
2298
|
* ```
|
|
2270
|
-
* GET /api/plugins 装着的 + 市场里有什么 + 已加的市场 + pendingRestart
|
|
2299
|
+
* GET /api/plugins 装着的 + 市场里有什么 + 已加的市场 + pendingRestart + uploadEnabled
|
|
2271
2300
|
* POST /api/plugins/install/preview {ref} → 将安装什么,一个字节都不写
|
|
2272
2301
|
* POST /api/plugins/install {ref, token} → 真装
|
|
2273
2302
|
* POST /api/plugins/install/local/preview {path} → 同上,来源是本机一条目录
|
|
@@ -2277,6 +2306,9 @@ interface RuntimeModel {
|
|
|
2277
2306
|
* POST /api/plugins/uninstall {name} → 卸载
|
|
2278
2307
|
* POST /api/plugins/marketplaces {source} → 加一个市场
|
|
2279
2308
|
* POST /api/plugins/marketplaces/remove {name} → 移除一个市场
|
|
2309
|
+
* POST /api/plugins/upload/preview {path} → 会打包哪些文件,一个字节都不发
|
|
2310
|
+
* POST /api/plugins/upload {path, token} → 打包 + 交给宿主
|
|
2311
|
+
* POST /api/plugins/refresh (不读正文) → 让宿主重拉目录 + 回一份新列表
|
|
2280
2312
|
* ```
|
|
2281
2313
|
*
|
|
2282
2314
|
* ## ⚠️ 这一层**一条策略判断都没有**
|
|
@@ -2290,10 +2322,10 @@ interface RuntimeModel {
|
|
|
2290
2322
|
* 界面上是一条画成可点、点了却被拒的行(判据同 `PluginSearchHit.installable`
|
|
2291
2323
|
* 为什么不从结果里剔掉远程那条)。
|
|
2292
2324
|
*
|
|
2293
|
-
* ## ⚠️
|
|
2325
|
+
* ## ⚠️ 那一道闸:**十一条 POST 在回环之外一律 403**,这是这个文件的正题
|
|
2294
2326
|
*
|
|
2295
2327
|
* 判据全文在 protocol 的 [wire-plugin.ts](../../protocol/src/wire-plugin.ts) 文件头
|
|
2296
|
-
*
|
|
2328
|
+
* 第二节,这里记它对这一层的**六**条直接后果:
|
|
2297
2329
|
*
|
|
2298
2330
|
* 1. **`ctx.lanExposed` 为真时 403 `plugin-write-lan-exposed`,一个字节都不写。**
|
|
2299
2331
|
* 装一包插件会落下 `hooks.json` 和 `mcp.json`,那两样**起子进程** ——
|
|
@@ -2312,6 +2344,10 @@ interface RuntimeModel {
|
|
|
2312
2344
|
* 唯一后果是那一页在 LAN 那一档下一片空白,
|
|
2313
2345
|
* 偏偏那时用户最想知道的就是「我装了什么、生效了没有」。
|
|
2314
2346
|
* (对比 `GET /api/mcp/config` 为什么连读都挡:那份文件里的 `env` 是明文密钥。)
|
|
2347
|
+
* 6. **上传那两条是这张表里最该挡的**(2026-09-08):它们读这台机器上任意一条
|
|
2348
|
+
* 目录、把内容**发到外面去**。放行的话,一个 LAN 上的人能指着 `~/.ssh` 说
|
|
2349
|
+
* 「上传它」。⚠️ **预览那一条同样挡**,虽然它一个字节都不发 ——
|
|
2350
|
+
* 它回的那份**文件清单本身就是情报**(拿它能探这台机器上有什么)。
|
|
2315
2351
|
*
|
|
2316
2352
|
* ## ⚠️ 这一片可以整个不存在 —— 503 `no-plugin-control`
|
|
2317
2353
|
*
|
|
@@ -2320,6 +2356,11 @@ interface RuntimeModel {
|
|
|
2320
2356
|
* JSDoc 上)。六条一律 503,判据同 `no-goal-store`:那是一句关于**这个部署**的
|
|
2321
2357
|
* 真话,而一份「装了 0 个插件」的空列表会让用户去找一个不存在的安装按钮。
|
|
2322
2358
|
*
|
|
2359
|
+
* ⚠️ **上传那两条还有第二档「没开」,而它比 503 那一档窄**:`runtime.plugins`
|
|
2360
|
+
* 在、但宿主没给 `upload` 回调 —— 那时它们回 **200 + `upload-unavailable`**,
|
|
2361
|
+
* 不是 503。两档分得开是有用的:503 说「这一整页没开」,那一档说
|
|
2362
|
+
* 「页开着,只是没有交出去这条路」,而后者正是 `epoch web --plugins` 的常态。
|
|
2363
|
+
*
|
|
2323
2364
|
* ⚠️ **默认的 `epoch web` 就落在这一档上**(不给 `--plugins` 就不传
|
|
2324
2365
|
* `hostMarketplaces`)。那一格 2026-08-21 答掉了:`epoch web --plugins` 才接上
|
|
2325
2366
|
* (传 `{ sources: [] }`),**默认关**,判据在
|
|
@@ -2378,6 +2419,35 @@ interface PluginHitView {
|
|
|
2378
2419
|
source: string;
|
|
2379
2420
|
category?: string;
|
|
2380
2421
|
keywords?: readonly string[];
|
|
2422
|
+
/** 目录里那一格「谁写的」。**摊平在投影里做**,判据在 `WirePluginHit.author` 上 */
|
|
2423
|
+
author?: {
|
|
2424
|
+
name: string;
|
|
2425
|
+
};
|
|
2426
|
+
/**
|
|
2427
|
+
* 市场那一侧数的两个计数(2026-09-08)。**可选**,而缺席在网线上变 `null`
|
|
2428
|
+
* 不是 `0` —— 判据在 `WirePluginHit.downloads` 上(「没人报」和「一次都没装过」
|
|
2429
|
+
* 是两句不同的话)。
|
|
2430
|
+
*/
|
|
2431
|
+
downloads?: number;
|
|
2432
|
+
likes?: number;
|
|
2433
|
+
/** 展示用的那几格(2026-09-08)。全部可选 → 网线上一律 `null` */
|
|
2434
|
+
displayName?: string;
|
|
2435
|
+
icon?: string;
|
|
2436
|
+
/** **市场说的**版本。`updateAvailable` 是拿它和装着的那个比出来的 */
|
|
2437
|
+
version?: string;
|
|
2438
|
+
changelog?: string;
|
|
2439
|
+
size?: number;
|
|
2440
|
+
homepage?: string;
|
|
2441
|
+
visibility?: WirePluginVisibility;
|
|
2442
|
+
/**
|
|
2443
|
+
* 这一条在市场那一侧的档口(2026-09-10)。可选 → 网线上 `null`。
|
|
2444
|
+
*
|
|
2445
|
+
* ⚠️ **缺席有两种成因,而这一层不分**:这个市场压根不报这个,或者它报了、
|
|
2446
|
+
* 但在自己那份目录的 `display.fields` 里没选它(判据在 core 的
|
|
2447
|
+
* `projectEntryForDisplay` 上 —— 投影在「市场里有什么」那个唯一出口做完了)。
|
|
2448
|
+
* 两者在屏幕上是同一句话(那一格不画),所以这儿不需要第二格去分。
|
|
2449
|
+
*/
|
|
2450
|
+
status?: WirePluginStatus;
|
|
2381
2451
|
};
|
|
2382
2452
|
sourceType?: WirePluginSourceType;
|
|
2383
2453
|
installable: boolean;
|
|
@@ -2460,7 +2530,49 @@ type MarketOutcomeView = {
|
|
|
2460
2530
|
marketplace?: MarketplaceView;
|
|
2461
2531
|
} | RefusalView;
|
|
2462
2532
|
/**
|
|
2463
|
-
* `
|
|
2533
|
+
* `PackPlan`(core,经 runtime 原样透出)的镜像 —— 「会打包什么」那一屏的内容。
|
|
2534
|
+
*
|
|
2535
|
+
* ⚠️ 借的是**整份 `files`** 而不是一个长度:判据在 `WirePluginUploadPlan.files`
|
|
2536
|
+
* 上(这条路上最常见的事故是「目录里有一份 `.env`」,一个计数答不出那件事)。
|
|
2537
|
+
*/
|
|
2538
|
+
interface PackPlanView {
|
|
2539
|
+
manifest: {
|
|
2540
|
+
name: string;
|
|
2541
|
+
version: string;
|
|
2542
|
+
description?: string;
|
|
2543
|
+
author?: {
|
|
2544
|
+
name: string;
|
|
2545
|
+
};
|
|
2546
|
+
};
|
|
2547
|
+
files: readonly {
|
|
2548
|
+
path: string;
|
|
2549
|
+
bytes: number;
|
|
2550
|
+
}[];
|
|
2551
|
+
totalBytes: number;
|
|
2552
|
+
suspicious: readonly string[];
|
|
2553
|
+
skippedLinks: readonly string[];
|
|
2554
|
+
}
|
|
2555
|
+
type UploadPreviewOutcomeView = {
|
|
2556
|
+
ok: true;
|
|
2557
|
+
plan: PackPlanView;
|
|
2558
|
+
token: string;
|
|
2559
|
+
} | RefusalView;
|
|
2560
|
+
/**
|
|
2561
|
+
* 上传的结局。
|
|
2562
|
+
*
|
|
2563
|
+
* ⚠️ 成功那一档借的是 `detail`(**宿主原话**)而不是我们编的一句 —— 判据在
|
|
2564
|
+
* `WirePluginUploadResponse.detail` 上:上传之后发生什么(直接上架 / 排队审核)
|
|
2565
|
+
* 是宿主那一侧的流程,这一层编不出来。
|
|
2566
|
+
*/
|
|
2567
|
+
type UploadOutcomeView = {
|
|
2568
|
+
ok: true;
|
|
2569
|
+
detail: string;
|
|
2570
|
+
name: string;
|
|
2571
|
+
version: string;
|
|
2572
|
+
bytes: number;
|
|
2573
|
+
} | RefusalView;
|
|
2574
|
+
/**
|
|
2575
|
+
* `PluginControl`(runtime)在服务端眼里的样子 —— **只有那十四个动词加两格布尔**。
|
|
2464
2576
|
*
|
|
2465
2577
|
* 镜像里只有它们,于是这个包**写不出** core 的 `setPluginEnabled`
|
|
2466
2578
|
* (停用一个插件会静默拿掉别人正依赖的一批命令,正当性和「装 / 卸」不是一回事);
|
|
@@ -2486,7 +2598,49 @@ interface PluginControlView {
|
|
|
2486
2598
|
marketplaces(): readonly MarketplaceView[];
|
|
2487
2599
|
addMarketplace(source: string, lang?: Lang): Promise<MarketOutcomeView>;
|
|
2488
2600
|
removeMarketplace(name: string, lang?: Lang): Promise<MarketOutcomeView>;
|
|
2601
|
+
/**
|
|
2602
|
+
* 「会打包什么」。⚠️ **同步的**(runtime 那一层只走一遍目录,不起子进程)——
|
|
2603
|
+
* 镜像上写成 `Promise` 也满足,但那会让这一层顺手 `await` 一个不用等的东西
|
|
2604
|
+
*/
|
|
2605
|
+
previewUpload(path: string, lang?: Lang): UploadPreviewOutcomeView;
|
|
2606
|
+
upload(path: string, token: string,
|
|
2607
|
+
/**
|
|
2608
|
+
* 用户在那一屏上填的(2026-09-10 从两格放宽到七格)。
|
|
2609
|
+
*
|
|
2610
|
+
* ⚠️ **两道收窄都由下一层做,这一层一格都不过滤**:宿主没声明要问的丢掉、
|
|
2611
|
+
* 宿主锁死的(`editable: false`)也丢掉换成它自己的值 —— 判据在 runtime 的
|
|
2612
|
+
* `PluginUploadForm` 上。这一层只把请求体收窄成**形状**(见 {@link uploadFormOf})。
|
|
2613
|
+
*/
|
|
2614
|
+
form: UploadFormView, lang?: Lang): Promise<UploadOutcomeView>;
|
|
2489
2615
|
readonly pendingRestart: boolean;
|
|
2616
|
+
/**
|
|
2617
|
+
* 上传表单多问哪几格、每一格长什么样。**七个键恒有,不画的是 `null`** ——
|
|
2618
|
+
* runtime 那一层拍平好的(2026-09-10 从两个布尔换成形态表)。
|
|
2619
|
+
*
|
|
2620
|
+
* ⚠️ 这一层**原样投影,一格都不重判**:「哪几档写法等价」整个在 runtime 的
|
|
2621
|
+
* `normalizeUploadField` 里,在这儿再判一次就是给同一个问题第二个答案。
|
|
2622
|
+
*/
|
|
2623
|
+
readonly uploadOptions: WirePluginUploadFields;
|
|
2624
|
+
/**
|
|
2625
|
+
* 宿主给了上传出口吗。**runtime 算好的结论**,这一层原样投影上网线 ——
|
|
2626
|
+
* 在这儿重判一次(比如看那个回调在不在)就是给同一个问题第二个答案,
|
|
2627
|
+
* 判据同文件头那句「这一层一条策略判断都没有」。
|
|
2628
|
+
*/
|
|
2629
|
+
readonly uploadEnabled: boolean;
|
|
2630
|
+
/**
|
|
2631
|
+
* 去把目录重拉一遍(2026-09-10)。两段(宿主取新目录 → 我们吃进
|
|
2632
|
+
* `marketplaces.json`)都在 runtime 那一层,这儿只转一发。
|
|
2633
|
+
*
|
|
2634
|
+
* ⚠️ 回执里**没有列表** —— 刷完之后那一份由这一层自己 `pluginsPayload()`
|
|
2635
|
+
* 现读一次。让 runtime 顺手把列表捎回来的话,「首屏那一份长什么样」
|
|
2636
|
+
* 就有了第二个产地,判据在 `pluginsPayload` 上。
|
|
2637
|
+
*/
|
|
2638
|
+
refresh(lang?: Lang): Promise<ActionOutcomeView>;
|
|
2639
|
+
/**
|
|
2640
|
+
* 宿主给了刷新出口吗。**runtime 算好的结论**,这一层原样投影上网线 ——
|
|
2641
|
+
* 口径逐字同 {@link uploadEnabled}。
|
|
2642
|
+
*/
|
|
2643
|
+
readonly refreshEnabled: boolean;
|
|
2490
2644
|
}
|
|
2491
2645
|
/**
|
|
2492
2646
|
* 装着的一条 → 网线上那一行。
|
|
@@ -2497,6 +2651,10 @@ interface PluginControlView {
|
|
|
2497
2651
|
* 「什么都不带」。
|
|
2498
2652
|
*/
|
|
2499
2653
|
declare function toWirePluginEntry(entry: PluginEntryView): WirePluginEntry;
|
|
2654
|
+
/** `PluginUploadForm`(runtime)的镜像 —— 上传那一屏上用户填的七格 */
|
|
2655
|
+
type UploadFormView = {
|
|
2656
|
+
visibility?: WirePluginVisibility;
|
|
2657
|
+
} & Partial<Record<Exclude<WirePluginUploadFieldName, 'visibility'>, string>>;
|
|
2500
2658
|
|
|
2501
2659
|
/**
|
|
2502
2660
|
* 「我们支持哪几家、这一家有哪些模型、给这一家配一把 key」 —— `GET /api/providers`、
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'http';
|
|
2
|
-
import { t, ApprovalRelay, QuestionRelay, serializeStream, withRoleScope, withModelScope, withToolScope, nextRunAt, skillIndexDescription, SANDBOX_EXCLUDES, SANDBOX_COVERS, isSystemOpenable, backgroundTaskOutput, listBackgroundTasks, providerLabel, MAX_REASON_CHARS, MAX_OBJECTIVE_CHARS, MAX_GOAL_MAX_ROUNDS, DEFAULT_GOAL_MAX_ROUNDS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_DEFAULTS, coalesceRecording, readRecording } from '@epoch-agent/runtime';
|
|
2
|
+
import { t, ApprovalRelay, QuestionRelay, serializeStream, withRoleScope, withModelScope, withToolScope, nextRunAt, observeBackgroundTasks, skillIndexDescription, SANDBOX_EXCLUDES, SANDBOX_COVERS, isSystemOpenable, backgroundTaskOutput, listBackgroundTasks, providerLabel, MAX_REASON_CHARS, MAX_OBJECTIVE_CHARS, MAX_GOAL_MAX_ROUNDS, DEFAULT_GOAL_MAX_ROUNDS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_DEFAULTS, coalesceRecording, readRecording } from '@epoch-agent/runtime';
|
|
3
3
|
import { timingSafeEqual, randomBytes } from 'crypto';
|
|
4
|
-
import { PLAN_OUTCOMES, WIRE_AUTH_COOKIE, WIRE_AUTH_COOKIE_ATTRS, WIRE_AUTH_TOKEN_PARAM, extractAllMentions, MAX_ATTACH_FILES, utf8ByteLength, MAX_SESSION_REFS, attachSessionSurface, isWireRewindScope, WIRE_REWIND_SCOPES, MAX_ATTACH_TOTAL_BYTES, MAX_ATTACH_BYTES, WIRE_LANG_PARAM, isLang, isQuestionAnswerMap, isProviderType, apiKeyEnvVar, WIRE_FILE_STAT_MAX, WIRE_SETTING_WRITE_LAYERS, isWirePlanAction, WIRE_PLAN_ACTIONS, isPermissionLevel, PERMISSION_LEVELS, collectPendingApprovals, unfixedRules, isWireSettingWriteLayer, parseModelRef, WIRE_GOAL_ACTIONS, OPERATION_TYPES, isOperationType } from '@epoch-agent/protocol';
|
|
4
|
+
import { PLAN_OUTCOMES, WIRE_AUTH_COOKIE, WIRE_AUTH_COOKIE_ATTRS, WIRE_AUTH_TOKEN_PARAM, extractAllMentions, MAX_ATTACH_FILES, utf8ByteLength, MAX_SESSION_REFS, attachSessionSurface, isWireRewindScope, WIRE_REWIND_SCOPES, MAX_ATTACH_TOTAL_BYTES, MAX_ATTACH_BYTES, WIRE_LANG_PARAM, isLang, isQuestionAnswerMap, isProviderType, apiKeyEnvVar, WIRE_FILE_STAT_MAX, WIRE_SETTING_WRITE_LAYERS, isWirePlanAction, WIRE_PLAN_ACTIONS, isPermissionLevel, PERMISSION_LEVELS, collectPendingApprovals, unfixedRules, isWireSettingWriteLayer, parseModelRef, WIRE_GOAL_ACTIONS, OPERATION_TYPES, isOperationType, WIRE_PLUGIN_UPLOAD_FORM_FIELDS } from '@epoch-agent/protocol';
|
|
5
5
|
import { resolve, join, relative, isAbsolute, sep, dirname, basename, extname } from 'path';
|
|
6
6
|
import { existsSync, realpathSync, statSync, mkdirSync, createReadStream, readdirSync, accessSync, constants } from 'fs';
|
|
7
7
|
import { homedir } from 'os';
|
|
@@ -554,6 +554,10 @@ var SessionHub = class {
|
|
|
554
554
|
this.syncApprovalState(entry);
|
|
555
555
|
return true;
|
|
556
556
|
}
|
|
557
|
+
notifyTasksChanged(sessionId) {
|
|
558
|
+
if (this.closed) return;
|
|
559
|
+
this.publish(sessionId, { type: "tasks-changed" });
|
|
560
|
+
}
|
|
557
561
|
shutdown() {
|
|
558
562
|
if (this.closed) return;
|
|
559
563
|
this.closed = true;
|
|
@@ -2448,7 +2452,8 @@ function toWirePluginEntry(entry) {
|
|
|
2448
2452
|
counts: entry.counts ? toWireCounts(entry.counts) : null
|
|
2449
2453
|
};
|
|
2450
2454
|
}
|
|
2451
|
-
function toWirePluginHit(hit,
|
|
2455
|
+
function toWirePluginHit(hit, installed) {
|
|
2456
|
+
const mine = installed.get(hit.entry.name);
|
|
2452
2457
|
return {
|
|
2453
2458
|
marketplace: hit.marketplace,
|
|
2454
2459
|
ref: hit.ref,
|
|
@@ -2458,7 +2463,19 @@ function toWirePluginHit(hit, installedNames) {
|
|
|
2458
2463
|
keywords: [...hit.entry.keywords ?? []],
|
|
2459
2464
|
sourceType: hit.sourceType ?? null,
|
|
2460
2465
|
installable: hit.installable,
|
|
2461
|
-
installed:
|
|
2466
|
+
installed: mine !== void 0,
|
|
2467
|
+
author: hit.entry.author?.name ?? null,
|
|
2468
|
+
downloads: hit.entry.downloads ?? null,
|
|
2469
|
+
likes: hit.entry.likes ?? null,
|
|
2470
|
+
displayName: hit.entry.displayName ?? null,
|
|
2471
|
+
icon: hit.entry.icon ?? null,
|
|
2472
|
+
version: hit.entry.version ?? null,
|
|
2473
|
+
changelog: hit.entry.changelog ?? null,
|
|
2474
|
+
size: hit.entry.size ?? null,
|
|
2475
|
+
homepage: hit.entry.homepage ?? null,
|
|
2476
|
+
visibility: hit.entry.visibility ?? null,
|
|
2477
|
+
status: hit.entry.status ?? null,
|
|
2478
|
+
updateAvailable: mine !== void 0 && !mine.linked && hit.entry.version !== void 0 && hit.entry.version !== mine.version
|
|
2462
2479
|
};
|
|
2463
2480
|
}
|
|
2464
2481
|
function toWireMarketplace(market) {
|
|
@@ -2514,17 +2531,38 @@ function fieldOf(payload, key) {
|
|
|
2514
2531
|
const value = payload[key];
|
|
2515
2532
|
return typeof value === "string" ? value.trim() : "";
|
|
2516
2533
|
}
|
|
2534
|
+
function uploadFormOf(payload) {
|
|
2535
|
+
const form = {};
|
|
2536
|
+
for (const name of WIRE_PLUGIN_UPLOAD_FORM_FIELDS) {
|
|
2537
|
+
const raw = fieldOf(payload, name);
|
|
2538
|
+
if (raw === "") continue;
|
|
2539
|
+
if (name === "visibility") {
|
|
2540
|
+
if (raw === "private" || raw === "public") form[name] = raw;
|
|
2541
|
+
continue;
|
|
2542
|
+
}
|
|
2543
|
+
form[name] = raw;
|
|
2544
|
+
}
|
|
2545
|
+
return form;
|
|
2546
|
+
}
|
|
2517
2547
|
function listPlugins(ctx, res, lang) {
|
|
2518
2548
|
const control = controlOf(ctx, res, lang);
|
|
2519
2549
|
if (!control) return;
|
|
2550
|
+
sendJson(res, 200, pluginsPayload(control));
|
|
2551
|
+
}
|
|
2552
|
+
function pluginsPayload(control) {
|
|
2520
2553
|
const installed = control.list();
|
|
2521
|
-
const
|
|
2522
|
-
|
|
2554
|
+
const installedMap = new Map(
|
|
2555
|
+
installed.map((p) => [p.name, { version: p.version, linked: p.linked }])
|
|
2556
|
+
);
|
|
2557
|
+
return {
|
|
2523
2558
|
installed: installed.map(toWirePluginEntry),
|
|
2524
|
-
hits: control.search().map((hit) => toWirePluginHit(hit,
|
|
2559
|
+
hits: control.search().map((hit) => toWirePluginHit(hit, installedMap)),
|
|
2525
2560
|
marketplaces: control.marketplaces().map(toWireMarketplace),
|
|
2526
|
-
pendingRestart: control.pendingRestart
|
|
2527
|
-
|
|
2561
|
+
pendingRestart: control.pendingRestart,
|
|
2562
|
+
uploadEnabled: control.uploadEnabled,
|
|
2563
|
+
uploadOptions: control.uploadOptions,
|
|
2564
|
+
refreshEnabled: control.refreshEnabled
|
|
2565
|
+
};
|
|
2528
2566
|
}
|
|
2529
2567
|
function sendPreview(res, outcome) {
|
|
2530
2568
|
if (!outcome.ok) {
|
|
@@ -2658,6 +2696,88 @@ async function removePluginMarketplace(ctx, req, res, lang) {
|
|
|
2658
2696
|
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2659
2697
|
sendMarket(res, await control.removeMarketplace(fieldOf(asRecord(body.value), "name"), lang));
|
|
2660
2698
|
}
|
|
2699
|
+
function previewPluginUpload(ctx, req, res, lang) {
|
|
2700
|
+
return withUploadBody(ctx, req, res, lang, (control, payload) => {
|
|
2701
|
+
sendUploadPreview(res, control.previewUpload(fieldOf(payload, "path"), lang));
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
function uploadPlugin(ctx, req, res, lang) {
|
|
2705
|
+
return withUploadBody(ctx, req, res, lang, async (control, payload) => {
|
|
2706
|
+
sendUpload(
|
|
2707
|
+
res,
|
|
2708
|
+
await control.upload(
|
|
2709
|
+
fieldOf(payload, "path"),
|
|
2710
|
+
fieldOf(payload, "token"),
|
|
2711
|
+
uploadFormOf(payload),
|
|
2712
|
+
lang
|
|
2713
|
+
)
|
|
2714
|
+
);
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
async function withUploadBody(ctx, req, res, lang, run) {
|
|
2718
|
+
if (refusedByLan(ctx, res, lang)) return;
|
|
2719
|
+
const control = controlOf(ctx, res, lang);
|
|
2720
|
+
if (!control) return;
|
|
2721
|
+
const body = await readJsonBody(req);
|
|
2722
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2723
|
+
await run(control, asRecord(body.value));
|
|
2724
|
+
}
|
|
2725
|
+
function sendUploadPreview(res, outcome) {
|
|
2726
|
+
if (!outcome.ok) {
|
|
2727
|
+
return sendJson(res, 200, {
|
|
2728
|
+
ok: false,
|
|
2729
|
+
plan: null,
|
|
2730
|
+
token: null,
|
|
2731
|
+
reason: outcome.reason,
|
|
2732
|
+
detail: outcome.detail
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
const { manifest, files, totalBytes, suspicious, skippedLinks } = outcome.plan;
|
|
2736
|
+
sendJson(res, 200, {
|
|
2737
|
+
ok: true,
|
|
2738
|
+
plan: {
|
|
2739
|
+
name: manifest.name,
|
|
2740
|
+
version: manifest.version,
|
|
2741
|
+
description: manifest.description ?? "",
|
|
2742
|
+
author: manifest.author?.name ?? null,
|
|
2743
|
+
files: files.map((f) => ({ path: f.path, bytes: f.bytes })),
|
|
2744
|
+
totalBytes,
|
|
2745
|
+
suspicious: [...suspicious],
|
|
2746
|
+
skippedLinks: [...skippedLinks]
|
|
2747
|
+
},
|
|
2748
|
+
token: outcome.token,
|
|
2749
|
+
reason: null,
|
|
2750
|
+
detail: null
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
function sendUpload(res, outcome) {
|
|
2754
|
+
sendJson(res, 200, {
|
|
2755
|
+
ok: outcome.ok,
|
|
2756
|
+
detail: outcome.detail,
|
|
2757
|
+
uploaded: outcome.ok ? { name: outcome.name, version: outcome.version, bytes: outcome.bytes } : null,
|
|
2758
|
+
reason: outcome.ok ? null : outcome.reason
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
async function refreshPlugins(ctx, res, lang) {
|
|
2762
|
+
if (refusedByLan(ctx, res, lang)) return;
|
|
2763
|
+
const control = controlOf(ctx, res, lang);
|
|
2764
|
+
if (!control) return;
|
|
2765
|
+
const outcome = await control.refresh(lang);
|
|
2766
|
+
if (!outcome.ok) {
|
|
2767
|
+
return sendJson(res, 200, {
|
|
2768
|
+
ok: false,
|
|
2769
|
+
plugins: null,
|
|
2770
|
+
reason: outcome.reason,
|
|
2771
|
+
detail: outcome.detail
|
|
2772
|
+
});
|
|
2773
|
+
}
|
|
2774
|
+
sendJson(res, 200, {
|
|
2775
|
+
ok: true,
|
|
2776
|
+
plugins: pluginsPayload(control),
|
|
2777
|
+
reason: null,
|
|
2778
|
+
detail: outcome.detail === "" ? null : outcome.detail
|
|
2779
|
+
});
|
|
2780
|
+
}
|
|
2661
2781
|
function toWireProvider(option, lang) {
|
|
2662
2782
|
return {
|
|
2663
2783
|
type: option.type,
|
|
@@ -4370,6 +4490,15 @@ async function handleApi(opts, req, res, method, segments, lang) {
|
|
|
4370
4490
|
if (resource === "plugins" && first === "marketplaces" && second === void 0 && method === "POST") {
|
|
4371
4491
|
return addPluginMarketplace(ctx, req, res, lang);
|
|
4372
4492
|
}
|
|
4493
|
+
if (resource === "plugins" && first === "upload" && second === "preview" && method === "POST") {
|
|
4494
|
+
return previewPluginUpload(ctx, req, res, lang);
|
|
4495
|
+
}
|
|
4496
|
+
if (resource === "plugins" && first === "upload" && second === void 0 && method === "POST") {
|
|
4497
|
+
return uploadPlugin(ctx, req, res, lang);
|
|
4498
|
+
}
|
|
4499
|
+
if (resource === "plugins" && first === "refresh" && second === void 0 && method === "POST") {
|
|
4500
|
+
return refreshPlugins(ctx, res, lang);
|
|
4501
|
+
}
|
|
4373
4502
|
if (resource === "providers" && first === void 0 && isRead(method)) {
|
|
4374
4503
|
return listProviders(ctx, res, lang);
|
|
4375
4504
|
}
|
|
@@ -4599,6 +4728,9 @@ async function createWebServer(opts) {
|
|
|
4599
4728
|
onCooled: (sessionId) => opts.runtime.sessionFactory.release(sessionId)
|
|
4600
4729
|
});
|
|
4601
4730
|
if (opts.runtime.session) hub.register(opts.runtime.session);
|
|
4731
|
+
const unsubscribeTasks = observeBackgroundTasks((sessionId) => {
|
|
4732
|
+
hub.notifyTasksChanged(sessionId);
|
|
4733
|
+
});
|
|
4602
4734
|
let handle = null;
|
|
4603
4735
|
const server = createServer((req, res) => {
|
|
4604
4736
|
if (handle) return handle(req, res);
|
|
@@ -4607,7 +4739,10 @@ async function createWebServer(opts) {
|
|
|
4607
4739
|
});
|
|
4608
4740
|
const webRoot = opts.webRoot ?? defaultWebRoot();
|
|
4609
4741
|
const listened = await listen(server, binding.host, binding.port);
|
|
4610
|
-
if (!listened.ok)
|
|
4742
|
+
if (!listened.ok) {
|
|
4743
|
+
unsubscribeTasks();
|
|
4744
|
+
return listened;
|
|
4745
|
+
}
|
|
4611
4746
|
handle = buildHandler(opts, hub, binding, listened.port, webRoot);
|
|
4612
4747
|
return {
|
|
4613
4748
|
ok: true,
|
|
@@ -4619,7 +4754,7 @@ async function createWebServer(opts) {
|
|
|
4619
4754
|
...webRoot === void 0 ? {} : { webRoot },
|
|
4620
4755
|
observe: (sink) => hub.observe(sink),
|
|
4621
4756
|
snapshot: () => hub.listSessions(),
|
|
4622
|
-
close: makeCloser(server, hub, opts.runtime)
|
|
4757
|
+
close: makeCloser(server, hub, opts.runtime, unsubscribeTasks)
|
|
4623
4758
|
}
|
|
4624
4759
|
};
|
|
4625
4760
|
}
|
|
@@ -4670,10 +4805,11 @@ function describeListenError(err, host, port) {
|
|
|
4670
4805
|
}
|
|
4671
4806
|
return t("web.listen_failed", { host, port, reason: err.message }, void 0);
|
|
4672
4807
|
}
|
|
4673
|
-
function makeCloser(server, hub, runtime) {
|
|
4808
|
+
function makeCloser(server, hub, runtime, unsubscribeTasks) {
|
|
4674
4809
|
let closing = null;
|
|
4675
4810
|
return () => {
|
|
4676
4811
|
closing ??= (async () => {
|
|
4812
|
+
unsubscribeTasks();
|
|
4677
4813
|
hub.shutdown();
|
|
4678
4814
|
server.closeAllConnections();
|
|
4679
4815
|
await new Promise((resolve4) => server.close(() => resolve4()));
|