pi-webapp 0.1.22 → 0.1.24
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 +14 -7
- package/README.zh-CN.md +14 -7
- package/dist/extension.js +2 -2
- package/docs/desktop-testing.md +76 -0
- package/docs/desktop.md +21 -9
- package/package.json +3 -2
- package/web/dist/.vite/manifest.json +23 -23
- package/web/dist/assets/{app-D54rSb9U.js → app-uZ4wCYr8.js} +94 -94
- package/web/dist/assets/app.js +2 -2
- package/web/dist/assets/{graphql-DSgZM88T.js → graphql-BjSnAUKM.js} +1 -1
- package/web/dist/assets/{http-BnatDCTa.js → http-B7mqrZ_R.js} +1 -1
- package/web/dist/assets/{php-C7wdcWqa.js → php-Cvwc1Xta.js} +1 -1
- package/web/dist/assets/{plugin-U9RqGH7R.js → plugin-BB8YECov.js} +1 -1
- package/web/dist/assets/{plugin-CDXXKG8W.js → plugin-Bs3cPDKk.js} +1 -1
- package/web/dist/assets/{plugin-DuICpslz.js → plugin-DK_rN4N5.js} +1 -1
- package/web/dist/assets/{rst-DXm1XNUD.js → rst-DutdH0pA.js} +1 -1
- package/web/dist/assets/{ruby-BM86KL0M.js → ruby-Bpvk-Mco.js} +1 -1
- package/web/dist/assets/style.css +1 -1
- package/web/dist/assets/{svelte-Dizghd7i.js → svelte-CPcJi_Ee.js} +1 -1
- package/web/dist/assets/{vue-DJuuwuj_.js → vue-s7ynYl37.js} +1 -1
package/README.md
CHANGED
|
@@ -4,13 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
## Download pi desktop
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**⬇ Test release · v0.1.21 / desktop build 6 · bundled Pi 1.3.0**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
| Your computer | Download |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| macOS · Apple Silicon (M-series, arm64) | [Download DMG](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/pi-0.1.21-desktop.6-mac-arm64.dmg) |
|
|
12
|
+
| macOS · Intel (x64) | [Download DMG](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/pi-0.1.21-desktop.6-mac-x64.dmg) |
|
|
13
|
+
| Windows · x64 | [Download EXE installer](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/pi-0.1.21-desktop.6-win-x64.exe) |
|
|
14
|
+
| Linux · x64 | [Download AppImage](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/pi-0.1.21-desktop.6-linux-x86_64.AppImage) |
|
|
10
15
|
|
|
11
|
-
[Release notes
|
|
16
|
+
[Release notes](https://github.com/chengzhiyi/pi-webapp/releases/tag/desktop-v0.1.21-b6) · [SHA-256 checksums](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/SHA256SUMS) · [Runtime/source metadata](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/desktop-release.json) · [Previous build 5](https://github.com/chengzhiyi/pi-webapp/releases/tag/desktop-v0.1.20-b5) · [Use Web/npm instead](#install-and-open)
|
|
12
17
|
|
|
13
|
-
>
|
|
18
|
+
> **Install:** macOS: open the DMG and drag **pi.app** into Applications. Windows: run the EXE installer. Linux: make the AppImage executable, then run it; use `--appimage-extract-and-run` if FUSE is unavailable. Node and pinned Pi **1.3.0** are bundled; no separate installation is required. Configure your model account in Settings. Quit the old desktop before replacing it; a reused external Web service keeps its own Pi version and is never automatically stopped or upgraded.
|
|
19
|
+
>
|
|
20
|
+
> **Unsigned test build:** macOS is not notarized; Windows may show an unknown-publisher warning. Only proceed if you trust the source; never globally disable system security. Installer-payload smoke tests passed on all four targets, not every Linux distribution or real model-provider login. Windows ARM/Linux ARM are not included. Desktop automatic updates are not provided in this build.
|
|
14
21
|
|
|
15
22
|

|
|
16
23
|
|
|
@@ -57,7 +64,7 @@ In Pi, enter `/web` to open the interface in your default browser. Pi also print
|
|
|
57
64
|
- In **Git changes → Changes**, use each file’s **+ / −** button to stage or unstage it, use the fixed bottom composer to commit only the staged index (Ctrl/Cmd+Enter is supported), and click the branch name to search/switch local branches or open **New branch**. The file list scrolls independently; full repository metadata is available on the short repository name’s tooltip. Drafts stay with each chat’s Git tab. Commits and branch switches require a root workspace; switching also requires a clean working tree, including untracked files. Writes require all known chats using the worktree to be idle, reject stale snapshots, and honor Git hooks/signing. Conflicts, active Git operations, locks and occupied worktree branches block incompatible writes. Failed or unconfirmed operations retain drafts and never retry automatically. Changes are shared by chats using the same worktree. No fetch/push, forced switch, stash or discard is performed.
|
|
58
65
|
- In **Git changes → History**, browse commits alongside their branch/merge graph, with HEAD, local branch, remote-tracking branch and tag labels. Load history 50 commits at a time; a ref change resets pagination to prevent mixed histories. Open commit details for the full message, author/committer, timestamps, parents and changed files, then preview a read-only file diff alongside the same commit file list without opening another tab. Merge details let you select the parent to compare against; root commits compare against an empty tree. The graph shows repository-wide metadata while commit files and diffs stay within the session directory. Graph selection and loaded history remain with each chat’s Git tab until the page reloads. Remote labels use local refs without fetching.
|
|
59
66
|
- Attach up to 20 files of 20 MB each by selecting, dropping, or pasting them. Images show previews and are also sent as Pi image content.
|
|
60
|
-
- Open Plugins
|
|
67
|
+
- Open Plugins directly below New session in the sidebar to inspect and switch the bundled Git, Files and Terminal capabilities. Switches are global, persist locally, and require every chat to be idle. Disabling closes related tabs and blocks backend requests; Terminal asks for confirmation before ending all service-owned terminals. External Pi Web runtimes remain managed through Packages & extensions rather than a misleading UI-only switch.
|
|
61
68
|
- Manage appearance, Pi packages, extensions, skills, and models from the settings panel. The Models page can add models, edit display names, context windows, output limits, and input types, and restore built-in defaults; changes are saved to Pi's `models.json`. Browser sign-in opens the provider's authorization page automatically; manual code entry remains available when a callback cannot reach Pi. Stored provider credentials can also be removed there. Credentials stay in Pi's local `auth.json` and are never returned to the browser.
|
|
62
69
|
|
|
63
70
|
If you enter `/web` manually in a Pi terminal, that terminal session stays synchronized with the browser. With the launcher, Pi runs in the background. Sessions opened in other workspaces use the Pi SDK and load that workspace's skills and project context. They load enabled Web plugin Pi entries explicitly while keeping other Pi extensions isolated from the parent bridge.
|
|
@@ -68,11 +75,11 @@ On startup, the app discovers workspaces from Pi's saved sessions, so conversati
|
|
|
68
75
|
|
|
69
76
|
## Optional desktop application
|
|
70
77
|
|
|
71
|
-
**[Download pi desktop test releases](https://github.com/chengzhiyi/pi-webapp/releases)** — macOS Apple Silicon (arm64) only for now. Download the DMG from a `desktop-v...` prerelease; compare `SHA256SUMS`, install **pi.app**, and check the build in **pi → 关于 pi**.
|
|
78
|
+
**[Download pi desktop test releases](https://github.com/chengzhiyi/pi-webapp/releases)** — macOS Apple Silicon (arm64) only for now. Download the DMG from a `desktop-v...` prerelease; compare `SHA256SUMS`, install **pi.app**, and check the build in **pi → 关于 pi**. From desktop build 7, macOS packages are fully ad-hoc signed but have no publisher identity or notarization; Gatekeeper may still block them. Follow the [small-group test installation guide](docs/desktop-testing.md), never disable macOS security globally. Old downloads are not retroactively fixed. Windows/Linux are not yet validated release downloads.
|
|
72
79
|
|
|
73
80
|
The same UI can run in the **pi** Electron desktop application while the browser and `/web` entry points remain available. Desktop packages include Node/Pi, reuse compatible running services, and let you open the same service in a browser or choose whether to keep background tasks running on exit.
|
|
74
81
|
|
|
75
|
-
From source: `npm ci` → `npm run desktop:prepare` → `npm run desktop:start`. Run `npm run desktop:package` to create a host-native installer under `desktop-release/`. Local QA targets macOS arm64; Windows/Linux configurations need verification on their respective systems.
|
|
82
|
+
From source: `npm ci` → `npm run desktop:prepare` → `npm run desktop:start`. Run `npm run desktop:package` to create a host-native installer under `desktop-release/`. Local QA targets macOS arm64; Windows/Linux configurations need verification on their respective systems. Test packages without a publisher signature support checked downloads and manual installation only, not automatic installation. Installers include a desktop build number; check **pi → 关于 pi** for the running build and executable location. Quit the old desktop before replacing it; an external compatible Web backend need not be stopped. Finish tasks and stop an owned runtime before replacing that runtime.
|
|
76
83
|
|
|
77
84
|
See [desktop build, testing and release boundaries](docs/desktop.md).
|
|
78
85
|
|
package/README.zh-CN.md
CHANGED
|
@@ -4,13 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
## 下载 pi 桌面版
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**⬇ 测试版 · v0.1.21 / 桌面构建 6 · 内置 Pi 1.3.0**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
| 你的电脑 | 下载安装包 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| macOS · Apple 芯片(M 系列,arm64) | [下载 DMG](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/pi-0.1.21-desktop.6-mac-arm64.dmg) |
|
|
12
|
+
| macOS · Intel 芯片(x64) | [下载 DMG](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/pi-0.1.21-desktop.6-mac-x64.dmg) |
|
|
13
|
+
| Windows · x64 | [下载 EXE 安装程序](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/pi-0.1.21-desktop.6-win-x64.exe) |
|
|
14
|
+
| Linux · x64 | [下载 AppImage](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/pi-0.1.21-desktop.6-linux-x86_64.AppImage) |
|
|
10
15
|
|
|
11
|
-
[
|
|
16
|
+
[版本说明](https://github.com/chengzhiyi/pi-webapp/releases/tag/desktop-v0.1.21-b6) · [SHA-256 校验和](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/SHA256SUMS) · [运行时与源码元数据](https://github.com/chengzhiyi/pi-webapp/releases/download/desktop-v0.1.21-b6/desktop-release.json) · [旧构建 5(保留回退)](https://github.com/chengzhiyi/pi-webapp/releases/tag/desktop-v0.1.20-b5) · [改用 Web/npm 方式](#安装与打开)
|
|
12
17
|
|
|
13
|
-
> 打开 DMG,将 **pi.app**
|
|
18
|
+
> **安装:** macOS 打开 DMG,将 **pi.app** 拖入“应用程序”;Windows 运行 EXE 安装程序;Linux 为 AppImage 添加可执行权限后运行,缺少 FUSE 时可加 `--appimage-extract-and-run`。已内置 Node 和锁定版本的 Pi **1.3.0**,无需另装;首次使用在设置中配置模型账户。替换前先退出旧桌面。复用外部 Web 后台时,其 Pi 仍保持原版本,不会自动停止或升级。
|
|
19
|
+
>
|
|
20
|
+
> **未签名测试版:** macOS 未公证,Windows 可能提示未知发布者;仅在确认来源可信后按单个应用的系统提示处理,不要全局关闭系统安全保护。四种实际安装包均通过原生运行冒烟,但不代表已测试所有 Linux 发行版或真实模型账户登录。不含 Windows ARM/Linux ARM;本构建尚无桌面自动更新。
|
|
14
21
|
|
|
15
22
|

|
|
16
23
|
|
|
@@ -57,7 +64,7 @@ pi
|
|
|
57
64
|
- 在「Git 改动 → 改动」中,用文件右侧的 **+ / −** 逐文件暂存或取消暂存;底部固定提交栏仅提交暂存区内容(支持 Ctrl/Cmd+Enter);点击顶部分支名称,可搜索和切换本地分支,选择「新建分支」才展开创建表单。文件列表独立滚动,仓库完整信息通过短名称的悬停提示查看。草稿随各聊天的 Git 标签保留。提交和切换分支要求仓库根目录工作区,切换还要求整个工作树干净(包括未跟踪文件)。写操作要求共用工作树的已知聊天均为空闲,并校验状态快照,遵守 Git hooks 与签名配置。冲突、未完成的 Git 操作、锁及其他工作树占用的分支会阻止相应操作。失败或结果不明时保留草稿,不自动重试。同一工作树的改动由多个聊天共享。不执行 fetch/push、强制切换、stash 或丢弃改动。
|
|
58
65
|
- 在「Git 改动 → 历史」中浏览提交及其分支、合并关系图,显示 HEAD、本地分支、远端跟踪分支和标签。历史每次加载 50 条;引用变化时重新加载首页,避免分页混入不同版本的历史。点击提交查看完整说明、作者与提交者、时间、父提交及改动文件,再点击文件在当前提交详情内预览只读差异,保留文件列表,不再新建差异标签。合并提交可选择比较的父提交,根提交与空目录比较。关系图展示整个仓库的提交元数据,提交文件和差异仍限制在当前会话目录内。历史选中项与已加载页随各聊天的 Git 标签保留,网页刷新后重置。远端标签仅使用本地记录,不发起拉取。
|
|
59
66
|
- 通过选择、拖放或粘贴附加文件,最多 20 个,每个不超过 20 MB。图片会显示预览,并作为 Pi 图片内容发送。
|
|
60
|
-
-
|
|
67
|
+
- 从侧栏「新会话」下方的「插件」入口管理内置 Git、文件和终端。支持列表/详情、搜索、真实启停和全局持久化;所有聊天空闲后才可切换。停用会关闭相关标签并阻止后端请求,停用终端需确认并结束此服务的全部终端。外部 Pi Web 插件展示运行时归属,仍通过「包与扩展」管理,不提供只隐藏 UI 的假开关。
|
|
61
68
|
- 在设置面板中管理外观、Pi 软件包、扩展、技能和模型。模型页可添加模型、编辑显示名称、上下文窗口、最大输出 token 数和输入类型,或恢复内置模型的默认参数;配置写入 Pi 的 `models.json`。网页登录会自动打开提供方授权页;当回调无法到达 Pi 时,仍可手动粘贴授权码。已保存的提供方认证可在这里移除。凭据保留在 Pi 本地的 `auth.json`,不会返回给浏览器。
|
|
62
69
|
|
|
63
70
|
如果通过 Pi 终端手动输入 `/web`,该终端会话会与浏览器保持同步。使用启动器时,Pi 在后台运行。在其他工作区打开的会话使用 Pi SDK,并加载该工作区的技能和项目上下文;已启用的 Web 插件 Pi 入口会显式加载,其他普通 Pi 扩展仍与主会话隔离。
|
|
@@ -68,11 +75,11 @@ pi
|
|
|
68
75
|
|
|
69
76
|
## 桌面端(可选)
|
|
70
77
|
|
|
71
|
-
**[下载 pi 桌面测试版](https://github.com/chengzhiyi/pi-webapp/releases)** — 目前仅发布已验收的 macOS Apple 芯片(arm64)版本。在 `desktop-v...` 预发布版本中下载 DMG,核对 `SHA256SUMS` 后安装 **pi.app**,在“pi → 关于 pi
|
|
78
|
+
**[下载 pi 桌面测试版](https://github.com/chengzhiyi/pi-webapp/releases)** — 目前仅发布已验收的 macOS Apple 芯片(arm64)版本。在 `desktop-v...` 预发布版本中下载 DMG,核对 `SHA256SUMS` 后安装 **pi.app**,在“pi → 关于 pi”查看构建号。桌面构建 7 起使用完整 ad-hoc 临时签名,但没有发布者身份、未公证,macOS 仍可能拦截;请按[小范围测试安装说明](docs/desktop-testing.md)操作,不要全局关闭系统安全保护。旧测试包不会自动修复;Windows/Linux 暂不提供已验收的发行包。
|
|
72
79
|
|
|
73
80
|
同一套界面也可在名为 **pi** 的 Electron 桌面程序中使用,现有浏览器与 `/web` 方式保持不变。桌面包自带 Node/Pi,支持独立窗口、已有服务复用、在浏览器打开同一服务,以及退出时选择保留或停止后台任务。
|
|
74
81
|
|
|
75
|
-
源码构建:`npm ci` → `npm run desktop:prepare` → `npm run desktop:start`。运行 `npm run desktop:package` 生成当前系统/架构的安装包,输出到 `desktop-release/`。当前本地验证目标为 macOS arm64;Windows/Linux
|
|
82
|
+
源码构建:`npm ci` → `npm run desktop:prepare` → `npm run desktop:start`。运行 `npm run desktop:package` 生成当前系统/架构的安装包,输出到 `desktop-release/`。当前本地验证目标为 macOS arm64;Windows/Linux 打包配置需在对应系统验证。无发布者签名的测试包仅支持校验下载后手动安装,不会自动安装更新。安装包文件名包含桌面构建号;“pi → 关于 pi”显示实际运行的构建号和应用位置。替换前先退出旧桌面,外部兼容 Web 后台无需停止;若替换桌面自有运行时,请先结束任务并停止该自有后台。
|
|
76
83
|
|
|
77
84
|
详细说明见 [桌面端构建、测试与发布边界](docs/desktop.md)。
|
|
78
85
|
|
package/dist/extension.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(){try{var r=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},e=new r.Error().stack;e&&(r._sentryDebugIds=r._sentryDebugIds||{},r._sentryDebugIds[e]="456337da-7ef2-4728-a06e-2fbd3027f1d1",r._sentryDebugIdIdentifier="sentry-dbid-456337da-7ef2-4728-a06e-2fbd3027f1d1")}catch{}})();import{delimiter as Do,dirname as Oo,join as Xe,resolve as Ct}from"node:path";import{realpathSync as gi,watch as Fo}from"node:fs";import{fileURLToPath as Bo}from"node:url";import{DefaultPackageManager as yi,getAgentDir as ae,ModelRuntime as $o,SessionManager as bi,SettingsManager as jo,VERSION as Ye}from"@earendil-works/pi-coding-agent";import{getSupportedThinkingLevels as Lo}from"@earendil-works/pi-ai";import{randomBytes as jr,randomUUID as pt,timingSafeEqual as ms}from"node:crypto";import{readFile as gs,readdir as hs}from"node:fs/promises";import{createServer as ws}from"node:http";import{join as ut}from"node:path";import{getAgentDir as ys}from"@earendil-works/pi-coding-agent";import{isAbsolute as tn,relative as rn}from"node:path";var de={git:"@pi-webapp/git",files:"@pi-webapp/files",terminal:"@pi-webapp/terminal"};var _t={id:de.git,kind:"builtin",version:"1.0.0",name:{zh:"Git",en:"Git"},description:{zh:"\u5BA1\u9605\u5DE5\u4F5C\u533A\u6539\u52A8\u4E0E\u63D0\u4EA4\u5386\u53F2\uFF0C\u6682\u5B58\u6587\u4EF6\u3001\u63D0\u4EA4\u5E76\u7BA1\u7406\u672C\u5730\u5206\u652F\u3002",en:"Review workspace changes and history, stage files, commit and manage local branches."}};import{execFile as Si}from"node:child_process";import{realpath as xi}from"node:fs/promises";import{isAbsolute as Tt,relative as Ii,sep as Ci,win32 as _i}from"node:path";var P=class extends Error{status;reason;constructor(e,t=400,i="git_failed"){super(e),this.name="WorkspaceGitError",this.status=t,this.reason=i}};function T(r,e,t=2*1024*1024,i={}){let n=Object.fromEntries(Object.entries(process.env).filter(([s])=>!s.startsWith("GIT_")));return new Promise((s,a)=>{let l,p=Si("git",["--no-pager","--literal-pathspecs","-c","core.fsmonitor=false",...e],{cwd:r,env:{...n,LC_ALL:"C",GIT_OPTIONAL_LOCKS:"0",GIT_TERMINAL_PROMPT:"0",GIT_NO_REPLACE_OBJECTS:"1"},encoding:"utf8",timeout:i.write?3e4:1e4,maxBuffer:t,windowsHide:!0},(h,u,c)=>{if(!h){if(l){a(new P("Git \u8F93\u5165\u4F20\u8F93\u5931\u8D25\uFF0C\u64CD\u4F5C\u7ED3\u679C\u672A\u786E\u8BA4\uFF0C\u8BF7\u5237\u65B0\u68C0\u67E5\u4ED3\u5E93\u72B6\u6001\uFF1B\u4E0D\u8981\u81EA\u52A8\u91CD\u8BD5",503,i.write?"outcome_unknown":"git_failed"));return}s(u);return}if(i.write){let m=h.killed||h.code==="ERR_CHILD_PROCESS_STDIO_MAXBUFFER"?"outcome_unknown":/index\.lock|cannot lock ref|could not lock/i.test(c)?"index_locked":/already exists/i.test(c)?"branch_exists":/already checked out|used by worktree/i.test(c)?"branch_in_use":/would be overwritten|local changes/i.test(c)?"dirty_worktree":/identity unknown|unable to auto-detect email|empty ident/i.test(c)?"identity_missing":"git_failed";a(new P("Git \u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0\u68C0\u67E5\u4ED3\u5E93\u72B6\u6001\u3001\u8EAB\u4EFD\u6216 hooks \u914D\u7F6E",m==="outcome_unknown"?503:409,m));return}let f=h.code;f==="ENOENT"?a(new P("Git \u672A\u5B89\u88C5\u6216\u5DE5\u4F5C\u533A\u4E0D\u53EF\u7528",503)):f==="ERR_CHILD_PROCESS_STDIO_MAXBUFFER"?a(new P("Git \u8F93\u51FA\u8FC7\u5927\uFF0C\u8BF7\u7F29\u5C0F\u8BFB\u53D6\u8303\u56F4",413)):/not a git repository/.test(c)?a(new P("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93",404)):a(new P(h.killed?"Git \u8BFB\u53D6\u8D85\u65F6\uFF0C\u8BF7\u91CD\u8BD5":"Git \u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u4ED3\u5E93\u72B6\u6001\u548C\u76EE\u5F55\u6743\u9650",503))});p.stdin?.on("error",h=>{l=h}),i.input!==void 0&&p.stdin?.end(i.input)})}async function X(r){let e=await xi(r),t;try{t=(await T(e,["rev-parse","--show-toplevel"])).replace(/\n$/,"")}catch(n){if(n instanceof P&&n.status===404)return{kind:"not-repository"};if(n instanceof P&&n.message==="Git \u672A\u5B89\u88C5\u6216\u5DE5\u4F5C\u533A\u4E0D\u53EF\u7528")return{kind:"unavailable"};throw n}let i=Ii(t,e).split(Ci).join("/");if(i===".."||i.startsWith("../")||Tt(i))throw new P("\u4ED3\u5E93\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u4E4B\u5916",403);return{kind:"repository",cwd:e,root:t,scope:i?`${i}/`:""}}function te(r){if(typeof r!="string"||!r||r.length>4096||r.includes("\0")||r.includes("\\")||Tt(r)||_i.isAbsolute(r)||r.split("/").some(e=>!e||e==="."||e===".."))throw new P("\u5DE5\u4F5C\u533A\u6587\u4EF6\u8DEF\u5F84\u65E0\u6548",403)}import{createHash as Vi}from"node:crypto";import{lstat as it,realpath as Gt}from"node:fs/promises";import{dirname as Lt,isAbsolute as Ui,relative as zi,resolve as ve}from"node:path";import{lstat as ji}from"node:fs/promises";import{resolve as Li}from"node:path";import{constants as et}from"node:fs";import{open as Ti,opendir as Mi,realpath as Mt,stat as Wt}from"node:fs/promises";import{isAbsolute as Nt,relative as Ai,resolve as Ri,sep as Wi,win32 as Ni}from"node:path";var Di=1e3,Oi=512*1024,Fi=8*1024*1024,At=1e4,Bi=new Intl.Collator("en",{numeric:!0,sensitivity:"base"}),W=class extends Error{status;constructor(e,t=400){super(e),this.name="WorkspaceFileError",this.status=t}};function Rt(r,e){let t=Ai(r,e);return t!==".."&&!t.startsWith(`..${Wi}`)&&!Nt(t)}async function tt(r,e){if(typeof e!="string"||e.length>4096||e.includes("\0")||e.includes("\\")||Nt(e)||Ni.isAbsolute(e))throw new W("\u5DE5\u4F5C\u533A\u6587\u4EF6\u8DEF\u5F84\u65E0\u6548");let t=await Mt(r),i=Ri(t,e);if(!Rt(t,i))throw new W("\u6587\u4EF6\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u4E4B\u5916",403);let n=await Mt(i);if(!Rt(t,n))throw new W("\u6587\u4EF6\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u4E4B\u5916",403);return n}async function Dt(r){try{return await r()}catch(e){if(e instanceof W)throw e;let t=e.code;throw t==="ENOENT"||t==="ENOTDIR"?new W("\u6587\u4EF6\u6216\u76EE\u5F55\u5DF2\u4E0D\u5B58\u5728",404):t==="EACCES"||t==="EPERM"?new W("\u6CA1\u6709\u6743\u9650\u8BFB\u53D6\u6587\u4EF6\u6216\u76EE\u5F55",403):e}}async function Ot(r,e){return Dt(async()=>{let t=await tt(r,e),i=[],n=!1;for await(let s of await Mi(t)){if(i.length===Di){n=!0;break}let a=e?`${e}/${s.name}`:s.name,l=s.isDirectory()?"directory":s.isFile()?"file":"other";if(s.isSymbolicLink())try{let p=await Wt(await tt(r,a));l=p.isDirectory()?"directory":p.isFile()?"file":"other"}catch{}i.push({name:s.name,path:a,kind:l})}return i.sort((s,a)=>+(a.kind==="directory")-+(s.kind==="directory")||Bi.compare(s.name,a.name)||s.name.localeCompare(a.name)),{path:e,entries:i,truncated:n}})}function $i(r){if(r.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])))return"image/png";if(r[0]===255&&r[1]===216&&r[2]===255)return"image/jpeg";if(["GIF87a","GIF89a"].includes(r.subarray(0,6).toString("ascii")))return"image/gif";if(r.subarray(0,4).toString("ascii")==="RIFF"&&r.subarray(8,12).toString("ascii")==="WEBP")return"image/webp"}async function be(r,e){return Dt(async()=>{let t=await tt(r,e);if(!(await Wt(t)).isFile())throw new W("\u8FD9\u4E0D\u662F\u53EF\u9884\u89C8\u7684\u666E\u901A\u6587\u4EF6");let i=await Ti(t,et.O_RDONLY|et.O_NONBLOCK|(et.O_NOFOLLOW??0));try{let n=await i.stat();if(!n.isFile())throw new W("\u8FD9\u4E0D\u662F\u53EF\u9884\u89C8\u7684\u666E\u901A\u6587\u4EF6");let s={path:e,size:n.size},a=Buffer.alloc(16);await i.read(a,0,a.length,0);let l=$i(a),p=l?Fi:Oi;if(n.size>p)return{...s,kind:"too-large",limit:p,unit:"bytes"};let h=Buffer.alloc(Math.min(n.size+1,p+1)),u=0;for(;u<h.length;){let{bytesRead:m}=await i.read(h,u,h.length-u,u);if(!m)break;u+=m}if(u>n.size)throw new W("\u6587\u4EF6\u6B63\u5728\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6",409);let c=h.subarray(0,u);if(l)return{...s,kind:"image",mimeType:l,data:c.toString("base64")};if(c.some(m=>m===0||m<32&&![9,10,12,13,27].includes(m)))return{...s,kind:"unsupported"};let f=1;for(let m=0;m<c.length-1;m++)if(c[m]===10&&++f>At)return{...s,kind:"too-large",limit:At,unit:"lines"};try{return{...s,kind:"text",text:new TextDecoder("utf-8",{fatal:!0}).decode(c)}}catch{return{...s,kind:"unsupported"}}}finally{await i.close()}})}var Gi=1e3,Ft=512*1024,Bt=1e4;function $t(r,e){return e.startsWith(r)&&e.slice(r.length)||void 0}async function pe(r){let e=await X(r);if(e.kind!=="repository")return e;let{root:t,scope:i}=e,n=await T(e.cwd,["status","--porcelain=v2","--branch","-z","--untracked-files=all","--","."]),s={kind:"repository",root:t,scope:i,branch:null,head:null,upstream:null,ahead:null,behind:null,entries:[],truncated:!1},a=n.split("\0");for(let l=0;l<a.length;l++){let p=a[l];if(p.startsWith("# branch.oid ")){let I=p.slice(13);s.head=I==="(initial)"?null:I;continue}if(p.startsWith("# branch.head ")){let I=p.slice(14);s.branch=I==="(detached)"?null:I;continue}if(p.startsWith("# branch.upstream ")){s.upstream=p.slice(18);continue}if(p.startsWith("# branch.ab ")){let I=p.match(/\+(\d+) -(\d+)/);I&&(s.ahead=Number(I[1]),s.behind=Number(I[2]));continue}let h=p[0];if(!["1","2","u","?"].includes(h??""))continue;let u=h==="?"?1:h==="1"?8:h==="2"?9:10,c=0;for(let I=0;I<u;I++)c=p.indexOf(" ",c)+1;let f=$t(i,p.slice(c)),m=h==="2"?a[++l]:void 0;if(!f)continue;if(s.entries.length===Gi){s.truncated=!0;continue}let g=p.slice(0,c).split(" "),E={path:f,index:h==="?"?"?":g[1][0],worktree:h==="?"?"?":g[1][1],conflict:h==="u",untracked:h==="?",submodule:h!=="?"&&g[2]!=="N..."},x=m===void 0?void 0:$t(i,m);x&&(E.originalPath=x),s.entries.push(E)}return s}async function jt(r,e,t){if(te(e),!["staged","unstaged","untracked"].includes(t))throw new P("Git \u5DEE\u5F02\u8303\u56F4\u65E0\u6548");let i=await pe(r);if(i.kind!=="repository")throw new P("\u5F53\u524D\u5DE5\u4F5C\u533A\u6CA1\u6709\u53EF\u8BFB\u53D6\u7684 Git \u4ED3\u5E93",404);let n={path:e,area:t},s=i.entries.find(l=>l.path===e);if(!s)return{...n,kind:"empty"};if(s.conflict)return{...n,kind:"conflict"};if(s.submodule)return{...n,kind:"submodule"};if(t==="untracked"){if(!s.untracked)return{...n,kind:"empty"};if(!(await ji(Li(r,e))).isFile())return{...n,kind:"unsupported"};let l=await be(r,e);if(l.kind==="too-large")return{...n,kind:"too-large"};if(l.kind!=="text")return{...n,kind:"binary"};if(!l.text)return{...n,kind:"empty"};let p=l.text.split(`
|
|
1
|
+
(function(){try{var r=typeof window<"u"?window:typeof global<"u"?global:typeof globalThis<"u"?globalThis:typeof self<"u"?self:{},e=new r.Error().stack;e&&(r._sentryDebugIds=r._sentryDebugIds||{},r._sentryDebugIds[e]="0154e117-4e78-4f70-8313-bc1dd639a757",r._sentryDebugIdIdentifier="sentry-dbid-0154e117-4e78-4f70-8313-bc1dd639a757")}catch{}})();import{delimiter as Do,dirname as Oo,join as Xe,resolve as Ct}from"node:path";import{realpathSync as gi,watch as Fo}from"node:fs";import{fileURLToPath as Bo}from"node:url";import{DefaultPackageManager as yi,getAgentDir as ae,ModelRuntime as $o,SessionManager as bi,SettingsManager as jo,VERSION as Ye}from"@earendil-works/pi-coding-agent";import{getSupportedThinkingLevels as Lo}from"@earendil-works/pi-ai";import{randomBytes as jr,randomUUID as pt,timingSafeEqual as ms}from"node:crypto";import{readFile as gs,readdir as hs}from"node:fs/promises";import{createServer as ws}from"node:http";import{join as ut}from"node:path";import{getAgentDir as ys}from"@earendil-works/pi-coding-agent";import{isAbsolute as tn,relative as rn}from"node:path";var de={git:"@pi-webapp/git",files:"@pi-webapp/files",terminal:"@pi-webapp/terminal"};var _t={id:de.git,kind:"builtin",version:"1.0.0",name:{zh:"Git",en:"Git"},description:{zh:"\u5BA1\u9605\u5DE5\u4F5C\u533A\u6539\u52A8\u4E0E\u63D0\u4EA4\u5386\u53F2\uFF0C\u6682\u5B58\u6587\u4EF6\u3001\u63D0\u4EA4\u5E76\u7BA1\u7406\u672C\u5730\u5206\u652F\u3002",en:"Review workspace changes and history, stage files, commit and manage local branches."}};import{execFile as Si}from"node:child_process";import{realpath as xi}from"node:fs/promises";import{isAbsolute as Tt,relative as Ii,sep as Ci,win32 as _i}from"node:path";var P=class extends Error{status;reason;constructor(e,t=400,i="git_failed"){super(e),this.name="WorkspaceGitError",this.status=t,this.reason=i}};function T(r,e,t=2*1024*1024,i={}){let n=Object.fromEntries(Object.entries(process.env).filter(([s])=>!s.startsWith("GIT_")));return new Promise((s,a)=>{let l,p=Si("git",["--no-pager","--literal-pathspecs","-c","core.fsmonitor=false",...e],{cwd:r,env:{...n,LC_ALL:"C",GIT_OPTIONAL_LOCKS:"0",GIT_TERMINAL_PROMPT:"0",GIT_NO_REPLACE_OBJECTS:"1"},encoding:"utf8",timeout:i.write?3e4:1e4,maxBuffer:t,windowsHide:!0},(h,u,c)=>{if(!h){if(l){a(new P("Git \u8F93\u5165\u4F20\u8F93\u5931\u8D25\uFF0C\u64CD\u4F5C\u7ED3\u679C\u672A\u786E\u8BA4\uFF0C\u8BF7\u5237\u65B0\u68C0\u67E5\u4ED3\u5E93\u72B6\u6001\uFF1B\u4E0D\u8981\u81EA\u52A8\u91CD\u8BD5",503,i.write?"outcome_unknown":"git_failed"));return}s(u);return}if(i.write){let m=h.killed||h.code==="ERR_CHILD_PROCESS_STDIO_MAXBUFFER"?"outcome_unknown":/index\.lock|cannot lock ref|could not lock/i.test(c)?"index_locked":/already exists/i.test(c)?"branch_exists":/already checked out|used by worktree/i.test(c)?"branch_in_use":/would be overwritten|local changes/i.test(c)?"dirty_worktree":/identity unknown|unable to auto-detect email|empty ident/i.test(c)?"identity_missing":"git_failed";a(new P("Git \u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0\u68C0\u67E5\u4ED3\u5E93\u72B6\u6001\u3001\u8EAB\u4EFD\u6216 hooks \u914D\u7F6E",m==="outcome_unknown"?503:409,m));return}let f=h.code;f==="ENOENT"?a(new P("Git \u672A\u5B89\u88C5\u6216\u5DE5\u4F5C\u533A\u4E0D\u53EF\u7528",503)):f==="ERR_CHILD_PROCESS_STDIO_MAXBUFFER"?a(new P("Git \u8F93\u51FA\u8FC7\u5927\uFF0C\u8BF7\u7F29\u5C0F\u8BFB\u53D6\u8303\u56F4",413)):/not a git repository/.test(c)?a(new P("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F Git \u4ED3\u5E93",404)):a(new P(h.killed?"Git \u8BFB\u53D6\u8D85\u65F6\uFF0C\u8BF7\u91CD\u8BD5":"Git \u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u4ED3\u5E93\u72B6\u6001\u548C\u76EE\u5F55\u6743\u9650",503))});p.stdin?.on("error",h=>{l=h}),i.input!==void 0&&p.stdin?.end(i.input)})}async function X(r){let e=await xi(r),t;try{t=(await T(e,["rev-parse","--show-toplevel"])).replace(/\n$/,"")}catch(n){if(n instanceof P&&n.status===404)return{kind:"not-repository"};if(n instanceof P&&n.message==="Git \u672A\u5B89\u88C5\u6216\u5DE5\u4F5C\u533A\u4E0D\u53EF\u7528")return{kind:"unavailable"};throw n}let i=Ii(t,e).split(Ci).join("/");if(i===".."||i.startsWith("../")||Tt(i))throw new P("\u4ED3\u5E93\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u4E4B\u5916",403);return{kind:"repository",cwd:e,root:t,scope:i?`${i}/`:""}}function te(r){if(typeof r!="string"||!r||r.length>4096||r.includes("\0")||r.includes("\\")||Tt(r)||_i.isAbsolute(r)||r.split("/").some(e=>!e||e==="."||e===".."))throw new P("\u5DE5\u4F5C\u533A\u6587\u4EF6\u8DEF\u5F84\u65E0\u6548",403)}import{createHash as Vi}from"node:crypto";import{lstat as it,realpath as Gt}from"node:fs/promises";import{dirname as Lt,isAbsolute as Ui,relative as zi,resolve as ve}from"node:path";import{lstat as ji}from"node:fs/promises";import{resolve as Li}from"node:path";import{constants as et}from"node:fs";import{open as Ti,opendir as Mi,realpath as Mt,stat as Wt}from"node:fs/promises";import{isAbsolute as Nt,relative as Ai,resolve as Ri,sep as Wi,win32 as Ni}from"node:path";var Di=1e3,Oi=512*1024,Fi=8*1024*1024,At=1e4,Bi=new Intl.Collator("en",{numeric:!0,sensitivity:"base"}),W=class extends Error{status;constructor(e,t=400){super(e),this.name="WorkspaceFileError",this.status=t}};function Rt(r,e){let t=Ai(r,e);return t!==".."&&!t.startsWith(`..${Wi}`)&&!Nt(t)}async function tt(r,e){if(typeof e!="string"||e.length>4096||e.includes("\0")||e.includes("\\")||Nt(e)||Ni.isAbsolute(e))throw new W("\u5DE5\u4F5C\u533A\u6587\u4EF6\u8DEF\u5F84\u65E0\u6548");let t=await Mt(r),i=Ri(t,e);if(!Rt(t,i))throw new W("\u6587\u4EF6\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u4E4B\u5916",403);let n=await Mt(i);if(!Rt(t,n))throw new W("\u6587\u4EF6\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u4E4B\u5916",403);return n}async function Dt(r){try{return await r()}catch(e){if(e instanceof W)throw e;let t=e.code;throw t==="ENOENT"||t==="ENOTDIR"?new W("\u6587\u4EF6\u6216\u76EE\u5F55\u5DF2\u4E0D\u5B58\u5728",404):t==="EACCES"||t==="EPERM"?new W("\u6CA1\u6709\u6743\u9650\u8BFB\u53D6\u6587\u4EF6\u6216\u76EE\u5F55",403):e}}async function Ot(r,e){return Dt(async()=>{let t=await tt(r,e),i=[],n=!1;for await(let s of await Mi(t)){if(i.length===Di){n=!0;break}let a=e?`${e}/${s.name}`:s.name,l=s.isDirectory()?"directory":s.isFile()?"file":"other";if(s.isSymbolicLink())try{let p=await Wt(await tt(r,a));l=p.isDirectory()?"directory":p.isFile()?"file":"other"}catch{}i.push({name:s.name,path:a,kind:l})}return i.sort((s,a)=>+(a.kind==="directory")-+(s.kind==="directory")||Bi.compare(s.name,a.name)||s.name.localeCompare(a.name)),{path:e,entries:i,truncated:n}})}function $i(r){if(r.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])))return"image/png";if(r[0]===255&&r[1]===216&&r[2]===255)return"image/jpeg";if(["GIF87a","GIF89a"].includes(r.subarray(0,6).toString("ascii")))return"image/gif";if(r.subarray(0,4).toString("ascii")==="RIFF"&&r.subarray(8,12).toString("ascii")==="WEBP")return"image/webp"}async function be(r,e){return Dt(async()=>{let t=await tt(r,e);if(!(await Wt(t)).isFile())throw new W("\u8FD9\u4E0D\u662F\u53EF\u9884\u89C8\u7684\u666E\u901A\u6587\u4EF6");let i=await Ti(t,et.O_RDONLY|et.O_NONBLOCK|(et.O_NOFOLLOW??0));try{let n=await i.stat();if(!n.isFile())throw new W("\u8FD9\u4E0D\u662F\u53EF\u9884\u89C8\u7684\u666E\u901A\u6587\u4EF6");let s={path:e,size:n.size},a=Buffer.alloc(16);await i.read(a,0,a.length,0);let l=$i(a),p=l?Fi:Oi;if(n.size>p)return{...s,kind:"too-large",limit:p,unit:"bytes"};let h=Buffer.alloc(Math.min(n.size+1,p+1)),u=0;for(;u<h.length;){let{bytesRead:m}=await i.read(h,u,h.length-u,u);if(!m)break;u+=m}if(u>n.size)throw new W("\u6587\u4EF6\u6B63\u5728\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6",409);let c=h.subarray(0,u);if(l)return{...s,kind:"image",mimeType:l,data:c.toString("base64")};if(c.some(m=>m===0||m<32&&![9,10,12,13,27].includes(m)))return{...s,kind:"unsupported"};let f=1;for(let m=0;m<c.length-1;m++)if(c[m]===10&&++f>At)return{...s,kind:"too-large",limit:At,unit:"lines"};try{return{...s,kind:"text",text:new TextDecoder("utf-8",{fatal:!0}).decode(c)}}catch{return{...s,kind:"unsupported"}}}finally{await i.close()}})}var Gi=1e3,Ft=512*1024,Bt=1e4;function $t(r,e){return e.startsWith(r)&&e.slice(r.length)||void 0}async function pe(r){let e=await X(r);if(e.kind!=="repository")return e;let{root:t,scope:i}=e,n=await T(e.cwd,["status","--porcelain=v2","--branch","-z","--untracked-files=all","--","."]),s={kind:"repository",root:t,scope:i,branch:null,head:null,upstream:null,ahead:null,behind:null,entries:[],truncated:!1},a=n.split("\0");for(let l=0;l<a.length;l++){let p=a[l];if(p.startsWith("# branch.oid ")){let I=p.slice(13);s.head=I==="(initial)"?null:I;continue}if(p.startsWith("# branch.head ")){let I=p.slice(14);s.branch=I==="(detached)"?null:I;continue}if(p.startsWith("# branch.upstream ")){s.upstream=p.slice(18);continue}if(p.startsWith("# branch.ab ")){let I=p.match(/\+(\d+) -(\d+)/);I&&(s.ahead=Number(I[1]),s.behind=Number(I[2]));continue}let h=p[0];if(!["1","2","u","?"].includes(h??""))continue;let u=h==="?"?1:h==="1"?8:h==="2"?9:10,c=0;for(let I=0;I<u;I++)c=p.indexOf(" ",c)+1;let f=$t(i,p.slice(c)),m=h==="2"?a[++l]:void 0;if(!f)continue;if(s.entries.length===Gi){s.truncated=!0;continue}let g=p.slice(0,c).split(" "),E={path:f,index:h==="?"?"?":g[1][0],worktree:h==="?"?"?":g[1][1],conflict:h==="u",untracked:h==="?",submodule:h!=="?"&&g[2]!=="N..."},x=m===void 0?void 0:$t(i,m);x&&(E.originalPath=x),s.entries.push(E)}return s}async function jt(r,e,t){if(te(e),!["staged","unstaged","untracked"].includes(t))throw new P("Git \u5DEE\u5F02\u8303\u56F4\u65E0\u6548");let i=await pe(r);if(i.kind!=="repository")throw new P("\u5F53\u524D\u5DE5\u4F5C\u533A\u6CA1\u6709\u53EF\u8BFB\u53D6\u7684 Git \u4ED3\u5E93",404);let n={path:e,area:t},s=i.entries.find(l=>l.path===e);if(!s)return{...n,kind:"empty"};if(s.conflict)return{...n,kind:"conflict"};if(s.submodule)return{...n,kind:"submodule"};if(t==="untracked"){if(!s.untracked)return{...n,kind:"empty"};if(!(await ji(Li(r,e))).isFile())return{...n,kind:"unsupported"};let l=await be(r,e);if(l.kind==="too-large")return{...n,kind:"too-large"};if(l.kind!=="text")return{...n,kind:"binary"};if(!l.text)return{...n,kind:"empty"};let p=l.text.split(`
|
|
2
2
|
`),h=p.at(-1)==="";h&&p.pop();let u=`--- /dev/null
|
|
3
3
|
+++ b/${e.replace(/[\r\n]/g," ")}
|
|
4
4
|
@@ -0,0 +1,${p.length} @@
|
|
@@ -25,7 +25,7 @@ ${h.stack.split(`
|
|
|
25
25
|
`).slice(1).join(`
|
|
26
26
|
`)}`);let c=this.reportError(u,{...s,stage:"provider_login"});p.view={id:l,providerId:e,method:t,status:"error",error:"\u767B\u5F55\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u63D0\u4F9B\u65B9\u63D0\u793A\u5E76\u91CD\u8BD5\u3002",errorId:c}}}),{...p.view}}get(e){let t=this.flows.get(e)?.view;return t?{...t}:void 0}getActive(){let e=[...this.flows.values()].find(({view:t})=>t.status==="running"||t.status==="waiting")?.view;return e?{...e}:void 0}respond(e,t){let i=this.flows.get(e);if(!i?.answer||i.view.status!=="waiting"||i.view.prompt?.type==="select"&&!i.view.prompt.options.some(s=>s.id===t))return!1;let n=i.answer;return i.answer=void 0,i.reject=void 0,i.view={...i.view,status:"running",prompt:void 0},n(t),!0}cancel(e){let t=this.flows.get(e);return!t||t.view.status==="done"||t.view.status==="error"?!1:(t.view={...t.view,status:"cancelled",prompt:void 0},t.controller.abort(),t.reject?.(new Error("\u767B\u5F55\u5DF2\u53D6\u6D88")),t.answer=void 0,t.reject=void 0,!0)}close(){for(let e of this.flows.keys())this.cancel(e);this.flows.clear()}};import{randomBytes as Un}from"node:crypto";import{readFile as zn,rename as Hn,rm as Jn,stat as qn,writeFile as Kn,mkdir as Xn}from"node:fs/promises";import{dirname as Yn}from"node:path";var Zn=["openai-completions","openai-responses","anthropic-messages"];function dt(r){if(!r||typeof r!="object")return!1;let e=r;if(typeof e.id!="string"||!/^[a-z0-9][a-z0-9._-]{0,63}$/.test(e.id)||typeof e.name!="string"||!e.name.trim()||e.name.length>100||typeof e.modelId!="string"||!e.modelId.trim()||e.modelId.length>200||!Zn.includes(e.api)||typeof e.baseUrl!="string"||e.baseUrl.length>2048)return!1;try{let t=new URL(e.baseUrl);return(t.protocol==="https:"||t.protocol==="http:")&&!!t.hostname&&!t.username&&!t.password&&!t.hash}catch{return!1}}async function Mr(r,e){if(!dt(e))throw new Error("\u81EA\u5B9A\u4E49\u63D0\u4F9B\u65B9\u540D\u79F0\u3001\u5730\u5740\u6216\u6A21\u578B\u65E0\u6548");let t;try{t=await zn(r,"utf8")}catch(l){if(l.code!=="ENOENT")throw l}let i;if(t===void 0)i={providers:{}};else{try{i=JSON.parse(t)}catch{throw new Error("models.json \u542B\u6CE8\u91CA\u6216\u683C\u5F0F\u9519\u8BEF\uFF0C\u8BF7\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u624B\u52A8\u6DFB\u52A0\u63D0\u4F9B\u65B9")}if(!i||typeof i!="object"||!i.providers||typeof i.providers!="object"||Array.isArray(i.providers))throw new Error("models.json \u7ED3\u6784\u65E0\u6548")}if(Object.hasOwn(i.providers,e.id))throw new Error("\u63D0\u4F9B\u65B9\u5DF2\u5B58\u5728");let n={...i,providers:{...i.providers,[e.id]:{name:e.name.trim(),baseUrl:e.baseUrl,api:e.api,models:[{id:e.modelId.trim()}]}}};await Xn(Yn(r),{recursive:!0});let s=t===void 0?384:(await qn(r)).mode&511,a=`${r}.pi-web-${Un(6).toString("hex")}.tmp`;try{await Kn(a,`${JSON.stringify(n,null,2)}
|
|
27
27
|
`,{mode:s,flag:"wx"}),await Hn(a,r)}finally{await Jn(a,{force:!0})}}import{randomBytes as Qn}from"node:crypto";import{mkdir as es,readFile as ts,rename as rs,rm as is,stat as ns,writeFile as ss}from"node:fs/promises";import{dirname as os}from"node:path";function Ar(r){if(!r||r.length>2048)return!1;try{let e=new URL(r);return(e.protocol==="https:"||e.protocol==="http:")&&!!e.hostname&&!e.username&&!e.password&&!e.search&&!e.hash}catch{return!1}}function Rr(r){if(!r||typeof r!="object")return!1;let e=r;return e.action==="save"?(e.originalId===void 0||typeof e.originalId=="string")&&Wr(e.model):e.action==="base_url"?e.baseUrl===null||typeof e.baseUrl=="string"&&Ar(e.baseUrl):e.action==="remove"&&typeof e.id=="string"&&e.id.length>0&&e.id.length<=200}function Wr(r){if(!r||typeof r!="object")return!1;let e=r;if(typeof e.id!="string"||!e.id.trim()||e.id.length>200||e.name!==void 0&&(typeof e.name!="string"||!e.name.trim()||e.name.length>200))return!1;for(let t of[e.contextWindow,e.maxTokens])if(t!==void 0&&(!Number.isSafeInteger(t)||t<=0))return!1;return e.input===void 0||Array.isArray(e.input)&&e.input.length>0&&e.input.every(t=>t==="text"||t==="image")&&new Set(e.input).size===e.input.length}async function Nr(r){let e;try{e=await ts(r,"utf8")}catch(i){if(i.code!=="ENOENT")throw i}if(e===void 0)return{config:{providers:{}}};let t;try{t=JSON.parse(e)}catch{throw new Error("models.json \u542B\u6CE8\u91CA\u6216\u683C\u5F0F\u9519\u8BEF\uFF0C\u8BF7\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u624B\u52A8\u4FEE\u6539")}if(!t||typeof t!="object"||!t.providers||typeof t.providers!="object"||Array.isArray(t.providers))throw new Error("models.json \u7ED3\u6784\u65E0\u6548");return{config:t,existing:e}}async function as(r,e,t){await es(os(r),{recursive:!0});let i=t===void 0?384:(await ns(r)).mode&511,n=`${r}.pi-web-${Qn(6).toString("hex")}.tmp`;try{await ss(n,`${JSON.stringify(e,null,2)}
|
|
28
|
-
`,{mode:i,flag:"wx"}),await rs(n,r)}finally{await is(n,{force:!0})}}async function Dr(r,e,t){if(!t.getProvider(e))throw new Error("\u63D0\u4F9B\u65B9\u4E0D\u5B58\u5728");let{config:i}=await Nr(r),n=i.providers[e],s=new Set(n?.models?.map(a=>a.id)??[]);return{providerId:e,baseUrl:n?.baseUrl,models:t.getModels(e).map(a=>({id:a.id,name:a.name,contextWindow:a.contextWindow,maxTokens:a.maxTokens,input:[...a.input],source:s.has(a.id)?"custom":"builtin",overridden:!!n?.modelOverrides?.[a.id]}))}}async function Or(r,e,t,i){if(!i.getProvider(e))throw new Error("\u63D0\u4F9B\u65B9\u4E0D\u5B58\u5728");let{config:n,existing:s}=await Nr(r),a={...n.providers[e]??{}},l=[...a.models??[]],p={...a.modelOverrides??{}};if(t.action==="base_url"){if(t.baseUrl!==null&&!Ar(t.baseUrl))throw new Error("API \u5730\u5740\u65E0\u6548");t.baseUrl===null?delete a.baseUrl:a.baseUrl=t.baseUrl}else if(t.action==="save"){if(!Wr(t.model))throw new Error("\u6A21\u578B\u53C2\u6570\u65E0\u6548");let h={id:t.model.id.trim(),...t.model.name===void 0?{}:{name:t.model.name},...t.model.contextWindow===void 0?{}:{contextWindow:t.model.contextWindow},...t.model.maxTokens===void 0?{}:{maxTokens:t.model.maxTokens},...t.model.input===void 0?{}:{input:t.model.input}},u=t.originalId,c=u===void 0?-1:l.findIndex(f=>f.id===u);if(u===void 0&&(l.some(f=>f.id===h.id)||i.getModel(e,h.id)))throw new Error("\u6A21\u578B ID \u5DF2\u5B58\u5728");if(c>=0){if(h.id!==u&&(l.some(m=>m.id===h.id)||i.getModel(e,h.id)))throw new Error("\u6A21\u578B ID \u5DF2\u5B58\u5728");let f={...l[c],...h};for(let m of["name","contextWindow","maxTokens","input"])h[m]===void 0&&delete f[m];l[c]=f}else if(u!==void 0&&i.getModel(e,u)){if(h.id!==u)throw new Error("\u5185\u7F6E\u6A21\u578B ID \u4E0D\u53EF\u4FEE\u6539");let{id:f,...m}=h;p[u]={...p[u],...m};for(let g of["name","contextWindow","maxTokens","input"])h[g]===void 0&&delete p[u][g]}else{if(u!==void 0)throw new Error("\u6A21\u578B\u4E0D\u5B58\u5728");l.push(h)}}else{let h=l.findIndex(u=>u.id===t.id);if(h>=0)l.splice(h,1);else if(Object.hasOwn(p,t.id))delete p[t.id];else throw new Error("\u53EA\u80FD\u79FB\u9664\u81EA\u5B9A\u4E49\u6A21\u578B\u6216\u6062\u590D\u5DF2\u4FEE\u6539\u7684\u5185\u7F6E\u6A21\u578B")}l.length?a.models=l:delete a.models,Object.keys(p).length?a.modelOverrides=p:delete a.modelOverrides,Object.keys(a).length?n.providers[e]=a:delete n.providers[e],await as(r,n,s)}function Fr(r){return/^(?:npm:|git:|https?:\/\/|ssh:\/\/|git@)/i.test(r.trim())}import{randomBytes as Br,createHash as cs}from"node:crypto";import{fileURLToPath as ls}from"node:url";var Be="pi-webapp@0.1.
|
|
28
|
+
`,{mode:i,flag:"wx"}),await rs(n,r)}finally{await is(n,{force:!0})}}async function Dr(r,e,t){if(!t.getProvider(e))throw new Error("\u63D0\u4F9B\u65B9\u4E0D\u5B58\u5728");let{config:i}=await Nr(r),n=i.providers[e],s=new Set(n?.models?.map(a=>a.id)??[]);return{providerId:e,baseUrl:n?.baseUrl,models:t.getModels(e).map(a=>({id:a.id,name:a.name,contextWindow:a.contextWindow,maxTokens:a.maxTokens,input:[...a.input],source:s.has(a.id)?"custom":"builtin",overridden:!!n?.modelOverrides?.[a.id]}))}}async function Or(r,e,t,i){if(!i.getProvider(e))throw new Error("\u63D0\u4F9B\u65B9\u4E0D\u5B58\u5728");let{config:n,existing:s}=await Nr(r),a={...n.providers[e]??{}},l=[...a.models??[]],p={...a.modelOverrides??{}};if(t.action==="base_url"){if(t.baseUrl!==null&&!Ar(t.baseUrl))throw new Error("API \u5730\u5740\u65E0\u6548");t.baseUrl===null?delete a.baseUrl:a.baseUrl=t.baseUrl}else if(t.action==="save"){if(!Wr(t.model))throw new Error("\u6A21\u578B\u53C2\u6570\u65E0\u6548");let h={id:t.model.id.trim(),...t.model.name===void 0?{}:{name:t.model.name},...t.model.contextWindow===void 0?{}:{contextWindow:t.model.contextWindow},...t.model.maxTokens===void 0?{}:{maxTokens:t.model.maxTokens},...t.model.input===void 0?{}:{input:t.model.input}},u=t.originalId,c=u===void 0?-1:l.findIndex(f=>f.id===u);if(u===void 0&&(l.some(f=>f.id===h.id)||i.getModel(e,h.id)))throw new Error("\u6A21\u578B ID \u5DF2\u5B58\u5728");if(c>=0){if(h.id!==u&&(l.some(m=>m.id===h.id)||i.getModel(e,h.id)))throw new Error("\u6A21\u578B ID \u5DF2\u5B58\u5728");let f={...l[c],...h};for(let m of["name","contextWindow","maxTokens","input"])h[m]===void 0&&delete f[m];l[c]=f}else if(u!==void 0&&i.getModel(e,u)){if(h.id!==u)throw new Error("\u5185\u7F6E\u6A21\u578B ID \u4E0D\u53EF\u4FEE\u6539");let{id:f,...m}=h;p[u]={...p[u],...m};for(let g of["name","contextWindow","maxTokens","input"])h[g]===void 0&&delete p[u][g]}else{if(u!==void 0)throw new Error("\u6A21\u578B\u4E0D\u5B58\u5728");l.push(h)}}else{let h=l.findIndex(u=>u.id===t.id);if(h>=0)l.splice(h,1);else if(Object.hasOwn(p,t.id))delete p[t.id];else throw new Error("\u53EA\u80FD\u79FB\u9664\u81EA\u5B9A\u4E49\u6A21\u578B\u6216\u6062\u590D\u5DF2\u4FEE\u6539\u7684\u5185\u7F6E\u6A21\u578B")}l.length?a.models=l:delete a.models,Object.keys(p).length?a.modelOverrides=p:delete a.modelOverrides,Object.keys(a).length?n.providers[e]=a:delete n.providers[e],await as(r,n,s)}function Fr(r){return/^(?:npm:|git:|https?:\/\/|ssh:\/\/|git@)/i.test(r.trim())}import{randomBytes as Br,createHash as cs}from"node:crypto";import{fileURLToPath as ls}from"node:url";var Be="pi-webapp@0.1.24+3298bbb5efd1dad75bf6",K="3298bbb5efd1dad75bf6";var ds="https://070c0b7c5ac18d940c294e63ecfa0793@o4506663318716416.ingest.us.sentry.io/4512191040716800";function ps(r=process.env,e=console.warn){let t=i=>{let n={enabled:!1,environment:r.PI_WEB_SENTRY_ENVIRONMENT||"production",release:Be,buildId:K},s=r[`PI_WEB_SENTRY_${i}_DSN`]??r.PI_WEB_SENTRY_DSN??ds;if(r.PI_WEB_SENTRY_ENABLED==="false"||!s)return n;try{let a=new URL(s);if(a.protocol!=="https:"||!/^[\w-]+$/.test(a.username)||a.password||a.search||a.hash||!/\/(?:[\w-]+\/)*\d+$/.test(a.pathname))throw new Error;return{...n,enabled:!0,dsn:s}}catch{return e(`pi-webapp: invalid ${i.toLowerCase()} Sentry DSN; error reporting disabled for this side.`),n}};return{browser:t("BROWSER"),node:t("NODE")}}var Z=class{config;ready;reporter;client;salt=Br(16);breadcrumbs=new Ee;seen=new WeakSet;early=[];state={nodeVersion:process.version,platform:process.platform};fatal;closed=!1;failed=!1;get enabled(){return this.config.node.enabled&&!this.closed&&!this.failed}constructor(e={}){if(this.config=ps(e.env),!this.config.node.enabled){this.ready=Promise.resolve();return}if(e.monitor!==!1){let t=ls(import.meta.url).replace(/\\/g,"/"),i=t.lastIndexOf("/src/"),n=i>=0?t.slice(0,i+5):t.slice(0,t.lastIndexOf("/")+1);this.fatal=s=>{s.stack?.replace(/\\/g,"/").includes(t.endsWith("extension.js")?t:n)&&this.capture(s,{stage:"fatal"})},process.on("uncaughtExceptionMonitor",this.fatal)}this.ready=import("@sentry/node").then(t=>{if(!this.closed){this.client=new t.NodeClient({...this.config.node,dist:K,integrations:[],transport:e.transport??(i=>or(t.createTransport,i)),stackParser:t.defaultStackParser,beforeSend:sr,sendClientReports:!1,dataCollection:{...rr,httpBodies:[]},includeServerName:!1,enableRuntimeChannelInjection:!1,beforeSendLog:()=>null,beforeSendMetric:()=>null,enableOpenTelemetrySetup:!1,maxBreadcrumbs:100}),this.reporter=new Me(this.client,"node",K,t,this.breadcrumbs),this.reporter.setState(this.state);for(let i of this.early)this.reporter.capture(i.error,i.context,i.eventId)}}).catch(()=>{this.failed=!0,console.warn("pi-webapp: Sentry initialization failed; error reporting disabled.")}).finally(()=>{this.early=[]})}capture(e,t={}){try{if(!this.enabled||re(e))return;if(typeof e=="object"&&e!==null){if(this.seen.has(e))return;this.seen.add(e)}if(this.reporter)return this.reporter.capture(e,t);if(this.early.length>=20)return;let i=Br(16).toString("hex");return this.early.push({error:ue(e),context:q(t),eventId:i}),i}catch{return}}setState(e){this.state=q({...this.state,...e}),this.reporter?.setState(this.state)}breadcrumb(e,t={}){this.enabled&&this.breadcrumbs.add(e,t)}session(e){return cs("sha256").update(this.salt).update(e).digest("hex").slice(0,16)}async flush(e=3e3){await this.ready;try{return this.enabled&&!!await this.client?.flush(e)}catch{return!1}}async close(){let e=performance.now(),t;try{await Promise.race([this.ready,new Promise(i=>{t=setTimeout(i,500)})])}finally{clearTimeout(t),this.closed=!0,this.fatal&&process.off("uncaughtExceptionMonitor",this.fatal)}try{await this.client?.close(Math.max(1,500-(performance.now()-e)))}catch{}}};import{VERSION as bs}from"@earendil-works/pi-coding-agent";import{readFile as us}from"node:fs/promises";import{join as fs}from"node:path";async function $r(r){try{let e=JSON.parse(await us(fs(r,".vite/manifest.json"),"utf8"));if(!e||typeof e!="object")return{};let t=new Set;for(let n of Object.values(e))if(!(!n||typeof n!="object"))for(let s of[n.file,...Array.isArray(n.css)?n.css:[],...Array.isArray(n.assets)?n.assets:[]])typeof s=="string"&&/^assets\/[\w.-]+\.(?:js|css|woff2?|ttf|png|jpe?g|svg|webp)$/.test(s)&&t.add(s);let i={js:"text/javascript; charset=utf-8",css:"text/css; charset=utf-8",svg:"image/svg+xml",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",webp:"image/webp",woff:"font/woff",woff2:"font/woff2",ttf:"font/ttf"};return Object.fromEntries([...t].map(n=>[`/${n}`,{path:n,type:i[n.split(".").pop()]}]))}catch{return{}}}var vs={"/":{path:"index.html",type:"text/html; charset=utf-8"},"/assets/app.js":{path:"assets/app.js",type:"text/javascript; charset=utf-8"},"/assets/style.css":{path:"assets/style.css",type:"text/css; charset=utf-8"}};function y(r,e,t){typeof t=="object"&&t!==null&&"error"in t&&(t={errorCode:e>=500?"server_error":"validation_error",...t,requestId:r.getHeader("X-Request-ID"),operationId:r.getHeader("X-Operation-ID")}),r.writeHead(e,{"Content-Type":"application/json; charset=utf-8","Cache-Control":"no-store"}),r.end(JSON.stringify(t))}function ks(r,e){let t=r.headers.authorization;if(typeof t!="string"||!t.startsWith("Bearer "))return!1;let i=Buffer.from(t.slice(7),"utf8");return i.length===e.length&&ms(i,e)}async function Es(r){let e=0,t=[];for await(let i of r){let n=Buffer.isBuffer(i)?i:Buffer.from(i);if(e+=n.length,e>64*1024)throw new Error("\u8BF7\u6C42\u5185\u5BB9\u8FC7\u5927");t.push(n)}return JSON.parse(Buffer.concat(t).toString("utf8"))}async function Ps(r){let e=0,t=[];for await(let i of r){let n=Buffer.isBuffer(i)?i:Buffer.from(i);if(e+=n.length,e>Oe)throw new Error("\u6587\u4EF6\u8D85\u8FC7 20 MB");t.push(n)}return Buffer.concat(t)}async function Ss(r,e=0){await new Promise((i,n)=>{r.once("error",n),r.listen(e,"127.0.0.1",()=>{r.off("error",n),i()})});let t=r.address();if(t===null||typeof t=="string")throw new Error("\u65E0\u6CD5\u786E\u5B9A\u76D1\u542C\u7AEF\u53E3");return t.port}async function Lr(r,e,t=new Z,i={}){t.setState({piVersion:bs});let n=process.env.PI_WEBAPP_RESTART_TOKEN,s=n&&/^[A-Za-z0-9_-]{32}$/.test(n)?n:jr(24).toString("base64url"),a=Buffer.from(s),l=Pr(),p=new Set,h=new We(i.pluginSettingsFile??ut(ys(),"pi-web","plugins.json"));try{await h.load()}catch(b){throw await t.close(),b}let u=new Ne(gr(),h),c=async()=>await r.plugins?.()??{plugins:[],errors:[]},f=()=>({type:"plugin_state",disabled:u.plugins.filter(b=>!h.enabled(b.metadata.id)).map(b=>b.metadata.id)}),m=()=>{for(let b of p)b.write(`${JSON.stringify(f())}
|
|
29
29
|
`)},g=new Map,E=new Fe((b,w)=>t.capture(b,w)),x=b=>t.setState({nodeVersion:process.version,platform:process.platform,session:t.session(b.sessionId),idle:b.idle,model:b.model,thinkingLevel:b.thinkingLevel,messageCount:b.messages.length}),I="",J=!1,Q={...vs,...await $r(e)};for(let b of await hs(ut(e,"assets")).catch(()=>[])){let w=/^[\w.-]+\.(js|css|woff2?|ttf)$/.exec(b);if(!w)continue;let U={js:"text/javascript; charset=utf-8",css:"text/css; charset=utf-8",woff:"font/woff",woff2:"font/woff2",ttf:"font/ttf"};Q[`/assets/${b}`]={path:`assets/${b}`,type:U[w[1]]}}let ce=ws(async(b,w)=>{let U=jr(18).toString("base64"),le=j=>typeof j=="string"&&/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(j),we=le(b.headers["x-request-id"])?b.headers["x-request-id"]:pt(),B={requestId:we,operationId:le(b.headers["x-operation-id"])?b.headers["x-operation-id"]:void 0};w.setHeader("X-Request-ID",we),B.operationId&&w.setHeader("X-Operation-ID",B.operationId);let k="/",Pi=performance.now(),$=(j,d,v)=>{let C=!re(j)&&ir(k)?Y(j,"Agent or provider operation failed"):j,A=t.capture(C,{...B,route:k,method:b.method,status:d,stage:"http"});y(w,d,{error:v,errorId:A,errorReported:t.enabled,errorCode:re(j)?"validation_error":"unexpected_error"})};w.once("finish",()=>{k.startsWith("/api/telemetry/")||t.breadcrumb("request",{...B,route:k,method:b.method,status:w.statusCode,durationMs:Math.round(performance.now()-Pi)})});try{w.setHeader("X-Content-Type-Options","nosniff"),w.setHeader("Referrer-Policy","no-referrer"),w.setHeader("Content-Security-Policy",`default-src 'self'; script-src 'self'; style-src 'self' 'nonce-${U}'; style-src-attr 'unsafe-inline'; font-src 'self' data:; connect-src 'self'${t.config.browser.dsn?` ${new URL(t.config.browser.dsn).origin}`:""}; img-src 'self' data: blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'`);try{k=new URL(b.url??"/",I).pathname}catch{y(w,400,{error:"\u8BF7\u6C42\u5730\u5740\u65E0\u6548"});return}let j=Q[k];if(b.method==="GET"&&j){try{let d=await gs(ut(e,j.path));w.writeHead(200,{"Content-Type":j.type,"Cache-Control":"no-store"}),w.end(k==="/"?d.toString("utf8").replace("<head>",`<head><meta name="pi-style-nonce" content="${U}">`):d)}catch(d){$(d,500,"\u9875\u9762\u6587\u4EF6\u4E0D\u53EF\u7528")}return}if(b.method==="GET"&&k.startsWith("/plugins/")){try{let d=await r.pluginAsset?.(b.url??k);if(!d){y(w,404,{error:"\u63D2\u4EF6\u8D44\u6E90\u4E0D\u5B58\u5728"});return}w.writeHead(200,{"Content-Type":d.type,"Cache-Control":"no-store"}),w.end(d.bytes)}catch(d){$(d,404,"\u63D2\u4EF6\u8D44\u6E90\u4E0D\u53EF\u7528")}return}if(!k.startsWith("/api/")){y(w,404,{error:"\u672A\u627E\u5230"});return}if(!ks(b,a)){y(w,401,{error:"\u8BBF\u95EE\u4EE4\u724C\u65E0\u6548"});return}if(b.method==="GET"&&k==="/api/telemetry/config"){y(w,200,t.config.browser);return}if(b.method==="GET"&&k==="/api/update/version"){try{y(w,200,{current:await r.selfUpdate?.runningVersion()??null})}catch(d){$(d,503,d instanceof Error?d.message:"\u65E0\u6CD5\u8BFB\u53D6\u7248\u672C")}return}if(b.method==="GET"&&k==="/api/update"){try{y(w,200,await r.selfUpdate?.check()??{canRestart:!1,reason:"\u5F53\u524D\u6269\u5C55\u4E0D\u652F\u6301\u5728\u7EBF\u5347\u7EA7"})}catch(d){$(d,503,d instanceof Error?d.message:"\u68C0\u67E5\u66F4\u65B0\u5931\u8D25")}return}if(b.method==="GET"&&k==="/api/image"){let d=new URL(b.url??k,I),v=d.searchParams.get("sessionId")??"",_=d.searchParams.get("messageId")??"",C=Number(d.searchParams.get("index"));if(!v||v!==r.snapshot().sessionId||!_||!Number.isInteger(C)||C<0||C>100){y(w,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362\u6216\u56FE\u7247\u5730\u5740\u65E0\u6548"});return}let A=r.image?.(v,_,C);if(!A||!["image/png","image/jpeg","image/webp","image/gif"].includes(A.mimeType)){y(w,404,{error:"\u56FE\u7247\u4E0D\u53EF\u7528"});return}w.writeHead(200,{"Content-Type":A.mimeType,"Cache-Control":"private, no-store"}),w.end(A.data);return}if(b.method==="POST"&&k==="/api/attachment"){if(b.headers.origin!==void 0&&b.headers.origin!==I){y(w,403,{error:"\u6765\u6E90\u4E0D\u5339\u914D"});return}let d=new URL(b.url??k,I),v=d.searchParams.get("sessionId"),_=d.searchParams.get("name");try{if(!v||!_||_.length>1024||v!==r.snapshot().sessionId){y(w,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362\u6216\u6587\u4EF6\u540D\u65E0\u6548"});return}if(Number(b.headers["content-length"]??0)>Oe){y(w,413,{error:"\u6587\u4EF6\u8D85\u8FC7 20 MB"});return}let C=await Cr(v,_,b.headers["content-type"]??"application/octet-stream",await Ps(b));if(v!==r.snapshot().sessionId){y(w,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362"});return}g.set(C.id,C),y(w,201,{id:C.id,name:C.name,bytes:C.bytes})}catch(C){$(C,400,C instanceof Error?C.message:"\u4E0A\u4F20\u5931\u8D25")}return}if(b.method==="GET"&&k==="/api/session"){try{let d=r.snapshot();x(d),y(w,200,d)}catch(d){$(d,503,"\u5F53\u524D\u4F1A\u8BDD\u4E0D\u53EF\u7528")}return}if(b.method==="GET"&&k==="/api/plugin-management"){try{y(w,200,u.inventory(await c()))}catch(d){$(d,503,"\u63D2\u4EF6\u76EE\u5F55\u4E0D\u53EF\u7528")}return}if(b.method==="GET"&&k==="/api/plugins"){try{y(w,200,u.catalog(await c()))}catch(d){$(d,503,"\u63D2\u4EF6\u76EE\u5F55\u4E0D\u53EF\u7528")}return}if(b.method==="GET"&&k==="/api/providers"){try{y(w,200,await r.providers?.()??[])}catch(d){$(d,503,"\u6A21\u578B\u63D0\u4F9B\u65B9\u4E0D\u53EF\u7528")}return}if(b.method==="GET"&&k==="/api/provider/models"){let d=new URL(b.url??k,I).searchParams.get("providerId")??"";try{if(!d||!r.providerModels){y(w,400,{error:"\u63D0\u4F9B\u65B9\u65E0\u6548"});return}y(w,200,await r.providerModels(d))}catch(v){$(v,400,v instanceof Error?v.message:"\u6A21\u578B\u76EE\u5F55\u4E0D\u53EF\u7528")}return}if(b.method==="GET"&&k==="/api/provider/login/active"){y(w,200,{active:E.getActive()??null});return}if(b.method==="GET"&&k==="/api/provider/login"){let d=new URL(b.url??k,I).searchParams.get("id")??"",v=E.get(d);y(w,v?200:404,v??{error:"\u767B\u5F55\u6D41\u7A0B\u4E0D\u5B58\u5728"});return}if(b.method==="GET"&&(k==="/api/models"||k==="/api/config"||k==="/api/workspaces"||k==="/api/commands")){try{y(w,200,k==="/api/models"?await r.models():k==="/api/config"?await r.config():k==="/api/commands"?r.commands?.()??[]:await r.workspaces())}catch(d){$(d,503,"Pi \u914D\u7F6E\u4E0D\u53EF\u7528")}return}if(b.method==="GET"&&k==="/api/directory/capability"){y(w,200,{kind:l});return}if(b.method==="GET"&&k==="/api/events"){let d;try{d=r.snapshot(),x(d)}catch(v){$(v,503,"\u5F53\u524D\u4F1A\u8BDD\u4E0D\u53EF\u7528");return}w.writeHead(200,{"Content-Type":"application/x-ndjson; charset=utf-8","Cache-Control":"no-store",Connection:"keep-alive","X-Accel-Buffering":"no"}),p.add(w),w.write(`${JSON.stringify({type:"snapshot",session:d})}
|
|
30
30
|
`),w.on("close",()=>p.delete(w));return}if(b.method==="POST"&&[...u.routes,"/api/plugin-management","/api/plugin-interaction","/api/plugin-action","/api/update","/api/message","/api/attachment/remove","/api/abort","/api/resume","/api/compact","/api/new-session","/api/model","/api/thinking-level","/api/config","/api/provider/login","/api/provider/login/respond","/api/provider/login/cancel","/api/provider/custom","/api/provider/models","/api/provider/logout","/api/workspace/add","/api/workspace/select","/api/workspace/new-session","/api/workspace/remove","/api/session/select","/api/directory/list","/api/directory/create","/api/directory/pick"].includes(k)){if(b.headers.origin!==void 0&&b.headers.origin!==I){y(w,403,{error:"\u6765\u6E90\u4E0D\u5339\u914D"});return}if(!b.headers["content-type"]?.startsWith("application/json")){y(w,415,{error:"\u9700\u8981 JSON \u8BF7\u6C42"});return}try{let d;try{d=await Es(b)}catch(v){if(v instanceof SyntaxError||re(v)){y(w,400,{error:"\u8BF7\u6C42\u5185\u5BB9\u65E0\u6548"});return}throw v}if(typeof d!="object"||d===null||!("sessionId"in d)||typeof d.sessionId!="string"){y(w,400,{error:"\u7F3A\u5C11\u4F1A\u8BDD\u6807\u8BC6"});return}if(k==="/api/plugin-management"){let v=d;if(typeof v.id!="string"||v.id.length>200||typeof v.enabled!="boolean"||typeof v.expected!="string"){y(w,400,{error:"\u63D2\u4EF6\u5F00\u5173\u53C2\u6570\u65E0\u6548"});return}let _=()=>{if(r.snapshot().sessionId!==d.sessionId)throw new N("\u4F1A\u8BDD\u5DF2\u5207\u6362");if(!r.snapshot().idle||(r.busyGitWorkspaces?.()??[]).length)throw new N("\u8BF7\u7B49\u5F85\u6240\u6709\u804A\u5929\u7A7A\u95F2\u540E\u4FEE\u6539\u5168\u5C40\u63D2\u4EF6\u5F00\u5173")};try{await u.toggle(v.id,v.enabled,v.expected,_),y(w,200,u.inventory(await c()))}catch(C){if(C instanceof N)y(w,C.status,{error:C.message});else throw C}finally{m()}return}if(u.mutating&&k!=="/api/abort"){y(w,409,{error:"\u63D2\u4EF6\u914D\u7F6E\u6B63\u5728\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"});return}if(await u.dispatch({path:k,body:d,res:w,snapshot:()=>r.snapshot(),busyWorkspaces:()=>r.busyGitWorkspaces?.()??[],json:(v,_)=>y(w,v,_)}))return;if(k==="/api/plugin-interaction"){if(d.sessionId!==r.snapshot().sessionId){y(w,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362"});return}if(!("pluginId"in d)||typeof d.pluginId!="string"||d.pluginId.length>200||!("requestId"in d)||typeof d.requestId!="string"||d.requestId.length>200||!r.resolvePluginInteraction||!("value"in d)){y(w,400,{error:"\u63D2\u4EF6\u4EA4\u4E92\u54CD\u5E94\u65E0\u6548"});return}await u.coordinateStart(r.snapshot().cwd,async()=>r.resolvePluginInteraction(d.sessionId,d.pluginId,d.requestId,d.value)),y(w,200,{accepted:!0});return}if(k==="/api/plugin-action"){if(d.sessionId!==r.snapshot().sessionId){y(w,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362"});return}if(!("pluginId"in d)||typeof d.pluginId!="string"||d.pluginId.length>200||!("action"in d)||typeof d.action!="string"||d.action.length>100){y(w,400,{error:"\u63D2\u4EF6\u52A8\u4F5C\u65E0\u6548"});return}if(u.plugins.some(C=>C.metadata.id===d.pluginId)){let C=u.actionRoute(d.pluginId,d.action);if(!C){y(w,400,{error:"\u63D2\u4EF6\u52A8\u4F5C\u4E0D\u5B58\u5728"});return}let A="input"in d?d.input:{};if(!A||typeof A!="object"||Array.isArray(A)){y(w,400,{error:"\u63D2\u4EF6\u52A8\u4F5C\u53C2\u6570\u65E0\u6548"});return}await u.dispatch({path:C,body:{...A,sessionId:d.sessionId},res:w,snapshot:()=>r.snapshot(),busyWorkspaces:()=>r.busyGitWorkspaces?.()??[],json:(ye,R)=>y(w,ye,ye>=200&&ye<300?{value:R}:R)});return}if(!r.invokePluginAction){y(w,400,{error:"\u63D2\u4EF6\u52A8\u4F5C\u4E0D\u53EF\u7528"});return}let _=await u.coordinateStart(r.snapshot().cwd,async()=>r.invokePluginAction(d.sessionId,d.pluginId,d.action,"input"in d?d.input:void 0));y(w,200,{value:_});return}if(k==="/api/update"){if(d.sessionId!==r.snapshot().sessionId){y(w,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362"});return}if(!r.snapshot().idle){y(w,409,{error:"\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F\u540E\u518D\u5347\u7EA7"});return}if(!r.selfUpdate){y(w,409,{error:"\u5F53\u524D\u6269\u5C55\u4E0D\u652F\u6301\u5728\u7EBF\u5347\u7EA7"});return}let v=await r.selfUpdate.prepare();J=!0,y(w,202,{accepted:!0,version:v.version}),setTimeout(()=>{r.selfUpdate?.requestRestart(v).catch(_=>{J=!1,t.capture(_,{stage:"restart"}),console.error("pi-webapp restart:",_)})},500).unref();return}if(k==="/api/provider/custom"){if(d.sessionId!==r.snapshot().sessionId){y(w,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362"});return}if(!("provider"in d)||!dt(d.provider)||!r.addCustomProvider){y(w,400,{error:"\u81EA\u5B9A\u4E49\u63D0\u4F9B\u65B9\u65E0\u6548"});return}await r.addCustomProvider(d.sessionId,d.provider),y(w,201,{accepted:!0});return}if(k==="/api/provider/models"||k==="/api/provider/logout"){if(d.sessionId!==r.snapshot().sessionId){y(w,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362"});return}if(!("providerId"in d)||typeof d.providerId!="string"){y(w,400,{error:"\u63D0\u4F9B\u65B9\u65E0\u6548"});return}if(k==="/api/provider/models"){if(!("change"in d)||!Rr(d.change)||!r.updateProviderModel){y(w,400,{error:"\u6A21\u578B\u53C2\u6570\u65E0\u6548"});return}await r.updateProviderModel(d.sessionId,d.providerId,d.change)}else{if(!r.logoutProvider){y(w,400,{error:"\u65E0\u6CD5\u79FB\u9664\u8BA4\u8BC1"});return}await r.logoutProvider(d.sessionId,d.providerId)}y(w,200,{accepted:!0});return}if(k.startsWith("/api/provider/login")){if(d.sessionId!==r.snapshot().sessionId){y(w,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362"});return}if(k==="/api/provider/login"){if(!("providerId"in d)||typeof d.providerId!="string"||!("method"in d)||d.method!=="api_key"&&d.method!=="oauth"||!r.loginProvider||!r.providers){y(w,400,{error:"\u767B\u5F55\u65B9\u5F0F\u65E0\u6548"});return}if(!(await r.providers()).some(A=>A.id===d.providerId&&A.methods.includes(d.method))){y(w,400,{error:"\u63D0\u4F9B\u65B9\u4E0D\u652F\u6301\u6B64\u767B\u5F55\u65B9\u5F0F"});return}let _="initialSecret"in d?d.initialSecret:void 0;if(_!==void 0&&(d.method!=="api_key"||typeof _!="string"||!_.trim()||_.length>16384)){y(w,400,{error:"API Key \u65E0\u6548"});return}let C=E.start(d.providerId,d.method,r.loginProvider,_,B);y(w,202,{id:C.id});return}if(!("id"in d)||typeof d.id!="string"){y(w,400,{error:"\u767B\u5F55\u6D41\u7A0B\u65E0\u6548"});return}if(k==="/api/provider/login/cancel"){y(w,E.cancel(d.id)?202:404,{accepted:!0});return}if(!("value"in d)||typeof d.value!="string"||d.value.length>16384||!E.respond(d.id,d.value)){y(w,400,{error:"\u767B\u5F55\u8F93\u5165\u65E0\u6548"});return}y(w,202,{accepted:!0});return}if(k==="/api/attachment/remove"){if(!("id"in d)||typeof d.id!="string"||d.sessionId!==r.snapshot().sessionId){y(w,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362\u6216\u9644\u4EF6\u65E0\u6548"});return}g.get(d.id)?.sessionId===d.sessionId&&g.delete(d.id),y(w,202,{accepted:!0});return}if(k==="/api/directory/pick"){if(l!=="native"){y(w,409,{error:"\u672C\u5730\u76EE\u5F55\u9009\u62E9\u5668\u4E0D\u53EF\u7528"});return}let v=new AbortController;w.once("close",()=>v.abort()),y(w,200,{path:await Sr(v.signal)});return}if(k==="/api/directory/list"){if("path"in d&&(typeof d.path!="string"||d.path.length>4096)){y(w,400,{error:"\u76EE\u5F55\u8DEF\u5F84\u65E0\u6548"});return}y(w,200,await kr("path"in d?d.path:void 0));return}if(k==="/api/directory/create"){if(!("path"in d)||typeof d.path!="string"||d.path.length>4096||!("name"in d)||typeof d.name!="string"||d.name.length>255){y(w,400,{error:"\u6587\u4EF6\u5939\u540D\u79F0\u6216\u8DEF\u5F84\u65E0\u6548"});return}y(w,200,{path:await Er(d.path,d.name)});return}if(k==="/api/workspace/add"){if(!("path"in d)||typeof d.path!="string"||d.path.length>4096||!("create"in d)||typeof d.create!="boolean"){y(w,400,{error:"\u5DE5\u4F5C\u533A\u8DEF\u5F84\u65E0\u6548"});return}await r.addWorkspace(d.path,d.create),y(w,202,{accepted:!0});return}if(k==="/api/workspace/select"||k==="/api/workspace/remove"||k==="/api/workspace/new-session"){if(!("id"in d)||typeof d.id!="string"){y(w,400,{error:"\u5DE5\u4F5C\u533A\u65E0\u6548"});return}k==="/api/workspace/select"?await r.selectWorkspace(d.id):k==="/api/workspace/new-session"?await r.newSessionInWorkspace(d.id):await r.removeWorkspace(d.id),y(w,202,{accepted:!0});return}if(k==="/api/session/select"){if(!("workspaceId"in d)||typeof d.workspaceId!="string"||!("id"in d)||typeof d.id!="string"||!("path"in d)||typeof d.path!="string"){y(w,400,{error:"\u4F1A\u8BDD\u65E0\u6548"});return}await r.selectSession(d.workspaceId,d.id,d.path),y(w,202,{accepted:!0});return}if(k==="/api/thinking-level"){if(!("level"in d)||typeof d.level!="string"){y(w,400,{error:"\u63A8\u7406\u5F3A\u5EA6\u65E0\u6548"});return}await r.setThinkingLevel(d.sessionId,d.level)}else if(k==="/api/model"){if(!("provider"in d)||typeof d.provider!="string"||!("id"in d)||typeof d.id!="string"){y(w,400,{error:"\u6A21\u578B\u65E0\u6548"});return}await r.setModel(d.sessionId,d.provider,d.id)}else if(k==="/api/config"){let v=d;if(!["packages","extensions","skills"].includes(v.kind)||!["global","project"].includes(v.scope)||!["add","remove"].includes(v.action)||typeof v.value!="string"||!v.value.trim()||v.value.length>2048){y(w,400,{error:"\u914D\u7F6E\u9879\u65E0\u6548"});return}if(v.action==="add"&&v.kind!=="packages"&&Fr(v.value)){y(w,400,{error:"npm \u6216 Git \u6765\u6E90\u8BF7\u5728\u300C\u5305\u300D\u4E2D\u6DFB\u52A0\uFF1B\u6269\u5C55\u548C\u6280\u80FD\u53EA\u63A5\u53D7\u6587\u4EF6\u6216\u76EE\u5F55\u8DEF\u5F84"});return}y(w,200,await r.updateConfig(v));return}else if(k==="/api/message"){let v="attachments"in d?d.attachments:[];if(!Array.isArray(v)||v.length>Ir||v.some(R=>typeof R!="string")||new Set(v).size!==v.length){y(w,400,{error:"\u9644\u4EF6\u65E0\u6548\u6216\u8FC7\u591A"});return}let _=v.map(R=>g.get(R));if(_.some(R=>!R||R.sessionId!==d.sessionId)){y(w,400,{error:"\u9644\u4EF6\u4E0D\u53EF\u7528"});return}if(!("text"in d)||typeof d.text!="string"||!d.text.trim()&&v.length===0||d.text.length>32e3){y(w,400,{error:"\u6D88\u606F\u65E0\u6548\u6216\u8FC7\u957F"});return}let C=_,A=[d.text.trim(),...C.map(R=>`\u9644\u4EF6 ${JSON.stringify(R.name)}\uFF1A${R.path}`)].filter(Boolean).join(`
|
|
31
31
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# macOS 小范围测试安装 / Small-group test installation
|
|
2
|
+
|
|
3
|
+
适用于 **desktop build 7 起、采用新签名流程构建**的 macOS Apple Silicon / Intel 测试包。
|
|
4
|
+
旧包不会因源码更新而自动修复;请安装新构建,不要覆盖发布过的同名安装包。
|
|
5
|
+
|
|
6
|
+
## 这些测试包能保证什么?
|
|
7
|
+
|
|
8
|
+
- 默认以 `codesign` 的 ad-hoc 临时签名封装完整应用、Electron helpers 和内置运行时,
|
|
9
|
+
无需 Developer ID 证书。打包和发布准备都要求通过 deep/strict 签名完整性检查。
|
|
10
|
+
- **不是 Developer ID 发布者签名,没有 Apple 公证**。这不是面向公众“下载即打开”的发行版。
|
|
11
|
+
Gatekeeper 仍可能拦截;`spctl --assess` 拒绝此包是预期限制,不等于打包签名校验失败。
|
|
12
|
+
- SHA-256 只核对发行文件;临时签名只检查包是否符合当前签名,不验证开发者身份,
|
|
13
|
+
二者都不能证明软件安全。只邀请明确知情、愿意信任项目的测试用户。
|
|
14
|
+
- 当前主分支没有桌面自动更新器。测试包从 Release 下载、校验后手动安装。
|
|
15
|
+
|
|
16
|
+
## 测试用户安装步骤
|
|
17
|
+
|
|
18
|
+
1. 从项目官方 [GitHub Releases](https://github.com/chengzhiyi/pi-webapp/releases)
|
|
19
|
+
下载明确邀请测试的构建 DMG 和同一 Release 的 `SHA256SUMS`。
|
|
20
|
+
不要使用转发的未知安装包、源码 ZIP 或不匹配版本的校验文件。
|
|
21
|
+
2. 在终端运行 `shasum -a 256 <下载的DMG路径>`,与 `SHA256SUMS` 中该 DMG 的一行比较。
|
|
22
|
+
不一致就停止,不要关闭系统保护。`SHA256SUMS` 含四平台安装包;只下载一个 DMG 时,
|
|
23
|
+
逐行比较该文件即可,不要因未下载其他平台文件而误判其校验结果。
|
|
24
|
+
3. 退出旧桌面再替换应用。先完成所有聊天/终端任务;若旧桌面使用内置后台,
|
|
25
|
+
从其原入口停止该后台再替换。不要停止仍需使用的外部 Web 后台,也不要删除 `~/.pi`。
|
|
26
|
+
打开 DMG,把 **pi.app** 拖入“应用程序”。旧 **Pi Webapp.app** 不会自动被覆盖,
|
|
27
|
+
请更新 Dock 快捷方式以免打开旧版。
|
|
28
|
+
4. 检查安装后的应用完整性:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
codesign --verify --deep --strict --verbose=2 "/Applications/pi.app"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**失败就停止**,保存报错、构建号及 DMG 校验结果反馈给维护者。
|
|
35
|
+
不要自行重签,因为那会为可能被修改的内容建立新签名、掩盖原始问题。
|
|
36
|
+
5. 尝试打开应用。如果被拦截,可在 macOS 提供该选项时使用
|
|
37
|
+
**系统设置 → 隐私与安全性 → 仍要打开**。
|
|
38
|
+
如果仍因下载隔离无法启动,且你已核对来源、校验和、签名,并明确愿意信任该测试包,
|
|
39
|
+
可以手动运行以下命令,**只针对此应用**移除下载隔离后重试:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
codesign --verify --deep --strict --verbose=2 "/Applications/pi.app" &&
|
|
43
|
+
xattr -dr com.apple.quarantine "/Applications/pi.app"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
这会跳过该应用的下载隔离检查,**不代表 Apple 已认证其安全**。
|
|
47
|
+
不要对来源不明的软件使用,不要全局关闭 Gatekeeper/SIP;遇到权限错误先反馈,
|
|
48
|
+
不要盲目追加 `sudo`。项目不会自动执行此操作。
|
|
49
|
+
6. 在 **pi → 关于 pi** 确认版本、桌面构建号及应用位置,再配置模型并测试聊天和终端。
|
|
50
|
+
若启动失败,反馈系统版本、芯片、构建号及错误;不要上传 API 密钥、`auth.json` 或私钥。
|
|
51
|
+
|
|
52
|
+
再次安装/更新未公证测试包时,可能需要重复信任步骤。不接受这些限制的用户可继续使用
|
|
53
|
+
Web 版,或从可信源码构建运行,不必购买开发者会员。
|
|
54
|
+
|
|
55
|
+
## Release operator / 维护者验收
|
|
56
|
+
|
|
57
|
+
- 默认 `mac.identity` 为 `-`;证书自动发现保持关闭。
|
|
58
|
+
正式签名须由有权限的操作员显式配置 `CSC_NAME`,不得把 ad-hoc 宣称为发布者签名。
|
|
59
|
+
- electron-builder 在生成 DMG **之前**,自内向外签署嵌套代码并做 strict 验证。
|
|
60
|
+
默认 hardened runtime 及 Electron 所需 JIT/library-validation entitlements 由固定版本
|
|
61
|
+
electron-builder 提供;不得在签名后改写应用资源。
|
|
62
|
+
- `npm run desktop:package` 与发布准备均拒绝未封装资源或签名校验失败的应用。
|
|
63
|
+
`desktop-release.json` 的 `signing` 记录 `type` 和 `verified`;兼容字段 `unsigned: true`
|
|
64
|
+
表示**没有发布者身份**,即使 ad-hoc 完整性校验通过也保持为 true。
|
|
65
|
+
- 打包命令还会只读挂载 DMG,自动校验其中实际应用的 deep/strict 签名。
|
|
66
|
+
当前主分支保持 DMG 分发,不为此引入 ZIP 更新包或自动更新器。
|
|
67
|
+
执行 packaged smoke(含 PTY/浏览器共享后台)。
|
|
68
|
+
这些检查不能替代“浏览器下载 → Finder 安装 → 用户明确同意”的隔离场景人工验收。
|
|
69
|
+
- 每次交付递增 `desktop/release.json` 的构建号、重新生成校验和。
|
|
70
|
+
不覆盖已有公开 Release,不以移除隔离或本机可启动宣称通过 Gatekeeper。
|
|
71
|
+
|
|
72
|
+
English summary: These are complete **ad-hoc signed**, **unnotarized** opt-in test builds,
|
|
73
|
+
not publisher-authenticated releases. Verify the official DMG checksum and the installed app's
|
|
74
|
+
`codesign --verify --deep --strict` result before trusting it. Stop on verification failure;
|
|
75
|
+
do not re-sign a downloaded app to hide a failure. Any quarantine exception must be a conscious,
|
|
76
|
+
manual, app-scoped tester choice. Never disable system security globally.
|
package/docs/desktop.md
CHANGED
|
@@ -88,13 +88,24 @@ These are two separate release responsibilities:
|
|
|
88
88
|
- **Safe updates** verify an update's publisher/integrity and replace a compatible
|
|
89
89
|
application/runtime without destroying user data.
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
Starting with desktop build 7, default macOS packages use **complete ad-hoc
|
|
92
|
+
signatures, without publisher identity or notarization**, for opt-in small-group
|
|
93
|
+
testing. No certificate is needed: `mac.identity` defaults to `-`; an authorized
|
|
94
|
+
operator may explicitly select `CSC_NAME` for Developer ID signing. Certificate
|
|
95
|
+
discovery stays disabled. Windows/Linux's unsigned release paths are unchanged.
|
|
96
|
+
|
|
97
|
+
Electron-builder signs nested code before creating the DMG. Packaging verifies
|
|
98
|
+
the app and the read-only mounted DMG; release preparation requires deep/strict
|
|
99
|
+
verification and a complete resource seal on both macOS architectures. macOS
|
|
100
|
+
receipts record `signing: { type: "ad-hoc" | "developer-id", verified: true }`;
|
|
101
|
+
`unsigned: true` means **no publisher identity**, including valid ad-hoc signatures.
|
|
102
|
+
|
|
103
|
+
**Ad-hoc is not Developer ID.** Gatekeeper can still block the download. Follow
|
|
104
|
+
the [tester installation and trust instructions](desktop-testing.md): verify the
|
|
105
|
+
checksum and signature, stop on verification failure, and only use a conscious,
|
|
106
|
+
manual, app-scoped trust exception for a known test build. Never disable OS
|
|
107
|
+
security globally. Public distribution needs authorized signing/notarization;
|
|
108
|
+
local packaging success is not proof of downloaded first-launch acceptance.
|
|
98
109
|
|
|
99
110
|
For now, install a newer desktop package to upgrade; if replacing a running
|
|
100
111
|
desktop-owned runtime, first finish tasks and stop that owned service. A desktop
|
|
@@ -158,8 +169,9 @@ The candidate build-6 pipeline adds four **native** targets:
|
|
|
158
169
|
|
|
159
170
|
Each uses locked dependencies, standalone Node/Pi, full unit tests and actual
|
|
160
171
|
installer-payload desktop/browser/PTY smoke. The smoke reads the installed Pi
|
|
161
|
-
package and requires the same pinned version.
|
|
162
|
-
disabled
|
|
172
|
+
package and requires the same pinned version. Certificate discovery and Sentry
|
|
173
|
+
are disabled; macOS ad-hoc signing and strict integrity checks are required from
|
|
174
|
+
build 7. ARM Windows/Linux and other targets are not implicitly supported.
|
|
163
175
|
|
|
164
176
|
A read-only aggregation job requires **all four** verified receipts from the
|
|
165
177
|
same source/version/build/Pi pair. It collects four installers, SHA256SUMS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-webapp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A browser interface for Pi with live chat, workspaces, models, and attachments",
|
|
6
6
|
"repository": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"pi-webapp": "./bin/pi-webapp.mjs"
|
|
16
16
|
},
|
|
17
17
|
"pi": {
|
|
18
|
-
"image": "https://cdn.jsdelivr.net/npm/pi-webapp@0.1.
|
|
18
|
+
"image": "https://cdn.jsdelivr.net/npm/pi-webapp@0.1.24/assets/cover-webapp.png",
|
|
19
19
|
"extensions": [
|
|
20
20
|
"./dist/extension.js"
|
|
21
21
|
]
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"docs/sentry.md",
|
|
33
33
|
"docs/sentry-triage.md",
|
|
34
34
|
"docs/desktop.md",
|
|
35
|
+
"docs/desktop-testing.md",
|
|
35
36
|
"scripts/prepare-terminal.mjs"
|
|
36
37
|
],
|
|
37
38
|
"scripts": {
|
|
@@ -122,13 +122,13 @@
|
|
|
122
122
|
"isDynamicEntry": true
|
|
123
123
|
},
|
|
124
124
|
"../../node_modules/@shikijs/langs/dist/graphql.mjs": {
|
|
125
|
-
"file": "assets/graphql-
|
|
125
|
+
"file": "assets/graphql-BjSnAUKM.js",
|
|
126
126
|
"name": "graphql",
|
|
127
127
|
"src": "../../node_modules/@shikijs/langs/dist/graphql.mjs",
|
|
128
128
|
"isDynamicEntry": true,
|
|
129
129
|
"imports": [
|
|
130
130
|
"_javascript-D_ijBMSX.js",
|
|
131
|
-
"_app-
|
|
131
|
+
"_app-uZ4wCYr8.js",
|
|
132
132
|
"index.html"
|
|
133
133
|
]
|
|
134
134
|
},
|
|
@@ -161,12 +161,12 @@
|
|
|
161
161
|
]
|
|
162
162
|
},
|
|
163
163
|
"../../node_modules/@shikijs/langs/dist/http.mjs": {
|
|
164
|
-
"file": "assets/http-
|
|
164
|
+
"file": "assets/http-B7mqrZ_R.js",
|
|
165
165
|
"name": "http",
|
|
166
166
|
"src": "../../node_modules/@shikijs/langs/dist/http.mjs",
|
|
167
167
|
"isDynamicEntry": true,
|
|
168
168
|
"imports": [
|
|
169
|
-
"_app-
|
|
169
|
+
"_app-uZ4wCYr8.js",
|
|
170
170
|
"../../node_modules/@shikijs/langs/dist/xml.mjs",
|
|
171
171
|
"../../node_modules/@shikijs/langs/dist/graphql.mjs",
|
|
172
172
|
"index.html",
|
|
@@ -275,7 +275,7 @@
|
|
|
275
275
|
]
|
|
276
276
|
},
|
|
277
277
|
"../../node_modules/@shikijs/langs/dist/php.mjs": {
|
|
278
|
-
"file": "assets/php-
|
|
278
|
+
"file": "assets/php-Cvwc1Xta.js",
|
|
279
279
|
"name": "php",
|
|
280
280
|
"src": "../../node_modules/@shikijs/langs/dist/php.mjs",
|
|
281
281
|
"isDynamicEntry": true,
|
|
@@ -284,7 +284,7 @@
|
|
|
284
284
|
"../../node_modules/@shikijs/langs/dist/xml.mjs",
|
|
285
285
|
"../../node_modules/@shikijs/langs/dist/sql.mjs",
|
|
286
286
|
"_javascript-D_ijBMSX.js",
|
|
287
|
-
"_app-
|
|
287
|
+
"_app-uZ4wCYr8.js",
|
|
288
288
|
"../../node_modules/@shikijs/langs/dist/css.mjs",
|
|
289
289
|
"../../node_modules/@shikijs/langs/dist/java.mjs",
|
|
290
290
|
"index.html"
|
|
@@ -315,7 +315,7 @@
|
|
|
315
315
|
"isDynamicEntry": true
|
|
316
316
|
},
|
|
317
317
|
"../../node_modules/@shikijs/langs/dist/rst.mjs": {
|
|
318
|
-
"file": "assets/rst-
|
|
318
|
+
"file": "assets/rst-DutdH0pA.js",
|
|
319
319
|
"name": "rst",
|
|
320
320
|
"src": "../../node_modules/@shikijs/langs/dist/rst.mjs",
|
|
321
321
|
"isDynamicEntry": true,
|
|
@@ -324,7 +324,7 @@
|
|
|
324
324
|
"../../node_modules/@shikijs/langs/dist/cpp.mjs",
|
|
325
325
|
"../../node_modules/@shikijs/langs/dist/python.mjs",
|
|
326
326
|
"_javascript-D_ijBMSX.js",
|
|
327
|
-
"_app-
|
|
327
|
+
"_app-uZ4wCYr8.js",
|
|
328
328
|
"../../node_modules/@shikijs/langs/dist/yaml.mjs",
|
|
329
329
|
"../../node_modules/@shikijs/langs/dist/cmake.mjs",
|
|
330
330
|
"../../node_modules/@shikijs/langs/dist/ruby.mjs",
|
|
@@ -340,7 +340,7 @@
|
|
|
340
340
|
]
|
|
341
341
|
},
|
|
342
342
|
"../../node_modules/@shikijs/langs/dist/ruby.mjs": {
|
|
343
|
-
"file": "assets/ruby-
|
|
343
|
+
"file": "assets/ruby-Bpvk-Mco.js",
|
|
344
344
|
"name": "ruby",
|
|
345
345
|
"src": "../../node_modules/@shikijs/langs/dist/ruby.mjs",
|
|
346
346
|
"isDynamicEntry": true,
|
|
@@ -353,7 +353,7 @@
|
|
|
353
353
|
"../../node_modules/@shikijs/langs/dist/graphql.mjs",
|
|
354
354
|
"../../node_modules/@shikijs/langs/dist/cpp.mjs",
|
|
355
355
|
"../../node_modules/@shikijs/langs/dist/c.mjs",
|
|
356
|
-
"_app-
|
|
356
|
+
"_app-uZ4wCYr8.js",
|
|
357
357
|
"../../node_modules/@shikijs/langs/dist/lua.mjs",
|
|
358
358
|
"../../node_modules/@shikijs/langs/dist/yaml.mjs",
|
|
359
359
|
"../../node_modules/@shikijs/langs/dist/java.mjs",
|
|
@@ -388,13 +388,13 @@
|
|
|
388
388
|
"isDynamicEntry": true
|
|
389
389
|
},
|
|
390
390
|
"../../node_modules/@shikijs/langs/dist/svelte.mjs": {
|
|
391
|
-
"file": "assets/svelte-
|
|
391
|
+
"file": "assets/svelte-CPcJi_Ee.js",
|
|
392
392
|
"name": "svelte",
|
|
393
393
|
"src": "../../node_modules/@shikijs/langs/dist/svelte.mjs",
|
|
394
394
|
"isDynamicEntry": true,
|
|
395
395
|
"imports": [
|
|
396
396
|
"_javascript-D_ijBMSX.js",
|
|
397
|
-
"_app-
|
|
397
|
+
"_app-uZ4wCYr8.js",
|
|
398
398
|
"../../node_modules/@shikijs/langs/dist/css.mjs",
|
|
399
399
|
"index.html"
|
|
400
400
|
]
|
|
@@ -430,14 +430,14 @@
|
|
|
430
430
|
"isDynamicEntry": true
|
|
431
431
|
},
|
|
432
432
|
"../../node_modules/@shikijs/langs/dist/vue.mjs": {
|
|
433
|
-
"file": "assets/vue-
|
|
433
|
+
"file": "assets/vue-s7ynYl37.js",
|
|
434
434
|
"name": "vue",
|
|
435
435
|
"src": "../../node_modules/@shikijs/langs/dist/vue.mjs",
|
|
436
436
|
"isDynamicEntry": true,
|
|
437
437
|
"imports": [
|
|
438
438
|
"../../node_modules/@shikijs/langs/dist/css.mjs",
|
|
439
439
|
"_javascript-D_ijBMSX.js",
|
|
440
|
-
"_app-
|
|
440
|
+
"_app-uZ4wCYr8.js",
|
|
441
441
|
"../../node_modules/@shikijs/langs/dist/html.mjs",
|
|
442
442
|
"_html-derivative-C2Epw9Yg.js",
|
|
443
443
|
"index.html"
|
|
@@ -704,37 +704,37 @@
|
|
|
704
704
|
]
|
|
705
705
|
},
|
|
706
706
|
"../../src/plugins/builtin/files/client/plugin.tsx": {
|
|
707
|
-
"file": "assets/plugin-
|
|
707
|
+
"file": "assets/plugin-DK_rN4N5.js",
|
|
708
708
|
"name": "plugin",
|
|
709
709
|
"src": "../../src/plugins/builtin/files/client/plugin.tsx",
|
|
710
710
|
"isDynamicEntry": true,
|
|
711
711
|
"imports": [
|
|
712
|
-
"_app-
|
|
712
|
+
"_app-uZ4wCYr8.js",
|
|
713
713
|
"index.html"
|
|
714
714
|
]
|
|
715
715
|
},
|
|
716
716
|
"../../src/plugins/builtin/git/client/plugin.tsx": {
|
|
717
|
-
"file": "assets/plugin-
|
|
717
|
+
"file": "assets/plugin-BB8YECov.js",
|
|
718
718
|
"name": "plugin",
|
|
719
719
|
"src": "../../src/plugins/builtin/git/client/plugin.tsx",
|
|
720
720
|
"isDynamicEntry": true,
|
|
721
721
|
"imports": [
|
|
722
|
-
"_app-
|
|
722
|
+
"_app-uZ4wCYr8.js",
|
|
723
723
|
"index.html"
|
|
724
724
|
]
|
|
725
725
|
},
|
|
726
726
|
"../../src/plugins/builtin/terminal/client/plugin.tsx": {
|
|
727
|
-
"file": "assets/plugin-
|
|
727
|
+
"file": "assets/plugin-Bs3cPDKk.js",
|
|
728
728
|
"name": "plugin",
|
|
729
729
|
"src": "../../src/plugins/builtin/terminal/client/plugin.tsx",
|
|
730
730
|
"isDynamicEntry": true,
|
|
731
731
|
"imports": [
|
|
732
|
-
"_app-
|
|
732
|
+
"_app-uZ4wCYr8.js",
|
|
733
733
|
"index.html"
|
|
734
734
|
]
|
|
735
735
|
},
|
|
736
|
-
"_app-
|
|
737
|
-
"file": "assets/app-
|
|
736
|
+
"_app-uZ4wCYr8.js": {
|
|
737
|
+
"file": "assets/app-uZ4wCYr8.js",
|
|
738
738
|
"name": "app",
|
|
739
739
|
"isDynamicEntry": true,
|
|
740
740
|
"imports": [
|
|
@@ -882,7 +882,7 @@
|
|
|
882
882
|
"isEntry": true,
|
|
883
883
|
"dynamicImports": [
|
|
884
884
|
"../../src/host/client/telemetry-client.ts",
|
|
885
|
-
"_app-
|
|
885
|
+
"_app-uZ4wCYr8.js"
|
|
886
886
|
]
|
|
887
887
|
},
|
|
888
888
|
"style.css": {
|