@epoch-agent/server 0.3.1 → 0.4.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 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, 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, 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,15 +2263,20 @@ interface RuntimeModel {
2263
2263
  }
2264
2264
 
2265
2265
  /**
2266
- * 插件那一页的六条端点(方案 59 §六 E2,2026-08-21):
2266
+ * 插件那一页的十条端点(方案 59 §六 E2,2026-08-21;本地源与市场管理 2026-09-04
2267
+ * 补,判据在 protocol 的 `wire-plugin.ts` 文件头第一节那次**翻案**):
2267
2268
  *
2268
2269
  * ```
2269
- * GET /api/plugins 装着的 + 市场里有什么 + pendingRestart
2270
- * POST /api/plugins/install/preview {ref} → 将安装什么,一个字节都不写
2271
- * POST /api/plugins/install {ref, token} → 真装
2272
- * POST /api/plugins/update/preview {name} 更新会带来什么
2273
- * POST /api/plugins/update {name, token} 真更新
2274
- * POST /api/plugins/uninstall {name} 卸载
2270
+ * GET /api/plugins 装着的 + 市场里有什么 + 已加的市场 + pendingRestart
2271
+ * POST /api/plugins/install/preview {ref} → 将安装什么,一个字节都不写
2272
+ * POST /api/plugins/install {ref, token} → 真装
2273
+ * POST /api/plugins/install/local/preview {path} 同上,来源是本机一条目录
2274
+ * POST /api/plugins/install/local {path, token} 真装
2275
+ * POST /api/plugins/update/preview {name} 更新会带来什么
2276
+ * POST /api/plugins/update {name, token} → 真更新
2277
+ * POST /api/plugins/uninstall {name} → 卸载
2278
+ * POST /api/plugins/marketplaces {source} → 加一个市场
2279
+ * POST /api/plugins/marketplaces/remove {name} → 移除一个市场
2275
2280
  * ```
2276
2281
  *
2277
2282
  * ## ⚠️ 这一层**一条策略判断都没有**
@@ -2285,10 +2290,10 @@ interface RuntimeModel {
2285
2290
  * 界面上是一条画成可点、点了却被拒的行(判据同 `PluginSearchHit.installable`
2286
2291
  * 为什么不从结果里剔掉远程那条)。
2287
2292
  *
2288
- * ## ⚠️ 那一道闸:**五条 POST 在回环之外一律 403**,这是这个文件的正题
2293
+ * ## ⚠️ 那一道闸:**九条 POST 在回环之外一律 403**,这是这个文件的正题
2289
2294
  *
2290
2295
  * 判据全文在 protocol 的 [wire-plugin.ts](../../protocol/src/wire-plugin.ts) 文件头
2291
- * 第二节,这里记它对这一层的四条直接后果:
2296
+ * 第二节,这里记它对这一层的五条直接后果:
2292
2297
  *
2293
2298
  * 1. **`ctx.lanExposed` 为真时 403 `plugin-write-lan-exposed`,一个字节都不写。**
2294
2299
  * 装一包插件会落下 `hooks.json` 和 `mcp.json`,那两样**起子进程** ——
@@ -2297,8 +2302,12 @@ interface RuntimeModel {
2297
2302
  * 2. **`install/preview` 也吃这道闸,虽然它一个字节都不写**:`allowRemote: true`
2298
2303
  * 的部署上,预览一条远程条目会 spawn `git clone --depth 1` 到 staging。
2299
2304
  * 按「写不写盘」切一刀会漏掉它;
2300
- * 3. **那道闸在读请求体之前**,同 `addRole`:被拒的那一发连正文都不该被解析;
2301
- * 4. **`GET` 刻意不挡。** 那一份里是插件名 / 版本 / 目录路径,和
2305
+ * 3. **本地那两条同样挡**,虽然它们既不拉网也不起子进程。本地路径这一档的
2306
+ * **全部前提**是「网线另一头就是这台机器」,而 `lanExposed` 为真时那句前提是
2307
+ * 假的 —— 放行的话,LAN 上的人能指着这台机器上任意一个目录说「装它」,
2308
+ * 而那个目录里的 `hooks.json` 是谁写的他知道我们不知道;
2309
+ * 4. **那道闸在读请求体之前**,同 `addRole`:被拒的那一发连正文都不该被解析;
2310
+ * 5. **`GET` 刻意不挡。** 那一份里是插件名 / 版本 / 目录路径,和
2302
2311
  * `GET /api/sessions/:id/capabilities` 早就在发的那些同一档 —— 而挡掉它的
2303
2312
  * 唯一后果是那一页在 LAN 那一档下一片空白,
2304
2313
  * 偏偏那时用户最想知道的就是「我装了什么、生效了没有」。
@@ -2437,13 +2446,27 @@ type ActionOutcomeView = {
2437
2446
  ok: true;
2438
2447
  detail: string;
2439
2448
  } | RefusalView;
2449
+ /** `PluginMarketplaceEntry`(runtime)的镜像 */
2450
+ interface MarketplaceView {
2451
+ name: string;
2452
+ source: string;
2453
+ entries: number;
2454
+ addedAt: number;
2455
+ local: boolean;
2456
+ }
2457
+ /** 加 / 移除一个市场的结局。移除成功那一档没有 `marketplace` —— 它已经不在表里了 */
2458
+ type MarketOutcomeView = {
2459
+ ok: true;
2460
+ marketplace?: MarketplaceView;
2461
+ } | RefusalView;
2440
2462
  /**
2441
- * `PluginControl`(runtime)在服务端眼里的样子 —— **只有那六个动词加一格布尔**。
2463
+ * `PluginControl`(runtime)在服务端眼里的样子 —— **只有那十一个动词加一格布尔**。
2442
2464
  *
2443
2465
  * 镜像里只有它们,于是这个包**写不出** core 的 `setPluginEnabled`
2444
- * (停用一个插件会静默拿掉别人正依赖的一批命令,正当性和「装 / 卸」不是一回事)、
2445
- * 也写不出「往任意路径装一个插件」——那不是靠 review 盯住的,是编译期的事
2446
- * (同 `SkillImportControlView` 只有两个动作、`RoleWriteControlView` 只有 `add`)。
2466
+ * (停用一个插件会静默拿掉别人正依赖的一批命令,正当性和「装 / 卸」不是一回事);
2467
+ * 装进来的那两个动词也各自只表达得出一种来源 —— `install(ref)` 表达不出一条路径,
2468
+ * `installLocal(path)` 表达不出一个 `github:`(那不是靠 review 盯住的,是编译期的事,
2469
+ * 同 `SkillImportControlView` 只有两个动作、`RoleWriteControlView` 只有 `add`)。
2447
2470
  *
2448
2471
  * ⚠️ 每个方法都带 `lang?`。**镜像上漏一格不一定会红**(多一个可选参数是宽化,
2449
2472
  * `EpochRuntime` 照样满足这份镜像),而漏了的表现是这一层递不出去、一声不吭
@@ -2455,9 +2478,14 @@ interface PluginControlView {
2455
2478
  search(keyword?: string): readonly PluginHitView[];
2456
2479
  preview(ref: string, lang?: Lang): Promise<PreviewOutcomeView>;
2457
2480
  install(ref: string, token: string, lang?: Lang): Promise<InstallOutcomeView>;
2481
+ previewLocal(path: string, lang?: Lang): Promise<PreviewOutcomeView>;
2482
+ installLocal(path: string, token: string, lang?: Lang): Promise<InstallOutcomeView>;
2458
2483
  previewUpdate(name: string, lang?: Lang): Promise<PreviewOutcomeView>;
2459
2484
  update(name: string, token: string, lang?: Lang): Promise<InstallOutcomeView>;
2460
2485
  uninstall(name: string, lang?: Lang): Promise<ActionOutcomeView>;
2486
+ marketplaces(): readonly MarketplaceView[];
2487
+ addMarketplace(source: string, lang?: Lang): Promise<MarketOutcomeView>;
2488
+ removeMarketplace(name: string, lang?: Lang): Promise<MarketOutcomeView>;
2461
2489
  readonly pendingRestart: boolean;
2462
2490
  }
2463
2491
  /**
@@ -3965,6 +3993,17 @@ interface WebRuntimeView extends RuntimeCapabilities, RuntimeSecurity, RuntimeSe
3965
3993
  */
