@epoch-agent/runtime 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
@@ -2352,6 +2352,7 @@ declare function buildModelCatalogControl(opts: ModelCatalogOptions): ModelCatal
2352
2352
  * | 档 | 说的是 | 下一步 |
2353
2353
  * | ----------------- | -------------------------------------------------- | -------------- |
2354
2354
  * | `unknown-ref` | 不是 `<市场>/<插件>`,或者那个市场 / 插件不存在 | 换个引用 |
2355
+ * | `not-local` | 本地那条路收到的不是一条**本地绝对路径** | 换条绝对路径 |
2355
2356
  * | `remote-disabled` | 它是远程 source,而 `allowRemote` 关着 | 宿主去开那个开关 |
2356
2357
  * | `conflict` | 已经装过同名的 | 先卸载 |
2357
2358
  * | `stale-token` | 你确认的和现在要装的对不上,**一个字节都没写** | 重看一次预览 |
@@ -2359,7 +2360,7 @@ declare function buildModelCatalogControl(opts: ModelCatalogOptions): ModelCatal
2359
2360
  * | `nothing-to-do` | **不是出错**:软链装的插件一直是最新的,没得更新 | 什么都不用做 |
2360
2361
  * | `failed` | 其余(拉不到 / 清单坏了 / 写记录时被别的进程抢先) | 看 `detail` |
2361
2362
  */
2362
- type PluginRefuseReason = 'unknown-ref' | 'remote-disabled' | 'conflict' | 'stale-token' | 'not-installed' | 'nothing-to-do' | 'failed';
2363
+ type PluginRefuseReason = 'unknown-ref' | 'not-local' | 'remote-disabled' | 'conflict' | 'stale-token' | 'not-installed' | 'nothing-to-do' | 'failed';
2363
2364
  /** 拒绝的共同形状。`preview` 有值时说明失败发生在读完清单之后,界面可以照样画那一屏 */
