@pi-harness/pi-harness 0.1.1
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 +161 -0
- package/apps/web/dist/assets/index-C4tLweAB.js +20 -0
- package/apps/web/dist/assets/index-DPgRbQ4_.css +1 -0
- package/apps/web/dist/index.html +14 -0
- package/apps/web/index.html +13 -0
- package/apps/web/package.json +24 -0
- package/apps/web/profile/cordis.yml +44 -0
- package/apps/web/server-dist/bin.d.ts +3 -0
- package/apps/web/server-dist/bin.d.ts.map +1 -0
- package/apps/web/server-dist/bin.js +67 -0
- package/apps/web/server-dist/bin.js.map +1 -0
- package/apps/web/src/command-palette.ts +84 -0
- package/apps/web/src/file-diff.ts +42 -0
- package/apps/web/src/main.ts +12 -0
- package/apps/web/src/runtime.css +4 -0
- package/apps/web/src/server/bin.ts +64 -0
- package/apps/web/src/style.css +7 -0
- package/apps/web/tsconfig.build.json +1 -0
- package/apps/web/tsconfig.json +1 -0
- package/package.json +48 -0
- package/packages/api-gateway/dist/index.d.ts +8 -0
- package/packages/api-gateway/dist/index.d.ts.map +1 -0
- package/packages/api-gateway/dist/index.js +582 -0
- package/packages/api-gateway/dist/index.js.map +1 -0
- package/packages/api-gateway/package.json +29 -0
- package/packages/api-gateway/src/index.ts +577 -0
- package/packages/api-gateway/test/index.test.ts +327 -0
- package/packages/api-gateway/tsconfig.build.json +1 -0
- package/packages/api-gateway/tsconfig.json +1 -0
- package/packages/bundle-web-app/cordis.patch.yml +12 -0
- package/packages/bundle-web-app/dist/index.d.ts +12 -0
- package/packages/bundle-web-app/dist/index.d.ts.map +1 -0
- package/packages/bundle-web-app/dist/index.js +63 -0
- package/packages/bundle-web-app/dist/index.js.map +1 -0
- package/packages/bundle-web-app/package.json +30 -0
- package/packages/bundle-web-app/src/index.ts +69 -0
- package/packages/bundle-web-app/test/index.test.ts +29 -0
- package/packages/bundle-web-app/tsconfig.build.json +1 -0
- package/packages/bundle-web-app/tsconfig.json +1 -0
- package/packages/cli/dist/args.d.ts +16 -0
- package/packages/cli/dist/args.d.ts.map +1 -0
- package/packages/cli/dist/args.js +53 -0
- package/packages/cli/dist/args.js.map +1 -0
- package/packages/cli/dist/bin.d.ts +3 -0
- package/packages/cli/dist/bin.d.ts.map +1 -0
- package/packages/cli/dist/bin.js +40 -0
- package/packages/cli/dist/bin.js.map +1 -0
- package/packages/cli/dist/main.d.ts +14 -0
- package/packages/cli/dist/main.d.ts.map +1 -0
- package/packages/cli/dist/main.js +148 -0
- package/packages/cli/dist/main.js.map +1 -0
- package/packages/cli/dist/node-stdio.d.ts +10 -0
- package/packages/cli/dist/node-stdio.d.ts.map +1 -0
- package/packages/cli/dist/node-stdio.js +34 -0
- package/packages/cli/dist/node-stdio.js.map +1 -0
- package/packages/cli/dist/relaunch.d.ts +5 -0
- package/packages/cli/dist/relaunch.d.ts.map +1 -0
- package/packages/cli/dist/relaunch.js +52 -0
- package/packages/cli/dist/relaunch.js.map +1 -0
- package/packages/cli/package.json +25 -0
- package/packages/cli/src/args.ts +50 -0
- package/packages/cli/src/bin.ts +41 -0
- package/packages/cli/src/main.ts +155 -0
- package/packages/cli/src/node-stdio.ts +40 -0
- package/packages/cli/src/relaunch.ts +48 -0
- package/packages/cli/test/args.test.ts +28 -0
- package/packages/cli/test/cli.test.ts +153 -0
- package/packages/cli/test/relaunch.test.ts +29 -0
- package/packages/cli/tsconfig.build.json +11 -0
- package/packages/cli/tsconfig.json +10 -0
- package/packages/client-web/dist/control-room.d.ts +102 -0
- package/packages/client-web/dist/control-room.d.ts.map +1 -0
- package/packages/client-web/dist/control-room.js +12 -0
- package/packages/client-web/dist/control-room.js.map +1 -0
- package/packages/client-web/dist/design-contract.d.ts +85 -0
- package/packages/client-web/dist/design-contract.d.ts.map +1 -0
- package/packages/client-web/dist/design-contract.js +99 -0
- package/packages/client-web/dist/design-contract.js.map +1 -0
- package/packages/client-web/dist/index.d.ts +17 -0
- package/packages/client-web/dist/index.d.ts.map +1 -0
- package/packages/client-web/dist/index.js +19 -0
- package/packages/client-web/dist/index.js.map +1 -0
- package/packages/client-web/dist/react-room.d.ts +6 -0
- package/packages/client-web/dist/react-room.d.ts.map +1 -0
- package/packages/client-web/dist/react-room.js +103 -0
- package/packages/client-web/dist/react-room.js.map +1 -0
- package/packages/client-web/package.json +29 -0
- package/packages/client-web/src/control-room.ts +10 -0
- package/packages/client-web/src/design-contract.ts +112 -0
- package/packages/client-web/src/index.ts +21 -0
- package/packages/client-web/src/react-room.tsx +78 -0
- package/packages/client-web/test/design-contract.test.ts +24 -0
- package/packages/client-web/tsconfig.build.json +1 -0
- package/packages/client-web/tsconfig.json +1 -0
- package/packages/core/dist/boot.d.ts +13 -0
- package/packages/core/dist/boot.d.ts.map +1 -0
- package/packages/core/dist/boot.js +96 -0
- package/packages/core/dist/boot.js.map +1 -0
- package/packages/core/dist/context.d.ts +3 -0
- package/packages/core/dist/context.d.ts.map +1 -0
- package/packages/core/dist/context.js +2 -0
- package/packages/core/dist/context.js.map +1 -0
- package/packages/core/dist/index.d.ts +7 -0
- package/packages/core/dist/index.d.ts.map +1 -0
- package/packages/core/dist/index.js +6 -0
- package/packages/core/dist/index.js.map +1 -0
- package/packages/core/dist/plugins/model.d.ts +8 -0
- package/packages/core/dist/plugins/model.d.ts.map +1 -0
- package/packages/core/dist/plugins/model.js +12 -0
- package/packages/core/dist/plugins/model.js.map +1 -0
- package/packages/core/dist/plugins/models.d.ts +16 -0
- package/packages/core/dist/plugins/models.d.ts.map +1 -0
- package/packages/core/dist/plugins/models.js +24 -0
- package/packages/core/dist/plugins/models.js.map +1 -0
- package/packages/core/dist/plugins/resources.d.ts +18 -0
- package/packages/core/dist/plugins/resources.d.ts.map +1 -0
- package/packages/core/dist/plugins/resources.js +48 -0
- package/packages/core/dist/plugins/resources.js.map +1 -0
- package/packages/core/dist/plugins/runtime.d.ts +14 -0
- package/packages/core/dist/plugins/runtime.d.ts.map +1 -0
- package/packages/core/dist/plugins/runtime.js +68 -0
- package/packages/core/dist/plugins/runtime.js.map +1 -0
- package/packages/core/dist/plugins/session.d.ts +15 -0
- package/packages/core/dist/plugins/session.d.ts.map +1 -0
- package/packages/core/dist/plugins/session.js +20 -0
- package/packages/core/dist/plugins/session.js.map +1 -0
- package/packages/core/dist/plugins/stdio.d.ts +8 -0
- package/packages/core/dist/plugins/stdio.d.ts.map +1 -0
- package/packages/core/dist/plugins/stdio.js +20 -0
- package/packages/core/dist/plugins/stdio.js.map +1 -0
- package/packages/core/dist/plugins/tools.d.ts +13 -0
- package/packages/core/dist/plugins/tools.d.ts.map +1 -0
- package/packages/core/dist/plugins/tools.js +17 -0
- package/packages/core/dist/plugins/tools.js.map +1 -0
- package/packages/core/dist/profile.d.ts +8 -0
- package/packages/core/dist/profile.d.ts.map +1 -0
- package/packages/core/dist/profile.js +28 -0
- package/packages/core/dist/profile.js.map +1 -0
- package/packages/core/dist/runtime.d.ts +16 -0
- package/packages/core/dist/runtime.d.ts.map +1 -0
- package/packages/core/dist/runtime.js +33 -0
- package/packages/core/dist/runtime.js.map +1 -0
- package/packages/core/dist/services.d.ts +63 -0
- package/packages/core/dist/services.d.ts.map +1 -0
- package/packages/core/dist/services.js +39 -0
- package/packages/core/dist/services.js.map +1 -0
- package/packages/core/dist/stdio.d.ts +25 -0
- package/packages/core/dist/stdio.d.ts.map +1 -0
- package/packages/core/dist/stdio.js +74 -0
- package/packages/core/dist/stdio.js.map +1 -0
- package/packages/core/package.json +44 -0
- package/packages/core/profiles/default/cordis.yml +35 -0
- package/packages/core/profiles/development/cordis.yml +47 -0
- package/packages/core/src/boot.ts +103 -0
- package/packages/core/src/context.ts +3 -0
- package/packages/core/src/index.ts +6 -0
- package/packages/core/src/plugins/model.ts +12 -0
- package/packages/core/src/plugins/models.ts +32 -0
- package/packages/core/src/plugins/resources.ts +57 -0
- package/packages/core/src/plugins/runtime.ts +73 -0
- package/packages/core/src/plugins/session.ts +27 -0
- package/packages/core/src/plugins/stdio.ts +20 -0
- package/packages/core/src/plugins/tools.ts +23 -0
- package/packages/core/src/profile.ts +31 -0
- package/packages/core/src/runtime.ts +39 -0
- package/packages/core/src/services.ts +105 -0
- package/packages/core/src/stdio.ts +88 -0
- package/packages/core/test/boot.test.ts +86 -0
- package/packages/core/test/profile.test.ts +39 -0
- package/packages/core/test/profiles.integration.test.ts +93 -0
- package/packages/core/test/runtime-fixture.ts +31 -0
- package/packages/core/test/runtime.test.ts +54 -0
- package/packages/core/test/services.test.ts +131 -0
- package/packages/core/test/stdio.test.ts +87 -0
- package/packages/core/tsconfig.build.json +10 -0
- package/packages/core/tsconfig.json +10 -0
- package/packages/host-webserver/dist/index.d.ts +29 -0
- package/packages/host-webserver/dist/index.d.ts.map +1 -0
- package/packages/host-webserver/dist/index.js +73 -0
- package/packages/host-webserver/dist/index.js.map +1 -0
- package/packages/host-webserver/package.json +27 -0
- package/packages/host-webserver/src/index.ts +97 -0
- package/packages/host-webserver/test/index.test.ts +43 -0
- package/packages/host-webserver/tsconfig.build.json +1 -0
- package/packages/host-webserver/tsconfig.json +1 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export interface DesignWorkspace { readonly path: string; readonly sessions: string; readonly live: boolean; readonly branch: string; readonly dirty: string; }
|
|
2
|
+
export interface DesignSession { readonly id: string; readonly title: string; readonly meta: string; readonly state: "running" | "ok" | "warn" | "error"; readonly group: "今天" | "昨天" | "更早"; readonly ws: string; }
|
|
3
|
+
export interface DesignTurn { readonly kind: "user" | "reasoning" | "plugin" | "tool" | "text" | "approval" | "subagent" | "todo" | "stats"; readonly text?: string; readonly name?: string; readonly hook?: string; readonly target?: string; readonly output?: string; readonly dur?: string; readonly stat?: string; readonly statFg?: string; readonly glyph?: string; readonly isDiff?: boolean; readonly diff?: readonly { no: string; sign: string; text: string }[]; readonly preset?: string; readonly goal?: string; readonly lineage?: string; readonly steps?: readonly { tool: string; target: string; dur: string }[]; readonly verdict?: string; readonly progress?: string; readonly todos?: readonly { text: string; done: boolean }[]; readonly stats?: readonly string[]; }
|
|
4
|
+
export interface DesignEvent { readonly ts: string; readonly source: string; readonly summary: string; readonly by: string; readonly dur: string; }
|
|
5
|
+
export interface DesignPlugin { readonly name: string; readonly ver: string; readonly capability: string; readonly on: boolean; readonly update: boolean; readonly local?: boolean; readonly desc: string; readonly hooks: readonly string[]; }
|
|
6
|
+
export interface DesignProvider { readonly id: string; readonly name: string; readonly state: string; readonly ok: boolean; readonly protocol: "openai" | "anthropic"; readonly baseUrl: string; readonly key: string; readonly models: readonly string[]; readonly latency: string; readonly by: string; }
|
|
7
|
+
|
|
8
|
+
export const DESIGN_WORKSPACES: readonly DesignWorkspace[] = [
|
|
9
|
+
{ path: "~/work/checkout-svc", sessions: "6", live: true, branch: "fix/3ds-timeout", dirty: "4 处未提交" },
|
|
10
|
+
{ path: "~/work/pi-harness", sessions: "12", live: false, branch: "main", dirty: "干净" },
|
|
11
|
+
{ path: "~/scratch/spike-otel", sessions: "2", live: false, branch: "spike", dirty: "12 处未提交" },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export const DESIGN_SESSIONS: readonly DesignSession[] = [
|
|
15
|
+
{ id: "s1", title: "修复 3DS 超时导致的支付失败", meta: "ds-coder-v3 · 14 步 · 4 分钟前", state: "running", group: "今天", ws: "~/work/checkout-svc" },
|
|
16
|
+
{ id: "s2", title: "为 refund 模块补单测", meta: "ds-coder-v3 · 22 步 · 1 小时前", state: "ok", group: "今天", ws: "~/work/checkout-svc" },
|
|
17
|
+
{ id: "s3", title: "梳理仓库结构与主要包", meta: "ds-chat · 6 步 · 3 小时前", state: "ok", group: "今天", ws: "~/work/pi-harness" },
|
|
18
|
+
{ id: "s4", title: "把 webhook 重试改成指数退避", meta: "ds-coder-v3 · 31 步 · 昨天", state: "warn", group: "昨天", ws: "~/work/checkout-svc" },
|
|
19
|
+
{ id: "s5", title: "升级 pnpm 到 10 并修构建", meta: "ds-coder-v3 · 48 步 · 昨天", state: "error", group: "昨天", ws: "~/work/pi-harness" },
|
|
20
|
+
{ id: "s6", title: "给 CI 加 e2e 冒烟任务", meta: "ds-coder-v3 · 19 步 · 本周一", state: "ok", group: "更早", ws: "~/scratch/spike-otel" },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export const DESIGN_TURNS: readonly DesignTurn[] = [
|
|
24
|
+
{ kind: "user", text: "支付成功率从昨天上线后掉了很多,日志里全是 3DS challenge timeout。定位一下,能改就直接改,改完跑测试。" },
|
|
25
|
+
{ kind: "reasoning", dur: "6s", text: "先找超时配置在哪定义,再看是不是 2.4.0 版本引入的默认值变化。优先只读检索,确认后再动代码。" },
|
|
26
|
+
{ kind: "plugin", name: "context-repo-map", hook: "context_provider", text: "注入仓库地图与最近 3 次提交摘要(1,842 tok)" },
|
|
27
|
+
{ kind: "tool", glyph: "⌕", name: "grep", target: "\"3DS\" src/**/*.ts", dur: "0.4s", stat: "12 命中", statFg: "#61666B", output: "src/payment/threeds.ts:41 const CHALLENGE_TIMEOUT_MS = 8_000\nsrc/payment/threeds.ts:88 if (elapsed > CHALLENGE_TIMEOUT_MS) throw new ChallengeTimeout\nsrc/payment/gateway.ts:114 retries: 0\ntest/payment/threeds.spec.ts:23 vi.advanceTimersByTime(8_100)" },
|
|
28
|
+
{ kind: "tool", glyph: "▤", name: "read", target: "src/payment/threeds.ts:30-96", dur: "0.1s", stat: "66 行", statFg: "#61666B", output: "const CHALLENGE_TIMEOUT_MS = 8_000\n\nexport async function runChallenge(ctx: Ctx) {\n const started = now()\n const res = await sdk.challenge(ctx.token) // sdk 2.4.0\n const elapsed = now() - started\n if (elapsed > CHALLENGE_TIMEOUT_MS) throw new ChallengeTimeout()\n return res\n}" },
|
|
29
|
+
{ kind: "text", text: "原因找到了:sdk 2.4.0 起 iOS 端 challenge 平均耗时从 3.1s 涨到 9.4s,而我们的超时仍是 8s,且 retries 为 0,一次超时就直接失败。\n\n我按两处改:超时放宽到 20s,并加一次带抖动的重试。" },
|
|
30
|
+
{ kind: "tool", glyph: "✎", name: "edit", target: "src/payment/threeds.ts", dur: "0.2s", stat: "+9 −2", statFg: "#22C55E", isDiff: true, diff: [{ no: "41", sign: "-", text: "const CHALLENGE_TIMEOUT_MS = 8_000" }, { no: "41", sign: "+", text: "const CHALLENGE_TIMEOUT_MS = 20_000" }, { no: "42", sign: "+", text: "const CHALLENGE_MAX_ATTEMPTS = 2" }, { no: "86", sign: "-", text: " const res = await sdk.challenge(ctx.token)" }, { no: "86", sign: "+", text: " const res = await withRetry(CHALLENGE_MAX_ATTEMPTS, () =>" }, { no: "87", sign: "+", text: " sdk.challenge(ctx.token), { jitter: true })" }] },
|
|
31
|
+
{ kind: "plugin", name: "guard-secret-scan", hook: "before_write", text: "改动无凭据外泄风险,放行" },
|
|
32
|
+
{ kind: "approval", name: "bash — pnpm vitest run test/payment", text: "cd ~/work/checkout-svc && pnpm vitest run test/payment --reporter=dot" },
|
|
33
|
+
{ kind: "tool", glyph: "$", name: "bash", target: "pnpm vitest run test/payment", dur: "12.4s", stat: "exit 0", statFg: "#22C55E", output: "···········\n Test Files 3 passed (3)\n Tests 27 passed (27)\n Duration 11.8s\n\n✓ threeds.spec.ts (9)\n✓ gateway.spec.ts (12)\n✓ webhook.spec.ts (6)" },
|
|
34
|
+
{ kind: "subagent", name: "reviewer", preset: "read-only", goal: "复核 threeds.ts 的改动是否引入回归", dur: "8.1s", lineage: "session #s1 › turn 6 › subagent reviewer(继承 workspace,权限降级为只读)", steps: [{ tool: "read", target: "src/payment/threeds.ts", dur: "0.1s" }, { tool: "read", target: "src/payment/with-retry.ts", dur: "0.1s" }, { tool: "grep", target: "\"CHALLENGE_TIMEOUT_MS\" test/**", dur: "0.3s" }], verdict: "改动本身正确。两点提醒:test/payment/threeds.spec.ts:23 仍按 8s 断言,会在下次改动时误报通过;withRetry 的抖动上限没设,最坏情况下总耗时可能到 44s,建议加 maxTotalMs。" },
|
|
35
|
+
{ kind: "todo", progress: "3 / 4", todos: [{ text: "定位超时来源", done: true }, { text: "放宽超时并加重试", done: true }, { text: "跑 payment 相关测试", done: true }, { text: "补一条针对 20s 边界的回归用例", done: false }] },
|
|
36
|
+
{ kind: "text", text: "测试全绿。还剩一条回归用例没补:现有 spec 里 `advanceTimersByTime(8_100)` 断言的是旧阈值,需要改成 20s 边界并新增一条「首次超时后重试成功」的用例。要我继续补吗?" },
|
|
37
|
+
{ kind: "stats", stats: ["输入 34.2k tok", "缓存命中 61%", "输出 4.8k tok", "用时 1m 42s", "$0.28"] },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
export const DESIGN_EVENTS: readonly DesignEvent[] = [
|
|
41
|
+
{ ts: "02:14:01", source: "system_prompt", summary: "系统提示词组装(preset: coder)", by: "内置", dur: "12ms" },
|
|
42
|
+
{ ts: "02:14:01", source: "context_injection", summary: "注入仓库地图与最近提交(1,842 tok)", by: "context-repo-map", dur: "88ms" },
|
|
43
|
+
{ ts: "02:14:02", source: "user_message", summary: "支付成功率下降,定位并修复", by: "用户", dur: "—" },
|
|
44
|
+
{ ts: "02:14:03", source: "reasoning", summary: "先只读检索超时配置,再判断是否与 sdk 版本相关", by: "ds-coder-v3", dur: "6.0s" },
|
|
45
|
+
{ ts: "02:14:09", source: "tool_call", summary: "grep \"3DS\" src/**/*.ts", by: "内置", dur: "0.4s" },
|
|
46
|
+
{ ts: "02:14:10", source: "tool_result", summary: "12 命中,定位 threeds.ts:41", by: "内置", dur: "—" },
|
|
47
|
+
{ ts: "02:14:11", source: "tool_call", summary: "read src/payment/threeds.ts:30-96", by: "内置", dur: "0.1s" },
|
|
48
|
+
{ ts: "02:14:14", source: "tool_call", summary: "edit src/payment/threeds.ts (+9 −2)", by: "内置", dur: "0.2s" },
|
|
49
|
+
{ ts: "02:14:14", source: "plugin_hook", summary: "凭据扫描通过,放行写入", by: "guard-secret-scan", dur: "31ms" },
|
|
50
|
+
{ ts: "02:14:15", source: "approval", summary: "用户允许一次:bash pnpm vitest", by: "用户", dur: "4.2s" },
|
|
51
|
+
{ ts: "02:14:19", source: "tool_call", summary: "bash pnpm vitest run test/payment", by: "内置", dur: "12.4s" },
|
|
52
|
+
{ ts: "02:14:32", source: "tool_result", summary: "27 passed,exit 0", by: "内置", dur: "—" },
|
|
53
|
+
{ ts: "02:14:33", source: "subagent", summary: "派生 reviewer 子代理复核 diff", by: "subagent-review", dur: "8.1s" },
|
|
54
|
+
{ ts: "02:14:41", source: "assistant_message", summary: "给出结论与剩余一条回归用例建议", by: "ds-coder-v3", dur: "2.6s" },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
export const DESIGN_PLUGINS: readonly DesignPlugin[] = [
|
|
58
|
+
{ name: "context-repo-map", ver: "1.4.2", capability: "上下文", on: true, update: false, desc: "把仓库结构、入口文件与最近提交压缩成上下文,超预算时自动裁剪", hooks: ["context_provider", "before_step"] },
|
|
59
|
+
{ name: "guard-secret-scan", ver: "2.0.1", capability: "策略", on: true, update: true, desc: "写入与提交前扫描凭据、私钥、token,命中即拦截并说明位置", hooks: ["before_write", "before_commit"] },
|
|
60
|
+
{ name: "subagent-review", ver: "0.8.5", capability: "子代理", on: true, update: false, desc: "改动落盘后派生只读 reviewer 子代理复核 diff,产出可执行意见", hooks: ["after_write"] },
|
|
61
|
+
{ name: "sandbox-landlock", ver: "1.1.0", capability: "沙箱", on: true, update: false, desc: "Linux Landlock 限制子进程可写路径与出网,越权直接拒绝", hooks: ["shell_exec"] },
|
|
62
|
+
{ name: "model-deepseek", ver: "3.2.0", capability: "模型", on: true, update: true, desc: "DeepSeek 官方模型路由,支持 prompt 缓存与推理内容透传", hooks: ["model_provider"] },
|
|
63
|
+
{ name: "store-sqlite", ver: "1.0.7", capability: "存储", on: true, update: false, desc: "会话事件流落地到本地 SQLite,支持 resume、fork、replay", hooks: ["session_store"] },
|
|
64
|
+
{ name: "vision-handoff", ver: "path", capability: "上下文", on: true, update: false, local: true, desc: "图片交给独立视觉模型描述后回填上下文,从本地路径加载(../pi-pkg-vision-handoff)", hooks: ["context_provider"] },
|
|
65
|
+
{ name: "notify-webhook", ver: "0.3.1", capability: "通知", on: false, update: false, desc: "任务结束或需要确认时推送到自定义 webhook", hooks: ["after_turn"] },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
export const DESIGN_PROVIDERS: readonly DesignProvider[] = [
|
|
69
|
+
{ id: "deepseek", name: "DeepSeek", state: "已连接", ok: true, protocol: "openai", baseUrl: "https://api.deepseek.com/v1", key: "sk-••••••••••••••••7f3a", models: ["ds-coder-v3", "ds-chat", "ds-reasoner"], latency: "41ms", by: "model-deepseek" },
|
|
70
|
+
{ id: "anthropic", name: "Anthropic", state: "已连接", ok: true, protocol: "anthropic", baseUrl: "https://api.anthropic.com", key: "sk-ant-••••••••••2c91", models: ["claude-sonnet", "claude-haiku"], latency: "128ms", by: "model-anthropic" },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
export const DESIGN_TOML = `# ~/.config/pi/pi.toml
|
|
74
|
+
# 一切都是包:模型、工具、沙箱、存储、界面都在这里装卸。
|
|
75
|
+
|
|
76
|
+
[agent]
|
|
77
|
+
preset = "coder"
|
|
78
|
+
model = "ds-coder-v3"
|
|
79
|
+
max_steps = 40
|
|
80
|
+
budget_usd = 1.50
|
|
81
|
+
ask_before = ["write", "shell", "network"]
|
|
82
|
+
|
|
83
|
+
[sandbox]
|
|
84
|
+
backend = "landlock"
|
|
85
|
+
writable = ["."]
|
|
86
|
+
network = false
|
|
87
|
+
|
|
88
|
+
[context]
|
|
89
|
+
window = 64000
|
|
90
|
+
compact_at = 0.82
|
|
91
|
+
|
|
92
|
+
[[packages]]
|
|
93
|
+
name = "context-repo-map"
|
|
94
|
+
version = "1.4.2"
|
|
95
|
+
|
|
96
|
+
[[packages]]
|
|
97
|
+
name = "guard-secret-scan"
|
|
98
|
+
version = "2.0.1"
|
|
99
|
+
|
|
100
|
+
[[packages]]
|
|
101
|
+
name = "subagent-review"
|
|
102
|
+
version = "0.8.5"
|
|
103
|
+
|
|
104
|
+
[[packages]]
|
|
105
|
+
path = "../pi-pkg-vision-handoff" # 本地开发中
|
|
106
|
+
|
|
107
|
+
[keys]
|
|
108
|
+
palette = "cmd+k"
|
|
109
|
+
settings = "cmd+,"
|
|
110
|
+
interrupt = "ctrl+c"
|
|
111
|
+
`;
|
|
112
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createElement } from "react";
|
|
2
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
3
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
4
|
+
import { ControlRoomView } from "./react-room.js";
|
|
5
|
+
import { createClientApi, type ClientApi } from "./control-room.js";
|
|
6
|
+
|
|
7
|
+
declare module "@deepseek-ai/cordis" { interface Context { clientRoot: HTMLElement; clientApi: ClientApi; } }
|
|
8
|
+
|
|
9
|
+
export class AppWebEntry {
|
|
10
|
+
readonly context: Context;
|
|
11
|
+
readonly root: HTMLElement;
|
|
12
|
+
#reactRoot?: Root;
|
|
13
|
+
constructor(root: HTMLElement) { this.root = root; this.context = new Context(); }
|
|
14
|
+
run(): void {
|
|
15
|
+
this.context.provide("clientRoot", this.root);
|
|
16
|
+
this.context.provide("clientApi", createClientApi());
|
|
17
|
+
this.#reactRoot = createRoot(this.root);
|
|
18
|
+
this.#reactRoot.render(createElement(ControlRoomView, { api: this.context.clientApi }));
|
|
19
|
+
}
|
|
20
|
+
async dispose(): Promise<void> { this.#reactRoot?.unmount(); await this.context.fiber.dispose(); }
|
|
21
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState, type FormEvent } from "react";
|
|
2
|
+
import { createClientApi, type ClientApi, type ClientCommand, type ClientFile, type ClientModel, type ClientPlugin, type ClientProvider, type ClientSession, type ClientStatus } from "./control-room.js";
|
|
3
|
+
|
|
4
|
+
export type { ClientApi } from "./control-room.js";
|
|
5
|
+
|
|
6
|
+
type View = "chat" | "trajectory" | "files";
|
|
7
|
+
type SettingsTab = "general" | "plugins" | "providers" | "toml";
|
|
8
|
+
type Page = "session" | "plugins";
|
|
9
|
+
interface RoomData { status?: ClientStatus; session?: ClientSession; sessions: readonly Record<string, unknown>[]; files: readonly ClientFile[]; models: readonly ClientModel[]; providers: readonly ClientProvider[]; plugins: readonly ClientPlugin[]; commands: readonly ClientCommand[]; }
|
|
10
|
+
|
|
11
|
+
const value = (input: unknown, fallback = "—"): string => { if (input === undefined || input === null || input === "") return fallback; if (typeof input === "string" || typeof input === "number" || typeof input === "boolean" || typeof input === "bigint") return String(input); try { return JSON.stringify(input); } catch { return fallback; } };
|
|
12
|
+
const messageText = (message: Record<string, unknown>): string => { const content = message.content; if (typeof content === "string") return content; if (Array.isArray(content)) return content.map((part) => typeof part === "string" ? part : typeof part === "object" && part !== null && typeof (part as { text?: unknown }).text === "string" ? (part as { text: string }).text : "").join(""); return ""; };
|
|
13
|
+
const eventLabel = (event: Record<string, unknown>): string => value(event.summary ?? event.message ?? event.toolName ?? event.type ?? event.event, "未命名事件");
|
|
14
|
+
const capability = (name: string): string => name.includes("model") ? "模型" : name.includes("tool") ? "工具" : name.includes("session") ? "会话" : name.includes("resource") ? "资源" : name.includes("web") || name.includes("gateway") ? "界面" : "运行时";
|
|
15
|
+
function sessionGroups(sessions: readonly Record<string, unknown>[]): readonly [string, readonly Record<string, unknown>[]][] { const now = new Date(); const start = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); const yesterday = start - 86_400_000; const groups = new Map<string, Record<string, unknown>[]>(); for (const session of sessions) { const raw = session.modified ?? session.created; const timestamp = typeof raw === "number" ? raw : typeof raw === "string" ? Date.parse(raw) : Number.NaN; const label = Number.isFinite(timestamp) && timestamp >= start ? "今天" : Number.isFinite(timestamp) && timestamp >= yesterday ? "昨天" : "更早"; groups.set(label, [...(groups.get(label) ?? []), session]); } return ["今天", "昨天", "更早"].flatMap((label) => { const items = groups.get(label); return items?.length ? [[label, items] as const] : []; }); }
|
|
16
|
+
|
|
17
|
+
function RuntimeCard({ event }: { event: Record<string, unknown> }) {
|
|
18
|
+
const type = value(event.type, "event");
|
|
19
|
+
const output = event.output ?? event.result ?? event.message;
|
|
20
|
+
if (type === "tool_execution_start" || type === "tool_execution_end") return <article className="turn runtime-event"><details className="tool-card" open={type === "tool_execution_end"}><summary className="tool-head"><b>{type === "tool_execution_end" ? "✓" : "▤"} {value(event.toolName ?? event.name, "tool")}</b><span className="tool-target">{value(event.args ?? event.toolCallId ?? event.duration, "运行时工具")}</span></summary>{output !== undefined && <pre className="tool-output">{typeof output === "string" ? output : JSON.stringify(output, null, 2)}</pre>}</details></article>;
|
|
21
|
+
if (type === "message_update" && typeof event.assistantMessageEvent === "object" && event.assistantMessageEvent !== null && (event.assistantMessageEvent as { type?: unknown }).type === "thinking_delta") return <article className="turn runtime-event"><details className="reasoning"><summary className="reasoning-head">思考</summary><p className="reasoning-body">{value((event.assistantMessageEvent as { delta?: unknown }).delta, "")}</p></details></article>;
|
|
22
|
+
if (["agent_start", "turn_start"].includes(type)) return <article className="turn runtime-event"><div className="plugin-event">▷ {type === "agent_start" ? "agent 开始" : "新一轮开始"}</div></article>;
|
|
23
|
+
if (["agent_end", "agent_settled", "turn_end"].includes(type)) return <article className="turn runtime-event"><div className="stats-row">✓ {type === "agent_settled" ? "agent 已停止" : type === "agent_end" ? "agent 完成" : "本轮完成"}</div></article>;
|
|
24
|
+
if (type === "queue_update") return <article className="turn runtime-event"><div className="plugin-event">队列更新</div><p className="turn-meta">steering {Array.isArray(event.steering) ? event.steering.length : value(event.steering, "0")} · follow-up {Array.isArray(event.followUp) ? event.followUp.length : value(event.followUp, "0")}</p></article>;
|
|
25
|
+
if (type.startsWith("compaction") || type.startsWith("auto_retry") || type.startsWith("summarization") || type === "bash_execution_update") return <article className="turn runtime-event"><div className="plugin-event">◈ {type}</div><p className="turn-meta">{typeof output === "string" ? output : JSON.stringify(event)}</p></article>;
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Workspace({ status, onCreate, onStarter, onToml }: { status: ClientStatus | undefined; onCreate: () => void; onStarter: (value: string) => void; onToml: () => void }) {
|
|
30
|
+
const starters: readonly [string, string][] = [["定位问题", "分析当前仓库并给出根因"], ["修复并测试", "实现修复并运行相关测试"], ["审查改动", "只读检查未提交变更"], ["解释代码", "解释当前文件的关键逻辑"]];
|
|
31
|
+
return <div className="new-session-screen"><span className="pi-mark large">π</span><h2>新会话</h2><p>选择当前运行时工作区后开始真实会话。</p><div className="workspace-picker"><button className="workspace-row" onClick={onCreate} type="button"><span className="live">●</span><span><code>{status?.cwd ?? "加载工作区…"}</code><small>{status ? `${status.sessionId} · ${status.model}` : "由 /api/status 返回"}</small></span><span>›</span></button><button className="workspace-row" disabled type="button"><span className="offline">+</span><span><code>添加目录…</code><small>当前 API 未提供工作区管理接口</small></span></button></div><div className="effective-config"><code>pi.toml 生效值</code><span>{status?.model ?? "由运行时提供"}</span><span>{status ? `${status.messages} 条消息` : "—"}</span><a href="#" onClick={(event) => { event.preventDefault(); onToml(); }}>改配置 ⌘,</a></div><div className="starter-grid">{starters.map(([title, prompt]) => <button className="starter-card" key={title} onClick={() => onStarter(prompt)} type="button"><strong>{title}</strong><small>{prompt}</small></button>)}</div></div>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function Details({ event, onClose, onCopy }: { event: Record<string, unknown> | undefined; onClose: () => void; onCopy: () => void }) {
|
|
35
|
+
if (!event) return <aside className="details-panel"><header><strong>事件详情</strong><button onClick={onClose} type="button">×</button></header><div className="details-body"><div className="empty-state">选择轨迹中的事件查看原始数据。</div></div></aside>;
|
|
36
|
+
const output = event.output ?? event.result ?? event.message;
|
|
37
|
+
const stats: readonly [string, string][] = [["来源", value(event.type ?? event.source, "event")], ["产生者", value(event.by ?? event.source)], ["耗时", value(event.duration ?? event.dur)], ["时间", value(event.timestamp ?? event.ts ?? event.time)]];
|
|
38
|
+
return <aside className="details-panel"><header><strong>{eventLabel(event)}</strong><button onClick={onClose} type="button">×</button></header><div className="details-body"><div className="detail-stats">{stats.map(([key, item]) => <div key={key}><small>{key}</small><b>{item}</b></div>)}</div>{output !== undefined && <div className="detail-section"><small>输出</small><pre className="tool-output">{typeof output === "string" ? output : JSON.stringify(output, null, 2)}</pre></div>}<div className="detail-section"><small>经过的插件</small><div className="detail-plugin">Cordis loader · runtime event</div></div><div className="detail-actions"><button onClick={onCopy} type="button">复制 JSON</button><button disabled title="当前 API 未提供重放接口" type="button">重放</button></div><details className="raw-json"><summary>原始 JSON</summary><pre className="raw-json-body">{JSON.stringify(event, null, 2)}</pre></details></div></aside>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function Trajectory({ events, onSelect }: { events: readonly Record<string, unknown>[]; onSelect: (event: Record<string, unknown>) => void }) {
|
|
42
|
+
const [filter, setFilter] = useState("all");
|
|
43
|
+
const counts = useMemo(() => { const map = new Map<string, number>(); events.forEach((event) => { const type = value(event.type, "event"); map.set(type, (map.get(type) ?? 0) + 1); }); return map; }, [events]);
|
|
44
|
+
const visible = events.filter((event) => filter === "all" || value(event.type, "event") === filter);
|
|
45
|
+
return <section className="view-panel trajectory-view"><div className="trajectory-summary"><span>按轮次</span><b>{events.length} 个事件</b><div className="timeline">{events.length ? events.map((event, index) => <span className="timeline-turn" key={index}><i className="timeline-strip" title={eventLabel(event)}></i></span>) : <span className="timeline-empty">等待真实事件…</span>}</div></div><div className="source-filters"><button className={`filter ${filter === "all" ? "active" : ""}`} onClick={() => setFilter("all")} type="button">全部 {events.length}</button>{[...counts].map(([type, count]) => <button className={`filter ${filter === type ? "active" : ""}`} key={type} onClick={() => setFilter(type)} type="button">{type} {count}</button>)}</div><div className="event-table"><div className="event-head"><span>时间</span><span>来源</span><span>事件</span><span>产生者</span><span>耗时</span></div>{visible.map((event, index) => <button className="event-row" key={index} onClick={() => onSelect(event)} type="button"><span>{value(event.timestamp ?? event.ts ?? event.time)}</span><span><i className="event-dot"></i>{value(event.type, "event")}</span><strong>{eventLabel(event)}</strong><span>{value(event.by ?? event.source)}</span><span>{value(event.duration ?? event.dur)}</span></button>)}{!visible.length && <div className="empty-state">暂无轨迹事件。</div>}</div></section>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function Files({ files, api, onDiff, onRefresh }: { files: readonly ClientFile[]; api: ClientApi; onDiff: (path: string) => void; onRefresh: () => void }) { const additions = files.filter((file) => file.status.includes("A") || file.status === "??").length; const deletions = files.filter((file) => file.status.includes("D")).length; const [message, setMessage] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const commit = () => { const text = message.trim(); if (!text || busy) return; setBusy(true); setError(""); void api.commitFiles(files.map((file) => file.path), text).then(() => { setMessage(""); onRefresh(); }).catch((cause: unknown) => setError(cause instanceof Error ? cause.message : String(cause))).finally(() => setBusy(false)); }; const revert = () => { if (!files.length || busy || !window.confirm("撤销这些未提交改动?此操作不可恢复。")) return; setBusy(true); setError(""); void api.revertFiles(files.map((file) => file.path)).then(onRefresh).catch((cause: unknown) => setError(cause instanceof Error ? cause.message : String(cause))).finally(() => setBusy(false)); }; return <section className="view-panel files-view"><div className="files-content"><div className="files-title"><strong>本次会话改动</strong><span>由 /api/files 提供</span></div><div className="file-summary">{files.length} 个文件 · {additions} 个新增 · {deletions} 个删除</div><div className="file-list">{files.length ? files.map((file) => <div className="file-row" key={file.path}><b className={`file-kind ${file.label === "untracked" ? "new" : ""}`}>{file.label}</b><code>{file.path}</code><span className={file.status.includes("D") ? "del" : "add"}>{file.status}</span><button className="diff-button" onClick={() => onDiff(file.path)} type="button">查看差异</button></div>) : <div className="empty-state">工作区没有未提交改动。</div>}</div>{files.length > 0 && <div className="file-actions"><input aria-label="提交说明" onChange={(event) => setMessage(event.target.value)} placeholder="提交说明" value={message} /><button className="primary" disabled={busy || !message.trim()} onClick={commit} type="button">{busy ? "处理中…" : "提交这些改动"}</button><button disabled={busy} onClick={revert} type="button">全部撤销</button></div>}{error && <p className="action-error">{error}</p>}</div></section>; }
|
|
49
|
+
|
|
50
|
+
function Plugins({ plugins, tab, onTab, onToml }: { plugins: readonly ClientPlugin[]; tab: "installed" | "extensions"; onTab: (tab: "installed" | "extensions") => void; onToml: () => void }) { const groups = useMemo(() => { const map = new Map<string, string[]>(); plugins.forEach((plugin) => map.set(capability(plugin.name), [...(map.get(capability(plugin.name)) ?? []), plugin.name])); return map; }, [plugins]); return <section className="view-panel plugins-view"><div className="plugins-page"><div className="subnav"><div className="segmented"><button className={tab === "installed" ? "active" : ""} onClick={() => onTab("installed")} type="button">已安装</button><button className={tab === "extensions" ? "active" : ""} onClick={() => onTab("extensions")} type="button">扩展点</button></div><span>运行时插件清单</span><a href="#" onClick={(event) => { event.preventDefault(); onToml(); }}>在 pi.toml 里看这份清单</a></div><div className="plugins-scroll">{tab === "installed" ? <><div className="plugins-list">{plugins.map((plugin) => <article className="plugin-card" key={plugin.id}><div className="plugin-card-head"><span className="plugin-icon">◈</span><div className="plugin-copy"><div className="plugin-title"><code>{plugin.name}</code><small>{plugin.state}</small><span className="capability">{capability(plugin.name)}</span></div><p className="plugin-description">{plugin.enabled ? "由当前 Cordis loader 加载并启用,能力与 hook 由运行时注册。" : "由当前 Cordis loader 加载但已停用。"}</p><div className="hook-list"><span>loader</span><span>{plugin.state === "active" ? "active" : `state:${plugin.state}`}</span></div></div><span className={`switch ${plugin.enabled ? "on" : ""}`}><i></i></span></div></article>)}</div><div className="plugin-add"><code>dsh plugin add</code><input disabled placeholder="github:owner/repo" /><button className="primary" disabled type="button">安装</button></div></> : <><p className="extension-note">每个扩展点由哪些包占用,按 Cordis loader 的执行顺序排列。</p><div className="extension-table"><div className="extension-row extension-head"><span>扩展点</span><span>占用者(按序)</span><span>数量</span></div>{[...groups].map(([point, owners]) => <div className="extension-row" key={point}><code>{point}</code><div className="extension-owners">{owners.map((owner) => <span key={owner}>{owner}</span>)}</div><code>{owners.length}</code></div>)}</div></>}</div></div></section>; }
|
|
51
|
+
|
|
52
|
+
function Settings({ data, api, tab, onTab, onClose }: { data: RoomData; api: ClientApi; tab: SettingsTab; onTab: (tab: SettingsTab) => void; onClose: () => void }) { const status = data.status; const [providerState, setProviderState] = useState<Record<string, string>>({}); const runProviderAction = (provider: string, action: "test" | "refresh") => { setProviderState((current) => ({ ...current, [provider]: action === "test" ? "测试中…" : "刷新中…" })); if (action === "test") void api.testProvider(provider).then((result) => setProviderState((current) => ({ ...current, [provider]: result.reachable ? "连接正常" : "未检测到认证" }))).catch((cause: unknown) => setProviderState((current) => ({ ...current, [provider]: cause instanceof Error ? cause.message : String(cause) }))); else void api.refreshProvider(provider).then((result) => setProviderState((current) => ({ ...current, [provider]: `${result.models.length} 个模型已刷新` }))).catch((cause: unknown) => setProviderState((current) => ({ ...current, [provider]: cause instanceof Error ? cause.message : String(cause) }))); }; return <div className="settings-overlay"><div className="settings-dialog"><aside><strong>设置</strong>{(["general", "plugins", "providers", "toml"] as const).map((item) => <button className={`settings-tab ${tab === item ? "active" : ""}`} key={item} onClick={() => onTab(item)} type="button">{item === "general" ? "通用" : item === "plugins" ? `插件 ${data.plugins.length}` : item === "providers" ? `提供商 ${data.providers.length}` : "pi.toml"}</button>)}</aside><section><header><strong>{tab === "general" ? "通用" : tab === "plugins" ? "插件" : tab === "providers" ? "提供商" : "pi.toml"}</strong><small>{tab === "toml" ? "配置即代码,改完重载" : "运行时状态与快捷键"}</small><button onClick={onClose} type="button">×</button></header><div className="settings-body">{tab === "general" && <>{[["工作目录", status?.cwd], ["agent 目录", status?.agentDir], ["会话", status ? `${status.sessionId} · ${status.messages} 条消息` : "—"], ["快捷键", "⌘K 命令 · ⌘, 设置 · ⌃C 中断"], ["权限策略", "当前 API 未提供修改接口"]].map(([key, item]) => <div className="general-row" key={key}><div><strong>{key}</strong><small>{value(item)}</small></div></div>)}<div className="general-row"><div><strong>任务结束提醒</strong><small>由 runtime 插件提供</small></div><span className="switch"><i></i></span></div><div className="general-row"><div><strong>自动压缩上下文</strong><small>事件通过 SSE 实时刷新</small></div><span className="switch on"><i></i></span></div></>}{tab === "plugins" && data.plugins.map((plugin) => <div className="settings-plugin-row" key={plugin.id}>◈ <code>{plugin.name} · {plugin.id} · {plugin.state}</code></div>)}{tab === "providers" && <><small>运行时注册提供商 · /api/providers</small>{data.providers.length ? data.providers.map((provider) => <article className="provider-card" key={provider.provider}><div className="provider-head"><span className="provider-dot">●</span><strong>{provider.name}</strong><span className="provider-state">{provider.active ? "当前会话" : value(provider.auth?.configured, "未配置")}</span></div><div className="provider-field"><code>provider</code><input disabled value={provider.provider} readOnly /></div><div className="provider-field"><code>model</code><input disabled value={provider.activeModel ? `${provider.activeModel.provider}/${provider.activeModel.id}` : provider.models.map((model) => model.id).join(", ")} readOnly /></div><div className="provider-field"><code>api_key</code><input disabled value="不会在浏览器显示" readOnly /></div><div className="provider-footer"><code>{provider.models.length} 个模型</code><button onClick={() => runProviderAction(provider.provider, "test")} type="button">测试连接</button><button className="link-button" onClick={() => runProviderAction(provider.provider, "refresh")} type="button">拉取模型</button></div>{providerState[provider.provider] && <small className="provider-result">{providerState[provider.provider]}</small>}</article>) : <div className="empty-state">运行时没有注册提供商。</div>}</>}{tab === "toml" && <><div className="toml-toolbar"><span>~/.config/pi/pi.toml</span><button className="active" type="button">表单</button><button disabled type="button">源码</button><button className="primary" disabled type="button">重载</button></div><div className="toml"><div className="empty-state">pi.toml 读取与写入 API 尚未提供。</div></div></>}</div></section></div></div>; }
|
|
53
|
+
|
|
54
|
+
function CommandPalette({ commands, onClose, onUse }: { commands: readonly ClientCommand[]; onClose: () => void; onUse: (value: string) => void }) { const [query, setQuery] = useState(""); const visible = commands.filter((command) => `${command.invocationName} ${command.description ?? ""}`.toLowerCase().includes(query.toLowerCase())); return <div className="command-palette" onClick={(event) => { if (event.target === event.currentTarget) onClose(); }}><div className="palette-dialog"><input autoFocus onChange={(event) => setQuery(event.target.value)} placeholder="命令、包、会话、文件" value={query} /><div className="palette-group"><small>COMMANDS · RUNTIME REGISTRY</small>{visible.length ? visible.map((command) => { const invocation = `/${command.invocationName}`; return <button key={`${command.invocationName}:${command.source ?? "runtime"}`} onClick={() => { onUse(invocation); onClose(); }} type="button"><code>{invocation}</code><span>{command.description ?? command.source ?? "由当前运行时注册"}</span></button>; }) : <div className="empty-state">{commands.length ? "没有匹配的命令。" : "当前运行时没有可用的命令注册清单。"}</div>}</div></div></div>; }
|
|
55
|
+
|
|
56
|
+
export function ControlRoomView({ api = createClientApi() }: { api?: ClientApi }) {
|
|
57
|
+
const [data, setData] = useState<RoomData>({ sessions: [], files: [], models: [], providers: [], plugins: [], commands: [] });
|
|
58
|
+
const [view, setView] = useState<View>("chat");
|
|
59
|
+
const [page, setPage] = useState<Page>("session");
|
|
60
|
+
const [pluginTab, setPluginTab] = useState<"installed" | "extensions">("installed");
|
|
61
|
+
const [settings, setSettings] = useState<SettingsTab>();
|
|
62
|
+
const [details, setDetails] = useState<Record<string, unknown> | undefined>({});
|
|
63
|
+
const [commandOpen, setCommandOpen] = useState(false);
|
|
64
|
+
const [sessionMenuOpen, setSessionMenuOpen] = useState(false);
|
|
65
|
+
const [draft, setDraft] = useState("");
|
|
66
|
+
const [search, setSearch] = useState("");
|
|
67
|
+
const [permission, setPermission] = useState(true);
|
|
68
|
+
const refresh = useCallback(async () => { const [status, session, sessions, files, models, providers, plugins, commands] = await Promise.allSettled([api.getStatus(), api.getSession(), api.listSessions(), api.getFiles(), api.listModels(), api.listProviders(), api.listPlugins(), api.listCommands()]); setData((current) => ({ status: status.status === "fulfilled" ? status.value : current.status, session: session.status === "fulfilled" ? session.value : current.session, sessions: sessions.status === "fulfilled" ? sessions.value : current.sessions, files: files.status === "fulfilled" ? files.value : current.files, models: models.status === "fulfilled" ? models.value : current.models, providers: providers.status === "fulfilled" ? providers.value : current.providers, plugins: plugins.status === "fulfilled" ? plugins.value : current.plugins, commands: commands.status === "fulfilled" ? commands.value : current.commands })); }, [api]);
|
|
69
|
+
useEffect(() => { void refresh(); const unsubscribe = api.subscribeEvents(() => void refresh()); const timer = window.setInterval(() => void refresh(), 5000); return () => { unsubscribe(); window.clearInterval(timer); }; }, [api, refresh]);
|
|
70
|
+
useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { setCommandOpen(false); setSessionMenuOpen(false); setSettings(undefined); } if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { event.preventDefault(); setCommandOpen(true); } if ((event.metaKey || event.ctrlKey) && event.key === ",") { event.preventDefault(); setSettings("general"); } }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, []);
|
|
71
|
+
const events = data.session?.events ?? [];
|
|
72
|
+
const filteredSessions = data.sessions.filter((session) => !search || value(session.name ?? session.firstMessage, "未命名会话").toLowerCase().includes(search.toLowerCase()));
|
|
73
|
+
const submit = (event: FormEvent) => { event.preventDefault(); const prompt = draft.trim(); if (!prompt) return; setDraft(""); void api.prompt(prompt).then(() => refresh()); };
|
|
74
|
+
const openSession = (session: Record<string, unknown>) => { const path = typeof session.path === "string" ? session.path : ""; if (path) void api.openSession(path).then(refresh); };
|
|
75
|
+
const content = page === "plugins" ? <Plugins plugins={data.plugins} tab={pluginTab} onTab={setPluginTab} onToml={() => setSettings("toml")} /> : view === "chat" ? <section className="view-panel chat-view"><div className="chat-scroll">{data.session?.messages.length ? data.session.messages.map((message, index) => <article className={`turn ${message.role === "user" ? "user" : "text"}`} key={index}>{message.role === "user" ? <div className="user-bubble">{messageText(message)}</div> : <p className="turn-text">{messageText(message)}</p>}</article>) : <Workspace status={data.status} onCreate={() => void api.createSession().then(refresh)} onStarter={setDraft} onToml={() => setSettings("toml")} />}{events.map((event, index) => <RuntimeCard event={event} key={`${value(event.type, "event")}-${index}`} />)}</div><div className="composer-wrap"><div className="context-line"><span>上下文 <b>{data.status ? `${data.status.messages} / ${data.status.model}` : "0 / —"}</b></span><div className={`context-bar ${permission ? "" : "expanded"}`}><i></i><i></i><i></i></div><button onClick={() => setPermission((current) => !current)} type="button">{permission ? "构成" : "收起"}</button></div><form className="composer" onSubmit={submit}><textarea aria-label="Prompt" onChange={(event) => setDraft(event.target.value)} placeholder="描述要做的改动,⌘↵ 发送;@ 引用文件,/ 调用命令" rows={2} value={draft}></textarea><div className="composer-tools"><select aria-label="模型" value={data.status?.model ?? ""} onChange={(event) => { const [provider, model] = event.target.value.split("/"); if (provider && model) void api.selectModel(provider, model); }}>{data.models.map((model) => <option key={`${model.provider}/${model.id}`} value={`${model.provider}/${model.id}`}>{model.name === model.id ? `${model.provider}/${model.id}` : `${model.name} (${model.provider})`}</option>)}</select><button className="tool-chip" onClick={() => setPermission((current) => !current)} type="button">● {permission ? "改动前询问" : "自动允许"}</button><button className="tool-chip" onClick={() => setCommandOpen(true)} type="button">/ 命令</button><span className="composer-hint">⌘↵ 发送 · ⌘K 命令 · ⌃C 中断</span><button className="send-button" type="submit">↑</button></div></form></div></section> : view === "trajectory" ? <Trajectory events={events} onSelect={setDetails} /> : <Files api={api} files={data.files} onDiff={(file) => void api.getFileDiff(file).then((diff) => setDetails({ type: "file_diff", path: diff.path, output: diff.diff }))} onRefresh={() => void refresh()} />;
|
|
76
|
+
const groups = sessionGroups(filteredSessions);
|
|
77
|
+
return <div className="app-frame"><aside className="sidebar"><header className="brand-row"><span className="pi-mark">π</span><strong>pi harness</strong><span className="version">0.9.4</span></header><div className="sidebar-actions"><button className="new-session" onClick={() => void api.createSession().then(refresh)} type="button">+ 新建会话</button><input onChange={(event) => setSearch(event.target.value)} placeholder="搜索会话与事件" type="search" value={search} /></div><div className="sidebar-scroll">{groups.length ? groups.map(([label, sessions]) => <div className="session-group" key={label}><div className="group-label">{label}</div>{sessions.map((session, index) => <button className={`session-row ${session.sessionId === data.session?.sessionId ? "active" : ""}`} key={index} onClick={() => openSession(session)} type="button"><span className="session-dot ok"></span><span className="session-copy"><strong>{value(session.name ?? session.firstMessage, "未命名会话")}</strong><small>{value(session.messageCount, "0")} 条消息</small></span></button>)}</div>) : <div className="empty-state">暂无已保存会话</div>}</div><footer className="sidebar-footer"><div className="runtime-cells"><span>model <b>{value(data.status?.model)}</b></span><span>msgs <b>{data.status?.messages ?? 0}</b></span><span>status <b>{value(data.status?.status, "connecting")}</b></span></div><button className={`sidebar-link ${page === "plugins" ? "active" : ""}`} onClick={() => { setPage("plugins"); setSettings(undefined); }} type="button">◈ <span>插件</span><b>{data.plugins.length}</b></button><button className="sidebar-link" onClick={() => setSettings("general")} type="button">⚙ <span>设置</span></button><button className="sidebar-link" onClick={() => setCommandOpen(true)} type="button"><span className="link-glyph">⌘</span><span>命令面板</span><small>⌘K</small></button></footer></aside><section className="main-pane"><header className="main-header"><div className="active-heading"><strong>{page === "plugins" ? "插件" : data.session?.messages.length ? data.session.sessionId.slice(0, 12) : "新会话"}</strong><small>{page === "plugins" ? "Cordis loader 运行时清单" : data.session?.sessionFile ?? "未选择工作区"}</small></div><div className="header-spacer"></div><div className="run-indicator">● {data.status?.status === "running" ? "running · Pi agent" : "idle"}</div><button className="stop-button" disabled={data.status?.status !== "running"} onClick={() => void api.abort().then(refresh)} type="button">停止</button>{page === "session" && <div className="view-tabs">{(["chat", "trajectory", "files"] as const).map((item) => <button className={`view-tab ${view === item ? "active" : ""}`} key={item} onClick={() => setView(item)} type="button">{item === "chat" ? "对话" : item === "trajectory" ? "轨迹" : "产出"}</button>)}</div>}<button className="session-menu" onClick={() => setSessionMenuOpen((current) => !current)} type="button" aria-label="会话操作">⋯</button><button className="details-toggle" onClick={() => setDetails(details ? undefined : {})} type="button">◨ 详情</button><span className={`status-pill ${data.status?.status === "running" ? "running" : "online"}`}>{value(data.status?.status, "connecting")}</span>{sessionMenuOpen && <div className="session-menu-popover"><button className="session-action" onClick={() => void api.createSession().then(refresh)} type="button"><strong>新建会话</strong><small>清空并开始新的运行时会话</small></button><button className="session-action" onClick={() => window.location.reload()} type="button"><strong>刷新会话</strong><small>重新读取运行时状态</small></button><button className="session-action" disabled type="button"><strong>导出事件</strong><small>API 暂未提供导出接口</small></button><button className="session-action" disabled type="button"><strong>删除会话</strong><small>API 暂未提供删除接口</small></button></div>}</header><div className="view-host">{content}</div></section>{details !== undefined && <Details event={Object.keys(details).length ? details : undefined} onClose={() => setDetails(undefined)} onCopy={() => void navigator.clipboard?.writeText(JSON.stringify(details, null, 2))} />}{settings && <Settings api={api} data={data} tab={settings} onTab={setSettings} onClose={() => setSettings(undefined)} />}{commandOpen && <CommandPalette commands={data.commands} onClose={() => setCommandOpen(false)} onUse={setDraft} />}</div>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { DESIGN_EVENTS, DESIGN_PLUGINS, DESIGN_SESSIONS, DESIGN_WORKSPACES, DESIGN_TURNS, DESIGN_PROVIDERS, DESIGN_TOML } from "../src/design-contract.js";
|
|
3
|
+
|
|
4
|
+
describe("Pi Harness design contract", () => {
|
|
5
|
+
it("keeps the workspace and session surfaces represented", () => {
|
|
6
|
+
expect(DESIGN_WORKSPACES.length).toBeGreaterThanOrEqual(3);
|
|
7
|
+
expect(DESIGN_SESSIONS.length).toBeGreaterThanOrEqual(6);
|
|
8
|
+
expect(DESIGN_WORKSPACES.every((workspace) => workspace.path && workspace.branch && workspace.dirty)).toBe(true);
|
|
9
|
+
expect(new Set(DESIGN_SESSIONS.map((session) => session.group))).toEqual(new Set(["今天", "昨天", "更早"]));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("covers the event cards shown in the design", () => {
|
|
13
|
+
expect(new Set(DESIGN_TURNS.map((turn) => turn.kind))).toEqual(new Set(["user", "reasoning", "plugin", "tool", "text", "approval", "subagent", "todo", "stats"]));
|
|
14
|
+
expect(DESIGN_EVENTS.some((event) => event.source === "plugin_hook")).toBe(true);
|
|
15
|
+
expect(DESIGN_EVENTS.some((event) => event.source === "subagent")).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("keeps package, provider and pi.toml content available to the UI", () => {
|
|
19
|
+
expect(DESIGN_PLUGINS.length).toBeGreaterThanOrEqual(8);
|
|
20
|
+
expect(DESIGN_PROVIDERS.map((provider) => provider.id)).toEqual(["deepseek", "anthropic"]);
|
|
21
|
+
expect(DESIGN_TOML).toContain("[[packages]]");
|
|
22
|
+
expect(DESIGN_TOML).toContain("[sandbox]");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"extends":"../../tsconfig.build.json","compilerOptions":{"outDir":"dist","paths":{},"rootDir":"src","lib":["ES2023","DOM","DOM.Iterable"],"jsx":"react-jsx","exactOptionalPropertyTypes":false,"noUncheckedIndexedAccess":false},"include":["src/**/*.ts","src/**/*.tsx"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"extends":"../../tsconfig.json","compilerOptions":{"paths":{},"lib":["ES2023","DOM","DOM.Iterable"],"jsx":"react-jsx","exactOptionalPropertyTypes":false,"noUncheckedIndexedAccess":false},"include":["src/**/*.ts","src/**/*.tsx"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
export interface BootHarnessOptions {
|
|
3
|
+
configPath: string;
|
|
4
|
+
prepare?: (context: Context) => Promise<void> | void;
|
|
5
|
+
onFullReload?: () => void;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export interface BootedHarness {
|
|
9
|
+
readonly context: Context;
|
|
10
|
+
dispose(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function bootHarness(options: BootHarnessOptions): Promise<BootedHarness>;
|
|
13
|
+
//# sourceMappingURL=boot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../src/boot.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAmB,MAAM,qBAAqB,CAAC;AAS/D,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACrD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAmDD,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CA8BrF"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
4
|
+
import Group from "@deepseek-ai/cordis-plugin-group";
|
|
5
|
+
import Include from "@deepseek-ai/cordis-plugin-include";
|
|
6
|
+
import Loader, {} from "@deepseek-ai/cordis-plugin-loader";
|
|
7
|
+
class ReadonlyInclude extends Include {
|
|
8
|
+
write() { }
|
|
9
|
+
}
|
|
10
|
+
const FIBER_PENDING = 0;
|
|
11
|
+
const FIBER_ACTIVE = 2;
|
|
12
|
+
const FIBER_FAILED = 3;
|
|
13
|
+
function formatError(error) {
|
|
14
|
+
return error instanceof Error ? error.stack ?? error.message : String(error);
|
|
15
|
+
}
|
|
16
|
+
async function assertEntriesActivated(context) {
|
|
17
|
+
const loader = context.get("loader");
|
|
18
|
+
if (loader === undefined)
|
|
19
|
+
throw new Error("Cordis Loader was disposed during startup");
|
|
20
|
+
const failures = [];
|
|
21
|
+
for (const entry of loader.entries()) {
|
|
22
|
+
if (entry.disabled)
|
|
23
|
+
continue;
|
|
24
|
+
const fiber = entry.fiber;
|
|
25
|
+
if (fiber === undefined) {
|
|
26
|
+
failures.push(`${entry.options.name}: module did not load`);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (fiber.state === FIBER_ACTIVE)
|
|
30
|
+
continue;
|
|
31
|
+
if (fiber.state === FIBER_FAILED) {
|
|
32
|
+
try {
|
|
33
|
+
await fiber.await();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
failures.push(`${entry.options.name}: ${formatError(error)}`);
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (fiber.state === FIBER_PENDING) {
|
|
41
|
+
const missing = Object.keys(fiber.inject).filter((service) => fiber.ctx.get(service) === undefined);
|
|
42
|
+
failures.push(`${entry.options.name}: pending (waiting for services: ${missing.join(", ") || "unknown"})`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
failures.push(`${entry.options.name}: fiber state ${String(fiber.state)}`);
|
|
46
|
+
}
|
|
47
|
+
if (failures.length > 0)
|
|
48
|
+
throw new Error(`Cordis plugin tree did not activate:\n${failures.join("\n")}`);
|
|
49
|
+
}
|
|
50
|
+
async function mountProfile(context, configPath) {
|
|
51
|
+
context.loader.builtins.include = ReadonlyInclude;
|
|
52
|
+
context.loader.builtins.group = Group;
|
|
53
|
+
const root = {
|
|
54
|
+
id: "profile",
|
|
55
|
+
name: "cordis:include",
|
|
56
|
+
config: { path: pathToFileURL(configPath).href },
|
|
57
|
+
};
|
|
58
|
+
await context.loader.create(root);
|
|
59
|
+
}
|
|
60
|
+
export async function bootHarness(options) {
|
|
61
|
+
const configPath = resolve(options.configPath);
|
|
62
|
+
const context = new Context();
|
|
63
|
+
const abort = () => {
|
|
64
|
+
void context.fiber.dispose().catch(() => { });
|
|
65
|
+
};
|
|
66
|
+
if (options.signal?.aborted)
|
|
67
|
+
throw new Error("Pi Harness startup was aborted", { cause: options.signal.reason });
|
|
68
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
69
|
+
let stage = "host preparation";
|
|
70
|
+
try {
|
|
71
|
+
context.baseUrl = `${pathToFileURL(dirname(configPath)).href}/`;
|
|
72
|
+
await context.plugin(Loader);
|
|
73
|
+
if (options.onFullReload !== undefined)
|
|
74
|
+
context.loader.exit = options.onFullReload;
|
|
75
|
+
await options.prepare?.(context);
|
|
76
|
+
stage = "plugin tree activation";
|
|
77
|
+
await mountProfile(context, configPath);
|
|
78
|
+
await context.get("loader")?.await();
|
|
79
|
+
if (context.get("loader") !== undefined)
|
|
80
|
+
await assertEntriesActivated(context);
|
|
81
|
+
}
|
|
82
|
+
catch (cause) {
|
|
83
|
+
await context.fiber.dispose();
|
|
84
|
+
throw new Error(`Pi Harness ${stage} failed: ${formatError(cause)}`, { cause });
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
options.signal?.removeEventListener("abort", abort);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
context,
|
|
91
|
+
async dispose() {
|
|
92
|
+
await context.fiber.dispose();
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=boot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boot.js","sourceRoot":"","sources":["../src/boot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,mBAAmB,cAAc,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAE1B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/plugins/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;;;;mBAKlC,OAAO;;AAHxB,wBASE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
name: "pi-model",
|
|
3
|
+
inject: ["piModelRuntime", "piResources"],
|
|
4
|
+
apply(context) {
|
|
5
|
+
const { runtime, provider, model: modelId } = context.piModelRuntime;
|
|
6
|
+
const model = runtime.getModel(provider, modelId);
|
|
7
|
+
if (model === undefined)
|
|
8
|
+
throw new Error(`Pi model is not registered: ${provider}/${modelId}`);
|
|
9
|
+
context.provide("piModels", { runtime, model });
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/plugins/model.ts"],"names":[],"mappings":"AAEA,eAAe;IACb,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,CAAC,gBAAgB,EAAE,aAAa,CAAC;IACzC,KAAK,CAAC,OAAgB;QACpB,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC;QACrE,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,IAAI,OAAO,EAAE,CAAC,CAAC;QAC/F,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAClD,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
export interface ModelsPluginConfig {
|
|
4
|
+
provider: string;
|
|
5
|
+
model: string;
|
|
6
|
+
refreshOnCreate?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const Config: z<ModelsPluginConfig>;
|
|
9
|
+
declare const _default: {
|
|
10
|
+
name: string;
|
|
11
|
+
inject: string[];
|
|
12
|
+
Config: z<ModelsPluginConfig>;
|
|
13
|
+
apply(context: Context, config: ModelsPluginConfig): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=models.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/plugins/models.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,CAAC,MAAM,0BAA0B,CAAC;AAGzC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,kBAAkB,CAIvC,CAAC;;;;;mBAMoB,OAAO,UAAU,kBAAkB;;AAJ1D,wBAcE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
export const Config = z.object({
|
|
5
|
+
provider: z.string().required(),
|
|
6
|
+
model: z.string().required(),
|
|
7
|
+
refreshOnCreate: z.boolean().default(false),
|
|
8
|
+
});
|
|
9
|
+
export default {
|
|
10
|
+
name: "pi-models",
|
|
11
|
+
inject: ["piHarnessLaunch"],
|
|
12
|
+
Config,
|
|
13
|
+
async apply(context, config) {
|
|
14
|
+
const agentDir = context.piHarnessLaunch.agentDir;
|
|
15
|
+
const runtime = await ModelRuntime.create({
|
|
16
|
+
authPath: join(agentDir, "auth.json"),
|
|
17
|
+
modelsPath: join(agentDir, "models.json"),
|
|
18
|
+
modelsStorePath: join(agentDir, "models-store.json"),
|
|
19
|
+
refreshOnCreate: config.refreshOnCreate ?? false,
|
|
20
|
+
});
|
|
21
|
+
context.provide("piModelRuntime", { runtime, provider: config.provider, model: config.model });
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/plugins/models.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,CAAC,MAAM,0BAA0B,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAQ/D,MAAM,CAAC,MAAM,MAAM,GAA0B,CAAC,CAAC,MAAM,CAAC;IACpD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAC5C,CAAC,CAAC;AAEH,eAAe;IACb,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,CAAC,iBAAiB,CAAC;IAC3B,MAAM;IACN,KAAK,CAAC,KAAK,CAAC,OAAgB,EAAE,MAA0B;QACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,QAAQ,CAAC;QAClD,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC;YACxC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC;YACrC,UAAU,EAAE,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC;YACzC,eAAe,EAAE,IAAI,CAAC,QAAQ,EAAE,mBAAmB,CAAC;YACpD,eAAe,EAAE,MAAM,CAAC,eAAe,IAAI,KAAK;SACjD,CAAC,CAAC;QACH,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IACjG,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
export interface ResourcesPluginConfig {
|
|
4
|
+
noExtensions?: boolean;
|
|
5
|
+
noSkills?: boolean;
|
|
6
|
+
noPromptTemplates?: boolean;
|
|
7
|
+
noThemes?: boolean;
|
|
8
|
+
noContextFiles?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const Config: z<ResourcesPluginConfig>;
|
|
11
|
+
declare const _default: {
|
|
12
|
+
name: string;
|
|
13
|
+
inject: string[];
|
|
14
|
+
Config: z<ResourcesPluginConfig>;
|
|
15
|
+
apply(context: Context, config: ResourcesPluginConfig): Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
export default _default;
|
|
18
|
+
//# sourceMappingURL=resources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/plugins/resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,CAAC,MAAM,0BAA0B,CAAC;AAGzC,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,qBAAqB,CAM1C,CAAC;;;;;mBAMoB,OAAO,UAAU,qBAAqB;;AAJ7D,wBAoCE"}
|