cicy-desktop 2.1.277 → 2.1.279
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/.github/workflows/linux-app-release.yml +10 -1
- package/.github/workflows/mac-app-release.yml +12 -8
- package/.github/workflows/npm-publish.yml +21 -0
- package/.github/workflows/windows-exe-release.yml +6 -4
- package/docs/superpowers/plans/2026-08-23-electron-inject-installer.md +79 -0
- package/docs/superpowers/plans/2026-08-24-telegram-matrix.md +85 -0
- package/docs/superpowers/specs/2026-08-23-electron-inject-installer-design.md +45 -0
- package/package.json +6 -5
- package/src/backends/docker-audit.js +17 -0
- package/src/backends/homepage-preload.js +2 -0
- package/src/backends/sidecar-ipc.js +50 -10
- package/src/chrome/chrome-install-navigation.js +23 -0
- package/src/main.js +42 -9
- package/src/sidecar/cicy-runtime-health.js +8 -0
- package/src/sidecar/container-scripts/cicy-code-update.sh +35 -5
- package/src/sidecar/docker-protect.js +57 -0
- package/src/sidecar/gateway-key-health.js +14 -0
- package/src/sidecar/host-mihomo.js +66 -3
- package/src/sidecar/wsl-docker.js +360 -29
- package/src/tabbrowser/cicy-code-tab-restore.js +10 -0
- package/src/tabbrowser/newtab-protocol.js +4 -1
- package/src/tabbrowser/panel-cell-visibility.js +5 -0
- package/src/tabbrowser/panel-cells.js +109 -9
- package/src/tabbrowser/panel-menu.js +14 -0
- package/src/tabbrowser/panel-page-router.js +11 -0
- package/src/tabbrowser/panel-preload.js +3 -0
- package/src/tabbrowser/panel-presets.js +9 -0
- package/src/tabbrowser/tab-shell-preload.js +3 -2
- package/src/tabbrowser/tab-shell.html +4 -1
- package/src/tabbrowser/telegram-matrix-profiles.js +42 -0
- package/src/tabbrowser/telegram-matrix.html +304 -0
- package/src/tabbrowser/telegram-web-preferences.js +22 -0
- package/src/tools/electron-inject-tools.js +113 -0
- package/src/tools/index.js +1 -0
- package/src/tools/tab-browser-tools.js +53 -15
- package/src/tray.js +14 -1
- package/src/utils/rpc-guard.js +1 -0
- package/test/chrome-install-navigation.test.js +41 -0
- package/test/cicy-code-tab-restore.test.js +21 -0
- package/test/cicy-code-update.test.js +69 -0
- package/test/cicy-runtime-health.test.js +12 -0
- package/test/docker-audit.test.js +20 -0
- package/test/docker-protect-wiring.test.js +37 -0
- package/test/docker-protect.test.js +43 -0
- package/test/electron-inject-tools.test.js +74 -0
- package/test/gateway-key-health.test.js +40 -0
- package/test/mihomo-host-sync.test.js +55 -0
- package/test/panel-launcher.test.js +36 -0
- package/test/start-bat.test.js +21 -0
- package/test/telegram-matrix.test.js +137 -0
- package/test/wsl-update-script-permissions.test.js +12 -0
|
@@ -68,10 +68,19 @@ jobs:
|
|
|
68
68
|
shell: bash
|
|
69
69
|
env:
|
|
70
70
|
GH_TOKEN: ${{ github.token }}
|
|
71
|
+
PUBLISH_OWNER: ${{ github.repository_owner }}
|
|
72
|
+
PUBLISH_REPO: ${{ github.event.repository.name }}
|
|
71
73
|
CICY_OBFUSCATE: "1" # 发版混淆 JS(prebuild 主进程 + Vite 渲染层),仅 release CI
|
|
72
74
|
run: |
|
|
73
75
|
set -euo pipefail
|
|
74
|
-
|
|
76
|
+
if [ "$GITHUB_REF_TYPE" = "tag" ]; then
|
|
77
|
+
for attempt in $(seq 1 60); do
|
|
78
|
+
gh release view "$GITHUB_REF_NAME" --repo "${{ github.repository }}" >/dev/null 2>&1 && break
|
|
79
|
+
[ "$attempt" -eq 60 ] && { echo "::error::release was not created by the Windows workflow within 5 minutes"; exit 1; }
|
|
80
|
+
sleep 5
|
|
81
|
+
done
|
|
82
|
+
fi
|
|
83
|
+
npm run build:linux -- --x64 --config.linux.target=AppImage --config.publish.owner="$PUBLISH_OWNER" --config.publish.repo="$PUBLISH_REPO" --publish always
|
|
75
84
|
|
|
76
85
|
# AppImage + 版本指针传到 OSS —— app-updater 现已全走 OSS(不碰 GitHub),linux 也要在
|
|
77
86
|
# OSS 上有 versioned AppImage(CiCy-Desktop-<ver>.AppImage)和自己的指针 linux-latest-version.txt。
|
|
@@ -133,17 +133,21 @@ jobs:
|
|
|
133
133
|
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
|
134
134
|
echo "tag=v${version}" >> "$GITHUB_OUTPUT"
|
|
135
135
|
|
|
136
|
-
- name:
|
|
136
|
+
- name: Wait for Windows workflow to create the GitHub release
|
|
137
137
|
shell: bash
|
|
138
138
|
env:
|
|
139
139
|
GH_TOKEN: ${{ github.token }}
|
|
140
140
|
run: |
|
|
141
|
-
|
|
142
|
-
--repo "
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
if [ "$GITHUB_REF_TYPE" != "tag" ]; then
|
|
142
|
+
gh release create "${{ steps.meta.outputs.tag }}" --repo "${{ github.repository }}" --title "CiCy Desktop v${{ steps.meta.outputs.version }}" --generate-notes --draft 2>/dev/null || true
|
|
143
|
+
exit 0
|
|
144
|
+
fi
|
|
145
|
+
for attempt in $(seq 1 60); do
|
|
146
|
+
gh release view "${{ steps.meta.outputs.tag }}" --repo "${{ github.repository }}" >/dev/null 2>&1 && exit 0
|
|
147
|
+
sleep 5
|
|
148
|
+
done
|
|
149
|
+
echo "::error::release was not created by the Windows workflow within 5 minutes"
|
|
150
|
+
exit 1
|
|
147
151
|
|
|
148
152
|
- name: Build and publish PKG installers
|
|
149
153
|
shell: bash
|
|
@@ -183,7 +187,7 @@ jobs:
|
|
|
183
187
|
pkgbuild --root "$ROOT" --component-plist "$PLIST" --scripts "$SCR" \
|
|
184
188
|
--identifier com.cicy.desktop --version "$VER" --install-location / "$PKG"
|
|
185
189
|
gh release upload "${{ steps.meta.outputs.tag }}" "$PKG" \
|
|
186
|
-
--repo "
|
|
190
|
+
--repo "${{ github.repository }}" --clobber
|
|
187
191
|
echo "uploaded $PKG ($(du -h "$PKG" | cut -f1))"
|
|
188
192
|
done
|
|
189
193
|
|
|
@@ -15,7 +15,28 @@ concurrency:
|
|
|
15
15
|
cancel-in-progress: true
|
|
16
16
|
|
|
17
17
|
jobs:
|
|
18
|
+
# 单元测试(node:test,零依赖)。发布前必过 —— 这些用例守的是 WSL/Docker sidecar 那条
|
|
19
|
+
# 只在 Windows 真机上才跑得到的路径,本地很难复现,只能靠断言兜住。
|
|
20
|
+
# Node 22:`node --test "<glob>"` 的 glob 展开是 node ≥21 的能力(20 只认目录参数)。
|
|
21
|
+
test:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- name: Checkout
|
|
25
|
+
uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Setup Node.js
|
|
28
|
+
uses: actions/setup-node@v4
|
|
29
|
+
with:
|
|
30
|
+
node-version: '22'
|
|
31
|
+
|
|
32
|
+
- name: Install dependencies
|
|
33
|
+
run: npm ci --ignore-scripts
|
|
34
|
+
|
|
35
|
+
- name: Run tests
|
|
36
|
+
run: npm test
|
|
37
|
+
|
|
18
38
|
publish:
|
|
39
|
+
needs: test
|
|
19
40
|
runs-on: ubuntu-latest
|
|
20
41
|
steps:
|
|
21
42
|
- name: Checkout
|
|
@@ -75,7 +75,7 @@ jobs:
|
|
|
75
75
|
GH_TOKEN: ${{ github.token }}
|
|
76
76
|
run: |
|
|
77
77
|
gh release create "${{ steps.meta.outputs.tag }}" \
|
|
78
|
-
--repo "
|
|
78
|
+
--repo "${{ github.repository }}" \
|
|
79
79
|
--title "CiCy Desktop v${{ steps.meta.outputs.version }}" \
|
|
80
80
|
--generate-notes \
|
|
81
81
|
--draft \
|
|
@@ -86,14 +86,16 @@ jobs:
|
|
|
86
86
|
shell: powershell
|
|
87
87
|
env:
|
|
88
88
|
GH_TOKEN: ${{ github.token }}
|
|
89
|
+
PUBLISH_OWNER: ${{ github.repository_owner }}
|
|
90
|
+
PUBLISH_REPO: ${{ github.event.repository.name }}
|
|
89
91
|
CICY_OBFUSCATE: "1" # 发版混淆 JS(prebuild 主进程 + Vite 渲染层),仅 release CI
|
|
90
92
|
run: |
|
|
91
93
|
if (Test-Path dist) { Remove-Item -Recurse -Force dist }
|
|
92
94
|
if (Test-Path "build\logo.ico") {
|
|
93
|
-
npm run build:win -- --config.win.icon=build/logo.ico --publish always
|
|
95
|
+
npm run build:win -- --config.win.icon=build/logo.ico --config.publish.owner="$env:PUBLISH_OWNER" --config.publish.repo="$env:PUBLISH_REPO" --publish always
|
|
94
96
|
} else {
|
|
95
97
|
Write-Host "::warning::no icon, using default"
|
|
96
|
-
npm run build:win -- --publish always
|
|
98
|
+
npm run build:win -- --config.publish.owner="$env:PUBLISH_OWNER" --config.publish.repo="$env:PUBLISH_REPO" --publish always
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
# 推 exe 到 OSS。三件事(不再是"永远一个版本"):
|
|
@@ -179,6 +181,6 @@ jobs:
|
|
|
179
181
|
GH_TOKEN: ${{ github.token }}
|
|
180
182
|
run: |
|
|
181
183
|
gh release edit "${{ steps.meta.outputs.tag }}" \
|
|
182
|
-
--repo "
|
|
184
|
+
--repo "${{ github.repository }}" \
|
|
183
185
|
--draft=false \
|
|
184
186
|
--latest
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Electron Inject Installer Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add a restricted Desktop RPC and public `agent-electron` CLI that install JavaScript into `~/data/electron/extension/inject/`.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Desktop owns path validation and atomic host writes through one `electron_inject` tool. The public Skill reads a caller-provided source file and forwards its UTF-8 content without printing it.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Electron/Node.js CommonJS Desktop tools, Zod schemas, Node test runner, ESM public Skill CLI.
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-08-23-electron-inject-installer-design.md`
|
|
12
|
+
|
|
13
|
+
## Global Constraints
|
|
14
|
+
|
|
15
|
+
- Fixed Desktop root: `path.join(os.homedir(), "data", "electron", "extension", "inject")`.
|
|
16
|
+
- Accept only filenames matching `^[a-z0-9][a-z0-9._-]*\.js$`.
|
|
17
|
+
- Reject payloads larger than 1 MiB and never return JavaScript content.
|
|
18
|
+
- Use atomic sibling-temp write, mode `0600`, and SHA-256 verification.
|
|
19
|
+
- Do not use the existing arbitrary-path `file_write` tool.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### Task 1: Desktop `electron_inject` RPC
|
|
24
|
+
|
|
25
|
+
**Files:**
|
|
26
|
+
- Create: `src/tools/electron-inject-tools.js`
|
|
27
|
+
- Modify: `src/tools/index.js`
|
|
28
|
+
- Modify: `src/utils/rpc-guard.js`
|
|
29
|
+
- Test: `test/electron-inject-tools.test.js`
|
|
30
|
+
|
|
31
|
+
**Interfaces:**
|
|
32
|
+
- Consumes: `{operation:"install"|"status"|"uninstall", name:string, content?:string}`.
|
|
33
|
+
- Produces: `{operation,name,path,exists,size,sha256}` encoded in the standard tool text envelope.
|
|
34
|
+
|
|
35
|
+
- [ ] Write tests registering the tool with `CICY_ELECTRON_INJECT_DIR` set to a temporary directory. Assert install, status, replacement, mode `0600`, SHA-256, uninstall, traversal rejection, extension rejection, symlink rejection, and the 1 MiB limit.
|
|
36
|
+
- [ ] Run `node --test test/electron-inject-tools.test.js` and confirm RED because the tool does not exist.
|
|
37
|
+
- [ ] Implement `electron-inject-tools.js` with exported validation helpers and `registerTools`; resolve and realpath-check the fixed root, use `openSync(temp,"wx",0o600)`, `writeFileSync`, `renameSync`, and clean the temp on errors.
|
|
38
|
+
- [ ] Register it in `src/tools/index.js` and add `electron_inject` to the RPC guard's host-write list.
|
|
39
|
+
- [ ] Run the focused test, full `node --test test/*.test.js`, and `git diff --check`.
|
|
40
|
+
- [ ] Commit, fetch/rebase, and push `cicy-ai/cicy-desktop` `main`.
|
|
41
|
+
|
|
42
|
+
### Task 2: Public `agent-electron inject` CLI
|
|
43
|
+
|
|
44
|
+
**Files:**
|
|
45
|
+
- Modify: `skills/agent-electron/bin/agent-electron`
|
|
46
|
+
- Modify: `skills/agent-electron/manifest.json`
|
|
47
|
+
- Modify: `skills/agent-electron/SKILL.md`
|
|
48
|
+
- Modify: `skills/agent-electron/README.md`
|
|
49
|
+
- Modify: `skills/agent-electron/references/help.md`
|
|
50
|
+
- Modify: `skills/agent-electron/references/help.en.md`
|
|
51
|
+
- Modify: `skills/agent-electron/references/help.cn.md`
|
|
52
|
+
- Modify: `skills/agent-electron/references/tools.md`
|
|
53
|
+
- Modify: `skills/agent-electron/references/tools.en.md`
|
|
54
|
+
- Modify: `skills/agent-electron/references/tools.cn.md`
|
|
55
|
+
- Modify: `skills/agent-electron/test/test.js`
|
|
56
|
+
|
|
57
|
+
**Interfaces:**
|
|
58
|
+
- Consumes: `inject install <name> --source <file>`, `inject status <name>`, and `inject uninstall <name>`.
|
|
59
|
+
- Produces: the unwrapped `electron_inject` Desktop result with no source content in stdout/stderr.
|
|
60
|
+
|
|
61
|
+
- [ ] Add CLI tests using a temporary source containing a sentinel secret and a fake local cicy-code HTTP server. Assert the RPC payload selects `electron_inject`, forwards content, and output never contains the sentinel.
|
|
62
|
+
- [ ] Run `node tools/test-skill.js skills/agent-electron` and confirm RED.
|
|
63
|
+
- [ ] Add the `inject` dispatcher and parser. Require `--source` only for install, read UTF-8 with a 1 MiB pre-check, then call `rpc("electron_inject", payload)`.
|
|
64
|
+
- [ ] Update help and metadata; bump `agent-electron` from `1.0.17` to `1.1.0` because this is backward-compatible functionality.
|
|
65
|
+
- [ ] Run the Skill test, validator, sensitive-path scan, and `git diff --check`.
|
|
66
|
+
- [ ] Commit, fetch/rebase, push `cicy-ai/cicy-skills` `main`, tag `agent-electron-v1.1.0`, and monitor the public release and registry.
|
|
67
|
+
|
|
68
|
+
### Task 3: Integration verification
|
|
69
|
+
|
|
70
|
+
**Files:**
|
|
71
|
+
- Verify both repositories at remote `main`.
|
|
72
|
+
|
|
73
|
+
**Interfaces:**
|
|
74
|
+
- Consumes: a running new Desktop plus `agent-electron@1.1.0`.
|
|
75
|
+
- Produces: local tests, releases, and registry evidence; live host installation only when a Desktop containing the new RPC is connected.
|
|
76
|
+
|
|
77
|
+
- [ ] Re-run both focused/full suites from clean `main` checkouts.
|
|
78
|
+
- [ ] Confirm Desktop commit is on remote `main`, Skill workflow succeeded, Release ZIP exists, and registry reports `agent-electron@1.1.0`.
|
|
79
|
+
- [ ] If no updated Desktop is connected, report live installation as pending instead of claiming it ran.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Telegram Matrix Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Turn the `Telegram 矩阵` preset into a profile manager with a left-side profile table and a phone-sized Telegram Web preview on the right.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Keep the system profile panel as renderer chrome and place one main-process `BrowserView` above its preview slot. The selected item supplies `persist:sandbox-N`; profile creation and proxy changes go through `profile-store`, and the BrowserView is rebuilt/reloaded when its profile or proxy changes.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Electron BrowserView, IPC/contextBridge, CommonJS, HTML/CSS/JavaScript, Node test runner.
|
|
10
|
+
|
|
11
|
+
**Spec:** Approved in chat on 2026-08-24: unlimited Electron profiles, one item per profile, custom per-profile proxy, phone-sized Telegram preview.
|
|
12
|
+
|
|
13
|
+
## Global Constraints
|
|
14
|
+
|
|
15
|
+
- Ordinary `面板` behavior remains unchanged.
|
|
16
|
+
- Telegram profile data uses `persist:sandbox-<accountIdx>` and existing `~/data/electron/account-<N>.json` persistence.
|
|
17
|
+
- New profiles default to `http://127.0.0.1:20001`; each profile can use a custom `http://`, `https://`, or `socks5://` proxy, or direct mode.
|
|
18
|
+
- The Telegram target is `https://web.telegram.org/k/`.
|
|
19
|
+
- Profile 0 and reserved profile 9 must not appear in the matrix.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### Task 1: Matrix preset routing
|
|
24
|
+
|
|
25
|
+
**Files:**
|
|
26
|
+
- Modify: `src/tabbrowser/panel-presets.js`
|
|
27
|
+
- Modify: `src/tools/tab-browser-tools.js`
|
|
28
|
+
- Modify: `src/tabbrowser/newtab-protocol.js`
|
|
29
|
+
- Create: `src/tabbrowser/telegram-matrix.html`
|
|
30
|
+
- Test: `test/telegram-matrix.test.js`
|
|
31
|
+
|
|
32
|
+
**Interfaces:**
|
|
33
|
+
- Produces: `resolvePanelPreset("telegram-matrix")` with a `query` field; `cicyui://panel/<id>?preset=telegram-matrix` serves `telegram-matrix.html`.
|
|
34
|
+
|
|
35
|
+
- [ ] Write a failing test asserting the Telegram preset URL query and dedicated HTML routing contract.
|
|
36
|
+
- [ ] Run `node --test test/telegram-matrix.test.js` and confirm the missing query/page behavior fails.
|
|
37
|
+
- [ ] Add the preset query, append it in `openPanelTab`, and select `telegram-matrix.html` in the protocol handler.
|
|
38
|
+
- [ ] Run the test and confirm it passes.
|
|
39
|
+
|
|
40
|
+
### Task 2: Profile creation and custom proxy service
|
|
41
|
+
|
|
42
|
+
**Files:**
|
|
43
|
+
- Create: `src/tabbrowser/telegram-matrix-profiles.js`
|
|
44
|
+
- Modify: `src/tabbrowser/panel-cells.js`
|
|
45
|
+
- Modify: `src/tabbrowser/panel-preload.js`
|
|
46
|
+
- Test: `test/telegram-matrix.test.js`
|
|
47
|
+
|
|
48
|
+
**Interfaces:**
|
|
49
|
+
- Produces: `listTelegramProfiles()`, `addTelegramProfile(defaultProxy)`, `setTelegramProfileProxy(accountIdx, proxy)`.
|
|
50
|
+
- IPC: `panelcells:profiles`, `panelcells:add-profile`, `panelcells:set-profile-proxy`.
|
|
51
|
+
|
|
52
|
+
- [ ] Write failing tests for next-ID allocation, default proxy persistence, accepted proxy schemes, direct mode, and invalid proxy rejection.
|
|
53
|
+
- [ ] Run the focused test and confirm each new behavior fails for the expected reason.
|
|
54
|
+
- [ ] Implement the pure validation/allocation service and IPC handlers backed by `profile-store`.
|
|
55
|
+
- [ ] Apply proxy immediately with `session.setProxy`, rebuild the selected profile view when needed, and expose methods through `panelAPI`.
|
|
56
|
+
- [ ] Run focused and profile-store tests and confirm they pass.
|
|
57
|
+
|
|
58
|
+
### Task 3: Table and phone preview UI
|
|
59
|
+
|
|
60
|
+
**Files:**
|
|
61
|
+
- Modify: `src/tabbrowser/telegram-matrix.html`
|
|
62
|
+
- Test: `test/telegram-matrix.test.js`
|
|
63
|
+
|
|
64
|
+
**Interfaces:**
|
|
65
|
+
- Consumes: `panelAPI.profiles()`, `panelAPI.addProfile()`, `panelAPI.setProfileProxy()`, and `panelAPI.sync([{ id: "telegram-preview", url, profile, rect }])`.
|
|
66
|
+
|
|
67
|
+
- [ ] Write a failing DOM-contract test for the profile table, add button, proxy editor, empty state, and phone preview slot.
|
|
68
|
+
- [ ] Run the test and confirm the absent controls fail.
|
|
69
|
+
- [ ] Implement rendering, first-profile selection, add-profile flow, per-row proxy editing, status feedback, and a single responsive 390-by-844 preview slot.
|
|
70
|
+
- [ ] Sync only the selected profile to the native BrowserView and preserve the list selection locally.
|
|
71
|
+
- [ ] Run the focused test and confirm it passes.
|
|
72
|
+
|
|
73
|
+
### Task 4: Integration verification
|
|
74
|
+
|
|
75
|
+
**Files:**
|
|
76
|
+
- Test: `test/panel-launcher.test.js`
|
|
77
|
+
- Test: `test/telegram-matrix.test.js`
|
|
78
|
+
|
|
79
|
+
**Interfaces:**
|
|
80
|
+
- Verifies the complete menu-to-matrix flow without changing ordinary panels.
|
|
81
|
+
|
|
82
|
+
- [ ] Run `node --test test/panel-launcher.test.js test/telegram-matrix.test.js`.
|
|
83
|
+
- [ ] Run syntax checks for every changed JavaScript file and `git diff --check`.
|
|
84
|
+
- [ ] Restart `C:\projects\start-cicy-desktop-win.bat` and wait for the source Electron client to reconnect.
|
|
85
|
+
- [ ] Open `Telegram 矩阵`, inspect its DOM via `agent-electron`, and confirm profile selection produces one Telegram BrowserView in the selected partition.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Electron Inject Installer Design
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Allow a Skill running outside the desktop host, including inside WSL Docker, to install and manage an Electron Desktop Client injection script at:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
~/data/electron/extension/inject/telegram.org.js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The interface must not expose arbitrary host filesystem writes.
|
|
12
|
+
|
|
13
|
+
## Desktop Tool
|
|
14
|
+
|
|
15
|
+
Add an `electron_inject` RPC tool with three operations:
|
|
16
|
+
|
|
17
|
+
- `install`: accept a safe filename and UTF-8 JavaScript content, write it atomically, set file mode `0600`, and return path, byte size, and SHA-256.
|
|
18
|
+
- `status`: return existence, path, byte size, and SHA-256 without returning file content.
|
|
19
|
+
- `uninstall`: remove the named file and report whether it existed.
|
|
20
|
+
|
|
21
|
+
Resolve the root from `os.homedir()` plus `data/electron/extension/inject`. Accept only a basename matching `^[a-z0-9][a-z0-9._-]*\.js$`; reject separators, traversal, symlinks, non-JavaScript names, and targets outside the fixed root. Write to a temporary sibling with exclusive creation, rename atomically, and reject payloads larger than 1 MiB.
|
|
22
|
+
|
|
23
|
+
Register the tool in the normal Desktop tool catalog and classify it as a guarded host-write operation. Audit logging remains handled by the existing RPC guard.
|
|
24
|
+
|
|
25
|
+
## Public Skill Interface
|
|
26
|
+
|
|
27
|
+
Extend the public `agent-electron` Skill with:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
agent-electron inject install telegram.org.js --source <file>
|
|
31
|
+
agent-electron inject status telegram.org.js
|
|
32
|
+
agent-electron inject uninstall telegram.org.js
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The CLI reads `--source` locally, sends content through the Desktop RPC, and never prints content. `install` requires an explicit source file; no WSL Hook or generated placeholder is substituted for the Mac injection script. JSON output preserves Desktop path, size, and SHA-256.
|
|
36
|
+
|
|
37
|
+
## Testing
|
|
38
|
+
|
|
39
|
+
- Desktop unit tests use a temporary home/root override and prove fixed-root resolution, safe-name validation, traversal rejection, size limit, atomic replacement, hash reporting, mode `0600`, status, and uninstall.
|
|
40
|
+
- `agent-electron` tests use a fake RPC transport and prove argv parsing, source reading, content forwarding without output leakage, stable JSON, and error handling.
|
|
41
|
+
- Existing Desktop and Skill suites must remain green.
|
|
42
|
+
|
|
43
|
+
## Release
|
|
44
|
+
|
|
45
|
+
Commit and push Desktop independently after its tests pass. Then bump and publish `agent-electron` with a tag-driven public Skill release. A Desktop application release is a separate explicit step because packaging and distributing a new Desktop binary affects all clients.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cicy-desktop",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.279",
|
|
4
4
|
"description": "CiCy - AI-powered operating system browser",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node ./bin/cicy-desktop",
|
|
11
|
+
"test": "node --test \"test/**/*.test.js\"",
|
|
11
12
|
"start:master": "node src/master/master-main.js",
|
|
12
13
|
"format": "prettier --write \"src/**/*.js\"",
|
|
13
14
|
"format:check": "prettier --check \"src/**/*.js\"",
|
|
@@ -143,10 +144,10 @@
|
|
|
143
144
|
"//optionalDependencies": "(2026-06 回调): mac/linux 改回 native cicy-code(:8008,colima 在 16G mac 上压垮内存)→ 重新内置 cicy-code-<plat> / cicy-mihomo-<plat>,localbin.fromBundle 零网络 seed(npm 仅作更新通道,带 npmmirror→npmjs 回退)。这些由 scripts/sync-runtime-deps.cjs 在 tag-push 时同步到最新版。npm 的 os/cpu 字段保证每个平台只装自己那份。Windows 仍走 docker(WSL :8009),它那份 cicy-code-windows 用不到但 bundle 着无害。",
|
|
144
145
|
"optionalDependencies": {
|
|
145
146
|
"electron": "41.0.3",
|
|
146
|
-
"cicy-code-darwin-x64": "2.3.
|
|
147
|
-
"cicy-code-darwin-arm64": "2.3.
|
|
148
|
-
"cicy-code-linux-x64": "2.3.
|
|
149
|
-
"cicy-code-linux-arm64": "2.3.
|
|
147
|
+
"cicy-code-darwin-x64": "2.3.567",
|
|
148
|
+
"cicy-code-darwin-arm64": "2.3.567",
|
|
149
|
+
"cicy-code-linux-x64": "2.3.567",
|
|
150
|
+
"cicy-code-linux-arm64": "2.3.567",
|
|
150
151
|
"cicy-code-windows-x64": "2.3.193",
|
|
151
152
|
"cicy-mihomo-darwin-x64": "1.10.4",
|
|
152
153
|
"cicy-mihomo-darwin-arm64": "1.10.4",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function createDockerAuditRecord(channel, event, args = {}) {
|
|
2
|
+
return {
|
|
3
|
+
ts: new Date().toISOString(),
|
|
4
|
+
channel,
|
|
5
|
+
webContentsId: event?.sender?.id ?? null,
|
|
6
|
+
senderUrl: event?.senderFrame?.url || event?.sender?.getURL?.() || "",
|
|
7
|
+
args,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function auditDestructiveIpc(log, channel, event, args = {}) {
|
|
12
|
+
const record = createDockerAuditRecord(channel, event, args);
|
|
13
|
+
log.warn(`[docker-audit] ${JSON.stringify(record)}`);
|
|
14
|
+
return record;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = { createDockerAuditRecord, auditDestructiveIpc };
|
|
@@ -178,6 +178,8 @@ contextBridge.exposeInMainWorld("cicy", {
|
|
|
178
178
|
appUpdate: () => logInvoke("docker:app-update"),
|
|
179
179
|
appStop: () => logInvoke("docker:app-stop"),
|
|
180
180
|
appUpgrade: () => logInvoke("docker:app-upgrade"),
|
|
181
|
+
getProtect: () => logInvoke("docker:get-protect"), // 容器保护开关(默认开)
|
|
182
|
+
setProtect: (on) => logInvoke("docker:set-protect", !!on),
|
|
181
183
|
getPorts: () => logInvoke("docker:get-ports"), // 读已发布的额外端口
|
|
182
184
|
setPorts: (ports) => logInvoke("docker:set-ports", { ports }), // 存端口 + recreate 带 -p
|
|
183
185
|
// Open :8008 with the live container token (refuses if it can't read it).
|
|
@@ -20,6 +20,9 @@ const fs = require("fs");
|
|
|
20
20
|
const os = require("os");
|
|
21
21
|
const path = require("path");
|
|
22
22
|
const log = require("electron-log");
|
|
23
|
+
const { auditDestructiveIpc } = require("./docker-audit");
|
|
24
|
+
const { nextGatewayKeyHealth } = require("../sidecar/gateway-key-health");
|
|
25
|
+
const dockerProtect = require("../sidecar/docker-protect"); // 容器保护开关(默认开)
|
|
23
26
|
// Background-computed docker status lives here; the homepage READS this (never
|
|
24
27
|
// probes WSL live → never blocks the UI / strands it on 「重试检测」).
|
|
25
28
|
const DOCKER_STATUS_FILE = path.join(os.homedir(), "cicy-ai", "db", "docker-status.json");
|
|
@@ -137,6 +140,8 @@ function register({ sidecarLogPath } = {}) {
|
|
|
137
140
|
// docker:app-status handler just returns the cache — instant, never blocks.
|
|
138
141
|
let _dockerStatusCache = null;
|
|
139
142
|
let _dockerDaemonBusy = false;
|
|
143
|
+
let _gatewayKeyMissingChecks = 0;
|
|
144
|
+
let _autostartEnsured = false;
|
|
140
145
|
async function refreshDockerStatus() {
|
|
141
146
|
try {
|
|
142
147
|
const s = await appDocker.status(APP_PORT); // { wsl, distro, engineUp, running, unknown }
|
|
@@ -183,14 +188,31 @@ function register({ sidecarLogPath } = {}) {
|
|
|
183
188
|
// 数据保留)。mac/win 同一套(hasGatewayKey 两个 docker 模块都实现了)。
|
|
184
189
|
try {
|
|
185
190
|
const opts = await appOpts();
|
|
186
|
-
if (opts.env && opts.env.CICY_AI_GATEWAY_LLM_API_KEY && appDocker.hasGatewayKey
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
await appDocker.
|
|
190
|
-
|
|
191
|
-
|
|
191
|
+
if (opts.env && opts.env.CICY_AI_GATEWAY_LLM_API_KEY && appDocker.hasGatewayKey && dockerProtect.isProtected()) {
|
|
192
|
+
_gatewayKeyMissingChecks = 0; // 容器保护:绝不因缺 key 自动重建;用户可从卡片菜单手动「重建」
|
|
193
|
+
} else if (opts.env && opts.env.CICY_AI_GATEWAY_LLM_API_KEY && appDocker.hasGatewayKey) {
|
|
194
|
+
const keyState = await appDocker.hasGatewayKey(APP_CONTAINER);
|
|
195
|
+
const health = nextGatewayKeyHealth(_gatewayKeyMissingChecks, keyState);
|
|
196
|
+
_gatewayKeyMissingChecks = health.missingChecks;
|
|
197
|
+
if (!health.shouldRecreate) {
|
|
198
|
+
if (keyState === false) log.warn(`[docker-daemon] 网关 key 明确缺失 (${_gatewayKeyMissingChecks}/3),暂不重建`);
|
|
199
|
+
} else {
|
|
200
|
+
log.warn("[docker-daemon] 网关 key 连续 3 次明确缺失 → 带 key 重建(数据保留)");
|
|
201
|
+
auditDestructiveIpc(log, "docker:self-heal-missing-gateway-key", null, {
|
|
202
|
+
container: APP_CONTAINER,
|
|
203
|
+
confirmedMissingChecks: 3,
|
|
204
|
+
});
|
|
205
|
+
await appDocker.recreate(opts);
|
|
206
|
+
try { await registerAppTeam(); } catch {}
|
|
207
|
+
await refreshDockerStatus();
|
|
208
|
+
}
|
|
192
209
|
}
|
|
193
210
|
} catch (e) { log.warn(`[docker-daemon] key self-heal failed: ${e.message}`); }
|
|
211
|
+
// 开机自启链:每次会话确认一次登录任务 + wsl.conf [boot](幂等;老装机也能补上)。
|
|
212
|
+
if (!_autostartEnsured && appDocker.ensureAutostart) {
|
|
213
|
+
_autostartEnsured = true;
|
|
214
|
+
try { await appDocker.ensureAutostart(); } catch (err) { log.warn(`[docker-daemon] ensureAutostart failed: ${err.message}`); }
|
|
215
|
+
}
|
|
194
216
|
// Chrome 代理:开关开着但宿主 mihomo 没跑 → 拉起(从容器同步配置,幂等)。
|
|
195
217
|
try { await maybeStartChromeProxy(); } catch (e) { log.warn(`[chrome-proxy] auto-start failed: ${e.message}`); }
|
|
196
218
|
}
|
|
@@ -214,12 +236,16 @@ function register({ sidecarLogPath } = {}) {
|
|
|
214
236
|
// 已在跑也同步一次配置(容器侧节点可能被云端更新),变了才重启。幂等。二进制不在会自动下。
|
|
215
237
|
async function maybeStartChromeProxy() {
|
|
216
238
|
if (!APP_DOCKER_SUPPORTED) return;
|
|
217
|
-
const yaml = await
|
|
239
|
+
const [yaml, selections] = await Promise.all([
|
|
240
|
+
appDocker.readMihomoConfig(APP_CONTAINER),
|
|
241
|
+
appDocker.readMihomoSelections(APP_CONTAINER),
|
|
242
|
+
]);
|
|
218
243
|
if (hostMihomo.binPresent() && hostMihomo.running()) {
|
|
219
244
|
if (hostMihomo.writeConfig(yaml)) hostMihomo.start({ force: true });
|
|
245
|
+
await hostMihomo.syncSelections(selections);
|
|
220
246
|
return;
|
|
221
247
|
}
|
|
222
|
-
await hostMihomo.enable({ containerYaml: yaml });
|
|
248
|
+
await hostMihomo.enable({ containerYaml: yaml, selections });
|
|
223
249
|
}
|
|
224
250
|
|
|
225
251
|
ipcMain.handle("sidecar:status", async () => {
|
|
@@ -464,10 +490,12 @@ function register({ sidecarLogPath } = {}) {
|
|
|
464
490
|
// ⋯ menu → 重建 Docker:删容器 + 用新 env(docker team 网关 key)重新 docker run(保留
|
|
465
491
|
// volume 数据)。换 key 的唯一途径。渲染端已 confirm。
|
|
466
492
|
ipcMain.handle("docker:app-recreate", async (e) => {
|
|
493
|
+
auditDestructiveIpc(log, "docker:app-recreate", e);
|
|
467
494
|
try {
|
|
468
495
|
await ensureDockerTeam();
|
|
469
496
|
await appDocker.recreate({
|
|
470
497
|
...(await appOpts()),
|
|
498
|
+
force: true, // 用户在卡片菜单明确点击并 confirm → 放行
|
|
471
499
|
onProgress: (ev) => { try { e.sender.send("docker:app-progress", ev); } catch {} },
|
|
472
500
|
});
|
|
473
501
|
await registerAppTeam();
|
|
@@ -509,11 +537,13 @@ function register({ sidecarLogPath } = {}) {
|
|
|
509
537
|
|
|
510
538
|
// ⋯ menu → 升级: re-pull the latest R2 image, re-create the :8008 container.
|
|
511
539
|
ipcMain.handle("docker:app-upgrade", async (e) => {
|
|
540
|
+
auditDestructiveIpc(log, "docker:app-upgrade", e);
|
|
512
541
|
if (!APP_DOCKER_SUPPORTED) return { ok: false, error: "Docker cicy-code 仅支持 Windows" };
|
|
513
542
|
try {
|
|
514
543
|
await ensureDockerTeam();
|
|
515
544
|
const result = await appDocker.upgrade({
|
|
516
545
|
...(await appOpts()),
|
|
546
|
+
force: true, // 用户显式升级
|
|
517
547
|
onProgress: (ev) => { try { e.sender.send("docker:app-progress", ev); } catch {} },
|
|
518
548
|
});
|
|
519
549
|
if (result && result.ok) await registerAppTeam();
|
|
@@ -524,18 +554,27 @@ function register({ sidecarLogPath } = {}) {
|
|
|
524
554
|
});
|
|
525
555
|
|
|
526
556
|
// 端口设置:读当前已发布的额外端口(:8008 固定,不在列)。
|
|
557
|
+
// 容器保护开关:开启时任何自动流程都不得 rm/recreate 容器或 wsl --shutdown。
|
|
558
|
+
ipcMain.handle("docker:get-protect", () => ({ ok: true, enabled: dockerProtect.isProtected() }));
|
|
559
|
+
ipcMain.handle("docker:set-protect", (e, on) => {
|
|
560
|
+
auditDestructiveIpc(log, "docker:set-protect", e, { enabled: !!on });
|
|
561
|
+
try { return { ok: true, enabled: dockerProtect.setProtected(!!on) }; } catch (err) { return { ok: false, error: err.message }; }
|
|
562
|
+
});
|
|
563
|
+
|
|
527
564
|
ipcMain.handle("docker:get-ports", () => {
|
|
528
565
|
return { ok: true, mainPort: APP_PORT, ports: readExtraPorts() };
|
|
529
566
|
});
|
|
530
567
|
// 保存端口 → 持久化 → recreate 容器带上新的 -p(:8008 + 各额外端口)。volume 保留。
|
|
531
568
|
ipcMain.handle("docker:set-ports", async (e, input) => {
|
|
569
|
+
auditDestructiveIpc(log, "docker:set-ports", e, { ports: input?.ports });
|
|
532
570
|
if (!APP_DOCKER_SUPPORTED) return { ok: false, error: "Docker cicy-code 仅支持 Windows" };
|
|
533
571
|
const ports = sanitizePorts(input && input.ports);
|
|
534
572
|
writeExtraPorts(ports);
|
|
535
573
|
try {
|
|
536
574
|
await ensureDockerTeam();
|
|
537
575
|
const result = await appDocker.recreate({
|
|
538
|
-
...(await appOpts()),
|
|
576
|
+
...(await appOpts()),
|
|
577
|
+
force: true, // 用户显式改端口 // 已含 extraPorts: readExtraPorts()(刚写入的)
|
|
539
578
|
onProgress: (ev) => { try { e.sender.send("docker:app-progress", ev); } catch {} },
|
|
540
579
|
});
|
|
541
580
|
await registerAppTeam();
|
|
@@ -655,11 +694,12 @@ function register({ sidecarLogPath } = {}) {
|
|
|
655
694
|
try { return { ok: true, dood: sidecar.isDood() }; } catch (e) { return { ok: false, dood: false, error: e.message }; }
|
|
656
695
|
});
|
|
657
696
|
ipcMain.handle("sidecar:set-dood", async (e, on) => {
|
|
697
|
+
auditDestructiveIpc(log, "sidecar:set-dood", e, { enabled: !!on });
|
|
658
698
|
const emit = (ev) => { try { e.sender.send("sidecar:op-progress", { op: "restart", ...ev }); } catch {} };
|
|
659
699
|
try {
|
|
660
700
|
sidecar.setDood(!!on);
|
|
661
701
|
emit({ phase: "swap", status: "running", message: `容器 Docker 访问已${on ? "开启" : "关闭"},正在重建容器…` });
|
|
662
|
-
try { await appDocker.recreate({ ...(await appOpts()), onProgress: emit }); }
|
|
702
|
+
try { await appDocker.recreate({ ...(await appOpts()), force: true, onProgress: emit }); }
|
|
663
703
|
catch (err) { try { await appDocker.dockerRestart?.({ onProgress: emit }); } catch {} }
|
|
664
704
|
let up = false;
|
|
665
705
|
for (let i = 0; i < 240; i++) { if ((await appDocker.probeHealth?.(PORT)) || (await sidecar.probeExisting(PORT))) { up = true; break; } await new Promise((r) => setTimeout(r, 500)); }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright 2026 CiCy AI
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
const CHROME_INSTALL_URL = "https://www.google.com/chrome/";
|
|
5
|
+
|
|
6
|
+
function isChromeBinaryMissingError(error) {
|
|
7
|
+
return /Chrome\/Chromium binary not found/i.test(String((error && error.message) || error || ""));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function handleChromeLaunchError(error, { openInstallPage, showError }) {
|
|
11
|
+
if (isChromeBinaryMissingError(error)) {
|
|
12
|
+
await openInstallPage(CHROME_INSTALL_URL);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
await showError(error);
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
CHROME_INSTALL_URL,
|
|
21
|
+
handleChromeLaunchError,
|
|
22
|
+
isChromeBinaryMissingError,
|
|
23
|
+
};
|