@epoch-agent/server 0.7.0 → 0.8.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.
- package/README.md +11 -2
- package/dist/index.d.ts +133 -14
- package/dist/index.js +113 -19
- package/dist/web/assets/index-Cl7UCWRQ.css +1 -0
- package/dist/web/assets/index-De1G68kR.js +376 -0
- package/dist/web/index.html +25 -3
- package/package.json +3 -3
- package/dist/web/assets/index-CqnJSD17.js +0 -375
- package/dist/web/assets/index-aYGNEOwi.css +0 -1
package/README.md
CHANGED
|
@@ -254,8 +254,17 @@ sessionId 就是方案 30 §9.5 那条 bug 的形状 —— 编译得过、跑
|
|
|
254
254
|
|
|
255
255
|
**录像那一条不再脱敏一次**:脱敏在落盘那一刻就做完了(core 的 `redactLine`,
|
|
256
256
|
跑在已经序列化过的一整行上)。在这一层补一道会让下一个人以为盘上那一份是干净的
|
|
257
|
-
—— 而盘上那一份才是真正会被 `cat`
|
|
258
|
-
|
|
257
|
+
—— 而盘上那一份才是真正会被 `cat` 的那份。
|
|
258
|
+
|
|
259
|
+
发出去之前**先把流式增量折回消息**(core 的 `coalesceRecording`):盘上是一个
|
|
260
|
+
`text-delta` 一帧,几千个输出 token 就是几千帧,而这些帧在运行结束之后没有独立
|
|
261
|
+
含义(渲染那侧只是把它们拼成一段正文)。折完之后一帧 ≈ 一段正文 / 一次工具调用,
|
|
262
|
+
`MAX_RECORDING_FRAMES`(4000)这把尺子才量得到真东西 —— **它量的是折叠之后的帧数**。
|
|
263
|
+
|
|
264
|
+
超了就截断 + `truncated: true`,界面必须把它画出来。⚠️ **截掉的是开头,留的是
|
|
265
|
+
尾巴**(`slice(-N)`):一次运行里人要看的正是那句结论和 `finish` 那一帧上的账,
|
|
266
|
+
而留头去尾会让正文断在半句话上 —— 屏幕上那半截自己不会说「我是半截的」
|
|
267
|
+
(判据同 protocol 的 `NoticeCode: 'response-truncated'`)。
|
|
259
268
|
|
|
260
269
|
⚠️ 这一组借的是 `WebRuntimeView.schedules`(`ScheduleControlView`,
|
|
261
270
|
结构镜像在 [src/schedule/view.ts](./src/schedule/view.ts))。判定、注册进 OS、
|
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, 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';
|
|
2
2
|
import { SerializableApprovalRequest, SerializableQuestionRequest, ApprovalRevokeResult, LevelChangeResult, WorkspaceFilesView } from '@epoch-agent/runtime';
|
|
3
3
|
import { ServerResponse } from 'node:http';
|
|
4
4
|
|
|
@@ -2263,11 +2263,12 @@ interface RuntimeModel {
|
|
|
2263
2263
|
}
|
|
2264
2264
|
|
|
2265
2265
|
/**
|
|
2266
|
-
*
|
|
2267
|
-
* 补,判据在 protocol 的 `wire-plugin.ts`
|
|
2266
|
+
* 插件那一页的十二条端点(方案 59 §六 E2,2026-08-21;本地源与市场管理 2026-09-04
|
|
2267
|
+
* 补,判据在 protocol 的 `wire-plugin.ts` 文件头第一节那次**翻案**;上传 2026-09-08
|
|
2268
|
+
* 补,判据在那份文件头第一节之二):
|
|
2268
2269
|
*
|
|
2269
2270
|
* ```
|
|
2270
|
-
* GET /api/plugins 装着的 + 市场里有什么 + 已加的市场 + pendingRestart
|
|
2271
|
+
* GET /api/plugins 装着的 + 市场里有什么 + 已加的市场 + pendingRestart + uploadEnabled
|
|
2271
2272
|
* POST /api/plugins/install/preview {ref} → 将安装什么,一个字节都不写
|
|
2272
2273
|
* POST /api/plugins/install {ref, token} → 真装
|
|
2273
2274
|
* POST /api/plugins/install/local/preview {path} → 同上,来源是本机一条目录
|
|
@@ -2277,6 +2278,8 @@ interface RuntimeModel {
|
|
|
2277
2278
|
* POST /api/plugins/uninstall {name} → 卸载
|
|
2278
2279
|
* POST /api/plugins/marketplaces {source} → 加一个市场
|
|
2279
2280
|
* POST /api/plugins/marketplaces/remove {name} → 移除一个市场
|
|
2281
|
+
* POST /api/plugins/upload/preview {path} → 会打包哪些文件,一个字节都不发
|
|
2282
|
+
* POST /api/plugins/upload {path, token} → 打包 + 交给宿主
|
|
2280
2283
|
* ```
|
|
2281
2284
|
*
|
|
2282
2285
|
* ## ⚠️ 这一层**一条策略判断都没有**
|
|
@@ -2290,10 +2293,10 @@ interface RuntimeModel {
|
|
|
2290
2293
|
* 界面上是一条画成可点、点了却被拒的行(判据同 `PluginSearchHit.installable`
|
|
2291
2294
|
* 为什么不从结果里剔掉远程那条)。
|
|
2292
2295
|
*
|
|
2293
|
-
* ## ⚠️
|
|
2296
|
+
* ## ⚠️ 那一道闸:**十一条 POST 在回环之外一律 403**,这是这个文件的正题
|
|
2294
2297
|
*
|
|
2295
2298
|
* 判据全文在 protocol 的 [wire-plugin.ts](../../protocol/src/wire-plugin.ts) 文件头
|
|
2296
|
-
*
|
|
2299
|
+
* 第二节,这里记它对这一层的**六**条直接后果:
|
|
2297
2300
|
*
|
|
2298
2301
|
* 1. **`ctx.lanExposed` 为真时 403 `plugin-write-lan-exposed`,一个字节都不写。**
|
|
2299
2302
|
* 装一包插件会落下 `hooks.json` 和 `mcp.json`,那两样**起子进程** ——
|
|
@@ -2312,6 +2315,10 @@ interface RuntimeModel {
|
|
|
2312
2315
|
* 唯一后果是那一页在 LAN 那一档下一片空白,
|
|
2313
2316
|
* 偏偏那时用户最想知道的就是「我装了什么、生效了没有」。
|
|
2314
2317
|
* (对比 `GET /api/mcp/config` 为什么连读都挡:那份文件里的 `env` 是明文密钥。)
|
|
2318
|
+
* 6. **上传那两条是这张表里最该挡的**(2026-09-08):它们读这台机器上任意一条
|
|
2319
|
+
* 目录、把内容**发到外面去**。放行的话,一个 LAN 上的人能指着 `~/.ssh` 说
|
|
2320
|
+
* 「上传它」。⚠️ **预览那一条同样挡**,虽然它一个字节都不发 ——
|
|
2321
|
+
* 它回的那份**文件清单本身就是情报**(拿它能探这台机器上有什么)。
|
|
2315
2322
|
*
|
|
2316
2323
|
* ## ⚠️ 这一片可以整个不存在 —— 503 `no-plugin-control`
|
|
2317
2324
|
*
|
|
@@ -2320,6 +2327,11 @@ interface RuntimeModel {
|
|
|
2320
2327
|
* JSDoc 上)。六条一律 503,判据同 `no-goal-store`:那是一句关于**这个部署**的
|
|
2321
2328
|
* 真话,而一份「装了 0 个插件」的空列表会让用户去找一个不存在的安装按钮。
|
|
2322
2329
|
*
|
|
2330
|
+
* ⚠️ **上传那两条还有第二档「没开」,而它比 503 那一档窄**:`runtime.plugins`
|
|
2331
|
+
* 在、但宿主没给 `upload` 回调 —— 那时它们回 **200 + `upload-unavailable`**,
|
|
2332
|
+
* 不是 503。两档分得开是有用的:503 说「这一整页没开」,那一档说
|
|
2333
|
+
* 「页开着,只是没有交出去这条路」,而后者正是 `epoch web --plugins` 的常态。
|
|
2334
|
+
*
|
|
2323
2335
|
* ⚠️ **默认的 `epoch web` 就落在这一档上**(不给 `--plugins` 就不传
|
|
2324
2336
|
* `hostMarketplaces`)。那一格 2026-08-21 答掉了:`epoch web --plugins` 才接上
|
|
2325
2337
|
* (传 `{ sources: [] }`),**默认关**,判据在
|
|
@@ -2378,6 +2390,26 @@ interface PluginHitView {
|
|
|
2378
2390
|
source: string;
|
|
2379
2391
|
category?: string;
|
|
2380
2392
|
keywords?: readonly string[];
|
|
2393
|
+
/** 目录里那一格「谁写的」。**摊平在投影里做**,判据在 `WirePluginHit.author` 上 */
|
|
2394
|
+
author?: {
|
|
2395
|
+
name: string;
|
|
2396
|
+
};
|
|
2397
|
+
/**
|
|
2398
|
+
* 市场那一侧数的两个计数(2026-09-08)。**可选**,而缺席在网线上变 `null`
|
|
2399
|
+
* 不是 `0` —— 判据在 `WirePluginHit.downloads` 上(「没人报」和「一次都没装过」
|
|
2400
|
+
* 是两句不同的话)。
|
|
2401
|
+
*/
|
|
2402
|
+
downloads?: number;
|
|
2403
|
+
likes?: number;
|
|
2404
|
+
/** 展示用的那几格(2026-09-08)。全部可选 → 网线上一律 `null` */
|
|
2405
|
+
displayName?: string;
|
|
2406
|
+
icon?: string;
|
|
2407
|
+
/** **市场说的**版本。`updateAvailable` 是拿它和装着的那个比出来的 */
|
|
2408
|
+
version?: string;
|
|
2409
|
+
changelog?: string;
|
|
2410
|
+
size?: number;
|
|
2411
|
+
homepage?: string;
|
|
2412
|
+
visibility?: WirePluginVisibility;
|
|
2381
2413
|
};
|
|
2382
2414
|
sourceType?: WirePluginSourceType;
|
|
2383
2415
|
installable: boolean;
|
|
@@ -2460,7 +2492,49 @@ type MarketOutcomeView = {
|
|
|
2460
2492
|
marketplace?: MarketplaceView;
|
|
2461
2493
|
} | RefusalView;
|
|
2462
2494
|
/**
|
|
2463
|
-
* `
|
|
2495
|
+
* `PackPlan`(core,经 runtime 原样透出)的镜像 —— 「会打包什么」那一屏的内容。
|
|
2496
|
+
*
|
|
2497
|
+
* ⚠️ 借的是**整份 `files`** 而不是一个长度:判据在 `WirePluginUploadPlan.files`
|
|
2498
|
+
* 上(这条路上最常见的事故是「目录里有一份 `.env`」,一个计数答不出那件事)。
|
|
2499
|
+
*/
|
|
2500
|
+
interface PackPlanView {
|
|
2501
|
+
manifest: {
|
|
2502
|
+
name: string;
|
|
2503
|
+
version: string;
|
|
2504
|
+
description?: string;
|
|
2505
|
+
author?: {
|
|
2506
|
+
name: string;
|
|
2507
|
+
};
|
|
2508
|
+
};
|
|
2509
|
+
files: readonly {
|
|
2510
|
+
path: string;
|
|
2511
|
+
bytes: number;
|
|
2512
|
+
}[];
|
|
2513
|
+
totalBytes: number;
|
|
2514
|
+
suspicious: readonly string[];
|
|
2515
|
+
skippedLinks: readonly string[];
|
|
2516
|
+
}
|
|
2517
|
+
type UploadPreviewOutcomeView = {
|
|
2518
|
+
ok: true;
|
|
2519
|
+
plan: PackPlanView;
|
|
2520
|
+
token: string;
|
|
2521
|
+
} | RefusalView;
|
|
2522
|
+
/**
|
|
2523
|
+
* 上传的结局。
|
|
2524
|
+
*
|
|
2525
|
+
* ⚠️ 成功那一档借的是 `detail`(**宿主原话**)而不是我们编的一句 —— 判据在
|
|
2526
|
+
* `WirePluginUploadResponse.detail` 上:上传之后发生什么(直接上架 / 排队审核)
|
|
2527
|
+
* 是宿主那一侧的流程,这一层编不出来。
|
|
2528
|
+
*/
|
|
2529
|
+
type UploadOutcomeView = {
|
|
2530
|
+
ok: true;
|
|
2531
|
+
detail: string;
|
|
2532
|
+
name: string;
|
|
2533
|
+
version: string;
|
|
2534
|
+
bytes: number;
|
|
2535
|
+
} | RefusalView;
|
|
2536
|
+
/**
|
|
2537
|
+
* `PluginControl`(runtime)在服务端眼里的样子 —— **只有那十四个动词加两格布尔**。
|
|
2464
2538
|
*
|
|
2465
2539
|
* 镜像里只有它们,于是这个包**写不出** core 的 `setPluginEnabled`
|
|
2466
2540
|
* (停用一个插件会静默拿掉别人正依赖的一批命令,正当性和「装 / 卸」不是一回事);
|
|
@@ -2486,7 +2560,29 @@ interface PluginControlView {
|
|
|
2486
2560
|
marketplaces(): readonly MarketplaceView[];
|
|
2487
2561
|
addMarketplace(source: string, lang?: Lang): Promise<MarketOutcomeView>;
|
|
2488
2562
|
removeMarketplace(name: string, lang?: Lang): Promise<MarketOutcomeView>;
|
|
2563
|
+
/**
|
|
2564
|
+
* 「会打包什么」。⚠️ **同步的**(runtime 那一层只走一遍目录,不起子进程)——
|
|
2565
|
+
* 镜像上写成 `Promise` 也满足,但那会让这一层顺手 `await` 一个不用等的东西
|
|
2566
|
+
*/
|
|
2567
|
+
previewUpload(path: string, lang?: Lang): UploadPreviewOutcomeView;
|
|
2568
|
+
upload(path: string, token: string,
|
|
2569
|
+
/** 用户在那一屏上填的。**宿主没声明要问的格子由下一层丢掉**,这一层不过滤 */
|
|
2570
|
+
form: {
|
|
2571
|
+
visibility?: WirePluginVisibility;
|
|
2572
|
+
changelog?: string;
|
|
2573
|
+
}, lang?: Lang): Promise<UploadOutcomeView>;
|
|
2489
2574
|
readonly pendingRestart: boolean;
|
|
2575
|
+
/** 上传表单多问哪几格。**两个恒有的布尔**,runtime 那一层算好的 */
|
|
2576
|
+
readonly uploadOptions: {
|
|
2577
|
+
visibility: boolean;
|
|
2578
|
+
changelog: boolean;
|
|
2579
|
+
};
|
|
2580
|
+
/**
|
|
2581
|
+
* 宿主给了上传出口吗。**runtime 算好的结论**,这一层原样投影上网线 ——
|
|
2582
|
+
* 在这儿重判一次(比如看那个回调在不在)就是给同一个问题第二个答案,
|
|
2583
|
+
* 判据同文件头那句「这一层一条策略判断都没有」。
|
|
2584
|
+
*/
|
|
2585
|
+
readonly uploadEnabled: boolean;
|
|
2490
2586
|
}
|
|
2491
2587
|
/**
|
|
2492
2588
|
* 装着的一条 → 网线上那一行。
|
|
@@ -2666,8 +2762,26 @@ interface ScheduleCreateView {
|
|
|
2666
2762
|
endDate?: string;
|
|
2667
2763
|
enabled?: boolean;
|
|
2668
2764
|
}
|
|
2669
|
-
/**
|
|
2670
|
-
|
|
2765
|
+
/**
|
|
2766
|
+
* 改一条任务 —— core 的 `UpdateScheduleInput`。没给的字段一个都不动。
|
|
2767
|
+
*
|
|
2768
|
+
* ⚠️ **四格收 `null` = 清空这一格**,和网线上那份(`WireScheduleUpdateRequest`)
|
|
2769
|
+
* 逐字段对得上。别写成 `Partial<ScheduleCreateView>`(2026-09-07 之前就是那样):
|
|
2770
|
+
* 那一版比真身**窄**,而窄的方向 TS 不报 —— 入参是逆变的,一个「什么都收得下」
|
|
2771
|
+
* 的真身赋给一个「只收 string」的镜像永远合法。于是这一层压根表达不出「清空」,
|
|
2772
|
+
* `schedule/body.ts` 只好把 `null` 翻成空串往下递,而 core 从来不认空串是清空。
|
|
2773
|
+
* 那次的表现是:表单上两格生效区间都没填,保存却报两行「生效区间不对: 」。
|
|
2774
|
+
*
|
|
2775
|
+
* 也就是说**这一格的正确性没有编译器守着**,只有这段注释和用例。判据全文在
|
|
2776
|
+
* core 的 `UpdateScheduleInput` 上。
|
|
2777
|
+
*/
|
|
2778
|
+
type ScheduleUpdateView = Partial<Omit<ScheduleCreateView, 'workDir' | 'model' | 'startDate' | 'endDate'>> & {
|
|
2779
|
+
/** `null` = 不绑工作区了 → 落回 `~/.epoch/automation/<id>/`(runtime 那一层解析) */
|
|
2780
|
+
workDir?: string | null;
|
|
2781
|
+
model?: string | null;
|
|
2782
|
+
startDate?: string | null;
|
|
2783
|
+
endDate?: string | null;
|
|
2784
|
+
};
|
|
2671
2785
|
/** 存下来了没有 —— core / runtime 的 `ScheduleSaveResult` */
|
|
2672
2786
|
interface ScheduleSaveView {
|
|
2673
2787
|
/** 校验不过时 `undefined`,且库里一个字都没写 */
|
|
@@ -4884,12 +4998,17 @@ declare function collectTasks(sessionId: string, registry: TaskRegistryView): Wi
|
|
|
4884
4998
|
*/
|
|
4885
4999
|
|
|
4886
5000
|
/**
|
|
4887
|
-
*
|
|
5001
|
+
* 一份录像最多回多少帧 —— **折叠之后的帧**。
|
|
5002
|
+
*
|
|
5003
|
+
* 一次跑飞的运行能写出几万帧,整份推给浏览器会让那个标签页卡死。超了就截断 +
|
|
5004
|
+
* `truncated: true` —— **必须说出来**,判据同 `WireSkillBodyResponse.truncated`:
|
|
5005
|
+
* 一份看起来完整、其实少了后半截的回放,比一句「太长了没给全」坏得多。
|
|
4888
5006
|
*
|
|
4889
|
-
*
|
|
4890
|
-
*
|
|
4891
|
-
*
|
|
4892
|
-
*
|
|
5007
|
+
* ⚠️ **这把尺子量的是折叠之后的帧数**(2026-09-07 改准)。原来量的是盘上的原始
|
|
5008
|
+
* 帧数,而盘上「每个 `text-delta` 一帧」—— 于是几千个输出 token 就是几千帧,
|
|
5009
|
+
* **一次正常的多轮任务**(实测:27 轮、0.016 美元)就撞上了这道闸门。
|
|
5010
|
+
* 折叠在 core 的 `coalesceRecording` 里,折完一帧 ≈ 一段正文 / 一次工具调用,
|
|
5011
|
+
* 4000 才重新是「一次真正巨大的运行」的意思。
|
|
4893
5012
|
*/
|
|
4894
5013
|
declare const MAX_RECORDING_FRAMES = 4000;
|
|
4895
5014
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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, readRecording } from '@epoch-agent/runtime';
|
|
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';
|
|
3
3
|
import { timingSafeEqual, randomBytes } from 'crypto';
|
|
4
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';
|
|
5
5
|
import { resolve, join, relative, isAbsolute, sep, dirname, basename, extname } from 'path';
|
|
@@ -2448,7 +2448,8 @@ function toWirePluginEntry(entry) {
|
|
|
2448
2448
|
counts: entry.counts ? toWireCounts(entry.counts) : null
|
|
2449
2449
|
};
|
|
2450
2450
|
}
|
|
2451
|
-
function toWirePluginHit(hit,
|
|
2451
|
+
function toWirePluginHit(hit, installed) {
|
|
2452
|
+
const mine = installed.get(hit.entry.name);
|
|
2452
2453
|
return {
|
|
2453
2454
|
marketplace: hit.marketplace,
|
|
2454
2455
|
ref: hit.ref,
|
|
@@ -2458,7 +2459,18 @@ function toWirePluginHit(hit, installedNames) {
|
|
|
2458
2459
|
keywords: [...hit.entry.keywords ?? []],
|
|
2459
2460
|
sourceType: hit.sourceType ?? null,
|
|
2460
2461
|
installable: hit.installable,
|
|
2461
|
-
installed:
|
|
2462
|
+
installed: mine !== void 0,
|
|
2463
|
+
author: hit.entry.author?.name ?? null,
|
|
2464
|
+
downloads: hit.entry.downloads ?? null,
|
|
2465
|
+
likes: hit.entry.likes ?? null,
|
|
2466
|
+
displayName: hit.entry.displayName ?? null,
|
|
2467
|
+
icon: hit.entry.icon ?? null,
|
|
2468
|
+
version: hit.entry.version ?? null,
|
|
2469
|
+
changelog: hit.entry.changelog ?? null,
|
|
2470
|
+
size: hit.entry.size ?? null,
|
|
2471
|
+
homepage: hit.entry.homepage ?? null,
|
|
2472
|
+
visibility: hit.entry.visibility ?? null,
|
|
2473
|
+
updateAvailable: mine !== void 0 && !mine.linked && hit.entry.version !== void 0 && hit.entry.version !== mine.version
|
|
2462
2474
|
};
|
|
2463
2475
|
}
|
|
2464
2476
|
function toWireMarketplace(market) {
|
|
@@ -2518,12 +2530,16 @@ function listPlugins(ctx, res, lang) {
|
|
|
2518
2530
|
const control = controlOf(ctx, res, lang);
|
|
2519
2531
|
if (!control) return;
|
|
2520
2532
|
const installed = control.list();
|
|
2521
|
-
const
|
|
2533
|
+
const installedMap = new Map(
|
|
2534
|
+
installed.map((p) => [p.name, { version: p.version, linked: p.linked }])
|
|
2535
|
+
);
|
|
2522
2536
|
sendJson(res, 200, {
|
|
2523
2537
|
installed: installed.map(toWirePluginEntry),
|
|
2524
|
-
hits: control.search().map((hit) => toWirePluginHit(hit,
|
|
2538
|
+
hits: control.search().map((hit) => toWirePluginHit(hit, installedMap)),
|
|
2525
2539
|
marketplaces: control.marketplaces().map(toWireMarketplace),
|
|
2526
|
-
pendingRestart: control.pendingRestart
|
|
2540
|
+
pendingRestart: control.pendingRestart,
|
|
2541
|
+
uploadEnabled: control.uploadEnabled,
|
|
2542
|
+
uploadOptions: control.uploadOptions
|
|
2527
2543
|
});
|
|
2528
2544
|
}
|
|
2529
2545
|
function sendPreview(res, outcome) {
|
|
@@ -2658,6 +2674,71 @@ async function removePluginMarketplace(ctx, req, res, lang) {
|
|
|
2658
2674
|
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2659
2675
|
sendMarket(res, await control.removeMarketplace(fieldOf(asRecord(body.value), "name"), lang));
|
|
2660
2676
|
}
|
|
2677
|
+
function previewPluginUpload(ctx, req, res, lang) {
|
|
2678
|
+
return withUploadBody(ctx, req, res, lang, (control, payload) => {
|
|
2679
|
+
sendUploadPreview(res, control.previewUpload(fieldOf(payload, "path"), lang));
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
function uploadPlugin(ctx, req, res, lang) {
|
|
2683
|
+
return withUploadBody(ctx, req, res, lang, async (control, payload) => {
|
|
2684
|
+
sendUpload(
|
|
2685
|
+
res,
|
|
2686
|
+
await control.upload(
|
|
2687
|
+
fieldOf(payload, "path"),
|
|
2688
|
+
fieldOf(payload, "token"),
|
|
2689
|
+
{
|
|
2690
|
+
...fieldOf(payload, "visibility") === "private" ? { visibility: "private" } : fieldOf(payload, "visibility") === "public" ? { visibility: "public" } : {},
|
|
2691
|
+
...fieldOf(payload, "changelog") ? { changelog: fieldOf(payload, "changelog") } : {}
|
|
2692
|
+
},
|
|
2693
|
+
lang
|
|
2694
|
+
)
|
|
2695
|
+
);
|
|
2696
|
+
});
|
|
2697
|
+
}
|
|
2698
|
+
async function withUploadBody(ctx, req, res, lang, run) {
|
|
2699
|
+
if (refusedByLan(ctx, res, lang)) return;
|
|
2700
|
+
const control = controlOf(ctx, res, lang);
|
|
2701
|
+
if (!control) return;
|
|
2702
|
+
const body = await readJsonBody(req);
|
|
2703
|
+
if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
|
|
2704
|
+
await run(control, asRecord(body.value));
|
|
2705
|
+
}
|
|
2706
|
+
function sendUploadPreview(res, outcome) {
|
|
2707
|
+
if (!outcome.ok) {
|
|
2708
|
+
return sendJson(res, 200, {
|
|
2709
|
+
ok: false,
|
|
2710
|
+
plan: null,
|
|
2711
|
+
token: null,
|
|
2712
|
+
reason: outcome.reason,
|
|
2713
|
+
detail: outcome.detail
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
const { manifest, files, totalBytes, suspicious, skippedLinks } = outcome.plan;
|
|
2717
|
+
sendJson(res, 200, {
|
|
2718
|
+
ok: true,
|
|
2719
|
+
plan: {
|
|
2720
|
+
name: manifest.name,
|
|
2721
|
+
version: manifest.version,
|
|
2722
|
+
description: manifest.description ?? "",
|
|
2723
|
+
author: manifest.author?.name ?? null,
|
|
2724
|
+
files: files.map((f) => ({ path: f.path, bytes: f.bytes })),
|
|
2725
|
+
totalBytes,
|
|
2726
|
+
suspicious: [...suspicious],
|
|
2727
|
+
skippedLinks: [...skippedLinks]
|
|
2728
|
+
},
|
|
2729
|
+
token: outcome.token,
|
|
2730
|
+
reason: null,
|
|
2731
|
+
detail: null
|
|
2732
|
+
});
|
|
2733
|
+
}
|
|
2734
|
+
function sendUpload(res, outcome) {
|
|
2735
|
+
sendJson(res, 200, {
|
|
2736
|
+
ok: outcome.ok,
|
|
2737
|
+
detail: outcome.detail,
|
|
2738
|
+
uploaded: outcome.ok ? { name: outcome.name, version: outcome.version, bytes: outcome.bytes } : null,
|
|
2739
|
+
reason: outcome.ok ? null : outcome.reason
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2661
2742
|
function toWireProvider(option, lang) {
|
|
2662
2743
|
return {
|
|
2663
2744
|
type: option.type,
|
|
@@ -2855,21 +2936,28 @@ function numberList(value) {
|
|
|
2855
2936
|
}
|
|
2856
2937
|
return out;
|
|
2857
2938
|
}
|
|
2858
|
-
|
|
2939
|
+
var CLEARABLE = ["model", "workDir", "startDate", "endDate"];
|
|
2940
|
+
function optionals(raw, allowClear, lang) {
|
|
2859
2941
|
const out = {};
|
|
2860
|
-
|
|
2942
|
+
const bad = (field) => ({
|
|
2943
|
+
ok: false,
|
|
2944
|
+
message: t("web.schedule_bad_field", { field }, lang)
|
|
2945
|
+
});
|
|
2946
|
+
for (const key of ["name", "prompt"]) {
|
|
2947
|
+
const value = raw[key];
|
|
2948
|
+
if (value === void 0) continue;
|
|
2949
|
+
if (typeof value !== "string") return bad(key);
|
|
2950
|
+
out[key] = value;
|
|
2951
|
+
}
|
|
2952
|
+
for (const key of CLEARABLE) {
|
|
2861
2953
|
const value = raw[key];
|
|
2862
2954
|
if (value === void 0) continue;
|
|
2863
2955
|
if (value === null) {
|
|
2864
|
-
if (
|
|
2865
|
-
|
|
2866
|
-
}
|
|
2867
|
-
out[key] = "";
|
|
2956
|
+
if (!allowClear) return bad(key);
|
|
2957
|
+
out[key] = null;
|
|
2868
2958
|
continue;
|
|
2869
2959
|
}
|
|
2870
|
-
if (typeof value !== "string")
|
|
2871
|
-
return { ok: false, message: t("web.schedule_bad_field", { field: key }, lang) };
|
|
2872
|
-
}
|
|
2960
|
+
if (typeof value !== "string") return bad(key);
|
|
2873
2961
|
out[key] = value;
|
|
2874
2962
|
}
|
|
2875
2963
|
for (const key of ["maxTurns", "maxBudgetUsd", "timeoutMs"]) {
|
|
@@ -2939,7 +3027,7 @@ function parseCreate2(body, lang) {
|
|
|
2939
3027
|
return { ok: false, message: t("web.schedule_bad_trigger", void 0, lang) };
|
|
2940
3028
|
if (budget === null)
|
|
2941
3029
|
return { ok: false, message: t("web.schedule_budget_required", void 0, lang) };
|
|
2942
|
-
const rest = optionals(raw, lang);
|
|
3030
|
+
const rest = optionals(raw, false, lang);
|
|
2943
3031
|
if (!rest.ok) return rest;
|
|
2944
3032
|
return {
|
|
2945
3033
|
ok: true,
|
|
@@ -2947,7 +3035,7 @@ function parseCreate2(body, lang) {
|
|
|
2947
3035
|
};
|
|
2948
3036
|
}
|
|
2949
3037
|
function parseUpdate2(body, lang) {
|
|
2950
|
-
return optionals(asRecord(body), lang);
|
|
3038
|
+
return optionals(asRecord(body), true, lang);
|
|
2951
3039
|
}
|
|
2952
3040
|
function parseFix(body, lang) {
|
|
2953
3041
|
const runId = asRecord(body)["runId"];
|
|
@@ -3105,14 +3193,14 @@ function toRecording(run) {
|
|
|
3105
3193
|
if (run.logPath === void 0) {
|
|
3106
3194
|
return { runId: run.id, frames: [], missing: true, truncated: false };
|
|
3107
3195
|
}
|
|
3108
|
-
const frames = readRecording(run.logPath);
|
|
3196
|
+
const frames = coalesceRecording(readRecording(run.logPath));
|
|
3109
3197
|
if (frames.length === 0) {
|
|
3110
3198
|
return { runId: run.id, frames: [], missing: true, truncated: false };
|
|
3111
3199
|
}
|
|
3112
3200
|
const truncated = frames.length > MAX_RECORDING_FRAMES;
|
|
3113
3201
|
return {
|
|
3114
3202
|
runId: run.id,
|
|
3115
|
-
frames: truncated ? frames.slice(
|
|
3203
|
+
frames: truncated ? frames.slice(-MAX_RECORDING_FRAMES) : frames,
|
|
3116
3204
|
missing: false,
|
|
3117
3205
|
truncated
|
|
3118
3206
|
};
|
|
@@ -4363,6 +4451,12 @@ async function handleApi(opts, req, res, method, segments, lang) {
|
|
|
4363
4451
|
if (resource === "plugins" && first === "marketplaces" && second === void 0 && method === "POST") {
|
|
4364
4452
|
return addPluginMarketplace(ctx, req, res, lang);
|
|
4365
4453
|
}
|
|
4454
|
+
if (resource === "plugins" && first === "upload" && second === "preview" && method === "POST") {
|
|
4455
|
+
return previewPluginUpload(ctx, req, res, lang);
|
|
4456
|
+
}
|
|
4457
|
+
if (resource === "plugins" && first === "upload" && second === void 0 && method === "POST") {
|
|
4458
|
+
return uploadPlugin(ctx, req, res, lang);
|
|
4459
|
+
}
|
|
4366
4460
|
if (resource === "providers" && first === void 0 && isRead(method)) {
|
|
4367
4461
|
return listProviders(ctx, res, lang);
|
|
4368
4462
|
}
|