2364
2365
  interface PluginRefusal {
2365
2366
  ok: false;
@@ -2436,24 +2437,46 @@ type PluginInstallOutcome = {
2436
2437
  * 所以 `HostMarketplaces.allowRemote` **默认 false**,而被它挡下的那一条
2437
2438
  * **是拒绝 + 一句话,不是静默跳过**:静默的表现是搜索里明明有那一条、点了没反应。
2438
2439
  *
2439
- * ## 四、⚠️ 这一层**只收 `<市场>/<插件>`,收不了一条路径** —— 比 `skill-import` 更紧
2440
+ * ## 四、⚠️ 装进来的动词有**两个**,各自只表达得出一种来源
2440
2441
  *
2441
- * {@link PluginControl.preview} / {@link PluginControl.install} 的入参上**没有**
2442
- * 「source」这个东西,只有一个市场引用。于是「指一条任意路径装一个插件」这件事
2443
- * 在这个形状下**说不出口** —— `HostAgentRole` 少一个 `source` 的手法,
2444
- * 不是靠 review 盯住的,是编译期的事。逐条对着今天那条最像的路
2445
- * (`server/src/skill-import.ts`):
2442
+ * | 动词 | 入参 | 表达得出 |
2443
+ * | ----------------------------------- | -------- | ---------------------------- |
2444
+ * | {@link PluginControl.install} | `ref` | `<市场>/<插件>`,仅此 |
2445
+ * | {@link PluginControl.installLocal} | `path` | 本机一条**绝对路径**,仅此 |
2446
+ * | 没有第三个 | — | — |
2446
2447
  *
2447
- * | 判据 | `skill-import` 今天 | 这一条 |
2448
- * | ---------------- | ------------------------- | -------------------------- |
2449
- * | 能不能传字节 | 不能(只收路径) | 不能 |
2450
- * | 能不能指任意路径 | **能**(收一个任意 path) | **不能**(只能从清单里挑) |
2451
- * | 清单是谁放的 | — | 宿主主进程 / 用户自己敲的 |
2448
+ * 两个入参上都**没有**「source」这个东西。于是「给我一个 `github:acme/x` 我就
2449
+ * clone」这件事在这两个形状下**都说不出口** —— `HostAgentRole` 少一个 `source`
2450
+ * 的手法,不是靠 review 盯住的,是编译期的事。
2451
+ *
2452
+ * ### ⚠️ 这一节 2026-09-04 翻过案:原来只有第一行
2453
+ *
2454
+ * 原话是「这一层**只收 `<市场>/<插件>`,收不了一条路径** —— 比 `skill-import`
2455
+ * 更紧」。翻案的是**本地路径那一档**,判据全文(三条)在 protocol 的
2456
+ * `wire-plugin.ts` 文件头第一节,这里只记最要紧的那一条:
2457
+ *
2458
+ * **原来那条路在真实部署上是断的。** `epoch web --plugins` 传的是
2459
+ * `{ sources: [] }` —— 一台自己机器上起的界面没有任何预置市场,而加市场当时只能
2460
+ * 在命令行上。于是「从界面装一个插件」在 CLI 宿主上从来没有成立过。
2461
+ * 一道**只在没人走得到的路上生效**的闸门不是安全性,是一个空转的判断。
2462
+ *
2463
+ * ⚠️ 而**真正的那道闸原地不动**:本地那条在网线那一侧照样吃
2464
+ * `server/src/plugin.ts` 的 403(`lanExposed` 为真时一律拒)。
2465
+ * 本地路径这一档的全部前提就是「网线另一头就是这台机器」。
2466
+ *
2467
+ * 逐条对着今天那条最像的路(`server/src/skill-import.ts`):
2468
+ *
2469
+ * | 判据 | `skill-import` 今天 | `install` | `installLocal` |
2470
+ * | ---------------- | ------------------------- | -------------------- | ------------------- |
2471
+ * | 能不能传字节 | 不能(只收路径) | 不能 | 不能 |
2472
+ * | 能不能指任意路径 | **能**(收一个任意 path) | **不能** | **能,但只限本地** |
2473
+ * | 远程写法收不收 | — | 只收清单里那条 | **不收**(`not-local`)|
2452
2474
  *
2453
2475
  * ⚠️ 「清单」= `~/.epoch/marketplaces.json` 里那些,也就是**宿主预置的**加上
2454
- * **用户自己 `epoch plugin marketplace add` 加的**。后者照样算数:那是用户在自己
2455
- * 机器上做的决定,和宿主主进程同一档。这条路唯一挡死的是「谁都没列过的那个东西」——
2456
- * 要装清单外的插件,路仍然在,只是**在命令行上**(那一问的闸门只该有一份)。
2476
+ * **用户自己加的**(命令行那条 `epoch plugin marketplace add`,或
2477
+ * {@link PluginControl.addMarketplace})。后者照样算数:那是用户在自己机器上做的
2478
+ * 决定,和宿主主进程同一档。这条路唯一挡死的是「谁都没列过的那个**远程**东西」——
2479
+ * 要装那种,路仍然在,只是**在命令行上**。
2457
2480
  *
2458
2481
  * ## 五、preview → install 是两段,中间那条缝用 token 缝上
2459
2482
  *
@@ -2499,6 +2522,21 @@ interface PluginListEntry {
2499
2522
  */
2500
2523
  counts?: PluginCounts;
2501
2524
  }
2525
+ /**
2526
+ * 已加的一个市场 —— {@link PluginControl.marketplaces} 交出来的一行。
2527
+ *
2528
+ * `local` 是**这一层算好的结论**(拿 `isLocalSource` 判一次),不是把
2529
+ * `allowRemote` 那个开关透出去让上层自己推 —— 判据同 {@link PluginSearchHit.installable}:
2530
+ * 一个问题只该有一个答案,两处能推的那天它们就会分叉。
2531
+ */
2532
+ interface PluginMarketplaceEntry {
2533
+ name: string;
2534
+ source: string;
2535
+ /** 目录里有几条。**0 是合法的** —— 一个空市场和「没加过市场」是两回事 */
2536
+ entries: number;
2537
+ addedAt: number;
2538
+ local: boolean;
2539
+ }
2502
2540
  /** 市场里的一条,加上「在当前开关下装不装得上」 */
2503
2541
  interface PluginSearchHit {
2504
2542
  marketplace: string;
@@ -2520,12 +2558,23 @@ type PluginActionOutcome = {
2520
2558
  ok: true;
2521
2559
  detail: string;
2522
2560
  } | PluginRefusal;
2561
+ /** 加 / 移除一个市场的结局。加成功那一档带回刚进来的那一条 */
2562
+ type PluginMarketOutcome = {
2563
+ ok: true;
2564
+ marketplace?: PluginMarketplaceEntry;
2565
+ } | PluginRefusal;
2523
2566
  /**
2524
- * 收窄到六个动词的控制面。
2567
+ * 收窄到十一个动词的控制面。
2525
2568
  *
2526
- * 只有这六个,于是上层(`@epoch-agent/server` 那一页,方案 59 E2)**写不出**
2527
- * 「往任意路径装一个插件」和「改一条安装记录」—— `McpControl` 只露
2528
- * `reconnect`、`SkillControl` 只露两个导入动作。
2569
+ * 只有这些,于是上层(`@epoch-agent/server` 那一页,方案 59 E2)**写不出**
2570
+ * 「改一条安装记录」,也写不出「给我一个 `github:acme/x` 我就 clone」——
2571
+ * `McpControl` 只露 `reconnect`、`SkillControl` 只露两个导入动作。
2572
+ *
2573
+ * ⚠️ **装进来的动词有两个({@link install} / {@link installLocal}),
2574
+ * 而不是一个带 source 的**。判据全文在 protocol 的 `wire-plugin.ts` 文件头
2575
+ * 第一节(含 2026-09-04 那次翻案):一个 `install(source)` 能表达任意来源,
2576
+ * 这一对各自只表达得出一种,而它们的风险不是一个量级
2577
+ * (本地 = `symlinkSync`,市场那条可能 clone / 拉包)。
2529
2578
  *
2530
2579
  * ⚠️ 刻意**没有 enable / disable**(core 的 `setPluginEnabled` 在那儿摆着):
2531
2580
  * 那两个动作的正当性和「装 / 卸」不是一回事(停用一个插件会**静默拿掉**别人正
@@ -2558,6 +2607,17 @@ interface PluginControl {
2558
2607
  * ⚠️ 装完**不会生效**,{@link pendingRestart} 会变成 `true`。
2559
2608
  */
2560
2609
  install(ref: string, token: string, lang?: Lang): Promise<PluginInstallOutcome>;
2610
+ /**
2611
+ * 「将安装什么」,来源是**本机一条目录**(2026-09-04 补,判据在文件头第四节
2612
+ * 那次翻案)。
2613
+ *
2614
+ * ⚠️ **和 {@link preview} 是两个方法而不是一个重载**,这是形状上的事:一个
2615
+ * `preview(source)` 能表达 `github:acme/x`,而这一对方法**只表达得出一条路径**。
2616
+ * 传进来的东西不是本地绝对路径时回 `not-local`,不会被当成远程顺手装了。
2617
+ */
2618
+ previewLocal(path: string, lang?: Lang): Promise<PluginPreviewOutcome>;
2619
+ /** 真装一条本地目录。`token` 来自上一次 {@link previewLocal} */
2620
+ installLocal(path: string, token: string, lang?: Lang): Promise<PluginInstallOutcome>;
2561
2621
  /**
2562
2622
  * 「更新会带来什么」。同 {@link preview},只是来源取自那条安装记录。
2563
2623
  *
@@ -2575,6 +2635,31 @@ interface PluginControl {
2575
2635
  * **还活着** —— 同样要重建,见 {@link pendingRestart}。
2576
2636
  */
2577
2637
  uninstall(name: string, lang?: Lang): Promise<PluginActionOutcome>;
2638
+ /**
2639
+ * 已加了哪几个市场。**纯读盘**(`marketplaces.json`),一次网都不上。
2640
+ *
2641
+ * ⚠️ 和 {@link search} 是两个问题:那个答「有什么插件」,这个答「**清单是谁放的**」。
2642
+ * 一个空的 `search()` 分不出「没加过市场」和「加了但市场是空的」——
2643
+ * 而在 `epoch web --plugins`(`sources: []`)那一档下,前者是常态。
2644
+ */
2645
+ marketplaces(): PluginMarketplaceEntry[];
2646
+ /**
2647
+ * 加一个市场。**取一份目录**(本地就读文件,远程要上网),成了就写进
2648
+ * `marketplaces.json`。
2649
+ *
2650
+ * ⚠️ **远程市场吃 `allowRemote`**:取目录是一次网络动作,判据同
2651
+ * `wireHostMarketplaces` 里那个循环(那个文件头明写着「两处是同一个风险的两处
2652
+ * 出口」)。挡下的那一条是**拒绝 + 一句话**,不是静默跳过。
2653
+ *
2654
+ * ⚠️ 加一个市场**一个扩展物都不动** —— 它改的是「有什么可以装」。
2655
+ * 所以这条路**不碰** {@link pendingRestart}。
2656
+ */
2657
+ addMarketplace(source: string, lang?: Lang): Promise<PluginMarketOutcome>;
2658
+ /**
2659
+ * 移除一个市场。**只改那份清单,装着的插件一个都不动** ——
2660
+ * 从它装的那些照常在(安装记录里存的是 source,不是市场名)。
2661
+ */
2662
+ removeMarketplace(name: string, lang?: Lang): Promise<PluginMarketOutcome>;
2578
2663
  /**
2579
2664
  * **这一程动过插件、而那些改动还没生效。**
2580
2665
  *
@@ -6704,6 +6789,33 @@ interface EpochRuntime {
6704
6789
  * 接着涨,也可能是重新开始后恰好超过。所以只能由装配方直说。
6705
6790
  */
6706
6791
  usageScope: 'session' | 'run';
6792
+ /**
6793
+ * 一段会话到此刻**累计**花了多少 —— 从落盘那本账里读,不需要 Hub 里有它。
6794
+ *
6795
+ * ## 它补的是「刷新之后那个数就没了」(2026-09-04)
6796
+ *
6797
+ * `usage` 事件是**帧**:新连上来的浏览器不可能收到过去那几帧。于是 2026-09-02
6798
+ * 搬到输入框那一格的用量在**打开一段旧会话 / 刷新页面**之后是空的 ——
6799
+ * 而同一屏上每一轮的脚注都有数(那几笔 2026-08-18 就落盘了,走
6800
+ * `EpochTurnFacts`)。屏幕上因此出现「逐轮都有账、总账没有」。
6801
+ *
6802
+ * ⚠️ **不能拿逐轮的账相加代替它。** 那两个数真的加不到一起,判据逐字在
6803
+ * `web/src/settings/spend.tsx` 文件头:这一份含 `BudgetStore` 从上个进程恢复的
6804
+ * 历史、还含一轮里最后一次模型调用之后的压缩计费,而 `usageScope === 'run'` 时
6805
+ * 它压根不是会话总额。所以真源只有这一本账。
6806
+ *
6807
+ * ## 缺席的两种情况,都不是故障
6808
+ *
6809
+ * 1. `usageScope === 'run'` —— 没有 `BudgetStore`(状态目录不可写),
6810
+ * 这个函数恒回 `undefined`。而那一档下「会话累计」本来就不存在,
6811
+ * 界面上缺席是**对的**;
6812
+ * 2. 这个会话在账本里没有记录 —— 一轮都还没跑过,或者记录过了保留期
6813
+ * (`BudgetStore` 的 `RETENTION_DAYS`)。`undefined` = 不知道,**不是 0**。
6814
+ *
6815
+ * 回的那份和 `usage` 事件里的 `cumulative` **逐字同一套算术**
6816
+ * (两处都走 `persistedUsage()`),所以直播那一帧接上来时屏幕上的数不会跳。
6817
+ */
6818
+ sessionUsage(sessionId: string): TokenUsage | undefined;
6707
6819
  /**
6708
6820
  * 自动压缩那条线的三个数(决定 8 的第三个时刻,2026-08-16)。
6709
6821
  *
@@ -7714,4 +7826,4 @@ interface McpToolDeps {
7714
7826
  */
7715
7827
  declare function createMcpTools(deps: McpToolDeps): EpochTool[];
7716
7828
 
7717
- export { type AgentEventBus, type AgentEventSubscriber, AgentRoleError, AgentSession, type AgentSessionOptions, type ApplyHostPresetOptions, ApprovalRelay, type ApprovalRevokeResult, type ApproveFn, type BindWorkspaceFailure, type BindWorkspaceResult, type BuildRuntimeOptions, type BuildToolsInput, type CommandControl, type CommandWiring, type DiscoverOptions, type EffectiveSetting, type EpochRuntime, type ExecutorRuntime, type FileCandidatesResult, type FileOpenPlan, type FileReadOutcome, type FileStatOutcome, type FireResult, type FireScheduleOptions, type GoalControl, HOST_CAPABILITIES_MODULE, HOST_MARKETPLACE_MODULE, HOST_PRESET_MODULE, type HistoryMessage, type HookStatus, type HostAgentRole, type HostCapabilities, type HostCapabilityWiring, type HostMarketplaces, type HostPreset, type HostPresetProvider, type HostPresetWorkspace, type HostSkillDir, type LevelChangeResult, MCP_ADD_TOOL, MCP_LIST_TOOL, MCP_REMOVE_TOOL, MCP_UPDATE_TOOL, type McpAddInput, type McpAddOutcome, type McpAdminOptions, type McpApplyAction, type McpApplyChange, type McpApplyOutcome, type McpApplySource, type McpConfigIssue, type McpConfigReadOutcome, type McpConfigSaveOutcome, type McpConfigSnapshot, type McpControl, type McpEditFailure, type McpEditOutcome, type McpListResult, type McpLoginCliOptions, type McpReconnectOutcome, type McpRemoveOutcome, type McpServerBatch, type McpServerOverview, type McpToolDeps, type McpUpdateOutcome, type McpWriteOutcome, type ModelCatalogControl, type ModelCatalogOptions, type ModelControl, type ModelSuggestions, type ModelTurnScope, type PermissionsControl, type PlanControl, type PluginActionOutcome, type PluginControl, type PluginInstallOutcome, type PluginListEntry, type PluginPreviewOutcome, type PluginRefusal, type PluginRefuseReason, type PluginSearchHit, type PolicyDirStatus, type PolicyStatus, type ProviderKeyWritten, type ProviderOption, QuestionRelay, type RelayClock, type RoleAddInput, type RoleAddOutcome, type RoleControl, type RoleTurnScope, type RoleWriteControl, type RunOptions, SCHEDULE_EXIT_CODES, type ScheduleCapability, type ScheduleControl, type ScheduleControlOptions, type ScheduleSaveResult, type SerializableAgentEvent, type SerializableApprovalRequest, type SerializableQuestionRequest, type SerializedEvent, type ServiceOptions, type Services, type SessionControl, type SessionDeletion, type SessionFileChange, type SessionHit, SessionStore, type SessionSummary, type SessionWorkspace, SessionWorkspaces, type SessionWorkspacesOptions, type SettingWriteDone, type SettingWriteInput, type SettingWriteRefused, type SettingWriteReport, type SettingsControl, type StoredMessage, type TurnDetail, type TurnStep, type UserActionResult, type UserActions, type UtilityControl, type UtilityGenerateInput, type UtilityGenerateResult, type WireWorkspaceInput, type WorkspaceBindingState, type WorkspaceControl, type WorkspaceFilesDeps, type WorkspaceFilesView, applyHostPreset, applyMcpConfig, buildModelCatalogControl, buildRuntime, buildServices, buildWorkspaceFilesControl, collectFileChanges, createMcpTools, createScheduleControl, createUserActions, ensureSecretsReady, exitCodeFor, fireSchedule, isSystemOpenable, listMcpServers, mcpConfigRevision, mcpLogin, mcpLogout, probeMcpServer, providerLabel, readMcpConfigFile, registerAskQuestion, registerLocalTools, registerMcpTools, removeMcpServer, removeServerText, replaceServerText, serializeStream, splitDirList, toSerializable, updateMcpServer, utilityBudgetKey, wireCommands, wireHostCapabilities, wirePluginMcpServers, wireWorkspace, withModelScope, withRoleScope, withToolScope, writeMcpConfigFile, writeMcpServer };
7829
+ export { type AgentEventBus, type AgentEventSubscriber, AgentRoleError, AgentSession, type AgentSessionOptions, type ApplyHostPresetOptions, ApprovalRelay, type ApprovalRevokeResult, type ApproveFn, type BindWorkspaceFailure, type BindWorkspaceResult, type BuildRuntimeOptions, type BuildToolsInput, type CommandControl, type CommandWiring, type DiscoverOptions, type EffectiveSetting, type EpochRuntime, type ExecutorRuntime, type FileCandidatesResult, type FileOpenPlan, type FileReadOutcome, type FileStatOutcome, type FireResult, type FireScheduleOptions, type GoalControl, HOST_CAPABILITIES_MODULE, HOST_MARKETPLACE_MODULE, HOST_PRESET_MODULE, type HistoryMessage, type HookStatus, type HostAgentRole, type HostCapabilities, type HostCapabilityWiring, type HostMarketplaces, type HostPreset, type HostPresetProvider, type HostPresetWorkspace, type HostSkillDir, type LevelChangeResult, MCP_ADD_TOOL, MCP_LIST_TOOL, MCP_REMOVE_TOOL, MCP_UPDATE_TOOL, type McpAddInput, type McpAddOutcome, type McpAdminOptions, type McpApplyAction, type McpApplyChange, type McpApplyOutcome, type McpApplySource, type McpConfigIssue, type McpConfigReadOutcome, type McpConfigSaveOutcome, type McpConfigSnapshot, type McpControl, type McpEditFailure, type McpEditOutcome, type McpListResult, type McpLoginCliOptions, type McpReconnectOutcome, type McpRemoveOutcome, type McpServerBatch, type McpServerOverview, type McpToolDeps, type McpUpdateOutcome, type McpWriteOutcome, type ModelCatalogControl, type ModelCatalogOptions, type ModelControl, type ModelSuggestions, type ModelTurnScope, type PermissionsControl, type PlanControl, type PluginActionOutcome, type PluginControl, type PluginInstallOutcome, type PluginListEntry, type PluginMarketOutcome, type PluginMarketplaceEntry, type PluginPreviewOutcome, type PluginRefusal, type PluginRefuseReason, type PluginSearchHit, type PolicyDirStatus, type PolicyStatus, type ProviderKeyWritten, type ProviderOption, QuestionRelay, type RelayClock, type RoleAddInput, type RoleAddOutcome, type RoleControl, type RoleTurnScope, type RoleWriteControl, type RunOptions, SCHEDULE_EXIT_CODES, type ScheduleCapability, type ScheduleControl, type ScheduleControlOptions, type ScheduleSaveResult, type SerializableAgentEvent, type SerializableApprovalRequest, type SerializableQuestionRequest, type SerializedEvent, type ServiceOptions, type Services, type SessionControl, type SessionDeletion, type SessionFileChange, type SessionHit, SessionStore, type SessionSummary, type SessionWorkspace, SessionWorkspaces, type SessionWorkspacesOptions, type SettingWriteDone, type SettingWriteInput, type SettingWriteRefused, type SettingWriteReport, type SettingsControl, type StoredMessage, type TurnDetail, type TurnStep, type UserActionResult, type UserActions, type UtilityControl, type UtilityGenerateInput, type UtilityGenerateResult, type WireWorkspaceInput, type WorkspaceBindingState, type WorkspaceControl, type WorkspaceFilesDeps, type WorkspaceFilesView, applyHostPreset, applyMcpConfig, buildModelCatalogControl, buildRuntime, buildServices, buildWorkspaceFilesControl, collectFileChanges, createMcpTools, createScheduleControl, createUserActions, ensureSecretsReady, exitCodeFor, fireSchedule, isSystemOpenable, listMcpServers, mcpConfigRevision, mcpLogin, mcpLogout, probeMcpServer, providerLabel, readMcpConfigFile, registerAskQuestion, registerLocalTools, registerMcpTools, removeMcpServer, removeServerText, replaceServerText, serializeStream, splitDirList, toSerializable, updateMcpServer, utilityBudgetKey, wireCommands, wireHostCapabilities, wirePluginMcpServers, wireWorkspace, withModelScope, withRoleScope, withToolScope, writeMcpConfigFile, writeMcpServer };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { loadCommandDefinitions, SLASH_COMMANDS_DIAG_MODULE, createToolScope, expandCommand, RecentWorkspaces, clearModelCache, discoverModels, ScheduleStore, isWithinWindow, acquireScheduleLock, triggerPeriodMs, ScheduleRecorder, pruneRecordings, suggestRuleFromApproval, needsAllowlist, isNonInteractive, loadPlugins, assertLevelAllowed, discoverProject, ProviderRouter, ProviderError, PROVIDER_NO_CREDENTIAL, SessionManager, GoalService, MemoryManager, MemoryReviewer, resolveSkillDirs, SkillSystem, SkillLearner, HookManager, loadHookSources, ApprovalCache, PermissionManager, resolvePolicyDirs, resolvePoliciesDir, loadPolicyFiles, checkShadowing, compileRuleLists, ContextCompressor, compressionOptions, TrackerDB, getConfigOrigins, resolveComplianceDirs, resolveComplianceDir, loadLexiconFiles, ComplianceEngine, DEFAULT_COMPLIANCE_SETTINGS, mapOperationType, affectedPaths, workspaceDisplayPath, persistParts, persistToolResults, ToolRegistry, createTodoTool, createGoalTool, createMemoryTool, createCodeExecTool, createRunCodeTool, createSkillViewTool, createSkillWriteTools, createToolSearchTool, createToolRevealState, createAskQuestionTool, resolveWorkspace, unloadedInstructionFiles, resolveProjectRoot, loadConfig, CodeSandbox, CheckpointManager, createDelegateTool, findRole, systemOpenArgv, resolveInWorkspace, listWorkspaceFiles, loadRoleDefinitions, mergeRoles, AGENT_ROLE_DIAG_MODULE, builtinRoles, unknownRoleTools, BLOCKED_TOOLS, blockedRoleTools, unknownRoleSkills, BudgetStore, mergeHeadlessPolicy, describeHeadlessPolicy, importSkills, previewSkillImport, removeSessionArtifacts, gateForTool, managedSwitchNotes, originOf, EpochConfigSchema, defaultConfig, SettingsFileSchema, isWritableSettingKey, writeSettingValue, SETTING_WRITE_APPLY, resolveSettings, SETTING_WRITE_LAYERS, readProviderKeyEnv, validateSchedule, createAllowAllGate, setTrustGate, TrustManager, createTrustGate, createDenyAllGate, getImportGate, setImportGate, createAllowAllImportGate, ImportTrustStore, createDenyAllImportGate, createImportGate, getCapability, restoreParts, createSessionSearchTools, PlanModeState, createPlanModeTools, toolTableTokens, explainSetting, settingWriteTargets, settingValueKind, settingValueChoices, uninstallPlugin, readMarketplaces, searchMarketplaces, resolveEntrySource, resolveSource, readPluginRecords, createRoleFile, AgentLoop, resolveArtifactRetention, roleScopedProvider, createRoleScope, revealScopedProvider, withGoalPrompt, withGoalCompression, ScheduleRegistrar, resolveScheduleBackend, updateMarketplace, addMarketplace, SETTINGS_LAYER_ORDER, resolveMarketplaceRef, BudgetGuard, installPlugin, currentEpochVersion } from '@epoch-agent/core';
1
+ import { loadCommandDefinitions, SLASH_COMMANDS_DIAG_MODULE, createToolScope, expandCommand, RecentWorkspaces, clearModelCache, discoverModels, substitutePluginRoot, ScheduleStore, isWithinWindow, acquireScheduleLock, triggerPeriodMs, ScheduleRecorder, pruneRecordings, suggestRuleFromApproval, needsAllowlist, isNonInteractive, loadPlugins, assertLevelAllowed, discoverProject, ProviderRouter, ProviderError, PROVIDER_NO_CREDENTIAL, SessionManager, GoalService, MemoryManager, MemoryReviewer, resolveSkillDirs, SkillSystem, SkillLearner, HookManager, loadHookSources, ApprovalCache, PermissionManager, resolvePolicyDirs, resolvePoliciesDir, loadPolicyFiles, checkShadowing, compileRuleLists, ContextCompressor, compressionOptions, TrackerDB, getConfigOrigins, resolveComplianceDirs, resolveComplianceDir, loadLexiconFiles, ComplianceEngine, DEFAULT_COMPLIANCE_SETTINGS, mapOperationType, affectedPaths, workspaceDisplayPath, persistParts, persistToolResults, ToolRegistry, createTodoTool, createGoalTool, createMemoryTool, createCodeExecTool, createRunCodeTool, createSkillViewTool, createSkillWriteTools, createToolSearchTool, createToolRevealState, createAskQuestionTool, resolveWorkspace, unloadedInstructionFiles, resolveProjectRoot, loadConfig, CodeSandbox, CheckpointManager, createDelegateTool, findRole, persistedUsage, systemOpenArgv, resolveInWorkspace, listWorkspaceFiles, loadRoleDefinitions, mergeRoles, AGENT_ROLE_DIAG_MODULE, builtinRoles, unknownRoleTools, BLOCKED_TOOLS, blockedRoleTools, unknownRoleSkills, BudgetStore, mergeHeadlessPolicy, describeHeadlessPolicy, importSkills, previewSkillImport, removeSessionArtifacts, gateForTool, managedSwitchNotes, originOf, EpochConfigSchema, defaultConfig, SettingsFileSchema, isWritableSettingKey, writeSettingValue, SETTING_WRITE_APPLY, resolveSettings, SETTING_WRITE_LAYERS, readProviderKeyEnv, validateSchedule, createAllowAllGate, setTrustGate, TrustManager, createTrustGate, createDenyAllGate, getImportGate, setImportGate, createAllowAllImportGate, ImportTrustStore, createDenyAllImportGate, createImportGate, getCapability, restoreParts, createSessionSearchTools, PlanModeState, createPlanModeTools, toolTableTokens, explainSetting, settingWriteTargets, settingValueKind, settingValueChoices, uninstallPlugin, removeMarketplace, addMarketplace, readMarketplaces, searchMarketplaces, resolveEntrySource, resolveSource, readPluginRecords, createRoleFile, AgentLoop, resolveArtifactRetention, roleScopedProvider, createRoleScope, revealScopedProvider, withGoalPrompt, withGoalCompression, ScheduleRegistrar, resolveScheduleBackend, updateMarketplace, SETTINGS_LAYER_ORDER, resolveMarketplaceRef, BudgetGuard, installPlugin, currentEpochVersion } from '@epoch-agent/core';
2
2
  export { DEFAULT_GOAL_MAX_ROUNDS, MAX_GOAL_MAX_ROUNDS, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, nextRunAt, readRecording, skillIndexDescription } from '@epoch-agent/core';
3
3
  import { projectCommandsDir, commandsDir, t, issueDetails, resolveHomeDir, configPath, readKey, setScalar, setSectionField, mcpConfigPath, workspacesPath, writeProviderSecret, envPath, maskApiKey, dbPath, automationLogsDir, pluginsStatePath, safeInit, memoriesDir, skillsDir, claudeUserSettingsPath, hooksPath, artifactsDir, resolveRipgrep, withTimeout, createProcessTable, marketplacesPath, pluginsDir, agentsDir, budgetStatePath, createSecretStore, setSecretStore, prefetchProviderSecrets, setSecretValues, migrateEnvSecrets, setDataKey, parseDataKey, generateDataKey, i18nDiagnostics, automationWorkDir, trustPath, trustedImportsPath, setShell, probeShell } from '@epoch-agent/infra';
4
4
  export { t } from '@epoch-agent/infra';
@@ -995,6 +995,15 @@ function createPluginControl(deps) {
995
995
  }
996
996
  return { ok: true, source: hit.ref.source, marketplace: hit.ref.marketplace };
997
997
  };
998
+ const resolveLocal = (path, lang) => {
999
+ const refuse2 = {
1000
+ ok: false,
1001
+ reason: "not-local",
1002
+ detail: t("plugin_control.not_local_path", { path: path === "" ? "(empty)" : path }, lang)
1003
+ };
1004
+ if (path === "" || !isAbsolute(path)) return refuse2;
1005
+ return resolveSource(path)?.type === "local" ? { ok: true, source: path } : refuse2;
1006
+ };
998
1007
  const recordOf = (name, lang) => {
999
1008
  const record = readPluginRecords(deps.statePath).records.find((r) => r.name === name);
1000
1009
  if (!record) {
@@ -1051,6 +1060,58 @@ function createPluginControl(deps) {
1051
1060
  const target = resolveRef(ref, lang);
1052
1061
  return target.ok ? mark(await flow.install(target.source, token, { marketplace: target.marketplace }, lang)) : target;
1053
1062
  },
1063
+ previewLocal: async (path, lang) => {
1064
+ const target = resolveLocal(path, lang);
1065
+ return target.ok ? flow.preview(target.source, {}, lang) : target;
1066
+ },
1067
+ installLocal: async (path, token, lang) => {
1068
+ const target = resolveLocal(path, lang);
1069
+ return target.ok ? mark(await flow.install(target.source, token, {}, lang)) : target;
1070
+ },
1071
+ marketplaces: () => readMarketplaces(deps.marketplacesPath).records.map((r) => ({
1072
+ name: r.name,
1073
+ source: r.source,
1074
+ entries: r.catalog.plugins.length,
1075
+ addedAt: r.addedAt,
1076
+ local: isLocalSource(r.source)
1077
+ })),
1078
+ addMarketplace: async (source, lang) => {
1079
+ const raw = source.trim();
1080
+ if (raw === "") {
1081
+ return {
1082
+ ok: false,
1083
+ reason: "unknown-ref",
1084
+ detail: t("plugin_control.market_source_empty", void 0, lang)
1085
+ };
1086
+ }
1087
+ if (!deps.allowRemote && !isLocalSource(raw)) {
1088
+ return {
1089
+ ok: false,
1090
+ reason: "remote-disabled",
1091
+ detail: t("plugin_control.market_add_remote_off", { source: raw }, lang)
1092
+ };
1093
+ }
1094
+ const outcome = await addMarketplace(raw, { statePath: deps.marketplacesPath });
1095
+ if (!outcome.ok) return { ok: false, reason: "failed", detail: outcome.reason };
1096
+ return {
1097
+ ok: true,
1098
+ marketplace: {
1099
+ name: outcome.record.name,
1100
+ source: outcome.record.source,
1101
+ entries: outcome.record.catalog.plugins.length,
1102
+ addedAt: outcome.record.addedAt,
1103
+ local: isLocalSource(outcome.record.source)
1104
+ }
1105
+ };
1106
+ },
1107
+ removeMarketplace: async (name, lang) => {
1108
+ const outcome = await removeMarketplace(name, { statePath: deps.marketplacesPath });
1109
+ return outcome.ok ? { ok: true } : {
1110
+ ok: false,
1111
+ reason: "not-installed",
1112
+ detail: outcome.reason ?? t("plugin_control.market_missing", { name }, lang)
1113
+ };
1114
+ },
1054
1115
  previewUpdate: async (name, lang) => {
1055
1116
  const found = recordOf(name, lang);
1056
1117
  return found.ok ? flow.preview(found.record.source, replaceOpts(found.record), lang) : found;
@@ -1105,6 +1166,7 @@ function readOne(ref, diags) {
1105
1166
  for (const issue of parsed.issues) {
1106
1167
  diags.warn(`${MODULE} ${issue.path}`, issue.message, "mcp-config-invalid");
1107
1168
  }
1169
+ const root = dirname(ref.path);
1108
1170
  const servers = [];
1109
1171
  for (const server of parsed.servers) {
1110
1172
  if (!AGENT_ROLE_NAME_PATTERN.test(server.name)) {
@@ -1115,10 +1177,21 @@ function readOne(ref, diags) {
1115
1177
  );
1116
1178
  continue;
1117
1179
  }
1118
- servers.push({ ...server, name: `${ref.plugin}__${server.name}` });
1180
+ servers.push(withPluginRoot({ ...server, name: `${ref.plugin}__${server.name}` }, root));
1119
1181
  }
1120
1182
  return servers;
1121
1183
  }
1184
+ function withPluginRoot(server, root) {
1185
+ const out = { ...server };
1186
+ if (out.command !== void 0) out.command = substitutePluginRoot(out.command, root);
1187
+ if (out.args !== void 0) out.args = out.args.map((a) => substitutePluginRoot(a, root));
1188
+ if (out.env !== void 0) {
1189
+ out.env = Object.fromEntries(
1190
+ Object.entries(out.env).map(([k, v]) => [k, substitutePluginRoot(v, root)])
1191
+ );
1192
+ }
1193
+ return out;
1194
+ }
1122
1195
  function reason(err) {
1123
1196
  return err instanceof Error ? err.message : String(err);
1124
1197
  }
@@ -5268,6 +5341,10 @@ async function assembleRuntime(opts, opened, processes) {
5268
5341
  }));
5269
5342
  },
5270
5343
  usageScope: budgetWiring.budgetStore ? "session" : "run",
5344
+ sessionUsage: (sessionId2) => {
5345
+ const saved = budgetWiring.budgetStore?.load(sessionId2);
5346
+ return saved ? persistedUsage(saved) : void 0;
5347
+ },
5271
5348
  compression: compressionLine(config, services),
5272
5349
  get providerInfo() {
5273
5350
  return provider ? { provider: provider.getProviderType(), apiKeyHint: provider.getApiKeyHint() } : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epoch-agent/runtime",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "epoch-agent 组装层:把 core / plugins / MCP 装配成可运行的 agent",
6
6
  "repository": {
@@ -22,14 +22,14 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@epoch-agent/core": "0.3.1",
26
- "@epoch-agent/plugin-file": "^0.3.1",
27
- "@epoch-agent/infra": "0.3.1",
28
- "@epoch-agent/plugin-mcp": "0.3.1",
29
- "@epoch-agent/plugin-lsp": "0.3.1",
30
- "@epoch-agent/protocol": "0.3.1",
31
- "@epoch-agent/plugin-web": "^0.3.1",
32
- "@epoch-agent/plugin-terminal": "^0.3.1"
25
+ "@epoch-agent/core": "0.4.0",
26
+ "@epoch-agent/infra": "0.4.0",
27
+ "@epoch-agent/plugin-web": "^0.4.0",
28
+ "@epoch-agent/plugin-lsp": "0.4.0",
29
+ "@epoch-agent/plugin-mcp": "0.4.0",
30
+ "@epoch-agent/plugin-terminal": "^0.4.0",
31
+ "@epoch-agent/protocol": "0.4.0",
32
+ "@epoch-agent/plugin-file": "^0.4.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@opentelemetry/api": "^1.9.0",