3966
3994
  artifactsRoot: string;
3967
3995
  usageScope: 'session' | 'run';
3996
+ /**
3997
+ * 一段会话到此刻的**累计**用量(2026-09-04)。`EpochRuntime.sessionUsage`
3998
+ * 结构上正好满足它。
3999
+ *
4000
+ * 收窄成一个函数而不是一份快照,理由和 `tools` 那个 getter 同一条:这本账
4001
+ * 在别的会话 / 别的进程里也会被写,拍一张快照的话第二个会话读到的是第一个的数。
4002
+ *
4003
+ * ⚠️ **它是「刷新之后总账没了」唯一的来路**,判据全文在
4004
+ * `WireMessageListResponse.usage` 上。`undefined` = 不知道,不是 0。
4005
+ */
4006
+ sessionUsage(sessionId: string): TokenUsage | undefined;
3968
4007
  /**
3969
4008
  * 自动压缩那条线(决定 8 的第三个时刻,2026-08-16)。`EpochRuntime.compression`
3970
4009
  * 结构上正好满足它 —— **对不上就是两边长歪了,装配那一行当场编译不过**。
package/dist/index.js CHANGED
@@ -1449,6 +1449,7 @@ function config(ctx, res, lang) {
1449
1449
  ...runtime.config.budget?.maxCostUsd !== void 0 ? { maxCostUsd: runtime.config.budget.maxCostUsd } : {},
1450
1450
  ...runtime.config.brand !== void 0 ? { brand: runtime.config.brand } : {},
1451
1451
  ...runtime.config.sidebarMenu !== void 0 ? { sidebarMenu: runtime.config.sidebarMenu } : {},
1452
+ ...runtime.config.blankCards !== void 0 ? { blankCards: runtime.config.blankCards } : {},
1452
1453
  provider: runtime.providerInfo,
1453
1454
  tools: runtime.tools,
1454
1455
  diagnostics: (lang && runtime.diagnosticsIn?.(lang)) ?? runtime.diagnosticList,
@@ -1540,7 +1541,11 @@ function listMessages(ctx, res, sessionId, lang) {
1540
1541
  return sendUnknownSession(res, sessionId, lang);
1541
1542
  }
1542
1543
  const messages = ctx.runtime.sessionStore?.loadMessages(sessionId) ?? [];
1543
- sendJson(res, 200, { messages });
1544
+ const usage = ctx.runtime.sessionUsage(sessionId);
1545
+ sendJson(res, 200, {
1546
+ messages,
1547
+ ...usage ? { usage } : {}
1548
+ });
1544
1549
  }
1545
1550
  async function postMessage(ctx, req, res, sessionId, lang) {
1546
1551
  if (!ctx.hub.has(sessionId)) {
@@ -2453,6 +2458,15 @@ function toWirePluginHit(hit, installedNames) {
2453
2458
  installed: installedNames.has(hit.entry.name)
2454
2459
  };
2455
2460
  }
2461
+ function toWireMarketplace(market) {
2462
+ return {
2463
+ name: market.name,
2464
+ source: market.source,
2465
+ entries: market.entries,
2466
+ addedAt: market.addedAt,
2467
+ local: market.local
2468
+ };
2469
+ }
2456
2470
  function toWirePreview2(preview) {
2457
2471
  const { manifest, source, inventory } = preview;
2458
2472
  return {
@@ -2505,6 +2519,7 @@ function listPlugins(ctx, res, lang) {
2505
2519
  sendJson(res, 200, {
2506
2520
  installed: installed.map(toWirePluginEntry),
2507
2521
  hits: control.search().map((hit) => toWirePluginHit(hit, installedNames)),
2522
+ marketplaces: control.marketplaces().map(toWireMarketplace),
2508
2523
  pendingRestart: control.pendingRestart
2509
2524
  });
2510
2525
  }
@@ -2534,6 +2549,14 @@ async function previewPluginInstall(ctx, req, res, lang) {
2534
2549
  if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
2535
2550
  sendPreview(res, await control.preview(fieldOf(asRecord(body.value), "ref"), lang));
2536
2551
  }
2552
+ async function previewLocalPluginInstall(ctx, req, res, lang) {
2553
+ if (refusedByLan(ctx, res, lang)) return;
2554
+ const control = controlOf(ctx, res, lang);
2555
+ if (!control) return;
2556
+ const body = await readJsonBody(req);
2557
+ if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
2558
+ sendPreview(res, await control.previewLocal(fieldOf(asRecord(body.value), "path"), lang));
2559
+ }
2537
2560
  async function previewPluginUpdate(ctx, req, res, lang) {
2538
2561
  if (refusedByLan(ctx, res, lang)) return;
2539
2562
  const control = controlOf(ctx, res, lang);
@@ -2574,6 +2597,19 @@ async function installPluginFromMarket(ctx, req, res, lang) {
2574
2597
  await control.install(fieldOf(payload, "ref"), fieldOf(payload, "token"), lang)
2575
2598
  );
2576
2599
  }
2600
+ async function installLocalPlugin(ctx, req, res, lang) {
2601
+ if (refusedByLan(ctx, res, lang)) return;
2602
+ const control = controlOf(ctx, res, lang);
2603
+ if (!control) return;
2604
+ const body = await readJsonBody(req);
2605
+ if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
2606
+ const payload = asRecord(body.value);
2607
+ sendAction(
2608
+ res,
2609
+ control,
2610
+ await control.installLocal(fieldOf(payload, "path"), fieldOf(payload, "token"), lang)
2611
+ );
2612
+ }
2577
2613
  async function updateInstalledPlugin(ctx, req, res, lang) {
2578
2614
  if (refusedByLan(ctx, res, lang)) return;
2579
2615
  const control = controlOf(ctx, res, lang);
@@ -2595,6 +2631,30 @@ async function uninstallPluginByName(ctx, req, res, lang) {
2595
2631
  if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
2596
2632
  sendAction(res, control, await control.uninstall(fieldOf(asRecord(body.value), "name"), lang));
2597
2633
  }
2634
+ function sendMarket(res, outcome) {
2635
+ sendJson(res, 200, {
2636
+ ok: outcome.ok,
2637
+ marketplace: outcome.ok && outcome.marketplace ? toWireMarketplace(outcome.marketplace) : null,
2638
+ reason: outcome.ok ? null : outcome.reason,
2639
+ detail: outcome.ok ? null : outcome.detail
2640
+ });
2641
+ }
2642
+ async function addPluginMarketplace(ctx, req, res, lang) {
2643
+ if (refusedByLan(ctx, res, lang)) return;
2644
+ const control = controlOf(ctx, res, lang);
2645
+ if (!control) return;
2646
+ const body = await readJsonBody(req);
2647
+ if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
2648
+ sendMarket(res, await control.addMarketplace(fieldOf(asRecord(body.value), "source"), lang));
2649
+ }
2650
+ async function removePluginMarketplace(ctx, req, res, lang) {
2651
+ if (refusedByLan(ctx, res, lang)) return;
2652
+ const control = controlOf(ctx, res, lang);
2653
+ if (!control) return;
2654
+ const body = await readJsonBody(req);
2655
+ if (!body.ok) return sendError(res, body.status, "bad-body", body.message);
2656
+ sendMarket(res, await control.removeMarketplace(fieldOf(asRecord(body.value), "name"), lang));
2657
+ }
2598
2658
  function toWireProvider(option, lang) {
2599
2659
  return {
2600
2660
  type: option.type,
@@ -4185,6 +4245,12 @@ async function handleApi(opts, req, res, method, segments, lang) {
4185
4245
  if (resource === "plugins" && first === "install" && second === void 0 && method === "POST") {
4186
4246
  return installPluginFromMarket(ctx, req, res, lang);
4187
4247
  }
4248
+ if (resource === "plugins" && first === "install" && second === "local" && detail === "preview" && method === "POST") {
4249
+ return previewLocalPluginInstall(ctx, req, res, lang);
4250
+ }
4251
+ if (resource === "plugins" && first === "install" && second === "local" && detail === void 0 && method === "POST") {
4252
+ return installLocalPlugin(ctx, req, res, lang);
4253
+ }
4188
4254
  if (resource === "plugins" && first === "update" && second === "preview" && method === "POST") {
4189
4255
  return previewPluginUpdate(ctx, req, res, lang);
4190
4256
  }
@@ -4194,6 +4260,12 @@ async function handleApi(opts, req, res, method, segments, lang) {
4194
4260
  if (resource === "plugins" && first === "uninstall" && second === void 0 && method === "POST") {
4195
4261
  return uninstallPluginByName(ctx, req, res, lang);
4196
4262
  }
4263
+ if (resource === "plugins" && first === "marketplaces" && second === "remove" && method === "POST") {
4264
+ return removePluginMarketplace(ctx, req, res, lang);
4265
+ }
4266
+ if (resource === "plugins" && first === "marketplaces" && second === void 0 && method === "POST") {
4267
+ return addPluginMarketplace(ctx, req, res, lang);
4268
+ }
4197
4269
  if (resource === "providers" && first === void 0 && isRead(method)) {
4198
4270
  return listProviders(ctx, res, lang);
4199
4271
  }