dsh-git-ui 0.1.0 → 0.1.2
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 +28 -0
- package/README.zh.md +27 -0
- package/lib/client.js +26 -26
- package/lib/client.js.map +4 -4
- package/lib/contracts/host-endpoints.d.ts +27 -0
- package/lib/host/index.d.ts +20 -12
- package/lib/host/index.js +59 -38
- package/lib/host/index.js.map +3 -3
- package/package.json +1 -1
- package/src/adapters/dsh/client-adapter.ts +137 -0
- package/src/adapters/dsh/types/cordis.d.ts +48 -0
- package/src/adapters/dsh/types/typert-protocol.d.ts +81 -0
- package/src/adapters/dsh/types/ui-primitives.d.ts +45 -0
- package/src/adapters/dsh/ui-primitives.ts +16 -0
- package/src/client/GitCenter.tsx +3 -1
- package/src/client/GitPill.tsx +5 -15
- package/src/client/controller.ts +5 -37
- package/src/client/index.ts +38 -135
- package/src/client/remote.ts +2 -2
- package/src/contracts/client-platform.ts +147 -0
- package/src/contracts/host-endpoints.ts +58 -0
- package/src/contracts/plugin-activation.ts +129 -0
- package/src/contracts/ui-context.tsx +28 -0
- package/src/contracts/ui-primitives.ts +48 -0
- package/src/host/index.ts +52 -57
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host 端点契约:纯业务逻辑,零框架依赖。
|
|
3
|
+
*
|
|
4
|
+
* 将 `snapshotForSession` / `runAction` / `runQuery` 聚合为统一的
|
|
5
|
+
* `HostEndpoints` 接口——宿主适配层(Cordis/typert 或其他框架)只需调用
|
|
6
|
+
* `createHostEndpoints(deps, config)` 即可获得全部端点方法,再以任何
|
|
7
|
+
* RPC 机制暴露。业务层对此一无所知。
|
|
8
|
+
*/
|
|
9
|
+
import { snapshotForSession, type GitStatusConfig, type SnapshotDeps } from '../host/core.ts'
|
|
10
|
+
import { runAction } from '../host/actions.ts'
|
|
11
|
+
import { runQuery } from '../host/queries.ts'
|
|
12
|
+
import type { GitActionRequest, GitActionResult, GitQueryRequest, GitQueryResponse, GitSnapshotRequest, GitSnapshotResult } from '../host/types.ts'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 业务端点集合:三个 RPC 方法的纯函数实现。
|
|
16
|
+
* 每个方法接收 wire 请求、可选取消信号,返回 wire 响应。
|
|
17
|
+
*/
|
|
18
|
+
export interface HostEndpoints {
|
|
19
|
+
snapshot(request: GitSnapshotRequest, signal?: AbortSignal): Promise<GitSnapshotResult>
|
|
20
|
+
run(request: GitActionRequest, signal?: AbortSignal): Promise<GitActionResult>
|
|
21
|
+
query(request: GitQueryRequest, signal?: AbortSignal): Promise<GitQueryResponse>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 构造业务端点实例。
|
|
26
|
+
*
|
|
27
|
+
* `deps` 携带全部宿主能力(子进程、会话查找、文件系统);`config` 携带
|
|
28
|
+
* 运行参数。两者均为结构化接口,与任何框架无关。返回的端点对象可直接
|
|
29
|
+
* 绑定到 RPC 装饰器、HTTP 路由、或测试桩。
|
|
30
|
+
*/
|
|
31
|
+
export function createHostEndpoints(deps: SnapshotDeps, config: GitStatusConfig): HostEndpoints {
|
|
32
|
+
return {
|
|
33
|
+
snapshot(request, signal) {
|
|
34
|
+
// 合并调用方信号与 deps 自带信号(deps.signal 来自 Cordis Remote 的
|
|
35
|
+
// 取消槽;此处 signal 来自适配层传入——两者取并集,任一触发即中止)。
|
|
36
|
+
const merged = mergeSignals(deps.signal, signal)
|
|
37
|
+
const effectiveDeps = merged === deps.signal ? deps : { ...deps, signal: merged }
|
|
38
|
+
return snapshotForSession(effectiveDeps, config, request.sessionId)
|
|
39
|
+
},
|
|
40
|
+
run(request, signal) {
|
|
41
|
+
const merged = mergeSignals(deps.signal, signal)
|
|
42
|
+
const effectiveDeps = merged === deps.signal ? deps : { ...deps, signal: merged }
|
|
43
|
+
return runAction(effectiveDeps, config, request)
|
|
44
|
+
},
|
|
45
|
+
query(request, signal) {
|
|
46
|
+
const merged = mergeSignals(deps.signal, signal)
|
|
47
|
+
const effectiveDeps = merged === deps.signal ? deps : { ...deps, signal: merged }
|
|
48
|
+
return runQuery(effectiveDeps, config, request)
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 合并两个可选信号:任一 undefined 取另一个,均存在则 AbortSignal.any。 */
|
|
54
|
+
function mergeSignals(a: AbortSignal | undefined, b: AbortSignal | undefined): AbortSignal | undefined {
|
|
55
|
+
if (a === undefined) return b
|
|
56
|
+
if (b === undefined) return a
|
|
57
|
+
return AbortSignal.any([a, b])
|
|
58
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 插件激活逻辑:纯业务编排,零框架依赖。
|
|
3
|
+
*
|
|
4
|
+
* 接收 `ClientPlatform` 接口和插件依赖,完成:
|
|
5
|
+
* 1. 注册国际化字典
|
|
6
|
+
* 2. 挂载 Remote 并获取 gitInfo 服务
|
|
7
|
+
* 3. 注册 header utility slot
|
|
8
|
+
* 4. 订阅连接重置事件
|
|
9
|
+
*
|
|
10
|
+
* 适配层(如 `adapters/dsh/client-adapter.ts`)负责将宿主 API 翻译为
|
|
11
|
+
* `ClientPlatform`,然后调用本函数。业务层对宿主一无所知。
|
|
12
|
+
*/
|
|
13
|
+
import type { ClientPlatform, SlotEntryDescriptor, GitInjected, GitRemoteLike } from './client-platform.ts'
|
|
14
|
+
import type { RemoteContribution } from './client-platform.ts'
|
|
15
|
+
import type { LocaleDicts } from './client-platform.ts'
|
|
16
|
+
import type { GitAction } from '../host/types.ts'
|
|
17
|
+
import type { GitQueryRequest, GitActionResult } from '../host/types.ts'
|
|
18
|
+
import type { GitQueryOutcome } from './client-platform.ts'
|
|
19
|
+
|
|
20
|
+
/** Slot 名称常量。 */
|
|
21
|
+
const HEADER_UTILITIES_SLOT = 'conversation.session.header.utilities'
|
|
22
|
+
|
|
23
|
+
/** 本插件的 slot 条目 ID。 */
|
|
24
|
+
const SLOT_ENTRY_ID = 'git'
|
|
25
|
+
|
|
26
|
+
/** 国际化命名空间。 */
|
|
27
|
+
const LOCALE_NS = 'git'
|
|
28
|
+
|
|
29
|
+
/** 控制器接口:插件激活逻辑依赖的控制器能力。 */
|
|
30
|
+
export interface ControllerLike {
|
|
31
|
+
subscribe(listener: () => void): () => void
|
|
32
|
+
getSnapshot(): import('./client-platform.ts').GitView
|
|
33
|
+
refresh(): Promise<void>
|
|
34
|
+
resync(): void
|
|
35
|
+
run(action: GitAction): Promise<GitActionResult>
|
|
36
|
+
query(query: GitQueryRequest['query']): Promise<GitQueryOutcome>
|
|
37
|
+
dispose(): void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 插件依赖:由调用方提供的具体实现。 */
|
|
41
|
+
export interface PluginDependencies {
|
|
42
|
+
/** Remote 贡献配置 */
|
|
43
|
+
remoteContribution: RemoteContribution
|
|
44
|
+
/** 国际化字典 */
|
|
45
|
+
locales: LocaleDicts
|
|
46
|
+
/** 控制器构造函数 */
|
|
47
|
+
createController: (remote: GitRemoteLike, sessionId: string) => ControllerLike
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 激活插件。
|
|
52
|
+
*
|
|
53
|
+
* @param platform 宿主平台能力
|
|
54
|
+
* @param deps 插件依赖(由调用方提供)
|
|
55
|
+
* @param component 要注册的 UI 组件(已包裹 UI 基础组件上下文)
|
|
56
|
+
*/
|
|
57
|
+
export async function activatePlugin(
|
|
58
|
+
platform: ClientPlatform,
|
|
59
|
+
deps: PluginDependencies,
|
|
60
|
+
component: unknown,
|
|
61
|
+
): Promise<void> {
|
|
62
|
+
// 1. 注册国际化字典
|
|
63
|
+
platform.effect(
|
|
64
|
+
() => platform.registerLocale(LOCALE_NS, deps.locales),
|
|
65
|
+
'dsh-git-ui: dictionaries',
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
// 2. 挂载 Remote 并获取 gitInfo 服务
|
|
69
|
+
const gitInfoService = await platform.mountRemoteAndGetService(deps.remoteContribution, 'gitInfo')
|
|
70
|
+
|
|
71
|
+
// 3. 控制器缓存(按 sessionId)
|
|
72
|
+
const controllers = new Map<string, ControllerLike>()
|
|
73
|
+
const faces = new Map<string, GitInjected>()
|
|
74
|
+
|
|
75
|
+
const controllerFor = (sessionId: string): ControllerLike => {
|
|
76
|
+
let controller = controllers.get(sessionId)
|
|
77
|
+
if (controller === undefined) {
|
|
78
|
+
controller = deps.createController(gitInfoService, sessionId)
|
|
79
|
+
controllers.set(sessionId, controller)
|
|
80
|
+
}
|
|
81
|
+
return controller
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 4. 注册 slot 条目
|
|
85
|
+
const slotOptions: SlotEntryDescriptor = {
|
|
86
|
+
name: HEADER_UTILITIES_SLOT,
|
|
87
|
+
id: SLOT_ENTRY_ID,
|
|
88
|
+
order: 10,
|
|
89
|
+
locale: LOCALE_NS,
|
|
90
|
+
inject: (sessionId): GitInjected => {
|
|
91
|
+
// Per-session stable face: the slot runtime may re-invoke the
|
|
92
|
+
// inject factory on every render, and components depend on the
|
|
93
|
+
// `refresh` reference staying stable (a fresh arrow function per
|
|
94
|
+
// call would re-run mount effects and loop: refresh → view
|
|
95
|
+
// change → re-render → new refresh → refresh …). Cache the face
|
|
96
|
+
// so the same controller (and its bound refresh/run) is always
|
|
97
|
+
// handed out per session.
|
|
98
|
+
let face = faces.get(sessionId)
|
|
99
|
+
if (face === undefined) {
|
|
100
|
+
const controller = controllerFor(sessionId)
|
|
101
|
+
face = {
|
|
102
|
+
hooks: { git: controller as GitInjected['hooks']['git'] },
|
|
103
|
+
refresh: () => controller.refresh(),
|
|
104
|
+
run: (action) => controller.run(action),
|
|
105
|
+
query: (query) => controller.query(query),
|
|
106
|
+
}
|
|
107
|
+
faces.set(sessionId, face)
|
|
108
|
+
}
|
|
109
|
+
return face
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const disposeSlot = platform.registerSlotEntry(slotOptions, component)
|
|
114
|
+
|
|
115
|
+
// 5. 注册清理:释放 slot 并销毁全部控制器
|
|
116
|
+
platform.effect(() => {
|
|
117
|
+
return () => {
|
|
118
|
+
disposeSlot()
|
|
119
|
+
for (const controller of controllers.values()) controller.dispose()
|
|
120
|
+
controllers.clear()
|
|
121
|
+
faces.clear()
|
|
122
|
+
}
|
|
123
|
+
}, 'dsh-git-ui: slot and controller lifecycle')
|
|
124
|
+
|
|
125
|
+
// 6. 订阅连接重置事件:重连后刷新全部控制器
|
|
126
|
+
platform.onEvent('connection/reset', () => {
|
|
127
|
+
for (const controller of controllers.values()) controller.resync()
|
|
128
|
+
})
|
|
129
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI 基础组件 React Context:业务组件通过 `useUI()` 获取实现,
|
|
3
|
+
* 适配层在插件激活时通过 `<UIPrimitivesProvider>` 注入宿主组件。
|
|
4
|
+
*
|
|
5
|
+
* 设计要点:
|
|
6
|
+
* - 默认值为 null——若未提供则 `useUI()` 抛出明确错误,而非静默渲染空组件。
|
|
7
|
+
* - Provider 在插件入口(client/index.ts)包裹整棵组件树。
|
|
8
|
+
* - 测试可直接用 Provider 注入 mock 组件,无需模拟宿主环境。
|
|
9
|
+
*/
|
|
10
|
+
import { createContext, useContext } from 'react'
|
|
11
|
+
import type { ReactNode } from 'react'
|
|
12
|
+
import type { UIPrimitives } from './ui-primitives.ts'
|
|
13
|
+
|
|
14
|
+
const UIPrimitivesContext = createContext<UIPrimitives | null>(null)
|
|
15
|
+
|
|
16
|
+
/** 消费 UI 基础组件。必须在 `<UIPrimitivesProvider>` 内使用。 */
|
|
17
|
+
export function useUI(): UIPrimitives {
|
|
18
|
+
const value = useContext(UIPrimitivesContext)
|
|
19
|
+
if (value === null) {
|
|
20
|
+
throw new Error('useUI(): missing <UIPrimitivesProvider> — 插件入口未注入 UI 基础组件')
|
|
21
|
+
}
|
|
22
|
+
return value
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 注入 UI 基础组件实现。在插件入口包裹整棵组件树。 */
|
|
26
|
+
export function UIPrimitivesProvider({ value, children }: { value: UIPrimitives; children?: ReactNode }): ReactNode {
|
|
27
|
+
return <UIPrimitivesContext.Provider value={value}>{children}</UIPrimitivesContext.Provider>
|
|
28
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI 基础组件契约:业务层只依赖此接口,不直接 import 任何宿主 UI 库。
|
|
3
|
+
*
|
|
4
|
+
* 属性集取「实际使用的最小并集」——GitPill / GitCenter 用到的全部 props
|
|
5
|
+
* 均已覆盖;宿主适配层可提供更丰富的实现,但业务代码保证只消费此处的
|
|
6
|
+
* 声明。未来 dsh 升级导致 Modal/Button/Toast 变更时,只需更新适配层,
|
|
7
|
+
* 业务组件零改动。
|
|
8
|
+
*/
|
|
9
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react'
|
|
10
|
+
|
|
11
|
+
/** 受控全屏对话框:遮罩 + Escape 关闭。 */
|
|
12
|
+
export interface ModalProps {
|
|
13
|
+
readonly open: boolean
|
|
14
|
+
readonly onClose: () => void
|
|
15
|
+
readonly title: string
|
|
16
|
+
readonly closeLabel?: string
|
|
17
|
+
readonly description?: string
|
|
18
|
+
readonly children?: ReactNode
|
|
19
|
+
readonly footer?: ReactNode
|
|
20
|
+
readonly className?: string
|
|
21
|
+
readonly contentClassName?: string
|
|
22
|
+
/** 隐藏默认标题栏(自定义头部时使用)。 */
|
|
23
|
+
readonly headless?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 令牌化按钮原子。原生 button 属性透传。 */
|
|
27
|
+
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
28
|
+
readonly variant?: 'primary' | 'ghost' | 'outline' | 'toolbar'
|
|
29
|
+
readonly size?: 'md' | 'sm'
|
|
30
|
+
readonly icon?: ReactNode
|
|
31
|
+
readonly className?: string | undefined
|
|
32
|
+
readonly children?: ReactNode
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 瞬时顶部提示条:自动消失后调用 onDone 通知父级卸载。 */
|
|
36
|
+
export interface ToastProps {
|
|
37
|
+
readonly text: string
|
|
38
|
+
readonly icon?: ReactNode
|
|
39
|
+
readonly anchor?: HTMLElement | null
|
|
40
|
+
readonly onDone: () => void
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 三个基础组件的聚合:适配层一次性提供整套实现。 */
|
|
44
|
+
export interface UIPrimitives {
|
|
45
|
+
readonly Modal: (props: ModalProps) => ReactNode
|
|
46
|
+
readonly Button: (props: ButtonProps) => ReactNode
|
|
47
|
+
readonly Toast: (props: ToastProps) => ReactNode
|
|
48
|
+
}
|
package/src/host/index.ts
CHANGED
|
@@ -1,107 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-git-ui host
|
|
2
|
+
* dsh-git-ui host 适配层:Cordis/typert 壳。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* 本文件是 host 端**唯一** import `@deepseek-ai/*` 的地方。职责:
|
|
5
|
+
* 1. 将 Cordis 服务(subprocess / sessions / sessionPersistence)适配为
|
|
6
|
+
* 结构化 `SnapshotDeps` 接口;
|
|
7
|
+
* 2. 调用 `createHostEndpoints(deps, config)` 获得纯业务端点;
|
|
8
|
+
* 3. 以 `@Remote` 装饰器将端点暴露给 typert Gateway。
|
|
9
|
+
*
|
|
10
|
+
* 业务逻辑全部在 `contracts/host-endpoints.ts` → `host/core.ts` /
|
|
11
|
+
* `host/actions.ts` / `host/queries.ts`,与框架无关。
|
|
10
12
|
*/
|
|
11
13
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
|
|
12
14
|
import type { Context } from '@deepseek-ai/cordis'
|
|
13
15
|
import { realpath, stat } from 'node:fs/promises'
|
|
14
16
|
import { createGitRunner, type SubprocessLike } from './git.ts'
|
|
15
|
-
import { normalizeConfig,
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import type { GitActionResult, GitActionRequest, GitQueryRequest, GitQueryResponse, GitSnapshotRequest, GitSnapshotResult } from './types.ts'
|
|
17
|
+
import { normalizeConfig, type GitStatusConfig, type SnapshotDeps } from './core.ts'
|
|
18
|
+
import { createHostEndpoints, type HostEndpoints } from '../contracts/host-endpoints.ts'
|
|
19
|
+
import type { GitActionRequest, GitActionResult, GitQueryRequest, GitQueryResponse, GitSnapshotRequest, GitSnapshotResult } from './types.ts'
|
|
19
20
|
|
|
20
21
|
export type { GitSnapshot, GitSnapshotResult, GitSnapshotFailure, GitSnapshotRequest, GitCommit, GitChange, GitAction, GitActionResult, GitActionRequest, GitQuery, GitQueryResult, GitQueryRequest, GitQueryResponse, GitBranch, GitFileStat, GitRef } from './types.ts'
|
|
21
22
|
export { normalizeConfig, DEFAULT_CONFIG } from './core.ts'
|
|
22
23
|
export { parseStatusOutput, parseLogOutput, parseBranchOutput, parseNameStatusOutput } from './parser.ts'
|
|
23
24
|
export { isSafePath, isValidBranchName, runAction } from './actions.ts'
|
|
24
25
|
export { runQuery } from './queries.ts'
|
|
26
|
+
export { createHostEndpoints, type HostEndpoints } from '../contracts/host-endpoints.ts'
|
|
25
27
|
|
|
26
|
-
/**
|
|
27
|
-
interface SessionLike {
|
|
28
|
-
readonly header?: { readonly cwd?: string }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Structural face of the sessions service. */
|
|
28
|
+
/** Cordis sessions 服务的结构化切片。 */
|
|
32
29
|
interface SessionsLike {
|
|
33
|
-
get(id: string):
|
|
30
|
+
get(id: string): { readonly header?: { readonly cwd?: string } } | undefined
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
/**
|
|
33
|
+
/** Cordis sessionPersistence 服务的结构化切片。 */
|
|
37
34
|
interface SessionPersistenceLike {
|
|
38
35
|
inspect(id: string): Promise<{ readonly meta: { readonly cwd?: string } }>
|
|
39
36
|
}
|
|
40
37
|
|
|
41
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* gitInfo Remote 服务:Cordis 壳。
|
|
40
|
+
*
|
|
41
|
+
* 构造时从 Cordis Context 取出宿主服务,适配为 `SnapshotDeps`,再调用
|
|
42
|
+
* `createHostEndpoints` 获得纯业务端点。三个 `@Remote` 方法仅做委托。
|
|
43
|
+
*/
|
|
42
44
|
export class GitStatusService extends TypertRemoteService {
|
|
43
45
|
static inject = ['subprocess', 'sessions', 'sessionPersistence']
|
|
44
46
|
|
|
45
|
-
private readonly
|
|
47
|
+
private readonly endpoints: HostEndpoints
|
|
46
48
|
|
|
47
49
|
constructor(ctx: Context, config: unknown) {
|
|
48
50
|
super(ctx, 'gitInfo')
|
|
49
|
-
|
|
51
|
+
const normalizedConfig = normalizeConfig(config)
|
|
52
|
+
const deps = this.buildDeps(ctx, normalizedConfig)
|
|
53
|
+
this.endpoints = createHostEndpoints(deps, normalizedConfig)
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
/**
|
|
53
|
-
private
|
|
54
|
-
const subprocess =
|
|
56
|
+
/** 将 Cordis 服务适配为结构化 SnapshotDeps。 */
|
|
57
|
+
private buildDeps(ctx: Context, config: GitStatusConfig): SnapshotDeps {
|
|
58
|
+
const subprocess = ctx.get('subprocess') as SubprocessLike | undefined
|
|
55
59
|
if (subprocess === undefined) {
|
|
56
|
-
|
|
60
|
+
// 返回一个永远失败的 deps——端点调用会走到 git-unavailable 降级路径。
|
|
61
|
+
return {
|
|
62
|
+
run: { run: async () => { throw new Error('subprocess service unavailable') } },
|
|
63
|
+
fs: { realpath, stat },
|
|
64
|
+
sessions: { liveCwd: () => undefined, persistedMeta: async () => undefined },
|
|
65
|
+
}
|
|
57
66
|
}
|
|
58
|
-
const sessions =
|
|
59
|
-
const persistence =
|
|
60
|
-
const runner = createGitRunner(subprocess, this.config.timeoutMs, this.config.maxStatusBytes)
|
|
67
|
+
const sessions = ctx.get('sessions') as SessionsLike | undefined
|
|
68
|
+
const persistence = ctx.get('sessionPersistence') as SessionPersistenceLike | undefined
|
|
61
69
|
return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
},
|
|
70
|
+
run: createGitRunner(subprocess, config.timeoutMs, config.maxStatusBytes),
|
|
71
|
+
fs: { realpath, stat },
|
|
72
|
+
sessions: {
|
|
73
|
+
liveCwd: (id) => sessions?.get(id)?.header?.cwd,
|
|
74
|
+
persistedMeta: async (id) => {
|
|
75
|
+
if (persistence === undefined) return undefined
|
|
76
|
+
try {
|
|
77
|
+
const inspection = await persistence.inspect(id)
|
|
78
|
+
return { cwd: inspection.meta.cwd }
|
|
79
|
+
} catch {
|
|
80
|
+
return undefined
|
|
81
|
+
}
|
|
76
82
|
},
|
|
77
|
-
signal,
|
|
78
83
|
},
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
@Remote('snapshot')
|
|
83
88
|
async snapshot(request: GitSnapshotRequest, signal?: AbortSignal): Promise<GitSnapshotResult> {
|
|
84
|
-
|
|
85
|
-
if ('failure' in adapted) return { ok: false, error: adapted.failure }
|
|
86
|
-
return snapshotForSession(adapted.deps, this.config, request.sessionId)
|
|
89
|
+
return this.endpoints.snapshot(request, signal)
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
@Remote('run')
|
|
90
93
|
async run(request: GitActionRequest, signal?: AbortSignal): Promise<GitActionResult> {
|
|
91
|
-
|
|
92
|
-
if ('failure' in adapted) {
|
|
93
|
-
return { ok: false, error: { code: 'git-error', message: adapted.failure.detail } }
|
|
94
|
-
}
|
|
95
|
-
return runAction(adapted.deps, this.config, request)
|
|
94
|
+
return this.endpoints.run(request, signal)
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
@Remote('query')
|
|
99
98
|
async query(request: GitQueryRequest, signal?: AbortSignal): Promise<GitQueryResponse> {
|
|
100
|
-
|
|
101
|
-
if ('failure' in adapted) {
|
|
102
|
-
return { ok: false, error: { code: 'git-error', message: adapted.failure.detail } }
|
|
103
|
-
}
|
|
104
|
-
return runQuery(adapted.deps, this.config, request)
|
|
99
|
+
return this.endpoints.query(request, signal)
|
|
105
100
|
}
|
|
106
101
|
}
|
|
107
102
|
|