@dsh-workbench/core 0.3.6 → 0.3.7
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/client.js +1260 -1928
- package/dist/client/client.body.js +1260 -1928
- package/dist/client/index.js +1260 -1928
- package/dist/host/index.js +1240 -790
- package/dist/types/app/client.d.ts +76 -3388
- package/dist/types/app/layouts/env-gate/boot-gate.d.ts +8 -1
- package/dist/types/app/layouts/rightbar/market-busy.d.ts +28 -0
- package/dist/types/app/layouts/rightbar/market-hint.d.ts +7 -0
- package/dist/types/app/layouts/sidebar/DebugFooterAction.d.ts +2 -1
- package/dist/types/app/layouts/sidebar/RecordFooterAction.d.ts +2 -1
- package/dist/types/app/layouts/sidebar/SessionList.d.ts +3 -1
- package/dist/types/app/layouts/sidebar/session-pick.d.ts +2 -0
- package/dist/types/app/layouts/sidebar/use-attention.d.ts +7 -1
- package/dist/types/modules/panels/model/diy-context.d.ts +9 -0
- package/dist/types/modules/panels/model/diy-record-beat.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-close.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-hook.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-open.d.ts +28 -0
- package/dist/types/modules/panels/model/diy-record-open.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-shot.test.d.ts +1 -0
- package/dist/types/modules/panels/model/diy-record-text.d.ts +16 -1
- package/dist/types/modules/panels/model/diy-record-tools.d.ts +2 -1
- package/dist/types/modules/panels/model/diy-record.d.ts +40 -1
- package/dist/types/modules/panels/model/diy-report.d.ts +1 -1
- package/dist/types/modules/sessions/index.d.ts +1 -1
- package/dist/types/modules/sessions/model/attention-long-task.test.d.ts +1 -0
- package/dist/types/modules/sessions/model/attention.d.ts +23 -1
- package/dist/types/modules/sessions/model/session-node.d.ts +12 -1
- package/dist/types/shared/api/host/long-tasks.d.ts +10 -0
- package/dist/types/shared/api/host/preflight.d.ts +7 -1
- package/dist/types/shared/api/host/workshop-root.d.ts +9 -0
- package/dist/types/shared/api/long-tasks.d.ts +19 -0
- package/dist/types/shared/hooks/panel-state-report.d.ts +1 -1
- package/dist/types/shared/hooks/use-diy-debug.d.ts +1 -1
- package/dist/types/shared/hooks/use-diy-record.d.ts +1 -1
- package/dist/types/shared/hooks/use-shell-long-tasks.d.ts +8 -0
- package/package.json +2 -2
- package/skills/workbench-module-diy/BUILD.md +1 -1
- package/skills/workbench-module-diy/FAMILY.md +1 -1
- package/skills/workbench-module-diy/KIT.md +23 -23
- package/skills/workbench-module-diy/PANEL.md +1 -1
- package/skills/workbench-module-diy/TOOLS.md +3 -1
- package/skills/workbench-module-diy/WRITES.md +20 -3
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { Translate } from '@dsh-workbench/shared';
|
|
2
2
|
import { apiGet } from '@dsh-workbench/shared';
|
|
3
|
+
/**
|
|
4
|
+
* 每一拍轮询带什么参数:平时什么都不带(宿主那边跟着正在跑的那一轮,刚跑出 `ready` 的直接回上一次),
|
|
5
|
+
* 用户按了「重试」之后的那一拍带 `hard=1`(不看新鲜度、一律重来)。首拍不再强探 —— 页面上一次
|
|
6
|
+
* 刚体检过就不必再跑一轮,两页同时刷新也只有一轮。
|
|
7
|
+
*/
|
|
3
8
|
export declare function createForcePlan(): {
|
|
4
|
-
next():
|
|
9
|
+
next(): {
|
|
10
|
+
hard?: 1;
|
|
11
|
+
};
|
|
5
12
|
again(): void;
|
|
6
13
|
};
|
|
7
14
|
export interface BootGate {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一条「市场自己正在动它」的事实最多认多久(毫秒):从发布那一刻起算,到点自动撤。
|
|
3
|
+
* 取的是工作台那一跳同一条窗口 —— 一次替换最长等这么久;正常落地会由发布方先撤掉。
|
|
4
|
+
*/
|
|
5
|
+
export declare const MARKET_BUSY_MS = 180000;
|
|
6
|
+
/**
|
|
7
|
+
* 这一刻哪些单元算「正在被动」:发布过、且没到 {@link MARKET_BUSY_MS} 的那些 id。
|
|
8
|
+
* 给纯函数与判据用;React 里读 {@link useMarketBusy}(同一份,只是走订阅)。
|
|
9
|
+
*/
|
|
10
|
+
export declare function marketBusyIds(): ReadonlySet<string>;
|
|
11
|
+
/**
|
|
12
|
+
* 发布「哪几个单元正被市场自己动」(每次整体替换):点下去那一刻发,落地/失败后发空数组。
|
|
13
|
+
* 同一个 id 连续发布不会顺延它的期限;空数组等于全撤。
|
|
14
|
+
*/
|
|
15
|
+
export declare function publishMarketBusy(ids: readonly string[]): void;
|
|
16
|
+
/**
|
|
17
|
+
* 订阅这份事实的变化(发布内容真变了、或有条目到期才通知)。返回退订函数。
|
|
18
|
+
*/
|
|
19
|
+
export declare function subscribeMarketBusy(listener: () => void): () => void;
|
|
20
|
+
/**
|
|
21
|
+
* 组件里读「正被市场自己动的单元」:返回的集合在两次通知之间是同一份引用,可以直接进渲染。
|
|
22
|
+
*/
|
|
23
|
+
export declare function useMarketBusy(): ReadonlySet<string>;
|
|
24
|
+
/**
|
|
25
|
+
* 把「市场正在动哪个单元」挂到这一拍上:`id` 变了就整体替换发布,`null` 等于撤。
|
|
26
|
+
* 卸载那一下**不清**(更新还在跑,徽标不该改口);到 {@link MARKET_BUSY_MS} 由自己到期兜底。
|
|
27
|
+
*/
|
|
28
|
+
export declare function usePublishMarketBusy(id: string | null): void;
|
|
@@ -12,5 +12,12 @@ export interface MarketHintInput {
|
|
|
12
12
|
readonly installed?: readonly InstalledWorkbenchModule[] | undefined;
|
|
13
13
|
readonly plugins?: readonly InstalledPlugin[] | null | undefined;
|
|
14
14
|
readonly workbench?: WorkbenchVersion | null | undefined;
|
|
15
|
+
readonly busy?: ReadonlySet<string> | undefined;
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* 左栏市场入口那枚徽标这一刻该说什么:`problem`(模块异常)> `restart`(重启后生效)> `updates`(有新版本),
|
|
19
|
+
* 都没有就 `null`。读法与市场页同一份数据、同一套造行函数。
|
|
20
|
+
* `busy` 里的单元(正被市场自己更新/卸载的那一单元)当它不在名单里:更新是「先卸再装」,
|
|
21
|
+
* 卸的那一半会让它从名册里消失,不排掉就会在跑的过程中报一次假的「重启后生效」。
|
|
22
|
+
*/
|
|
16
23
|
export declare function marketHintOf(input: MarketHintInput): PanelHint | null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SessionsSnapshot, SnapshotSelectorHook, Translate, WorkspaceSnapshot } from '@dsh-workbench/shared';
|
|
2
|
+
import type { DebugState } from '@dsh-workbench/shared/host';
|
|
2
3
|
export interface DebugFooterActionProps {
|
|
3
4
|
readonly wide?: boolean | undefined;
|
|
4
5
|
readonly t: Translate;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Translate } from '@dsh-workbench/shared';
|
|
2
|
+
import type { RecordIntent, RecordState } from '@dsh-workbench/shared/host';
|
|
2
3
|
export interface RecordFooterActionProps {
|
|
3
4
|
readonly wide?: boolean | undefined;
|
|
4
5
|
readonly t: Translate;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { RenderSlot, SessionStatusSnapshot, SessionsSnapshot, SnapshotSelectorHook, Translate, WorkspaceView } from '@dsh-workbench/shared';
|
|
2
|
+
import { type LongTaskPresence } from '../../../modules/sessions';
|
|
2
3
|
export interface SessionListProps {
|
|
3
4
|
workspace: WorkspaceView | null;
|
|
4
5
|
pinnedIds: readonly string[];
|
|
5
6
|
archived: readonly string[];
|
|
6
7
|
panelActive: boolean;
|
|
8
|
+
longTasks: LongTaskPresence;
|
|
7
9
|
useSessions: SnapshotSelectorHook<SessionsSnapshot>;
|
|
8
10
|
useSessionStatus: SnapshotSelectorHook<SessionStatusSnapshot>;
|
|
9
11
|
onOpen: (sessionId: string) => void;
|
|
10
12
|
renderSlot: RenderSlot;
|
|
11
13
|
t: Translate;
|
|
12
14
|
}
|
|
13
|
-
export declare function SessionList({ workspace, pinnedIds, archived, panelActive, useSessions, useSessionStatus, onOpen, renderSlot, t, }: SessionListProps): JSX.Element;
|
|
15
|
+
export declare function SessionList({ workspace, pinnedIds, archived, panelActive, longTasks, useSessions, useSessionStatus, onOpen, renderSlot, t, }: SessionListProps): JSX.Element;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { SessionStatusSnapshot, SessionsSnapshot, WorkspaceView } from '@dsh-workbench/shared';
|
|
2
|
+
import { type LongTaskPresence } from '../../../modules/sessions';
|
|
2
3
|
export interface PickProjectInput {
|
|
3
4
|
readonly workspaceId: string;
|
|
4
5
|
readonly items: readonly WorkspaceView[];
|
|
5
6
|
readonly sessions: SessionsSnapshot;
|
|
6
7
|
readonly statusById: SessionStatusSnapshot;
|
|
7
8
|
readonly archivedIds: ReadonlySet<string>;
|
|
9
|
+
readonly longTasks: LongTaskPresence;
|
|
8
10
|
readonly openSession: (sessionId: string) => void;
|
|
9
11
|
readonly startSession: (workspaceId: string) => void;
|
|
10
12
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SessionStatusSnapshot, SessionsSnapshot, WorkspaceView } from '@dsh-workbench/shared';
|
|
2
|
-
import { type Attention } from '../../../modules/sessions';
|
|
2
|
+
import { type Attention, type LongTaskPresence } from '../../../modules/sessions';
|
|
3
3
|
export interface SidebarAttentionInput {
|
|
4
4
|
readonly items: readonly WorkspaceView[];
|
|
5
5
|
readonly sessions: SessionsSnapshot;
|
|
@@ -7,6 +7,7 @@ export interface SidebarAttentionInput {
|
|
|
7
7
|
readonly archived: readonly string[];
|
|
8
8
|
readonly storedId: string | null;
|
|
9
9
|
readonly panelActive: boolean;
|
|
10
|
+
readonly longTasks: LongTaskPresence;
|
|
10
11
|
}
|
|
11
12
|
export interface SidebarAttentionState {
|
|
12
13
|
readonly current: WorkspaceView | null;
|
|
@@ -14,3 +15,8 @@ export interface SidebarAttentionState {
|
|
|
14
15
|
readonly attention: Attention;
|
|
15
16
|
}
|
|
16
17
|
export declare function useSidebarAttention(input: SidebarAttentionInput): SidebarAttentionState;
|
|
18
|
+
/**
|
|
19
|
+
* 左栏要的那份长任务归属:外壳那条读口交回「全进程还在跑的长任务」,这里折成会话侧与工作区侧。
|
|
20
|
+
* @returns 两侧的归属(谁起了 / 哪个工作区里有几趟)。
|
|
21
|
+
*/
|
|
22
|
+
export declare function useLongTaskPresence(): LongTaskPresence;
|
|
@@ -88,6 +88,15 @@ export declare function showOrReason(panels: PanelsService, unit: string, worksp
|
|
|
88
88
|
readonly ok: boolean;
|
|
89
89
|
readonly reason?: string;
|
|
90
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* 这一次 DIY 调用落在哪个工坊上:**先认工坊自己的家**(`$DSH_HOME/workbench/diy`,宿主就是按它把工坊
|
|
93
|
+
* 登记成工作区的),**不跟着会话 cwd 走** —— 用户把自己的项目设成工作区时,cwd 往上找会撞上任意一个
|
|
94
|
+
* 带 `packages/` 的仓库,模块列表与 `shots/` 落点会一起变样(真机踩过:录制落点跑到仓库根、截图 EPERM)。
|
|
95
|
+
* 只有拿不到工坊的家时,才退回"从会话工作区往上找像工坊的目录"。
|
|
96
|
+
* @param ctx - 宿主上下文。
|
|
97
|
+
* @param exec - 这次工具调用(退回那一档要读它的会话工作区)。
|
|
98
|
+
* @returns 工坊的 packages 目录、根路径与标题。
|
|
99
|
+
*/
|
|
91
100
|
export declare function workshopOf(ctx: DshHostContext, exec: DshToolRunContext): Promise<{
|
|
92
101
|
readonly packagesDir: string;
|
|
93
102
|
readonly workspace: string | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { DshShell } from '@dsh-workbench/shared';
|
|
2
|
+
/**
|
|
3
|
+
* 两个地址是不是"同一页":比之前先把查询串 / 锚点 / 末尾斜杠抹掉 —— `open` 导航之后
|
|
4
|
+
* 地址栏常常多一个斜杠或一串 hash,那不算开错了页。
|
|
5
|
+
* @param here - 那一页现在真实所在的地址(`location.href`)。@param want - 我们要它去的地址。@returns 同一页为 `true`。
|
|
6
|
+
*/
|
|
7
|
+
export declare function samePageOf(here: string, want: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* 把录制那一页开起来,并且**确认它真的开起来了**才回来。
|
|
10
|
+
*
|
|
11
|
+
* 先收掉这条会话的残留(否则新页会开进上一次留下的那扇旧窗里,不顶前台、看上去"根本没弹窗");
|
|
12
|
+
* 再 `tab new --window foreground` 开我们的页;**然后读一次 `location.href` 核实** —— 开出来是空白页
|
|
13
|
+
* 或别的地址时,回落到 opencli 的 `open`(它落在会话租约那个标签上)再核一次;两次都不对就
|
|
14
|
+
* `ok: false`,让调用方把这句话原样报给 agent,**不许在没开起来的页上装浮条说"已开始录制"**。
|
|
15
|
+
* @param input - 这条会话的名字 / 目标地址 / 捕获文件(收残留那一拍要用)。
|
|
16
|
+
* @returns `ok: false` 时 `at` 是那一页现在真实的地址(`''` = 空白页),`why` 是给 agent 看的一句话。
|
|
17
|
+
*/
|
|
18
|
+
export declare function openRecordPage(input: {
|
|
19
|
+
readonly shell: DshShell;
|
|
20
|
+
readonly command: string;
|
|
21
|
+
readonly session: string;
|
|
22
|
+
readonly url: string;
|
|
23
|
+
readonly captureFile: string;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
readonly ok: boolean;
|
|
26
|
+
readonly at: string;
|
|
27
|
+
readonly why: string;
|
|
28
|
+
}>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RecordExpect, RecordMove, RecordRefusal, RecordState } from '@dsh-workbench/shared';
|
|
1
|
+
import type { RecordExpect, RecordMove, RecordRefusal, RecordState } from '@dsh-workbench/shared/host';
|
|
2
2
|
export interface RecordCall {
|
|
3
3
|
readonly key: string;
|
|
4
4
|
readonly method: string;
|
|
@@ -20,8 +20,18 @@ export interface RecordPageRead {
|
|
|
20
20
|
readonly here: string;
|
|
21
21
|
readonly dropped: number;
|
|
22
22
|
readonly unhooked: boolean;
|
|
23
|
+
readonly shots: number;
|
|
24
|
+
readonly shotsAt: number | null;
|
|
25
|
+
readonly beat: boolean;
|
|
26
|
+
readonly beatKind: string;
|
|
23
27
|
readonly calls: readonly RecordCall[];
|
|
24
28
|
}
|
|
29
|
+
export interface RecordShotLine {
|
|
30
|
+
readonly n: number;
|
|
31
|
+
readonly at: number;
|
|
32
|
+
readonly path: string;
|
|
33
|
+
readonly error?: string;
|
|
34
|
+
}
|
|
25
35
|
export declare function recordPageOf(payload: Record<string, unknown> | undefined): RecordPageRead;
|
|
26
36
|
export declare function filterTextOf(expect: RecordExpect | null): string;
|
|
27
37
|
export declare function probeCommandOf(calls: readonly RecordCall[]): string;
|
|
@@ -61,8 +71,12 @@ export interface RecordReadFacts {
|
|
|
61
71
|
readonly nav: boolean;
|
|
62
72
|
readonly filter: string;
|
|
63
73
|
readonly captureRound: string;
|
|
74
|
+
readonly shots?: readonly RecordShotLine[];
|
|
75
|
+
readonly beats?: number;
|
|
76
|
+
readonly beatMs?: number;
|
|
64
77
|
}
|
|
65
78
|
export declare function renderRecordRead(input: RecordReadFacts): string;
|
|
79
|
+
/** 详情那一拍的原料:入参 / 出参 / 请求头那几块原文的渲染全靠它。 */
|
|
66
80
|
export interface RecordDetailFacts {
|
|
67
81
|
readonly found: boolean;
|
|
68
82
|
readonly where: string;
|
|
@@ -83,6 +97,7 @@ export interface RecordDetailFacts {
|
|
|
83
97
|
readonly maxBytes: number;
|
|
84
98
|
readonly note: string;
|
|
85
99
|
}
|
|
100
|
+
/** 把字段证据表插进详情正文:接在出参那一块之后、`请求头` 那一行(或第一条附注)之前;没有锚点就接在末尾。空串原样回。 */
|
|
86
101
|
export declare function renderRecordDetail(input: RecordDetailFacts): string;
|
|
87
102
|
export declare function moveOrThrow(move: RecordMove, held: RecordState | null): RecordState;
|
|
88
103
|
export declare function domainOf(url: string): string;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type { DshHostContext
|
|
1
|
+
import type { DshHostContext } from '@dsh-workbench/shared';
|
|
2
|
+
import type { RecordStore } from '@dsh-workbench/shared/host';
|
|
2
3
|
export declare function registerRecordTools(ctx: DshHostContext, store: RecordStore): readonly (() => void)[];
|
|
@@ -1,16 +1,36 @@
|
|
|
1
|
-
import type { DshHostContext, DshShell
|
|
1
|
+
import type { DshHostContext, DshShell } from '@dsh-workbench/shared';
|
|
2
|
+
import type { RecordState, RecordStore } from '@dsh-workbench/shared/host';
|
|
2
3
|
import type { CaptureEntry } from './diy-record-capture';
|
|
3
4
|
import type { RecordCall, RecordListRow, RecordPageRead } from './diy-record-text';
|
|
4
5
|
export declare const OPEN_TIMEOUT_MS = 30000;
|
|
5
6
|
export declare const SETTLE_MS = 2000;
|
|
6
7
|
export declare const WAIT_CAP_MS: number;
|
|
7
8
|
export declare const POLL_MS = 1000;
|
|
9
|
+
/**
|
|
10
|
+
* 捕获那一层要滤掉的 URL:静态资源(`data:` 与常见后缀)。用法 `new RegExp(...).test(url)`,命中即丢。
|
|
11
|
+
*/
|
|
12
|
+
export declare const STATIC_URL_PATTERN = "^(data:)|(\\.(js|css|png|jpe?g|gif|svg|woff2?|ico|map|webp|mp4)(\\?|$))";
|
|
8
13
|
export declare const DETAIL_MAX_BYTES = 60000;
|
|
9
14
|
export declare const pauseFor: (ms: number) => Promise<void>;
|
|
10
15
|
export declare function waitOf(value: unknown): number;
|
|
11
16
|
export declare function foreignDomainText(design: unknown, url: string): string;
|
|
12
17
|
export declare function captureFileOf(packagesDir: string, unit: string): string;
|
|
13
18
|
export declare function lastCaptureFileOf(file: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* 这一族自己的落图路径:跟 `rec-<unit>.jsonl` 同目录、`rec-shot-` 前缀(工坊那一页的图另有名字)。
|
|
21
|
+
* @param captureFile - 这一场捕获文件;图就落在它旁边。@param unit - 单元名。@param n - 页面「已拍」计数。
|
|
22
|
+
* @returns 形如 `…/shots/rec-shot-<unit>-<n>.png` 的路径。
|
|
23
|
+
*/
|
|
24
|
+
export declare function shotFileOf(captureFile: string, unit: string, n: number): string;
|
|
25
|
+
/**
|
|
26
|
+
* 把这一张现场图落进 `shots/`:argv **只有 `screenshot <path>`,一个视口参数都不给** —— `--full-page` 走
|
|
27
|
+
* `captureBeyondViewport`、`--width` / `--height` 走 `setDeviceMetricsOverride`:真机上按一下页面**看得见地闪**
|
|
28
|
+
* (2026-10-21 两次报过)、还可能让填了一半的表单跳位;调试族那把平时也是这一档(先 `eval` 量盒子再本地 `clip`)。
|
|
29
|
+
* 真机上这个平台整页高与视口一样(1512×698)—— `--full-page` 一点没多给,白闪。
|
|
30
|
+
* @param input - 看护那一拍的依赖(shell / 命令 / 会话 / 台账)。
|
|
31
|
+
* @param held - 当前那份约定(路径按它的 `unit` 拼)。@param n - 页面浮条上那个「已拍」计数。
|
|
32
|
+
*/
|
|
33
|
+
export declare function takeShotOf(input: WatchFinishDeps, held: RecordState, n: number): Promise<void>;
|
|
14
34
|
export declare function startRecordCapture(shell: DshShell, command: string, session: string, file: string): Promise<boolean>;
|
|
15
35
|
export interface WatchFinishDeps {
|
|
16
36
|
readonly ctx: DshHostContext;
|
|
@@ -21,10 +41,29 @@ export interface WatchFinishDeps {
|
|
|
21
41
|
readonly url: string;
|
|
22
42
|
readonly captureFile: string;
|
|
23
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* 看护的一拍(= 一圈循环):读数那一条 `eval` **自己在页面里最多等 `RECORD_WATCH_MS`**(浮条在他按
|
|
46
|
+
* 「开始 / 截图 / 结束」时会唤醒挂在页面上的那个 waiter ⇒ 一按就回来),所以这一轮 = **恰好一条 `eval`**:
|
|
47
|
+
* 命中就立刻做那一拍,没人按就照旧 3 秒一拍 —— 兜底周期一字未变。该落图就落图;认到「结束」就封口、
|
|
48
|
+
* **叫醒 agent**、**关掉录制窗**、收工(关窗排在叫醒之后:关窗慢 / 失败不许拖住叫醒,失败也不抛 ——
|
|
49
|
+
* 那一扇是我们为录制借来的,ADR-0064 补记)。
|
|
50
|
+
* ⚠️ 读数那条**不许**改成 opencli 的 `wait xhr`:它**读一次清一次**那只捕获环(`cdp.js:346-353`),而 reaper
|
|
51
|
+
* 也读同一只环才拿得到 body ⇒ 会把 `rec-<unit>.jsonl` 吃空。理由与实测见 `.scratch/diy-write-record/issues/06-doorbell.md` §1.1。
|
|
52
|
+
* @param input - 看护那一拍的依赖。@returns `stop` = 这条循环可以收了;`delivered` = 这一拍叫醒过 agent。
|
|
53
|
+
*/
|
|
24
54
|
export declare function watchFinishStepOf(input: WatchFinishDeps): Promise<{
|
|
25
55
|
readonly stop: boolean;
|
|
26
56
|
readonly delivered: boolean;
|
|
27
57
|
}>;
|
|
58
|
+
export interface WatchRoundOut {
|
|
59
|
+
readonly stop: boolean;
|
|
60
|
+
readonly delivered: boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 看护的一轮(就是 `watchFinishStepOf`,留着这个名字是给测试与调用点一个稳定的形)。
|
|
64
|
+
* @param input - 看护那一拍的依赖。@returns 收不收工、有没有叫醒。
|
|
65
|
+
*/
|
|
66
|
+
export declare function watchRoundOf(input: WatchFinishDeps): Promise<WatchRoundOut>;
|
|
28
67
|
export declare function watchRecordFinish(input: WatchFinishDeps): void;
|
|
29
68
|
export interface CaptureRead {
|
|
30
69
|
readonly text: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PanelUnitReport } from '@dsh-workbench/shared';
|
|
1
|
+
import type { PanelUnitReport } from '@dsh-workbench/shared/host';
|
|
2
2
|
import { type WorkshopModule, type WorkshopSync, type WorkshopToolchain } from '../../../shared/api/host/diy-workshop';
|
|
3
3
|
export declare const SYNC_STALE_MINUTES = 15;
|
|
4
4
|
export declare const SYNC_STALE_MS: number;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { currentSessionId, orderByRecency, sessionNode, orderSessionRows, sessionRows, workspaceOfSession, sessionStatuses, visiblePendingKind, type SessionNode, type SessionStatusDot, } from './model/session-node';
|
|
2
2
|
export { scrollEdgesOf, type ScrollEdges } from './model/scroll-edges';
|
|
3
|
-
export { attentionLabel, attentionOf, attentionOfSession, attentionSessionIdOf, attentionWordLabel, type Attention, type AttentionInput, type AttentionSeverity, type WorkspaceAttention, } from './model/attention';
|
|
3
|
+
export { attentionLabel, attentionOf, attentionOfSession, attentionSessionIdOf, attentionWordLabel, longTaskPresenceOf, type Attention, type AttentionInput, type AttentionSeverity, type LongTaskPresence, type WorkspaceAttention, } from './model/attention';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import type { SessionStatusSnapshot, SessionSummary, SessionsSnapshot, Translate, WorkspaceView } from '@dsh-workbench/shared';
|
|
2
2
|
export type AttentionSeverity = 'warning' | 'ongoing' | 'done';
|
|
3
|
+
/**
|
|
4
|
+
* 一个工作区那颗状态点:`count` 是**会话**条数(照旧,长任务没有会话可归时它是 0);
|
|
5
|
+
* `longTask` 为真表示这一档说的是长任务而不是会话 —— 只改那句人话的措辞。
|
|
6
|
+
*/
|
|
3
7
|
export interface WorkspaceAttention {
|
|
4
8
|
readonly severity: AttentionSeverity;
|
|
5
9
|
readonly count: number;
|
|
10
|
+
readonly longTask?: boolean | undefined;
|
|
6
11
|
}
|
|
12
|
+
/** 还在跑的长任务按两边归:会话行那颗 loading 点看 `bySession`,项目下拉每一行看 `byWorkspace`(按工作区路径数条数)。 */
|
|
13
|
+
export interface LongTaskPresence {
|
|
14
|
+
readonly bySession: ReadonlySet<string>;
|
|
15
|
+
readonly byWorkspace: ReadonlyMap<string, number>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 把外壳那条长任务读口交回的行折成两侧的归属:有会话可归的进 `bySession`(那一行自己亮),
|
|
19
|
+
* 每一条都按它属于哪个工作区进 `byWorkspace`(没有会话可归的那些只有这一侧看得见)。
|
|
20
|
+
* @param rows 还在跑的长任务;每行要有它属于哪个工作区,会话那格可以没有。
|
|
21
|
+
* @returns 两侧的归属;空输入就是两侧都空。
|
|
22
|
+
*/
|
|
23
|
+
export declare function longTaskPresenceOf(rows: readonly {
|
|
24
|
+
readonly workspace: string;
|
|
25
|
+
readonly session?: string | undefined;
|
|
26
|
+
}[]): LongTaskPresence;
|
|
7
27
|
export interface Attention {
|
|
8
28
|
readonly byWorkspace: ReadonlyMap<string, WorkspaceAttention>;
|
|
9
29
|
readonly elsewhere: WorkspaceAttention | null;
|
|
@@ -15,13 +35,15 @@ export interface AttentionInput {
|
|
|
15
35
|
readonly statusById: SessionStatusSnapshot;
|
|
16
36
|
readonly archivedIds: ReadonlySet<string>;
|
|
17
37
|
readonly currentWorkspaceId: string | null;
|
|
38
|
+
readonly longTasks?: LongTaskPresence | undefined;
|
|
18
39
|
}
|
|
19
|
-
export declare function attentionOfSession(summary: SessionSummary, statusById: SessionStatusSnapshot, sessions: SessionsSnapshot): AttentionSeverity | undefined;
|
|
40
|
+
export declare function attentionOfSession(summary: SessionSummary, statusById: SessionStatusSnapshot, sessions: SessionsSnapshot, longTaskIds?: ReadonlySet<string>): AttentionSeverity | undefined;
|
|
20
41
|
export interface AttentionPickInput {
|
|
21
42
|
readonly workspace: WorkspaceView | null;
|
|
22
43
|
readonly sessions: SessionsSnapshot;
|
|
23
44
|
readonly statusById: SessionStatusSnapshot;
|
|
24
45
|
readonly archivedIds: ReadonlySet<string>;
|
|
46
|
+
readonly longTasks?: LongTaskPresence | undefined;
|
|
25
47
|
}
|
|
26
48
|
export declare function attentionSessionIdOf(input: AttentionPickInput): string | null;
|
|
27
49
|
export declare function attentionOf(input: AttentionInput): Attention;
|
|
@@ -5,6 +5,7 @@ export interface SessionNode {
|
|
|
5
5
|
readonly blank: boolean;
|
|
6
6
|
readonly running: boolean;
|
|
7
7
|
readonly runningSubagentCount: number;
|
|
8
|
+
readonly longTask: boolean;
|
|
8
9
|
readonly completed: boolean;
|
|
9
10
|
readonly pinned: boolean;
|
|
10
11
|
readonly updatedAt: number;
|
|
@@ -16,7 +17,17 @@ export interface SessionStatusDot {
|
|
|
16
17
|
readonly trailingLabel?: string;
|
|
17
18
|
}
|
|
18
19
|
export declare function visiblePendingKind(kind: string | undefined): string | undefined;
|
|
19
|
-
|
|
20
|
+
/**
|
|
21
|
+
* 把一条会话读成行上要的那几格。
|
|
22
|
+
* @param summary 会话摘要。
|
|
23
|
+
* @param statusById 会话状态表。
|
|
24
|
+
* @param list 会话快照(子代理花名册在这里)。
|
|
25
|
+
* @param pinnedIds 置顶的那些。
|
|
26
|
+
* @param archivedIds 归档的那些。
|
|
27
|
+
* @param longTaskIds 有长任务在跑的那些会话(外壳那条读口按“这趟是谁起的”算出来的);不给就是都没有。
|
|
28
|
+
* @returns 行模型;`longTask` 为真表示这条会话起的某趟长任务还在跑(它自己那一轮可能早结束了)。
|
|
29
|
+
*/
|
|
30
|
+
export declare function sessionNode(summary: SessionSummary, statusById: SessionStatusSnapshot, list: SessionsSnapshot, pinnedIds: ReadonlySet<string>, archivedIds: ReadonlySet<string>, longTaskIds?: ReadonlySet<string>): SessionNode;
|
|
20
31
|
export declare function sessionStatuses(node: SessionNode, t: Translate): SessionStatusDot[];
|
|
21
32
|
export declare function currentSessionId(list: SessionsSnapshot, panelActive: boolean): string | undefined;
|
|
22
33
|
export declare function orderByRecency(rows: readonly SessionSummary[]): SessionSummary[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LongTaskBase, LongTaskRegistry } from '@dsh-workbench/shared/host';
|
|
2
|
+
import type { ShellLongTaskRow } from '../long-tasks';
|
|
3
|
+
/**
|
|
4
|
+
* 把「单元 → 它那把长任务注册表」收成一条条**还在跑**的行(`live()` 已经只给活着的那几趟,
|
|
5
|
+
* 收尾的是面板那一行的事);注册表是上一版、没有 `live()` 就跳过这个单元 —— 与停止那条动作
|
|
6
|
+
* 同一个理由:那一版里没有这份读口,不是这一版坏了。
|
|
7
|
+
* @param runs 当前挂着的单元各自那把长任务注册表(外壳进程里就这一份)。
|
|
8
|
+
* @returns 正在跑的长任务(含 `needs-login` 那种),每条带上是哪个单元起的。
|
|
9
|
+
*/
|
|
10
|
+
export declare function liveLongTaskRows(runs: ReadonlyMap<string, LongTaskRegistry<LongTaskBase>>): ShellLongTaskRow[];
|
|
@@ -26,9 +26,15 @@ export interface PreflightSnapshot {
|
|
|
26
26
|
readonly guidance?: PreflightGuidance;
|
|
27
27
|
readonly bridgeIssues?: readonly PreflightBridgeIssue[];
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* 环境体检:`run()` 跑一次并回快照,`snapshot()` 看眼下这一拍。`run` 的取舍是:
|
|
31
|
+
* 已经有在跑的那一轮就跟它(不再排第二轮);上一次跑出 `ready` 且不到 `PREFLIGHT_FRESH_MS`
|
|
32
|
+
* 也直接回那一份 —— 于是两页同时打开只体检一轮、第二页还不用等。`hard` 不看新鲜度、一律重来,
|
|
33
|
+
* 留给用户按的那颗「重试」;上一次是失败/没跑完的也一律重来。
|
|
34
|
+
*/
|
|
29
35
|
export interface Preflight {
|
|
30
36
|
run(options?: {
|
|
31
|
-
|
|
37
|
+
hard?: boolean;
|
|
32
38
|
}): Promise<PreflightSnapshot>;
|
|
33
39
|
snapshot(): PreflightSnapshot;
|
|
34
40
|
}
|
|
@@ -4,4 +4,13 @@ export interface WorkshopRoot {
|
|
|
4
4
|
readonly packagesDir: string;
|
|
5
5
|
readonly title: string;
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* 工坊**自己的家**(`$DSH_HOME/workbench/diy`)—— 宿主启动时就是按它把工坊登记成工作区的。
|
|
9
|
+
* DIY 会话的工坊根取这一份,**不随会话 cwd 漂**:cwd 一离开工坊(用户把自己的项目设成工作区),
|
|
10
|
+
* 从 cwd 往上找会撞上任意一个"带 `packages/` 的仓库",于是模块列表、`shots/` 落点、装机名单全跟着变样。
|
|
11
|
+
* 这一档拿不到(没有 `dshHomePath` 服务)时才退回 cwd 那套。
|
|
12
|
+
* @param ctx - 宿主上下文(要从里面取 `dshHomePath`)。
|
|
13
|
+
* @returns 工坊的家;取不到给 `null`。
|
|
14
|
+
*/
|
|
15
|
+
export declare function workshopHomeOf(ctx: DshHostContext): WorkshopRoot | null;
|
|
7
16
|
export declare function workshopRootOf(ctx: DshHostContext, workspace: string | null): Promise<WorkshopRoot | null>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LongTaskStatus } from '@dsh-workbench/shared';
|
|
2
|
+
/**
|
|
3
|
+
* 外壳自己的长任务读口:一次把**全进程还在跑的**长任务交出来(不分单元、不分工作区)——
|
|
4
|
+
* 左栏拿它点会话行与项目下拉里那颗 loading 点。
|
|
5
|
+
*/
|
|
6
|
+
export declare const LONG_TASKS_ROUTE = "long-tasks";
|
|
7
|
+
/** 一条还在跑的长任务:左栏只要这几格,模块自己那份投影不参与。 */
|
|
8
|
+
export interface ShellLongTaskRow {
|
|
9
|
+
readonly unit: string;
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly workspace: string;
|
|
12
|
+
readonly target: string;
|
|
13
|
+
readonly status: LongTaskStatus;
|
|
14
|
+
readonly session?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
/** 外壳长任务读口的载荷。 */
|
|
17
|
+
export interface ShellLongTasksPayload {
|
|
18
|
+
readonly runs: readonly ShellLongTaskRow[];
|
|
19
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ShellLongTaskRow } from '../api/long-tasks';
|
|
2
|
+
/**
|
|
3
|
+
* 左栏那颗 loading 点读的长任务清单:**全进程还在跑的**那些(不分单元、不分工作区,每行带它属于
|
|
4
|
+
* 哪个工作区、是哪个会话起的)。起 / 走一步 / 收尾宿主都会推一条事实,收到就当场重读;那个定时器
|
|
5
|
+
* 只是兜底 —— 有在跑的按快档问,都闲着按慢档。
|
|
6
|
+
* @returns 还在跑的长任务;读数还没回来时是空数组。
|
|
7
|
+
*/
|
|
8
|
+
export declare function useShellLongTasks(): readonly ShellLongTaskRow[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "./icon.svg",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"access": "public"
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@dsh-workbench/shared": "0.3.
|
|
90
|
+
"@dsh-workbench/shared": "0.3.7"
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "node scripts/build.mjs && node scripts/build-types.mjs",
|
|
@@ -166,7 +166,7 @@ export function apply(ctx) {
|
|
|
166
166
|
`card` 拿到 `{ t, world, refreshToken, onRefresh, onSummary }`(`world.current.workspace` 就是当前项目),
|
|
167
167
|
返回一棵元素树。**三态用外壳给的组件,别自己画**:
|
|
168
168
|
加载 → `kit.components.SkeletonRows`;空 → `kit.components.EmptyState`(不带按钮);
|
|
169
|
-
出错 → `kit.components.BlockerState` + `kit.
|
|
169
|
+
出错 → `kit.components.BlockerState` + `kit.lib.blockerOfError(error, t)`。
|
|
170
170
|
⚠️ **`idle` 是独立的一格**(没工作区、或取数被 `enabled: false` 关掉时),不许画成"空" ——
|
|
171
171
|
"空"和"还没开始"是两件事,说错了他会去查他根本没配的东西。
|
|
172
172
|
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
| 工具 | 干什么 |
|
|
46
46
|
|---|---|
|
|
47
47
|
| `_record_prepare` | 开一扇窗**顶到最前**(**要用户点头一次**)、装浮条、臂上捕获。参数 `unit` / `url` / `doing`,可选 `expect:{method,urlContains}`;**不给 `url` 当场拒**,**跨域也当场拒**(那个模块的 `design.json` 说过读源时,只许同域录;还没说过就不拦)。返回带 `wrote: false` |
|
|
48
|
-
| `_record_read` | 取那一段的**清单**:`rows`(每行 `index` / `key` / `at` / `method` / `url` / `status` / `ct` / `size` / `source` / `hasRequest` / `hasResponse` / `likelyWrite` / `marked`)· `hookCaught`(钩子这一段**原始**条数)· `capturedLines` / `capturedInSegment` / `capturedOutside` · `dropped` · `noBody` · `nav` · `captureRound`(会话捕获**最近一轮**成没成:`ok` / `fail` / 空 = 还没回)。**清单一条都不删、也不回体**:页面钩子(`[t0,t1]` 段)与会话捕获(整场按段切)合并,**同名只配掉一条钩子行**(缺的 key / 入参 / 出参由捕获那条补上);捕获里两条同 method+url 是**两次真调用,都留着**。**`expect` 不再是过滤器**:它只给命中的行标 ⭐,⭐ 排在前面(不然只列最后十几条时它可能不在正文里);不设 expect 时 ⭐ = 非 GET 或带 request body
|
|
48
|
+
| `_record_read` | 取那一段的**清单**:`rows`(每行 `index` / `key` / `at` / `method` / `url` / `status` / `ct` / `size` / `source` / `hasRequest` / `hasResponse` / `likelyWrite` / `marked`)· `hookCaught`(钩子这一段**原始**条数)· `capturedLines` / `capturedInSegment` / `capturedOutside` · `dropped` · `noBody` · `nav` · `captureRound`(会话捕获**最近一轮**成没成:`ok` / `fail` / 空 = 还没回)。**清单一条都不删、也不回体**:页面钩子(`[t0,t1]` 段)与会话捕获(整场按段切)合并,**同名只配掉一条钩子行**(缺的 key / 入参 / 出参由捕获那条补上);捕获里两条同 method+url 是**两次真调用,都留着**。**`expect` 不再是过滤器**:它只给命中的行标 ⭐,⭐ 排在前面(不然只列最后十几条时它可能不在正文里);不设 expect 时 ⭐ = 非 GET 或带 request body。**正文每一行把它要的两样直接摆出来**:`· [0] ⭐ POST 站点/路径(捕获 · 入参 有 · 出参 40 B · at 1792583917392)` —— 开头那串就是 `key` 原文(opencli 给的,可能带 `#2`),`at` 是毫秒时间戳;**没有 `key` 的钩子行**写成 `method url(钩子 · … · 无 key)`,那种行只在还开着时按 `index` 取得到。要哪一条的原文就带它的 `key`(或 `index`)去 `_record_detail`。没按开始 / 还在录都如实说,不假装"没有请求"。可选 `waitMs`(默认 0 = 立刻回,帽 15 分钟):等不到就回 `timedOut` 并挂一条 notice 叫醒你 —— **一场就是一个周期(开始 → 结束)**,结束之后要再录就**重新 `_record_prepare` 一场**(同场没有"再按一次开始"这回事)。**他按「结束录制」不用他再跟你说一声**:录制期间宿主每 3 秒自己看一眼那一页(浮条在目标页里、推不到宿主;宿主能叫醒你的通道只有 `deliverDebugNotice`),一看到 `finished` 就推状态并发一条 notice 叫你来 `read` |
|
|
49
49
|
| `_record_detail` | 取**某一条**的原文:入参(`request.body`)与出参(`response body`),外加请求头(可能含凭据,别落进 `design.json`)。参数 `key`(清单里那一条,原样给、含 `#2` 这种后缀)或 `index`(清单里的序号,只在那一场还开着时可用);**同一个 key 跨轮会重名**(opencli 的 key 计数器每批重置),所以再带上清单里那行的 `at` 才钉得住是哪一次(不给就取最早那条,并在正文里说重名几次)。可选 `unit` —— 那一场**收工之后**要给 unit,我去读 `shots/rec-<unit>.jsonl.last`(`_record_stop` 会把原文留成 `.last`,下一场 prepare 开录时清掉它)。取不到时如实说"两边都没有",并给兜底:DevTools 复制 curl。可选 `maxBody`(默认 60000 字符,0 = 不限) |
|
|
50
50
|
| `_record_stop` | 停捕获、收掉那个标签、清状态(**收工前先读一次捕获文件**,与 read 同一套切段 / 合并;**原文另存成 `.last`**,所以收工之后还能 `_record_detail`;没挪成就不说"原文留在…")。回一段能直接落 `design.json` 的原文(`probeCommand`)—— **只认第一条带 body 的**;一条都没有就回空串,并如实说"只有方法 / URL,body 拿不到 —— 补到 body 再落"(没 body 的写命令落下去是一条**会打错的**命令)。**先 read(或先 detail 完)再 stop** |
|
|
51
51
|
|