@wingsky-1/dsh-worktree-sidebar 0.2.5
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/LICENSE +21 -0
- package/README.en.md +186 -0
- package/README.md +190 -0
- package/cordis.patch.yml +9 -0
- package/lib/client/bindings.d.ts +16 -0
- package/lib/client/index.d.ts +23 -0
- package/lib/client/inject.d.ts +21 -0
- package/lib/client/shared/ports.d.ts +141 -0
- package/lib/client/source.d.ts +22 -0
- package/lib/client/takeover.d.ts +51 -0
- package/lib/client.js +394 -0
- package/lib/index.d.ts +28 -0
- package/lib/index.js +1614 -0
- package/lib/server/api/deps.d.ts +35 -0
- package/lib/server/api/impl/handlers/index.d.ts +24 -0
- package/lib/server/api/impl/route/index.d.ts +25 -0
- package/lib/server/api/impl/service/index.d.ts +31 -0
- package/lib/server/api/interface.d.ts +12 -0
- package/lib/server/binding/deps.d.ts +9 -0
- package/lib/server/binding/impl/model/index.d.ts +16 -0
- package/lib/server/binding/impl/model/type.d.ts +37 -0
- package/lib/server/binding/impl/service/index.d.ts +67 -0
- package/lib/server/binding/impl/store/index.d.ts +6 -0
- package/lib/server/binding/interface.d.ts +24 -0
- package/lib/server/git/deps.d.ts +59 -0
- package/lib/server/git/impl/exec/index.d.ts +3 -0
- package/lib/server/git/impl/inspect/index.d.ts +47 -0
- package/lib/server/git/impl/service/index.d.ts +70 -0
- package/lib/server/git/interface.d.ts +41 -0
- package/lib/server/host/agents.d.ts +39 -0
- package/lib/server/host/sessions.d.ts +41 -0
- package/lib/server/host/typert.d.ts +11 -0
- package/lib/server/scope/deps.d.ts +110 -0
- package/lib/server/scope/impl/inherit/index.d.ts +25 -0
- package/lib/server/scope/impl/own/index.d.ts +21 -0
- package/lib/server/scope/impl/resolve/index.d.ts +40 -0
- package/lib/server/scope/impl/service/index.d.ts +115 -0
- package/lib/server/scope/interface.d.ts +40 -0
- package/lib/server/shared/file-io.d.ts +19 -0
- package/lib/server/shared/interface.d.ts +11 -0
- package/lib/server/shared/paths.d.ts +2 -0
- package/lib/server/shared/type.d.ts +6 -0
- package/lib/server/tools/deps.d.ts +52 -0
- package/lib/server/tools/impl/bind/index.d.ts +62 -0
- package/lib/server/tools/impl/create/index.d.ts +4 -0
- package/lib/server/tools/impl/protocol/index.d.ts +36 -0
- package/lib/server/tools/impl/register/index.d.ts +4 -0
- package/lib/server/tools/impl/remove/index.d.ts +12 -0
- package/lib/server/tools/impl/service/index.d.ts +28 -0
- package/lib/server/tools/impl/session/index.d.ts +18 -0
- package/lib/server/tools/interface.d.ts +14 -0
- package/lib/shared/contract.d.ts +26 -0
- package/lib/shared/interface.d.ts +8 -0
- package/package.json +103 -0
- package/shared/client/ensure-style.d.ts +21 -0
- package/shared/client/i18n.d.ts +15 -0
- package/shared/dsh-home.d.ts +15 -0
- package/shared/host-utils.d.ts +65 -0
- package/shared/loopback.d.ts +21 -0
- package/shared/settings-namespace.d.ts +46 -0
- package/shared/sse-hub.d.ts +76 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scope 域依赖声明。这里全部是本插件自己的窄类型——官方 typert 的类型体操留在组合根的适配层里,
|
|
3
|
+
* 于是本域可以完全脱离 cordis 与官方类型被单测驱动。
|
|
4
|
+
*/
|
|
5
|
+
import type * as bindingApi from "../binding/interface.js";
|
|
6
|
+
import type * as gitApi from "../git/interface.js";
|
|
7
|
+
import type { LoggerPort } from "../shared/interface.js";
|
|
8
|
+
/** 与官方同形的文件根解析结果。 */
|
|
9
|
+
export interface FileScope {
|
|
10
|
+
readonly sessionId: string;
|
|
11
|
+
readonly workspaceRoot: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* binding 域给本域的能力面。**含 `drop`**:检测到失效绑定时要摘掉它,
|
|
15
|
+
* 好让客户端的 revision 缓存随之失效(见 G5 与 `impl/resolve` 的注释)。
|
|
16
|
+
*/
|
|
17
|
+
export type BindingPort = Pick<typeof bindingApi, "get" | "drop">;
|
|
18
|
+
/** git 域给本域的能力面:只要一个归属判定。 */
|
|
19
|
+
type GitPort = Pick<typeof gitApi, "belongsTo">;
|
|
20
|
+
/** typert 查找表的一个描述符。`resolve` 是「当前生效的那一个」。 */
|
|
21
|
+
export interface LookupDescriptorPort {
|
|
22
|
+
readonly resolve: (sessionId: string) => Promise<FileScope | undefined>;
|
|
23
|
+
}
|
|
24
|
+
/** typert 查找表。键名由本域持有,官方类型只在组合根出现。 */
|
|
25
|
+
export interface TypertPort {
|
|
26
|
+
/**
|
|
27
|
+
* 读**当前生效**的描述符;provider 尚未注册时返回 undefined。
|
|
28
|
+
*
|
|
29
|
+
* 调用时机是硬约束:必须在 `configure` **之前**。configure 之后 `get` 回的是我们自己的包装,
|
|
30
|
+
* 官方 resolve 从此不可达——晚一步读,捕获到的就是自己,委托会变成无限递归。
|
|
31
|
+
*/
|
|
32
|
+
current(): LookupDescriptorPort | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* 订阅这张查找表的变化(该键的 provider 注册与撤销都会触发)。
|
|
35
|
+
*
|
|
36
|
+
* provider 是**别人**注册的:官方 dsh-api-workspace-files 在自己的 apply 期才 `register`,
|
|
37
|
+
* 谁先谁后由宿主启动序决定。所以本域不能只在 install 那一刻读一次——读不到就等这条通知。
|
|
38
|
+
*/
|
|
39
|
+
subscribe(listener: () => void): () => void;
|
|
40
|
+
/**
|
|
41
|
+
* 注册我们的解析器。
|
|
42
|
+
* @throws 当该键已经被别人 configure 过时(第三方接管是全局唯一的,这里只捕获、不抢)。
|
|
43
|
+
*/
|
|
44
|
+
configure(resolver: (sessionId: string) => Promise<FileScope | undefined>): () => void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 会话链只读面:本域只要「父会话是谁」这一个事实,但它有**两个来源**。
|
|
48
|
+
*
|
|
49
|
+
* 子 agent 的会话与用户 fork 的会话都是**独立会话**——创建时只把父的 cwd 拷进 header
|
|
50
|
+
* (dsh-subagent 建子会话见 dsh-subagent/lib/index.js:504-510;用户 fork 走的是同一条 header 判据
|
|
51
|
+
* `parentSession`),所以「子 agent / fork 跟着父会话的 worktree」不会自动发生:
|
|
52
|
+
* 绑定记在父会话 id 上,它们自己那条永远是空的,不问父链就永远看不到。本域因此不区分两者。
|
|
53
|
+
*
|
|
54
|
+
* 而父链只在**活会话**的 header 上随时可读:官方 `ctx.sessions.get` 是 live-only
|
|
55
|
+
* (`dsh-session/lib/index.js:1550-1557`)。UI 里能点选的子会话(以及已结束的 fork)恰恰是
|
|
56
|
+
* **已结束**的那些,所以还要一条持久读面。两者的分工必须显式——用 `undefined` 同时表示「没有父」和「不在册」时,
|
|
57
|
+
* 每个普通会话都会去问一次持久面,把一次确定的「到顶」变成请求路径上的额外 IO。
|
|
58
|
+
*/
|
|
59
|
+
export type LiveParent = {
|
|
60
|
+
readonly kind: "parent";
|
|
61
|
+
readonly id: string;
|
|
62
|
+
} | {
|
|
63
|
+
readonly kind: "root";
|
|
64
|
+
} | {
|
|
65
|
+
readonly kind: "not-live";
|
|
66
|
+
};
|
|
67
|
+
export interface SessionChainPort {
|
|
68
|
+
/** 活会话的父链读数;不在册时回 `not-live`,由本域决定要不要回落持久面。 */
|
|
69
|
+
liveParentOf(sessionId: string): LiveParent;
|
|
70
|
+
/**
|
|
71
|
+
* 已结束会话的父 id(持久 header)。取不到回 undefined。
|
|
72
|
+
* 后端出错(找不到 / 父链成环 / 持久化失败)**可以抛**,由本域收口成「到顶」。
|
|
73
|
+
*/
|
|
74
|
+
storedParentOf(sessionId: string): Promise<string | undefined>;
|
|
75
|
+
/**
|
|
76
|
+
* 活会话的身份;不在册时回 undefined。
|
|
77
|
+
*
|
|
78
|
+
* 会话身份存在的唯一理由是 id **不是**跨进程稳定键:官方 id 是实例字段计数器
|
|
79
|
+
* (`dsh-session/lib/index.js` 的 `counter = 0` 与 `session-${++this.counter}`),
|
|
80
|
+
* 只防同进程冲突,重启后新会话会重新拿到 `session-1`。没有身份核对,上一进程遗留的登记
|
|
81
|
+
* 会被一个全新的会话静默继承——那正是本插件最想避免的「看错地方」。
|
|
82
|
+
*
|
|
83
|
+
* 与父链一样**分成两个来源**:合起来变成一个 `identityOf` 会把「活 header 一下就答了」
|
|
84
|
+
* 与「要读持久面」混成同一次记账,health 的成本读数就不再是它字面上的意思。
|
|
85
|
+
*/
|
|
86
|
+
liveIdentityOf(sessionId: string): SessionIdentity | undefined;
|
|
87
|
+
/** 已结束会话的身份(持久 header)。取不到回 undefined;后端出错**可以抛**,由调用方收口。 */
|
|
88
|
+
storedIdentityOf(sessionId: string): Promise<SessionIdentity | undefined>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 会话身份里唯一可作凭据的那一项。`createdAt` 来自会话 header(官方类型里是必填的 epoch 毫秒),
|
|
92
|
+
* 活会话与已结束会话读到的都是同一个值,因此「真恢复的会话」仍然对得上。
|
|
93
|
+
*/
|
|
94
|
+
export interface SessionIdentity {
|
|
95
|
+
readonly createdAt: number;
|
|
96
|
+
}
|
|
97
|
+
export interface ScopeDeps {
|
|
98
|
+
readonly logger: LoggerPort;
|
|
99
|
+
readonly binding: BindingPort;
|
|
100
|
+
readonly git: GitPort;
|
|
101
|
+
readonly typert: TypertPort;
|
|
102
|
+
readonly sessions: SessionChainPort;
|
|
103
|
+
/**
|
|
104
|
+
* 目录存在性判定。缺省直连 fs(`impl/resolve` 的 `directoryExists`)。
|
|
105
|
+
* 注入点存在的理由不是「方便测试」,而是这条判据的两条分支(目录消失 / 读不了)在真实权限下无法稳定构造,
|
|
106
|
+
* 而它们的正确性直接决定用户会不会被永久摘掉绑定。
|
|
107
|
+
*/
|
|
108
|
+
readonly existsDirectory?: (path: string) => boolean;
|
|
109
|
+
}
|
|
110
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 继承父会话的登记:本会话没登记时沿父链向上找。
|
|
3
|
+
*
|
|
4
|
+
* 子会话是**独立会话**——dsh-subagent 建它时只把父的 cwd 拷进子 header
|
|
5
|
+
* (dsh-subagent/lib/index.js:504-510),而绑定记在父会话 id 上(工具是从会话上下文取 id 写登记的)。
|
|
6
|
+
* 不问父链,子 agent 的文件根就永远是它自己的 cwd。
|
|
7
|
+
*
|
|
8
|
+
* 用户 fork 出来的会话走的是同一条判据(header.parentSession),本域**刻意不区分** fork 与子 agent:
|
|
9
|
+
* fork 的 header 同样拷了父的 cwd,视图根跟着一起继承才与「文件根是会话 cwd 的改写」自洽。
|
|
10
|
+
*
|
|
11
|
+
* 三条边界是硬的:到顶即停(没有父就回未绑定)、父链成环时不转圈(错数据不该把请求拖死)、
|
|
12
|
+
* 父会话的登记失效时按它自己的自愈逻辑摘掉并继续向上(最终仍是未命中,也就是回退自己的 cwd)。
|
|
13
|
+
*/
|
|
14
|
+
import type { BindingRecord } from "../../../binding/interface.js";
|
|
15
|
+
import type { ScopeDeps } from "../../deps.js";
|
|
16
|
+
/**
|
|
17
|
+
* 沿父链找到的第一个有效登记,连同**持有它的那个会话 id**(不是直接父)。
|
|
18
|
+
*
|
|
19
|
+
* `ownerSessionId` 是工具面唯一能给出的「去哪个会话解绑」的答案,所以它必须是持有登记的那个祖先,
|
|
20
|
+
* 而不是只上跳一步的父——多级 fork 链上这两者并不相同。
|
|
21
|
+
*/
|
|
22
|
+
export declare function inheritedOrigin(deps: ScopeDeps, sessionId: string): Promise<{
|
|
23
|
+
record: BindingRecord;
|
|
24
|
+
ownerSessionId: string;
|
|
25
|
+
} | undefined>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BindingRecord } from "../../../binding/interface.js";
|
|
2
|
+
import type { ScopeDeps } from "../../deps.js";
|
|
3
|
+
/** 存在性判定看到的形状。窄到只需要一个方法,测试才能用一个字面量替身驱动。 */
|
|
4
|
+
interface StatLike {
|
|
5
|
+
isDirectory(): boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 目录是否真的在。只有 ENOENT/ENOTDIR 才算「不在」;其它错误(EACCES、EIO)按存在处理。
|
|
9
|
+
*
|
|
10
|
+
* `stat` 是可注入的:这段「读不了不等于不存在」的语义正是最容易写错、也最不该靠真实权限去构造
|
|
11
|
+
* 测试环境的地方(换成 `return false` 会把一次权限抖动变成永久摘掉用户的绑定)。
|
|
12
|
+
*/
|
|
13
|
+
export declare function directoryExists(path: string, stat?: (p: string) => StatLike | undefined): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* 只看本会话自己的登记:没有、或已失效(目录没了 / 已不是该仓库的 worktree)都回 undefined。
|
|
16
|
+
*
|
|
17
|
+
* 返回整条记录而不是根:调用方(解析器与工具面)还要用到根之外的字段(分支名、仓库根),
|
|
18
|
+
* 只回一个字符串会让它们各自再查一遍表。
|
|
19
|
+
*/
|
|
20
|
+
export declare function ownRecord(deps: ScopeDeps, sessionId: string): Promise<BindingRecord | undefined>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把「绑定」翻译成「生效的文件根」。解析器与浏览器路由**共用**这一个函数,
|
|
3
|
+
* 两端因此不可能各说各话(降级预案 G7 的根治办法:不是让两边都算对,而是让只有一边在算)。
|
|
4
|
+
*
|
|
5
|
+
* 文件根的两个来源分别是 `impl/own`(本会话自己的登记)与 `impl/inherit`(沿父链继承),
|
|
6
|
+
* 本文件只负责把它们合成一个答案、并保证解析器**永不抛出**。
|
|
7
|
+
*
|
|
8
|
+
* `bindingOrigin` 是这条链条对外的**唯一事实源**:它不只回答「根在哪」,还回答「根是谁的」。
|
|
9
|
+
* 工具面要区分「本会话自己的登记」与「继承来的登记」,唯一正确的做法就是读它——让工具面
|
|
10
|
+
* 另算一遍继承,得到的就是本插件最该避免的「两个事实源各说一句话」。
|
|
11
|
+
*/
|
|
12
|
+
import type { BindingRecord } from "../../../binding/interface.js";
|
|
13
|
+
import type { FileScope, ScopeDeps } from "../../deps.js";
|
|
14
|
+
/**
|
|
15
|
+
* 绑定来源。用判别联合而不是「根 + 若干冗余字段」:`root` 恒等于 `record.worktreeRoot`,
|
|
16
|
+
* 存两份就多一个能漂移的地方;`kind` 也把「没有登记」时其余字段该取什么值这个问题整个消掉。
|
|
17
|
+
*/
|
|
18
|
+
export type WorktreeOrigin = {
|
|
19
|
+
readonly kind: "none";
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: "own";
|
|
22
|
+
readonly record: BindingRecord;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: "inherited";
|
|
25
|
+
readonly record: BindingRecord;
|
|
26
|
+
readonly ownerSessionId: string;
|
|
27
|
+
};
|
|
28
|
+
/** 来源对应的 worktree 根;`none`(该会话按官方语义走)回 null。 */
|
|
29
|
+
export declare function rootOf(origin: WorktreeOrigin): string | null;
|
|
30
|
+
/** 本会话的绑定来源:自己的登记优先,其次沿父链继承,都没有就是 none。 */
|
|
31
|
+
export declare function bindingOrigin(deps: ScopeDeps, sessionId: string): Promise<WorktreeOrigin>;
|
|
32
|
+
/** 当前生效的 worktree 根;null 表示该会话按官方语义(cwd)走。 */
|
|
33
|
+
export declare function effectiveWorktree(deps: ScopeDeps, sessionId: string): Promise<string | null>;
|
|
34
|
+
/**
|
|
35
|
+
* 解析器的入口:命中绑定就给 worktree,否则委托。
|
|
36
|
+
*
|
|
37
|
+
* **永不抛出**是这里的核心承诺:官方 gateway 把 resolver 的异常翻成 `gateway/lookup-failed`,
|
|
38
|
+
* 那是整条文件读取链路的硬失败,不是回退。所以 catch 必须在这里,而不是指望上游。
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveScope(deps: ScopeDeps, delegate: (sessionId: string) => Promise<FileScope | undefined>, sessionId: string): Promise<FileScope | undefined>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scope 域装配:接管 `workspaceFileScope` 的解析,命中绑定就把会话的文件根指到 worktree。
|
|
3
|
+
*
|
|
4
|
+
* 接管是**全局替换**(一个键只有一个解析器),所以本域的三条纪律是硬的:
|
|
5
|
+
* 1. 捕获委托对象必须在 configure 之前;
|
|
6
|
+
* 2. configure 抛错(别人已接管)就整片放弃,不抢;
|
|
7
|
+
* 3. 解析器永不抛出,任何异常都回落官方语义。
|
|
8
|
+
*
|
|
9
|
+
* **provider 由别人注册,我们等它**:官方 dsh-api-workspace-files 在自己的 apply 期才
|
|
10
|
+
* `register("workspaceFileScope", …)`,与本插件的装配先后由宿主启动序决定。所以 provider 不在时
|
|
11
|
+
* 本域既不自己复刻官方默认语义、也不失败,而是**订阅查找表、等它出现**(先订阅再重读一次:
|
|
12
|
+
* 只重读不订阅会漏掉两者之间注册的 provider,只订阅不重读会漏掉订阅之前就已注册的那一个)。
|
|
13
|
+
* 等待期不接管——文件根按官方语义走,这是**正常启动态**而不是降级。
|
|
14
|
+
*
|
|
15
|
+
* 状态(委托对象、装配入参、两个 disposer)住在实例里;域是**进程内单例**,第二次 `install` 由
|
|
16
|
+
* 状态守卫**显式抛错**(响亮失败优于静默共享/丢数据)。
|
|
17
|
+
*/
|
|
18
|
+
import type { ScopeDeps } from "../../deps.js";
|
|
19
|
+
import type { WorktreeOrigin } from "../resolve/index.js";
|
|
20
|
+
/**
|
|
21
|
+
* 接管状态。`waiting` 与 `abandoned` 都不接管,但成因不同,故分开报:
|
|
22
|
+
* 前者是「宿主还没把 provider 装上」(正常启动态,会自动收敛),后者是「这个键被别人占了」(终态)。
|
|
23
|
+
*/
|
|
24
|
+
export type TakeoverState = "idle" | "waiting" | "live" | "abandoned";
|
|
25
|
+
/** scope 域的服务面。 */
|
|
26
|
+
export interface ScopeApi {
|
|
27
|
+
/**
|
|
28
|
+
* 当前**生效**的 worktree 根;null 表示该会话按 cwd 走。
|
|
29
|
+
* 浏览器路由读它,所以它与解析器给出的答案是同一个(G7)。
|
|
30
|
+
*/
|
|
31
|
+
effectiveWorktree(sessionId: string): Promise<string | null>;
|
|
32
|
+
/**
|
|
33
|
+
* 该会话的**绑定来源**(自己的登记 / 继承来的登记 / 没有)。工具面读它,用来区分
|
|
34
|
+
* 「本会话自己的登记」与「继承自哪个会话」。
|
|
35
|
+
*
|
|
36
|
+
* 它与 `effectiveWorktree` 的**唯一差别是不设 takeover 门**(后者在 `state !== "live"` 时回 null)。
|
|
37
|
+
* 这是刻意的:工具面要的是「登记事实」,而「文件根有没有真的换根」是宿主启动期读数,
|
|
38
|
+
* 由 `/health` 的 `scopeTakeover` 报告。waiting / abandoned 期两者结论不同,不是 bug。
|
|
39
|
+
*/
|
|
40
|
+
worktreeOrigin(sessionId: string): Promise<WorktreeOrigin>;
|
|
41
|
+
/** 接管状态的诊断读数:只给 health 用,判定逻辑不看它。接管与否看它是否等于 `live`。 */
|
|
42
|
+
takeoverState(): TakeoverState;
|
|
43
|
+
/** 会话链持久面的读数:查了几次、坏了几次、最后一次为什么坏。同样只给 health 用。 */
|
|
44
|
+
chainDiagnostics(): ChainDiagnostics;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 会话链持久面的读数。
|
|
48
|
+
*
|
|
49
|
+
* 它存在的唯一理由是**那处收口是无声的**:持久面读不出来时本域按「到顶」处理(正确的行为),
|
|
50
|
+
* 于是功能悄悄降级成 live-only,而真机上插件的 `logger.warn` 不落盘(§19.5),
|
|
51
|
+
* 排查时既没有日志也没有别的痕迹。health 是唯一一条能落地的观测面。
|
|
52
|
+
*/
|
|
53
|
+
export interface ChainDiagnostics {
|
|
54
|
+
/** 持久面被查了几次:父链的「会话不在册」回落,以及每次会话身份核对。 */
|
|
55
|
+
readonly storedReads: number;
|
|
56
|
+
/** 其中抛错的次数——到顶收口就是在这里发生的。 */
|
|
57
|
+
readonly storedFailures: number;
|
|
58
|
+
/** 最后一次失败的原因;没有失败时缺席。 */
|
|
59
|
+
readonly lastFailure: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
/** `workspaceFileScope` 的接管者:唯一实例。 */
|
|
62
|
+
declare class ScopeService implements ScopeApi {
|
|
63
|
+
private state;
|
|
64
|
+
private deps;
|
|
65
|
+
/** 捕获到的委托对象。释放即丢掉,不留在实例上供下一次装配复用。 */
|
|
66
|
+
private delegate;
|
|
67
|
+
/** configure 交回的释放面:调用它官方默认解析即恢复。 */
|
|
68
|
+
private dispose;
|
|
69
|
+
/** 查找表订阅的退订面。等待期需要它,进入终态后它只是空转。 */
|
|
70
|
+
private unsubscribe;
|
|
71
|
+
/** 等待期只出一次声:通知可能来很多次,每次都报会把日志淹掉。 */
|
|
72
|
+
private warnedWaiting;
|
|
73
|
+
/** 持久面读数。跨调用存活,所以只能住在这里,不能住在 `inherit`(那是无状态函数)。 */
|
|
74
|
+
private chain;
|
|
75
|
+
/** 装配 scope 域。重复装配是编程错误,当场暴露。 */
|
|
76
|
+
install(deps: ScopeDeps): void;
|
|
77
|
+
/**
|
|
78
|
+
* 卸载:把 resolver 交还官方、退订、丢掉捕获到的委托与装配入参,复位状态。重复调用无害。
|
|
79
|
+
* 此后到达的解析请求回 undefined(gateway 走它自己的 lookup-not-found),不会拿旧 deps 继续服务。
|
|
80
|
+
*/
|
|
81
|
+
release(): void;
|
|
82
|
+
takeoverState(): TakeoverState;
|
|
83
|
+
/** 读数是快照:调用方拿不到本域内部那个对象,改不动它。 */
|
|
84
|
+
chainDiagnostics(): ChainDiagnostics;
|
|
85
|
+
/**
|
|
86
|
+
* 给会话链的持久面加一圈读数。失败**照原样抛回**,收口仍在调用方——
|
|
87
|
+
* 这里只负责记账,不改变任何判定。
|
|
88
|
+
*
|
|
89
|
+
* 身份核对也走同一条持久面,所以它同样计入读数:只统计父链会让「身份读不出来」这类
|
|
90
|
+
* 故障在 health 上完全不可见。
|
|
91
|
+
*/
|
|
92
|
+
private observed;
|
|
93
|
+
/** 记一次持久面读取:无论成败都算一次读,抛错另计一次失败并留下最后原因。 */
|
|
94
|
+
private counted;
|
|
95
|
+
effectiveWorktree(sessionId: string): Promise<string | null>;
|
|
96
|
+
/**
|
|
97
|
+
* 绑定来源。**不设 takeover 门**:登记是否存在与「文件根是否已经换成它」是两件事,
|
|
98
|
+
* 工具面(register / create / remove)要的是前者的完整答案——它据此决定能不能摘、
|
|
99
|
+
* 该不该报「继承自哪个会话」。把工具面也卡在 `live` 上,接管冲突时连一条已确认失效的
|
|
100
|
+
* 登记都清理不掉。
|
|
101
|
+
*/
|
|
102
|
+
worktreeOrigin(sessionId: string): Promise<WorktreeOrigin>;
|
|
103
|
+
/**
|
|
104
|
+
* 试一次接管。provider 没出现就退回等待态,等待由查找表的变更通知再次叫醒。
|
|
105
|
+
*/
|
|
106
|
+
private attempt;
|
|
107
|
+
/**
|
|
108
|
+
* 交给官方 typert 的解析器。委托对象与装配入参都按**调用当刻**取:释放之后这个闭包只剩
|
|
109
|
+
* undefined,官方拿到的是它自己的 lookup-not-found,而不是一份过期绑定。
|
|
110
|
+
*/
|
|
111
|
+
private resolve;
|
|
112
|
+
}
|
|
113
|
+
/** 本域唯一实例:类不外放,外面 `new` 不出第二份接管者。 */
|
|
114
|
+
export declare const scopeService: ScopeService;
|
|
115
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scope 域对外契约:接管 `workspaceFileScope` 的解析,命中绑定就把会话的文件根指到 worktree。
|
|
3
|
+
*
|
|
4
|
+
* 本文件只做收口——单例与它的 `ScopeApi` 形状的物理定义都在 `impl/service`(形状不从门面转出),
|
|
5
|
+
* 解析判定在 `impl/resolve`。单例本身不出这道门:它一旦被转出就成了
|
|
6
|
+
* 本域的第二张公开契约,调用方还能持有它、绕过释放。
|
|
7
|
+
*/
|
|
8
|
+
import type { ScopeDeps } from "./deps.js";
|
|
9
|
+
import type { WorktreeOrigin } from "./impl/resolve/index.js";
|
|
10
|
+
import { rootOf } from "./impl/resolve/index.js";
|
|
11
|
+
import { scopeService } from "./impl/service/index.js";
|
|
12
|
+
export { rootOf };
|
|
13
|
+
export type { WorktreeOrigin };
|
|
14
|
+
/** 装配 scope 域(组合根在 `apply` 期调用一次)。重复装配是编程错误,当场抛错。 */
|
|
15
|
+
export declare function installScope(deps: ScopeDeps): void;
|
|
16
|
+
/** 卸载 scope 域,与 `installScope` 配对:把 resolver 交还官方。此后能力面当场失败。 */
|
|
17
|
+
export declare function releaseScope(): void;
|
|
18
|
+
/** 当前**生效**的 worktree 根;null 表示该会话按 cwd 走。 */
|
|
19
|
+
export declare function effectiveWorktree(sessionId: string): Promise<string | null>;
|
|
20
|
+
/**
|
|
21
|
+
* 该会话的**绑定来源**(自己的登记 / 继承来的登记 / 没有)。
|
|
22
|
+
*
|
|
23
|
+
* 与 `effectiveWorktree` 的唯一差别是**不设 takeover 门**:工具面要的是「登记事实」,
|
|
24
|
+
* 而文件根有没有真的换成它由 `/health` 的 `scopeTakeover` 报告。waiting / abandoned 期
|
|
25
|
+
* 两者结论不同(工具面照旧给出登记,浏览器面仍按 cwd),这是刻意设计,不是 bug。
|
|
26
|
+
*/
|
|
27
|
+
export declare function worktreeOrigin(sessionId: string): Promise<WorktreeOrigin>;
|
|
28
|
+
/**
|
|
29
|
+
* 接管状态的诊断读数(`idle` / `waiting` / `live` / `abandoned`)。
|
|
30
|
+
*
|
|
31
|
+
* 只给 health 端点用:真实启动序里「provider 与插件谁先到」没有稳定保证,这个读数让
|
|
32
|
+
* 「文件根没换根」当场可分辨成「还没等到 provider」「被别人占了」「接管了但没命中绑定」三种。
|
|
33
|
+
*/
|
|
34
|
+
export declare function takeoverState(): ReturnType<typeof scopeService.takeoverState>;
|
|
35
|
+
/**
|
|
36
|
+
* 会话链持久面的读数(查了几次 / 坏了几次 / 最后一次的原因)。
|
|
37
|
+
*
|
|
38
|
+
* 只给 health 端点用:持久面读失败被收口成「到顶」是一处**静默降级**,这条读数是它唯一落地的痕迹。
|
|
39
|
+
*/
|
|
40
|
+
export declare function chainDiagnostics(): ReturnType<typeof scopeService.chainDiagnostics>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** 读取结果:「不存在」「不可读」「是目录」归为同一类——调用方对三者的处置都是回落空值。 */
|
|
2
|
+
type FileRead = {
|
|
3
|
+
readonly ok: true;
|
|
4
|
+
readonly text: string;
|
|
5
|
+
} | {
|
|
6
|
+
readonly ok: false;
|
|
7
|
+
};
|
|
8
|
+
/** 写入结果。 `reason` 是给日志用的原因文本,不含路径之外的额外事实。 */
|
|
9
|
+
export type FileWrite = {
|
|
10
|
+
readonly ok: true;
|
|
11
|
+
} | {
|
|
12
|
+
readonly ok: false;
|
|
13
|
+
readonly reason: string;
|
|
14
|
+
};
|
|
15
|
+
/** 同步读全文。只给装配路径用:装配返回时必须已拿到最终值,否则会开一个「读面已可用、文件还没加载」的窗口。 */
|
|
16
|
+
export declare function readTextFileSync(file: string): FileRead;
|
|
17
|
+
/** 原子写全文:补齐父目录 → 写临时文件 → `rename` 覆盖。父目录在这里补齐,谁给出路径谁负责让它可写。 */
|
|
18
|
+
export declare function writeTextAtomic(file: string, text: string): Promise<FileWrite>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 共享层门面:包内跨域引用共享设施的唯一入口。
|
|
3
|
+
*
|
|
4
|
+
* 共享层是叶子——它不依赖任何域,域依赖它。收口到一处,「共享层提供了什么」
|
|
5
|
+
* 才有可被门禁校验的答案,而不是散在各域对若干实现文件的直引里
|
|
6
|
+
* (verify-dir-imports 的规则 1/2 判据)。
|
|
7
|
+
*/
|
|
8
|
+
export type { FileWrite } from "./file-io.js";
|
|
9
|
+
export { readTextFileSync, writeTextAtomic } from "./file-io.js";
|
|
10
|
+
export type { LoggerPort } from "./type.js";
|
|
11
|
+
export { bindingsFile } from "./paths.js";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** tools 域依赖声明:只声明「我需要外部什么」,声明面只有类型。 */
|
|
2
|
+
import type { ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
3
|
+
import type * as bindingApi from "../binding/interface.js";
|
|
4
|
+
import type * as gitApi from "../git/interface.js";
|
|
5
|
+
import type * as scopeApi from "../scope/interface.js";
|
|
6
|
+
import type { LoggerPort } from "../shared/interface.js";
|
|
7
|
+
/**
|
|
8
|
+
* binding 域给工具的能力面。**工具是唯一的写入口**——api 域拿不到这三个方法,
|
|
9
|
+
* 所以浏览器侧不存在任何写绑定的授权路径。
|
|
10
|
+
*/
|
|
11
|
+
export type BindingPort = Pick<typeof bindingApi, "get" | "put" | "drop">;
|
|
12
|
+
/** git 域给工具的能力面。只列本域真正要用的方法,域内不认识 git 的其余能力。 */
|
|
13
|
+
type GitPort = Pick<typeof gitApi, "commonDir" | "belongsTo" | "headBranch" | "checkRefFormat" | "resolveCommit" | "addWorktree" | "removeWorktree" | "listWorktrees">;
|
|
14
|
+
/**
|
|
15
|
+
* scope 域给工具的能力面。只要「绑定来源」这一个读数——工具据此区分「本会话自己的登记」与
|
|
16
|
+
* 「继承自哪个会话的登记」,这正是它必须与浏览器面同源的那件事。
|
|
17
|
+
*
|
|
18
|
+
* **刻意不取 `effectiveWorktree`**:那是浏览器面的生效根,带 takeover 门(provider 未就绪时回 null)。
|
|
19
|
+
* 工具面要的是稳定的登记事实,否则接管冲突期连一条已确认失效的登记都清理不掉。
|
|
20
|
+
*/
|
|
21
|
+
type ScopePort = Pick<typeof scopeApi, "worktreeOrigin">;
|
|
22
|
+
/** 一条 agent 的窄面。 */
|
|
23
|
+
export interface AgentFace {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
/**
|
|
26
|
+
* 会话工作目录。判定「是否在 git 仓库里」与解析相对路径都用它。
|
|
27
|
+
* 缺省时不注册工具——没有 cwd 就无法回答「这个会话在不在仓库里」,猜一个会给出错的工具。
|
|
28
|
+
*/
|
|
29
|
+
readonly cwd: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* agent 注册面。三个方法各自对应一件事,域内不出现 `ctx`:
|
|
33
|
+
* 谁订事件、从哪枚举、怎么把工具装进某个 agent 的作用域,都是组合根的知识。
|
|
34
|
+
*/
|
|
35
|
+
export interface AgentPort {
|
|
36
|
+
/** 订阅「新 agent 发布」。返回退订函数。 */
|
|
37
|
+
subscribe(handler: (agent: AgentFace) => void): () => void;
|
|
38
|
+
/** 当前存活的所有 agent 快照(含子 agent;插件加载前就存在的那些都在里面)。 */
|
|
39
|
+
list(): readonly AgentFace[];
|
|
40
|
+
/** 把工具装进该 agent 的作用域,返回释放函数。 */
|
|
41
|
+
publish(agent: AgentFace, definitions: readonly ToolDefinition[]): () => void;
|
|
42
|
+
}
|
|
43
|
+
export interface ToolsDeps {
|
|
44
|
+
readonly logger: LoggerPort;
|
|
45
|
+
readonly binding: BindingPort;
|
|
46
|
+
readonly git: GitPort;
|
|
47
|
+
readonly scope: ScopePort;
|
|
48
|
+
readonly agents: AgentPort;
|
|
49
|
+
/** 登记时间戳。由组合根注入,与 binding 域共用同一个时钟实现。 */
|
|
50
|
+
readonly now: () => string;
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { WorktreeOrigin } from "../../../scope/interface.js";
|
|
2
|
+
import type { AgentFace, ToolsDeps } from "../../deps.js";
|
|
3
|
+
import type { SessionFace } from "../session/index.js";
|
|
4
|
+
import type { ToolResultValue } from "../protocol/index.js";
|
|
5
|
+
/** 当前的绑定状态(三个工具的返回信封都要它)。 */
|
|
6
|
+
export interface BindingState {
|
|
7
|
+
readonly bound: boolean;
|
|
8
|
+
readonly worktree: string;
|
|
9
|
+
readonly branch: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 还没解析出来源时用的中性读数。早退路径(缺会话、缺 cwd、不在仓库、缺必填参数)用它:
|
|
13
|
+
* 为一句「没有 cwd」白付一趟 git 与持久面读不值得,那些失败的成因也和「绑在哪」无关。
|
|
14
|
+
*/
|
|
15
|
+
export declare const NO_ORIGIN: WorktreeOrigin;
|
|
16
|
+
/**
|
|
17
|
+
* 读来源的结果:`problem` 非空时 `origin` 是中性读数,调用方应直接把 `problem` 当作失败原因报出去。
|
|
18
|
+
*/
|
|
19
|
+
export interface OriginRead {
|
|
20
|
+
readonly origin: WorktreeOrigin;
|
|
21
|
+
readonly problem: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 读某个会话的绑定来源(自己的登记 / 继承来的登记 / 没有)。
|
|
25
|
+
*
|
|
26
|
+
* 解析异常在这里收口。scope 域未装配或已释放时它抛的是**插件自己的装配状态**(内部文案是
|
|
27
|
+
* 「scope 域尚未装配」):那句话与调用者正在做的事毫无关系,透给模型只会让它照着一句无法行动的
|
|
28
|
+
* 话去猜。所以对外只说「读不到绑定状态、稍后重试」,原因留给 logger —— 现场痕迹不丢,
|
|
29
|
+
* 模型也不会拿到内部装配文案。
|
|
30
|
+
*
|
|
31
|
+
* 不静默翻成「没有绑定」:那会把一次真实故障说成事实,而「看错地方」正是本插件最该避免的事。
|
|
32
|
+
*/
|
|
33
|
+
export declare function readOrigin(deps: ToolsDeps, sessionId: string): Promise<OriginRead>;
|
|
34
|
+
/** 把来源翻成信封里的状态读数:继承态下 worktree / branch 取的是继承到的那条登记。 */
|
|
35
|
+
export declare function stateOf(origin: WorktreeOrigin): BindingState;
|
|
36
|
+
/**
|
|
37
|
+
* 组装结果信封。**继承来源说明只在这里加一次**:三个工具、每条成功与失败路径都经过它。
|
|
38
|
+
* 各调用点自己拼就一定会漏——漏掉之后模型读到的是「本会话没有绑定」,它会去重建一条
|
|
39
|
+
* 本已存在的登记(#847 的现场就是这样)。
|
|
40
|
+
*/
|
|
41
|
+
export declare function resultOf(origin: WorktreeOrigin, ok: boolean, detail: string): ToolResultValue;
|
|
42
|
+
/**
|
|
43
|
+
* 把调用方给的路径解析成绝对路径。相对路径按**会话 cwd** 解析(那是调用者心里的基准),不是进程 cwd。
|
|
44
|
+
*
|
|
45
|
+
* 参数是 `string` 而不是 `string | undefined`:两个调用方都在更早的分支上把「没有 cwd」判失败并返回了,
|
|
46
|
+
* 这里再留一条 undefined 分支就是一段**不可达代码**(覆盖率替一段永远到不了的代码记账没有意义)。
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveTarget(cwd: string, raw: string): string;
|
|
49
|
+
/** 是目录才继续。不是就给一句能照着做的失败。 */
|
|
50
|
+
export declare function directoryProblem(target: string): string | undefined;
|
|
51
|
+
/** 会话所属仓库的可用 worktree 清单,用于失败时的可操作提示。 */
|
|
52
|
+
export declare function availableWorktrees(deps: ToolsDeps, repo: string): Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* 落一条绑定。归属校验在这里做唯一一次——三个工具都经过它,
|
|
55
|
+
* 所以「只允许绑定同一仓库的 worktree」这条不变量不会因为某条路径漏写而破。
|
|
56
|
+
*
|
|
57
|
+
* `before` 由调用方先解析好再递进来:调用方在更早的分支上就要用它(那几处失败路径也要报现状),
|
|
58
|
+
* 在这里重算一遍等于白付一趟 git 与持久面读。
|
|
59
|
+
*/
|
|
60
|
+
export declare function bindWorktree(deps: ToolsDeps, session: SessionFace, repo: string, target: string, registeredAt: string, before: WorktreeOrigin): Promise<ToolResultValue>;
|
|
61
|
+
/** 会话是否在一个 git 仓库里。不在就没有工具可言。 */
|
|
62
|
+
export declare function repoOf(deps: ToolsDeps, agent: AgentFace): Promise<string | undefined>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具的输入读取与结果信封。
|
|
3
|
+
*
|
|
4
|
+
* 三个工具共用同一个结果形状,是为了让「当前指向哪个 worktree」这件事永远出现在返回文本里:
|
|
5
|
+
* 失败路径也要说清现状,否则模型在错误之后只知道「失败了」,不知道自己现在到底绑在哪。
|
|
6
|
+
*
|
|
7
|
+
* 输出 schema 只用 enforced subset 支持的键(单一 `type`、无 type 数组),
|
|
8
|
+
* 「没有值」一律用空串表达而不是 null——该子集不接受可空类型。
|
|
9
|
+
*/
|
|
10
|
+
import type { JsonSchemaNode } from "@deepseek-ai/dsh-tools";
|
|
11
|
+
/** 三个工具的统一返回值。 */
|
|
12
|
+
export interface ToolResultValue {
|
|
13
|
+
/** 本次请求的操作是否成功。 */
|
|
14
|
+
readonly ok: boolean;
|
|
15
|
+
/** 调用之后该会话是否仍有绑定(含沿父链继承来的那条)。 */
|
|
16
|
+
readonly bound: boolean;
|
|
17
|
+
/** 绑定指向的 worktree 绝对路径;`bound` 为 false 时是空串。 */
|
|
18
|
+
readonly worktree: string;
|
|
19
|
+
/** 绑定的分支名;无绑定或 detached 时是空串。 */
|
|
20
|
+
readonly branch: string;
|
|
21
|
+
/** 面向模型的结果说明:成功是事实陈述,失败是原因加可操作的下一步。 */
|
|
22
|
+
readonly detail: string;
|
|
23
|
+
}
|
|
24
|
+
/** 结果信封的 schema(`output.schema`)。 */
|
|
25
|
+
export declare const RESULT_SCHEMA: JsonSchemaNode;
|
|
26
|
+
/**
|
|
27
|
+
* 结果渲染。**状态行永远在前**:模型据此知道调用后的真实状态,不必再调一次工具确认。
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderResult(value: ToolResultValue): Array<{
|
|
30
|
+
type: "text";
|
|
31
|
+
text: string;
|
|
32
|
+
}>;
|
|
33
|
+
/** 读一个非空字符串参数。空白串视同缺席(模型偶尔会给空串表示「没填」)。 */
|
|
34
|
+
export declare function argString(args: unknown, key: string): string | undefined;
|
|
35
|
+
/** 读一个布尔参数。只有显式 `true` 为真——缺席、非布尔、字符串 "true" 都不算。 */
|
|
36
|
+
export declare function argBool(args: unknown, key: string): boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ws_worktree_remove`:摘掉登记;只有显式 `removeDirectory` 才连带 `git worktree remove`。
|
|
3
|
+
*
|
|
4
|
+
* 默认不删目录是刻意的:删除可能丢掉未提交改动,而「我只是想换个根」与「我要销毁这个工作区」
|
|
5
|
+
* 是两件事,不该由同一个默认值承担。
|
|
6
|
+
*
|
|
7
|
+
* **继承态不摘任何东西**:本会话没有自己的登记时,右栏的根属于父会话——摘它是改别人的状态,
|
|
8
|
+
* 删目录更是销毁别人的工作区。这一态只如实说明并给出路。
|
|
9
|
+
*/
|
|
10
|
+
import type { ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
11
|
+
import type { ToolsDeps } from "../../deps.js";
|
|
12
|
+
export declare function buildRemoveTool(deps: ToolsDeps): ToolDefinition;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ToolsDeps } from "../../deps.js";
|
|
2
|
+
/** 三个工具的注册者:唯一实例。 */
|
|
3
|
+
declare class ToolsService {
|
|
4
|
+
/** 是否已装配;单例实例重复装配是编程错误,当场暴露。 */
|
|
5
|
+
private installed;
|
|
6
|
+
private deps;
|
|
7
|
+
/** 每个 agent 的工具摘除器。释放时逐个调用并清空,下一次装配才装得进同一个 agent。 */
|
|
8
|
+
private readonly perAgent;
|
|
9
|
+
private unsubscribe;
|
|
10
|
+
private chain;
|
|
11
|
+
/**
|
|
12
|
+
* 装配代数。在飞的异步判定跨过一次 release 就作废——否则它会把上一代的 deps 装进新一代,
|
|
13
|
+
* 而那种串味的症状是「工具装了但绑的是上一个装配体的表」。
|
|
14
|
+
*/
|
|
15
|
+
private generation;
|
|
16
|
+
/** 装配工具域。重复装配是编程错误,当场暴露。 */
|
|
17
|
+
install(deps: ToolsDeps): void;
|
|
18
|
+
/**
|
|
19
|
+
* 卸载:退订、逐个摘掉每个 agent 的工具、丢掉映射与在飞的异步链,复位装配标记。重复调用无害。
|
|
20
|
+
* 映射必须清空——留着它下一次装配会把同一个 agent 当成「已装过」直接跳过,工具永远装不上。
|
|
21
|
+
*/
|
|
22
|
+
release(): void;
|
|
23
|
+
/** 判定一个 agent 是否该装工具,该装就装。同一个 agent 只处理一次。 */
|
|
24
|
+
private consider;
|
|
25
|
+
}
|
|
26
|
+
/** 本域唯一实例:类不外放,外面 `new` 不出第二份注册表。 */
|
|
27
|
+
export declare const toolsService: ToolsService;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 执行期身份解析:从 `exec` 取会话身份与工作目录。
|
|
3
|
+
*
|
|
4
|
+
* `exec.agent` 在类型上是可选的(子派发、非 agent 调用都没有它),所以这里返回
|
|
5
|
+
* `undefined` 而不是抛错;调用方必须把它翻成一句明确的失败,而不是回落到 `process.cwd()`
|
|
6
|
+
* ——后者会把绑定挂到一个与调用者无关的目录上。
|
|
7
|
+
*/
|
|
8
|
+
export interface SessionFace {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly cwd: string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* 会话 header 的创建时间。登记要把它一起落盘:id 是进程内计数器,重启后会被新会话复用,
|
|
13
|
+
* 没有它就分不清「同一个会话」与「另一个会话拿到了同一个 id」。
|
|
14
|
+
*/
|
|
15
|
+
readonly createdAt: number | undefined;
|
|
16
|
+
}
|
|
17
|
+
/** 解析会话身份。形状不符即 undefined,不猜。 */
|
|
18
|
+
export declare function sessionOf(exec: unknown): SessionFace | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tools 域对外契约:三个 agent 工具的注册与释放。
|
|
3
|
+
*
|
|
4
|
+
* 工具按 **agent 作用域**注册(每个 agent 自己的 `ctx.tools`),只在会话位于 git 仓库里时才装;
|
|
5
|
+
* 会话不在仓库里就一个工具都不给——给了也只会每次都失败。
|
|
6
|
+
*
|
|
7
|
+
* 本文件只做收口:门面的物理定义在 `impl/service`。本域不对外提供能力(工具就是它的产物),
|
|
8
|
+
* 门面因此只有装配与释放。
|
|
9
|
+
*/
|
|
10
|
+
import type { ToolsDeps } from "./deps.js";
|
|
11
|
+
/** 装配工具域(组合根在 `apply` 期调用一次)。重复装配是编程错误,当场抛错。 */
|
|
12
|
+
export declare function installTools(deps: ToolsDeps): void;
|
|
13
|
+
/** 卸载工具域,与 `installTools` 配对:退订 + 摘掉每个 agent 的工具(重复调用无害)。 */
|
|
14
|
+
export declare function releaseTools(): void;
|