Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9527402003 | ||
|
|
50481deaa1 | ||
|
|
baa6defc0c | ||
|
|
775763eee0 | ||
|
|
82107ad86a | ||
|
|
b6c7de5f91 | ||
|
|
663aa85e1a | ||
|
|
3c31f0b499 | ||
|
|
169941ceda | ||
|
|
fdf7d83b20 | ||
|
|
f571301764 | ||
|
|
53b9ecbcae | ||
|
|
d859cbfdeb | ||
|
|
855b941aeb | ||
|
|
a1a1d24022 | ||
|
|
9d8477b728 | ||
|
|
0fd2f7e3c8 | ||
|
|
7aad78752b | ||
|
|
6ced760c1a | ||
|
|
09c415bd41 | ||
|
|
e97263c79e | ||
|
|
02a6f5c4e7 | ||
|
|
dc635d185e | ||
|
|
4d1ce38dad | ||
|
|
c3fa262832 | ||
|
|
0ce9606c89 | ||
|
|
ee4eb737e0 | ||
|
|
1f420cd042 | ||
|
|
43be465286 | ||
|
|
5f1ff24333 | ||
|
|
d773f5e961 | ||
|
|
90ea33ef5c | ||
|
|
983cdd4e05 | ||
|
|
bf6c4a91a2 | ||
|
|
1e10935d22 | ||
|
|
5dc65457d9 | ||
|
|
3d4e9577b5 | ||
|
|
80579ee421 | ||
|
|
8280158edc | ||
|
|
5368e0ecf4 | ||
|
|
20317c21b5 | ||
|
|
b1129cedec | ||
|
|
2e5c348dce | ||
|
|
f4d8fe7f06 | ||
|
|
dd29f309f1 | ||
|
|
b02ce31886 | ||
|
|
7cb74cce78 | ||
|
|
5e77118508 | ||
|
|
227507de8f | ||
|
|
c8f327fc4e | ||
|
|
016f987026 | ||
|
|
2c5300d031 | ||
|
|
a3b41bf2b1 | ||
|
|
0a02a17cb6 | ||
|
|
d2ceff8db5 | ||
|
|
8201c9c27e | ||
|
|
cc451f698d | ||
|
|
9cc5dc1244 | ||
|
|
f7f501a8e3 | ||
|
|
e4f7b8d993 | ||
|
|
b6d24ada87 | ||
|
|
c23dca65e8 | ||
|
|
bf64181c85 | ||
|
|
3be55348a2 | ||
|
|
89fc100d4f | ||
|
|
6edfc0046b | ||
|
|
ab85e72f01 | ||
|
|
80edd15438 | ||
|
|
7fb23d9b6b | ||
|
|
925d410247 | ||
|
|
2d8e58e995 | ||
|
|
6293ef105d | ||
|
|
d30f794184 | ||
|
|
608a9a5fec | ||
|
|
a662b16f5e | ||
|
|
911eed5e67 | ||
|
|
ed47e2c2a5 | ||
|
|
9c88ca20f3 | ||
|
|
d1b92deff3 | ||
|
|
9a9170441e | ||
|
|
3c9229ac7a | ||
|
|
86347af0b1 | ||
|
|
4baf4ec142 | ||
|
|
1603871f7c | ||
|
|
393a07ccdf | ||
|
|
50e8fde64c | ||
|
|
13ff182858 | ||
|
|
594222a2b5 | ||
|
|
1c6264dd87 | ||
|
|
120076a13d | ||
|
|
d8f0ef1301 | ||
|
|
10f3b4f2c1 | ||
|
|
58e6808f1f | ||
|
|
8650907b75 | ||
|
|
c11f204b17 | ||
|
|
be23c1346b | ||
|
|
47ba54f47d | ||
|
|
76e781a0a8 | ||
|
|
0cdf04261a | ||
|
|
66f77b83c2 | ||
|
|
83a3ea261e | ||
|
|
013e72f69e | ||
|
|
61590395ef | ||
|
|
4e5239256d | ||
|
|
65a21390de | ||
|
|
706726bad6 | ||
|
|
5cdffee9ee | ||
|
|
d9e1e42288 | ||
|
|
d47f967781 | ||
|
|
f35198df01 | ||
|
|
8e607366ca | ||
|
|
ee1bf8a187 | ||
|
|
b8b6007b51 | ||
|
|
12594961fe | ||
|
|
5a6ba6c14f | ||
|
|
0f8bf405db | ||
|
|
5ecc19dfe0 | ||
|
|
4a2986ca5d | ||
|
|
2713a81bd9 | ||
|
|
11facf0392 | ||
|
|
0f0b6ac21d | ||
|
|
eb53c827c6 | ||
|
|
8b15399577 | ||
|
|
67dedc2ed2 | ||
|
|
ceb275bdb8 | ||
|
|
44cd944601 | ||
|
|
40c108da1a | ||
|
|
5177b47aeb | ||
|
|
4edf06b685 | ||
|
|
f81f342cde | ||
|
|
1094433a7d | ||
|
|
55efca3ee1 | ||
|
|
5116613615 | ||
|
|
0759d5dcff | ||
|
|
325b702934 | ||
|
|
4c7193e1ea | ||
|
|
e6ac0734de | ||
|
|
e8d93b5bb6 | ||
|
|
cd27163660 | ||
|
|
107dc974e3 | ||
|
|
d23af99708 | ||
|
|
ff746bdc1a | ||
|
|
d4bc10c0f3 | ||
|
|
db5e43d7d5 | ||
|
|
f68584b4e2 | ||
|
|
7adae97baa | ||
|
|
0ed1be1773 | ||
|
|
d5f6dc6990 | ||
|
|
c71c0adf9e | ||
|
|
344008aaec | ||
|
|
cebd513da8 | ||
|
|
2ca19ebf50 | ||
|
|
7756231b3b | ||
|
|
b16fc900bb | ||
|
|
bf8ff7f78e | ||
|
|
bb8c04af6c | ||
|
|
073aa45a30 | ||
|
|
1cf74d3d2c | ||
|
|
163e245b00 | ||
|
|
bf8ece0590 | ||
|
|
83a48a4079 | ||
|
|
cdd573778c | ||
|
|
0265cfb762 | ||
|
|
fb4b9bdd46 | ||
|
|
984f16f178 | ||
|
|
7d487f9326 | ||
|
|
785b1ee468 | ||
|
|
be84534e77 | ||
|
|
f7541203e4 | ||
|
|
24bde0c2e3 | ||
|
|
4366bdc716 | ||
|
|
598c68f955 | ||
|
|
e6a25b779c | ||
|
|
a0b35d1192 | ||
|
|
6c4c054180 | ||
|
|
38d0923d79 | ||
|
|
8dce1d7007 | ||
|
|
6739e8e902 | ||
|
|
fe093bc15f | ||
|
|
3cd6f44d7d | ||
|
|
9e8c98290e | ||
|
|
9836a4cc9d | ||
|
|
bf1d34e897 | ||
|
|
ecf09f4e95 | ||
|
|
a3b7a27045 | ||
|
|
375b2159c8 | ||
|
|
b5d8204986 | ||
|
|
c98eb69cbd | ||
|
|
df89d96803 | ||
|
|
ec08109a09 | ||
|
|
6f06fce039 | ||
|
|
b1eb5d00d5 | ||
|
|
a9b59ceb1c | ||
|
|
935a8f56bf | ||
|
|
17186b0c11 | ||
|
|
c7a93803c9 | ||
|
|
164e89253a | ||
|
|
a8d1dbc11f | ||
|
|
5aad0cf5f7 | ||
|
|
9bd0dae865 | ||
|
|
daa14d0b7e | ||
|
|
ca3c919a34 | ||
|
|
bbae893c45 | ||
|
|
6dd6297414 | ||
|
|
04d9ea9eba | ||
|
|
94852acf82 | ||
|
|
3639566e1b | ||
|
|
1698d9fd3e | ||
|
|
441f14b5f7 | ||
|
|
cc9ea7c718 | ||
|
|
7e11d1099a | ||
|
|
0a8439a855 | ||
|
|
da23810205 | ||
|
|
cb245a6402 | ||
|
|
e84d7bd185 | ||
|
|
6c045bfe2e | ||
|
|
eaf71ce16f | ||
|
|
f1d7197480 | ||
|
|
625182c3dc | ||
|
|
b16b0d10e7 | ||
|
|
a383b2d11f |
@@ -0,0 +1,9 @@
|
||||
from pathlib import Path
|
||||
import difflib
|
||||
files = ['src/main/assistant/assistant-database.test.ts', 'src/main/assistant/activity-history-io.test.ts', 'src/main/assistant/assistant-storage-upgrade.test.ts', 'src/main/assistant/heartbeat-database.test.ts', 'src/main/magic-notes/magic-note-storage.test.ts']
|
||||
patch = ''
|
||||
for name in files:
|
||||
old = Path(name).read_text(encoding='utf-8')
|
||||
new = old.replace('DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;', 'DROP VIEW IF EXISTS supervision_review_current;\n DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;\n DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;\n DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;')
|
||||
patch += ''.join(difflib.unified_diff(old.splitlines(True), new.splitlines(True), fromfile='a/'+name, tofile='b/'+name))
|
||||
Path('.validation-fixtures.patch').write_text(patch, encoding='utf-8')
|
||||
@@ -228,12 +228,14 @@ jobs:
|
||||
set -euo pipefail
|
||||
output="$RUNNER_TEMP/goodbuddy-agent-output"
|
||||
mkdir -p "$output"
|
||||
continue_archive="$(node build/acquire-continue-runtime.cjs "$RUNNER_TEMP/goodbuddy-continue-runtime" "$AGENT_ARCH" "$AGENT_PLATFORM")"
|
||||
node build/agent-package.cjs build \
|
||||
--platform "$AGENT_PLATFORM" \
|
||||
--arch "$AGENT_ARCH" \
|
||||
--minimum-desktop-version "$(node -p "require('./package.json').version")" \
|
||||
--node-archive "$RUNNER_TEMP/goodbuddy-agent-release-inputs/$NODE_ARCHIVE" \
|
||||
--runtime-archive "$RUNNER_TEMP/goodbuddy-agent-release-inputs/$OPENCODE_ARCHIVE" \
|
||||
--continue-archive "$continue_archive" \
|
||||
--output "$output/goodbuddy-agent-$AGENT_VERSION-$AGENT_PLATFORM-$AGENT_ARCH.gbagent" \
|
||||
> "$output/result.json"
|
||||
node - "$output/result.json" "$AGENT_ARCH" "$AGENT_VERSION" <<'NODE'
|
||||
@@ -484,6 +486,7 @@ jobs:
|
||||
AGENT_VERSION: ${{ needs.authorize.outputs.version }}
|
||||
OSS_BUCKET: ${{ vars.ALIYUN_OSS_BUCKET }}
|
||||
OSS_ENDPOINT: ${{ vars.ALIYUN_OSS_ENDPOINT }}
|
||||
OSS_UPLOAD_ENDPOINT: https://oss-accelerate.aliyuncs.com
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export OSS_ACCESS_KEY_ID="$ALIBABA_CLOUD_ACCESS_KEY_ID"
|
||||
@@ -497,7 +500,7 @@ jobs:
|
||||
name="$(basename "$file")"
|
||||
ossutil cp "$file" \
|
||||
"oss://${OSS_BUCKET}/agent-releases/v${AGENT_VERSION}/${name}" \
|
||||
--endpoint "$OSS_ENDPOINT" \
|
||||
--endpoint "$OSS_UPLOAD_ENDPOINT" \
|
||||
--region "$region" \
|
||||
--update
|
||||
done
|
||||
|
||||
@@ -305,11 +305,13 @@ jobs:
|
||||
rm -rf "$directory"
|
||||
mkdir -p "$directory"
|
||||
agent_version="$(node -p "require('./agent-runtime-lock.json').agentVersion")"
|
||||
continue_archive="$(node build/acquire-continue-runtime.cjs "$RUNNER_TEMP/goodbuddy-continue-runtime" "$AGENT_ARCH" "$AGENT_PLATFORM")"
|
||||
node build/agent-ci-bundle.cjs \
|
||||
--platform "$AGENT_PLATFORM" \
|
||||
--arch "$AGENT_ARCH" \
|
||||
--node-archive "$RUNNER_TEMP/goodbuddy-agent-node/$NODE_ARCHIVE" \
|
||||
--opencode-archive "$RUNNER_TEMP/goodbuddy-opencode-runtime/$OPENCODE_ARCHIVE" \
|
||||
--continue-archive "$continue_archive" \
|
||||
--output-directory "$directory/bundle" \
|
||||
--archive "$directory/goodbuddy-agent-$agent_version-$AGENT_PLATFORM-$AGENT_ARCH.gbagent" |
|
||||
tee "$directory/result.json"
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
validate:
|
||||
name: Validate source
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
@@ -40,9 +40,22 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Prepare sandboxed Electron tests
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends --yes xvfb xauth
|
||||
sandbox="node_modules/electron/dist/chrome-sandbox"
|
||||
if [[ ! -f "$sandbox" ]]; then
|
||||
rm -rf node_modules/electron/dist
|
||||
force_no_cache=true node node_modules/electron/install.js
|
||||
fi
|
||||
test -f "$sandbox"
|
||||
sudo chown root:root "$sandbox"
|
||||
sudo chmod 4755 "$sandbox"
|
||||
|
||||
- name: Run validators
|
||||
run: |
|
||||
npm test
|
||||
xvfb-run --auto-servernum npm test
|
||||
npm run typecheck
|
||||
npm run lint
|
||||
|
||||
@@ -445,6 +458,7 @@ jobs:
|
||||
env:
|
||||
OSS_BUCKET: ${{ vars.ALIYUN_OSS_BUCKET }}
|
||||
OSS_ENDPOINT: ${{ vars.ALIYUN_OSS_ENDPOINT }}
|
||||
OSS_UPLOAD_ENDPOINT: https://oss-accelerate.aliyuncs.com
|
||||
OSS_REGION: ${{ steps.oss-release.outputs.region }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -459,7 +473,7 @@ jobs:
|
||||
name="$(basename "$file")"
|
||||
ossutil cp "$file" \
|
||||
"oss://${OSS_BUCKET}/releases/${GITHUB_REF_NAME}/${name}" \
|
||||
--endpoint "$OSS_ENDPOINT" \
|
||||
--endpoint "$OSS_UPLOAD_ENDPOINT" \
|
||||
--region "$OSS_REGION" \
|
||||
--update
|
||||
done
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
--- a/src/main/assistant/assistant-database.test.ts
|
||||
+++ b/src/main/assistant/assistant-database.test.ts
|
||||
@@ -174,7 +174,10 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
- DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
+ DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 35;`)
|
||||
legacy.close()
|
||||
try {
|
||||
@@ -325,7 +328,10 @@
|
||||
DROP VIEW supervision_review_current;
|
||||
DROP TABLE supervision_review_navigation; DROP TABLE supervision_review_batches;
|
||||
DROP TABLE supervision_review_sources; DROP TABLE supervision_review_runs;
|
||||
- DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
+ DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 41;`)
|
||||
legacy.close()
|
||||
try {
|
||||
@@ -1088,6 +1094,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 3;
|
||||
`)
|
||||
@@ -1251,6 +1260,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 26;
|
||||
`)
|
||||
@@ -1367,6 +1379,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 30;
|
||||
`)
|
||||
@@ -1445,6 +1460,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 30;
|
||||
`)
|
||||
@@ -1542,6 +1560,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 24;
|
||||
`)
|
||||
@@ -1589,6 +1610,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 24;
|
||||
`)
|
||||
@@ -1637,6 +1661,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 24;
|
||||
`)
|
||||
@@ -1681,6 +1708,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 24;
|
||||
`)
|
||||
@@ -1714,6 +1744,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 24;
|
||||
`)
|
||||
@@ -1744,6 +1777,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 5;
|
||||
`)
|
||||
@@ -1871,6 +1907,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 21;
|
||||
COMMIT;
|
||||
@@ -1967,6 +2006,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 9;
|
||||
`)
|
||||
@@ -2025,6 +2067,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 16`)
|
||||
legacy.close()
|
||||
@@ -2977,6 +3022,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 23`)
|
||||
legacy.close()
|
||||
@@ -3141,6 +3189,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 18;
|
||||
`)
|
||||
@@ -3654,6 +3705,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 31;
|
||||
`)
|
||||
--- a/src/main/assistant/activity-history-io.test.ts
|
||||
+++ b/src/main/assistant/activity-history-io.test.ts
|
||||
@@ -27,6 +27,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version=43`)
|
||||
const legacySave = raw.prepare('UPDATE activity_history SET records_json = ?, legacy_history_may_be_incomplete = 1')
|
||||
--- a/src/main/assistant/assistant-storage-upgrade.test.ts
|
||||
+++ b/src/main/assistant/assistant-storage-upgrade.test.ts
|
||||
@@ -101,7 +101,10 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
- DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
+ DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 37`)
|
||||
} finally { legacy.close() }
|
||||
expect(hasPendingAssistantStorageUpgrade(path)).toBe(true)
|
||||
@@ -419,7 +422,10 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
- DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
+ DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = ${sourceVersion}; BEGIN`)
|
||||
const insert = legacy.prepare(
|
||||
`INSERT INTO task_events(
|
||||
--- a/src/main/assistant/heartbeat-database.test.ts
|
||||
+++ b/src/main/assistant/heartbeat-database.test.ts
|
||||
@@ -82,6 +82,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 2`)
|
||||
raw.close()
|
||||
@@ -141,6 +144,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 20`)
|
||||
raw.close()
|
||||
--- a/src/main/magic-notes/magic-note-storage.test.ts
|
||||
+++ b/src/main/magic-notes/magic-note-storage.test.ts
|
||||
@@ -223,6 +223,9 @@
|
||||
ALTER TABLE activity_history RENAME COLUMN record_order_json TO records_json;
|
||||
DROP TRIGGER messages_review_insert; DROP TRIGGER messages_review_update;
|
||||
DROP TRIGGER messages_review_delete; DROP TRIGGER tasks_review_delete;
|
||||
+ DROP VIEW IF EXISTS supervision_review_current;
|
||||
+ DROP TABLE IF EXISTS supervision_review_navigation; DROP TABLE IF EXISTS supervision_review_batches;
|
||||
+ DROP TABLE IF EXISTS supervision_review_sources; DROP TABLE IF EXISTS supervision_review_runs;
|
||||
DROP TABLE review_checkpoints; ALTER TABLE messages DROP COLUMN review_revision;
|
||||
PRAGMA user_version = 37`)
|
||||
rmSync(join(directory, 'notes'), { recursive: true })
|
||||
@@ -0,0 +1,5 @@
|
||||
--- a/src/renderer/src/App.test.tsx
|
||||
+++ b/src/renderer/src/App.test.tsx
|
||||
@@ -13972 +13972 @@
|
||||
- supervisionReview: { pageSize: 17, batchCharacters: 8000, batchMessages: 30, executionSeconds: 300 }
|
||||
+ supervisionReview: { pageSize: 17, batchCharacters: 8000, batchMessages: 30, executionSeconds: 300, responseKiB: 1024 }
|
||||
@@ -31,7 +31,7 @@ risk or an explicit user requirement.
|
||||
## Design Simplicity
|
||||
|
||||
Follow KISS and YAGNI. Use the smallest conventional design that completes the
|
||||
real user workflow.
|
||||
real user workflow. NO over-engineering or over-design.
|
||||
|
||||
- A demonstrated failure authorizes only the smallest proportional fix, not a
|
||||
general recovery, compatibility, or security framework.
|
||||
@@ -294,7 +294,7 @@ Repository-wide release invariants:
|
||||
|
||||
## Validation
|
||||
|
||||
Run all validators after source changes:
|
||||
Run validators after source changes when function/fix done, not on every source change:
|
||||
|
||||
```text
|
||||
npm test
|
||||
|
||||
@@ -43,6 +43,31 @@ npm run typecheck
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Vitest runs two ordered projects from `vitest.config.ts`. The `unit` project runs
|
||||
first with up to four workers, bounded by Node's available CPU parallelism. Once
|
||||
it finishes, the `integration` project runs with one worker. The serial group
|
||||
contains all test files under `tests/` (including `*.electron.test.ts`),
|
||||
`src/main/agent/**/*runtime*.test.ts`, and
|
||||
`src/agent-daemon/private-endpoint.test.ts`. Other tests remain in the unit group.
|
||||
Both projects inherit the React plugin, jsdom environment, renderer setup, and
|
||||
existing exclusions; test-file isolation remains enabled.
|
||||
|
||||
This preserves serialization for the process/socket tests that previously timed
|
||||
out on Linux with two workers (commit `93721d6`). `sequence.groupOrder` keeps the
|
||||
two phases from overlapping. Existing file filters still work, for example
|
||||
`npm test -- src/main/agent/opencode-runtime.test.ts`. To select a whole phase,
|
||||
use `npm test -- --project unit` or `npm test -- --project integration`. To inspect
|
||||
file selection without running tests, use `npx vitest list --filesOnly`.
|
||||
|
||||
For activity-history persistence, run
|
||||
`npx vitest run src/main/assistant/activity-history-io.test.ts`. This checks both
|
||||
WAL growth and repeated full-history reads: consecutive saves compare against
|
||||
the last committed in-memory records, invalidated on close, data clearing or
|
||||
another SQLite connection's commit. It also checks rollback and retry. The cache
|
||||
does not change the database schema or the full-snapshot IPC contract. WAL size
|
||||
and SQL scan counts do not measure physical disk throughput or conversation
|
||||
message persistence; validate those separately when investigating live I/O.
|
||||
|
||||
监听模式:
|
||||
|
||||
```bash
|
||||
@@ -78,6 +103,13 @@ npm run build
|
||||
|
||||
中间构建输出位于 `out`。该目录为生成内容,应修改源文件后重新构建,不要直接编辑。
|
||||
|
||||
Obsidian 接入的构建钩子将锁定的 npm `@bitbonsai/mcpvault@0.16.0` 及递归运行依赖、
|
||||
许可证和原生辅助资源复制到 `out/main/obsidian-mcpvault`,桌面打包时解包到
|
||||
`app.asar.unpacked`。运行时复用现有工具执行环境的 Node,不执行在线 npm 安装。
|
||||
生产组件自动化、Electron 托管 Node、Windows 设置与聊天 UI,以及受限网络下的
|
||||
Windows x64 解包应用已验证;物理断网、安装器及其他系统验收待完成。
|
||||
验证记录见 [Obsidian 进度](./docs/features/obsidian/progress.md)。
|
||||
|
||||
## 图标生成
|
||||
|
||||
应用图标源文件位于 `icons`。修改源图或图标处理逻辑后运行:
|
||||
@@ -150,13 +182,37 @@ npm run dist:linux:arm64
|
||||
已由 Vite 打包的 `@antv/g6` 归类为构建期依赖,不会再作为独立 Node 模块复制进桌面
|
||||
安装包。
|
||||
|
||||
Desktop packaging includes only `out/main`, `out/preload`, and `out/renderer`;
|
||||
validation builds elsewhere under `out` are not shipped. Renderer-only React,
|
||||
Xterm, Mermaid, Fabric, Quill, i18next, and Markdown/math dependencies are build-time
|
||||
dependencies: Vite ships their browser bundles, while the existing license
|
||||
resources remain included. Install development dependencies before building;
|
||||
do not prune them before bundling and copying license resources.
|
||||
|
||||
The desktop DS Web resource filter excludes JavaScript/TypeScript source maps,
|
||||
`.d.ts` / `.d.mts` / `.d.cts` declarations, and `.pdb` debug symbols. It preserves
|
||||
the installed dependency layout, plugin configuration, browser assets, licenses,
|
||||
native binaries, and non-code `.map` assets. This filter does not change the
|
||||
separately built remote Agent or Runtime payloads.
|
||||
|
||||
Windows x64 development validation on 2026-09-28 compared the existing 0.15.2
|
||||
portable directory (excluding `data`) with a fresh unpacked test build:
|
||||
`app.asar` decreased from 609.4 to 350.5 MiB, and DS Web decreased from 28,701
|
||||
files / 463.4 MiB to 14,984 files / 357.0 MiB. Total measured size decreased from
|
||||
1,758.3 to 1,438.4 MiB despite the test build retaining 45.6 MiB more Electron
|
||||
locales than the portable build. These are unpacked sizes, not installer download
|
||||
sizes. Builder-copy regression tests, production build/type checks, packaged
|
||||
Harness/OpenCode/npm smoke checks, DS Web authentication/page scripts/workspace
|
||||
creation, and packaged App/settings/notes-page loading passed. Full editor/canvas
|
||||
interaction and other platform packages were not revalidated in this check.
|
||||
|
||||
以上 `dist*` 与 `portable` 命令用于普通本地桌面打包。它们会携带
|
||||
`agent-runtime-lock.json`、`remote-runtime-lock.json` 与公开的
|
||||
`agent-release-keys.json`,但所有桌面构建路径(包括本地 `portable`)都不会嵌入
|
||||
可安装的远端 Agent 或远端 Runtime payload。没有使用托管 SSH 时,无需下载任何额外
|
||||
组件,缺少远端包也不能阻塞普通桌面打包或发布。
|
||||
|
||||
“设置 > 平台功能 > 远程项目(技术预览)”列出用户数据目录中已经下载或导入的
|
||||
“设置 > 平台功能 > 远程项目”列出用户数据目录中已经下载或导入的
|
||||
Linux x64、Linux arm64 和 macOS arm64 复合 Agent 包。首次打开和手动刷新会从“关于与更新”所选来源读取
|
||||
小型签名目录,显示本地版本、在线最新兼容版本以及 `有更新 / 已是最新 / 未下载 /
|
||||
校验失败`;此检查不会下载 `.gbagent`。只有用户点击“下载”或“更新到”后才传输包,
|
||||
@@ -313,7 +369,7 @@ OSS 先写 `agent-releases/v<version>/` 不可变对象,GitHub Release 公开
|
||||
`agent-catalog.json` 与 `agent-catalog.sig`,避免客户端观察到跨版本组合。Agent Release 必须使用
|
||||
`--latest=false`,不得改变桌面 Release 的 Latest 标记。
|
||||
|
||||
### 远程 OpenCode Runtime 基础
|
||||
### 远程 Runtime 工件
|
||||
|
||||
`remote-runtime-lock.json` 独立锁定首个远程 Runtime:OpenCode 1.18.29 的 Linux x64
|
||||
baseline 与 arm64 官方包 integrity、`bin/opencode` 入口和固定 `acp` 参数。它不同于
|
||||
@@ -321,6 +377,14 @@ baseline 与 arm64 官方包 integrity、`bin/opencode` 入口和固定 `acp`
|
||||
的 Koffi 版本。Agent bundle 将 Koffi loader 作为 external module,并只携带目标
|
||||
Linux 架构的 glibc/musl 原生 binding;构建与导入都会校验其 ELF 架构和 MIT 许可证。
|
||||
|
||||
lock 同时固定 `@continuedev/cli@1.5.47` 的 npm integrity。CN 工件使用
|
||||
`lib/continue/dist/cn.js`,由 Agent 中的 HTTP-to-ACP helper 启动,不能传入原生 `acp`
|
||||
参数。`remote-runtime:build` 与 `build/agent-package.cjs build` 接受
|
||||
`--runtime-id continue`;默认 Agent 组包通过 `--continue-archive` 同时携带 OC/CN。
|
||||
CN 的 JS payload 通过文件摘要与签名校验,执行它的 Node 仍来自 Agent 固定包。
|
||||
默认 workflow 已接入联合组包;开发包与完整桌面到 Linux 的实测见
|
||||
[CN 部署记录](./docs/features/remote-host/runtime-checklist-validation.md#cn-部署与生命周期)。
|
||||
|
||||
远程 Runtime 工件由以下命令独立管理:
|
||||
|
||||
```bash
|
||||
|
||||
+439
-70
@@ -24,7 +24,7 @@ records are listed separately and do not introduce another feature status.
|
||||
packages target compatible x64 and arm64 environments, including UOS, Kylin,
|
||||
Hygon, Zhaoxin, Kunpeng, and Phytium systems. This is not vendor certification.
|
||||
LoongArch has a separate experimental loong64 preview outside standard
|
||||
releases and automatic updates; no preview is planned for 0.13.5. See the
|
||||
releases and automatic updates; it is built only on explicit request. See the
|
||||
[preview boundaries](./docs/development/loongarch-preview-build.md).
|
||||
- [x] **Configurable global shortcut**: Enable, disable, or record an Electron
|
||||
accelerator under Platform Features / General. The default remains
|
||||
@@ -45,10 +45,35 @@ records are listed separately and do not introduce another feature status.
|
||||
menus provide keyboard navigation and exact conversation opening without a
|
||||
full activity dialog. The summary hides when idle, long conversation submenus
|
||||
scroll independently, and project selection continues to show activity counts.
|
||||
Successful background conversations retain a completion notice during the
|
||||
current client session until their chat is visible and loaded, not across restarts.
|
||||
- [x] **Pinned conversations and complete saved history**: Pin common
|
||||
conversations and retain their position after restart. Lists and search no
|
||||
longer stop at 100 conversations, and consecutive sends no longer truncate
|
||||
saved history beyond 500 messages. Model context limits still apply;
|
||||
previously lost messages cannot be restored.
|
||||
Pinned and unpinned groups order by the latest message creation time,
|
||||
keeping positions stable during streaming text and tool updates.
|
||||
- [x] **Optional desktop task notifications**: A default-on switch under
|
||||
Platform Features / General immediately controls system task reminders.
|
||||
In-app notifications remain available independently; General settings group
|
||||
shortcuts, conversations and notifications, and the working directory.
|
||||
- [x] **On-demand contextual help**: Supplementary page and settings
|
||||
explanations use shared title- or field-adjacent help controls with mouse
|
||||
and keyboard access. Important operation consequences and errors remain visible.
|
||||
- [x] **Compact conversation controls**: Composer options share a compact
|
||||
settings panel while Runtime, mode, sending, and queue controls remain
|
||||
available. Conversation search has an inline clear action that restores the
|
||||
scoped list and returns focus to the input.
|
||||
scoped list and returns focus to the input. New conversation and expandable
|
||||
search share one row. Composer shortcut hints reflect successful registration
|
||||
and saved settings, without being overwritten by late startup responses.
|
||||
- [x] **On-demand conversation history**: Lists load lightweight summaries
|
||||
rather than every conversation's process metadata. Opened and active
|
||||
conversations retain full details; search, copy, export, continued requests,
|
||||
and remote pending questions remain available. Unchanged image reconciliation
|
||||
does not resend saved messages, and idle acknowledged details follow the
|
||||
existing view-cache lifetime. See the
|
||||
[history loading and retention rules](./docs/features/assistant-workbar/execution-history-storage.md#会话列表读取与前端保留).
|
||||
- [x] **File, screenshot, window, and clipboard context**: Added to model
|
||||
context only after explicit user selection. Paste one or more supported local
|
||||
files into the chat composer with `Ctrl+V` to add attachments, using the same
|
||||
@@ -60,6 +85,17 @@ records are listed separately and do not introduce another feature status.
|
||||
model memory after 60 seconds without active or queued work and reloads on
|
||||
demand; fast text/index modes still skip OCR. See the
|
||||
[parsing behavior and real-file evidence](./docs/features/document-processing/chat-attachments-technical-design.md).
|
||||
- [x] **HTTP OCR and persistent attachment previews**: Configure HTTP
|
||||
PaddleOCR-VL under Settings > Document Parsing, shared by chat documents and
|
||||
knowledge imports. Saves originals and extracted images and provides
|
||||
text/image/detail previews, explicit image-to-text actions, and selecting
|
||||
document images into a named conversation draft before sending. Ordinary chat
|
||||
images use image input by default, subject to the effective model and Runtime.
|
||||
This adds no Application Center entry; Local Inference Monitor manages local
|
||||
services only. Real validation covers Windows, direct visual input, and managed
|
||||
OpenCode on the shared Linux Host. Deployment-specific advanced OCR models and
|
||||
other Runtime combinations need separate verification. See
|
||||
[scope and evidence](./docs/features/document-processing/README.md).
|
||||
- [x] **Per-file workspace diffs**: Changed files expose separate staged and
|
||||
unstaged diffs, including deleted, renamed, and untracked files. Refresh
|
||||
reloads the selected diff, browsed directory, and expanded directories; more than 50 changes
|
||||
@@ -78,6 +114,8 @@ records are listed separately and do not introduce another feature status.
|
||||
stash or discard. Managed SSH operations require Agent `0.11.23`.
|
||||
- [x] **Rich responses**: Supports GitHub Flavored Markdown, LaTeX math,
|
||||
constrained Mermaid diagrams, and static in-conversation HTML previews.
|
||||
Mermaid's large-diagram viewer keeps all edges reachable when zoomed and
|
||||
exports the complete diagram as PNG, independently of zoom and pan.
|
||||
Complete HTML and HTML code blocks can be previewed in place after an Agent
|
||||
reply finishes, with icon actions to reveal the source or open a full-screen
|
||||
preview. The feature is on by default and can be disabled under Platform
|
||||
@@ -91,26 +129,69 @@ records are listed separately and do not introduce another feature status.
|
||||
toggle sits beside the theme switch. The right workbar uses a persistent “+”
|
||||
capability catalog and application tabs.
|
||||
Task Center and Workspace are permanent singleton tabs; browser tabs are
|
||||
independent instances. Task Center can show the current project, global
|
||||
tasks, or all projects, and retains the selected scope with the layout.
|
||||
independent instances. Task Center can show the current project or all projects,
|
||||
and retains the selected scope with the layout. Tasks without a project appear
|
||||
under all projects with an “Unbound project” label.
|
||||
Users can open multiple independent terminals for the current local or
|
||||
managed SSH project with bounded output, resizing, termination, and explicit
|
||||
reconnection. Closing a terminal tab ends its Shell; restarting the app
|
||||
restores only an ended tab description and never restarts the Shell. The
|
||||
main sidebar and Magic Notes list support pointer and keyboard resizing;
|
||||
the notes list can be hidden, remembers its layout, and stacks at narrow
|
||||
widths.
|
||||
main sidebar and Magic Notes editor's AI pane support pointer and keyboard
|
||||
resizing. The AI pane remembers its visibility and width, and stacks below
|
||||
the editor at narrow widths.
|
||||
- [ ] **Project Agent Space** (planned): Unifies roles, knowledge, Skills/MCP,
|
||||
models, approval policy, budgets, and timeouts in a Project, with reusable
|
||||
templates.
|
||||
- [ ] **Application navigation and system-tool shortcuts** (design only):
|
||||
Plans fixed system entries, configurable built-in application visibility,
|
||||
and footer shortcuts into the existing workbar. These are not implemented
|
||||
interface changes. See the [design](./docs/features/application-tool-navigation/README.md).
|
||||
- [x] **Application center and navigation**:
|
||||
The footer shows equal-height Apps and Settings buttons with visible labels and a subtle vertical divider.
|
||||
Clicking Apps, marked with an upward chevron, opens a lightweight upward anchored popup with enabled apps,
|
||||
regardless of pinning or opening history, and no modal backdrop. Clicking an app row closes
|
||||
the popup; Local Inference Monitor opens a separate modal preserving the workspace, while other apps
|
||||
open their main content pages. Manage Apps uses a LayoutGrid icon and opens Application Center with searchable cards and settings details;
|
||||
layout and interaction rules are defined in the [UI design](./docs/features/application-tool-navigation/ui-design.md).
|
||||
All five application rows support arrow and drag reordering regardless of enablement or pinning.
|
||||
Cards, the launcher, and the filtered sidebar share one persisted order. Knowledge
|
||||
is always enabled and pinned, with Open and reorder controls. Supervisor, Notes,
|
||||
and Local Inference Monitor have enablement and pinning settings. Device Sharing
|
||||
is optional and off by default during testing, with an enable switch but no pinning.
|
||||
Supervisor uses
|
||||
the existing Heartbeat identity and defaults to off when no preference is saved;
|
||||
explicit saved choices are preserved. Disabling it retains plans and history,
|
||||
blocks new reviews, and hides sidebar feedback; in-flight work may finish.
|
||||
Enabling the app does not create automatic plans. Older application orders retain
|
||||
their relative order when missing defaults are added.
|
||||
Durable settings saves, including configuration-tool writes, synchronize through change events;
|
||||
reopening the center locks edits until refresh completes, and newer snapshots supersede stale reads.
|
||||
The `local-inference` modal shows a single service list without task history or external
|
||||
connections. Embedding shows its independent process CPU and working-set memory;
|
||||
ASR/OCR explain shared-process attribution limits. ASR readiness is unknown rather
|
||||
than inferred from active requests. Supported service controls retain impact confirmation;
|
||||
failed operations refresh the list and require a new confirmation. TTS remains unavailable.
|
||||
Cancelling inference remains active in service-stop impact until worker
|
||||
acknowledgement or exit. Implementation is connected to production App;
|
||||
acceptance records distinguish full-App checks, component fixtures, real
|
||||
local-engine checks, and remaining platform/package coverage. Historical
|
||||
failures are not the current full-suite result. See the
|
||||
[validation progress](./docs/features/application-tool-navigation/progress.md).
|
||||
- [x] **Local device-sharing metadata** (implemented in source): Enable Device Sharing
|
||||
in Application Center first; it is off by default during testing. Saved explicit
|
||||
enablement is preserved. Disabling leaves the page without clearing catalog records
|
||||
or revoking publications. Open it from Apps or Application Center, save a service URL and device name, register the
|
||||
device, browse returned records, publish capability or knowledge metadata, and
|
||||
revoke this device ID's entries. The default service is `http://127.0.0.1:8787`.
|
||||
This unauthenticated local service uses IDs for identification only. Server source
|
||||
mode does not upload or sync content, and independent knowledge search/read/download
|
||||
flags are declarations, not content access or remote execution. See
|
||||
[scope and validation](./docs/features/device-sharing/README.md).
|
||||
- [ ] **Privately deployable application marketplace** (planned): Extends
|
||||
application management with an organization-owned catalog and application
|
||||
distribution. Current management covers built-in apps only, not marketplace
|
||||
browsing or installation. See
|
||||
[FR-15](./docs/features/application-tool-navigation/prd.md#fr-15-私有化市场与-yaml-交换后续).
|
||||
- [ ] **Additional assistant workbar and execution-space capabilities**
|
||||
(planned): Builds on the current workbar and multiple terminals with
|
||||
supervision, unified Runtime monitoring, managed processes, safe static HTML
|
||||
preview, target-pinnable workspace/browser/artifact instances, bottom
|
||||
broader execution supervision, unified Runtime monitoring, managed processes,
|
||||
target-pinnable workspace/browser/artifact instances, bottom
|
||||
docking, and separate windows. Task Center remains the singleton Task index;
|
||||
attachments and knowledge remain in the conversation composer, while memory,
|
||||
when implemented, and historical execution context belong to the associated Task. See
|
||||
@@ -134,7 +215,9 @@ records are listed separately and do not introduce another feature status.
|
||||
reopen the upgraded database. The schema-35 repair released in 0.13.2 also handles
|
||||
repeated tool blocks written after upgrading to 0.13.1, without deleting events.
|
||||
Terminal tasks release their write caches; duplicate remote replay does not
|
||||
rebuild a released cache. See
|
||||
rebuild a released cache. Structure-only upgrades do not reconvert history or
|
||||
reclaim ordinary free pages; the startup page distinguishes structure updates,
|
||||
legacy conversion, and actual space reclamation. See
|
||||
[storage and upgrade behavior](./docs/features/assistant-workbar/execution-history-storage.md).
|
||||
|
||||
- [x] **Direct model Runtime**: Supports question answering, knowledge
|
||||
@@ -207,8 +290,8 @@ records are listed separately and do not introduce another feature status.
|
||||
first; invalid paths fail at actual launch. On managed Linux ARM Hosts,
|
||||
Runtime activation reuses the registry and manifest verified during setup
|
||||
instead of rehashing or rechecking the complete OpenCode binary.
|
||||
- [x] **Managed SSH OpenCode loop (technical preview)**: Controlled by the
|
||||
separate Remote Projects (Technical Preview) tab under Settings / Platform
|
||||
- [x] **Managed SSH OpenCode and Continue**: Controlled by the
|
||||
separate Remote Projects tab under Settings / Platform
|
||||
Features and disabled by default. Disabling it does not affect local
|
||||
projects, ordinary desktop capabilities, or desktop releases. When enabled,
|
||||
users can manage SSH Hosts with pinned Host Keys, browse bounded remote
|
||||
@@ -220,6 +303,10 @@ records are listed separately and do not introduce another feature status.
|
||||
provider/tool rounds, Runtime processes, a stable model ledger, and a bounded
|
||||
semantic transcript over a private Unix socket and ACP v5. Work continues on
|
||||
the Host after Desktop exit, network loss, or local-process termination.
|
||||
The current Agent may remain resident; a superseded Agent drains active work
|
||||
before exiting. Idle Runtime processes are reclaimed after tasks finish,
|
||||
while shared processes retain other active tasks, including pending questions.
|
||||
Subsequent turns recreate the Runtime and restore conversation history.
|
||||
Reconnection attaches only to the original controller, binding, and
|
||||
operation, then atomically merges provenance into the original conversation
|
||||
without resending the Prompt. Model profiles and API keys enter Agent memory
|
||||
@@ -231,7 +318,7 @@ records are listed separately and do not introduce another feature status.
|
||||
state for Linux x64, Linux arm64, and macOS arm64. Intel Macs are not supported.
|
||||
Downloads occur only after a user action, and
|
||||
packages can also be imported or exported offline. Each compound `.gbagent`
|
||||
package contains the Agent, fixed Node, and the compatible OpenCode Runtime
|
||||
package contains the Agent, fixed Node, and compatible OpenCode and Continue Runtimes
|
||||
maintained by the desktop source. Online sources follow the GitHub/Beijing
|
||||
OSS choice in About and Updates. The cumulative signed catalog binds minimum
|
||||
Desktop version, Agent protocol, platform, architecture, size, SHA-256, and fixed URL.
|
||||
@@ -245,7 +332,8 @@ records are listed separately and do not introduce another feature status.
|
||||
SSH only for that architecture. Projects store only Host, remote path,
|
||||
Runtime selection, and mode. Current Host identity is read when a
|
||||
Workspace/Runtime is first used, then reused by other projects in the same
|
||||
process. Managed SSH conversations expose only supported OpenCode choices.
|
||||
process. Managed SSH conversations expose supported OpenCode and Continue choices;
|
||||
Continue requires the updated compound package and matching Desktop.
|
||||
Multiple projects and conversations can run concurrently on one Host;
|
||||
Agent-owned prompts are not stopped at a fixed model-call count or
|
||||
prompt-wide output-token total, and Runtime output uses transport
|
||||
@@ -257,7 +345,7 @@ records are listed separately and do not introduce another feature status.
|
||||
failure, Agent `SIGKILL`/restart, and recovery from a reopened Desktop SQLite
|
||||
database. Successful tool START/END events appear exactly once, with no
|
||||
Prompt, provider, or tool replay observed. The current Agent source lock is
|
||||
`0.13.0`, while the current Desktop release candidate is `0.13.5`; formal
|
||||
`0.15.3`, paired with the current Desktop release candidate `0.15.9`; formal
|
||||
publication status follows the separate Agent and Desktop
|
||||
release channels. Previous macOS validation covered native package installation,
|
||||
detached lifecycle, Attach, real Ask/Execute, and cancellation of tools in
|
||||
@@ -306,7 +394,8 @@ records are listed separately and do not introduce another feature status.
|
||||
compatible connection without requiring a duplicate selection. Settings
|
||||
displays the actual administrator or fallback model source. Ask permits
|
||||
only real Host-registered `read` and `skill` tools plus Main-managed Web
|
||||
Search/Fetch proxies, and rejects plugin impersonation of those names.
|
||||
Search/Fetch and enabled Story Graph proxies, and rejects plugin impersonation
|
||||
of those names.
|
||||
Execute allows all enabled built-in and plugin tools with the current user's
|
||||
permissions. Image input follows the selected model connection's declared
|
||||
capability: text models reject images before Host or model invocation, while
|
||||
@@ -326,16 +415,32 @@ records are listed separately and do not introduce another feature status.
|
||||
user's authorization for all tools, processes, network access, and writable
|
||||
paths available to the current local or SSH account, including paths outside
|
||||
the workspace and native subagent work.
|
||||
- **Local native Runtime clients (preview)**: The composer opens Continue or
|
||||
OpenCode in a workbar terminal, or the official DS Web client in the system
|
||||
browser, using the current local project and model. These are independent
|
||||
sessions, without imported GoodBuddy conversation history. Closing a DS page
|
||||
does not stop its service; use GoodBuddy's stop action. Remote shortcuts are
|
||||
not connected. Continue/OpenCode Ask now map Main-bound read tools through
|
||||
native permissions. Managed standard Node delivery and full cross-platform
|
||||
package acceptance remain incomplete;
|
||||
existing Windows real-model evidence does not establish full capability parity.
|
||||
See the [implementation and remaining work](./docs/features/assistant-workbar/progress.md#2026-09-27-local-native-client-validation).
|
||||
- **Runtime telemetry opt-out**: Local DS Web and remote Runtime/model-bridge
|
||||
launches share the telemetry opt-out environment without blocking model, MCP,
|
||||
or user-requested networking. Continue skips disabled automatic-update checks.
|
||||
Remote changes require a newly started Runtime from the matching Agent;
|
||||
current-source Linux x64 Agent/model validation is recorded in the
|
||||
[0.15.0 preparation record](./docs/development/release-preparation-0.15.0.md).
|
||||
- [x] **Efficient direct-model workspace tools**: Direct models use bundled
|
||||
ripgrep for compact file discovery and content search, read large UTF-8 files
|
||||
by line, and apply multi-file patches in Execute. Ask can search and read but
|
||||
cannot patch or run commands; packaged ripgrep does not require a system install.
|
||||
Actionable read/search errors let the model correct its arguments and
|
||||
continue. Partial search results remain available with an explicit
|
||||
incomplete-coverage warning.
|
||||
ripgrep with native arguments for file discovery and content search, read large
|
||||
UTF-8 files by line, and apply multi-file patches in Execute. Ask searches stay
|
||||
read-only within the workspace; Execute searches use current-account permissions.
|
||||
No system ripgrep install is required. Search preserves native output and exit
|
||||
codes, with complete large results available through `output_read`; actionable
|
||||
argument errors let the model correct its request and continue.
|
||||
- [x] **Long context and paged tool output**: Message history, long replies,
|
||||
and parsed document text no longer use the previous fixed truncation limits.
|
||||
Direct models can continue reading stored command and Subagent output in
|
||||
Direct models can continue reading stored search, command, and Subagent output in
|
||||
pages; the selected model's context window and bounded transports still apply.
|
||||
- [x] **Native Runtime interaction routing**: OpenCode and Continue questions
|
||||
support choices, yes/no, free-text answers, and skipping through the existing
|
||||
@@ -344,13 +449,21 @@ records are listed separately and do not introduce another feature status.
|
||||
and a newly started managed Runtime;
|
||||
this does not extend question support to arbitrary ACP services.
|
||||
Successful answers and skips retain the original questions and answers
|
||||
across rounds and local conversation reloads; answers discarded by older
|
||||
at their original position across rounds and local conversation reloads; answers discarded by older
|
||||
versions cannot be recovered.
|
||||
Concurrent questions wait in order, duplicate events preserve drafts, and
|
||||
failed submissions remain retryable. Cancelling a pending managed SSH
|
||||
question allows another message in the same conversation.
|
||||
Execute permission confirmations are handled automatically rather than
|
||||
waiting for another approval. See the [interaction boundaries](./docs/features/assistant-workbar/runtime-interactions.md).
|
||||
- [x] **Native execution checklists**: OpenCode and Continue update a
|
||||
read-only checklist above the conversation, with progress saved in history.
|
||||
Details expand in an anchored, scrollable floating panel without shifting chat
|
||||
content; outside clicks close it and Escape restores focus to its trigger.
|
||||
Explicit clears and remote replay retain request ownership; cancellation
|
||||
does not mark unfinished items complete. Remote delivery requires the matching
|
||||
Agent package. See the
|
||||
[checklist contract](./docs/features/assistant-workbar/runtime-checklist-technical-design.md).
|
||||
- [x] **Experts and Subagents**: Supports explicit experts, team analysis, and
|
||||
up to three experts running in parallel. Experts inherit the parent
|
||||
Ask/Execute mode and can use enabled local direct-model tools; Ask remains
|
||||
@@ -371,10 +484,19 @@ records are listed separately and do not introduce another feature status.
|
||||
direct models report their own backoff phase. Footer dots are static, and
|
||||
unsupported Runtime retry details are not invented. Conversation-list
|
||||
indicators and send/stop behavior remain unchanged.
|
||||
- [x] **Simplified Runtime selection**: Conversation and project menus no
|
||||
longer enumerate every Runtime/model combination. Configure Runtime models
|
||||
in system settings; saved fixed project selections remain usable and
|
||||
explicitly display their fixed model.
|
||||
Cancellation and failure retain existing replies and use a single status;
|
||||
cancellation is neutral, and Edit again appears only when input can be restored.
|
||||
Model-visible history preserves interrupted partial replies or placeholders
|
||||
so cancelled questions are not merged into later requests. Reused local
|
||||
OpenCode sessions receive a one-time interruption notice.
|
||||
- [x] **Layered Runtime and model selection**: Resolve Runtime and model per
|
||||
field through conversation, project, and global defaults. The two-column
|
||||
composer picker shows sources and supports overrides or restoring project
|
||||
defaults; project and channel settings share the same rules. Main resolves
|
||||
each request again, including queued dispatch. Deleted or incompatible
|
||||
connections fall back, while missing credentials remain explicit errors.
|
||||
Schema 49 migrates stored choices. Purpose-specific models remain planned;
|
||||
see the [selection design](./docs/features/model-connections/runtime-selection-design.md).
|
||||
- [x] **Role-bound model connections**: Each role can inherit the default model
|
||||
or select an independent text-model connection. Invalid connections safely
|
||||
fall back to the default; the synthesis role always inherits the default.
|
||||
@@ -399,7 +521,12 @@ records are listed separately and do not introduce another feature status.
|
||||
fields take precedence, and these ordinary connection settings must not be
|
||||
used to store API keys or other secrets. See
|
||||
[model request customization](./docs/features/model-connections/README.md).
|
||||
- [x] **Context usage and automatic compaction**: Direct models update usage
|
||||
- [x] **Context usage and compaction**: Local direct text-model conversations
|
||||
also support manual compaction below the automatic threshold or with automatic
|
||||
compaction disabled. The selected summary model may incur charges; recent turns
|
||||
and full stored history remain available, and saved summaries are reused.
|
||||
Image connections and remote direct-model entry points are excluded.
|
||||
Direct models update usage
|
||||
from each successful provider call. Images and tool rounds use the same
|
||||
accounting, with estimation only when the provider omits usage. The UI
|
||||
distinguishes This Model Call from Post-Compaction Conversation Estimate,
|
||||
@@ -504,10 +631,22 @@ records are listed separately and do not introduce another feature status.
|
||||
the applicable checks pass.
|
||||
- [x] **On-demand built-in MCP**: Knowledge, Magic Notes, GoodBuddy
|
||||
configuration, and built-in browser MCP can be enabled independently and assigned to direct
|
||||
models, GoodBuddy-managed OpenCode, and Continue. Settings explicitly marks
|
||||
DeepSeek Harness unsupported. Built-in MCP uses short-lived local authority
|
||||
models, GoodBuddy-managed OpenCode, and Continue. DeepSeek Harness remains
|
||||
unsupported for these servers; Story Graph supports it through the Main proxy.
|
||||
Built-in MCP uses short-lived local authority
|
||||
for the current request, and user configuration cannot loosen Ask/Execute
|
||||
read/write boundaries.
|
||||
- **Obsidian integration (implemented in source, acceptance in progress)**:
|
||||
Disabled by default, with all locally registered vaults as the default scope
|
||||
and an optional folder that can be tested before saving or enabling.
|
||||
Bundled MCPVault 0.16.0 provides all upstream abilities through 19 shared tools.
|
||||
Automated production-component checks, Electron managed-Node tests, Windows
|
||||
settings and chat UI checks, and restricted-network Windows x64 unpacked-app
|
||||
checks passed. Physical-offline, installer, and other-platform acceptance
|
||||
remain pending. DeepSeek
|
||||
Harness and remote execution remain unavailable. See the
|
||||
[feature docs and validation record](./docs/features/obsidian/README.md);
|
||||
this is not a release claim.
|
||||
- [x] **MCP Tools**: Explicitly enabled custom MCP can be assigned to direct
|
||||
models, GoodBuddy-managed OpenCode, Continue Agent Execute, and DeepSeek
|
||||
Harness, and loads only in Execute. Agent child processes receive only
|
||||
@@ -528,6 +667,10 @@ records are listed separately and do not introduce another feature status.
|
||||
retrieval parameters are collapsed by default. Documents distinguish ready,
|
||||
processing, and failed states, with source opening, retry, and confirmation
|
||||
before source removal.
|
||||
Sources and indexed documents share one searchable list: single-document
|
||||
sources do not duplicate rows, while directories retain grouped documents and
|
||||
failed sources retain recovery actions. Task history and secondary operations
|
||||
remain reachable from the consolidated controls.
|
||||
- [x] **External knowledge-base connections**: Manage Dify, FastGPT,
|
||||
and RAGFlow instances from the Knowledge page, then discover remote
|
||||
knowledge bases or enter their IDs and verify bindings with provider-specific
|
||||
@@ -546,6 +689,8 @@ records are listed separately and do not introduce another feature status.
|
||||
[validation evidence and remaining work](./docs/features/knowledge-base/progress.md).
|
||||
- [x] **Knowledge graph**: Supports rule-based, model-based, and hybrid
|
||||
extraction plus entity, relationship, alias, and evidence maintenance.
|
||||
Model extraction adds no fixed 8192-token output cap: OpenAI follows provider
|
||||
limits, and Anthropic uses the model connection's output setting.
|
||||
- [x] **Embedding configuration and retrieval**: Configures compatible
|
||||
Embeddings endpoints and uses them for semantic retrieval.
|
||||
- [x] **Embedding diagnostics and indexing jobs**: Provides real embedding
|
||||
@@ -566,17 +711,70 @@ records are listed separately and do not introduce another feature status.
|
||||
demand or before every response, show zero-result, degraded, failed, and
|
||||
cancelled states, and expose cited context or safely open its source.
|
||||
- [x] **Magic Notes**: A local-first notes and todo workbench with scope
|
||||
management, editing, filtering, and controlled AI comments. The todo view
|
||||
displays the source entry inside the selected item's details, without
|
||||
management, editing, filtering, and controlled AI comments. Notes open from a
|
||||
responsive card overview. Detail displays a continuous stream with independently
|
||||
collapsible left index and right AI pane. The index defaults to 168px and supports
|
||||
pointer and keyboard resizing from 140px to 320px, with saved width preferences;
|
||||
narrow layouts retain the fixed-width drawer without a resize handle.
|
||||
Real first-page thumbnails scroll to
|
||||
records without replacing editors. Successful saves establish the editor's
|
||||
normalized baseline; only unsaved changes require confirmation on leaving. Opening
|
||||
a note shows a blank text/canvas composer above the history, and creation resets it
|
||||
for the next record. Explicit editing updates a saved record and exits after a
|
||||
successful save; automatic comments do not reopen the editor, and failed saves
|
||||
retain drafts. The separate To-dos view offers search, status filters,
|
||||
grouped tasks and direct completion, with instructions, source entries and AI
|
||||
comments in a right detail pane beside a stable task list. Narrow views offer
|
||||
on-demand detail with an explicit return to the list. Wide views support a
|
||||
draggable list/detail divider with a saved width; source-note groups collapse
|
||||
independently without clearing the selected task's details. A content-sized switch sits
|
||||
beside New note in the header; status filters follow search. Source-note
|
||||
navigation returns to the same task context, without
|
||||
showing the previous item's source after a selection change. Failed reads
|
||||
use application notifications, and missing sources have an explicit fallback. The left
|
||||
use application notifications with inline retry, and missing sources have an explicit fallback. The left
|
||||
navigation can show the incomplete-todo count, and create, save, and comment
|
||||
results use application-wide notifications. Agent/MCP note writes automatically
|
||||
refresh the open workbench while preserving selection and unsaved drafts.
|
||||
- **Conversation capture and quick notes** (implemented; real Electron acceptance passed):
|
||||
Preview and edit an assistant response or complete conversation text before
|
||||
appending to an existing note or creating one with its first entry. The compact
|
||||
Notes workbar tab supports title/body search, quick text appends and full-workspace
|
||||
navigation. Saved sources link back to conversations and messages; drafts survive
|
||||
conversation switches. Real App/preload/IPC/SQLite acceptance passed, including
|
||||
complete 502-message history and native keyboard use in a 214px panel, with zero
|
||||
model calls. The [feature validation record](./docs/features/magic-notes/progress.md)
|
||||
separates focused passes, fixture repairs and earlier execution-time failures;
|
||||
the [0.15.0 candidate validation](./docs/development/release-preparation-0.15.0.md)
|
||||
passed the full suite, typecheck and lint.
|
||||
- [x] **Paged canvas notes**: The integrated PeopleLib Fabric + Quill editor
|
||||
combines flowing body text with pen/highlighter, object selection/transforms,
|
||||
floating text, images and paper templates. Editors and read-only viewers support
|
||||
25%-300% view zoom, 100% reset and responsive Fit width, without changing saved
|
||||
content or export dimensions. Import PDFs as page backgrounds
|
||||
with native text extraction, then export a raster PDF of text and annotations;
|
||||
exported PDFs do not retain a searchable text layer. Manual saves store entry
|
||||
bodies and binary assets in local note files; SQLite retains metadata, indexes,
|
||||
revisions, todos and comments. Backups must coordinate SQLite and the notes
|
||||
directory; there is no one-click backup action. Undo/redo
|
||||
is per annotation page or Quill mode, not global document history; there is no
|
||||
infinite canvas or PNG download button. Drafts, saved entries and canvas-source
|
||||
todos use the default model's image-input capability for page images plus text,
|
||||
or an explicitly labelled text-only fallback. Canvas pages to send selects the
|
||||
first 1-8 pages in current order (default 1) for both text and images; saving and
|
||||
exporting still support 50 pages. Purely visual entries/drafts
|
||||
require an image-capable model. Immediate-comment mode offers manual canvas
|
||||
analysis; after-save automatic analysis failures do not block or undo saving.
|
||||
Text-only comments survive unchanged extracted text; visual comments are
|
||||
invalidated by layout changes and reanalyzed in after-save-auto mode. MCP
|
||||
rejects plain-text replacement of canvas entries. Implementation is complete;
|
||||
final validation status is tracked in the [feature progress record](./docs/features/magic-notes/progress.md).
|
||||
Historical body migration moved stored bodies into files; schema 48 now adds
|
||||
conversation-source metadata. Older clients cannot reopen
|
||||
that database; rollback requires a pre-upgrade backup.
|
||||
- [ ] **MCP Server Control Plane** (planned): Unified MCP lifecycle, health
|
||||
checks, reconnection, schema cache, isolation, approval, and audit.
|
||||
- [ ] **Traceable note excerpts and AI editing** (planned): Collects sourced
|
||||
excerpts from conversations, knowledge, and the web and provides
|
||||
- [ ] **Knowledge/web excerpts and AI editing** (planned): Extend capture beyond
|
||||
the implemented conversation-text path to knowledge and web excerpts, with
|
||||
confirmation-based summarization, rewriting, and organization.
|
||||
|
||||
### Work management, long-term collaboration, and workflows
|
||||
@@ -590,7 +788,13 @@ records are listed separately and do not introduce another feature status.
|
||||
without a persistent legacy-cache truncation warning; previously lost
|
||||
history is not restored. Token usage is grouped by Runtime and
|
||||
model and normalizes the different OpenAI-compatible and Anthropic Messages
|
||||
cache-reporting semantics when showing cache hit rate. Activity is grouped
|
||||
cache-reporting semantics when showing cache hit rate. Project and conversation
|
||||
usage groups show collapsed totals by default and expand to Runtime/model child
|
||||
rows; model grouping remains flat. A separate System tasks tab groups
|
||||
background usage by source, with manual refresh and updates when usage is
|
||||
recorded. Supervisor reviews, image regeneration, graph extraction, and remote
|
||||
embeddings feed usage records; missing historical/provider data is not reconstructed.
|
||||
Activity is grouped
|
||||
by conversation and collapsed by default so long histories do not fill the
|
||||
page.
|
||||
- [x] **Compact conversation tool records**: Expand individual tool records
|
||||
@@ -620,25 +824,77 @@ records are listed separately and do not introduce another feature status.
|
||||
Run Now actions. See the
|
||||
[Task Center PRD](./docs/features/task-and-job/task-center-prd.md) and
|
||||
[Scheduled Task PRD](./docs/features/task-and-job/scheduled-task-prd.md).
|
||||
- [x] **Memory and Smart Heartbeat**: Provides periodic review, suggested
|
||||
memories, insights, follow-up tasks, and auditable run history.
|
||||
- [x] **Improved Smart Heartbeat entry and scope**: Smart Heartbeat / Heartbeat
|
||||
Plans is the sole authoritative full-configuration entry. Plans can be
|
||||
created and edited for Global or one or more selected Projects. Legacy
|
||||
single-project settings migrate without loss, and project-level memory and
|
||||
action output must explicitly target a Project in scope. Task Center and
|
||||
Settings no longer duplicate the form. Partition-aware review, candidate
|
||||
generation, and recall triggers remain to be designed. See the
|
||||
[Smart Heartbeat PRD](./docs/features/smart-heartbeat/prd.md).
|
||||
- [ ] **General supervision** (planned): Uses a fixed supervision section to
|
||||
observe user-selected conversations, Tasks, automations, or experiments and
|
||||
provide evidence-backed comments and requests for human intervention,
|
||||
without automatically speaking, approving tools, or switching to Execute.
|
||||
See the
|
||||
Status counts and filters identify running tasks and those needing attention;
|
||||
task approvals remain actionable in their task cards.
|
||||
- [x] **Memory and Smart Heartbeat**: Plans trigger shared incremental reviews
|
||||
instead of separate heartbeat reports. Choose memory/graph updates only or
|
||||
evidence-backed suggestions for open items, disagreements, revisions, and
|
||||
candidate conventions. Accepted open items create paused tasks; confirmed
|
||||
conventions become long-term background. Suggestion failures can be retried
|
||||
independently without undoing reviews.
|
||||
- [x] **Smart heartbeat settings and scope**: Supervisor / Smart heartbeat
|
||||
manages daily or weekly Global or selected-Project plans and suggestions.
|
||||
Task Center and Settings do not duplicate this form. Pending suggestions are
|
||||
not repeated; dismissed suggestions can return with changed evidence.
|
||||
Project/global processing progress share one source-version timeline; real-time conversation
|
||||
intervention remains planned. See the
|
||||
[implemented rules](./docs/features/conversation-supervision/logic-design.md).
|
||||
- [x] **Stories, experiences, and time-spiral navigation**: Reviews organize
|
||||
events into project features, threads, and optional cross-project stories.
|
||||
Users can rename, merge, reassign, remove, and undo adjustments. Experiences
|
||||
retain formation/application evidence and human-edit markers; heartbeat
|
||||
suggestions can identify stalled stories or potentially useful experiences.
|
||||
Flat and lazy-loaded WebGL spiral views share selection and sources, with
|
||||
a flat-view fallback and graphics cleanup. Organization and suggestions may
|
||||
incur model costs and require source verification. See the
|
||||
[implementation evidence](./docs/features/conversation-supervision/progress.md).
|
||||
- [x] **Supervisor reviews, story graphs, and activity**: Review a selected scope
|
||||
and period, read dated supervision results and history in Work review, inspect saved result graphs and sources, confirm or revise entities,
|
||||
and continue a discussion with source-linked review context. Sidebar feedback follows or pins a
|
||||
Conversation or Task and opens its matching result. It shows target names with
|
||||
compact pin/refresh buttons and preserves long-title readability and keyboard
|
||||
focus in narrow sidebars. Activity combines heartbeat
|
||||
and downstream review stages, including failures and links to older results.
|
||||
Automatic stages process only new, changed, or unprocessed source portions;
|
||||
no-change checks skip model calls. Manual review is also incremental by default;
|
||||
explicit reanalysis rereads the period without changing shared progress.
|
||||
Automatic reviews do not resume user-paused runs. Reviews save batches with
|
||||
their facts and sources. Runs can pause and resume from
|
||||
saved progress, without fixed-duration automatic pauses; settings expose
|
||||
concurrency, per-request timeouts, batch sizes, and response capacity.
|
||||
Activity emphasizes connected stages and saved-batch coverage, with secondary
|
||||
statistics in expandable details. Work review uses a vertical full-width
|
||||
flow with a separate run status and correctly placed empty state.
|
||||
Calls remain read-only without tools and may incur model charges. See the
|
||||
[implementation and evidence](./docs/features/conversation-supervision/progress.md).
|
||||
Manual starts, automatic triggers, and resume share one active review;
|
||||
cancellation from Activity preserves saved batches and holds admission until
|
||||
cleanup finishes. Cancelled reviews cannot resume. Story-graph events, entities,
|
||||
and relations have separate counted tabs synchronized with canvas selection.
|
||||
Continue discussion opens directly from feedback or graph sources, with an
|
||||
editable question and explicit target-conversation navigation. The UI no
|
||||
longer exposes knowledge-entity writeback controls or redundant context
|
||||
explanations. Smart heartbeat shows actionable suggestions rather than report
|
||||
metrics and trends.
|
||||
- [ ] **Further supervision capabilities** (planned): Full event-by-event replay,
|
||||
Experiment targets, chronological manual-edit audits, event
|
||||
triggers, and broader execution observation remain incomplete. Current automatic
|
||||
review respects input budgets and the rolling window; deleting reports does not
|
||||
reset processing progress or rebuild graphs. See the
|
||||
[Conversation Supervision PRD](./docs/features/conversation-supervision/prd.md).
|
||||
- [ ] **Batch runs and comparison lab** (planned): Compares model, Prompt,
|
||||
role, and workflow configurations in batches and summarizes quality,
|
||||
duration, tokens, cost, failure rate, and artifact differences.
|
||||
- **Story Graph read tools (Desktop 0.15.9 / Agent 0.15.3)**: Agents can search saved work,
|
||||
read current claims or timelines, and page through source evidence. Supervisor
|
||||
enablement and Runtime assignment control discovery and calls, including old
|
||||
sessions. Model, local Harness, and local/remote OpenCode and Continue share
|
||||
the desktop reader. Queries make no model calls and do not infer replacement
|
||||
from newer reviews. Shared source-version facts, stories, and experiences are
|
||||
available across permitted scopes; `as_of` remains deferred. Keyword matching
|
||||
and tool guidance support model-selected lookups without automatic full-graph
|
||||
context injection; experiences remain advice, not instructions. See the
|
||||
[design and validation](./docs/features/conversation-supervision/story-graph-mcp-design.md).
|
||||
- [ ] **Temporal memory and fact-conflict detection** (planned): Adds validity
|
||||
periods, current/expired/conflicting fact detection, fact checking, and
|
||||
evidence tracing to memory and the knowledge graph.
|
||||
@@ -688,9 +944,12 @@ records are listed separately and do not introduce another feature status.
|
||||
- [x] **WeCom and DingTalk connections**: Main-only encrypted settings,
|
||||
read-only environment-variable overrides, connection tests, dynamic
|
||||
enable/disable, sender scope, and status diagnostics.
|
||||
- [x] **Managed local-model download source**: Platform Features / General
|
||||
selects ModelScope (default) or Hugging Face globally for future speech-input
|
||||
and OCR model downloads. A source without a complete verified file is
|
||||
- [x] **Managed local-model download source**: The Model Connections header shows
|
||||
the current source and a secondary button, right-aligned on the same row as the
|
||||
model-type tabs below Save. Its dialog selects ModelScope (default) or Hugging Face
|
||||
globally for future managed local-model downloads; changes save only on Confirm,
|
||||
Cancel leaves the saved source unchanged, and errors allow retry. Embedding, speech,
|
||||
and OCR source links open this dialog directly. A source without a complete verified file is
|
||||
explicitly unavailable and never silently falls back or combines files.
|
||||
- [x] **Optional local speech-model management**: Model weights are not bundled.
|
||||
Provides verified downloads, progress and cancellation, source links, ZIP or
|
||||
@@ -719,12 +978,101 @@ records are listed separately and do not introduce another feature status.
|
||||
|
||||
### Open source, builds, and releases
|
||||
|
||||
- Current source candidates are Desktop `0.13.5` and Agent `0.13.0`, with
|
||||
OpenCode pinned to `1.18.29`. Publication status follows the independent
|
||||
- The current Desktop candidate is `0.15.9`; Agent candidate `0.15.3` requires Desktop `0.15.9`, with
|
||||
OpenCode pinned to `1.18.29` and Continue to `1.5.47`. Publication status follows the independent
|
||||
Desktop and Agent release channels.
|
||||
- Agent `0.13.0` adds remote image-tool integration and shared Runtime processes
|
||||
with conversation recovery fixes. Its packages require Desktop `0.13.5`;
|
||||
- Desktop `0.15.9` adds shared review progress, stories, experiences, and spiral
|
||||
graph navigation, with schema 56 backup/rollback requirements and possible
|
||||
model costs. Conversation/message windowing, Markdown caching, read-only
|
||||
Workers, incremental activity writes, and checkpoint event batches reduce
|
||||
repeated work without promising universal speedups. In-page find excludes
|
||||
unmounted rows, rapid scrolling can show placeholders, and unavailable Workers
|
||||
can fall back to synchronous reads. Execute configuration changes no longer
|
||||
require duplicate native confirmation. Electron is 44.5.1. Four current-source
|
||||
real Host model requests passed; exact-candidate CI and packaging remain required.
|
||||
See the [preparation record](./docs/development/release-preparation-0.15.9.md).
|
||||
- Desktop `0.15.8` and Agent `0.15.2` add Execute-only saving of conversation
|
||||
images to absolute local or Host paths, with explicit overwrite and no WebP
|
||||
conversion. MCP settings list image tools without promising availability in
|
||||
every mode. Notes gain new-entry cancellation, edit-focus restoration and full
|
||||
available content width. Missing browser and frosted-appearance preferences
|
||||
default to enabled; saved choices remain unchanged. Schema stays at 52.
|
||||
Real Linux x64 Host tool-layer evidence and remaining exact-candidate CI and
|
||||
package checks are separated in the
|
||||
[preparation record](./docs/development/release-preparation-0.15.8.md).
|
||||
- Desktop `0.15.7` reuses completed streaming Markdown blocks and conversation
|
||||
derived values without changing the default streaming cadence. Activity records
|
||||
load on entry, refresh every 15 seconds while visible, and offer manual refresh.
|
||||
Local custom MCP discovery failures are isolated per service. Managed model
|
||||
source selection moves to a confirmation dialog in Model Connections, and Device
|
||||
Sharing becomes an opt-in test feature. Schema remains 52. Exact-candidate CI
|
||||
and packaging are still required; component benchmarks are not full-App results.
|
||||
See the [preparation record](./docs/development/release-preparation-0.15.7.md).
|
||||
- Desktop `0.15.6` adds optional, persisted frosted-glass header/checklist surfaces
|
||||
(off by default, without OS desktop transparency), reduces repeated chat renders,
|
||||
and supports direct previews for draft/queued images. Settings are grouped with
|
||||
right-aligned switches, and note headers/editor spacing are tighter.
|
||||
Local OpenCode retries explicit transient MCP transport failures once after
|
||||
cleanup and retires uncertain names; it does not replay prompts or tool calls.
|
||||
Redacted MCP diagnostics support feedback. Schema 52 adds execution-statistics
|
||||
indexes; back up the database and notes before upgrading.
|
||||
See the [preparation record](./docs/development/release-preparation-0.15.6.md).
|
||||
- Desktop `0.15.5` updates Smart heartbeat and incremental review, preserves
|
||||
cancelled-turn history, floats checklist details, and exits note editing after
|
||||
saving. It also adds manual note tags with AND filtering and rename/merge/delete,
|
||||
a persistent two-pane note layout with a full-width overview, sticky workspace
|
||||
file controls, cached OpenCode terminal dependencies, and shorter MCP names.
|
||||
Schema 51 adds tag storage alongside the migration of pending legacy memory
|
||||
proposals into unconfirmed conventions; back up the database and adjacent notes before upgrading.
|
||||
See the [preparation record](./docs/development/release-preparation-0.15.5.md).
|
||||
- Desktop `0.15.4` introduces layered Runtime/model choices and separate
|
||||
system-task usage, and reduces duplicate Runtime instructions and history.
|
||||
Schema 49 migrates project/conversation choices; rollback requires the complete
|
||||
pre-upgrade database and adjacent notes backup. Agent `0.15.1` requires Desktop
|
||||
`0.15.4` and fixes remote Continue history wrapping without changing locked
|
||||
Runtime versions. See the [0.15.4 preparation record](./docs/development/release-preparation-0.15.4.md).
|
||||
- Desktop `0.15.3` restores local direct-model manual compaction, retains tool
|
||||
authorization for the request lifetime, clarifies cancellation and sidebar/note
|
||||
controls, avoids redundant startup/storage work, and trims duplicated package
|
||||
dependencies and development files. It does not claim a proven overall rendering
|
||||
speedup. Office live editing remains a design, not a delivered capability.
|
||||
See the [0.15.3 preparation record](./docs/development/release-preparation-0.15.3.md).
|
||||
- Desktop `0.15.1` carries forward the unpublished `0.15.0` Obsidian integration, conversation capture into Magic
|
||||
Notes, and preview local native clients, plus knowledge, Supervisor, and UI
|
||||
fixes. Schema 48 adds conversation-source metadata; older clients require a
|
||||
complete pre-upgrade backup for rollback. Agent `0.15.0` requires Desktop
|
||||
`0.15.2`, retains Node `24.19.0`, and updates remote telemetry opt-out and
|
||||
disabled Continue update-check behavior without adding remote client shortcuts.
|
||||
- Desktop `0.15.1` completed all six native release targets and GitHub/OSS publication.
|
||||
Desktop `0.15.2` adds read-only Story Graph tools and a fully editable
|
||||
Supervisor follow-up message, simplifies note capture, and improves exit diagnostics.
|
||||
Remote Story Graph requires the paired Agent `0.15.0`; planned Host and cloud
|
||||
management applications remain unimplemented. Current candidate evidence is in the
|
||||
[0.15.2 preparation record](./docs/development/release-preparation-0.15.2.md).
|
||||
- Desktop `0.13.15` carries forward the unpublished `0.13.14` changes to paged Supervisor reviews and resume behavior,
|
||||
native ripgrep searches, to-do layouts, and explicit remote follow-up after an
|
||||
uncertain result. Database schema 47 requires a complete pre-upgrade backup
|
||||
for rollback to older clients.
|
||||
- Desktop `0.13.16` separates structure upgrades from legacy history reclamation,
|
||||
removes fixed-duration review pauses, and clarifies Supervisor layouts.
|
||||
It adds no schema change over `0.13.15` and requires no Agent update.
|
||||
- Agent `0.13.3` reclaims idle Runtimes and retires displaced Agents after existing
|
||||
work drains. Its packages require Desktop `0.13.12`;
|
||||
update Desktop first, then the Host environment. Node remains `24.19.0`.
|
||||
- Agent `0.13.4` waits for model delivery acknowledgements before completion,
|
||||
preserves completed results when idle Runtime cleanup fails, and removes verified
|
||||
inactive Agent payloads and owned orphan processes after environment updates.
|
||||
History and active or uncertain installations are retained. Its independent
|
||||
packages require Desktop `0.13.14` or later. Since `0.13.14` did not publish,
|
||||
upgrade to Desktop `0.13.15` first, then update the Host.
|
||||
- Release preparation requires the full test suite, notes verification,
|
||||
typecheck, and lint. Candidate main-branch CI, its production build, native
|
||||
packaging, and publication verification remain separate acceptance steps.
|
||||
No local production build, packaging, or LoongArch preview is requested.
|
||||
Current validation status is recorded in the
|
||||
[release preparation record](./docs/development/release-preparation-0.15.0.md).
|
||||
- Desktop `0.13.12` and Agent `0.13.3` have completed their native release jobs;
|
||||
this does not replace development-time real-Host scenario coverage.
|
||||
- Validation records remain separate from implementation status. Current-source
|
||||
storage validation covers real local and Linux x64 Host tool workloads,
|
||||
concurrent projects/conversations, cancellation, and lossless database migration.
|
||||
@@ -735,10 +1083,14 @@ records are listed separately and do not introduce another feature status.
|
||||
covered by those records. The candidate still requires main CI and
|
||||
native release packaging. See the [Runtime evidence](./docs/features/assistant-workbar/progress.md)
|
||||
and [image-tool evidence](./docs/features/conversation-media-generation/progress.md).
|
||||
- Candidate checks on 2026-09-15 passed: bilingual release notes, `npm test`
|
||||
(4,293 passed, 67 skipped), `npm run typecheck`, and `npm run lint`.
|
||||
This metadata/documentation preparation made no new real model calls and did
|
||||
not rerun Host scenarios or build local production packages.
|
||||
- Before release preparation, `9836a4c` passed 4,858 local tests with 67 skipped,
|
||||
full typecheck and lint; real-provider canvas analysis remains unverified.
|
||||
Its three native Agent CI builds passed. Desktop CI run `35485936807`
|
||||
passed 4,875 tests but failed eight Electron tests before UI assertions because
|
||||
the Linux sandbox helper was not configured. The candidate configures the
|
||||
helper and runs tests under Xvfb without disabling sandboxing. Candidate-local
|
||||
validation, main-branch CI and native tag packaging are tracked separately;
|
||||
no local production build or package is part of release preparation.
|
||||
- [x] **0BSD open-source license**: Original code can be freely used, copied,
|
||||
modified, distributed, and commercialized. Third-party components and
|
||||
resources retain their own licenses.
|
||||
@@ -761,6 +1113,17 @@ records are listed separately and do not introduce another feature status.
|
||||
|
||||
### Open interfaces, team collaboration, and remote execution
|
||||
|
||||
- [x] **Remote Continue delivery**: Current source supports a
|
||||
managed CN 1.5.47 package, Agent HTTP-to-ACP adaptation, model routing, project
|
||||
selection, and native checklists. Linux x64 development packages passed real
|
||||
model, cancellation and reconnection checks. Default packages include OC/CN;
|
||||
Windows desktop-to-Linux checks cover installation, checklist updates, long lists,
|
||||
cancellation and restart recovery. Execute session MCP delivery passed real
|
||||
text-model Host checks with a substituted image service, not real image generation.
|
||||
Published Agent `0.13.4` requires Desktop `0.13.14` or later; publication and native
|
||||
platform acceptance remain separate from Linux development evidence. See the
|
||||
[validation record](./docs/features/remote-host/runtime-checklist-validation.md).
|
||||
|
||||
- [x] **Remote task delegation**: Enabled only after the user explicitly
|
||||
configures an endpoint and token, uses HTTP(S) according to the global
|
||||
private-network compatibility setting, and writes results to a persistent
|
||||
@@ -788,9 +1151,15 @@ records are listed separately and do not introduce another feature status.
|
||||
offline GoodBuddy transfer release acceptance. Until these gates pass, this
|
||||
path must not be described as published or as having passed formal release
|
||||
acceptance.
|
||||
- [ ] **Multi-cloud remote sandbox Agents** (planned): Manages dedicated Linux
|
||||
sandboxes through cloud-provider APIs and SSH Agents. Credentials remain in
|
||||
Main, and high-risk control-plane operations receive separate confirmation.
|
||||
- [ ] **Host and cloud environment management** (planned): Move host management
|
||||
into App Center with a list/detail workspace. A sibling cloud application
|
||||
reuses host connections and remote execution. The first-version adapter plan
|
||||
covers AWS, Azure, Alibaba Cloud, Tencent Cloud, VMware vCenter / vSphere,
|
||||
and ZStack, delivered in batches with real instance discovery, import, and
|
||||
execution acceptance. Instance creation, start/stop, deletion, and network
|
||||
orchestration follow later. Provider APIs are not implemented; existing cloud
|
||||
servers can already be added manually when SSH requirements are met. See the
|
||||
[product scope](./docs/features/remote-host/prd.md).
|
||||
|
||||
## Roadmap Principles
|
||||
|
||||
|
||||
+70
-24
@@ -16,52 +16,66 @@
|
||||
### 桌面基础、工作空间与上下文
|
||||
|
||||
- [x] **跨平台桌面应用**:支持 Windows、macOS、Linux,以及 `x64`、`arm64` 发布目标。
|
||||
- [x] **信创软硬件覆盖**:正式 Linux 安装包覆盖国产 `x64` 与 `arm64` 架构,可用于统信 UOS、银河麒麟及海光、兆芯、鲲鹏、飞腾等兼容环境。龙芯 LoongArch 另行提供 `loong64` 实验预览版,不属于正式发布矩阵,也未纳入自动更新;`0.13.5` 不构建该预览。这里的覆盖范围不等同于厂商或整机兼容认证,具体状态与限制见[龙芯预览版构建说明](./docs/development/loongarch-preview-build.md)。
|
||||
- [x] **信创软硬件覆盖**:正式 Linux 安装包覆盖国产 `x64` 与 `arm64` 架构,可用于统信 UOS、银河麒麟及海光、兆芯、鲲鹏、飞腾等兼容环境。龙芯 LoongArch 另行提供 `loong64` 实验预览版,不属于正式发布矩阵,也未纳入自动更新;仅在明确要求时构建。这里的覆盖范围不等同于厂商或整机兼容认证,具体状态与限制见[龙芯预览版构建说明](./docs/development/loongarch-preview-build.md)。
|
||||
- [x] **可配置全局快捷唤起**:在“平台功能 / 通用设置”中启停或录制 Electron accelerator;默认保留 `CommandOrControl+Shift+Space`,冲突或保存失败时继续使用上一组已注册快捷键,并显示可处理的状态。
|
||||
- [x] **Projects、独立对话与会话分支**:按项目隔离上下文,管理会话、附件和 Git 工作区变更;本地会话可在稳定状态下复制当前聊天内容到独立分支,分支持续显示来源徽标且不复制 Task、队列或成果归属;项目选择器区分本地、托管 SSH 与远程消息通道项目,托管 SSH 项目按 Host 分组并在 Host 标题显示真实 Agent 连接状态,项目行只保留远端路径。
|
||||
- [x] **跨项目会话活动**:统一入口汇总各项目正在运行和需要处理的对话,保留仍有活动的较早会话。锚定的“项目 → 会话”菜单支持键盘导航和精确打开对话,不再使用整块活动弹窗;空闲时隐藏摘要,长会话子菜单独立滚动,项目选择器继续显示活动数量。
|
||||
后台会话成功完成后,在本次客户端运行期间保留完成提示,直到对应聊天可见且加载完成;提示不跨重启保留。
|
||||
- [x] **会话置顶与完整历史**:常用会话可置顶,重启后保留。列表和搜索不再限于最近 100 个会话,连续发送不再截断超过 500 条的已保存历史;模型上下文限制仍有效,已丢失的历史无法恢复。
|
||||
置顶与非置顶分组按最近一条消息的创建时间排序,流式文本和工具进度更新不再反复改变位置。
|
||||
- [x] **可选桌面任务通知**:“平台功能 / 通用设置”提供默认开启、保存后立即生效的系统任务提醒开关,应用内提示独立保留;通用设置按快捷键、会话与通知、工作目录分组。
|
||||
- [x] **按需上下文帮助**:页面和设置中的补充说明使用标题或字段旁的共享帮助入口,支持鼠标与键盘;关键操作后果和错误提示保持可见。
|
||||
- [x] **紧凑会话控件**:输入选项集中在紧凑设置面板,Runtime、工作模式、发送和队列操作保持可用;会话搜索提供内嵌清除按钮,清除后恢复当前范围列表并把焦点返回输入框。
|
||||
- [x] **会话历史按需加载**:列表只加载轻量摘要,不再一次读入全部会话的过程元数据;已打开和活动会话保留完整详情,搜索、复制、导出、续聊和远程待答问题保持可用。图片合并不会重新发送未变化消息,已确认保存的空闲详情随现有视图缓存释放。详见[历史读取与保留规则](./docs/features/assistant-workbar/execution-history-storage.md#会话列表读取与前端保留)。
|
||||
- [x] **文件、截图、窗口、剪贴板上下文**:用户明确选择后才加入模型上下文。聊天输入框支持 `Ctrl+V` 粘贴一个或多个本地支持文件并自动添加为附件,沿用附件按钮的解析进度与限制;普通文本和截图仍可直接粘贴。详见[聊天附件规则](./docs/features/document-processing/prd.md#321-聊天附件入口)。
|
||||
- [x] **图片型 PPTX 文字识别**:配置本地 OCR 并选择启用 OCR 的解析方式后,可识别内嵌 PNG、JPEG 和 WebP 图片,保留原生文字与幻灯片定位。OCR 在没有活动或排队任务时空闲 60 秒自动释放模型内存,下次按需重载;快速文本/索引模式仍跳过 OCR。详见[解析行为与真实文件验证](./docs/features/document-processing/chat-attachments-technical-design.md)。
|
||||
- [x] **HTTP OCR 与附件持久预览**:在“设置 / 文档解析”配置 HTTP PaddleOCR-VL,供聊天文档和知识库导入共用。保存原件与插图,提供正文/图片/详情预览、显式图片文字提取,以及选图加入指定会话草稿。普通图片默认走图片输入,发送与入队检查实际模型和 Runtime。Windows、直连视觉输入和共享 Linux Host 托管 OpenCode 已有真实验证;部署高级 OCR 模型及其他 Runtime 组合需分别测试。本机推理监控只管理本地服务,不新增应用中心入口,详见[范围与证据](./docs/features/document-processing/README.md)。
|
||||
- [x] **工作区单文件差异**:变更文件可分别查看已暂存和未暂存差异,覆盖删除、重命名和未跟踪文件;刷新会重读当前差异、当前浏览目录和已展开目录,超过 50 项变更可继续加载。
|
||||
- [x] **工作区文件与 Git 管理**:支持文件和目录的新建、重命名、移动、删除、属性查看,以及本地/远程分支搜索与切换、新建分支、显式 Fetch、提交历史和单文件提交差异。工具按文件/Git 视图分组,分支搜索使用统一紧凑表单,子目录保留文字导航而根目录不再显示孤立图标。提交历史贴底,首次展开占 Git 内容区一半,支持调整比例、独立滚动和统一刷新;切换项目自动检测 Git,不沿用其他项目状态。移动限于工作区内,删除须确认,分支冲突不自动 stash 或丢弃改动;托管 SSH 操作需要 Agent `0.11.23`。
|
||||
- [x] **富文本回答**:支持 GitHub Flavored Markdown、LaTeX 数学公式、受控 Mermaid 图表和会话内 HTML 静态预览。Agent 完成回复后,完整 HTML 与 HTML 代码块可在原位置预览,并通过图标按钮查看源码或打开全屏预览;该能力默认开启,可在“平台功能 / 通用设置”关闭,预览不执行脚本、联网、表单提交或窗口操作。
|
||||
- [x] **富文本回答**:支持 GitHub Flavored Markdown、LaTeX 数学公式、受控 Mermaid 图表和会话内 HTML 静态预览。Mermaid 大图放大后可滚动到各个边缘,并可导出完整 PNG,不受当前缩放和拖动位置影响。Agent 完成回复后,完整 HTML 与 HTML 代码块可在原位置预览,并通过图标按钮查看源码或打开全屏预览;该能力默认开启,可在“平台功能 / 通用设置”关闭,预览不执行脚本、联网、表单提交或窗口操作。
|
||||
- [x] **AI 回复与完整会话复制**:已完成的 AI 回复可从消息底部复制不含推理、工具日志和引用元数据的 Markdown 原文;完整会话复制复用同一条经过 Preload/Main 校验的剪贴板路径。
|
||||
- [x] **助手工作栏、多终端与可调布局**:工作栏开关位于明暗主题按钮旁;右侧工作栏使用持久“+”能力目录和应用 Tab,任务中心与工作区为不可关闭的单实例,浏览器可打开多个独立实例。任务中心可切换当前项目、全局任务和全部项目,范围随布局保存。用户可为当前本机或托管 SSH 项目打开多个独立终端,获得有界输出、调整尺寸、结束与显式重连。关闭终端 Tab 会结束 Shell,应用重启只恢复已结束的 Tab 描述,不自动重启 Shell。主侧栏和魔法笔记列表支持鼠标与键盘调宽,魔法笔记列表可隐藏并记住布局,窄宽度下自动堆叠。
|
||||
- [x] **助手工作栏、多终端与可调布局**:工作栏开关位于明暗主题按钮旁;右侧工作栏使用持久“+”能力目录和应用 Tab,任务中心与工作区为不可关闭的单实例,浏览器可打开多个独立实例。任务中心可切换当前项目和所有项目,范围随布局保存;无项目任务在所有项目中标注“未绑定项目”。用户可为当前本机或托管 SSH 项目打开多个独立终端,获得有界输出、调整尺寸、结束与显式重连。关闭终端 Tab 会结束 Shell,应用重启只恢复已结束的 Tab 描述,不自动重启 Shell。主侧栏和魔法笔记编辑器的 AI 评论栏支持鼠标与键盘调宽;AI 评论栏记住显示状态及宽度,窄窗口下移至编辑器底部。
|
||||
- [ ] **项目 Agent Space**(规划中):在 Project 中统一角色、知识、Skills/MCP、模型、审批策略、预算和超时,并支持模板复用。
|
||||
- [ ] **应用导航与系统工具快捷入口**(仅设计):规划固定系统入口、内置应用显隐和打开现有工作栏的底部工具按钮;尚未实现新界面。详见[设计文档](./docs/features/application-tool-navigation/README.md)。
|
||||
- [ ] **通用助手工作栏与执行空间后续能力**(规划中):在现有工作栏和多终端基础上继续加入监督、统一 Runtime、受管进程、静态安全 HTML 预览、可固定目标的工作区/浏览器/成果实例、底部停靠与独立窗口。Task Center 保持 Task 的单例索引,附件与知识库继续由会话输入区管理,未来的记忆与历史执行上下文归入关联 Task。详见 [Feature PRD](./docs/features/assistant-workbar/prd.md)。
|
||||
- [x] **应用中心与导航**:底部“应用”和“设置”为显示文字的等高按钮,中间使用弱竖分隔线。“应用”带向上箭头,点击后向上展开轻量锚定菜单,无模态遮罩,只列已启用应用,不按打开历史或常驻筛选。点击应用行关闭菜单,本机推理监控打开保留底层工作区的独立 Modal,其余应用打开主内容区;底部“管理应用”使用 `LayoutGrid` 图标,打开标题仍为“应用中心”的可搜索列表及设置详情,布局与交互见 [UI 设计](./docs/features/application-tool-navigation/ui-design.md)。五个应用行均支持上下移动和拖动排序,不受启用或常驻状态限制;列表、菜单和经过过滤的侧栏共用一份保存顺序。知识库始终启用并常驻,提供“打开”和排序;监督者、魔法笔记和本机推理监控可设置启用与常驻。设备共享在测试阶段默认关闭,提供启用开关,不提供常驻开关。监督者沿用智能心跳身份,缺失启用设置时默认关闭,已明确保存的选择保留;关闭后保留计划和历史,拒绝新回顾并隐藏侧栏反馈,在途运行可完成。启用应用不会自动创建计划。旧应用顺序补齐默认项并保留已有相对顺序。设置持久化后的事件同步各入口,包括工具配置写入;重开中心刷新期间锁定修改,新快照覆盖迟到的旧读取。`local-inference` Modal 直接显示服务列表,不展示任务历史或外部连接;向量服务显示独立进程 CPU 与工作集内存,ASR/OCR 说明共享进程无法独立统计的原因。ASR 就绪状态未知,不从活动请求推断运行中。受支持的服务操作保留影响确认,失败后刷新列表并要求重新确认;TTS 显示不可用。取消中的推理在 Worker 确认或退出前仍计入活动任务及服务停止影响。实现已接入生产 App;验收记录分别列出完整 App、组件 fixture、真实本地引擎和剩余平台/安装包覆盖,不以历史失败代替当前全量结果,详见[验证进度](./docs/features/application-tool-navigation/progress.md)。
|
||||
- [x] **本地设备共享元数据**(源码已实现):测试阶段默认关闭,先在应用中心启用,再从应用菜单或应用中心打开;已明确保存的启用选择保留,关闭会退出页面,但不清除目录或撤销已发布记录。可保存服务地址和设备名称、注册本机、浏览服务返回的目录、发布能力或知识元数据并撤销本机 ID 的条目。默认服务为 `http://127.0.0.1:8787`。服务未认证,ID 只用于识别;服务器来源不上传或同步内容,知识搜索/读取/下载标志是独立声明,不提供正文访问或远程执行。详见[范围与验证](./docs/features/device-sharing/README.md)。
|
||||
- [ ] **私有化应用市场**(规划中):在应用管理基础上提供组织自己的目录与应用分发。当前只管理内置应用,尚无市场浏览或安装能力,详见 [FR-15](./docs/features/application-tool-navigation/prd.md#fr-15-私有化市场与-yaml-交换后续)。
|
||||
- [ ] **通用助手工作栏与执行空间后续能力**(规划中):在现有工作栏和多终端基础上继续加入更完整的执行监督、统一 Runtime 监控、受管进程、可固定目标的工作区/浏览器/成果实例、底部停靠与独立窗口。Task Center 保持 Task 的单例索引,附件与知识库继续由会话输入区管理,未来的记忆与历史执行上下文归入关联 Task。详见 [Feature PRD](./docs/features/assistant-workbar/prd.md)。
|
||||
- [ ] **ShareServer Office 协同编辑**(设计中):通过可选 ShareServer 集成 ONLYOFFICE Docs,在助手工作栏中以文件名打开多个文档 Tab,支持 DOCX、XLSX、PPTX 的人工编辑、保存、撤销、源文件冲突保护和后续 AI 选区修改。Office 正文会由所选 ShareServer 和编辑引擎处理;Desktop 不内置或启动 Document Server,未配置服务时不宣称离线编辑。详见[设计文档](./docs/features/office-document-editing/README.md)。
|
||||
|
||||
### Agent Runtime 与模型连接
|
||||
|
||||
- [x] **子任务历史去重与空间回收**:本机与托管 SSH 子任务事件只存变化,不再反复保存完整进度。首次升级自动转换旧记录并回收空间,显示进度且支持退出重试;保留聊天、执行详情、结果和远程事件去重。已随 0.13.2 发布的 schema 35 补修还会整理升级到 0.13.1 后重复写入的工具块,不删除事件;终态及时释放写入缓存,远程重复重放不重建已释放的缓存。旧客户端不能打开升级后的数据库,具体规则见[执行记录存储与升级回收](./docs/features/assistant-workbar/execution-history-storage.md)。
|
||||
- [x] **子任务历史去重与空间回收**:本机与托管 SSH 子任务事件只存变化,不再反复保存完整进度。首次升级自动转换旧记录并回收空间,显示进度且支持退出重试;保留聊天、执行详情、结果和远程事件去重。已随 0.13.2 发布的 schema 35 补修还会整理升级到 0.13.1 后重复写入的工具块,不删除事件;终态及时释放写入缓存,远程重复重放不重建已释放的缓存。普通结构升级不重新转换历史或回收正常空闲页,启动页区分结构更新、旧内容转换与实际空间回收。旧客户端不能打开升级后的数据库,具体规则见[执行记录存储与升级回收](./docs/features/assistant-workbar/execution-history-storage.md)。
|
||||
|
||||
- [x] **直连模型 Runtime**:支持问答、知识总结、受控工具执行、图像生成,以及通过支持 OpenAI 兼容图片编辑接口的服务商进行参考图编辑。连续请求自动复用最近一次成功生成的图片和文字历史,已保存会话重开后仍可继续修改,本轮显式附图优先;上游明确不支持编辑时继续按文字生成,仅在回复底部提示未使用参考图。编辑使用 multipart 上传图片,结果仍只接受经过校验的内联图片,不下载服务商返回的图片 URL。详见[图片生成与连续修改](./docs/features/image-generation/README.md)。
|
||||
- [x] **当前会话图片工具**:为图片模型开启会话调用后,支持工具调用的聊天模型可在 Execute 中生成图片、修改上传图片或历史成果,无需另开会话或手动切换图片模型。直连文本模型、本地 OpenCode、Continue、DeepSeek Harness 和托管远程 OpenCode 复用统一服务,Runtime 分配由模型设置派生;图片请求可能产生费用,编辑需服务商支持。详见[实现与验证记录](./docs/features/conversation-media-generation/progress.md)。
|
||||
- [x] **跨项目 Runtime 进程复用**:兼容配置的本地 OpenCode 和 DeepSeek Harness 共享重型进程,托管 OpenCode 会话在同一 Host 上复用进程。项目适配器及会话的工作区、模型、工具、问答和取消仍独立路由;远程待答问题和任务状态可在重连或桌面重启后恢复,不重发已接受请求。新的远程行为需要 Desktop `0.13.5` 与 Agent `0.13.0` 配套更新,详见[设计与实测](./docs/features/assistant-workbar/runtime-process-reuse-technical-design.md)。
|
||||
- [x] **直连模型编程 Agent**:本机直连文本模型可在 Execute 模式运行平台 Shell,并可按父请求模式、模型、工作区和能力范围委派一层编程 Subagent;OpenCode、Continue、DeepSeek Harness 和托管 SSH 不重复注入这两个工具。Windows 本机命令与真实模型“修改、测试、修复、复核”闭环已通过,macOS 与 Linux 真机命令仍待对应平台验收。
|
||||
- [x] **高效直连模型工作区工具**:直连模型使用随包 ripgrep 紧凑地发现文件和搜索内容,按行分页读取大型 UTF-8 文件,并在 Execute 中应用多文件补丁。Ask 可以搜索和读取,但不能应用补丁或运行命令;用户无需另行安装 ripgrep。可处理的读取或搜索错误允许模型调整参数后继续,部分搜索结果保留并明确提示覆盖不完整。
|
||||
- [x] **长上下文与工具输出分页**:历史消息、长回复和文档解析文本不再采用旧的固定截断限制。直连模型可分页续读已保存的命令和 Subagent 输出;所选模型的上下文窗口及传输边界仍然有效。
|
||||
- [x] **高效直连模型工作区工具**:直连模型使用随包 ripgrep 的原生参数发现文件和搜索内容,按行分页读取大型 UTF-8 文件,并在 Execute 中应用多文件补丁。Ask 限于工作区只读搜索和读取,Execute 按当前账号权限搜索;用户无需另行安装 ripgrep。搜索保留原生输出与退出码,大结果通过 `output_read` 完整续读,可处理的参数错误允许模型调整后继续。
|
||||
- [x] **长上下文与工具输出分页**:历史消息、长回复和文档解析文本不再采用旧的固定截断限制。直连模型可分页续读已保存的搜索、命令和 Subagent 输出;所选模型的上下文窗口及传输边界仍然有效。
|
||||
- [x] **OpenCode 与 Continue**:使用隔离子进程、环境变量白名单、统一配置、取消、启动与控制请求时限、有界流式输出和活动记录;共享进程回收逻辑保留 Windows 完整进程树终止,并对采用独立进程组的 POSIX 子进程执行组回收。聊天状态检查使用一次性探测 Runtime 并在返回后立即回收,不进入按模型与项目隔离的执行 Runtime 缓存;本机 GoodBuddy 管理的 OpenCode 在该被动检查中只确认所选路径和模型凭据,不启动用后即毁的 Server,设置中的显式连接测试和原生能力清单仍执行完整启动及健康检查,原生能力清单随后与首次实际请求复用同一个执行 Runtime,不再重复冷启动;执行 Runtime 仍按项目复用,但 OpenCode 配置依赖和按内容摘要生成的 Skill 快照在所有本机项目间全局复用,会话数据、工具输出及请求级 MCP 状态继续隔离。本机 GoodBuddy 管理的 OpenCode 只对同一会话内的请求保持顺序,不同会话即使属于同一项目也可并行,不同项目同样可并行;请求级动态 MCP 通过默认通配禁用和当前请求精确放行保持隔离。托管的本机和远端 OpenCode 跳过界面未使用的自动 Git 快照,避免同步差异计算拖住会话,文件工具、子代理与工作区 Git 差异保留;Continue 持续排空不用展示的宿主 stdout,防止控制台输出填满管道。这些调整随新启动的 Runtime 生效,不改写外部 OpenCode Server 配置。OpenCode 与 Continue 不再按固定工具调用数或活动数中止单次运行,已观察到的工具与 Subagent 活动全部保留在本地会话,不再在原生任务继续执行时丢弃后续详情。交互提问只由前台对话回答,定时任务、远程通道和委派等后台执行遇到提问时会立即失败并提示改为前台运行,避免无限等待。本机 OpenCode 启动时直接使用已选择的绝对路径,不预先检查文件或运行 `--version`,路径失效时由实际启动返回错误;托管 Linux ARM Host 激活 Runtime 时复用准备阶段已验证的 registry 和 manifest,不重新哈希或检查完整 OpenCode 二进制。
|
||||
- [x] **托管 SSH OpenCode 闭环(技术预览)**:该能力由“设置 > 平台功能”中的独立“远程项目(技术预览)”页签控制并默认关闭;关闭时不影响本地项目、普通桌面功能或桌面发布。支持 Linux x64、Linux arm64 和 macOS arm64 Host,不支持 Intel Mac;通过 SSH 按需启动 detached Agent,不要求开机服务。Ask 在 Runtime 工具边界保持只读,Execute 使用所选 SSH 账号的完整权限。Agent 持有 Prompt、模型和工具轮次、Runtime 进程及语义 transcript,Desktop 断开后重新 Attach 原操作而不重发 Prompt;模型凭据仅在当前操作内存中使用,不写入 Renderer、SSH 参数、环境或磁盘。设置页按平台和架构显示包状态,用户显式下载、更新或导入/导出包含 Agent、固定 Node 和 OpenCode 的签名 `.gbagent`;三个目标共用一个签名 catalog,继续校验签名和 SHA-256。首次发布混合 catalog 前需先升级 Desktop,旧版 Linux-only 目录读取器无法解析包含 Darwin 的目录。macOS 原生安装、生命周期、Attach、真实 Ask/Execute 和独立进程组工具取消已在真实 Host 验证;这不等于新版本已经发布。详细运行边界和 Linux 历史回归证据见[远程主机技术设计](docs/features/remote-host/technical-design.md),正式发布状态以 Agent 与 Desktop 的独立发布渠道为准。
|
||||
- [x] **托管 SSH OpenCode 与 Continue**:该能力由“设置 > 平台功能”中的独立“远程项目”页签控制并默认关闭;关闭时不影响本地项目、普通桌面功能或桌面发布。支持 Linux x64、Linux arm64 和 macOS arm64 Host,不支持 Intel Mac;通过 SSH 按需启动 detached Agent,不要求开机服务。Ask 在 Runtime 工具边界保持只读,Execute 使用所选 SSH 账号的完整权限。Agent 持有 Prompt、模型和工具轮次、Runtime 进程及语义 transcript,Desktop 断开后重新 Attach 原操作而不重发 Prompt;模型凭据仅在当前操作内存中使用,不写入 Renderer、SSH 参数、环境或磁盘。当前 Agent 可以常驻,更新替换的旧 Agent 排空活动任务后退出。任务完成后回收空闲 Runtime,共享进程仍有任务或待答问题时保留;下一轮重建 Runtime 并恢复对话历史。设置页按平台和架构显示包状态,用户显式下载、更新或导入/导出包含 Agent、固定 Node、OpenCode 和 Continue 的签名 `.gbagent`;三个目标共用一个签名 catalog,继续校验签名和 SHA-256。Continue 需要更新后的联合包与配套 Desktop。首次发布混合 catalog 前需先升级 Desktop,旧版 Linux-only 目录读取器无法解析包含 Darwin 的目录。历史 macOS OpenCode 原生安装、生命周期、Attach、真实 Ask/Execute 和独立进程组工具取消已在真实 Host 验证,不代表 Continue 的同等实机覆盖。当前 Agent 源码锁为 `0.15.3`,配套 Desktop 候选为 `0.15.9`;详细运行边界和 Linux 历史回归证据见[远程主机技术设计](docs/features/remote-host/technical-design.md),正式发布状态以独立发布渠道为准。
|
||||
- [x] **SSH Host 手动环境准备源码链路**:Host Key、认证和系统探针成功后先保存 Host,并只读探测共享 Agent/Runtime;保存 Host、打开项目都不自动安装。Host 卡片只有一个按版本事实显示“安装远程环境”“更新远程环境”或“重新安装”的主按钮,次级 SegmentedControl 选择默认且不持久化的“自动”、Host 下载或 GoodBuddy 传输;“版本匹配”badge 不等同环境健康。自动模式只在 operation/prepare 前探测并择一,显式选择保持有效,任何 prepare、commit 或 adoption 失败都不跨 acquisition 自动 fallback。两种方式把同一签名 compound `.gbagent` 交付到固定 staging 后,共用 control-plane prepare、commit、Agent activate/health、Runtime activate、finalize 与显式 cleanup。GoodBuddy 路径可在同一次操作下载并验证缺失候选、缓存并取得 lease,再有界流式 SFTP 上传一个归档和其中已验证的 bootstrap Node,不把约 294 MiB 整包读入 Main `Buffer`;Host 在解包时完成一次完整 payload 校验。未完成操作只记录暂存 cleanup 所需的 operation ID;下次更新尽力清理旧暂存后重新 prepare,不保存远端 metadata 副本,也不让 cleanup 失败阻塞新更新或回滚健康环境。已有项目在实际使用 Workspace/Runtime 时按需解析 Host current identity 并执行固定 `attach-or-bootstrap`,注册后的 health、capabilities 和 prompt 启动不扫描完整 payload。详见 [设计说明](./docs/features/remote-host/environment-provisioning-technical-design.md)。
|
||||
- **SSH Host 环境准备验证记录**:当前源码已在隔离 Linux x64 环境通过包安装、Ask/Execute、原生子代理工作区外写入、重连及 stop/bootstrap。现有记录未覆盖 Host 卡片的完整 GitHub、北京镜像、Linux x64/arm64、取消和离线 GoodBuddy 传输矩阵,候选 CI/原生打包与系统休眠唤醒也尚未验证;开发记录不代表版本已经发布。
|
||||
- [x] **远程工作区与长任务更新**:Agent `0.11.23` 新增远程文件/Git 管理和可选模型限额,随包 Runtime 不再施加固定十分钟 Prompt 时限;无限请求时长仍保留独立连接超时。请先升级 Desktop 至 `0.12.11`,再下载并更新 Host 上的 Agent。
|
||||
- [x] **远程原生问答与取消修复**:Agent `0.11.24` 修复远程原生问答转交和取消待答后同一会话续发,需要先升级 Desktop 至 `0.13.0`。
|
||||
- [x] **DeepSeek Harness(预览)**:使用 GoodBuddy 固定 Host 和 OpenAI 兼容模型连接;优先使用管理员提供的连接,否则跟随兼容的默认模型或首个兼容连接,无需单独重复选择,设置页显示实际管理员或回退模型来源。Ask 只允许调用 Host 中真实注册的 `read`、`skill` 以及 Main 管理的 Web Search/Fetch 代理,拒绝插件同名冒充,Execute 放行全部已启用内置及插件工具,并以当前用户权限运行。图像输入跟随所选模型连接的能力声明,文本模型在 Host 或模型调用前拒绝图片,图片模型通过有界内联内容和临时 Attachment Store 接收 JPEG/PNG。Windows Host 启动和 ACP 会话共用规范工作区路径,同目录不同写法可复用正常 Runtime,不再因路径不一致而创建会话失败。
|
||||
- [x] **DeepSeek Harness(预览)**:使用 GoodBuddy 固定 Host 和 OpenAI 兼容模型连接;优先使用管理员提供的连接,否则跟随兼容的默认模型或首个兼容连接,无需单独重复选择,设置页显示实际管理员或回退模型来源。Ask 只允许调用 Host 中真实注册的 `read`、`skill` 以及 Main 管理的 Web Search/Fetch 和已启用的 Story Graph 代理,拒绝插件同名冒充,Execute 放行全部已启用内置及插件工具,并以当前用户权限运行。图像输入跟随所选模型连接的能力声明,文本模型在 Host 或模型调用前拒绝图片,图片模型通过有界内联内容和临时 Attachment Store 接收 JPEG/PNG。Windows Host 启动和 ACP 会话共用规范工作区路径,同目录不同写法可复用正常 Runtime,不再因路径不一致而创建会话失败。
|
||||
- [x] **DSH npm 插件市场**:市场默认关闭,由用户显式开启后搜索公共 npm 的 `dsh-plugin` 包,使用捆绑 npm 执行精确版本安装和普通 lifecycle scripts,并支持启停、JSON 配置、移除、失败启动自动停用和离线管理已安装插件;关闭市场只隐藏目录与管理界面,不改变已有插件的启停状态,第三方代码不受 Ask 初始化隔离。
|
||||
- [x] **Ask 与 Execute 工作模式**:Ask 保持只读;Execute 是用户对当前本机或 SSH 账号可用工具、进程、网络和可写路径的完整授权,包括工作区外路径及原生子代理工作。
|
||||
- [x] **Runtime 原生交互转交**:OpenCode 与 Continue 的选择、yes/no、自由文本回答和跳过使用现有问答卡片,本机与托管 SSH OpenCode 同时转交属于当前请求的子会话提问。托管 SSH 需要 Desktop `0.13.0`、Agent `0.11.24` 和新启动的托管 Runtime,不扩展到任意 ACP 服务。回答或跳过成功后保留原问题与答案,支持多轮记录和本地会话重载;旧版本已丢弃的答案不能恢复。并行问题按顺序等待回答,重复事件保留草稿,提交失败可重试;取消远程待答后可在同一会话继续发送。Execute 权限确认自动处理,不等待第二次审批;具体支持范围见[交互边界](./docs/features/assistant-workbar/runtime-interactions.md)。
|
||||
- **本地 Runtime 原生客户端(预览)**:输入区可按当前本地项目与模型,在工作栏终端打开 Continue/OpenCode,或在系统浏览器打开官方 DS Web。客户端使用独立会话,不导入 GoodBuddy 对话历史;关闭 DS 网页不停止服务,可在 GoodBuddy 中停止。Continue/OpenCode Ask 已通过原生权限映射 Main 绑定的只读工具。远程快捷入口未接通,托管标准 Node 交付和完整跨平台安装包验收仍有缺口;Windows 真实模型证据不代表全部会话能力等价,详见[实现与剩余工作](./docs/features/assistant-workbar/progress.md#2026-09-27-local-native-client-validation)。
|
||||
- **Runtime 遥测关闭配置**:本地 DS Web、远程 Runtime 和模型桥启动共用遥测关闭环境,不限制模型、MCP 或用户主动联网。Continue 在自动更新关闭时跳过检查;远程改动随配套 Agent 新启动的 Runtime 生效,当前源码 Linux x64 Agent/model 验证见 [0.15.0 发布准备](./docs/development/release-preparation-0.15.0.md)。
|
||||
- [x] **Runtime 原生交互转交**:OpenCode 与 Continue 的选择、yes/no、自由文本回答和跳过使用现有问答卡片,本机与托管 SSH OpenCode 同时转交属于当前请求的子会话提问。托管 SSH OpenCode 问答需要 Desktop `0.13.0`、Agent `0.11.24` 和新启动的托管 Runtime,不扩展到任意 ACP 服务。回答或跳过成功后在原位置保留问题与答案,支持多轮记录和本地会话重载;旧版本已丢弃的答案不能恢复。并行问题按顺序等待回答,重复事件保留草稿,提交失败可重试;取消远程待答后可在同一会话继续发送。Execute 权限确认自动处理,不等待第二次审批;具体支持范围见[交互边界](./docs/features/assistant-workbar/runtime-interactions.md)。
|
||||
- [x] **原生执行清单**:OpenCode 和 Continue 在对话顶部更新只读执行清单,进度随会话保存。详情在可滚动的锚定浮层中展开,不挤动聊天内容;点击外部关闭,Escape 关闭后焦点返回入口。显式清空和远程重放保留请求归属,取消不把未完成条目标成完成;远程交付需要配套 Agent,详见[清单契约](./docs/features/assistant-workbar/runtime-checklist-technical-design.md)。
|
||||
- **取消后的历史**:模型可见历史保留已中断的部分回复或占位标记,避免旧问题合入下一轮;本地 OpenCode 复用会话时附带一次中断说明。
|
||||
- [x] **专家与 Subagent**:支持显式专家、团队分析和最多三个专家并行分析。专家继承父请求 Ask/Execute 模式,可使用已启用的本机直连模型工具,Ask 仍只读;这些工具不是远程 OpenCode 子会话。聊天先展示可逐项展开的专家完整输出,再在其下展示总 Agent 的综合结果,并随会话保存。
|
||||
- [x] **OpenCode 子代理过程与最终结果**:子代理卡片按顺序显示文字、推理和工具过程,最终结果独立展示。远程实时过程需要 Agent `0.11.20`,旧包仍显示最终结果但不补造缺失过程。
|
||||
- [x] **OpenCode 事件连接回收**:聊天与原生上下文压缩在结束事件迭代前关闭各自订阅,覆盖完成、失败、取消和消费方提前结束,不取消其他并行会话。
|
||||
- [x] **准确的消息底部状态**:区分请求准备、等待重试、开始重试、工具活动、待回答和终态;本机 OpenCode 使用原生重试次数和计划时间,直连模型显示自身退避阶段。消息底部状态点保持静态,未接入的 Runtime 重试不编造提示,会话列表闪点及发送、停止行为不变。
|
||||
- [x] **直连模型命令工作目录**:Execute 命令可通过绝对路径、相对路径或符号链接使用工作区外目录;工作区仍为默认目录和相对路径基准,Ask 保持只读。
|
||||
- [x] **简化 Runtime 选择**:会话与项目菜单不再枚举每个 Runtime 与模型的组合,Runtime 模型在系统设置中配置;已保存的项目固定模型仍可使用,并明确显示其固定模型。
|
||||
- [x] **Runtime 与模型分层选择**:按会话、项目、全局逐字段解析 Runtime 和模型。会话双栏选择器显示来源,可覆盖或恢复项目默认;项目与通道设置共用规则,Main 在每次请求及排队派发时重新解析。已删除或不兼容的连接会回退,缺少凭据仍明确报错。schema 49 迁移已保存选择,用途模型仍为规划,详见[选择设计](./docs/features/model-connections/runtime-selection-design.md)。
|
||||
- [x] **角色绑定模型连接**:每个角色可继承默认模型或选择独立文本模型连接,失效连接安全回退默认模型,综合角色始终继承默认模型。
|
||||
- [x] **多协议模型配置**:支持 Anthropic Messages、OpenAI Responses、OpenAI Chat Completions、OpenAI Images 和无认证本机模型;新用户默认连接为本机 Ollama 兼容地址,不预置第三方云模型服务。升级时只替换从未配置凭据且仍完全等于旧内置值的历史默认连接,用户显式保存或加密凭据的连接保持不变;已有部署仅使用兼容环境变量提供凭据时继续沿用对应的历史连接参数,通用模型环境变量仍优先。“保存并测试模型”会发送有界的真实文本或图片生成请求并校验生成结果,而不是只检查 HTTP 连通性,因此可能产生少量服务商用量费用。
|
||||
- [x] **模型请求定制**:每个 LLM 连接可填写有界 JSON 自定义请求头和顶层请求体。直连与 Continue 支持两者;本地 OpenCode 和 DeepSeek Harness 只使用各自原生支持的请求头,受管 SSH OpenCode 模型网关支持两者。Runtime、协议和认证字段始终优先,这些普通连接设置不能用于保存 API Key 或其他密钥。详见[模型连接请求定制](./docs/features/model-connections/README.md)。
|
||||
- [x] **本地直连手动压缩**:文本模型会话可在自动压缩关闭或未到阈值时手动生成摘要,保留最近轮次和完整历史,保存的摘要用于后续对话;图像连接和远程直连入口不适用。摘要模型调用可能产生费用。
|
||||
- [x] **上下文用量与自动压缩**:直连模型按每次成功调用更新供应商用量,图片与工具轮次使用同一口径,供应商缺失 usage 时才回退估算;界面明确区分“本次模型调用”和“压缩后对话估算”,压缩线始终根据当前设置与所选模型窗口即时计算,不在每个对话中保存旧配置;压缩标识的前后值使用同一估算口径,运行记录仍保留各次模型调用的供应商 usage。对话与多轮工具 Agent 可在已完成调用越过阈值后自动重复压缩,规划时先为固定提示、工具定义和摘要预留预算;同一回复会分别保留 Agent 工具上下文与对话历史的压缩标识,并在应用重启或较早消息滚出本地历史窗口后继续复用摘要。
|
||||
- [x] **持久凭据保护**:API Key 由 Main 使用系统安全存储加密且不暴露给 Renderer。托管 SSH accepted Prompt 只把当前 profile 与密钥放入 Agent 内存,不写入 SSH 参数、远端环境或磁盘;其他路径仍在 Main 内使用。密钥随对应模型连接保存,修改服务地址或临时切换为无需认证不会要求重新输入;只有用户显式清除凭据或删除连接时才移除。
|
||||
- [x] **有界故障诊断**:Desktop 在用户数据目录中轮转保存启动、Runtime 和远程连接的固定阶段失败,最多 4 个 256 KiB 文件;GoodBuddy Agent 在各 installation 的私有 state 目录中轮转保存 daemon、连接、恢复和 Runtime 生命周期,最多 3 个 64 KiB 文件,并可通过固定 `diagnostics --installation-id` 命令读取。两端都只记录白名单阶段、稳定错误码/类型和固定短消息,不保存 Prompt、凭据、文件内容、路径、环境、SSH 参数或 Provider 原始响应;诊断写入失败不改变正常运行。
|
||||
@@ -76,34 +90,50 @@
|
||||
- [x] **Skills 按需接入**:可分配给直连模型、OpenCode、Continue 和 DeepSeek Harness,并使用有界资源和受控 Runtime 边界。
|
||||
- [x] **本机工具执行环境源码链路**:在“能力与工具 > 工具执行环境”中为本机 Skills 与 stdio MCP 选择 GoodBuddy 托管 Node.js、按需安装的托管 Python,或经过真实验证的自定义解释器;提供独立的原生地址/OSS 镜像选择、诊断、安装进度、取消和删除。新的本机 Runtime 与 stdio MCP 获得不可变 PATH 快照,不修改普通终端、系统环境或远程 Host。Windows x64 托管 Node 与原生地址 Python 已通过真实安装验证;托管 Python 归档按目标文件系统验证,Linux 保留大小写不同的合法路径,所有目标仍拒绝完全重复路径和不安全条目。
|
||||
- **本机工具执行环境验证记录**:六个平台/架构的 OSS 镜像对象已完成字节、大小和 SHA-256 公开回读验证。托管 Python 保持按需下载,不向 Desktop 发行包额外带入许可证文件。每个标准打包任务使用目标架构原生 Runner,并在打包前真实安装托管 Python,验证 SSL、pip 与 venv。不可变的 `v0.12.0` 尝试在发布前通过 Windows 与 macOS,但暴露了 Linux TAR 大小写处理问题;`v0.12.1` 在同步 Agent 发布时于原生打包前取消。已发布的 `v0.12.2` 随后通过全部六个原生打包任务及其托管 Python 安装探针。现有记录未覆盖真实 Skill/MCP、自定义解释器和运行中进程协调。
|
||||
- [x] **内置 MCP 按需接入**:知识库、魔法笔记、GoodBuddy 配置与内置浏览器 MCP 可分别启停,并可分配给直连模型、GoodBuddy 管理的 OpenCode 和 Continue;DeepSeek Harness 在设置中明确显示为暂不支持。内置 MCP 仅通过当前请求的短期本机权限提供,Ask / Execute 读写边界不受用户配置放宽。
|
||||
- [x] **内置 MCP 按需接入**:知识库、魔法笔记、GoodBuddy 配置与内置浏览器 MCP 可分别启停,并可分配给直连模型、GoodBuddy 管理的 OpenCode 和 Continue;这些 Server 在 DeepSeek Harness 中仍不支持。Story Graph 另通过 Main 代理支持 Harness。内置 MCP 仅通过当前请求的短期本机权限提供,Ask / Execute 读写边界不受用户配置放宽。
|
||||
- **Obsidian 接入(源码已实现,验收中)**:默认关闭,默认发现全部本机注册仓库,也可指定文件夹并在保存或启用前测试。随包提供 MCPVault 0.16.0,19 个共享工具保留全部上游能力;生产组件自动化、Electron 托管 Node、Windows 设置与聊天 UI,以及受限网络下的 Windows x64 解包应用已验证。物理断网、安装器及其他系统验收待完成。DeepSeek Harness 与远程执行保持不可用,详见[功能文档与验证记录](./docs/features/obsidian/README.md);此项不表示已发布。
|
||||
- [x] **MCP Tools**:显式启用的自定义 MCP 可按 Runtime 分配给直连模型、GoodBuddy 管理的 OpenCode、Continue Agent Execute 和 DeepSeek Harness,并仅在 Execute 加载;Agent 子进程只获得按请求签发的本机回环权限,MCP 地址、命令和凭据保留在 Main,动态工具仍经过发现、执行记录与权限边界。直连模型与 Harness 的健康调用复用当前请求已发现的工具清单,不在每次调用前重复发现。
|
||||
- [x] **MCP Prompts 与 Resources 元数据**:MCP 测试仅在 Server 声明对应能力时发现有界的 Prompt、参数与 Resource 元数据,不读取 Resource 内容;Runtime 支持的 Prompt 可填入聊天草稿后继续编辑。OpenCode 可报告实验性 Resource 清单,Continue 当前版本明确不支持 Resources。
|
||||
- [x] **本地知识库**:支持文件、目录和网页导入、SQLite FTS5 检索及来源追溯。知识范围按对话保存,新对话默认不选择知识库;创建与检索高级参数默认折叠。文档区分可用、处理中和失败,支持打开来源、失败重试及移除来源前确认。
|
||||
内容来源与文档索引共用可搜索列表,单文档来源不重复占行,目录保留分组文档,失败来源保留恢复操作;任务历史和次级操作从统一入口访问。
|
||||
- [x] **外部知识库接入**:在知识库页面管理 Dify、FastGPT 和 RAGFlow 实例,通过目录或手工 ID 验证并绑定远端知识库,保留 Provider 专属检索配置和引用;对话支持按需检索及每次回答前检索。外部系统只负责知识检索,不接入其 App、Chat、Workflow 或 Agent,也不批量同步、本地索引或修改远端内容。被回答引用的有界检索片段随会话保存在本机。详见[外部知识库接入 PRD](./docs/features/knowledge-base/external-knowledge-prd.md)。
|
||||
- **外部知识库验证记录**:三家 Provider 已通过真实服务、本机 HTTP MCP 和生产 IPC 短答案检查,Dify 另通过完整 App 输入区到回答及引用的链路。现有记录未覆盖 FastGPT/RAGFlow 桌面专属参数、复杂多库问答和远程回答前检索,详见[验证记录](./docs/features/knowledge-base/progress.md)。
|
||||
- [x] **知识图谱**:支持规则、模型和混合抽取,以及实体、关系、别名和证据维护。
|
||||
模型抽取不再额外限制为 8192 tokens;OpenAI 使用服务商上限,Anthropic 使用模型连接的输出设置。
|
||||
- [x] **向量模型配置与检索**:可配置兼容 Embeddings 接口并用于语义检索。
|
||||
- [x] **向量诊断与索引任务**:提供真实向量生成诊断、按文档重建进度、取消、失败状态与重启后结果恢复;每篇成功文档立即可用于检索。
|
||||
- [x] **混合检索测试台**:支持全文、中文词组、向量和图谱通道诊断,可调 Top K、阈值、权重、本地或学习型重排及上下文预算。
|
||||
- [x] **分块、维护与评估**:支持固定、结构化和父子分块,分块维护、可取消重建及双语检索评估。
|
||||
- [x] **受控知识本体**:每个知识库可定义实体、关系、别名和端点约束,保留证据偏移、置信度和抽取来源,并显式提示图谱重建。
|
||||
- [x] **强制检索与引用上下文**:对话可按需或每次先检索,显示零结果、降级、失败与取消状态,并可查看引用上下文或安全打开来源。
|
||||
- [x] **魔法笔记 / Magic Notes**:提供本地优先的笔记与待办工作台、范围管理、编辑、筛选和受控 AI 评论;来源记录直接显示在所选待办的详情中,切换事项不会混入上一项的来源内容,读取失败使用应用通知,来源缺失有明确提示。左侧入口可按设置显示未完成待办数量,创建、保存和评论结果使用统一应用通知。Agent / MCP 写入后,已打开的工作台自动更新,并保留当前选择与未保存草稿。
|
||||
- [x] **魔法笔记 / Magic Notes**:提供本地优先的笔记与待办工作台、范围管理、编辑、筛选和受控 AI 评论。笔记从自适应卡片总览打开;独立待办页提供搜索、状态筛选、分组列表和直接勾选,宽屏列表稳定在左侧,任务说明、来源记录及 AI 评论在右侧同一区域显示,窄屏按需显示详情并提供返回列表。紧凑页签放在页头新建按钮左侧,筛选放在搜索右侧。打开原笔记再返回可恢复任务上下文;切换任务不会混入上一项的来源内容,读取失败使用应用通知并提供就地重试,来源缺失有明确提示。左侧入口可按设置显示未完成待办数量,创建、保存和评论结果使用统一应用通知。Agent / MCP 写入后,已打开的工作台自动更新,并保留当前选择与未保存草稿。
|
||||
- **会话采集与快速笔记**(已实现,真实 Electron 验收通过):单条助手回答或完整会话正文可先预览、编辑,再追加到已有笔记或连同首条记录创建新笔记。右侧笔记 Tab 支持标题与正文搜索、快速追加及打开完整工作区,记录来源可返回会话和消息;切换会话保留草稿。真实 App/preload/IPC/SQLite 验收覆盖完整 502 条历史及 214px 面板原生键盘操作,模型调用为 0。[功能验证进度](./docs/features/magic-notes/progress.md)保留聚焦通过、fixture 修复与当时的测试和类型检查失败;[0.15.0 候选验证](./docs/development/release-preparation-0.15.0.md)已通过全量测试、类型检查和 lint。
|
||||
- 待办宽屏列表与详情之间可拖动分隔条并记住宽度;来源笔记分组可独立折叠,不清除当前选中任务的详情。
|
||||
- [x] **连续记录工作区**:笔记详情连续展示全部记录,左侧索引默认 168px,可拖动右边界或用键盘在 140–320px 内调宽,并记住偏好;索引与右侧 AI 评论独立收起。每条画布记录显示真实首页缩略图、页数和时间,文字记录显示摘要;点击索引只滚动定位,保留当前编辑器和草稿。打开笔记就在标题下方显示空白输入区,保留文字/画布选择和保存操作;新增保存后清空,供下一条记录使用,旧记录通过编辑入口修改。空白输入区和保存后未再修改的记录返回时不提示;保存失败和真实修改仍受保护。窄屏索引使用固定宽度折叠抽屉,不显示调宽分隔条,AI 评论移至底部。
|
||||
- [x] **分页画布笔记**:已集成 PeopleLib Fabric + Quill 编辑器,支持跨页正文、笔迹、高亮、对象选择及变换、浮动文字、图片和纸张模板。PDF 可作为分页底版导入并提取原生文字;正文和批注可导出为栅格 PDF,导出文件不保留可搜索文字层。用户手动保存后,记录正文与二进制资源存入本地笔记文件,SQLite 保留元数据、索引、修订、待办和评论。备份须协调保存 SQLite 与笔记目录,产品无一键备份入口。撤销/重做限当前批注页或 Quill 模式,不提供全局文档撤销、无限画布或 PNG 下载按钮。草稿、已保存记录和来源为画布的待办按默认模型图像输入能力使用页面图像加文字,或明确标注仅文字降级;“发送画布页数”可选 1~8,默认 1,按当前顺序发送前 N 页,文字和图片范围一致,保存与导出仍支持 50 页。纯视觉记录/草稿需要支持图像输入的模型。即时评论模式提供手动画布分析,保存后自动分析失败不阻断或回滚保存。提取文字不变时保留文字评论;布局变化使视觉评论失效,并在保存后自动评论模式下重新分析。MCP 禁止以纯文字覆写画布记录。实现已完成,最终验证状态见[功能进展](./docs/features/magic-notes/progress.md)。
|
||||
编辑与只读画布均支持 25%–300% 视图缩放、100% 还原及随视口调整的“适应宽度”,不改变保存内容或导出尺寸。
|
||||
历史正文迁移已改用文件存储,当前 schema 48 新增会话来源元数据;旧客户端不能再打开,回退需使用升级前备份。
|
||||
- [ ] **MCP Server Control Plane**(规划中):统一 MCP 生命周期、健康检查、重连、Schema 缓存、隔离、审批和审计。
|
||||
- [ ] **可追溯笔记摘录与 AI 编辑**(规划中):从对话、知识和网页收集带来源的摘录,并提供需确认的总结、改写和整理操作。
|
||||
- [ ] **知识/网页摘录与 AI 编辑**(规划中):在已实现的会话正文采集之外,扩展知识和网页摘录,并提供需确认的总结、改写和整理操作。
|
||||
|
||||
### 工作管理、长期协作与工作流
|
||||
|
||||
- [x] **任务、活动与成果**:集中管理任务状态、审计活动和独立成果文件;普通聊天回复只保留在会话中,不再自动复制到成果栏,已有重复聊天 Markdown 从成果列表隐藏但不物理删除。运行记录改由 Main SQLite 保存,不再受旧 Renderer 的 500 条、单详情 4,000 字符或约 2 MB 上限影响,页面按批继续显示;不再常驻旧缓存截断警告,但不会恢复此前已丢失的历史。Token 用量按 Runtime 与模型归类,并针对 OpenAI 兼容与 Anthropic Messages 的不同上报口径归一化展示缓存命中率;活动按会话分组并默认收起,避免长历史占满页面。
|
||||
- [x] **任务、活动与成果**:集中管理任务状态、审计活动和独立成果文件;普通聊天回复只保留在会话中,不再自动复制到成果栏,已有重复聊天 Markdown 从成果列表隐藏但不物理删除。运行记录改由 Main SQLite 保存,不再受旧 Renderer 的 500 条、单详情 4,000 字符或约 2 MB 上限影响,页面按批继续显示;不再常驻旧缓存截断警告,但不会恢复此前已丢失的历史。Token 用量按 Runtime 与模型归类,并针对 OpenAI 兼容与 Anthropic Messages 的不同上报口径归一化展示缓存命中率;用量按项目或会话分组时默认收起并显示汇总,展开后显示 Runtime / 模型子行,按模型分组时保持平铺。独立“系统任务”页签按来源汇总后台用量,支持手动刷新和记录更新后刷新;监督回顾、图片重新生成、图谱提取和远程嵌入补充用量记录,不补算缺失的历史或供应商数据。活动按会话分组并默认收起,避免长历史占满页面。
|
||||
- [x] **紧凑会话工具记录**:逐项展开工具记录即可查看和复制结果、错误及输入参数;会话与子任务进度使用简洁行,减少重复 Runtime 摘要。OpenCode、Continue、DeepSeek Harness 和直连模型可显示已有文件路径、命令或搜索摘要,并在工具完成和会话重载后保留。
|
||||
- [x] **Task 与定制任务体验**:每个产品级 Task 只关联一条 Conversation,一条 Conversation 可承载多个 Task;左侧会话列表通过行首展开按钮显示带共享状态点的 Task 子项,父会话行不重复任务标签,UI 只展示到 Task,不暴露 Job/Run 层级。新建定制任务可关联当前或新 Conversation,默认 Execute 并沿用 Runtime、工具和审批边界;重复触发复用同一 Task,文本结果回写 Conversation,独立文件和图片保留为成果。普通消息与到期 Scheduled Task 共用 Conversation 级持久发送队列,同一会话一次只执行一项;当前回复期间仍可继续发送,队列按顺序续跑,并允许删除或“立即中断并插入”。Task Center 继续作为完整索引,不建设独立 Automation Center。当前计划触发支持单次、每日和每周;高级时区、Cron、事件触发与重试治理仍按 PRD 逐步实现。详见 [Task Center PRD](./docs/features/task-and-job/task-center-prd.md) 和 [Scheduled Task PRD](./docs/features/task-and-job/scheduled-task-prd.md)。
|
||||
- [x] **记忆与智能心跳**:当前提供周期回顾、建议记忆、洞察、后续任务和可审计运行轨迹。
|
||||
- [x] **记忆与智能心跳**:计划触发共享增量回顾,不再单独生成心跳报告;可只更新记忆图谱,或从未决事项、分歧、修订和候选约定生成有依据的建议。接受未决事项创建暂停任务,确认约定写入长期背景;建议失败可单独重试,不撤销已完成回顾。
|
||||
- [x] **定时消息沿用会话**:定时消息使用会话当前历史、Runtime、工作模式和已保存的知识检索设置;任务详情显示实际运行模式,当前 occurrence 未结束时禁止重复“立即运行”。
|
||||
- [x] **智能心跳入口与范围改善**:将“智能心跳 > 心跳计划”作为完整配置的唯一权威入口,支持创建和编辑 Global 或指定一个、多个 Project 的计划;旧单项目配置无损迁移,项目级记忆与行动输出必须显式指定范围内的 Project。Task Center 和设置不再复制心跳表单;按分区回顾、候选生成和唤起条件仍待设计。详见 [智能心跳 PRD](./docs/features/smart-heartbeat/prd.md)。
|
||||
- [ ] **通用监督**(规划中):通过固定监督栏目观察用户选择的会话、任务、自动化或实验对象,提供带证据的评论与人工介入请求,但不自动发言、批准工具或切换 Execute。详见 [会话监督 PRD](./docs/features/conversation-supervision/prd.md)。
|
||||
任务中心通过状态数量和筛选定位运行中及待处理任务,审批操作保留在对应任务卡片中。
|
||||
- [x] **智能心跳设置与范围**:“监督者 > 智能心跳”统一管理 Global 或指定 Project 的每日、每周计划及建议,Task Center 和设置中心不复制表单。待处理建议不重复,忽略后有新依据才再次提出;项目与全局共用按来源版本维护的时间线进度,会话级实时主动介入仍为规划。详见[实现规则](./docs/features/conversation-supervision/logic-design.md)。
|
||||
- [x] **故事、经验与时间螺旋**:事件可组织为项目功能、子线索及可选跨项目故事,支持重命名、合并、调整归属、移除和撤销。经验保留形成及应用依据与人工调整标记,心跳可提示暂无进展故事或可能适用的经验。平铺和懒加载 WebGL 时间螺旋共用选择与来源,保留平铺回退及图形资源释放;整理与建议可能增加模型费用,自动结果须核对来源。详见[实施证据](./docs/features/conversation-supervision/progress.md)。
|
||||
- [x] **监督者回顾、故事线图谱与活动**:按范围和时间回顾工作,查看保存结果的图谱及来源,确认或修订实体,并带入相关回顾上下文继续讨论。侧栏可跟随或固定 Conversation、Task,并打开匹配结果。活动合并心跳触发与下游监督的阶段状态,保留失败和旧结果入口。自动与默认手动回顾只处理新增、修改及未处理来源,无变化时跳过模型;显式“重新整理”重读区间,但不推进或重置共享进度,自动回顾不续跑用户暂停的运行。回顾只读且禁止工具,模型调用可能产生费用,详见[实施与验证记录](./docs/features/conversation-supervision/progress.md)。
|
||||
手动开始、自动触发和继续共用一个活动回顾;活动记录可取消并保留已保存批次,清理结束前不接纳新回顾,已取消回顾不可继续。故事线事件、实体和关系按独立页签展示数量,并与画布选择同步。
|
||||
可从监督反馈或图谱来源直接继续讨论,编辑问题并明确打开目标会话;界面不再提供知识实体写回控件或重复上下文说明,智能心跳用可处理的监督建议代替报告指标和趋势。
|
||||
- 手动回顾分页处理所选历史,并按批保存事实与来源;不再按固定时长自动暂停,仍支持主动暂停和从保存进度继续。设置提供并发、单次请求超时、批次大小和响应容量;活动记录突出相连阶段、已保存批次与剩余来源,次要统计可展开查看。工作回顾采用全宽纵向布局,运行状态单独显示,空态不再挤在控件旁。
|
||||
- [ ] **监督后续能力**(规划中):逐事件完整回放、Experiment 目标、人工修改时间序列审计、事件触发及更完整的执行观察仍未完成。当前自动回顾受输入预算和滚动窗口限制,删除报告不会重置处理进度或重建图谱。详见 [会话监督 PRD](./docs/features/conversation-supervision/prd.md)。
|
||||
- [ ] **批量运行与对比实验室**(规划中):对模型、Prompt、角色和工作流配置执行批量对比,汇总质量、耗时、Token、费用、失败率和成果差异。
|
||||
- [ ] **时态记忆与事实冲突检测**(规划中):为记忆和知识图谱增加有效期、当前事实、过期与矛盾检测、事实核验及证据回溯。
|
||||
- **Story Graph 只读工具(Desktop 0.15.9 / Agent 0.15.3)**:Agent 可搜索已保存工作、故事和经验,读取上下文并分页核对来源。监督者开关及 Runtime 分配同时控制发现和调用,旧会话也受限制;本地和远程路径共用桌面读取入口。共享来源版本事实按允许范围复用,关键词匹配和工具说明引导模型按需查询,不自动注入全图;经验是参考而非指令,查询本身不调用模型,`as_of` 仍未实现。详见[设计与验证](./docs/features/conversation-supervision/story-graph-mcp-design.md)。
|
||||
- [ ] **可视化受控工作流**(规划中):提供版本化 DAG、条件分支、审批、取消和恢复,执行节点继续经过 Main Runtime 边界。
|
||||
- [ ] **统一 Run Graph 与回放**(规划中):关联任务、Subagent、模型、知识、工具审批、用量和成果,支持失败定位、重试和脱敏导出。
|
||||
|
||||
@@ -117,7 +147,7 @@
|
||||
- [x] **微信 ClawBot 扫码与媒体**:通过独立 Sidecar 完成本机扫码、验证码、加密凭据和文字收发;支持个人微信私聊图片与文件,单条消息最多 4 个附件、解密后合计不超过 12MB。
|
||||
- [x] **微信安全回传**:支持返回当前任务生成的图片,或在用户明确要求时将本次最终文本生成为 Markdown 附件;不自动读取或发送已有工作区文件。
|
||||
- [x] **企业微信与钉钉连接**:支持 Main-only 加密设置、环境变量只读覆盖、连接测试、动态启停、发送者范围和状态诊断。
|
||||
- [x] **受管本地模型下载源**:在“平台功能 / 通用设置”中为后续语音输入与 OCR 模型下载全局选择 ModelScope(默认)或 Hugging Face;所选来源缺少完整已验证文件时明确不可用,不静默换源或混合文件。
|
||||
- [x] **受管本地模型下载源**:“模型连接”页头在模型类型页签同行右侧、保存操作下方显示当前来源与次按钮。弹窗为后续受管本地模型下载全局选择 ModelScope(默认)或 Hugging Face,确认才保存,取消保持已保存来源不变,错误可重试;向量、语音和 OCR 的更换下载源入口直接打开该弹窗。所选来源缺少完整已验证文件时明确不可用,不静默换源或混合文件。
|
||||
- [x] **可选本地语音模型管理**:应用不内置模型权重;提供校验下载、进度与取消、来源链接、ZIP 或本地目录导入、切换和删除。
|
||||
- [x] **本地录音与离线转写**:采集麦克风音频并使用已选择的本地模型离线转写,支持停止、取消、状态反馈和资源释放。
|
||||
- [x] **版本检查与镜像节点**:在“关于与更新”中选择 GitHub(默认)或镜像节点;手动检查、启动时检查和下载页使用同一选择,并只读取固定可信的发布索引,不自动下载或安装。
|
||||
@@ -127,10 +157,24 @@
|
||||
|
||||
### 开源、构建与发布
|
||||
|
||||
- 当前源码候选为 Desktop `0.13.5`、Agent `0.13.0`,OpenCode 固定为 `1.18.29`;正式发布状态以 Desktop 与 Agent 的独立发布渠道为准。
|
||||
- Agent `0.13.0` 接入远程图片工具、共享 Runtime 进程并修复会话恢复,复合包要求 Desktop `0.13.5`;请先升级 Desktop,再更新 Host 环境。Node 保持 `24.19.0`。
|
||||
- Desktop `0.15.3` 修复本地直连手动压缩和请求有效期间的工具授权,区分取消与失败并去除重复提示,优化侧栏搜索、注册成功后的快捷键提示及笔记操作布局,减少启动和存储中的重复处理,并精简重复依赖及开发文件。不宣称整体渲染性能已显著提升;Office 实时编辑仍为设计。证据见[0.15.3 发布准备](./docs/development/release-preparation-0.15.3.md)。
|
||||
- 当前 Desktop 候选为 `0.15.9`,Agent 候选为 `0.15.3`、最低要求 Desktop `0.15.9`,OpenCode 固定为 `1.18.29`、Continue 为 `1.5.47`;正式发布状态以独立发布渠道为准。
|
||||
- Desktop `0.15.9` 增加共享回顾进度、故事经验与时间螺旋,schema 56 要求升级前备份,相关模型整理可能增加费用。会话与消息窗口化、Markdown 缓存、只读 Worker、活动差量保存及远程 checkpoint 批次落库减少重复工作,不承诺所有机器相同比例提速;页内查找不覆盖未挂载行,快速滚动可能显示占位,Worker 不可用时同步回退。Execute 配置修改取消重复原生确认,Electron 为 44.5.1。当前源码四次真实 Host 模型请求通过,仍需精确候选 CI 与打包验证,见[发布准备](./docs/development/release-preparation-0.15.9.md)。
|
||||
- Desktop `0.15.8` 与 Agent `0.15.2` 支持 Execute 模式保存会话图片到本机或 Host 绝对路径,覆盖需明确指定,不支持转换为 WebP。MCP 设置列出图像工具,不代表所有模式均可用。笔记增加新记录取消、编辑焦点返回及可用内容全宽;缺少浏览器和磨砂设置时默认开启,已保存选择不变。schema 仍为 52。真实 Linux x64 Host 工具层证据与待完成的精确候选 CI、打包验证分别记录在[发布准备](./docs/development/release-preparation-0.15.8.md)。
|
||||
- Desktop `0.15.7` 复用已完成的流式 Markdown 块与会话派生值,不改默认流式节奏。运行记录进入时读取、可见时每 15 秒刷新,也可手动刷新;本地自定义 MCP 发现故障按服务隔离。受管模型下载源移至模型连接中的确认弹窗,设备共享改为显式启用的测试功能。schema 仍为 52,仍需精确候选 CI 与打包验证,组件测量不等同完整 App 结果。详见[发布准备](./docs/development/release-preparation-0.15.7.md)。
|
||||
- Desktop `0.15.6` 增加默认关闭、可保存的顶栏与清单磨砂外观,不透出系统桌面;减少聊天重复渲染,草稿与队列图片支持直接预览,设置分组及开关右对齐,笔记标题和编辑间距更紧凑。本地 OpenCode 在清理后对明确暂时性 MCP 传输错误最多重试一次,作废结果不确定的名称,不重放 Prompt 或工具调用;反馈包含脱敏连接诊断。schema 52 增加执行统计索引,升级前备份数据库和笔记。详见[发布准备](./docs/development/release-preparation-0.15.6.md)。
|
||||
- Desktop `0.15.5` 更新智能心跳与增量回顾,保留中断轮次历史,执行清单改为浮层,笔记保存成功后退出编辑;自动评论不重新打开编辑器,失败保留草稿。新增手动笔记标签、AND 筛选及重命名/合并/删除,双栏笔记与全宽概览,固定工作区文件操作栏,缓存 OpenCode 终端依赖并缩短 MCP 名称。schema 51 增加标签存储,同时迁移旧待确认记忆为待处理约定;升级前备份数据库与相邻笔记。证据见[发布准备](./docs/development/release-preparation-0.15.5.md)。
|
||||
- Desktop `0.15.4` 增加 Runtime/模型分层选择及独立系统任务用量,减少 Runtime 指令和历史重复。schema 49 迁移项目与会话选择,回退须恢复完整升级前数据库和相邻笔记备份。Agent `0.15.1` 要求 Desktop `0.15.4`,修复远程 Continue 历史重复封装,Runtime 锁版本不变。证据见[0.15.4 发布准备](./docs/development/release-preparation-0.15.4.md)。
|
||||
- Desktop `0.15.1` 已完成六平台原生打包及 GitHub/OSS 发布。Desktop `0.15.2` 新增 Story Graph 只读工具和完整可编辑的监督讨论消息,简化笔记采集并补充退出诊断;远程图谱读取需配套 Agent `0.15.0`。主机与云环境管理应用仍为待实现规划,当前候选证据见[0.15.2 发布准备](./docs/development/release-preparation-0.15.2.md)。
|
||||
- Desktop `0.15.1` 承接未发布的 `0.15.0` Obsidian 接入、会话采集到魔法笔记和预览中的本地原生客户端,并修复知识库、监督者与界面操作。schema 48 新增会话来源元数据,回退旧客户端需使用升级前完整备份。Agent `0.15.0` 要求 Desktop `0.15.2`,保留 Node `24.19.0`,补齐远程遥测关闭配置及 Continue 跳过已关闭的更新检查,不新增远程客户端快捷入口。
|
||||
- Desktop `0.13.15` 承接未发布的 `0.13.14`,改善监督者分页回顾与续跑、ripgrep 原生搜索、待办布局和远程结果未知后的显式续聊。数据库升级至 schema 47,回退旧客户端需使用升级前完整备份。
|
||||
- Desktop `0.13.16` 分离结构升级与旧历史回收,移除回顾固定时长自动暂停,并改善监督者布局。相较 `0.13.15` 不新增 schema 变更,也不要求更新 Agent。
|
||||
- Agent `0.13.3` 回收空闲 Runtime,并使被替换的 Agent 等已有工作结束后退出;复合包要求 Desktop `0.13.12`,请先升级 Desktop,再更新 Host 环境。Node 保持 `24.19.0`。
|
||||
- Agent `0.13.4` 在完成前等待模型交付确认,空闲 Runtime 回收失败不改变已完成结果,并在环境更新后清理已确认停用的旧程序及所属孤儿进程;保留历史、活动及状态不明的安装。独立包要求 Desktop `0.13.14` 或更新版本;因 `0.13.14` 未发布,请先升级至 Desktop `0.13.15`,再更新 Host。
|
||||
- 发布准备要求全量测试、发布说明校验、类型检查及 lint;候选主分支 CI、生产构建、原生打包及公开发布核验为独立验收步骤。本轮不做本地生产构建、打包或龙芯预览,当前验证状态见[发布准备记录](./docs/development/release-preparation-0.15.0.md)。
|
||||
- Desktop `0.13.12` 和 Agent `0.13.3` 已完成原生发布流程,但不替代开发期真实 Host 场景覆盖。
|
||||
- 验证记录与功能状态分开。存储已有本地及 Linux x64 Host 真实工具任务、多项目多会话、取消和无损迁移验证;进程复用有 Windows 完整 App 与 Linux x64 Host 证据,PPTX OCR 与空闲内存释放有 Windows 真实文件证据。图片工具已有真实服务商生成/编辑及 Host 传输证据,这些记录未覆盖完整界面自然语言调用和本地/远程切换。候选仍需主分支 CI 及原生发布打包,详见[Runtime 验证记录](./docs/features/assistant-workbar/progress.md)与[图片工具验证记录](./docs/features/conversation-media-generation/progress.md)。
|
||||
- 2026-09-15 候选检查通过:双语更新说明、`npm test`(4,293 项通过、67 项跳过)、`npm run typecheck`、`npm run lint`。本次版本元数据与文档准备新增真实模型调用为 0,未重跑 Host 场景或进行本地生产打包。
|
||||
- 发布准备前,`9836a4c` 已通过本地 4,858 项测试(67 项跳过)、完整类型检查和 lint;真实服务商画布分析仍未验证。该提交的三个原生 Agent CI 构建通过;桌面 CI `35485936807` 为 4,875 项通过、8 项 Electron 测试失败,均因 Linux 沙箱辅助程序未配置,在页面断言前退出。候选配置该程序并在 Xvfb 下运行测试,不关闭沙箱;候选本地验证、主分支 CI 与原生标签打包分别记录,不在发布准备中运行本地生产构建或打包。
|
||||
- [x] **0BSD 开源许可**:原创代码可自由使用、复制、修改、分发和商用;第三方组件和资源仍遵循各自许可证。
|
||||
- [x] **可复现依赖安装与源码构建**:使用锁定依赖、Node.js 24 和统一的测试、类型检查、Lint、生产构建命令。
|
||||
- [x] **六平台原生发布矩阵**:Windows、macOS、Linux 的 `x64`、`arm64` 目标由原生 Runner 构建,并提供发布清单和 SHA-256 哈希。Windows 提供 NSIS 与 portable ZIP,macOS 仅 DMG,Linux 提供 AppImage、DEB 和 RPM,共 12 个安装包、20 个发布资产。
|
||||
@@ -139,11 +183,13 @@
|
||||
|
||||
### 开放接口、团队协作与远程执行
|
||||
|
||||
- [x] **远程 Continue 交付**:已接入 CN 1.5.47 受管包、Agent HTTP-to-ACP 适配、模型路由、项目选择和原生清单。默认包同时携带 OC/CN;Windows 桌面到 Linux x64 已通过安装、真实模型清单更新、长清单、取消及重启恢复验证。Execute 会话 MCP 已通过真实文本模型 Host 往返,图片服务使用替代接收端,不代表真实生图验证。已发布 Agent `0.13.4` 需要 Desktop `0.13.14` 或更新版本;正式发布及原生平台验收与 Linux 开发证据分开,详见[验证记录](./docs/features/remote-host/runtime-checklist-validation.md)。
|
||||
|
||||
- [x] **远程任务委派**:仅在用户显式配置端点和令牌后启用,按全局内网兼容模式使用 HTTP(S),结果进入持久化发件箱。
|
||||
- [ ] **Headless Runtime API**(规划中):提供本机优先的任务、事件、状态和成果 API,以及有范围、有效期、限流和撤销能力的令牌。
|
||||
- [ ] **GoodBuddy Team Hub**(规划中):以可选服务提供组织、RBAC、项目共享、远程 Agent、策略下发和租户审计。
|
||||
- **SSH 主机与远程执行空间验证记录**:主机 CRUD、Host Key、加密凭据、Project UI、Workspace、OpenCode ACP v5、Agent-owned Prompt/gateway/transcript、Ask 只读、Execute 完整账号权限、取消、detached Agent 精确重连和 release-only 双架构资源校验已经接线。Linux x64 当前源码已用真实模型与工具通过 detach、进程结束、relay 丢失、并发、取消、Provider 异常、Agent 重启和 Desktop SQLite 恢复矩阵;当前源码测试签名 Agent `0.11.17` 又通过两个并发 Ask 读取、一次 Execute 写入并读回,以及 6 次带自定义 Header 和 Body 的真实受管网关模型请求,未发现 Provider 请求重放,隔离 Agent state 中也没有 API Key 或自定义请求值。公开 signing key registry 已供应。控制面直连源码不等待新 installer-bearing 包;现有记录未覆盖当前 Linux x64/arm64 正式签名工件的完整公开核对及 GitHub/北京镜像、双架构和离线 GoodBuddy 传输矩阵,发布结果另行记录。
|
||||
- [ ] **多云远程沙盒 Agent**(规划中):通过云厂商 API 和 SSH Agent 管理专用 Linux 沙盒;凭据留在 Main 进程,高风险控制面操作单独确认。
|
||||
- [ ] **主机管理与云环境管理**(规划中):主机管理迁入应用中心,提供列表与详情工作页面;云环境管理作为同级应用,复用主机连接和执行能力。首版适配计划覆盖 AWS、Azure、阿里云、腾讯云、VMware vCenter / vSphere、ZStack,分批完成已有实例发现、接入及真实执行验收;实例创建、启停、销毁和网络编排后续交付。平台 API 尚未实现,现有云主机可按 SSH 条件手工接入。详见[产品范围](./docs/features/remote-host/prd.md)。
|
||||
|
||||
## 规划原则
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ current limitations.
|
||||
| Area | Available in GoodBuddy |
|
||||
| --- | --- |
|
||||
| Agent Runtimes | Direct model connections, OpenCode, Continue, and the preview DeepSeek Harness |
|
||||
| Data and execution | Local SQLite storage, read-only `Ask`, full-account `Execute`, and recorded tool activity |
|
||||
| Data and execution | Local SQLite and note-file storage, read-only `Ask`, full-account `Execute`, and recorded tool activity |
|
||||
| Knowledge | File, folder, and web imports with full-text, Chinese phrase, vector, and knowledge graph retrieval |
|
||||
| Desktop releases | Official Windows, macOS, and Linux builds for `x64` and `arm64`, plus an experimental LoongArch preview |
|
||||
| Integrations | Local or hosted model endpoints, custom MCP servers, WeChat ClawBot, WeCom, and DingTalk |
|
||||
@@ -68,6 +68,15 @@ current limitations.
|
||||
|
||||
### Magic Notes and Smart Heartbeat
|
||||
|
||||
Magic Notes displays a continuous record stream, with a narrow left index and
|
||||
independent right AI comments. Both panes can collapse; thumbnails scroll to a
|
||||
record while preserving drafts. It supports rich-text entries and paged canvases with flowing text,
|
||||
handwriting, highlights, images and PDF backgrounds. Import PDF text and export
|
||||
annotated pages as a raster PDF. Canvas saves are manual; AI comments use the
|
||||
default model's image-input capability, with an explicit text-only fallback when
|
||||
images are unsupported. See the [Magic Notes documentation](./docs/features/magic-notes/README.md)
|
||||
for behavior and final validation status.
|
||||
|
||||

|
||||
|
||||

|
||||
@@ -156,7 +165,8 @@ npm run lint
|
||||
|
||||
Scan the QR code to join the GoodBuddy WeChat group:
|
||||
|
||||
<img width="1250" height="1743" alt="46b5c3b5936388b0a55db6fe034a7d7f" src="https://github.com/user-attachments/assets/45cd1736-f1ae-4f99-85be-cdb2cc1160c0" />
|
||||
<img width="1250" height="1743" alt="8d6cd5dc3f347a0f3f1cf3844071b4d3" src="https://github.com/user-attachments/assets/1c92c1b8-d1f7-4270-be2c-b9543beadc11" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+9
-2
@@ -38,7 +38,7 @@ GoodBuddy 是用于模型对话、编程 Agent、知识检索、笔记和定时
|
||||
| 范围 | GoodBuddy 提供的能力 |
|
||||
| --- | --- |
|
||||
| Agent Runtime | 直连模型、OpenCode、Continue 和预览版 DeepSeek Harness |
|
||||
| 数据与执行 | 本地 SQLite、只读 `Ask`、使用当前账号完整权限的 `Execute`、工具调用记录 |
|
||||
| 数据与执行 | 本地 SQLite 与笔记文件存储、只读 `Ask`、使用当前账号完整权限的 `Execute`、工具调用记录 |
|
||||
| 知识库 | 导入文件、目录和网页,通过全文、中文词组、向量和知识图谱检索 |
|
||||
| 桌面版本 | Windows、macOS、Linux 的 `x64` 与 `arm64` 正式版本,以及龙芯 LoongArch 实验预览版 |
|
||||
| 外部连接 | 本机或内网模型服务、自定义 MCP、微信 ClawBot、企业微信和钉钉 |
|
||||
@@ -53,6 +53,12 @@ GoodBuddy 是用于模型对话、编程 Agent、知识检索、笔记和定时
|
||||
|
||||
### 魔法笔记与智能心跳
|
||||
|
||||
魔法笔记连续展示全部记录,左侧窄栏索引与右侧 AI 评论可独立收起,点击缩略图滚动定位并保留草稿。
|
||||
支持富文本记录和分页画布,可混排跨页正文、手写笔迹、高亮、图片与 PDF 底版,
|
||||
导入 PDF 时提取原生文字,并将批注页面导出为栅格 PDF。画布由用户手动保存;AI 评论按
|
||||
默认模型的图像输入能力分析,不支持图像时明确提示仅文字降级。具体行为与最终验证状态见
|
||||
[魔法笔记文档](./docs/features/magic-notes/README.md)。
|
||||
|
||||

|
||||
|
||||

|
||||
@@ -128,7 +134,8 @@ npm run lint
|
||||
|
||||
扫描二维码加入 GoodBuddy 微信群:
|
||||
|
||||
<img width="1250" height="1743" alt="46b5c3b5936388b0a55db6fe034a7d7f" src="https://github.com/user-attachments/assets/cc094468-2885-447a-afb4-91c7ac117fd3" />
|
||||
<img width="1250" height="1743" alt="8d6cd5dc3f347a0f3f1cf3844071b4d3" src="https://github.com/user-attachments/assets/e2cd4874-0b6c-40d4-8867-0a9e843a7cb5" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+164
-50
@@ -35,6 +35,7 @@
|
||||
- 加载、空内容、无结果、失败、只读和权限不足是不同状态,必须分别呈现。
|
||||
- 可撤销操作优先提供撤销,不可撤销操作必须在执行前说明影响范围。
|
||||
- 不依赖颜色单独表达状态或风险。
|
||||
- 当前模型或 Runtime 及切换进度集中显示在会话输入区,顶栏不重复显示可用性标识。切换失败、自动状态检查失败或返回不可用时,通过应用通知说明原因;发送按钮保留关联的无障碍状态说明。
|
||||
|
||||
### 2.5 默认支持浅色与深色主题
|
||||
|
||||
@@ -42,6 +43,10 @@
|
||||
|
||||
两种主题必须保持相同的信息层级,但不要求机械地反转明暗。浅色主题以白色主内容画布、冰蓝灰侧栏和轻微着色的顶栏建立空间关系;深色主题使用深海军蓝与蓝灰表面逐层提亮,避免纯黑。蓝色承担主要选择和交互,青绿色主要承担成功与可用状态,二者不得混用语义。
|
||||
|
||||
外观设置提供“透明磨砂特效”开关,默认开启,文字在左、共享 Switch 在右。新用户及缺少该字段的旧配置使用开启状态;已保存的开关选择保持不变。选择保存在应用设置中,保存成功后立即生效,重启后恢复。开启时,右侧内容框架顶栏的背景不透明度在浅色主题为 65%、深色主题为 70%,保留 18px 背景模糊与淡分隔线。聊天滚动视口向上延伸 58px,消息上滚时实际经过顶栏背后;不使用人工渐变充当背景。初始留白放在滚动内容中,包含顶栏高度和当前任务/待办条的实测高度,首条消息仍从这些控件下方开始。任务/待办条固定在顶栏下方并保持实色;展开的 Runtime 待办卡片使用两主题各自的 75% 不透明背景、18px 背景模糊和清晰的文字颜色。
|
||||
|
||||
已保存的开关状态同步到文档根节点,卸载时清理,使 body/top layer 中的待办卡片也能应用样式;其他菜单保持原有表面。输入区、右侧工作栏和非聊天页面保持原有位置,聊天加载态及无活动消息面板时不扩展视口。窗口拖拽和控制按钮保持原有行为;该效果不透出操作系统桌面。关闭后恢复顶栏下方的聊天布局,顶栏与待办卡片均恢复实色。`tests/frosted-glass.electron.test.ts` 使用真实 `ChatHistoryPane` 检查消息与顶栏的可见矩形交叠、初始留白、关闭恢复、浅深主题、跳底、历史加载锚点和会话切换。
|
||||
|
||||
## 3. 设计令牌
|
||||
|
||||
令牌以 CSS 自定义属性实现。`:root` 提供浅色值,`[data-theme="dark"]` 覆盖深色值。组件样式不得新增只服务于单个页面的颜色、阴影、圆角或间距常量。
|
||||
@@ -67,7 +72,7 @@
|
||||
| `--success`、`--success-subtle` | 成功状态 |
|
||||
| `--warning` | 警告状态 |
|
||||
| `--danger`、`--danger-strong` | 危险文本和状态 |
|
||||
| `--danger-solid` | 最终确认按钮等需要反白文字的危险背景 |
|
||||
| `--danger-solid`、`--danger-solid-hover` | 最终确认按钮等需要反白文字的危险背景及悬停背景 |
|
||||
| `--danger-border`、`--danger-subtle` | 危险入口和失败状态背景 |
|
||||
|
||||
浅色与深色具体值只在 `styles.css` 的主题根节点维护。状态组件必须同时显示文字或图标,不能仅靠颜色区分。
|
||||
@@ -115,6 +120,7 @@
|
||||
|
||||
- 业务组件通过 `--font-family-ui` 与字体尺寸令牌继承字体,不创建页面专属字体栈。
|
||||
- 表单按钮、输入框、选择框和文本域必须继承界面字体,避免回退为原生控件字体。
|
||||
- 文本表单复用 `.field` 下的共享输入规则;不适合增加字段包装层的独立输入或文本域使用 `.field-control`。文本输入规则排除 Checkbox 和 Radio,保留全局 `:focus-visible` 轮廓,不清除 Switch 焦点环。
|
||||
- 连续阅读内容使用 `14px`,持久辅助信息不得小于 `11px`。
|
||||
- 本地兜底字体资源必须随生产包交付,并同时包含 Inter 与 Noto Sans SC 的 OFL 许可证。
|
||||
- 页面内不得通过同时放大字号、加粗和使用强调色制造多个同级主标题。
|
||||
@@ -145,8 +151,9 @@
|
||||
| 变体 | 最大内容宽度 | 适用场景 | 页面映射 |
|
||||
| --- | --- | --- | --- |
|
||||
| `reading` | `820px` | 连续阅读、单列编辑、对话撰写 | 聊天正文与输入区 |
|
||||
| `standard` | `960px` | 常规列表、设置与表单 | 最近对话、设置 |
|
||||
| `dashboard` | `1040px` | 指标、卡片网格、宽表格与审计数据 | 智能心跳、活动记录 |
|
||||
| `standard` | `960px` | 常规列表与表单 | 最近对话 |
|
||||
| `dashboard` | `1040px` | 指标、卡片网格、宽表格与审计数据 | 活动记录 |
|
||||
| `supervisor` | `1440px` | 选择列表、图谱画布、来源详情三栏工作台 | 监督者(含智能心跳设置) |
|
||||
| `master-detail` | 可用空间内流式铺开 | 左侧选择、右侧编辑或预览 | 知识库 |
|
||||
|
||||
共同规则:
|
||||
@@ -176,7 +183,8 @@
|
||||
`PageHeader` 统一接收以下内容:
|
||||
|
||||
- `title`:必填,简短名词或任务名称。
|
||||
- `description`:可选,一行说明当前页面能做什么,不重复标题。
|
||||
- `description`:可选,需要常显的简短上下文,不重复标题或帮助内容。
|
||||
- `help`:可选,补充用途和操作说明,通过标题同行的 `InlineHelp` 按需展开,规则见 6.14。
|
||||
- `scope`:涉及范围的数据页面必填,使用 `ScopeBadge`。
|
||||
- `primaryAction`:可选,页面唯一主操作。
|
||||
- `secondaryActions`:可选,最多两个直接显示,其余进入更多菜单。
|
||||
@@ -193,7 +201,7 @@
|
||||
|
||||
### 6.1 PageTabs
|
||||
|
||||
用于同一一级页面内的同级内容面板,例如智能心跳的“运行概览”和“心跳计划”。
|
||||
用于同一一级页面内的同级内容面板,例如监督者的“工作回顾”和“故事线图谱”。
|
||||
|
||||
- 使用 `tablist`、`tab` 和 `tabpanel` 语义,当前项使用 `aria-selected="true"`。
|
||||
- 一级页面之间的导航由应用主导航承担,不复用 `PageTabs`。
|
||||
@@ -266,6 +274,11 @@
|
||||
- `danger-solid`:确认对话框中的最终危险操作。
|
||||
- `danger-zone`:设置页中集中展示的高影响操作区域。
|
||||
|
||||
`danger-solid` 必须是全局共享类,不依赖页面祖先;通过 Portal 挂到 `body` 后仍须生效。
|
||||
尺寸、圆角、字体、间距和禁用状态与 `primary-button`、`secondary-button` 共用规则,
|
||||
键盘焦点沿用全局 `button:focus-visible`。背景及可用态悬停使用危险实心色令牌,文字使用
|
||||
`--text-on-accent`;禁用时不触发悬停变色。
|
||||
|
||||
普通页面不得用实心红色按钮与主操作并列。危险入口必须使用具体动词和对象,例如“删除知识条目”,避免只写“确定”。
|
||||
|
||||
### 6.7 数据表格与活动列表
|
||||
@@ -283,9 +296,18 @@
|
||||
|
||||
- 浅色侧栏使用冰蓝灰表面,与白色主内容画布形成明确边界;深色侧栏使用比主画布略亮的蓝灰表面。
|
||||
- 一级导航与最近会话之间、最近会话与底部账户区之间必须有可见结构分隔线。浅色主题使用 `--border-default`,深色主题可在可辨前提下使用 `--border-subtle`。
|
||||
- 当前导航项和当前会话必须同时使用至少三种信号中的两种:强调背景、可见边框、图标或文字强调。浅色主题的当前项优先使用更完整的蓝色选中表面和较高字重。
|
||||
- 当前导航项和当前会话使用一致的淡蓝选中背景、强调色文字和中等字重,不额外描边或添加侧边标记;导航图标随选中状态使用强调色,悬停时保留选中状态。浅深主题遵循相同规则,强制颜色模式保留系统选中边框。
|
||||
- 固定导航行高至少 `36px`、行间距 `4px`,图标统一为 `16px`;导航与会话区域之间、会话区域与底部入口之间的分隔线上下均各留 `8px`(`--space-2`)。普通待办和待处理建议数量使用中性色徽标,不使用危险红色;徽标高度不得改变导航行高。搜索框获得内部焦点时显示完整焦点环。
|
||||
- 未选中项保持平整,不为每一行添加卡片边框或阴影。悬停反馈不得强于选中状态。
|
||||
- 项目选择器下方常驻跨项目活动汇总,空闲时显示“暂无活动”;项目计数与项目、会话级联菜单遵守
|
||||
- 最近会话列表在精细指针设备上使用细原生滚动条,滑块使用 `--border-control`、轨道透明;强制颜色与粗指针设备保留系统默认。
|
||||
- 会话区域使用 `--surface-raised` 与主导航区分,浅色为白底,深色为相邻的深蓝灰底。不显示“最近会话”标题,保留区域的无障碍名称,列表独立滚动。搜索与新对话共用主导航上方的一行,不在会话区域另设搜索头部;展开、关闭和键盘规则见[会话搜索](./docs/features/application-tool-navigation/ui-design.md#会话搜索)。搜索框使用透明背景、`--border-control` 边框与完整焦点环。
|
||||
- 窗口高度不超过 `600px` 时,侧栏允许整体纵向滚动,会话区域至少保留 `200px` 高度,避免搜索框、列表与底部入口相互覆盖。
|
||||
- 最近会话的顺序不随流式输出增量反复变化;置顶与新消息排序遵守
|
||||
[会话列表排序规则](./docs/features/application-tool-navigation/ui-design.md#会话置顶与操作菜单)。
|
||||
- 项目选择器顶行保留项目切换与新建按钮;项目设置统一从下拉列表对应行的管理按钮进入,关闭后焦点返回项目选择器。
|
||||
- 项目选择器与下方默认的新建对话按钮等宽,两行右侧图标按钮均宽 `36px`,与左侧控件间距均为 `8px`;搜索展开时项目选择器宽度保持不变。
|
||||
- 项目下拉列表通过 `FloatingPortal` 浮在侧栏与会话区上方,默认宽度 `380px`,不受侧栏裁剪;距窗口边缘至少 `16px`,窄窗口自动收窄,长列表内部滚动。浮层锚定项目选择按钮,窗口、滚动和尺寸变化后重新定位。
|
||||
- 项目选择器下方仅在有运行中、待处理或已完成未查看会话时显示跨项目活动汇总,无活动时隐藏;项目计数与项目、会话级联菜单遵守
|
||||
[项目活动汇总 UI 规则](./docs/features/application-tool-navigation/ui-design.md#10-项目活动汇总)。
|
||||
活动数量不改变项目菜单的稳定分组和顺序。
|
||||
- 桌面停靠状态下,主侧栏宽度允许在 `220px` 至 `420px` 范围内通过指针或键盘调整,
|
||||
@@ -314,9 +336,11 @@
|
||||
接受用户本机选择的 PNG、JPEG 或 WebP 内联数据,宽高不超过 `4096 × 4096`、总像素不
|
||||
超过 1600 万且大小不超过 `512 KB`;不得读取远程 Logo URL。名称或副标题溢出品牌区时
|
||||
单行截断,不能撑宽或挤压侧栏。
|
||||
- 侧栏底部固定显示文件、终端、浏览器和资源监控四个系统工具图标,设置按钮独立靠右。工具
|
||||
图标只负责打开或聚焦助手工作栏面板,不在侧栏复制工具内容。已有稳定设置入口时,不在顶栏
|
||||
重复提供同一入口。
|
||||
- 侧栏底部提供“应用 / Apps”和“设置 / Settings”两个横向等高 `nav-item` 按钮,均显示图标和文字,中间使用弱竖分隔线。“应用”带向下箭头,点击后向上展开轻量锚定菜单,无模态遮罩,只列已启用应用,不按打开历史或常驻状态筛选;点击行关闭菜单,知识库、智能心跳和魔法笔记在主内容区打开页面。本机推理监控从菜单、常驻入口或管理 Modal 打开时均使用独立 Modal,保留底层工作区。菜单底部“管理应用”使用 `LayoutGrid` 图标,打开既有整窗管理 Modal,标题仍为“应用中心 / Application Center”,共同应用设置沿用设置 Modal。
|
||||
五个内置应用的管理列表、菜单和侧栏共用保存顺序,菜单按启用过滤,侧栏按启用且常驻过滤。知识库始终启用并常驻,可调整位置;管理 Modal 提供“打开”和排序,不提供启用、常驻开关或通用空设置,标签为“始终显示”(英文 `Always shown`)。监督者沿用智能心跳身份,可修改启用和常驻,执行边界见[监督者逻辑](./docs/features/conversation-supervision/logic-design.md#应用启停与执行)。魔法笔记、本机推理监控同样可修改启用和常驻。设备共享在测试阶段默认关闭,标签为“可选”及启用状态,提供启用设置和排序,不提供常驻开关;启用后可从应用菜单及管理 Modal 在主内容区打开元数据页面,关闭时退出该页,规则见[设备共享 UI](./docs/features/device-sharing/ui-design.md#当前首期页面)。当前对话、运行记录和设置保持固定系统位置,不参与应用排序。
|
||||
设置持久化后的事件同步各入口及工具配置写入;重开中心刷新期间锁定修改,新快照不被旧读取覆盖。文件、终端、浏览器和资源监控只通过右侧工作栏访问,不在左侧复制入口。
|
||||
管理 Modal 始终使用紧凑单列横向列表,纵向对应保存顺序;左侧为图标、名称及简短用途,右侧为打开、可用的设置及上下移动操作,行间使用弱分隔线,不加卡片外框。窄窗口操作换行且不溢出,保留共享键盘焦点环。可选应用关闭时禁用“打开”。启用、常驻位于紧凑设置详情;五行均提供上下移动及拖动排序,不受启用或常驻影响,箭头按完整顺序判断边界。搜索时禁用拖动,箭头仍按完整顺序移动,不丢失未命中项。尺寸、返回与滚动重置规则见[应用中心 UI 设计](./docs/features/application-tool-navigation/ui-design.md)。已有稳定设置入口时,不在顶栏重复提供同一入口。
|
||||
- 本机推理监控 Modal 复用整窗 Portal、背景隔离和焦点循环;停止、重启及释放使用其上方的紧凑影响确认。Escape 只关闭最上层,操作提交期间禁止关闭及重复提交;焦点恢复与尺寸见[本机推理监控 UI 规则](./docs/features/application-tool-navigation/ui-design.md#91-本机推理管理)。
|
||||
|
||||
### 6.9 助手工作栏
|
||||
|
||||
@@ -334,19 +358,20 @@
|
||||
- Tab 行只显示已打开实例,行末始终提供“+”普通按钮;点击后在内容区打开应用选择页。
|
||||
“+”不使用 `tab` 语义,也不能因为终端或其他应用已经打开而消失。
|
||||
- 应用注册表分别声明单实例或多实例、默认打开、必须存在、可关闭和排序策略。在工作栏“+”目录中,
|
||||
单实例重复选择时聚焦已有 Tab;浏览器和终端每次选择创建独立实例。主侧栏系统工具快捷
|
||||
入口按当前上下文聚焦最近使用的匹配实例,额外实例仍从“+”目录创建。
|
||||
单实例重复选择时聚焦已有 Tab;浏览器和终端每次选择创建独立实例。选择已有 Tab 只激活
|
||||
该实例,保持其目标和历史;主侧栏不再提供按当前上下文匹配实例的重复工具入口。
|
||||
- Task Center 是默认跟随当前项目的单实例索引,不使用多实例模式。用户可在同一 Tab 内切换
|
||||
“当前项目”“全局任务”和“全部项目”;范围变化不得创建第二个 Task Center。每个 Task 只
|
||||
“当前项目”和“所有项目”;无项目任务在“所有项目”中标注“未绑定项目”。范围变化不得创建第二个 Task Center。每个 Task 只
|
||||
关联一条 Conversation,一条 Conversation 可以关联多个 Task;列表不得复制会话内容,也
|
||||
不得把 Job/Run 提升为可导航 UI 对象。
|
||||
- 各栏目读取当前会话或项目的对应内容;当前内容不可用时仍保留栏目入口并说明原因。
|
||||
- 能力、连接和内容可以动态变化,栏目入口不能随之自动隐藏。不可用状态必须说明原因、影响和可执行入口。
|
||||
- 各栏目按声明的范围读取内容;笔记使用应用级数据,选择和草稿不跟随项目或会话变化。当前内容不可用时仍保留栏目入口并说明原因。
|
||||
- 能力、连接和内容可以动态变化,栏目入口不能随之自动隐藏。不可用状态必须说明原因、影响和可执行入口。笔记遵循应用中心的业务启用设置,禁用前处理草稿,禁用后隐藏目录入口并移除笔记 Tab。
|
||||
- 用户可以主动排序栏目,并可关闭可关闭实例或恢复默认布局;任务中心和工作区不能隐藏或
|
||||
关闭。应用不能用用户偏好机制实现自动能力裁剪。
|
||||
- 已打开实例保留 `tablist`、`tab`、`tabpanel`、方向键、Home、End 和焦点恢复语义。
|
||||
实例过多时 Tab 区单行横向滚动,“+”保持可见,不换行或改成下拉框。不可关闭实例不渲染
|
||||
关闭按钮,也不响应关闭快捷命令。
|
||||
“+”位于滚动容器外并占用固定宽度;滚动箭头限定在剩余的页签区域内,不能覆盖添加入口。
|
||||
- 当前工作栏 Tab 必须同时使用连续内容表面、可见边框、顶部指示、图标或较高字重中的至少
|
||||
两种信号;悬停反馈不得强于当前选中态。Tab 关闭按钮保留固定布局空间,默认仅在对应 Tab 悬停或
|
||||
`focus-within` 时显示;无悬停或粗指针设备始终显示,键盘与辅助技术始终可访问。
|
||||
@@ -358,7 +383,7 @@
|
||||
记忆功能实现前不展示占位内容,未来的记忆与历史执行上下文归入关联 Task 的任务中心详情。
|
||||
- 徽标可以提示未解决意见、等待审批、失败或连接状态,但不能成为唯一状态信号,也不能无条件抢占当前栏目。
|
||||
- 浏览器 Tab 的内容顶部固定使用一条紧凑工具栏,按“返回、刷新/停止加载、地址输入、
|
||||
前往、交互、关闭浏览器”排列。“刷新”在当前页面加载时原位切换为“停止加载”;
|
||||
前往、交互、关闭浏览器”排列。“前往”使用纯右箭头图标按钮,保留悬停提示和可访问名称。“刷新”在当前页面加载时原位切换为“停止加载”;
|
||||
“关闭浏览器”必须使用明确文字和与页面导航控件清楚区分的危险操作视觉,不能继续标为
|
||||
含义模糊的“停止浏览器”。地址输入显示浏览器权威提交 URL,但用户编辑期间保留本地草稿,
|
||||
不被画面或状态刷新覆盖;提交裸域名时自动添加 `http://`,提交、失焦后的同步规则不得让
|
||||
@@ -373,6 +398,8 @@
|
||||
遮罩或隔离其内容。分栏计算排除最左侧主导航,主工作区与工作栏采用相同的 `300px`
|
||||
最小宽度;拖动保存两者在可用分栏宽度中的比例,任一侧到达最小值后都不得继续侵占。
|
||||
所有可容纳两侧最小值的尺寸均支持指针和键盘调整,并须保留全部栏目入口。
|
||||
- 工作栏分隔条须保留完整的 `9px` 拖拽命中区域,位于侧栏内部,不能被容器裁剪;栏目内容
|
||||
和原生浏览器视口须避开该区域。浏览器全屏时隐藏分隔条并取消这段预留空间。
|
||||
- 工作栏开关固定在顶栏 `.topbar__actions` 内、紧邻浅色/深色主题按钮之前,同一个按钮负责展开与收起并持续暴露状态;图标中的
|
||||
箭头方向必须随当前动作切换,收起状态提示展开,展开状态提示收起。按钮保持普通图标按钮
|
||||
视觉,不通过持续高亮表达展开状态。工作栏内部不再重复关闭按钮;当前会话没有 Task 时,
|
||||
@@ -397,14 +424,17 @@
|
||||
顶栏,不能保留一条未被遮罩的窗口控制色块。关闭操作由 Modal 自身提供。
|
||||
- 整窗固定遮罩通过 `createPortal(..., document.body)` 挂载,避免页面层叠上下文影响;
|
||||
保留 React Context、关闭与焦点管理,表单样式不得依赖已移出的页面祖先选择器。
|
||||
- 共享锚定菜单 `AnchoredMenu`、同行帮助 `InlineHelp` 和应用通知共用 `FloatingPortal` 的非模态 `popover="manual"`,由浏览器 top layer
|
||||
绘制,避免被 Modal 或滚动容器遮挡。菜单和帮助挂在触发按钮所属 Modal 内,通知跟随当前活动
|
||||
Modal,无 Modal 时挂在 `body`;不能只提高 `z-index` 或解除背景的 `inert`。
|
||||
- 原生浏览器 `WebContentsView` 不受网页 `z-index` 控制。应用级 Modal 打开时统一释放
|
||||
浏览器 viewport;非模态对话框、菜单、局部确认或应用通知与浏览器区域相交时也释放。
|
||||
浏览器 viewport;非模态对话框、菜单、同行帮助 tooltip、局部确认或应用通知与浏览器区域相交时也释放。
|
||||
浮层关闭后只恢复当前活动实例的 viewport,不关闭、重建或刷新网页。接线与测试见
|
||||
[浏览器多实例技术设计](./docs/features/assistant-workbar/browser-tabs-technical-design.md#4-renderer-与-viewport)。
|
||||
- 遮罩必须 `inset: 0` 铺满整窗,并由 `styles.css` 中统一的遮罩选择器声明
|
||||
`-webkit-app-region: no-drag`,否则 `.brand` 与 `.topbar` 的原生拖动区域会截获指针,
|
||||
使遮罩上的控件无法悬停和点击。新增整窗遮罩时扩展该共享选择器,不要在各自规则里复制
|
||||
`no-drag`,也不要用 `inset: 38px 0 0` 之类的偏移绕开标题栏。
|
||||
`no-drag`,也不要用 `inset: 38px 0 0` 之类的偏移绕开标题栏。遮罩回归按 fixed/inset 声明发现,不能只按 `backdrop` 或 `modal` 类名后缀筛选;知识引用的 `-dialog` 同样适用。
|
||||
- 当前会话存在 Task 时,Task 上下文栏位于标题栏下方并承载任务摘要与详情;没有 Task 时
|
||||
整行隐藏。折叠状态保持为 `46px` 单行表面,与右侧工作栏标签栏等高,左右分隔线必须水平
|
||||
对齐。该栏不重复放置新建入口。
|
||||
@@ -436,14 +466,18 @@
|
||||
尚未完成而禁用该入口。选择入口只展开流程,验证成功前不提交实际值;不得自动采用第一个
|
||||
候选。取消保持原值,验证错误在对应操作附近显示。
|
||||
- 项目选择器属于富信息单选菜单。收起时保持当前项目名称紧凑可见;展开后每项至少显示项目名称,并按数据可用性补充目录、远程来源或其他能帮助辨认项目的信息。托管 SSH 项目在“远程项目”内按已保存 SSH Host 做一层轻量分组,Host 标题旁显示真实 Agent 连接状态;项目副标题只显示远端路径,不重复“托管 SSH”类型。
|
||||
- 项目菜单区分“本地项目 / 远程项目 / 远程通道”,不把消息通道命名为远程项目。“远程项目(技术预览)”关闭时完整移除远程项目分组,而不是显示不可操作的残留选项;本地项目和远程通道继续可用。Agent 连接状态使用“未连接 / 连接中 / 就绪 / 异常”等文字标签,读取状态不得发起连接,且不能只依赖图标或颜色;新增项目来源时不得退回仅列名称的原生 `select`。
|
||||
- 项目菜单区分“本地项目 / 远程项目 / 远程通道”,不把消息通道命名为远程项目。“远程项目”关闭时完整移除远程项目分组,而不是显示不可操作的残留选项;本地项目和远程通道继续可用。Agent 连接状态使用“未连接 / 连接中 / 就绪 / 异常”等文字标签,读取状态不得发起连接,且不能只依赖图标或颜色;新增项目来源时不得退回仅列名称的原生 `select`。
|
||||
|
||||
### 6.12 应用通知与就地反馈
|
||||
|
||||
应用级通知统一进入全局通知视口,页面不得自行复制通知卡片或在内容流中长期堆放短期消息。
|
||||
|
||||
- 右上角通知使用紧凑的中性表面、弱边框和轻阴影,最大宽度为 `360px`,窄窗口保留两侧各 `16px`。状态色仅用于图标,不铺满卡片;正文直接呈现,通用状态标题仅供屏幕阅读器读取。图标、正文与关闭按钮统一垂直居中,不使用各自的上下偏移。
|
||||
- 异步操作成功、无需立即处理的信息,以及不属于某个字段的异步失败,使用应用级 `success`、`info` 或 `error` 通知。
|
||||
- 成功和信息通知默认在约 4.5 秒后自动消失;错误通知保持可见,直到用户关闭或同一去重键的更新替换它。
|
||||
- 成功和信息通知默认在约 2 秒后自动消失;错误通知保持可见,直到用户关闭或同一去重键的更新替换它。
|
||||
- 通知出现不抢焦点;Modal 打开时,通知关闭按钮纳入当前 Modal 的 Tab 循环。聚焦通知时
|
||||
Escape 返回原控件或当前 Modal 的可用控件,不关闭 Modal;关闭通知后同样恢复焦点。
|
||||
打开、关闭或切换 Modal 不重建通知,不重置自动消失计时。
|
||||
- 同一语义和文案的重复通知应去重。通知正文必须有长度上限,不包含凭据、私人内容或未脱敏的提供商响应。
|
||||
- 字段校验、破坏性确认、操作进度、阻塞整个页面的状态,以及需要就地重试或修正的错误保留在相关控件附近。
|
||||
- 就地错误必须与对应字段或操作建立程序化关联;全局错误使用 `alert` 和 assertive 实时区域,成功与信息使用 `status` 和 polite 实时区域。
|
||||
@@ -458,10 +492,34 @@ Switch 用于在两个持久状态之间立即切换,例如启用能力、开
|
||||
- Checkbox 保留原生 Checkbox 语义和方形勾选视觉,不得添加 `role="switch"`。多项分配、列表选择、确认声明和“保存时清除密钥”等一次性选择均属于 Checkbox。
|
||||
- 不创建页面专属 Switch 样式。需要紧凑布局时仍复用同一轨道、滑块、焦点环、禁用状态和动效,只调整共享组件支持的布局变体。
|
||||
- Switch 支持 Tab 聚焦和 Space 切换,键盘焦点至少显示 `2px` 高对比焦点环。可见标签应描述被控制的能力,不能只显示“开 / 关”。
|
||||
- 共享开关行统一为标签在左、Switch 靠右,说明放在标签下方;不因 JSX 中输入控件与文字的先后顺序改变视觉位置。窄窗口允许文字换行,开关保持固定尺寸。Checkbox 和 Radio 保留各自的选择布局。
|
||||
- 异步切换期间禁用重复操作并保留原状态。失败时恢复或保留最后确认状态,通过应用通知或就地可恢复错误说明原因。
|
||||
- 涉及联网、上传、电脑控制或其他外部影响的 Switch,附近必须持续说明数据去向、权限范围或风险,不能只靠设置名称表达影响。
|
||||
- 自动化测试应按 `switch` 角色查询二元开关,按 `checkbox` 角色查询多选或确认项,防止视觉迁移后语义回退。
|
||||
|
||||
### 6.14 InlineHelp 同行帮助
|
||||
|
||||
页面、区块和字段的补充用途、格式及操作说明统一收进标题或标签旁的 `InlineHelp` 问号按钮。
|
||||
`PageHeader` 与 `SettingsSectionHeader` 通过 `help` 接收这类内容;`description` 保留需要常显的上下文,两处不重复说明。描述缺省或为空时不渲染段落,不保留空行。
|
||||
当前范围、状态、字段错误、操作进度、风险、费用和影响用户决策的限制仍就地可见。混合文案先拆分,再将补充说明移入帮助。
|
||||
|
||||
- 标题或标签与问号使用共享 `inline-help-label` 同行排列,不为帮助另占说明行;字段标签通过 `htmlFor` 关联控件,帮助按钮放在 `label` 外,避免点击帮助触发输入或开关。
|
||||
- 悬停或键盘聚焦按钮时展开;指针可移入浮层阅读。点击(含 Enter、Space 激活)固定展开,再次点击关闭;未固定时,指针和焦点均离开后关闭。
|
||||
- Escape、点击外部或焦点移到外部关闭帮助。Escape 优先关闭帮助,不同时关闭所属 Modal;展开和关闭不主动转移焦点,不建立焦点陷阱。
|
||||
- 按钮提供与标题或标签对应的可访问名称、`aria-expanded`,展开时通过 `aria-controls`、`aria-describedby` 关联 `role="tooltip"` 内容。提供稳定帮助 ID 时,关闭状态保留同 ID 的隐藏说明,展开时由浮层替换,确保字段的 `aria-describedby` 始终有唯一目标。持久标签与必要提示不得依赖浮层。
|
||||
- SSH 密码字段常显认证用途和验证成功后加密保存的说明;Main 处理及不写入命令行、日志、普通设置的细节收进同行帮助。应用常驻说明与关闭后果使用独立翻译键,关闭后果保持常显,不按标点拆分翻译文本。
|
||||
- 浮层复用 `FloatingPortal`,遵循 6.10 的 Modal 归属规则。宽度最多 `360px`,四周保留 `16px` 视口边距;空间不足时翻转或调整位置,长内容在浮层内滚动,窗口及滚动变化后重新定位。
|
||||
- 问号使用共享 `28px × 28px` 点击区域、焦点环与主题令牌。不得另做原生 `title` 长说明或页面专属帮助浮层。
|
||||
|
||||
### 6.15 表单 Modal
|
||||
|
||||
- 复用 `styles.css` 中的 `custom-task-dialog__surface`、`custom-task-dialog__header`、`custom-task-dialog__content` 和 `custom-task-dialog__actions`,与定制任务、SSH Host 等现有弹窗使用同一结构;不为单个业务另建 Modal 框架。
|
||||
- 标题放在 `header` 左侧,右侧只放共享 `icon-button` 的 X 关闭按钮,使用本地化 `aria-label` 和工具提示。取消不放在标题下方或表单顶部。
|
||||
- `footer` 使用共享操作区,按钮整体右对齐,次要取消在前、主要保存在后。放弃修改确认沿用同一操作区,继续编辑在前、危险操作在后。
|
||||
- header、滚动 body、footer 是 surface 的三个直接子区域。沿用共享 Grid 的 `auto minmax(0, 1fr) auto`,只有 body 滚动,短窗口仍可见标题、关闭和提交操作;窄窗口沿用共享外边距、内边距和主题令牌。
|
||||
- X、取消、Escape 和遮罩关闭共用业务关闭检查,不绕过未保存确认或提交锁定。确认中的 Escape 或 X 返回编辑;继续复用 `activateModalFocus`、`trapTabFocus`,保留背景隔离、初始焦点和关闭后的焦点恢复。保存失败保留草稿及可恢复错误。
|
||||
- 从已有 Modal 打开的子 Modal 同样复用 `--z-dialog` 和 `activateModalFocus`,以较后挂载的 body Portal 绘制在父层上方,并隔离父层;不能只手动聚焦或使用旧的页面层级常量。子层关闭后恢复父层的隔离状态与触发按钮焦点,Escape 不继续关闭父层。
|
||||
|
||||
## 7. 交互状态
|
||||
|
||||
所有可交互组件必须实现:
|
||||
@@ -477,6 +535,8 @@ Switch 用于在两个持久状态之间立即切换,例如启用能力、开
|
||||
|
||||
异步提交成功后更新内容并通过统一应用通知提供明确反馈。失败时保留用户输入和筛选上下文。
|
||||
|
||||
共享控件的状态规则须在容器组合中生效:菜单通用按钮样式不能覆盖危险按钮,普通图标悬停不能覆盖危险图标或作用于禁用按钮,页签悬停保留当前选中态。深色主题通过令牌提供控件颜色,避免用页面祖先下的通用输入规则替换控件边框。验证时读取最终 computed style;共享令牌对比度与源码规则存在性检查不能代替容器内、body Portal 和伪状态回归。修复与验证记录见[共享控件审查](docs/quality/shared-controls-review-2026-09-27.md)。
|
||||
|
||||
## 8. 范围与数据语义
|
||||
|
||||
### 8.1 页面范围
|
||||
@@ -605,8 +665,9 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
||||
右边缘与用户气泡右边缘对齐;两侧为头像保留独立安全边距,滚动条出现或工作栏缩放不得改变对齐。
|
||||
- 对话标题和当前项目范围位于 `PageHeader` 或对话上下文区,不在消息流中重复。
|
||||
- 已结束且包含正文的助手回复在底部提供带可访问名称和工具提示的复制图标;复制 Markdown
|
||||
原文,不包含推理过程、工具日志或引用元数据,结果通过应用通知反馈。流式回复完成前不显示
|
||||
复制入口。
|
||||
原文,不包含推理过程、工具日志或引用元数据。复制成功后,当前按钮显示使用 `--success`
|
||||
的绿色对号,可访问名称和工具提示同步表示已复制,3 秒后恢复复制图标;重复复制成功重新
|
||||
计时,不显示成功通知。失败仍通过应用错误通知反馈。流式回复完成前不显示复制入口。
|
||||
- 默认开启“会话 HTML 渲染”时,已完成 Agent 回复中的完整 HTML 与 `html` / `htm`
|
||||
代码块在原位置显示隔离静态预览;用户消息、推理过程和流式中的回复不得执行 HTML。
|
||||
每个预览持续标注脚本和网络已禁用,并在标题右侧复用与 Mermaid 图表相同的图标按钮组:
|
||||
@@ -614,6 +675,10 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
||||
全屏预览遵循统一 Modal 的背景隔离、初始焦点、Tab 循环、Escape 关闭和焦点恢复规则,
|
||||
并沿用同一套清理与 Sandbox/CSP 边界。关闭平台功能后必须显示 HTML 源码,不能隐藏内容
|
||||
或保留旧预览。
|
||||
Tab 和 Shift+Tab 必须能进入及离开 iframe;焦点在预览内部时 Escape 仍关闭当前全屏层并返回入口。空 sandbox 与禁脚本 CSP 保持不变,跨文档键盘接线见[HTML 渲染技术设计](docs/features/conversation-html-rendering/technical-design.md#ui-与无障碍)。
|
||||
- Mermaid 大图支持缩放和拖动;图形超过画布时,四个边缘都必须能通过滚动到达,不能因居中而裁掉顶部或左侧。
|
||||
弹窗工具栏提供“导出 PNG”,保存完整图形,不受当前缩放和滚动位置影响;背景使用当前主题的画布底色。
|
||||
导出按原图四周留出 `16px`,以 2 倍分辨率绘制,任一边最多 `4096px`。导出期间禁用重复点击,失败在按钮旁提示重试。
|
||||
- 本地会话的更多操作提供“在新会话中继续”。创建前必须先持久化当前稳定消息;有活动运行
|
||||
或待发送内容时不可创建。新会话复制聊天内容与当前 Runtime/知识检索选择,重置压缩状态,
|
||||
使用新的会话和消息 ID,且不复制 Task、队列、运行、记忆或成果归属。
|
||||
@@ -639,8 +704,9 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
||||
- 已选择的工作模式在触发按钮中只显示 `Ask` 或 `Execute`;完整中文含义和说明保留在菜单选项、可访问名称及输入区下方的模式说明中。
|
||||
- 宽度大于 `700px` 时,主工具栏的添加内容、Runtime、模式、会话选项与发送保持同一行。窄输入区按容器宽度换行,保留发送及停止入口;选项浮层不得超出输入区宽度,不再常驻独立的 Runtime 专属功能行。
|
||||
- 输入框原生支持 `Ctrl+V`:文本直接进入草稿,系统截图和从文件管理器复制的一个或多个支持文件加入本次消息附件。文件粘贴与上传按钮共用读取、解析进度和数量限制;读取期间阻止重复导入及发送,错误就地关联输入框和附件按钮。具体类型、大小及批次失败规则见[聊天附件要求](./docs/features/document-processing/prd.md#321-聊天附件入口)。默认工具栏不提供独立“读取剪贴板”“截取当前屏幕”和“选择应用窗口”入口。
|
||||
- “Enter 发送 · Shift+Enter 换行 · Ctrl+V 粘贴文件、图片或文本”等输入操作提示放在空输入框内部,作为主占位文案的次级行;不得在输入框下方单独占用第二行。输入框下方只保留一行当前模式、安全边界或全局快捷键说明。
|
||||
- 空输入框使用三行原生占位文案:第一行为消息提示(生图模式保留图片描述提示),第二行为“Enter 发送 · Shift+Enter 换行 · Ctrl+V 粘贴”,第三行为“Ctrl+N 新建对话 · 实际已注册快捷键 快捷唤起”。新建与粘贴快捷键按平台显示,macOS 使用 Command;全局快捷键不可用时省略该项。输入文字后占位提示自然隐藏,输入框下方不再重复全局快捷键,保留模式、安全边界和其他元信息。
|
||||
- 输入操作提示不能替代表单的可访问名称,输入框始终保留持久的程序化标签。
|
||||
- 草稿与消息中的附件操作使用紧凑图标按钮,并提供悬停和键盘焦点提示;文件名、解析状态与操作行的布局遵循[附件操作规则](./docs/features/document-processing/ui-design.md#12-附件操作菜单)。
|
||||
- 空对话展示可执行的起始建议,发送失败保留输入并提供重试。
|
||||
- 消息流的滚动范围必须使用已渲染消息的真实高度。长会话可以按批次只挂载最近消息并由用户
|
||||
加载更早内容,但不得用固定的消息占位高度替代实际布局,避免推理、工具列表或长正文被
|
||||
@@ -669,12 +735,17 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
||||
|
||||
### 13.4 智能心跳
|
||||
|
||||
- 使用 `dashboard` 壳层。
|
||||
- 顶部先呈现运行状态、实际范围和主操作,再呈现指标、建议、历史与配置。
|
||||
- 监督者入口使用 `supervisor` 壳层;智能心跳设置位于同一壳层内。
|
||||
- 顶层页签为“工作回顾 / 故事线图谱 / 智能心跳 / 活动记录 / 设置”,使用一套共享 PageTabs,窄窗口单行横向滚动。页签下不重复同名标题,也不保留空标题容器;所选回顾使用“监督结果”标题,历史结果不得标为最新。
|
||||
- 工作回顾只展示统一监督结果、历史选择和紧凑的新回顾控件;结果保留自身范围、覆盖区间和生成时间,图谱读取同一结果 ID。智能心跳集中展示心跳计划、监督建议和只读的历史心跳报告;活动记录只展示统一执行活动、进度和详情。每页保留所属数据的一处刷新入口,范围与时间选择仅作用于新回顾请求。详细布局与阅读规则见[监督者 UI 设计](docs/features/conversation-supervision/ui-design.md#应用入口)。
|
||||
- 监督者保留统一页面壳层;工作回顾与活动子面板铺满壳层可用内容宽度,与页签左右对齐。工具栏、历史导航、状态和结果共享宽度,不再增加子面板宽度上限。工作回顾按纵向排列,空态独占工具栏下方整行并水平居中。活动筛选按内容紧邻排列,阶段使用编号圆点和连接线,次要统计收进详情;具体布局见[监督者 UI 设计](docs/features/conversation-supervision/ui-design.md)。
|
||||
- 范围明确区分 Global 与指定的一个或多个 Project;Global 与指定项目互斥,多项目选择使用
|
||||
Checkbox,不能依靠进入页面时的当前项目推断。
|
||||
- 状态卡片使用统一状态令牌,不只依赖颜色。
|
||||
- 保留“运行概览 / 待处理建议 / 报告与记录 / 心跳计划”四个同级页面。
|
||||
- 活动记录先显示运行状态、时间、范围及实际阶段,恢复操作紧邻进度。原始错误默认折叠,展开后在有界面板内滚动并可选择复制;配置归入次要详情。任务层级只展示可查询的记录,缺失阶段明确标注,不能用剩余来源为零推断发布成功。具体阶段与分页规则见[监督者活动设计](docs/features/conversation-supervision/ui-design.md#活动)。
|
||||
- 智能心跳的“心跳计划”卡片内直接排列状态说明、创建按钮和计划列表,不再嵌套第二层边框;仅保留一个创建按钮,说明文字按无计划、全部暂停、正常三种状态只显示一条。创建与编辑复用同一份表单及共享 Modal 焦点、背景隔离和遮罩样式;未保存关闭须确认,保存失败保留草稿,编辑暂停计划不恢复运行。“执行记录”进入按该计划 ID 筛选的活动页,活动页不重复显示心跳审计列表。
|
||||
- 设置只展示模型超时与并发、分页、分批和单次响应容量,不含计划、报告、审计或嵌套页签,不显示整页全局徽标或独立刷新行。
|
||||
- 监督者的设置、帮助、空态及错误文案不得向终端用户展示内部路线图、交付进度或设计与实现缺项;只说明当前行为、适用范围和可执行的下一步。不得因此夸大为提供商全局限流或完整语义识别;真实的运行失败、超时、暂停、来源失效及恢复操作必须保留。
|
||||
- 智能心跳菜单入口是完整配置的权威位置;任务中心和设置中心不得复制同一 CRUD 表单。
|
||||
- 分区记忆协同与唤起条件尚未实现,不得显示占位入口或平行的记忆类型。
|
||||
|
||||
@@ -682,14 +753,19 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
||||
|
||||
- 保留现有助手工作栏入口,首期在窄栏内适度完善,不先扩张成新的独立一级页面。
|
||||
- 只展示 Task;普通 Conversation、Job、Run、工具步骤、Subagent 和心跳事项不独立占行。
|
||||
- 每项显示名称、关联 Conversation、Global 或 Project 范围、Ask/Execute、状态、最近进展、
|
||||
最近真实活动时间及需要关注信息。
|
||||
- 范围固定为当前项目,不提供全局或所有项目入口;切换会话不筛掉本项目其他会话的任务。
|
||||
- 顶部紧凑显示当前会话消息总数和 Agent 累计回复时间,按每轮请求区间累加,包含轮内工具和
|
||||
审批等待,不计轮间空闲。历史缺失显示不完整提示,远程缺少可靠证据时不虚构用时。
|
||||
- 每项显示名称、关联 Conversation、所属项目、Ask/Execute、状态、最近进展、
|
||||
最近真实活动时间、累计执行时间及需要关注信息。任务用时汇总历次执行请求,不按父任务生命周期计算。
|
||||
- 点击列表项打开关联 Conversation 并定位该 Task,不显示第二份内容载体。
|
||||
- 需要关注、进行中、已暂停和已结束使用共享 `SegmentedControl`;窄栏不足时单行滚动。
|
||||
- 项目任务标题右侧依次为“+”新建和筛选图标;图标按钮提供可访问名称,筛选按钮提供展开状态。
|
||||
状态筛选默认收起且为全部;展开后用共享 `SegmentedControl` 提供全部、进行中、暂停、已结束,
|
||||
窄栏不足时单行滚动。收起保留条件,非全部时图标高亮并显示条件。状态归类遵循[任务中心 PRD](docs/features/task-and-job/task-center-prd.md#5-交互)。
|
||||
- 完整消息留在 Conversation;长错误、工具、审批和成果按 Task 关联到 Runtime、活动记录和
|
||||
成果查看器,不撑高列表,也不显示 Job/Run 树。
|
||||
- “新建定制任务”使用共享 Modal,明确选择当前或新 Conversation。聊天入口默认当前
|
||||
Conversation,Task Center 入口默认新 Conversation;选择必须持续可见。
|
||||
- “新建定制任务”使用共享 Modal,默认立即执行和当前 Conversation,可选择本项目其他可用
|
||||
Conversation 或新建会话,选择必须持续可见。内容必填,名称可选并从内容生成;定时提供单次、每日和每周。
|
||||
- 定时消息创建 Modal 使用关联 Conversation 的 Runtime 与 Ask/Execute 模式,不再提供独立
|
||||
模式开关。未运行计划的详情显示会话当前模式,已运行任务显示该次执行记录的模式;
|
||||
不得用旧计划字段表示下一次执行权限。不支持工具时不得静默降级。
|
||||
@@ -700,60 +776,98 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
||||
- 默认视图按“项目 → 任务或会话 → 活动详情”组织,项目范围持续可见,任务或会话详情可以折叠。
|
||||
- 任务或会话的综合状态以最近一次顶层请求对应的最终 Agent 结果为准;最终结果尚未产生时使用该请求的当前状态。中间工具或子专家的失败、取消和中断保留在活动详情中,但不得覆盖最终成功状态。
|
||||
- 活动时间线按项目分组、按任务或会话建立横向轨道,所有轨道共享同一执行顺序并按事件时间排列,以带身份名称的节点表达用户、主 Agent、子专家、工具、审批、状态和并行关系。节点内使用 `U / G / S / T / A` 拉丁字母简称,节点下显示完整身份或名称;选择节点后显示所属会话、不可变范围快照和完整详情。
|
||||
- 用量统计与活动记录分离,支持按项目、会话和模型切换统计维度,宽表格在独立容器内横向滚动。
|
||||
- 用量统计与活动记录分离,支持按项目、会话和模型切换统计维度;按项目或会话分组时默认收起并显示汇总,展开后显示 Runtime / 模型子行,按模型分组时保持平铺。宽表格在独立容器内横向滚动。
|
||||
- 活动状态筛选使用 `SegmentedControl`,不与页面页签混合。清空历史遵循破坏性操作政策。
|
||||
|
||||
### 13.7 魔法笔记
|
||||
|
||||
- “笔记 / 待办”属于同一工作台内的同级内容面板,使用 `PageTabs` 的 `segmented` 视觉变体,与模型设置的分段控件保持同一外观。
|
||||
- 页签切换保留 `tablist`、`tab` 和 `tabpanel` 语义;待办状态仍使用独立的 `SegmentedControl`,不得与内容页签合并。
|
||||
- 笔记编辑器保留 Quill 原生列表快捷输入:输入 `-`、`*`、`1.`、`[ ]` 或 `[x]` 后按空格,分别转换为项目符号、编号或待办清单。快捷输入只生成现有 Delta 富文本,不引入 Markdown 源码模式、第二套持久化格式或第三方 Markdown 插件;编辑区附近持续提供简短提示。
|
||||
- 左侧笔记与待办列表在双栏布局中允许在 `180px` 至 `360px` 范围内通过指针或键盘调整,
|
||||
也可从页面标题区隐藏和恢复;宽度与显示状态均记住用户选择。分隔条保留 `separator`
|
||||
语义及方向键、Home、End 操作,窄窗口堆叠布局不显示拖拽分隔条。
|
||||
- 左侧笔记列表依次显示标题、最多两行内容预览,以及同一行的记录数和本地化更新时间。记录数靠左、更新时间靠右,窄宽度下均保持单行省略,不挤压标题或扩大列表栏。
|
||||
会话联动已接入:右侧提供可关闭的单实例笔记 Tab,通过工作栏“+ → 笔记”打开;左下角和聊天页头不再提供重复的“快速笔记”入口。助手消息和会话标题菜单可打开文字采集预览;保存记录在面板和完整工作区显示独立来源条。草稿由 App 保留,关闭、替换及禁用前确认。具体交互由[魔法笔记会话联动界面设计](./docs/features/magic-notes/ui-design.md)维护。真实 Electron 验收已通过,全仓验证未全绿,见[验证进度](./docs/features/magic-notes/progress.md)。下列规则描述独立工作区。
|
||||
|
||||
- 未打开笔记详情时,笔记列表与待办使用页头普通按钮切换。按钮显示目标“切换到待办”或“切换到笔记”及对应图标,按内容宽度显示,不拉伸;笔记的新建入口位于左侧列表标题行,待办页的新建入口位于页头。窄窗口操作组可换行到标题下方,保留控件高度。
|
||||
- 切换按钮不使用页签语义,切换后焦点回到同一个按钮;内容 section 持续提供当前列表的 `aria-label`。切换沿用草稿保护入口,待办状态仍使用独立的 `SegmentedControl`。
|
||||
- 文字记录编辑器保留 Quill 原生列表快捷输入:输入 `-`、`*`、`1.`、`[ ]` 或 `[x]` 后按空格,分别转换为项目符号、编号或待办清单。快捷输入只生成现有 Delta 富文本,不引入 Markdown 源码模式、第二套文字记录持久化格式或第三方 Markdown 插件;新建文字记录的操作提示收进类型选择旁的 `InlineHelp`。
|
||||
- 新记录可选择“文字 / 画布”;已保存画布提供分页阅读和“展开编辑画布”。画布使用 PeopleLib 的 Fabric 分页批注与 Quill 跨页正文,支持纸张模板、增删页、笔迹、高亮、整对象橡皮、选择及移动缩放旋转、浮动文字、图片和 PDF 底版。画布有固定页面边界;已保存记录、待办来源和编辑态默认容纳完整当前页及工具栏,高度随当前页实际显示尺寸变化,纵向由外层笔记流滚动。多页仍属于一条记录,通过页码翻页;窄窗口工具栏换行,纸张仅在自身视口内横向滚动,不撑宽外层页面。
|
||||
- 画布通过“保存记录 / 保存修改”手动持久化,离开或切换记录类型前沿用未保存草稿确认。撤销/重做作用于当前批注页或 Quill 正文模式,增删页和 PDF 导入不属于全局撤销历史。删除非空批注页或 PDF 页需要在五秒内再次点击确认。
|
||||
- 画布编辑与只读工具栏均提供缩小、当前百分比、放大和“适应宽度”。默认 100%,范围 25%–300%;加减每次调整 25 个百分点,点击百分比还原 100%。画布区域内 Ctrl/Command + 滚轮连续缩放,普通滚轮和 Shift 横滚保持原有方向与边界传递。适应宽度按实际视口计算,窗口或分栏调整后自动更新;翻页保留固定比例或适应宽度策略。缩放仅影响显示,整页高度与横向滚动范围随之变化,不新增内层纵向滚动,也不改变正文分页、保存内容、导出尺寸或未保存状态。控件沿用画布的主题、焦点环与提示,窄屏整组换行,适应宽度通过 `aria-pressed` 和选中样式表达状态。
|
||||
- “导入 PDF”保留页面并提取原生文字供搜索与 AI 使用,扫描页不自动 OCR。“导出 PDF”将底版、正文和批注逐页合成为栅格 PDF,不保留可搜索文字层;当前 UI 无 PNG 下载按钮,页面 PNG 捕获仅作为程序接口提供。
|
||||
- 画布新建与编辑态不显示常驻分析提示。操作放在编辑区右上角:新建与类型选择同排,顺序为“分析画布草稿、保存记录”;编辑放在记录头部,顺序为“分析画布草稿、取消、保存修改”。保存为最右侧主按钮,窄屏允许换行;底部不重复操作,新建不增加取消按钮。`immediate` 仍通过按钮手动分析,不随笔画自动请求;`after-save-auto` 在手动保存后分析。设置读取或页面捕获失败不阻断保存,保存成功后通过应用通知说明自动分析失败,可稍后重新分析;模型分析失败也不回滚已保存内容。
|
||||
- 魔法笔记设置提供“发送画布页数”单选框,复用设置表单 `.field` 控件样式,范围 1~8,默认 1。标题同行的问号使用 `InlineHelp`,下拉框通过稳定的帮助 ID 关联展开的说明,交互遵循 6.14。说明包含当前页序前 N 页、不足时发送全部页、数量指单个画布页数而非记录数,以及完整保存与导出不受影响。修改后沿用设置保存反馈,下次分析生效。
|
||||
- 草稿、已保存画布记录及来源为画布的待办,按默认模型的 `supportsImageInput` 使用所选前 N 页图像与文字,或只发送同一范围文字并标注“仅文字分析(未使用画布图像)”。所选页只有视觉内容的记录/草稿遇到文字模型时提示切换支持图像输入的默认模型,不声称已理解笔迹或布局。待办分析同时保留任务标题、说明和所选来源画布页的上下文。
|
||||
- 画布文字评论在提取的 `plainText` 不变时保留;图像评论随画布内容或布局变化失效,在保存后自动评论模式下重新分析。草稿内容变化清除旧草稿分析,评论区显示实际使用的输入模式。存储和判定细节见[画布技术设计](./docs/features/magic-notes/technical-design.md#canvas-analysis-and-comments)。
|
||||
- 总览是全宽的“全部笔记”页,以自适应卡片网格(最小 `260px`)显示全部笔记,不显示右侧空白详情,也不自动打开第一篇;卡片显示标题、短日期、两行预览和记录数。打开笔记后切为左侧紧凑列表与右侧详情,页头右上角显示带文字的“返回总览”,点击回到全部笔记页并恢复搜索、滚动位置与卡片焦点;“切换到待办”只在总览显示。列表按紧凑两行行项显示:第一行为标题与短日期(今天显示时间,其余显示月/日,完整时间在工具提示中),第二行为单行预览;记录数只提供给读屏。行项无边框、透明底色,悬停为次级底色,选中为淡蓝底色加左侧 `3px` 强调标记;“更多”按钮悬停或聚焦时覆盖在日期位置。保留搜索、置顶菜单和当前选中状态。打开笔记只更新右侧详情;列表独立滚动,切换保留搜索和列表位置,并在工作区挂载期间记住各篇笔记的阅读位置。明确定位某条记录时优先跳到该记录。
|
||||
- 笔记列表默认展开、宽 `280px`,支持拖动分隔线在 `240–420px` 内调宽,方向键每次调整 `16px`,Home/End 跳到边界;桌面端详情页提供列表显隐开关,记住宽度和展开偏好。工作区宽度不超过 `900px` 时切换显示列表与详情,返回恢复列表位置和入口焦点,不覆盖桌面偏好。新建成功后打开新笔记;切换、新建和删除当前笔记都保护未保存的正文与标题。
|
||||
- 待办页是独立任务视图:搜索任务标题、说明或来源笔记,搜索框右侧同一工具栏放置“全部 / 未完成 / 已完成”筛选,显示匹配数与总数;任务按来源笔记分组。列表独立滚动,页头、搜索和筛选保持可见,窄屏工具栏空间不足时换行。直接勾选完成或恢复未完成,不自动选择任务;状态变化后按当前筛选更新列表。
|
||||
- 待办内容容器宽于 `700px` 时,任务列表稳定在左侧,右侧显示当前任务说明、来源记录和 AI 评论;未选择时右侧提示选择任务。点击任务只更新右侧,不改变列表宽度、行位置或滚动位置,再次点击取消选择。AI 不增加第三栏。容器不超过 `700px` 时,按需详情替换同一内容区域,并提供“返回待办列表”;列表保持挂载,返回恢复滚动和任务行焦点,不将列表与详情上下堆叠。任务按钮提供 `aria-expanded` 和 `aria-controls`。
|
||||
- 待办左右栏分割线支持拖拽,并记住列表宽度;键盘左右方向键每次调整 `16px`,Home/End 调整至边界。列表默认 `320px`、最小 `240px`,详情至少保留 `300px`;窄屏隐藏分割线,临时缩窄窗口不覆盖已保存宽度。每个来源笔记标题可独立收起或展开待办列表,保留标题与数量,通过 `aria-expanded` 和 `aria-controls` 表明状态;收起不清除右侧当前详情。
|
||||
- 待办 AI 评论保留方向选择、流式输出、重新分析和已有结果;来源加载失败通过应用通知提示,并提供就地重试。从待办打开原笔记后,“返回待办”恢复任务选择、搜索、筛选和列表滚动;宽屏焦点返回任务行,窄屏返回待办详情中的返回按钮。
|
||||
- 笔记详情主区按新到旧连续展示全部记录。打开笔记即在标题下、历史记录上显示空白输入区,保留文字/画布类型选择和保存操作,标题栏与索引均不提供“新增记录”按钮。新增保存成功后重建空白输入区,保留所选类型;自动分析结束也不转入旧记录编辑。旧记录通过显式编辑入口修改,保存使用同一 ID 和返回的修订号。点击索引或当前笔记内的 AI 来源只按 entry ID 滚动定位,不卸载编辑器、不清草稿、不询问离开。空白输入区离开不提示,返回总览、打开其他笔记或改为编辑另一条记录时仍保护未保存修改。
|
||||
- 打开笔记后页面一级标题仍为“魔法笔记”;笔记标题只在记录流顶部出现一次,使用 `22px` 的可编辑大标题,悬停显示下划线,聚焦显示强调色下划线,保留失焦保存、Enter 与 Escape。标题上方为吸顶的细工具栏,依次放置收起笔记列表、记录索引、条数与更新时间、AI 评论开关,均为带工具提示和可访问名称的图标按钮。记录流左右留白 `24px`(窄于 `600px` 时 `12px`),正文最大宽度 `880px` 并居中;吸顶工具栏不受此限制,始终铺满记录流宽度,AI 开关贴在右边缘,底部分隔线横贯整栏。
|
||||
- 桌面笔记详情的列表标题行、正文工具栏与 AI 评论方向标签行顶部对齐,首行统一使用 `40px` 控件高度并垂直居中;AI 区域不叠加面板和表单的顶部留白。
|
||||
- 文字记录的新建与编辑区域铺满卡片内沿,格式工具栏紧接头部,输入区域紧接工具栏;编辑器外层不再增加内边距或圆角。正文保留编辑器内部的文字留白,底部操作栏独立保留内边距与顶部分隔线;画布仍保留 `12px` 内容间距。
|
||||
- 新建记录输入区空闲时收成一行虚线提示框,隐藏类型切换、格式工具栏和保存栏;聚焦、已有草稿、选择画布或出现校验错误时展开,初始约四行并随内容增高。保存成功或切换笔记后恢复为一行。此规则不作用于旧记录编辑器。
|
||||
- 已保存记录的 AI 分析、编辑、删除为 `28px` 图标按钮,精细指针设备上悬停或键盘聚焦记录时才显示;删除悬停时使用危险色,确认条保持原样。粗指针设备始终显示。笔记内部列表、记录流、AI 区域和文字编辑区在精细指针设备上使用细原生滚动条,轨道透明;强制颜色与粗指针设备保留系统默认。说明、摘要和输入提示使用 `--font-body` 与 `--text-secondary`,时间和短标签保留 `--font-caption`。
|
||||
- 左侧记录索引默认收起,只在用户展开后记住展开状态;展开宽度默认 `168px`,可拖动右边界在 `140–320px` 内调整栏宽,缩略图随列宽等比适配。开关位于笔记工具栏,提供 `aria-controls`、展开状态和动作提示;收起后完全移除索引列、分隔条、间隙、边框及轨道,不占宽度,隐藏内容不可聚焦。右侧 AI 评论独立显示、滚动和收起,不与索引共享页签。每条记录一个导航项:画布显示真实首页缩略图及总页数,文字显示摘要,两者均显示类型和时间。两侧分别记住桌面展开状态和宽度,共用原有布局偏好;宽布局分隔条共用样式及 `separator` 语义,方向键每次调整 `16px`,Home、End 跳到当前上下限。两侧宽度按可用空间协调限制,为主区保留至少 `300px`,窗口缩小时不覆盖保存的偏好。内容容器不超过 `800px` 时索引默认隐藏,由同一标题栏按钮开闭标题栏下方固定 `168px` 的抽屉,不显示调宽分隔条;标题栏自身保留控件高度,不保留轨道。选择记录或按 Escape 后关闭,Escape 将焦点返回开关。AI 移至底部,高度不超过可用布局的 40% 且最多 `280px`,不显示分隔条。
|
||||
AI 评论默认不占列:当前笔记已有评论、正在分析或存在草稿评论时自动展开;用户对该笔记手动收起后保持收起,直到发起新的分析。无评论时手动展开即视为固定,固定状态跨笔记和重启保留,再次收起即取消固定。AI 评论的唯一显隐入口位于笔记工具栏右端,使用紧凑面板图标按钮;展开时应用共享选中样式,图标随显示或隐藏动作切换,并提供对应的工具提示、可访问名称、`aria-expanded` 和 `aria-controls`。评论面板内部不再显示重复的“AI 评论”标题或隐藏按钮行,保留区域的可访问名称、评论方向选择及各记录的分析操作。
|
||||
- 笔记详情的内部记录索引使用透明底色,与主内容表面连续,仅保留细调宽分隔线。每条索引记录保留 `1px` 的 `--border-subtle` 淡边框,不加卡片底色或阴影,悬停使用轻微次级底色;选中记录使用淡蓝背景和左侧 `3px` 蓝色内侧标记,悬停时保留选中态。缩略图保留纸张边界,键盘焦点环与强制颜色模式选中边框保持可见。窄屏抽屉使用不透明的主内容表面,避免下层正文透出;深色主题沿用语义令牌。
|
||||
- 笔记详情的标题、正文和记录流不再包裹额外的大外框,记录流容器只保留上述左右留白;缩略图列表顶部与正文区顶部对齐,左边界与索引栏左边界对齐,仅在右侧和底部保留 `8px` 留白,键盘焦点环向内绘制以避免滚动容器裁切。每条已保存记录使用一层 `1px` 的 `--border-subtle` 淡边框和 `--radius-control` 圆角,保留透明底色与记录间留白,不加阴影或头部下划线。记录编辑时保留输入区边框、页面安全边距及 AI 区域分隔。此规则仅作用于笔记详情,笔记列表与待办保持独立布局。
|
||||
- 画布新建、编辑和只读态统一保留 `12px` 内容间距;详情中已保存画布去掉预览外框和工具栏底色、下划线,保留灰色画布衬底与白色纸张,编辑态保留编辑器边框。记录头部操作按内容宽度排列,空间不足时自然换行。文字新建的保存区与编辑器属于同一卡片,空白编辑区使用最小高度,不随容器百分比高度拉长。文字工具栏图标、菜单和原生滚动条须跟随主题。
|
||||
- 标签使用中性胶囊样式(`--surface-subtle` 底色、`--border-subtle` 边框、`--font-caption`),不使用颜色区分。总览搜索框下方是标签筛选行:“全部”加各标签和对应篇数,均为 `aria-pressed` 切换按钮;可以多选,多选时只显示同时带有全部所选标签的笔记。选中时使用 `--accent-subtle` 底色和强调色边框。卡片最多显示 3 个标签,其余合并为“+N”。打开笔记后,左侧列表不显示筛选行,只显示“已筛选:A · B”提示条和清除按钮。
|
||||
- 笔记标题下方是标签编辑行:已有标签后带 × 移除按钮,末尾是虚线“+ 标签”按钮。点击后展开组合框,按输入内容过滤已有标签,也可以新建。方向键切换选项,Enter、逗号或中文逗号确认,Escape 关闭并把焦点还给“+ 标签”。达到 10 个时,“+ 标签”禁用并说明上限。
|
||||
- 列表标题行右侧只放“管理标签”,不再放 `+` 新建按钮。“新建笔记”统一放在页头右上角,是主按钮,始终位于“切换到待办 / 切换到笔记”或“返回总览”的右侧。打开笔记时点击,桌面端展开左侧列表并显示标题输入,窄屏先回到列表。“管理标签”在总览显示图标加文字,打开笔记后只显示图标。点击后打开共享 `custom-task-dialog` 对话框,按使用篇数列出标签,每行提供重命名和删除图标按钮。重命名在行内编辑;新名称与已有标签相同时,提示保存后会合并。删除在行内二次确认,并说明涉及的篇数。Escape 依次退出行内编辑或确认,最后关闭对话框。
|
||||
- 整篇笔记的置顶、取消置顶和删除放在列表行的“更多”菜单,打开详情时仍可操作,标题编辑区不重复提供入口。按钮在悬停、键盘聚焦或菜单展开时显示,触屏设备常显;菜单沿用会话列表样式,通过 Portal 避免滚动区域裁切,支持方向键、Home、End 和 Escape。删除在菜单内二次确认并说明笔记标题及全部记录;删除当前笔记还须处理未保存草稿,删除其他笔记保留当前内容与草稿。打开菜单不切换笔记。
|
||||
- 左侧“魔法笔记”入口可显示未完成待办数量。数量为 0 时隐藏徽标,超过 99 时显示 `99+`,可访问名称仍说明精确数量;勾选、取消勾选以及 Agent / MCP 对笔记清单的写入都必须刷新数量。
|
||||
- 未完成待办徽标的高度不得超过主导航图标;数量从 0 变为可见或再次隐藏时,主导航项行高和相邻菜单位置必须保持不变。
|
||||
- 在待办面板勾选或取消勾选后,当前缓存中的源笔记正文必须立即同步对应清单状态,不得依赖离开工作区后重新加载。
|
||||
- Agent / MCP 或其他入口成功写入笔记后,已打开的工作台自动更新笔记列表、当前详情、待办及来源记录,不增加刷新按钮。保留仍存在的笔记与待办选择、搜索和筛选,以及未保存的标题、输入区和记录编辑草稿;普通后台刷新不重建编辑器。
|
||||
- 外部删除当前笔记时,无草稿则选择剩余笔记;有草稿则保留编辑区并提示先复制草稿。外部删除正在编辑的记录时,同样保留该编辑器并明确提示记录已删除;保存失败不得清空草稿。
|
||||
- 当前记录草稿非空时,切换笔记、待办或内容面板必须先在编辑器旁就地确认;继续编辑时保留草稿并恢复编辑焦点,只有明确选择放弃后才切换。
|
||||
- 外部删除当前笔记时,无草稿则返回总览;有草稿则保留编辑区并提示先复制草稿。外部删除正在编辑的记录时,同样保留该编辑器并明确提示记录已删除;保存失败不得清空草稿。
|
||||
- 当前记录草稿非空或标题尚未保存时,返回总览必须先就地确认;编辑另一条记录等会丢弃正文草稿的操作沿用同一保护。继续编辑时保留草稿并恢复编辑焦点,只有明确选择放弃后才切换。
|
||||
- 文字和画布记录保存修改成功后退出编辑状态,显示已保存正文;后续自动评论成功或失败均不重新打开编辑器。再次修改须点击编辑入口,以该修订的编辑器初始化内容作为修改比较基线。编辑器归一化不算用户修改,后续输入、画布变更和保存失败仍保留离开保护;同修订的后台刷新不替换已建立的基线。
|
||||
- 创建、保存、更新、删除和 AI 评论完成等短期结果进入应用级通知,不在编辑区或列表上方堆放页内通知。
|
||||
- 标题或正文校验、删除确认、同步进度和可就地恢复的错误仍靠近对应编辑器或操作呈现。
|
||||
|
||||
### 13.8 设置中心
|
||||
|
||||
- 全页设置使用固定标题区、左侧分类导航和独立滚动的内容区。右上角关闭按钮是离开设置中心的稳定入口。
|
||||
- 设置中心以整窗遮罩上的大 Modal 打开,保留底层工作区、会话草稿和助手工作栏,不切换一级页面。固定标题区和右上角关闭按钮,左侧分类与右侧内容各自滚动。关闭后返回原工作区并恢复入口焦点。
|
||||
- 新建项目与项目设置弹窗固定标题、关闭按钮和底部操作区,仅表单内容滚动;滚动条不进入标题区,关闭按钮保留至少 40 × 40px 的点击区域。
|
||||
- 项目弹窗通过 Portal 挂载到 `document.body`,避免侧栏内的弹窗与主窗口标题栏拖动区域重叠时,Electron 截获关闭按钮的鼠标输入。
|
||||
- 左侧分类导航在宽屏使用 `220px`,中等窗口使用 `196px`,窄窗口转为横向滚动;纵向滚动条仅在内容溢出时占用右侧空间,不在左侧创建镜像预留,选项与左侧可见边界保持默认内距。分类标题使用正文级字号,分类说明使用辅助字号;右侧内容区在可用空间内流式伸缩,最大宽度使用 `standard` 壳层的 `960px`,不得以页面专属较窄宽度压缩表单。
|
||||
- 全页设置标题区与双栏内容使用共享 `--page-gutter`,标题、分类导航和内容区在同一页面边距基线上;标题继续使用标准 `PageHeader` 的底部分隔线与下内距。固定标题和双独立滚动区域不改变这些一级页面壳层规则;模态设置保留自身外框与标题边界。
|
||||
- Modal 最大尺寸为 `1200px × 900px`,常规外边距为 `24px`;窗口宽度不超过 `1020px` 或高度不超过 `720px` 时接近全屏,保留 `16px` 外边距。左侧分类宽度依次为 `220px`、`180px`(宽度不超过 `1020px`)、`120px`(不超过 `720px`);窄窗口仍保留左右布局,长分类名称可换行。内容区在可用空间内伸缩,最大宽度为 `960px`。
|
||||
- 设置 Modal 通过 Portal 挂载到 `document.body`,复用共享背景隔离与焦点恢复机制。打开后聚焦关闭按钮,Tab 在弹窗内循环;Escape 先交由嵌套弹窗处理,设置自身的 Escape 与关闭按钮共用未保存确认,确认出现后再按 Escape 返回编辑。点击遮罩不关闭;保存、测试和清理数据期间禁止关闭。每次打开重新挂载设置,模型和消息通道等直达入口使用本次指定分类,普通入口默认打开 Agent Runtime。
|
||||
- 设置中心不显示全局操作页脚,避免重复关闭入口和没有功能意义的整宽分隔线。
|
||||
- 所有分类使用共享的 `SettingsCategoryHeader` 呈现分类标题、说明、错误与操作,不得在内容卡片内复制分类标题或创建页面专属操作栏。左侧分类名称与说明来自同一份分类定义,新增分类时不得分别维护导航和内容标题。
|
||||
- 所有分类使用共享的 `SettingsCategoryHeader` 呈现分类标题、说明、错误与操作,不得在内容卡片内复制分类标题或创建页面专属操作栏。左侧分类名称与右侧标题来自同一份分类定义,新增分类时不得分别维护导航和内容标题。
|
||||
- 左侧按“常规 / 模型与执行 / 功能与扩展 / 系统”分组,顺序与键盘导航一致。分类按钮最小高度 `40px`,只显示名称;分类说明集中在右侧页头,外层标题不再重复功能简介。组名使用辅助字号,普通项使用正文和中等字重,选中项使用强调底色与加粗文字。
|
||||
- 设置区块使用共享圆角、间距和表面令牌;标题为 `--font-section-title`,持久说明至少为 `--font-caption`。嵌套设置区块以顶部细分隔线和留白组织,不叠加卡片背景与四周边框。“能力与工具”的上级页签使用默认 `PageTabs` 外观,与 MCP 内部的分段页签区分。
|
||||
- 当前分类存在“保存”或“测试”等未提交配置操作时,统一放在分类页头右侧;主保存操作在最右侧,测试等次操作排列在其左侧。
|
||||
- 自动生效、仅执行即时命令或自行管理编辑流程的分类不显示全局保存操作。窄窗口下操作区可以换行,但保存入口必须保持清晰可见。
|
||||
- 智能心跳的单条配置不在设置中心重复管理。设置中心如需呈现平台级说明,只提供
|
||||
“打开智能心跳”导航,不复制创建、暂停、恢复或删除表单。
|
||||
- 保存或测试成功统一进入应用通知视口,并按全局规则自动消失,不在分类页头或内容卡片中保留持久成功文案。加载、保存和测试错误显示在分类页头下方,并保留可处理的上下文。
|
||||
- “模型连接”的认证字段按“认证方式 → API Key → 凭据状态”排列;只有选择 API Key 时显示输入框。已保存的 API Key 属于该连接,修改模型服务地址或临时选择“无需认证”不得自动清除,也不得要求重新输入;仅由明确的“清除凭据”或“删除连接”操作移除。
|
||||
- “模型连接”页头在保存操作下方,将模型类型页签与全局模型下载源入口放在同一行;当前来源文字与“模型下载源”次按钮整体右对齐,窄窗口保持同行,空间不足时导航行可横向滚动。按钮打开挂载到 `document.body` 的 Portal 弹窗,复用共享 Modal 焦点管理;弹窗内使用 `fieldset`、持久 `legend` 与整行可点击的原生 Radio 卡片,选中状态同时依靠 Radio、边框和背景表达。Radio 只修改草稿,点击“确认”才保存;取消或关闭不改变已保存来源。读取失败提供重试并禁用确认,不以默认值伪装已保存选择;保存失败保留草稿并允许确认重试,保存期间禁止重复提交和关闭。向量、语音和 OCR 的更换下载源入口直接打开模型连接页的同一弹窗,跨分类时沿用未保存修改保护。详细规则见[模型下载源设计](docs/architecture/model-download-source-design.md#7-模型下载源界面)。
|
||||
- 设置中的连接列表与详情共用 `.model-connection-manager`,按容器可用宽度响应:不超过 `640px` 时上下排列,列表内部滚动高度最多 `180px`;更宽时恢复左右排列。LLM、向量连接和角色编辑复用此规则,不以整窗宽度代替表单宽度。接口协议的中英文当前值须完整可读。回归范围见[窄容器布局修复记录](docs/quality/responsive-controls-review-2026-09-27.md)。
|
||||
- “保存并测试模型”必须在保存后发送一次有界的真实模型请求。文本连接校验本次随机测试文本,图像连接校验实际返回的内联图片;仅收到成功 HTTP 状态、模型列表或健康检查响应不算测试成功。界面持续说明该操作可能产生少量服务商用量费用,成功反馈明确写为“真实生成测试通过”。
|
||||
- 所有显式保存的设置草稿都参与离开保护:关闭、切换分类、主侧栏或工作区导航及托盘导航不得静默丢弃,统一通过设置中心的就地确认提供继续编辑与明确放弃入口,保存失败后保留输入。“平台功能 / 通用设置”承载全局快捷唤起的共享 Switch、可访问 accelerator 录制输入、恢复默认、保存及注册、停用或冲突状态,不新增分类或页签;注册或持久化失败时保留上一组可用快捷键和当前草稿,保存或停用成功后同步更新输入区的快捷键提示。
|
||||
- “平台功能 / 通用设置”同时提供默认开启的“在会话中渲染 HTML”持久 Switch。说明必须
|
||||
明确该能力只生成禁用脚本、联网、表单提交和窗口操作的静态预览;保存成功后当前会话立即
|
||||
更新,保存失败时保留最后确认状态。
|
||||
- “平台功能”使用共享 `PageTabs` 区分“通用设置”“远程项目(技术预览)”和“魔法笔记”,默认进入通用设置。“远程项目”页将持久 Switch 与用户数据目录中的 Linux x64、Linux arm64、macOS arm64 Agent 包清单分成独立卡片;每张卡片与下载进度按平台和架构区分,不能把两个 arm64 目标合并。清单延迟到首次打开该页签时由 Main 完整校验,并读取所选更新源的小型签名目录,显示本地版本、在线最新兼容版本以及“有更新”“已是最新”“未下载”“校验失败”。打开页签或刷新不得自动下载 `.gbagent`;只有用户点击带目标版本的下载/更新按钮后才传输包。签名目录不可用时继续显示并允许导出已验证本地包,提供就地重试错误;清单不混入 Host 已安装状态、缓存路径或敏感签名细节。下载和导入期间保留上下文进度,成功进入全局通知视口。“魔法笔记”页提供默认开启的“显示未完成待办数量”持久 Switch,关闭后只隐藏左侧数量徽标,不改变待办数据或魔法笔记入口。全局模型下载源使用 `fieldset`、持久 `legend` 与整行可点击的原生 Radio 卡片;选中状态同时依靠 Radio、边框和背景表达,读取失败时不得用默认值伪装为已保存选择。
|
||||
- “关于与更新”的更新源位于“启动时检查新版本”开关下方,常规宽度下将标签、原生单选下拉框和用途说明放在同一行,并复用设置表单的统一控件样式;关闭启动检查后仍可选择更新源,手动检查继续使用当前选择。选项显示“GitHub(默认)”和中性的“镜像节点”。该选择同时控制手动检查、启动时检查、下载页和 Agent 包在线下载,不显示底层服务商名称。
|
||||
- 通用设置按快捷唤起、会话与通知、工作目录分组,复用现有卡片。“桌面通知”为默认开启的持久 Switch,保存后立即控制窗口未聚焦时的任务完成、失败和取消系统通知,关闭不影响应用内通知。旧配置缺少该字段时默认开启,明确关闭后重启仍保持关闭;保存失败保留最后确认状态并显示应用内错误通知。
|
||||
- “平台功能”使用共享 `PageTabs` 区分“通用设置”和“远程项目”,默认进入通用设置。“远程项目”页将持久 Switch 与用户数据目录中的 Linux x64、Linux arm64、macOS arm64 Agent 包清单分成独立卡片;每张卡片与下载进度按平台和架构区分,不能把两个 arm64 目标合并。清单延迟到首次打开该页签时由 Main 完整校验,并读取所选更新源的小型签名目录,显示本地版本、在线最新兼容版本以及“有更新”“已是最新”“未下载”“校验失败”。打开页签或刷新不得自动下载 `.gbagent`;只有用户点击带目标版本的下载/更新按钮后才传输包。签名目录不可用时继续显示并允许导出已验证本地包,提供就地重试错误;清单不混入 Host 已安装状态、缓存路径或敏感签名细节。下载和导入期间保留上下文进度,成功进入全局通知视口。
|
||||
- “关于与更新”的更新源位于“启动时检查新版本”开关下方,常规宽度下将标签、同行帮助和原生单选下拉框放在同一行,用途说明收进 `InlineHelp`,并复用设置表单的统一控件样式;关闭启动检查后仍可选择更新源,手动检查继续使用当前选择。选项显示“GitHub(默认)”和中性的“镜像节点”。该选择同时控制手动检查、启动时检查、下载页和 Agent 包在线下载,不显示底层服务商名称。
|
||||
- “关于与更新”在版本信息之后提供应用内反馈卡片。反馈对话框使用原生字段、明确隐私说明和一张可选截图;另提供默认关闭的原生 Checkbox,让用户主动附加最近桌面诊断摘要。Checkbox 附近必须持续说明摘要只含时间、组件、阶段、稳定错误码、错误类型和固定短消息,不含对话、Prompt、凭据、文件内容、路径、Provider 原始响应或远端 Agent 日志。勾选时为诊断摘要预留描述字符预算,超长草稿必须保留并显示字段错误,不得静默截断。失败时保留草稿、勾选状态和请求编号并提供就地重试,成功时在对话框内保留可复制编号而不重复发送全局通知;对话框遵循统一 Modal 的背景隔离、初始焦点、Tab 循环、Escape、焦点恢复和窄窗口布局。
|
||||
- Agent Runtime 分类页头的“保存设置”同时保存 Runtime 基础配置与 Runtime 原生定制,不在原生定制卡片内提供第二个保存入口。原生定制存在未保存更改时持续显示状态和撤销入口;切换设置分类或 Runtime 不丢弃草稿,关闭设置中心前必须先保存或撤销。
|
||||
- Agent Runtime 页面在低层程序与配置覆盖之外提供“能力与默认配置”区域。能力清单使用共享 `PageTabs`,按 Agents、Tools、Commands、Skills、MCP、Rules、Prompts、Resources、LSP、Formatters 和上下文 11 类单行滚动展示,一次只呈现当前分类的 `tabpanel`;清单只显示 Runtime 自有能力,不混入 GoodBuddy 分配的 Skills、临时 MCP 或 Continue 预设。Tools 必须独立于 Commands、LSP 和 Formatters,显示工具类型、来源及 Ask/Execute 可用性;清单状态必须区分完整、部分、不可用、仅连接和不支持,不能用进程连通性冒充清单可读。
|
||||
- “能力与默认配置”只显示一个模块标题,刷新入口位于该标题右侧,能力状态压缩为一行并排在默认 Agent 或 Continue 预设编辑器之前;不得再复制“Runtime 原生能力”等同义标题、说明或状态结论。刷新只更新能力快照,不覆盖未保存的原生定制草稿。
|
||||
- OpenCode 的默认 Agent 使用原生下拉选择;Continue 预设编辑器允许管理名称、说明、启用的 Rules 以及 Prompt 名称、说明和正文,并可展开查看原生 Rules 与启用预设 Rules 的最终合并顺序。持久启停仍使用共享 Switch,添加与删除使用明确按钮和可访问名称。
|
||||
- 魔法笔记设置统一从应用中心对应行进入;笔记页和全局设置不再提供重复入口。“显示未完成待办数量”持久 Switch 只控制左侧数量徽标,不改变待办数据。旧入口开关绑定业务启用值,迁为“启用应用”并保留原值,常驻使用独立配置。默认开启仅适用于未配置布尔值,只有独立显隐偏好才映射到常驻。此迁移规则见[应用导航 PRD](./docs/features/application-tool-navigation/prd.md#fr-11-应用导航设置)。
|
||||
- MCP 设置按“内置 MCP / 直连模型 / 自定义 MCP / 电脑控制”四个同级 `PageTabs` 组织。联网搜索保留在直连模型;内置浏览器归入内置 MCP,提供持久启停和 Runtime 分配,并明确说明其操作 GoodBuddy 隔离浏览器、不控制客户端已安装的浏览器,开启后可由已分配 Runtime 的 Execute 直接使用,不逐次询问。该开关只控制 Agent 工具访问,关闭时不得禁用浏览器工作栏中的用户手动导航、返回、刷新、停止加载、交互或关闭操作。尚未生效的命名浏览器配置不得显示在界面中,“电脑控制”只显示实际操作客户端电脑的能力。内置 MCP 卡片与 Skills 一样提供持久启停和 Runtime 分配;直连模型、GoodBuddy 管理的 OpenCode 与 Continue 默认选中且可调整,DeepSeek Harness 必须以置灰、未选择和“暂不支持”文案持续显示,不能呈现为可保存的分配。魔法笔记 MCP 的自身启停与平台功能依赖分别显示,依赖未开启时保留用户配置并说明当前不会加载。
|
||||
- MCP Server 测试结果在同一展开卡片中分组显示 Tools、Prompts 和 Resources 的支持状态、数量与有界元数据;Prompt 参数标明必填项,Resource 只显示 URI、名称、类型和说明,不读取或渲染 Resource 内容。
|
||||
- 内置 MCP 中的“图片生成与编辑”是自动管理例外:该行始终可见,标记“内置 · 自动管理”,不提供独立启停、分配保存、移除或凭据编辑。直连 Model、OpenCode、Continue、DeepSeek Harness 的分配只由已保存图片模型的“允许 AI 在会话中调用”设置推导;至少一个开启则全部勾选,没有开启模型则全部取消。使用禁用的原生 Checkbox、Runtime 标签及关联说明,保留可聚焦的“前往图片模型设置”导航;远程适用范围通过 GoodBuddy Agent 说明持续可见。Ask、离线、无工具能力或调用错误不改变勾选。图片模型默认关闭会话调用;默认生图模型独立于普通默认连接,关闭或删除后保留失效选择并提示改选,不自动回退。
|
||||
|
||||
### 13.9 文档解析设置
|
||||
|
||||
现有本地解析与本轮 HTTP 扩展的实现边界见[文档处理进度](./docs/features/document-processing/progress.md)。
|
||||
跨场景入口、附件菜单、共享预览及选图返回规则由[文档处理 UI 设计](./docs/features/document-processing/ui-design.md)维护;
|
||||
当前接线与存储行为见[生产实现说明](./docs/features/document-processing/implementation.md)。文档解析属于全局设置,不新增应用中心成员;本机推理监控只管理本地服务。
|
||||
|
||||
- 设置中心新增独立的“文档解析”分类,统一管理聊天附件、知识库导入以及后续文档审阅场景使用的提取、转换和 OCR 策略。OCR 不作为普通对话模型出现在“模型连接”中。
|
||||
- 分类页头说明文档解析的跨场景作用,右侧依次显示“测试解析”和“保存设置”;保存位于最右侧。测试必须选择真实文件并执行实际解析,不能只检查模型文件或接口连通性。
|
||||
- 页面首先显示原生解析、文档转换和 OCR 的运行状态,并明确当前可处理格式、回退能力与不可用原因。部分能力未配置时使用“部分可用”状态,不得把原生文本解析一并标记为失败。
|
||||
@@ -767,9 +881,9 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
||||
- PDF 先读取文本层。仅当页面无有效文本、乱码比例过高或用户选择“始终 OCR”时渲染该页并识别;不得因为单页需要 OCR 而丢弃其他页面已经提取的可靠文本。
|
||||
- DOCX、XLSX、PPTX 优先保留段落、单元格、公式、备注等原生语义。转换为 PDF 用于补充版面、页码、图表和图片理解,不作为唯一中间格式。
|
||||
- DOC、XLS、PPT 等旧格式通过受控转换 Provider 生成新式 Office 文档和 PDF。转换子进程必须禁用宏和网络,限制输入、输出、内存、超时与临时目录,并在关闭或取消时清理。
|
||||
- OCR 来源使用“本地模型 / 远程服务”互斥选择。选择本地后显示模型下载、模型下拉选择和本地运行参数;选择远程后显示 MinerU、PaddleOCR-VL 等服务连接配置。未实现的远程服务入口保持可读但禁用,不再增加与来源选择重复的“隐私与云端处理”授权区。
|
||||
- OCR 来源使用“本地模型 / 远程服务”互斥选择,远程为 HTTP PaddleOCR-VL;MinerU 不在本轮范围。来源切换保留两套配置,未保存时禁止测试。来源配置、保存和测试规则见[文档解析设置设计](./docs/features/document-processing/ui-design.md#2-文档解析设置),不另加逐文件授权区。
|
||||
- 用户配置并保存远程 OCR 服务即表示选择该处理路径,不再逐场景重复询问。界面仍须明确显示当前服务名称、处理范围和远程属性,API 密钥只保存在主进程加密设置中,未选中远程服务时不得上传文档。
|
||||
- 解析结果使用统一文档结构,至少保留文档标题、来源格式、页码或工作表定位、正文块、置信度、处理方式和警告。聊天附件保留完整解析文本,超过暂存容量时报错;知识库使用完整结果分块和索引,具体要求见[文档处理 PRD](./docs/features/document-processing/prd.md#32-场景工作流)。
|
||||
- 解析结果使用统一文档结构,保留文档标题、来源格式、页码或工作表定位、正文块、处理方式和警告;置信度仅在解析器提供时显示,缺失不补造。聊天文档附件保留完整解析文本,超过暂存容量时报错;知识库使用完整结果分块和索引,具体要求见[文档处理 PRD](./docs/features/document-processing/prd.md#32-场景工作流)。普通图片默认走图片输入,显式 OCR、原图保存和文档选图遵循该功能的规划规则。
|
||||
- 测试结果显示文件类型、页数、实际工作流、提取字数、OCR 页数、耗时和警告。测试文件不得自动进入聊天上下文或知识库。
|
||||
|
||||
## 14. 文案规则
|
||||
@@ -819,7 +933,7 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
||||
- [ ] 将输入快捷键与附件提示置于空输入框内部,输入区下方保持单行说明。
|
||||
- [ ] 最近对话迁移到 `standard`,统一搜索、范围、时间和删除行为。
|
||||
- [ ] 知识库迁移到 `master-detail`,清除内联浅色样式并补齐窄窗口单面板流程。
|
||||
- [x] 智能心跳使用 `dashboard`,保留概览、建议、轨迹和计划,并在计划中支持 Global / 多 Project 范围。
|
||||
- [x] 监督者使用 `supervisor`,工作回顾读取统一监督结果;智能心跳包含 Global / 多 Project 心跳计划、监督建议与历史心跳报告;活动记录显示统一执行,设置只保留算法参数。
|
||||
- [x] 在现有工作栏中完善任务中心,统一范围、状态、最近进展和筛选,不新建平行任务平台。
|
||||
- [ ] 活动记录迁移到 `dashboard`,统一导航、筛选和表格行为。
|
||||
- [ ] 设置中心使用共享分类定义与 `SettingsCategoryHeader`,将保存与测试操作统一放到分类页头右侧,并把成功反馈接入应用通知。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"agentVersion": "0.13.0",
|
||||
"agentVersion": "0.15.3",
|
||||
"protocol": {
|
||||
"major": 2,
|
||||
"minor": 0
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
const { createHash } = require('node:crypto')
|
||||
const { mkdirSync, readFileSync } = require('node:fs')
|
||||
const { join, resolve } = require('node:path')
|
||||
const { execFileSync } = require('node:child_process')
|
||||
const { readRemoteRuntimeLock, resolveLockedRuntimeInput } = require('./remote-runtime-bundle.cjs')
|
||||
|
||||
const [directory, architecture, platform = 'linux'] = process.argv.slice(2)
|
||||
if (!directory) throw new Error('Continue archive destination is required')
|
||||
const input = resolveLockedRuntimeInput(readRemoteRuntimeLock(), architecture, platform, 'continue')
|
||||
const destination = resolve(directory)
|
||||
mkdirSync(destination, { recursive: true })
|
||||
execFileSync('npm', ['pack', `${input.packageName}@${input.version}`, '--ignore-scripts',
|
||||
'--registry=https://registry.npmjs.org', '--pack-destination', destination], { stdio: 'ignore' })
|
||||
const archive = join(destination, input.archive)
|
||||
if (`sha512-${createHash('sha512').update(readFileSync(archive)).digest('base64')}` !== input.integrity) {
|
||||
throw new Error('Continue archive integrity mismatch')
|
||||
}
|
||||
process.stdout.write(`${archive}\n`)
|
||||
+22
-4
@@ -152,8 +152,8 @@ function assertPackageDescriptor(descriptor) {
|
||||
descriptor.files.length > 50_000 ||
|
||||
typeof descriptor.remoteRuntime !== 'object' ||
|
||||
descriptor.remoteRuntime === null ||
|
||||
descriptor.remoteRuntime.runtimeId !== 'opencode' ||
|
||||
descriptor.remoteRuntime.provider !== 'opencode' ||
|
||||
!['opencode', 'continue'].includes(descriptor.remoteRuntime.runtimeId) ||
|
||||
descriptor.remoteRuntime.provider !== descriptor.remoteRuntime.runtimeId ||
|
||||
!semanticVersionPattern.test(
|
||||
descriptor.remoteRuntime.version ?? ''
|
||||
) ||
|
||||
@@ -164,6 +164,7 @@ function assertPackageDescriptor(descriptor) {
|
||||
throw new Error('Agent package descriptor is invalid')
|
||||
}
|
||||
assertProtocol(descriptor.agentProtocol, 'Agent protocol')
|
||||
assertAdditionalRuntimes(descriptor)
|
||||
assertProtocol(
|
||||
descriptor.remoteRuntime.protocol,
|
||||
'Remote Runtime protocol'
|
||||
@@ -592,8 +593,8 @@ function assertCatalog(catalog) {
|
||||
entry.formatVersion !== 1 ||
|
||||
entry.product !== 'GoodBuddy' ||
|
||||
entry.component !== 'agent' ||
|
||||
entry.remoteRuntime?.runtimeId !== 'opencode' ||
|
||||
entry.remoteRuntime?.provider !== 'opencode' ||
|
||||
!['opencode', 'continue'].includes(entry.remoteRuntime?.runtimeId) ||
|
||||
entry.remoteRuntime?.provider !== entry.remoteRuntime?.runtimeId ||
|
||||
!semanticVersionPattern.test(
|
||||
entry.remoteRuntime?.version ?? ''
|
||||
) ||
|
||||
@@ -604,6 +605,7 @@ function assertCatalog(catalog) {
|
||||
throw new Error('Agent catalog entry is invalid')
|
||||
}
|
||||
assertProtocol(entry.agentProtocol, 'Agent protocol')
|
||||
assertAdditionalRuntimes(entry)
|
||||
assertProtocol(
|
||||
entry.remoteRuntime.protocol,
|
||||
'Remote Runtime protocol'
|
||||
@@ -634,6 +636,18 @@ function readVerifiedCatalog(catalogPath, signaturePath, registry) {
|
||||
return catalog
|
||||
}
|
||||
|
||||
function assertAdditionalRuntimes(value) {
|
||||
if (value.additionalRuntimes === undefined) return
|
||||
if (!Array.isArray(value.additionalRuntimes) || value.additionalRuntimes.length > 1) throw new Error('Invalid additional Runtimes')
|
||||
for (const runtime of value.additionalRuntimes) {
|
||||
if (!runtime || !['opencode', 'continue'].includes(runtime.runtimeId) || runtime.runtimeId === value.remoteRuntime.runtimeId ||
|
||||
runtime.provider !== runtime.runtimeId || !semanticVersionPattern.test(runtime.version ?? '') || !/^sha256:[a-f0-9]{64}$/u.test(runtime.bundleDigest ?? '')) {
|
||||
throw new Error('Invalid additional Runtime identity')
|
||||
}
|
||||
assertProtocol(runtime.protocol, 'Additional Runtime protocol')
|
||||
}
|
||||
}
|
||||
|
||||
function parseVersion(value) {
|
||||
const match =
|
||||
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?$/u
|
||||
@@ -710,6 +724,10 @@ function createCatalog(options) {
|
||||
...(options.darwinArm64Package ? [readPackageMetadata(options.darwinArm64Package, registry)] : [])
|
||||
]
|
||||
const [first, second] = packages
|
||||
const additionalIdentity = descriptor => canonicalJson((descriptor.additionalRuntimes ?? []).map(({ bundleDigest: _digest, ...identity }) => { void _digest; return identity }))
|
||||
if (packages.some(value => additionalIdentity(value.descriptor) !== additionalIdentity(first.descriptor))) {
|
||||
throw new Error('Agent package matrix has inconsistent additional Runtimes')
|
||||
}
|
||||
const {
|
||||
bundleDigest: _firstRuntimeDigest,
|
||||
...firstRuntimeIdentity
|
||||
|
||||
@@ -38,6 +38,7 @@ function parseArguments(argv) {
|
||||
'--platform',
|
||||
'--node-archive',
|
||||
'--opencode-archive',
|
||||
'--continue-archive',
|
||||
'--output-directory',
|
||||
'--archive'
|
||||
].includes(argument)
|
||||
@@ -63,6 +64,7 @@ function parseArguments(argv) {
|
||||
for (const key of [
|
||||
'nodearchive',
|
||||
'opencodearchive',
|
||||
'continuearchive',
|
||||
'outputdirectory',
|
||||
'archive'
|
||||
]) {
|
||||
@@ -77,6 +79,7 @@ function parseArguments(argv) {
|
||||
platform,
|
||||
nodeArchive: resolve(options.nodearchive),
|
||||
opencodeArchive: resolve(options.opencodearchive),
|
||||
continueArchive: resolve(options.continuearchive),
|
||||
outputDirectory: resolve(options.outputdirectory),
|
||||
archive: resolve(options.archive)
|
||||
}
|
||||
@@ -160,6 +163,11 @@ async function buildCiAgentBundle(options) {
|
||||
registry: signing.registry,
|
||||
testSigningIdentity: identity
|
||||
})
|
||||
const continueRuntime = buildRuntimeBundle({
|
||||
platform, runtimeId: 'continue', lock: runtimeLock, projectRoot, architecture: options.arch,
|
||||
runtimeArchive: options.continueArchive, outputRoot: runtimeRoot,
|
||||
registry: signing.registry, testSigningIdentity: identity
|
||||
})
|
||||
const assemble = (archive) =>
|
||||
assembleAgentPackage({
|
||||
platform,
|
||||
@@ -170,6 +178,7 @@ async function buildCiAgentBundle(options) {
|
||||
output: archive,
|
||||
agentBundle: options.outputDirectory,
|
||||
runtimeBundle: runtime.bundleDirectory,
|
||||
additionalRuntimeBundles: [continueRuntime.bundleDirectory],
|
||||
agentLock: lock,
|
||||
runtimeLock,
|
||||
registry: signing.registry,
|
||||
|
||||
+43
-6
@@ -142,7 +142,7 @@ function packageFileMode(path) {
|
||||
if (path.startsWith('agent/')) {
|
||||
return expectedAgentManifestMode(path.slice('agent/'.length))
|
||||
}
|
||||
return /\/bin\/opencode$/u.test(path) ? '0755' : '0644'
|
||||
return /(?:\/bin\/opencode|\/lib\/continue\/dist\/cn\.js)$/u.test(path) ? '0755' : '0644'
|
||||
}
|
||||
|
||||
function stagePackagePayload(options) {
|
||||
@@ -153,10 +153,13 @@ function stagePackagePayload(options) {
|
||||
join(
|
||||
staging,
|
||||
'runtime',
|
||||
'opencode',
|
||||
options.runtimeId ?? 'opencode',
|
||||
basename(options.runtimeBundle)
|
||||
)
|
||||
)
|
||||
for (const runtime of options.additionalRuntimes ?? []) {
|
||||
copyTree(runtime.bundleDirectory, join(staging, 'runtime', runtime.manifest.runtimeId, basename(runtime.bundleDirectory)))
|
||||
}
|
||||
for (const [value, destination] of [
|
||||
[
|
||||
options.registry,
|
||||
@@ -420,13 +423,22 @@ function assembleAgentPackage(options) {
|
||||
join(tmpdir(), `goodbuddy-agent-package-${target}-`)
|
||||
)
|
||||
try {
|
||||
const additionalRuntimes = (options.additionalRuntimeBundles ?? []).map(directory => verifyRuntimeBundle(directory, {
|
||||
platform, projectRoot, architecture, registry, lock: runtimeLock,
|
||||
verificationEnvironment: options.testSigningIdentity ? 'test' : 'production'
|
||||
}))
|
||||
if (new Set([runtime, ...additionalRuntimes].map(value => value.manifest.runtimeId)).size !== additionalRuntimes.length + 1) {
|
||||
throw new Error('Duplicate packaged Runtime')
|
||||
}
|
||||
stagePackagePayload({
|
||||
staging,
|
||||
registry,
|
||||
agentLock,
|
||||
runtimeLock,
|
||||
agentBundle: resolve(options.agentBundle),
|
||||
runtimeBundle: runtime.bundleDirectory
|
||||
runtimeBundle: runtime.bundleDirectory,
|
||||
runtimeId: runtime.manifest.runtimeId,
|
||||
additionalRuntimes
|
||||
})
|
||||
const payload = payloadFiles(staging)
|
||||
const initial = {
|
||||
@@ -447,6 +459,10 @@ function assembleAgentPackage(options) {
|
||||
bundleDigest: runtime.manifest.bundleDigest,
|
||||
protocol: runtime.manifest.protocol
|
||||
},
|
||||
...(additionalRuntimes.length ? { additionalRuntimes: additionalRuntimes.map(({ manifest }) => ({
|
||||
runtimeId: manifest.runtimeId, provider: manifest.provider, version: manifest.runtimeVersion,
|
||||
bundleDigest: manifest.bundleDigest, protocol: manifest.protocol
|
||||
})) } : {}),
|
||||
contentDigest: `sha256:${'0'.repeat(64)}`,
|
||||
files: payload.files
|
||||
}
|
||||
@@ -510,6 +526,7 @@ function buildAgentPackage(options) {
|
||||
: { signingIdentity })
|
||||
})
|
||||
const runtime = buildRuntimeBundle({
|
||||
runtimeId: options.runtimeId,
|
||||
platform: options.platform ?? 'linux',
|
||||
projectRoot,
|
||||
architecture: options.architecture,
|
||||
@@ -521,6 +538,16 @@ function buildAgentPackage(options) {
|
||||
? { testSigningIdentity: options.testSigningIdentity }
|
||||
: { signingIdentity })
|
||||
})
|
||||
const additionalRuntimeBundles = []
|
||||
if (!options.runtimeId || options.runtimeId === 'opencode') {
|
||||
if (!options.continueArchive) throw new Error('Default Agent package requires the locked Continue archive')
|
||||
additionalRuntimeBundles.push(buildRuntimeBundle({
|
||||
runtimeId: 'continue', platform: options.platform ?? 'linux', projectRoot,
|
||||
architecture: options.architecture, runtimeArchive: options.continueArchive, outputRoot: runtimeRoot,
|
||||
registry, ...(options.runtimeLock ? { lock: options.runtimeLock } : {}),
|
||||
...(options.testSigningIdentity ? { testSigningIdentity: options.testSigningIdentity } : { signingIdentity })
|
||||
}).bundleDirectory)
|
||||
}
|
||||
return assembleAgentPackage({
|
||||
...options,
|
||||
projectRoot,
|
||||
@@ -529,7 +556,8 @@ function buildAgentPackage(options) {
|
||||
? {}
|
||||
: { signingIdentity }),
|
||||
agentBundle,
|
||||
runtimeBundle: runtime.bundleDirectory
|
||||
runtimeBundle: runtime.bundleDirectory,
|
||||
additionalRuntimeBundles
|
||||
})
|
||||
} finally {
|
||||
rmSync(agentRoot, { recursive: true, force: true })
|
||||
@@ -555,6 +583,9 @@ function parseArguments(argv) {
|
||||
'--minimum-desktop-version',
|
||||
'--node-archive',
|
||||
'--runtime-archive',
|
||||
'--runtime-id',
|
||||
'--continue-archive',
|
||||
'--continue-bundle',
|
||||
'--agent-bundle',
|
||||
'--runtime-bundle',
|
||||
'--output'
|
||||
@@ -579,11 +610,15 @@ function parseArguments(argv) {
|
||||
throw new Error('Agent package arguments are incomplete')
|
||||
}
|
||||
const common = {
|
||||
runtimeId: options.runtimeId ?? 'opencode',
|
||||
platform: options.platform ?? 'linux',
|
||||
architecture: options.arch,
|
||||
minimumDesktopVersion: options.minimumDesktopVersion,
|
||||
output: resolve(options.output)
|
||||
}
|
||||
if (!['opencode', 'continue'].includes(common.runtimeId)) {
|
||||
throw new Error('Unsupported Agent package Runtime')
|
||||
}
|
||||
targetName(common.architecture, common.platform)
|
||||
if (command === 'build') {
|
||||
if (!options.nodeArchive || !options.runtimeArchive) {
|
||||
@@ -593,7 +628,8 @@ function parseArguments(argv) {
|
||||
command,
|
||||
...common,
|
||||
nodeArchive: resolve(options.nodeArchive),
|
||||
runtimeArchive: resolve(options.runtimeArchive)
|
||||
runtimeArchive: resolve(options.runtimeArchive),
|
||||
continueArchive: options.continueArchive ? resolve(options.continueArchive) : undefined
|
||||
}
|
||||
}
|
||||
if (!options.agentBundle || !options.runtimeBundle) {
|
||||
@@ -603,7 +639,8 @@ function parseArguments(argv) {
|
||||
command,
|
||||
...common,
|
||||
agentBundle: resolve(options.agentBundle),
|
||||
runtimeBundle: resolve(options.runtimeBundle)
|
||||
runtimeBundle: resolve(options.runtimeBundle),
|
||||
additionalRuntimeBundles: options.continueBundle ? [resolve(options.continueBundle)] : []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,7 +159,10 @@ function portableRequiredPaths(directory) {
|
||||
}
|
||||
|
||||
function assertPortableOutput(directory) {
|
||||
const requiredPaths = portableRequiredPaths(directory)
|
||||
const requiredPaths = [
|
||||
...portableRequiredPaths(directory),
|
||||
join(directory, 'resources', 'runtimes', 'dsh', 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js')
|
||||
]
|
||||
const missing = requiredPaths.filter(
|
||||
(filePath) => !statSync(filePath, { throwIfNoEntry: false })?.isFile()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// PERF-17 regression gate for the PERF-11 full-App benchmark report.
|
||||
// Usage: node build/check-perf-thresholds.cjs <result.json | report directory> [thresholds.json]
|
||||
// Exit codes: 0 all limits met, 1 a limit, scenario or status failed,
|
||||
// 2 the report cannot be judged (throttled run, unreadable input).
|
||||
const { existsSync, readFileSync, statSync } = require('node:fs')
|
||||
const { join, resolve } = require('node:path')
|
||||
|
||||
const defaultThresholdsPath = join(__dirname, 'perf-thresholds.json')
|
||||
|
||||
function metricValue(scenario, path) {
|
||||
let value = scenario
|
||||
for (const key of path.split('.')) {
|
||||
if (value === null || value === undefined) return undefined
|
||||
value = value[key]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Pure: judges a parsed report against parsed thresholds.
|
||||
// Returns { ok, refused, reason?, rows: [{ scenario, metric, value, limit, pass, note? }] }.
|
||||
function evaluate(report, thresholds) {
|
||||
const rows = []
|
||||
const throttle = Number(report?.environment?.rendererCpuThrottle ?? 1)
|
||||
const maxThrottle = Number(thresholds.maxRendererCpuThrottle ?? 1)
|
||||
if (!(throttle <= maxThrottle)) {
|
||||
return { ok: false, refused: true, reason: `renderer CPU throttle ${throttle}x exceeds ${maxThrottle}x; thresholds apply to non-throttled runs only`, rows }
|
||||
}
|
||||
const requiredStatus = thresholds.requiredStatus ?? 'passed'
|
||||
rows.push({ scenario: '(report)', metric: 'status', value: report?.status ?? null, limit: `= ${requiredStatus}`, pass: report?.status === requiredStatus })
|
||||
const scenarios = Array.isArray(report?.scenarios) ? report.scenarios : []
|
||||
for (const [id, limits] of Object.entries(thresholds.scenarios ?? {})) {
|
||||
const scenario = scenarios.find(entry => entry?.id === id)
|
||||
if (!scenario) {
|
||||
const optional = limits.optional === true
|
||||
rows.push({ scenario: id, metric: '(present)', value: 'missing', limit: optional ? 'optional' : 'required', pass: optional })
|
||||
continue
|
||||
}
|
||||
for (const [metric, bound] of Object.entries(limits)) {
|
||||
if (metric === 'optional') continue
|
||||
const raw = metricValue(scenario, metric)
|
||||
const value = typeof raw === 'number' && Number.isFinite(raw) ? raw : null
|
||||
let pass = value !== null
|
||||
const limitParts = []
|
||||
if (typeof bound.max === 'number') { limitParts.push(`<= ${bound.max}`); pass = pass && value <= bound.max }
|
||||
if (typeof bound.min === 'number') { limitParts.push(`>= ${bound.min}`); pass = pass && value >= bound.min }
|
||||
rows.push({ scenario: id, metric, value, limit: limitParts.join(', '), pass, ...(value === null ? { note: 'metric missing' } : {}) })
|
||||
}
|
||||
}
|
||||
return { ok: rows.every(row => row.pass), refused: false, rows }
|
||||
}
|
||||
|
||||
function resolveReportPath(input) {
|
||||
const path = resolve(input)
|
||||
return existsSync(path) && statSync(path).isDirectory() ? join(path, 'result.json') : path
|
||||
}
|
||||
|
||||
function formatValue(value) {
|
||||
if (typeof value !== 'number') return String(value)
|
||||
return Number.isInteger(value) ? String(value) : value.toFixed(value < 1 ? 3 : 1)
|
||||
}
|
||||
|
||||
function run(argv) {
|
||||
const [input, thresholdsArg] = argv
|
||||
if (!input) {
|
||||
console.error('usage: node build/check-perf-thresholds.cjs <result.json | report directory> [thresholds.json]')
|
||||
return 2
|
||||
}
|
||||
const reportPath = resolveReportPath(input)
|
||||
const thresholdsPath = resolve(thresholdsArg || defaultThresholdsPath)
|
||||
let report
|
||||
let thresholds
|
||||
try {
|
||||
report = JSON.parse(readFileSync(reportPath, 'utf8'))
|
||||
thresholds = JSON.parse(readFileSync(thresholdsPath, 'utf8'))
|
||||
} catch (error) {
|
||||
console.error(`cannot read report or thresholds: ${error instanceof Error ? error.message : error}`)
|
||||
return 2
|
||||
}
|
||||
const result = evaluate(report, thresholds)
|
||||
console.log(`\nperf thresholds: ${reportPath}`)
|
||||
if (result.refused) {
|
||||
console.error(`refused: ${result.reason}`)
|
||||
return 2
|
||||
}
|
||||
console.table(result.rows.map(row => ({
|
||||
scenario: row.scenario,
|
||||
metric: row.metric,
|
||||
value: formatValue(row.value),
|
||||
limit: row.limit,
|
||||
pass: row.pass ? 'ok' : `FAIL${row.note ? ` (${row.note})` : ''}`
|
||||
})))
|
||||
const failed = result.rows.filter(row => !row.pass).length
|
||||
console.log(failed ? `${failed} of ${result.rows.length} checks failed` : `all ${result.rows.length} checks passed`)
|
||||
return result.ok ? 0 : 1
|
||||
}
|
||||
|
||||
module.exports = { evaluate, run, defaultThresholdsPath }
|
||||
|
||||
if (require.main === module) process.exitCode = run(process.argv.slice(2))
|
||||
@@ -184,7 +184,7 @@ async function run() {
|
||||
model: 'qwen-plus',
|
||||
supportsImageInput: false,
|
||||
requestHeaders: {},
|
||||
harnessVersion: '0.1.2-rc.1',
|
||||
harnessVersion: '0.1.7-rc.2',
|
||||
credentialRefs: ['GOODBUDDY_HARNESS_MODEL_API_KEY'],
|
||||
skillPackages: [],
|
||||
extensionPackages: [],
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
const { cp, mkdir, readFile, rm } = require('node:fs/promises');
|
||||
const { existsSync } = require('node:fs');
|
||||
const { dirname, join, relative } = require('node:path');
|
||||
const tar = require('tar');
|
||||
|
||||
// Preserve npm's installed layout (including nested versions) and every package
|
||||
// asset. The official loader discovers plugins and browser assets at runtime.
|
||||
async function prepareNativeDshResources(projectDir, platform, architecture, downloadPackage, lockedIntegrity) {
|
||||
const target = join(projectDir, '.runtime-resources', `dsh-${architecture}`);
|
||||
await rm(target, { recursive: true, force: true });
|
||||
await mkdir(target, { recursive: true });
|
||||
const visited = new Set();
|
||||
function locate(name, from) {
|
||||
for (let directory = from; ; directory = dirname(directory)) {
|
||||
const candidate = join(directory, 'node_modules', name);
|
||||
if (existsSync(join(candidate, 'package.json'))) return candidate;
|
||||
if (dirname(directory) === directory) return undefined;
|
||||
}
|
||||
}
|
||||
async function copyPackage(directory) {
|
||||
if (visited.has(directory)) return;
|
||||
visited.add(directory);
|
||||
const manifest = JSON.parse(await readFile(join(directory, 'package.json'), 'utf8'));
|
||||
await cp(directory, join(target, relative(projectDir, directory)), { recursive: true, verbatimSymlinks: true });
|
||||
const optional = manifest.optionalDependencies ?? {};
|
||||
for (const name of Object.keys({ ...manifest.dependencies, ...manifest.peerDependencies, ...optional })) {
|
||||
const dependency = locate(name, directory);
|
||||
if (dependency) await copyPackage(dependency);
|
||||
else if (!(name in optional) && !manifest.peerDependenciesMeta?.[name]?.optional) {
|
||||
throw new Error(`DS Web bundle is missing ${name} required by ${manifest.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
await copyPackage(join(projectDir, 'node_modules', '@deepseek-ai', 'dsh'));
|
||||
// Cross-architecture packaging cannot rely on the build host's npm optional
|
||||
// selection. Fetch the exact target Sharp packages from the existing lock.
|
||||
const sharp = JSON.parse(await readFile(join(projectDir, 'node_modules', 'sharp', 'package.json'), 'utf8'));
|
||||
const lock = JSON.parse(await readFile(join(projectDir, 'package-lock.json'), 'utf8'));
|
||||
const nativePackages = [`@img/sharp-${platform}-${architecture}`];
|
||||
if (platform !== 'win32') nativePackages.push(`@img/sharp-libvips-${platform}-${architecture}`);
|
||||
for (const name of nativePackages) {
|
||||
const version = sharp.optionalDependencies[name];
|
||||
if (!version) throw new Error(`DS Web Sharp does not support ${platform}/${architecture}`);
|
||||
const destination = join(target, 'node_modules', 'sharp', 'node_modules', name);
|
||||
if (existsSync(join(destination, 'package.json'))) continue;
|
||||
const locked = lock.packages[`node_modules/sharp/node_modules/${name}`] ?? lock.packages[`node_modules/${name}`];
|
||||
if (locked?.version !== version || !locked.integrity) throw new Error(`Missing locked ${name}@${version}`);
|
||||
const integrity = locked.integrity;
|
||||
const archive = await downloadPackage(projectDir, name, version, integrity);
|
||||
await mkdir(destination, { recursive: true });
|
||||
await tar.x({ file: archive, cwd: destination, strip: 1 });
|
||||
}
|
||||
const koffi = JSON.parse(await readFile(join(projectDir, 'node_modules', 'koffi', 'package.json'), 'utf8'));
|
||||
const koffiName = `@koromix/koffi-${platform}-${architecture}`;
|
||||
const koffiDestination = join(target, 'node_modules', koffiName);
|
||||
if (!existsSync(join(koffiDestination, 'package.json'))) {
|
||||
const integrity = await lockedIntegrity(projectDir, koffiName, koffi.version);
|
||||
const archive = await downloadPackage(projectDir, koffiName, koffi.version, integrity);
|
||||
await mkdir(koffiDestination, { recursive: true });
|
||||
await tar.x({ file: archive, cwd: koffiDestination, strip: 1 });
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
module.exports = { prepareNativeDshResources };
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"note": "PERF-17 regression ceilings for the PERF-11 full-App benchmark (npm run perf:app). They apply ONLY to non-throttled runs (environment.rendererCpuThrottle = 1, the default) with the default GB_PERF_* scale, on the reference machine of the 2026-10-02 baseline (Intel Core Ultra X7 358H x16, 32 GB, DPR 1, 120 Hz). Limits are about 2-3x the 2026-10-02 values so that run-to-run noise does not fail; they catch regressions of the size seen before PERF-12 (e.g. ~130 long tasks / 9 s while streaming). Tighten them as PERF-12..PERF-14 land; do not loosen them to make a regression pass. Every listed scenario must be present unless marked optional, and report.status must equal requiredStatus.",
|
||||
"requiredStatus": "passed",
|
||||
"maxRendererCpuThrottle": 1,
|
||||
"scenarios": {
|
||||
"idle": {
|
||||
"renderer.longTasks.count": { "max": 2 },
|
||||
"renderer.longTasks.totalMs": { "max": 200 },
|
||||
"renderer.frames.p95": { "max": 20 },
|
||||
"renderer.frames.jankRatio": { "max": 0.05 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
},
|
||||
"typing-small": {
|
||||
"renderer.longTasks.count": { "max": 3 },
|
||||
"renderer.longTasks.totalMs": { "max": 300 },
|
||||
"renderer.frames.p95": { "max": 20 },
|
||||
"renderer.frames.jankRatio": { "max": 0.05 },
|
||||
"latency.p95": { "max": 25 },
|
||||
"latency.max": { "max": 100 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
},
|
||||
"idle-seeded": {
|
||||
"renderer.longTasks.count": { "max": 2 },
|
||||
"renderer.longTasks.totalMs": { "max": 200 },
|
||||
"renderer.frames.p95": { "max": 20 },
|
||||
"renderer.frames.jankRatio": { "max": 0.05 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
},
|
||||
"typing-seeded": {
|
||||
"renderer.longTasks.count": { "max": 3 },
|
||||
"renderer.longTasks.totalMs": { "max": 300 },
|
||||
"renderer.frames.p95": { "max": 20 },
|
||||
"renderer.frames.jankRatio": { "max": 0.05 },
|
||||
"latency.p95": { "max": 25 },
|
||||
"latency.max": { "max": 100 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
},
|
||||
"switch-conversations": {
|
||||
"renderer.longTasks.count": { "max": 5 },
|
||||
"renderer.longTasks.totalMs": { "max": 500 },
|
||||
"renderer.frames.jankRatio": { "max": 0.15 },
|
||||
"latency.p95": { "max": 350 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
},
|
||||
"switch-conversations-warm": {
|
||||
"renderer.longTasks.count": { "max": 3 },
|
||||
"renderer.longTasks.totalMs": { "max": 300 },
|
||||
"renderer.frames.jankRatio": { "max": 0.15 },
|
||||
"latency.p95": { "max": 200 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
},
|
||||
"switch-conversations-hot": {
|
||||
"optional": true,
|
||||
"renderer.longTasks.count": { "max": 3 },
|
||||
"renderer.longTasks.totalMs": { "max": 200 },
|
||||
"renderer.frames.jankRatio": { "max": 0.1 },
|
||||
"latency.p95": { "max": 100 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
},
|
||||
"open-long-conversation": {
|
||||
"renderer.longTasks.count": { "max": 4 },
|
||||
"renderer.longTasks.totalMs": { "max": 400 },
|
||||
"latency.p95": { "max": 400 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
},
|
||||
"scroll-long-conversation": {
|
||||
"renderer.longTasks.count": { "max": 2 },
|
||||
"renderer.longTasks.totalMs": { "max": 200 },
|
||||
"renderer.frames.p95": { "max": 20 },
|
||||
"renderer.frames.jankRatio": { "max": 0.05 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
},
|
||||
"stream-seeded": {
|
||||
"renderer.longTasks.count": { "max": 10 },
|
||||
"renderer.longTasks.totalMs": { "max": 1500 },
|
||||
"renderer.frames.p95": { "max": 34 },
|
||||
"renderer.frames.jankRatio": { "max": 0.15 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
},
|
||||
"stream-and-type": {
|
||||
"renderer.longTasks.count": { "max": 10 },
|
||||
"renderer.longTasks.totalMs": { "max": 1500 },
|
||||
"renderer.frames.p95": { "max": 34 },
|
||||
"renderer.frames.jankRatio": { "max": 0.2 },
|
||||
"latency.p95": { "max": 50 },
|
||||
"main.eventLoopDelay.p99": { "max": 100 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,17 @@ const profileDigests = Object.freeze({
|
||||
"sha256:ac7696abe504bbea444d8ad80dd8faa96437e6dbb27ed987ce46e1045f3dd365",
|
||||
acp: "sha256:0e3764ab897258bc0234162357c2eefc3faeb9918080ae81b60e2084ed7996a6",
|
||||
});
|
||||
function runtimeProfileDigests(runtimeId) {
|
||||
if (runtimeId === 'opencode') return profileDigests;
|
||||
if (runtimeId !== 'continue') throw new Error('Unsupported Runtime');
|
||||
return {
|
||||
adapter: `sha256:${sha256('GoodBuddy Continue HTTP ACP facade v1')}`,
|
||||
acp: `sha256:${sha256(canonicalJson({ protocolVersion: 1, capabilities: {
|
||||
loadSession: true, mcpCapabilities: { http: true }, promptCapabilities: { image: true },
|
||||
sessionCapabilities: { resume: {}, close: {} }
|
||||
} }))}`
|
||||
};
|
||||
}
|
||||
const fixedLimits = Object.freeze({
|
||||
// Zero leaves prompt duration to its requested deadline and cancellation.
|
||||
maximumPromptRuntimeMilliseconds: 0,
|
||||
@@ -365,9 +376,9 @@ function validateManifestShape(manifest) {
|
||||
if (
|
||||
manifest?.formatVersion !== 2 ||
|
||||
manifest.product !== "GoodBuddy" ||
|
||||
manifest.runtimeId !== "opencode" ||
|
||||
manifest.runtimeVersion !== "1.18.29" ||
|
||||
manifest.provider !== "opencode" ||
|
||||
!['opencode', 'continue'].includes(manifest.runtimeId) ||
|
||||
manifest.runtimeVersion !== (manifest.runtimeId === 'continue' ? '1.5.47' : '1.18.29') ||
|
||||
manifest.provider !== manifest.runtimeId ||
|
||||
!(
|
||||
manifest.platform === "linux" ||
|
||||
(manifest.platform === "darwin" && manifest.architecture === "arm64")
|
||||
@@ -396,10 +407,10 @@ function validateManifestShape(manifest) {
|
||||
throw new Error("Runtime manifest limits contract is invalid");
|
||||
}
|
||||
if (
|
||||
manifest.entrypoint?.identity !== "opencode-acp" ||
|
||||
manifest.entrypoint.path !== "bin/opencode" ||
|
||||
manifest.entrypoint?.identity !== `${manifest.runtimeId}-acp` ||
|
||||
manifest.entrypoint.path !== (manifest.runtimeId === 'continue' ? 'lib/continue/dist/cn.js' : 'bin/opencode') ||
|
||||
!/^[a-f0-9]{64}$/u.test(manifest.entrypoint.sha256) ||
|
||||
JSON.stringify(manifest.entrypoint.argvPrefix) !== JSON.stringify(["acp"])
|
||||
JSON.stringify(manifest.entrypoint.argvPrefix) !== JSON.stringify(manifest.runtimeId === 'continue' ? [] : ['acp'])
|
||||
) {
|
||||
throw new Error("Runtime manifest entrypoint is invalid");
|
||||
}
|
||||
@@ -436,13 +447,14 @@ function assertManifestMatchesLock(
|
||||
architecture,
|
||||
platform = "linux",
|
||||
) {
|
||||
const expected = lock.runtimes.opencode;
|
||||
const expected = lock.runtimes[manifest.runtimeId];
|
||||
if (!expected) throw new Error('Runtime is not locked');
|
||||
const target =
|
||||
expected.targets[
|
||||
platform === "linux" ? architecture : targetName(architecture, platform)
|
||||
];
|
||||
if (
|
||||
manifest.runtimeId !== "opencode" ||
|
||||
manifest.runtimeId !== expected.provider ||
|
||||
manifest.runtimeVersion !== expected.version ||
|
||||
manifest.provider !== expected.provider ||
|
||||
manifest.platform !== platform ||
|
||||
@@ -457,8 +469,8 @@ function assertManifestMatchesLock(
|
||||
JSON.stringify(expected.allowedEnvironmentNames) ||
|
||||
manifest.protocol?.major !== expected.protocol.major ||
|
||||
manifest.protocol?.minor !== expected.protocol.minor ||
|
||||
manifest.adapterDigest !== profileDigests.adapter ||
|
||||
manifest.acpCapabilitiesDigest !== profileDigests.acp
|
||||
manifest.adapterDigest !== runtimeProfileDigests(manifest.runtimeId).adapter ||
|
||||
manifest.acpCapabilitiesDigest !== runtimeProfileDigests(manifest.runtimeId).acp
|
||||
) {
|
||||
throw new Error(
|
||||
"Runtime manifest does not match the locked OpenCode profile",
|
||||
@@ -572,7 +584,7 @@ function verifyBundleDirectory(bundleDirectoryInput, options = {}) {
|
||||
) {
|
||||
throw new Error("Runtime entrypoint payload is missing");
|
||||
}
|
||||
assertElfArchitecture(
|
||||
if (manifest.runtimeId === 'opencode') assertElfArchitecture(
|
||||
join(bundleDirectory, ...manifest.entrypoint.path.split("/")),
|
||||
architecture,
|
||||
platform,
|
||||
@@ -584,8 +596,8 @@ function verifyBundleDirectory(bundleDirectoryInput, options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function lockedArchive(lock, architecture, archivePath, platform = "linux") {
|
||||
const input = resolveLockedRuntimeInput(lock, architecture, platform);
|
||||
function lockedArchive(lock, architecture, archivePath, platform = "linux", runtimeId = 'opencode') {
|
||||
const input = resolveLockedRuntimeInput(lock, architecture, platform, runtimeId);
|
||||
if (!archivePath || !existsSync(archivePath)) {
|
||||
throw new Error(`Locked OpenCode archive is required: ${input.archive}`);
|
||||
}
|
||||
@@ -603,14 +615,15 @@ function lockedArchive(lock, architecture, archivePath, platform = "linux") {
|
||||
`Runtime archive integrity mismatch for ${targetName(architecture)}`,
|
||||
);
|
||||
}
|
||||
return lock.runtimes.opencode.targets[
|
||||
return lock.runtimes[runtimeId].targets[
|
||||
platform === "linux" ? architecture : targetName(architecture, platform)
|
||||
];
|
||||
}
|
||||
|
||||
function resolveLockedRuntimeInput(lock, architecture, platform = "linux") {
|
||||
function resolveLockedRuntimeInput(lock, architecture, platform = "linux", runtimeId = 'opencode') {
|
||||
targetName(architecture, platform);
|
||||
const runtime = lock.runtimes.opencode;
|
||||
const runtime = lock.runtimes[runtimeId];
|
||||
if (!runtime) throw new Error('Runtime is not locked');
|
||||
const target =
|
||||
runtime.targets[
|
||||
platform === "linux" ? architecture : targetName(architecture, platform)
|
||||
@@ -619,7 +632,7 @@ function resolveLockedRuntimeInput(lock, architecture, platform = "linux") {
|
||||
packageName: target.package,
|
||||
version: runtime.version,
|
||||
integrity: target.integrity,
|
||||
archive: `${target.package}-${runtime.version}.tgz`,
|
||||
archive: `${target.package.replace('@', '').replace('/', '-')}-${runtime.version}.tgz`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -636,24 +649,24 @@ function createManifest(bundleDirectory, metadata) {
|
||||
path,
|
||||
size: statSync(filePath).size,
|
||||
sha256: sha256FileSync(filePath),
|
||||
mode: path === "bin/opencode" ? "0755" : "0644",
|
||||
mode: path === metadata.runtime.entrypoint ? "0755" : "0644",
|
||||
};
|
||||
});
|
||||
const entrypoint = files.find((file) => file.path === "bin/opencode");
|
||||
const entrypoint = files.find((file) => file.path === metadata.runtime.entrypoint);
|
||||
if (!entrypoint) {
|
||||
throw new Error("Runtime entrypoint is missing");
|
||||
}
|
||||
const initial = {
|
||||
formatVersion: 2,
|
||||
product: "GoodBuddy",
|
||||
runtimeId: "opencode",
|
||||
runtimeId: metadata.runtime.provider,
|
||||
runtimeVersion: metadata.runtime.version,
|
||||
provider: metadata.runtime.provider,
|
||||
platform: metadata.platform ?? "linux",
|
||||
architecture: metadata.architecture,
|
||||
signingKeyId: metadata.signingKeyId,
|
||||
bundleDigest: `sha256:${"0".repeat(64)}`,
|
||||
adapterDigest: profileDigests.adapter,
|
||||
adapterDigest: runtimeProfileDigests(metadata.runtime.provider).adapter,
|
||||
sourcePackage: metadata.sourcePackage,
|
||||
entrypoint: {
|
||||
identity: metadata.runtime.entrypointIdentity,
|
||||
@@ -664,15 +677,15 @@ function createManifest(bundleDirectory, metadata) {
|
||||
files,
|
||||
licenses: [
|
||||
{
|
||||
package: "opencode-ai",
|
||||
package: metadata.runtime.provider === 'continue' ? '@continuedev/cli' : 'opencode-ai',
|
||||
version: metadata.runtime.version,
|
||||
spdx: "MIT",
|
||||
path: "licenses/opencode-MIT.txt",
|
||||
spdx: metadata.runtime.provider === 'continue' ? 'Apache-2.0' : 'MIT',
|
||||
path: metadata.runtime.provider === 'continue' ? 'licenses/continue-Apache-2.0.txt' : 'licenses/opencode-MIT.txt',
|
||||
},
|
||||
],
|
||||
allowedEnvironmentNames: metadata.runtime.allowedEnvironmentNames,
|
||||
protocol: metadata.runtime.protocol,
|
||||
acpCapabilitiesDigest: profileDigests.acp,
|
||||
acpCapabilitiesDigest: runtimeProfileDigests(metadata.runtime.provider).acp,
|
||||
limits: fixedLimits,
|
||||
};
|
||||
return {
|
||||
@@ -685,11 +698,11 @@ function defaultOutputRoot(projectRoot = root) {
|
||||
return join(projectRoot, ".remote-runtime-resources");
|
||||
}
|
||||
|
||||
function bundleDirectory(outputRoot, architecture, digest, platform = "linux") {
|
||||
function bundleDirectory(outputRoot, architecture, digest, platform = "linux", runtimeId = 'opencode') {
|
||||
return join(
|
||||
outputRoot,
|
||||
targetName(architecture, platform),
|
||||
"opencode",
|
||||
runtimeId,
|
||||
digest.slice("sha256:".length),
|
||||
);
|
||||
}
|
||||
@@ -699,12 +712,14 @@ function buildRuntimeBundle(options) {
|
||||
const architecture = options.architecture;
|
||||
const platform = options.platform ?? "linux";
|
||||
const lock = options.lock ?? readRemoteRuntimeLock(projectRoot);
|
||||
const runtime = lock.runtimes.opencode;
|
||||
const runtimeId = options.runtimeId ?? 'opencode';
|
||||
const runtime = lock.runtimes[runtimeId];
|
||||
const sourcePackage = lockedArchive(
|
||||
lock,
|
||||
architecture,
|
||||
options.runtimeArchive,
|
||||
platform,
|
||||
runtimeId,
|
||||
);
|
||||
const registry = options.registry ?? readTrustedKeyRegistry(projectRoot);
|
||||
const signingIdentity =
|
||||
@@ -730,6 +745,7 @@ function buildRuntimeBundle(options) {
|
||||
sync: true,
|
||||
strict: true,
|
||||
filter(path) {
|
||||
if (runtimeId === 'continue') return path === 'package/package.json' || path.startsWith('package/dist/') || path === 'package/LICENSE';
|
||||
return (
|
||||
path === "package/package.json" || path === "package/bin/opencode"
|
||||
);
|
||||
@@ -753,6 +769,15 @@ function buildRuntimeBundle(options) {
|
||||
"OpenCode source package version does not match the lock",
|
||||
);
|
||||
}
|
||||
if (runtimeId === 'continue') {
|
||||
mkdirSync(join(staging, 'lib'), { mode: 0o700 });
|
||||
cpSync(join(extracted, 'package'), join(staging, 'lib', 'continue'), { recursive: true });
|
||||
for (const file of listFiles(staging)) chmodSync(file, 0o644);
|
||||
chmodSync(join(staging, runtime.entrypoint), 0o755);
|
||||
mkdirSync(join(staging, 'licenses'), { mode: 0o700 });
|
||||
// The pinned CN package declares Apache-2.0 but omits the license text.
|
||||
copyFileSync(join(projectRoot, 'node_modules', '@agentclientprotocol', 'sdk', 'LICENSE'), join(staging, 'licenses', 'continue-Apache-2.0.txt'));
|
||||
} else {
|
||||
mkdirSync(join(staging, "bin"), { mode: 0o700 });
|
||||
copyFileSync(
|
||||
join(extracted, "package", "bin", "opencode"),
|
||||
@@ -780,6 +805,7 @@ function buildRuntimeBundle(options) {
|
||||
join(staging, "licenses", "opencode-MIT.txt"),
|
||||
);
|
||||
chmodSync(join(staging, "licenses", "opencode-MIT.txt"), 0o644);
|
||||
}
|
||||
const manifest = createManifest(staging, {
|
||||
platform,
|
||||
architecture,
|
||||
@@ -811,6 +837,7 @@ function buildRuntimeBundle(options) {
|
||||
architecture,
|
||||
manifest.bundleDigest,
|
||||
platform,
|
||||
runtimeId,
|
||||
);
|
||||
mkdirSync(dirname(destination), { recursive: true, mode: 0o700 });
|
||||
if (existsSync(destination)) {
|
||||
@@ -849,6 +876,8 @@ function buildRuntimeBundle(options) {
|
||||
|
||||
function createRuntimeArchive(bundleDirectoryInput, archivePath) {
|
||||
const directory = resolve(bundleDirectoryInput);
|
||||
const manifest = readJson(join(directory, manifestFileName), 'Runtime manifest');
|
||||
const modes = new Map(manifest.files.map(file => [file.path, parseInt(file.mode, 8)]));
|
||||
const entries = listFiles(directory)
|
||||
.map((filePath) => posixRelative(directory, filePath))
|
||||
.sort(compareUtf8);
|
||||
@@ -862,6 +891,9 @@ function createRuntimeArchive(bundleDirectoryInput, archivePath) {
|
||||
portable: true,
|
||||
noPax: true,
|
||||
mtime: new Date(0),
|
||||
onWriteEntry(entry) {
|
||||
if (entry.stat) entry.stat.mode = modes.get(entry.path) ?? 0o644;
|
||||
},
|
||||
},
|
||||
entries,
|
||||
);
|
||||
@@ -896,6 +928,8 @@ function importRuntimeArchive(archivePath, options) {
|
||||
outputRoot,
|
||||
architecture,
|
||||
verified.manifest.bundleDigest,
|
||||
verified.manifest.platform,
|
||||
verified.manifest.runtimeId,
|
||||
);
|
||||
mkdirSync(dirname(destination), { recursive: true, mode: 0o700 });
|
||||
if (existsSync(destination)) {
|
||||
@@ -917,8 +951,8 @@ function importRuntimeArchive(archivePath, options) {
|
||||
}
|
||||
}
|
||||
|
||||
function findOnlyBundle(outputRoot, architecture) {
|
||||
const runtimeRoot = join(outputRoot, targetName(architecture), "opencode");
|
||||
function findOnlyBundle(outputRoot, architecture, runtimeId = 'opencode') {
|
||||
const runtimeRoot = join(outputRoot, targetName(architecture), runtimeId);
|
||||
const entries = existsSync(runtimeRoot)
|
||||
? readdirSync(runtimeRoot, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory())
|
||||
@@ -954,6 +988,7 @@ function parseArguments(argv) {
|
||||
const allowed = new Set([
|
||||
"--arch",
|
||||
"--runtime-archive",
|
||||
"--runtime-id",
|
||||
"--archive",
|
||||
"--output-root",
|
||||
"--bundle",
|
||||
@@ -997,6 +1032,7 @@ function main(argv = process.argv.slice(2)) {
|
||||
if (options.command === "build") {
|
||||
const verified = buildRuntimeBundle({
|
||||
architecture: options.arch,
|
||||
runtimeId: options.runtimeId,
|
||||
runtimeArchive: resolve(options.runtimeArchive ?? ""),
|
||||
outputRoot,
|
||||
});
|
||||
@@ -1006,7 +1042,7 @@ function main(argv = process.argv.slice(2)) {
|
||||
root,
|
||||
"dist",
|
||||
"remote-runtime-bundles",
|
||||
`goodbuddy-opencode-${targetName(options.arch)}.tar`,
|
||||
`goodbuddy-${options.runtimeId ?? 'opencode'}-${targetName(options.arch)}.tar`,
|
||||
);
|
||||
createRuntimeArchive(verified.bundleDirectory, archivePath);
|
||||
console.log(
|
||||
@@ -1027,7 +1063,7 @@ function main(argv = process.argv.slice(2)) {
|
||||
verifyBundleDirectory(
|
||||
options.bundle
|
||||
? resolve(options.bundle)
|
||||
: findOnlyBundle(outputRoot, options.arch),
|
||||
: findOnlyBundle(outputRoot, options.arch, options.runtimeId),
|
||||
{ architecture: options.arch },
|
||||
);
|
||||
console.log(`Remote Runtime bundle verified: ${targetName(options.arch)}`);
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
// Full-App interaction benchmark supervisor (PERF-11).
|
||||
// Starts a loopback fake OpenAI-compatible model on 127.0.0.1:11434 (the
|
||||
// fresh-profile default model endpoint), launches the current production
|
||||
// build in an isolated profile through tests/support/app-perf-driver.mjs,
|
||||
// enforces a total deadline and prints a summary of the driver report.
|
||||
const { spawn } = require('node:child_process')
|
||||
const { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } = require('node:fs')
|
||||
const { createServer } = require('node:http')
|
||||
const { tmpdir } = require('node:os')
|
||||
const { join, resolve } = require('node:path')
|
||||
|
||||
const root = resolve(__dirname, '..')
|
||||
// 11434 is the fresh-profile default endpoint. Another GoodBuddy (or Ollama)
|
||||
// may already use it; GB_PERF_MODEL_PORT moves the fake model and points the
|
||||
// isolated profile at it through the GOODBUDDY_MODEL_* environment variables.
|
||||
const defaultModelPort = 11434
|
||||
const modelPort = Number(process.env.GB_PERF_MODEL_PORT || defaultModelPort)
|
||||
const totalTimeoutMs = Number(process.env.GB_PERF_TIMEOUT_MS || 600_000)
|
||||
const keepProfile = process.env.GB_PERF_KEEP_PROFILE === '1'
|
||||
const streamBytes = Number(process.env.GB_PERF_STREAM_BYTES || 40_000)
|
||||
const chunkChars = Number(process.env.GB_PERF_CHUNK_CHARS || 12)
|
||||
const streamCharsPerSecond = Number(process.env.GB_PERF_STREAM_CPS || 2_000)
|
||||
const tickIntervalMs = 10
|
||||
|
||||
function buildAnswer(targetBytes) {
|
||||
const blocks = [
|
||||
'## Section {n}\n\nThis paragraph explains the {n}th step of the benchmark answer with **bold**, `inline code`, and a [link](https://example.com/{n}).\n\n',
|
||||
'- First item for block {n}\n- Second item with *emphasis*\n - Nested item {n}\n- Third item\n\n',
|
||||
'```ts\nexport function step{n}(input: number): number {\n const value = input * {n}\n return value + 1\n}\n```\n\n',
|
||||
'| Column | Value {n} | Notes |\n| --- | --- | --- |\n| alpha | {n} | first |\n| beta | {n}1 | second |\n\n',
|
||||
// Single-line display math is common in model output. GB_PERF_CORPUS=multiline-math
|
||||
// puts the delimiters on their own lines instead.
|
||||
process.env.GB_PERF_CORPUS === 'multiline-math'
|
||||
? 'Inline math $a_{n} = b^2 + c$ and display math:\n\n$$\n\\sum_{i=1}^{{n}} i = \\frac{{n}({n}+1)}{2}\n$$\n\n'
|
||||
: 'Inline math $a_{n} = b^2 + c$ and display math:\n\n$$\\sum_{i=1}^{{n}} i = \\frac{{n}({n}+1)}{2}$$\n\n',
|
||||
'> Quoted note {n}: keep the streaming renderer honest with mixed blocks.\n\n'
|
||||
]
|
||||
let text = ''
|
||||
let n = 1
|
||||
while (Buffer.byteLength(text) < targetBytes) {
|
||||
text += blocks[n % blocks.length].replaceAll('{n}', String(n))
|
||||
n += 1
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
function startModelServer(ledger) {
|
||||
const answer = buildAnswer(streamBytes)
|
||||
const server = createServer((request, response) => {
|
||||
const started = Date.now()
|
||||
const entry = { method: request.method, path: request.url, startedAt: started }
|
||||
ledger.push(entry)
|
||||
if (request.method === 'GET' && request.url?.endsWith('/models')) {
|
||||
response.writeHead(200, { 'content-type': 'application/json' })
|
||||
response.end(JSON.stringify({ object: 'list', data: [{ id: 'qwen3', object: 'model' }] }))
|
||||
return
|
||||
}
|
||||
if (request.method !== 'POST' || !request.url?.endsWith('/chat/completions')) {
|
||||
response.writeHead(404).end()
|
||||
return
|
||||
}
|
||||
let body = ''
|
||||
request.on('data', chunk => { body += chunk })
|
||||
request.on('end', () => {
|
||||
let parsed = {}
|
||||
try { parsed = JSON.parse(body) } catch { /* counted below */ }
|
||||
const messages = Array.isArray(parsed.messages) ? parsed.messages : []
|
||||
const last = messages.at(-1)
|
||||
const lastText = typeof last?.content === 'string' ? last.content : JSON.stringify(last?.content ?? '')
|
||||
const benchmark = lastText.includes('perf-stream')
|
||||
entry.kind = benchmark ? 'benchmark-stream' : 'auxiliary'
|
||||
entry.stream = parsed.stream === true
|
||||
const content = benchmark ? answer : 'Benchmark title'
|
||||
if (!entry.stream) {
|
||||
response.writeHead(200, { 'content-type': 'application/json' })
|
||||
response.end(JSON.stringify({
|
||||
id: 'perf', object: 'chat.completion', model: 'qwen3',
|
||||
choices: [{ index: 0, finish_reason: 'stop', message: { role: 'assistant', content } }],
|
||||
usage: { prompt_tokens: 10, completion_tokens: 10, total_tokens: 20 }
|
||||
}))
|
||||
entry.finishedAt = Date.now()
|
||||
return
|
||||
}
|
||||
response.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache', connection: 'keep-alive' })
|
||||
const send = payload => response.write(`data: ${JSON.stringify(payload)}\n\n`)
|
||||
const delta = text => send({ id: 'perf', object: 'chat.completion.chunk', model: 'qwen3', choices: [{ index: 0, delta: { content: text }, finish_reason: null }] })
|
||||
send({ id: 'perf', object: 'chat.completion.chunk', model: 'qwen3', choices: [{ index: 0, delta: { role: 'assistant' }, finish_reason: null }] })
|
||||
let offset = 0
|
||||
let chunks = 0
|
||||
const finish = () => {
|
||||
send({ id: 'perf', object: 'chat.completion.chunk', model: 'qwen3', choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] })
|
||||
send({ id: 'perf', object: 'chat.completion.chunk', model: 'qwen3', choices: [], usage: { prompt_tokens: 10, completion_tokens: Math.ceil(content.length / 4), total_tokens: 10 + Math.ceil(content.length / 4) } })
|
||||
response.write('data: [DONE]\n\n')
|
||||
response.end()
|
||||
entry.finishedAt = Date.now()
|
||||
entry.chunks = chunks
|
||||
entry.bytes = Buffer.byteLength(content)
|
||||
}
|
||||
if (!benchmark) { delta(content); finish(); return }
|
||||
// Emit at a fixed character rate, split into small SSE events like a
|
||||
// fast token stream. Pacing is time-based so coarse timers do not
|
||||
// change throughput.
|
||||
const streamStart = Date.now()
|
||||
const tick = () => {
|
||||
if (response.destroyed) { entry.aborted = true; return }
|
||||
const due = Math.min(content.length, Math.ceil(((Date.now() - streamStart) / 1000) * streamCharsPerSecond))
|
||||
while (offset < due) {
|
||||
delta(content.slice(offset, offset + chunkChars))
|
||||
offset += chunkChars
|
||||
chunks += 1
|
||||
}
|
||||
if (offset >= content.length) { finish(); return }
|
||||
setTimeout(tick, tickIntervalMs)
|
||||
}
|
||||
tick()
|
||||
})
|
||||
})
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
server.once('error', reject)
|
||||
server.listen(modelPort, '127.0.0.1', () => resolvePromise(server))
|
||||
})
|
||||
}
|
||||
|
||||
function format(value, digits = 1) {
|
||||
return value === null || value === undefined || Number.isNaN(value) ? '-' : Number(value).toFixed(digits)
|
||||
}
|
||||
|
||||
function printSummary(report) {
|
||||
console.log(`\nGoodBuddy full-App benchmark (${report.status})`)
|
||||
console.log(`source ${report.source?.commit ?? '?'}${report.source?.dirty ? ' (dirty)' : ''}, Electron ${report.environment?.electron}, DPR ${report.environment?.devicePixelRatio}, ${report.environment?.cpu}`)
|
||||
if (report.startup) {
|
||||
console.log(`startup: window ${format(report.startup.windowCreatedMs, 0)} ms, ready-to-show ${format(report.startup.readyToShowMs, 0)} ms, composer ready ${format(report.startup.composerReadyMs, 0)} ms`)
|
||||
}
|
||||
const rows = (report.scenarios ?? []).map(s => ({
|
||||
scenario: s.id,
|
||||
ms: format(s.durationMs, 0),
|
||||
longTasks: `${s.renderer?.longTasks?.count ?? '-'} / ${format(s.renderer?.longTasks?.totalMs, 0)} / ${format(s.renderer?.longTasks?.maxMs, 0)}`,
|
||||
'frame p50/p95/max': `${format(s.renderer?.frames?.p50)} / ${format(s.renderer?.frames?.p95)} / ${format(s.renderer?.frames?.max, 0)}`,
|
||||
'jank%': format(s.renderer?.frames?.jankRatio * 100),
|
||||
'latency p50/p95/max': s.latency ? `${format(s.latency.p50)} / ${format(s.latency.p95)} / ${format(s.latency.max)}` : '-',
|
||||
'mainLag p99/max': `${format(s.main?.eventLoopDelay?.p99)} / ${format(s.main?.eventLoopDelay?.max)}`,
|
||||
'cpu% main/renderer/gpu': `${format(s.processes?.cpu?.Browser, 0)} / ${format(s.processes?.cpu?.Tab, 0)} / ${format(s.processes?.cpu?.GPU, 0)}`,
|
||||
domNodes: s.renderer?.domNodes ?? '-',
|
||||
heapMB: format(s.renderer?.heapUsedMB)
|
||||
}))
|
||||
if (rows.length) console.table(rows)
|
||||
if (report.memoryGrowth) {
|
||||
console.log(`memory growth (${report.memoryGrowth.description}):`)
|
||||
console.table(report.memoryGrowth.samples)
|
||||
}
|
||||
if (report.environment?.rendererCpuThrottle > 1) console.log(`renderer CPU throttled ${report.environment.rendererCpuThrottle}x`)
|
||||
if (report.error) console.log(`error: ${report.error}`)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const required = ['out/main/index.js', 'out/preload/index.cjs', 'out/renderer/index.html']
|
||||
const missing = required.filter(file => !existsSync(join(root, file)))
|
||||
if (missing.length) throw new Error(`Missing production build (${missing.join(', ')}); run npm run build:bundle first`)
|
||||
const runDirectory = mkdtempSync(join(tmpdir(), 'goodbuddy-app-perf-'))
|
||||
const outputDirectory = resolve(process.env.GB_PERF_OUTPUT || join(runDirectory, 'artifacts'))
|
||||
mkdirSync(outputDirectory, { recursive: true })
|
||||
const ledger = []
|
||||
let server
|
||||
try {
|
||||
server = await startModelServer(ledger)
|
||||
} catch (error) {
|
||||
throw new Error(`Cannot listen on 127.0.0.1:${modelPort} for the fake model (${error.code ?? error.message}); stop the local service using that port, or set GB_PERF_MODEL_PORT to a free port`, { cause: error })
|
||||
}
|
||||
const environment = { ...process.env, GB_PERF_DIRECTORY: runDirectory, GB_PERF_ARTIFACTS: outputDirectory, GB_PERF_ROOT: root }
|
||||
delete environment.ELECTRON_RUN_AS_NODE
|
||||
delete environment.ELECTRON_RENDERER_URL
|
||||
for (const key of ['GOODBUDDY_MODEL_API_KEY', 'GOODBUDDY_MODEL_BASE_URL', 'GOODBUDDY_MODEL_NAME', 'GOODBUDDY_BIGTOKEN_API_KEY', 'GOODBUDDY_BIGTOKEN_BASE_URL', 'GOODBUDDY_BIGTOKEN_MODEL']) delete environment[key]
|
||||
if (modelPort !== defaultModelPort) {
|
||||
// The fake model ignores the key; it only makes the default profile use the URL.
|
||||
environment.GOODBUDDY_MODEL_API_KEY = 'perf-loopback'
|
||||
environment.GOODBUDDY_MODEL_BASE_URL = `http://127.0.0.1:${modelPort}/v1`
|
||||
environment.GOODBUDDY_MODEL_NAME = 'qwen3'
|
||||
}
|
||||
environment.GOODBUDDY_WORKSPACE = join(runDirectory, 'workspace')
|
||||
mkdirSync(environment.GOODBUDDY_WORKSPACE, { recursive: true })
|
||||
const started = Date.now()
|
||||
const child = spawn(require('electron'), [join(root, 'tests', 'support', 'app-perf-driver.mjs')], { cwd: root, env: environment, stdio: 'inherit', windowsHide: false })
|
||||
let timedOut = false
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true
|
||||
if (process.platform === 'win32') spawn('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' })
|
||||
else child.kill('SIGKILL')
|
||||
}, totalTimeoutMs)
|
||||
const exit = await new Promise(resolvePromise => child.once('exit', (code, signal) => resolvePromise({ code, signal })))
|
||||
clearTimeout(timer)
|
||||
await new Promise(resolvePromise => server.close(() => resolvePromise()))
|
||||
const resultPath = join(outputDirectory, 'result.json')
|
||||
let report
|
||||
if (existsSync(resultPath)) report = JSON.parse(readFileSync(resultPath, 'utf8'))
|
||||
else report = { status: 'failed', error: timedOut ? 'Supervisor timeout before report' : 'Driver exited without a report' }
|
||||
report.supervisor = { elapsedMs: Date.now() - started, timeoutMs: totalTimeoutMs, timedOut, exitCode: exit.code, signal: exit.signal }
|
||||
report.corpus = { kind: process.env.GB_PERF_CORPUS || 'single-line-math', bytes: streamBytes, charsPerSecond: streamCharsPerSecond, charsPerEvent: chunkChars }
|
||||
report.modelRequests = {
|
||||
boundary: 'loopback fake model only; no external requests',
|
||||
total: ledger.length,
|
||||
benchmarkStreams: ledger.filter(entry => entry.kind === 'benchmark-stream').length,
|
||||
auxiliary: ledger.filter(entry => entry.kind === 'auxiliary').length,
|
||||
ledger: ledger.map(({ method, path, kind, stream, chunks, bytes, aborted, startedAt, finishedAt }) => ({ method, path, kind, stream, chunks, bytes, aborted, durationMs: finishedAt ? finishedAt - startedAt : null }))
|
||||
}
|
||||
if (timedOut || exit.code !== 0) report.status = 'failed'
|
||||
writeFileSync(resultPath, JSON.stringify(report, null, 2))
|
||||
printSummary(report)
|
||||
console.log(`\nreport: ${resultPath}`)
|
||||
if (!keepProfile) rmSync(join(runDirectory, 'profile'), { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
||||
process.exitCode = String(report.status).startsWith('passed') ? 0 : 1
|
||||
// PERF-17: GB_PERF_CHECK=1 gates the run on build/perf-thresholds.json
|
||||
// (exit 1 on a regression, 2 when the report cannot be judged, e.g. throttled).
|
||||
if (process.env.GB_PERF_CHECK === '1') process.exitCode = require('./check-perf-thresholds.cjs').run([resultPath])
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(error instanceof Error ? error.message : error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
@@ -88,7 +88,7 @@ async function main() {
|
||||
`${JSON.stringify(
|
||||
{
|
||||
name: '@deepseek-ai/dsh-llm',
|
||||
version: '0.1.2-rc.1',
|
||||
version: '0.1.7-rc.2',
|
||||
private: true,
|
||||
type: 'module'
|
||||
},
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// Knowledge retrieval benchmark (PERF-05). Bundles the production
|
||||
// KnowledgeDatabase with esbuild, seeds synthetic libraries of increasing size
|
||||
// in a temporary SQLite file and times the synchronous search calls that the
|
||||
// Electron Main process runs inline during retrieval. Because these calls are
|
||||
// synchronous, their duration is the time Main is blocked per query.
|
||||
const { buildSync } = require('esbuild')
|
||||
const { createHash } = require('node:crypto')
|
||||
const { mkdtempSync, rmSync, writeFileSync } = require('node:fs')
|
||||
const { tmpdir } = require('node:os')
|
||||
const { join, resolve } = require('node:path')
|
||||
const { monitorEventLoopDelay, performance } = require('node:perf_hooks')
|
||||
|
||||
const root = resolve(__dirname, '..')
|
||||
|
||||
// Measure on the project's Electron (the production runtime). Older runtimes
|
||||
// (Node 24.18 / Electron 43) stall worker threads for hundreds of ms on many
|
||||
// small ArrayBuffer allocations (SQLite BLOB rows); GB_KPERF_NODE=1 opts out.
|
||||
if (process.env.GB_KPERF_NODE !== '1') {
|
||||
let electron
|
||||
let version
|
||||
try { electron = require('electron'); version = require('electron/package.json').version } catch { electron = undefined }
|
||||
if (typeof electron === 'string' && process.versions.electron !== version) {
|
||||
const { status } = require('node:child_process').spawnSync(electron, [__filename], {
|
||||
stdio: 'inherit', env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }
|
||||
})
|
||||
process.exit(status ?? 1)
|
||||
}
|
||||
}
|
||||
const sizes = (process.env.GB_KPERF_SIZES || '5000,20000,50000').split(',').map(Number)
|
||||
const dimensions = Number(process.env.GB_KPERF_DIMENSIONS || 384)
|
||||
const chunksPerDocument = 100
|
||||
const queries = Number(process.env.GB_KPERF_QUERIES || 15)
|
||||
|
||||
const directory = mkdtempSync(join(tmpdir(), 'goodbuddy-knowledge-perf-'))
|
||||
const bundle = join(directory, 'knowledge-database.cjs')
|
||||
const workerBundle = join(directory, 'readonly-query-worker.cjs')
|
||||
for (const [entry, outfile] of [
|
||||
['src/main/knowledge/knowledge-database.ts', bundle],
|
||||
['src/main/readonly-query-worker.ts', workerBundle]
|
||||
]) {
|
||||
buildSync({
|
||||
entryPoints: [join(root, entry)],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
target: 'node22',
|
||||
outfile,
|
||||
logLevel: 'error',
|
||||
// The worker reuses AssistantDatabase, which imports Electron-only modules lazily.
|
||||
external: ['electron']
|
||||
})
|
||||
}
|
||||
const { KnowledgeDatabase } = require(bundle)
|
||||
|
||||
// Deterministic pseudo-random generator so runs are comparable.
|
||||
let seed = 42
|
||||
const random = () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296)
|
||||
const words = 'lighthouse harbor policy engine renderer stream vector index budget latency schedule archive network storage contract invoice release window memory review'.split(' ')
|
||||
const sentence = n => Array.from({ length: n }, () => words[Math.floor(random() * words.length)]).join(' ')
|
||||
const vector = () => Array.from({ length: dimensions }, () => random() * 2 - 1)
|
||||
const checksum = value => createHash('sha256').update(value).digest('hex')
|
||||
|
||||
function percentile(values, p) {
|
||||
const sorted = [...values].sort((a, b) => a - b)
|
||||
return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1))]
|
||||
}
|
||||
function time(body) {
|
||||
const samples = []
|
||||
body() // warm statement cache and page cache
|
||||
for (let index = 0; index < queries; index += 1) {
|
||||
const started = performance.now()
|
||||
body()
|
||||
samples.push(performance.now() - started)
|
||||
}
|
||||
return { p50: round(percentile(samples, 50)), p95: round(percentile(samples, 95)), max: round(Math.max(...samples)) }
|
||||
}
|
||||
const round = value => Math.round(value * 10) / 10
|
||||
|
||||
// Main event-loop delay while `queries` searches run back to back. A sync
|
||||
// search blocks the loop for its whole duration; a worker search should not.
|
||||
async function loopDelay(body) {
|
||||
const histogram = monitorEventLoopDelay({ resolution: 5 })
|
||||
await body() // warm worker / caches outside the measurement
|
||||
// Let the warm-up's blocked interval drain before sampling.
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
histogram.enable()
|
||||
const started = performance.now()
|
||||
for (let index = 0; index < queries; index += 1) {
|
||||
await body()
|
||||
// Yield like a real IPC handler, so the histogram samples between queries.
|
||||
await new Promise(resolve => setImmediate(resolve))
|
||||
}
|
||||
const wallMs = performance.now() - started
|
||||
await new Promise(resolve => setTimeout(resolve, 20))
|
||||
histogram.disable()
|
||||
const ms = value => round(value / 1e6)
|
||||
return { p50: ms(histogram.percentile(50)), p99: ms(histogram.percentile(99)), max: ms(histogram.max), perQueryMs: round(wallMs / queries) }
|
||||
}
|
||||
|
||||
const results = []
|
||||
async function main() {
|
||||
console.log(`[kperf] runtime: node ${process.version}${process.versions.electron ? ` (electron ${process.versions.electron})` : ''}`)
|
||||
for (const size of sizes) {
|
||||
const path = join(directory, `knowledge-${size}.sqlite`)
|
||||
const database = new KnowledgeDatabase(path)
|
||||
database.initialize()
|
||||
const library = database.createKnowledgeBase({ name: `Perf ${size}`, storageMode: 'reference' })
|
||||
const sqlite = database.database
|
||||
const seedStarted = performance.now()
|
||||
for (let documentIndex = 0; documentIndex < size / chunksPerDocument; documentIndex += 1) {
|
||||
const source = database.upsertSource({ knowledgeBaseId: library.id, type: 'file', location: `C:\\perf\\doc-${documentIndex}.md`, displayName: `doc-${documentIndex}.md`, status: 'ready' })
|
||||
const chunks = Array.from({ length: chunksPerDocument }, (_, ordinal) => ({ id: `d${documentIndex}-c${ordinal}`, ordinal, content: sentence(80), location: `line ${ordinal + 1}` }))
|
||||
const document = database.upsertDocument({ knowledgeBaseId: library.id, sourceId: source.id, externalId: `doc-${documentIndex}`, title: `doc-${documentIndex}`, sourceLocation: source.location }, chunks)
|
||||
const stored = sqlite.prepare("SELECT id, index_content FROM chunks WHERE document_id = ? AND enabled = 1 AND role <> 'parent'").all(document.id)
|
||||
database.replaceDocumentEmbeddings(document.id, 'perf', 'perf-model', stored.map(row => ({ chunkId: row.id, contentChecksum: checksum(row.index_content), vector: vector() })))
|
||||
}
|
||||
const seedMs = performance.now() - seedStarted
|
||||
database.close()
|
||||
|
||||
// Reopen so timings start from a fresh connection, like an app restart.
|
||||
const reopened = new KnowledgeDatabase(path)
|
||||
reopened.initialize()
|
||||
const query = vector()
|
||||
const entry = {
|
||||
chunks: size,
|
||||
dimensions,
|
||||
seedSeconds: round(seedMs / 1000),
|
||||
ftsMs: time(() => reopened.search({ knowledgeBaseId: library.id, query: 'lighthouse budget', limit: 40 })),
|
||||
vectorMs: time(() => reopened.vectorSearch({ knowledgeBaseId: library.id, provider: 'perf', model: 'perf-model', vector: query, limit: 40 })),
|
||||
hybridMs: time(() => reopened.hybridSearchWithDiagnostics(hybridOptions(library.id, query)))
|
||||
}
|
||||
// Main event-loop delay: synchronous search on Main vs the readonly worker.
|
||||
entry.mainLoopDelaySyncMs = await loopDelay(async () => reopened.hybridSearchWithDiagnostics(hybridOptions(library.id, query)))
|
||||
reopened.enableReadonlyWorker(workerBundle)
|
||||
entry.mainLoopDelayWorkerMs = await loopDelay(() => reopened.hybridSearchWithDiagnosticsAsync(hybridOptions(library.id, query)))
|
||||
reopened.close()
|
||||
results.push(entry)
|
||||
console.log(`[kperf] ${size} chunks: fts p95 ${entry.ftsMs.p95} ms, vector p95 ${entry.vectorMs.p95} ms, hybrid p95 ${entry.hybridMs.p95} ms (seed ${entry.seedSeconds} s)`)
|
||||
console.log(`[kperf] ${size} chunks: Main loop delay max sync ${entry.mainLoopDelaySyncMs.max} ms vs worker ${entry.mainLoopDelayWorkerMs.max} ms (p99 ${entry.mainLoopDelaySyncMs.p99} / ${entry.mainLoopDelayWorkerMs.p99} ms; per query ${entry.mainLoopDelaySyncMs.perQueryMs} / ${entry.mainLoopDelayWorkerMs.perQueryMs} ms)`)
|
||||
}
|
||||
const output = process.env.GB_KPERF_OUTPUT
|
||||
if (output) writeFileSync(output, JSON.stringify({ kind: 'goodbuddy-knowledge-retrieval-benchmark', node: process.version, results }, null, 2))
|
||||
console.table(results.map(r => ({ chunks: r.chunks, 'fts p50/p95': `${r.ftsMs.p50} / ${r.ftsMs.p95}`, 'vector p50/p95': `${r.vectorMs.p50} / ${r.vectorMs.p95}`, 'hybrid p50/p95': `${r.hybridMs.p50} / ${r.hybridMs.p95}`, 'loop max sync/worker': `${r.mainLoopDelaySyncMs.max} / ${r.mainLoopDelayWorkerMs.max}` })))
|
||||
}
|
||||
const hybridOptions = (knowledgeBaseId, vector) => ({ knowledgeBaseId, query: 'lighthouse budget', limit: 40, provider: 'perf', model: 'perf-model', vector, graphEnabled: false, candidateMultiplier: 1 })
|
||||
main().catch(error => { process.exitCode = 1; console.error(error) }).finally(() => {
|
||||
rmSync(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
||||
})
|
||||
@@ -0,0 +1,251 @@
|
||||
// Main-process database benchmark (PERF-15). Bundles the production
|
||||
// AssistantDatabase and readonly worker with esbuild, seeds a temporary
|
||||
// assistant database (default 1,000 conversations / 20,000 messages and 5,000
|
||||
// activity records) and measures, for the IPC paths the renderer hits often:
|
||||
// - per-call time
|
||||
// - Main event-loop delay while the calls run back to back
|
||||
// for the previous synchronous path ("sync") and the current path ("after").
|
||||
// conversations:list-summaries sync listConversationSummaries vs worker
|
||||
// conversations:get sync getConversation vs worker
|
||||
// activity-history:replace legacy replace (zod parse + BEGIN IMMEDIATE +
|
||||
// JSON.stringify of every record) vs current
|
||||
// activity-history:update one changed record via the incremental API
|
||||
// Env: GB_MPERF_CONVERSATIONS, GB_MPERF_MESSAGES, GB_MPERF_ACTIVITY,
|
||||
// GB_MPERF_CALLS, GB_MPERF_OUTPUT (JSON), GB_MPERF_NODE=1 (skip Electron).
|
||||
const { buildSync } = require('esbuild')
|
||||
const { mkdtempSync, rmSync, writeFileSync, mkdirSync } = require('node:fs')
|
||||
const { tmpdir } = require('node:os')
|
||||
const { join, resolve } = require('node:path')
|
||||
const { randomUUID } = require('node:crypto')
|
||||
const { monitorEventLoopDelay, performance } = require('node:perf_hooks')
|
||||
|
||||
const root = resolve(__dirname, '..')
|
||||
|
||||
if (process.env.GB_MPERF_NODE !== '1') {
|
||||
let electron
|
||||
let version
|
||||
try { electron = require('electron'); version = require('electron/package.json').version } catch { electron = undefined }
|
||||
if (typeof electron === 'string' && process.versions.electron !== version) {
|
||||
const { status } = require('node:child_process').spawnSync(electron, [__filename], {
|
||||
stdio: 'inherit', env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }
|
||||
})
|
||||
process.exit(status ?? 1)
|
||||
}
|
||||
}
|
||||
|
||||
const conversationCount = Number(process.env.GB_MPERF_CONVERSATIONS || 1000)
|
||||
const messageCount = Number(process.env.GB_MPERF_MESSAGES || 20000)
|
||||
const activityCount = Number(process.env.GB_MPERF_ACTIVITY || 5000)
|
||||
const calls = Number(process.env.GB_MPERF_CALLS || 15)
|
||||
|
||||
const directory = mkdtempSync(join(tmpdir(), 'goodbuddy-main-perf-'))
|
||||
const bundle = join(directory, 'assistant-database.cjs')
|
||||
const workerBundle = join(directory, 'readonly-query-worker.cjs')
|
||||
const schemaBundle = join(directory, 'assistant-contracts.cjs')
|
||||
for (const [entry, outfile] of [
|
||||
['src/main/assistant/assistant-database.ts', bundle],
|
||||
['src/main/readonly-query-worker.ts', workerBundle],
|
||||
['src/shared/assistant-contracts.ts', schemaBundle]
|
||||
]) {
|
||||
buildSync({
|
||||
entryPoints: [join(root, entry)], bundle: true, platform: 'node', format: 'cjs',
|
||||
target: 'node22', outfile, logLevel: 'error', external: ['electron']
|
||||
})
|
||||
}
|
||||
const { AssistantDatabase } = require(bundle)
|
||||
const { activityHistorySnapshotSchema } = require(schemaBundle)
|
||||
|
||||
let seed = 42
|
||||
const random = () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296)
|
||||
const words = 'lighthouse harbor policy engine renderer stream vector index budget latency schedule archive network storage contract invoice release window memory review 发布 审批 流程'.split(' ')
|
||||
const sentence = n => Array.from({ length: n }, () => words[Math.floor(random() * words.length)]).join(' ')
|
||||
const round = value => Math.round(value * 10) / 10
|
||||
const percentile = (values, p) => {
|
||||
const sorted = [...values].sort((a, b) => a - b)
|
||||
return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1))]
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `body` `calls` times (after one warm-up) and reports the per-call wall
|
||||
* time and the Main event-loop delay histogram (p99/max) over the run.
|
||||
*/
|
||||
async function measure(body) {
|
||||
await body(-1)
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
// monitorEventLoopDelay is bound to the ~15 ms Windows timer granularity, so
|
||||
// the longest block is measured as the largest gap between setImmediate
|
||||
// ticks (each tick is one event-loop turn).
|
||||
const histogram = monitorEventLoopDelay({ resolution: 1 })
|
||||
histogram.enable()
|
||||
let probing = true
|
||||
let last = performance.now()
|
||||
let maxGap = 0
|
||||
const tick = () => {
|
||||
const now = performance.now()
|
||||
maxGap = Math.max(maxGap, now - last)
|
||||
last = now
|
||||
if (probing) setImmediate(tick)
|
||||
}
|
||||
setImmediate(tick)
|
||||
const samples = []
|
||||
for (let index = 0; index < calls; index += 1) {
|
||||
const started = performance.now()
|
||||
await body(index)
|
||||
samples.push(performance.now() - started)
|
||||
// Yield like the IPC loop does between messages.
|
||||
await new Promise(resolve => setImmediate(resolve))
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 20))
|
||||
probing = false
|
||||
histogram.disable()
|
||||
return {
|
||||
callP50: round(percentile(samples, 50)), callP95: round(percentile(samples, 95)),
|
||||
loopP99: round(histogram.percentile(99) / 1e6), loopMax: round(maxGap)
|
||||
}
|
||||
}
|
||||
|
||||
/** The pre-PERF-15 replace implementation, kept as the "before" reference. */
|
||||
function legacyReplace(raw, cache, input) {
|
||||
const snapshot = activityHistorySnapshotSchema.parse(input)
|
||||
raw.exec('BEGIN IMMEDIATE')
|
||||
try {
|
||||
const { data_version: dataVersion } = raw.prepare('PRAGMA data_version').get()
|
||||
let previous = cache.value?.dataVersion === dataVersion ? cache.value.records : undefined
|
||||
if (!previous) {
|
||||
previous = new Map(raw.prepare('SELECT record_key, record_json FROM activity_history_records').all()
|
||||
.map(row => [row.record_key, row.record_json]))
|
||||
}
|
||||
const records = new Map()
|
||||
const occurrences = new Map()
|
||||
const order = []
|
||||
const upsert = raw.prepare(`INSERT INTO activity_history_records (record_key, record_json) VALUES (?, ?)
|
||||
ON CONFLICT(record_key) DO UPDATE SET record_json = excluded.record_json`)
|
||||
for (const record of snapshot.records) {
|
||||
const occurrence = occurrences.get(record.id) ?? 0
|
||||
occurrences.set(record.id, occurrence + 1)
|
||||
const key = JSON.stringify([record.id, occurrence])
|
||||
const json = JSON.stringify(record)
|
||||
order.push(key)
|
||||
if (previous.get(key) !== json) upsert.run(key, json)
|
||||
records.set(key, json)
|
||||
}
|
||||
const remove = raw.prepare('DELETE FROM activity_history_records WHERE record_key = ?')
|
||||
for (const key of previous.keys()) if (!records.has(key)) remove.run(key)
|
||||
const orderJson = JSON.stringify(order)
|
||||
const incomplete = Number(snapshot.legacyHistoryMayBeIncomplete)
|
||||
raw.prepare(`UPDATE activity_history SET record_order_json = ?, legacy_history_may_be_incomplete = ?
|
||||
WHERE singleton = 1 AND (record_order_json != ? OR legacy_history_may_be_incomplete != ?)`)
|
||||
.run(orderJson, incomplete, orderJson, incomplete)
|
||||
raw.exec('COMMIT')
|
||||
cache.value = { records, dataVersion }
|
||||
} catch (error) {
|
||||
raw.exec('ROLLBACK')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`[mperf] runtime: node ${process.version}${process.versions.electron ? ` (electron ${process.versions.electron})` : ''}`)
|
||||
const workspace = join(directory, 'workspace')
|
||||
mkdirSync(workspace)
|
||||
const path = join(directory, 'assistant.sqlite')
|
||||
const seeding = new AssistantDatabase(path)
|
||||
seeding.initialize(workspace)
|
||||
const seedStarted = performance.now()
|
||||
const projectId = seeding.listProjects()[0].id
|
||||
// Message counts follow a skewed distribution; the first conversation is the largest.
|
||||
const weights = Array.from({ length: conversationCount }, (_, index) => 1 / (1 + index * 0.01))
|
||||
const weightSum = weights.reduce((sum, value) => sum + value, 0)
|
||||
const counts = weights.map(weight => Math.max(1, Math.round(messageCount * weight / weightSum)))
|
||||
const ids = counts.map(() => randomUUID())
|
||||
for (let start = 0; start < conversationCount; start += 50) {
|
||||
seeding.saveLocalConversations(ids.slice(start, start + 50).map((id, offset) => {
|
||||
const index = start + offset
|
||||
return {
|
||||
header: { id, projectId, title: `Conversation ${index} ${sentence(3)}`, updatedAt: 1_700_000_000_000 + index },
|
||||
messages: Array.from({ length: counts[index] }, (_, at) => ({
|
||||
id: randomUUID(), role: at % 2 === 0 ? 'user' : 'assistant', state: 'complete',
|
||||
content: sentence(at % 2 === 0 ? 30 : 120), createdAt: 1_700_000_000_000 + index * 1000 + at
|
||||
}))
|
||||
}
|
||||
}))
|
||||
}
|
||||
const records = Array.from({ length: activityCount }, (_, index) => ({
|
||||
id: randomUUID(), conversationId: ids[index % conversationCount], requestId: randomUUID(),
|
||||
...(index % 2 === 0 ? { callId: `call-${index}` } : {}),
|
||||
scope: index % 3 === 0 ? { kind: 'project', projectId, projectName: 'Perf' } : { kind: 'global' },
|
||||
kind: index % 5 === 0 ? 'request' : 'tool', title: `Tool ${index}`, detail: sentence(40),
|
||||
status: 'completed', createdAt: 1_700_000_000_000 + index
|
||||
}))
|
||||
seeding.replaceActivityHistory({ records, legacyHistoryMayBeIncomplete: false })
|
||||
const seedSeconds = round((performance.now() - seedStarted) / 1000)
|
||||
seeding.close()
|
||||
|
||||
const database = new AssistantDatabase(path)
|
||||
database.initialize(workspace)
|
||||
const raw = database.database
|
||||
const totalMessages = counts.reduce((sum, value) => sum + value, 0)
|
||||
const largest = ids[0]
|
||||
const typical = ids[Math.floor(conversationCount / 2)]
|
||||
// The renderer retains the current conversation's history.
|
||||
const detailIds = [ids[1]]
|
||||
const results = { conversations: conversationCount, messages: totalMessages, activityRecords: activityCount,
|
||||
largestConversationMessages: counts[0], seedSeconds }
|
||||
|
||||
results.listSummariesSync = await measure(async () => database.listConversationSummaries(detailIds))
|
||||
results.getLargestSync = await measure(async () => database.getConversation(largest))
|
||||
results.getTypicalSync = await measure(async () => database.getConversation(typical))
|
||||
database.enableReadonlyWorker(workerBundle)
|
||||
results.listSummariesWorker = await measure(() => database.listConversationSummariesAsync(detailIds))
|
||||
results.getLargestWorker = await measure(() => database.getConversationAsync(largest))
|
||||
results.getTypicalWorker = await measure(() => database.getConversationAsync(typical))
|
||||
|
||||
// The renderer sends a fresh structured clone of the whole list every save.
|
||||
const unchanged = () => structuredClone({ records, legacyHistoryMayBeIncomplete: false })
|
||||
const oneChanged = index => {
|
||||
const snapshot = unchanged()
|
||||
const record = snapshot.records[(index + 2) % 50]
|
||||
record.detail = `${record.detail} ${index}`
|
||||
return snapshot
|
||||
}
|
||||
// Inputs are cloned up front: cloning happens in the renderer / IPC layer.
|
||||
const run = async (makeInput, call) => {
|
||||
const inputs = Array.from({ length: calls + 1 }, (_, index) => makeInput(index - 1))
|
||||
return measure(async index => call(inputs[index + 1]))
|
||||
}
|
||||
const legacyCache = {}
|
||||
results.replaceUnchangedLegacy = await run(unchanged, input => legacyReplace(raw, legacyCache, input))
|
||||
results.replaceOneChangedLegacy = await run(oneChanged, input => legacyReplace(raw, legacyCache, input))
|
||||
// Restore a consistent state for the current implementation's cache.
|
||||
database.replaceActivityHistory({ records, legacyHistoryMayBeIncomplete: false })
|
||||
results.replaceUnchangedAfter = await run(unchanged, input => database.replaceActivityHistory(input))
|
||||
results.replaceOneChangedAfter = await run(oneChanged, input => database.replaceActivityHistory(input))
|
||||
results.updateOneRecord = await run(index => {
|
||||
const record = { ...records[(index + 2) % 50], detail: `incremental ${index}`, status: 'running' }
|
||||
return { changes: [{ type: 'upsert', position: 'front', record }] }
|
||||
}, input => database.updateActivityHistory(input))
|
||||
database.close()
|
||||
|
||||
console.log(`[mperf] seeded ${results.conversations} conversations / ${results.messages} messages (largest ${results.largestConversationMessages}), ${results.activityRecords} activity records in ${seedSeconds} s`)
|
||||
const rows = [
|
||||
['conversations:list-summaries', results.listSummariesSync, results.listSummariesWorker],
|
||||
[`conversations:get (${counts[0]} msgs)`, results.getLargestSync, results.getLargestWorker],
|
||||
[`conversations:get (${counts[Math.floor(conversationCount / 2)]} msgs)`, results.getTypicalSync, results.getTypicalWorker],
|
||||
['activity replace, unchanged', results.replaceUnchangedLegacy, results.replaceUnchangedAfter],
|
||||
['activity replace, 1 changed', results.replaceOneChangedLegacy, results.replaceOneChangedAfter],
|
||||
['activity update, 1 record', results.replaceOneChangedLegacy, results.updateOneRecord]
|
||||
]
|
||||
console.table(rows.map(([name, before, after]) => ({
|
||||
path: name,
|
||||
'before call p50/p95 ms': `${before.callP50} / ${before.callP95}`,
|
||||
'before loop max ms': before.loopMax,
|
||||
'after call p50/p95 ms': `${after.callP50} / ${after.callP95}`,
|
||||
'after loop max ms': after.loopMax
|
||||
})))
|
||||
const output = process.env.GB_MPERF_OUTPUT
|
||||
if (output) writeFileSync(output, JSON.stringify({ kind: 'goodbuddy-main-database-benchmark', node: process.version, results }, null, 2))
|
||||
}
|
||||
|
||||
main().catch(error => { process.exitCode = 1; console.error(error) }).finally(() => {
|
||||
rmSync(directory, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
||||
})
|
||||
@@ -0,0 +1,191 @@
|
||||
const { dirname, join, resolve } = require('node:path')
|
||||
const { copyFileSync, mkdtempSync, mkdirSync, rmSync } = require('node:fs')
|
||||
const { tmpdir } = require('node:os')
|
||||
const { pathToFileURL } = require('node:url')
|
||||
const assert = require('node:assert/strict')
|
||||
|
||||
if (!process.versions.electron || process.env.ELECTRON_RUN_AS_NODE === '1') {
|
||||
const { spawnSync } = require('node:child_process')
|
||||
const root = mkdtempSync(join(tmpdir(), 'goodbuddy-obsidian-ui-'))
|
||||
const env = { ...process.env, GOODBUDDY_OBSIDIAN_ACCEPTANCE_ROOT: root }
|
||||
if (process.argv.includes('--live') || process.argv.includes('--chat')) {
|
||||
env.GOODBUDDY_OBSIDIAN_ACCEPTANCE_LIVE = process.argv.includes('--live') ? '1' : '0'
|
||||
env.GOODBUDDY_OBSIDIAN_ACCEPTANCE_CHAT = process.argv.includes('--chat') ? '1' : '0'
|
||||
env.GOODBUDDY_OBSIDIAN_ACCEPTANCE_CONTINUE_ONLY = process.argv.includes('--continue-only') ? '1' : '0'
|
||||
require('esbuild').buildSync({ entryPoints: ['tests/obsidian-runtime-acceptance.ts'],
|
||||
outfile: join(root, 'runtime.cjs'), bundle: true, external: ['electron', 'ssh2', 'node-pty', 'koffi'], platform: 'node', format: 'cjs',
|
||||
banner: { js: `require = require('node:module').createRequire(${JSON.stringify(resolve('package.json'))});` } })
|
||||
}
|
||||
delete env.ELECTRON_RUN_AS_NODE
|
||||
try {
|
||||
const result = spawnSync(require('electron'), [__filename], {
|
||||
env, stdio: 'inherit', timeout: 300_000
|
||||
})
|
||||
if (result.error) throw result.error
|
||||
process.exitCode = result.status ?? 1
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 })
|
||||
}
|
||||
} else {
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const root = process.env.GOODBUDDY_OBSIDIAN_ACCEPTANCE_ROOT
|
||||
assert(root)
|
||||
const profile = join(root, 'profile')
|
||||
mkdirSync(profile)
|
||||
if (process.env.GOODBUDDY_OBSIDIAN_ACCEPTANCE_LIVE === '1' || process.env.GOODBUDDY_OBSIDIAN_ACCEPTANCE_CHAT === '1') {
|
||||
const settings = process.env.GOODBUDDY_ACCEPTANCE_SETTINGS ?? join(app.getPath('appData'), 'goodbuddy', 'runtime-settings.json')
|
||||
copyFileSync(join(dirname(settings), 'Local State'), join(profile, 'Local State'))
|
||||
}
|
||||
mkdirSync(join(root, 'Temporary Vault'))
|
||||
app.setPath('userData', profile)
|
||||
app.setAppPath(resolve('.'))
|
||||
const timer = setTimeout(() => { console.error('Acceptance timed out'); app.exit(1) }, 280_000)
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||
;(async () => {
|
||||
const chat = process.env.GOODBUDDY_OBSIDIAN_ACCEPTANCE_CHAT === '1'
|
||||
? await require(join(root, 'runtime.cjs')).prepareChat(root, profile) : undefined
|
||||
await import(pathToFileURL(resolve('out/main/index.js')).href)
|
||||
let window
|
||||
for (let attempt = 0; attempt < 120; attempt++) {
|
||||
window = BrowserWindow.getAllWindows().find(item => item.webContents.getURL().includes('index.html'))
|
||||
if (window && await window.webContents.executeJavaScript('Boolean(document.querySelector("button"))')) break
|
||||
await wait(250)
|
||||
}
|
||||
assert(window, 'Production main must open its BrowserWindow')
|
||||
assert.equal(app.getPath('userData'), profile)
|
||||
const preferences = window.webContents.getLastWebPreferences()
|
||||
assert.equal(preferences.contextIsolation, true)
|
||||
assert.equal(preferences.nodeIntegration, false)
|
||||
const js = code => window.webContents.executeJavaScript(code).catch(error => { throw new Error(`${code}: ${error.message}`) })
|
||||
const click = async expression => {
|
||||
window.focus()
|
||||
for (let i=0;i<100;i++) { if (await js(`Boolean(${expression})`)) break; await wait(100) }
|
||||
const point = await js(`(() => { const element = ${expression}; if (!element) throw new Error('Missing control'); element.scrollIntoView({block:'center'}); const r=element.getBoundingClientRect(); return {x:Math.round(r.x+r.width/2),y:Math.round(r.y+r.height/2)} })()`)
|
||||
window.webContents.sendInputEvent({ type: 'mouseDown', button: 'left', clickCount: 1, ...point })
|
||||
window.webContents.sendInputEvent({ type: 'mouseUp', button: 'left', clickCount: 1, ...point })
|
||||
await wait(500)
|
||||
}
|
||||
if (chat) {
|
||||
await js(`(async()=>{const p=(await window.goodbuddy.projects.list())[0];await window.goodbuddy.projects.update(p.id,{name:p.name,description:p.description,rootPath:${JSON.stringify(join(root,'chat-workspace'))},defaultWorkMode:'execute',runtimeSelection:{provider:'opencode'}})})()`)
|
||||
window.webContents.reload()
|
||||
await wait(1000)
|
||||
}
|
||||
await click(`document.querySelector('button[aria-label="\u8bbe\u7f6e"]')`)
|
||||
await click(`Array.from(document.querySelectorAll('button')).find(b=>b.innerText.startsWith('\u80fd\u529b\u4e0e\u5de5\u5177'))`)
|
||||
await click(`Array.from(document.querySelectorAll('[role="tab"]')).find(b=>b.innerText==='MCP' && b.getClientRects().length)`)
|
||||
const card = `Array.from(document.querySelectorAll('.capability-card')).find(c=>c.querySelector('strong')?.textContent==='Obsidian')`
|
||||
const poll = async (expression, attempts=100) => {
|
||||
for (let i=0;i<attempts;i++) { if (await js(expression)) return; await wait(200) }
|
||||
console.error('VISIBLE_TABS',await js(`JSON.stringify(Array.from(document.querySelectorAll('[role="tab"]')).map(e=>({text:e.innerText,selected:e.getAttribute('aria-selected')})))`))
|
||||
throw new Error(`Timed out: ${expression}`)
|
||||
}
|
||||
await poll(`Boolean(${card})`)
|
||||
assert.equal(await js(`(${card}).querySelector('[role="switch"]').checked`), false)
|
||||
await click(`(${card}).querySelector('select')`)
|
||||
window.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'End' })
|
||||
window.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Return' })
|
||||
window.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'Return' })
|
||||
await poll(`Boolean((${card}).querySelector('input:not([type="checkbox"])'))`)
|
||||
const input = `(${card}).querySelector('input:not([type="checkbox"])')`
|
||||
const vault = join(root, 'Temporary Vault')
|
||||
await click(input)
|
||||
await window.webContents.insertText(vault)
|
||||
await click(`(${card}).querySelectorAll('.capability-card__actions button')[1]`)
|
||||
await poll(`Boolean((${card}).querySelector('[role="status"]'))`)
|
||||
assert.match(await js(`(${card}).querySelector('[role="status"]').textContent`), /18/)
|
||||
assert.equal((await js('window.goodbuddy.capabilities.getSnapshot()')).obsidian.vaultPath, '')
|
||||
assert.equal(await js(`(${card}).querySelector('[role="switch"]').checked`), false)
|
||||
await click(`(${card}).querySelectorAll('.capability-card__actions button')[2]`)
|
||||
await poll(`!(${card}).querySelector('.primary-button').disabled`)
|
||||
assert.equal((await js('window.goodbuddy.capabilities.getSnapshot()')).obsidian.vaultPath, vault)
|
||||
await click(input)
|
||||
window.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A', modifiers: ['control'] })
|
||||
window.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'A', modifiers: ['control'] })
|
||||
await window.webContents.insertText(join(root, 'missing-vault'))
|
||||
await click(`(${card}).querySelectorAll('.capability-card__actions button')[1]`)
|
||||
await poll(`Boolean((${card}).querySelector('[role="alert"]'))`)
|
||||
assert.equal(await js(`(${input}).value`), join(root, 'missing-vault'))
|
||||
assert.equal((await js('window.goodbuddy.capabilities.getSnapshot()')).obsidian.vaultPath, vault)
|
||||
console.log('PASS: native UI draft test (18 tools), no implicit save/enable, save through IPC, missing-folder error preserves draft')
|
||||
if (process.platform === 'win32') {
|
||||
await click(`(${card}).querySelectorAll('.capability-card__actions button')[0]`)
|
||||
const user32 = require('koffi').load('user32.dll')
|
||||
const getWindow = user32.func('uintptr_t __stdcall GetWindow(uintptr_t hwnd, uint32_t command)')
|
||||
const getPid = user32.func('uint32_t __stdcall GetWindowThreadProcessId(uintptr_t hwnd, _Out_ uint32_t *pid)')
|
||||
const postMessage = user32.func('bool __stdcall PostMessageW(uintptr_t hwnd, uint32_t message, uintptr_t wparam, intptr_t lparam)')
|
||||
const handle = Number(window.getNativeWindowHandle().readBigUInt64LE())
|
||||
const popup = getWindow(handle, 6)
|
||||
assert(popup && Number(popup) !== handle, 'Native folder dialog must open')
|
||||
const pid = [0]
|
||||
getPid(popup, pid)
|
||||
assert.equal(pid[0], process.pid, 'Only close this acceptance process dialog')
|
||||
assert(postMessage(popup, 0x10, 0, 0))
|
||||
await poll(`!(${card}).querySelector('.primary-button').disabled`)
|
||||
assert.equal(await js(`(${input}).value`), join(root, 'missing-vault'))
|
||||
console.log('PASS: real Windows folder dialog opened and cancelled without changing draft')
|
||||
}
|
||||
window.setMinimumSize(640, 480)
|
||||
window.setSize(760, 720)
|
||||
await wait(500)
|
||||
const geometry = await js(`(() => {const c=${card};const r=c.getBoundingClientRect();return {width:innerWidth,left:r.left,right:r.right,scroll:c.scrollWidth,client:c.clientWidth}})()`)
|
||||
assert(geometry.scroll <= geometry.client + 1 && geometry.right <= geometry.width && geometry.left >= 0, JSON.stringify(geometry))
|
||||
console.log('PASS: narrow card has no horizontal overflow', JSON.stringify(geometry))
|
||||
await click(input)
|
||||
window.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A', modifiers: ['control'] })
|
||||
window.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'A', modifiers: ['control'] })
|
||||
await window.webContents.insertText(vault)
|
||||
await click(`(${card}).querySelectorAll('.capability-card__actions button')[1]`)
|
||||
await poll(`Boolean((${card}).querySelector('[role="status"]'))`)
|
||||
await js(`(${card}).querySelector('[role="switch"]').focus()`)
|
||||
window.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Space' })
|
||||
window.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'Space' })
|
||||
await poll(`(${card}).querySelector('[role="switch"]').checked && !(${card}).querySelector('[role="switch"]').disabled`)
|
||||
await click(`(${card}).querySelector('button[aria-expanded]')`)
|
||||
await poll(`(${card}).querySelectorAll('.mcp-server-tools li').length === 19`)
|
||||
console.log('PASS: keyboard switch enables Obsidian; expanded production UI lists 19 tools')
|
||||
for (const theme of ['light', 'dark']) {
|
||||
await js(`document.documentElement.dataset.theme=${JSON.stringify(theme)}`)
|
||||
const bounds = await js(`(()=>{const c=${card};const r=c.getBoundingClientRect();return {right:r.right,width:innerWidth,scroll:c.scrollWidth,client:c.clientWidth,background:getComputedStyle(c).backgroundColor}})()`)
|
||||
assert(bounds.right <= bounds.width && bounds.scroll <= bounds.client + 1)
|
||||
console.log(`PASS: ${theme} narrow Obsidian bounds`, JSON.stringify(bounds))
|
||||
}
|
||||
if (process.env.GOODBUDDY_OBSIDIAN_ACCEPTANCE_LIVE === '1') {
|
||||
await require(join(root, 'runtime.cjs')).run(root, await js('window.goodbuddy.capabilities.getSnapshot()'))
|
||||
}
|
||||
if (chat) {
|
||||
await click(`document.querySelector('.settings-panel__header button')`)
|
||||
window.setSize(1280, 850)
|
||||
try {
|
||||
const providers = process.env.GOODBUDDY_OBSIDIAN_ACCEPTANCE_CONTINUE_ONLY === '1' ? ['Continue'] : ['OpenCode', 'Continue']
|
||||
for (const provider of providers) {
|
||||
if (provider === 'Continue') {
|
||||
await poll(`!document.querySelector('button[title^="Runtime \u548c\u6a21\u578b"]').disabled`)
|
||||
await js(`document.querySelector('button[title^="Runtime \u548c\u6a21\u578b"]').click()`)
|
||||
await click(`Array.from(document.querySelectorAll('.runtime-picker__menu strong')).find(b=>b.innerText==='Continue Runtime')?.nextElementSibling`)
|
||||
}
|
||||
await poll(`!document.querySelector('button[title^="Runtime \u548c\u6a21\u578b"]').disabled`)
|
||||
const mode = `document.querySelector('button[aria-label^="\u5de5\u4f5c\u6a21\u5f0f"]')`
|
||||
if (!(await js(`(${mode}).innerText.includes('Execute')`))) {
|
||||
await click(mode)
|
||||
await click(`Array.from(document.querySelectorAll('[role="menuitemradio"]')).find(b=>b.innerText.startsWith('Execute'))`)
|
||||
}
|
||||
const marker = `CHAT_${provider}_${require('node:crypto').randomUUID()}`
|
||||
const before = chat.observations.length
|
||||
await click(`document.querySelector('textarea')`)
|
||||
await window.webContents.insertText(`Use only Obsidian tools. Write ${provider}-chat.md containing ${marker} using obsidian_write_note, then read it using obsidian_read_note. Reply with the content read. Do not use other tools.`)
|
||||
await click(`document.querySelector('button[aria-label="\u53d1\u9001"]')`)
|
||||
await poll(`Array.from(document.querySelectorAll('.message--assistant')).some(e=>e.innerText.includes(${JSON.stringify(marker)}))`,450)
|
||||
await poll(`!document.querySelector('button[title^="Runtime \u548c\u6a21\u578b"]').disabled`,450)
|
||||
assert((require('node:fs').readFileSync(join(vault,`${provider}-chat.md`),'utf8')).includes(marker))
|
||||
console.log(JSON.stringify({ provider, fullChatUi: 'passed', realModelHttpRequests: chat.observations.length-before }))
|
||||
}
|
||||
} finally {
|
||||
console.log(JSON.stringify({ chatModelHttpRequests: chat.observations.length }))
|
||||
await chat.close()
|
||||
}
|
||||
}
|
||||
await wait(1000)
|
||||
clearTimeout(timer)
|
||||
app.exit(0)
|
||||
})().catch(error => { console.error(error); clearTimeout(timer); app.exit(1) })
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const { spawn } = require('node:child_process')
|
||||
const { mkdtemp, mkdir, readFile, readdir, writeFile, rm, stat } = require('node:fs/promises')
|
||||
const { tmpdir } = require('node:os')
|
||||
const { join, resolve } = require('node:path')
|
||||
|
||||
async function main() {
|
||||
const executable = resolve(process.argv[2])
|
||||
await stat(executable)
|
||||
const root = await mkdtemp(join(tmpdir(), 'goodbuddy-obsidian-packaged-'))
|
||||
const profile = join(root, 'profile')
|
||||
const vault = join(root, 'Packaged Vault')
|
||||
await mkdir(profile)
|
||||
await mkdir(vault)
|
||||
const audit = join(root, 'node-audit.jsonl')
|
||||
const guard = join(root, 'offline.cjs')
|
||||
await writeFile(guard, `
|
||||
const fs = require('node:fs');
|
||||
const net = require('node:net');
|
||||
const original = net.Socket.prototype.connect;
|
||||
net.Socket.prototype.connect = function(...args) {
|
||||
const options = net._normalizeArgs(args)[0];
|
||||
const host = options.host || 'localhost';
|
||||
if (!options.path && !['localhost','127.0.0.1','::1'].includes(host)) throw new Error('Offline acceptance blocked network');
|
||||
return original.apply(this,args);
|
||||
};
|
||||
globalThis.fetch = async () => { throw new Error('Offline acceptance blocked fetch'); };
|
||||
require('node:module').syncBuiltinESMExports();
|
||||
fs.appendFileSync(${JSON.stringify(audit)}, JSON.stringify({entry:process.argv[1],exe:process.execPath,electron:process.versions.electron})+'\\n');
|
||||
`)
|
||||
const env = { ...process.env, NODE_OPTIONS: `--require ${JSON.stringify(guard)}`,
|
||||
PATH: join(process.env.SystemRoot, 'System32') }
|
||||
delete env.Path
|
||||
delete env.ELECTRON_RUN_AS_NODE
|
||||
const child = spawn(executable, [`--user-data-dir=${profile}`, '--remote-debugging-port=0', '--inspect=0',
|
||||
'--proxy-server=http://127.0.0.1:9', '--proxy-bypass-list=<-loopback>'], { env, stdio: ['ignore', 'ignore', 'pipe'] })
|
||||
let diagnostic = ''
|
||||
child.stderr.on('data', chunk => { diagnostic += chunk.toString() })
|
||||
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
let socket
|
||||
let mainSocket
|
||||
try {
|
||||
let port
|
||||
for (let i=0;i<100;i++) {
|
||||
try { port = (await readFile(join(profile, 'DevToolsActivePort'), 'utf8')).split('\n')[0]; break } catch { await wait(200) }
|
||||
}
|
||||
assert(port, 'Packaged app must expose debugging only in the isolated profile')
|
||||
const mainEndpoint = diagnostic.match(/ws:\/\/127\.0\.0\.1:\d+\/[-a-f0-9]+/u)?.[0]
|
||||
assert(mainEndpoint, 'The isolated Main inspector must be available')
|
||||
mainSocket = new WebSocket(mainEndpoint)
|
||||
await new Promise((resolve, reject) => { mainSocket.onopen=resolve;mainSocket.onerror=reject })
|
||||
// Packaged Electron strips NODE_OPTIONS at startup. Restore it only for this
|
||||
// disposable Main so its real MCPVault child receives the offline guard.
|
||||
await new Promise((resolve,reject) => {
|
||||
const timer=setTimeout(()=>reject(new Error('Main inspector timed out')),10_000)
|
||||
mainSocket.onmessage=message=>{const value=JSON.parse(message.data);if(value.id===1){clearTimeout(timer);if(value.error || value.result?.exceptionDetails)reject(new Error('Unable to set child network guard'));else resolve()}}
|
||||
mainSocket.send(JSON.stringify({id:1,method:'Runtime.evaluate',params:{expression:`process.env.NODE_OPTIONS=${JSON.stringify(env.NODE_OPTIONS)}`}}))
|
||||
})
|
||||
mainSocket.close()
|
||||
let target
|
||||
for (let i=0;i<100;i++) {
|
||||
target = (await (await fetch(`http://127.0.0.1:${port}/json/list`)).json()).find(item => item.type === 'page' && item.url.includes('index.html'))
|
||||
if (target) break
|
||||
await wait(200)
|
||||
}
|
||||
assert(target, 'Packaged production renderer must load')
|
||||
socket = new WebSocket(target.webSocketDebuggerUrl)
|
||||
await new Promise((resolve, reject) => { socket.onopen = resolve; socket.onerror = reject })
|
||||
let id = 0
|
||||
const pending = new Map()
|
||||
socket.onmessage = message => { const value = JSON.parse(message.data); if (pending.has(value.id)) { const {resolve,reject}=pending.get(value.id); pending.delete(value.id); if (value.error) reject(new Error(value.error.message)); else resolve(value.result) } }
|
||||
const send = (method, params={}) => new Promise((resolve,reject) => {
|
||||
const key=++id
|
||||
const timer=setTimeout(()=>{pending.delete(key);reject(new Error(`CDP timed out: ${method}`))},10_000)
|
||||
pending.set(key,{
|
||||
resolve: value=>{clearTimeout(timer);resolve(value)},
|
||||
reject: error=>{clearTimeout(timer);reject(error)}
|
||||
})
|
||||
socket.send(JSON.stringify({id:key,method,params}))
|
||||
})
|
||||
const js = async expression => { const result=await send('Runtime.evaluate',{expression,awaitPromise:true,returnByValue:true});assert(!result.exceptionDetails,JSON.stringify(result.exceptionDetails));return result.result.value }
|
||||
const poll = async expression => { for(let i=0;i<100;i++){ if(await js(expression))return;await wait(200) } throw new Error(`Timeout: ${expression}`) }
|
||||
const click = async expression => {
|
||||
await js(`(()=>{const e=${expression};if(!e)throw new Error('Missing control');e.scrollIntoView({block:'center'});e.focus();e.click()})()`)
|
||||
await wait(500)
|
||||
}
|
||||
await poll('Boolean(window.goodbuddy && document.querySelector("button"))')
|
||||
await stat(join(profile,'assistant.sqlite'))
|
||||
assert.equal(await js('typeof require'), 'undefined')
|
||||
await click(`document.querySelector('button[aria-label="\u8bbe\u7f6e"]')`)
|
||||
await poll(`Array.from(document.querySelectorAll('button')).some(b=>b.innerText.startsWith('\u80fd\u529b\u4e0e\u5de5\u5177'))`)
|
||||
await click(`Array.from(document.querySelectorAll('button')).find(b=>b.innerText.startsWith('\u80fd\u529b\u4e0e\u5de5\u5177'))`)
|
||||
await click(`Array.from(document.querySelectorAll('[role="tab"]')).find(b=>b.innerText==='MCP' && b.getClientRects().length)`)
|
||||
const card=`Array.from(document.querySelectorAll('.capability-card')).find(c=>c.querySelector('strong')?.textContent==='Obsidian')`
|
||||
await poll(`Boolean(${card})`)
|
||||
await js(`{const e=(${card}).querySelector('select');e.value='folder';e.dispatchEvent(new Event('change',{bubbles:true}));}`)
|
||||
await poll(`Boolean((${card}).querySelector('input:not([type="checkbox"])'))`)
|
||||
await click(`(${card}).querySelector('input:not([type="checkbox"])')`)
|
||||
await send('Input.insertText',{text:vault})
|
||||
await click(`(${card}).querySelectorAll('.capability-card__actions button')[1]`)
|
||||
await poll(`Boolean((${card}).querySelector('[role="status"]'))`)
|
||||
assert.match(await js(`(${card}).querySelector('[role="status"]').textContent`),/18/)
|
||||
await click(`(${card}).querySelectorAll('.capability-card__actions button')[2]`)
|
||||
await poll(`!(${card}).querySelector('.primary-button').disabled`)
|
||||
assert.equal((await js('window.goodbuddy.capabilities.getSnapshot()')).obsidian.vaultPath,vault)
|
||||
const bin=join(profile,'tool-environment','bin')
|
||||
const shims=(await readdir(bin,{recursive:true})).filter(name=>name.endsWith('node.cmd'))
|
||||
assert(shims.length>0)
|
||||
assert((await readFile(join(bin,shims[0]),'utf8')).includes(executable))
|
||||
console.log('PASS: packaged app.asar UI -> real IPC -> packaged MCPVault (18 tools), save, managed packaged Electron shim, PATH=System32, Chromium dead proxy')
|
||||
const records=(await readFile(audit,'utf8')).trim().split('\n').map(line=>JSON.parse(line))
|
||||
assert(records.some(item=>item.entry?.includes('app.asar.unpacked') && item.entry.endsWith('server.js') && item.exe===executable && item.electron))
|
||||
console.log('PASS: packaged MCPVault child loaded Node network guards; audited packaged executable and unpacked server entry')
|
||||
console.log('Boundary: MCPVault Node API guards + Chromium proxy; no OS firewall, physical disconnection, or installer execution')
|
||||
} finally {
|
||||
socket?.close()
|
||||
mainSocket?.close()
|
||||
if (child.exitCode === null) {
|
||||
const killer=spawn('taskkill',['/PID',String(child.pid),'/T','/F'],{stdio:'ignore'})
|
||||
await new Promise(resolve=>killer.once('exit',resolve))
|
||||
}
|
||||
await rm(root,{recursive:true,force:true,maxRetries:10,retryDelay:200})
|
||||
}
|
||||
}
|
||||
main().catch(error=>{console.error(error);process.exitCode=1})
|
||||
@@ -148,7 +148,7 @@ async function main() {
|
||||
[
|
||||
'appId: live.digiman.goodbuddy.harness-smoke',
|
||||
'productName: GoodBuddyHarnessSmoke',
|
||||
'electronVersion: "43.2.0"',
|
||||
`electronVersion: "${require('electron/package.json').version}"`,
|
||||
'asar: true',
|
||||
'npmRebuild: false',
|
||||
'files:',
|
||||
|
||||
@@ -18,6 +18,7 @@ const {
|
||||
prepareBundledOpenCodeConfig,
|
||||
} = require("./opencode-config.cjs");
|
||||
const { npmInvocation } = require("./npm-invocation.cjs");
|
||||
const { prepareNativeDshResources } = require("./native-dsh-resources.cjs");
|
||||
|
||||
const architectureNames = {
|
||||
1: "x64",
|
||||
@@ -184,6 +185,7 @@ module.exports = async function prepareBundledRuntimes(context) {
|
||||
architecture === "x64" ? `${architecture}-baseline` : architecture;
|
||||
const packageName = `opencode-${packagePlatform}-${suffix}`;
|
||||
const projectDir = context.packager.projectDir;
|
||||
await prepareNativeDshResources(projectDir, platform, architecture, downloadPackage, lockedIntegrity);
|
||||
await prepareBundledOpenCodeConfig(projectDir);
|
||||
const projectPackage = JSON.parse(
|
||||
await readFile(join(projectDir, "package.json"), "utf8"),
|
||||
|
||||
@@ -425,9 +425,8 @@ Renderer 只通过显式 IPC 列出、加入、删除、提升、释放和接收
|
||||
|
||||
```text
|
||||
任务中心
|
||||
├─ 需要关注
|
||||
├─ 进行中
|
||||
├─ 已暂停
|
||||
├─ 暂停
|
||||
└─ 已结束
|
||||
└─ 打开任务自身
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# 本机专用推理架构与任务控制评估
|
||||
|
||||
## 记录范围与状态
|
||||
|
||||
评估日期:2026-09-18。本文记录 ASR、OCR、内置 embedding 的执行边界、任务控制缺口和后续优化依据,供语音输入、文档解析、知识检索与索引共同使用。
|
||||
|
||||
现状依据本次工作区源码核对,包含尚未提交的变更,不等同于已发布版本或实机验收结论。本次仅整理文档,未运行引擎性能测试、复现故障或实施迁移。下文“建议”是评估方向,“待办”均未在本次实施,也没有约定交付日期。
|
||||
|
||||
产品 Task、Job、Run 的定义沿用 [Task 与 Job 领域模型](../features/task-and-job/task-and-job-model.md)。本文的“推理任务”指一次引擎请求及其执行记录,不自动等同于产品 Task,不据此新增产品对象、持久化日志或全局调度器。本机推理管理的现有界面和接入清单见[应用导航技术设计](../features/application-tool-navigation/technical-design.md#111-本机推理任务与执行服务);本文负责跨功能架构评估及优化待办。
|
||||
|
||||
## 当前执行边界
|
||||
|
||||
| 能力 | 源码现状 | 所有权与释放 | 评估建议 |
|
||||
| --- | --- | --- | --- |
|
||||
| ASR | 录音结果就绪后提交完整 PCM;Main 创建 `node:worker_threads` Worker,以 sherpa-onnx WASM 执行离线识别;服务一次只接受一个请求 | 每请求创建识别器;超时或 abort 调用并等待 `worker.terminate()`;正常结果路径的清理确认存在缺口,见 TC-4 | 保留请求级 Worker,适合间歇语音输入;常驻化需流式需求或测量支持 |
|
||||
| OCR | Main 排队后发往主窗口 Renderer;Renderer 复用 Web Worker,运行 `ppu-paddle-ocr/web` 与 `onnxruntime-web` WASM;文档串行处理,PDF 逐页处理 | Renderer 模块持有 Worker;活动请求取消或单页超时会终止 Worker;无活动和排队请求时空闲 60 秒释放 | 当前保留 Web Worker;独立于 UI 的持续任务可评估专用后台 Renderer 加 Worker |
|
||||
| 内置 embedding | Main 按模型目录共享引用计数 Broker;transport 使用 `utilityProcess.fork`,Granite tokenizer 和 ONNX session 首次请求懒加载并复用 | 多使用方共享进程;最后一个 provider 释放时 shutdown;也支持显式启停,停止等待 child exit | 保留共享 utilityProcess;优先核对取消后的容量回收与加载失败重试 |
|
||||
|
||||
### ASR 的证据与边界
|
||||
|
||||
[App.tsx](../../src/renderer/src/App.tsx) 在 `recording.result` 完成后调用 `speech.transcribe`;[录音模块](../../src/renderer/src/speech-recognition.ts)负责采样与重采样。[SpeechTranscriptionService](../../src/main/speech/speech-transcription-service.ts) 的 `active.size` 拒绝并发识别,`runSherpaTranscription` 为每次请求创建 Worker,转移音频缓冲区,使用 `createOfflineRecognizer` 同步 decode。Worker 创建后的识别超时为 120 秒,不包含此前模型解析的等待时间。
|
||||
|
||||
[package.json](../../package.json) 固定 `sherpa-onnx` 为 1.13.4;本次同时核对已安装包的 `index.js`,其入口加载 `sherpa-onnx-wasm-nodejs.js`。因此这里记录的是 Node Worker 内的 WASM 路径。Node Worker 与 Main 共享 OS 进程,线程隔离不能视为独立进程故障边界。
|
||||
|
||||
Worker 先 `postMessage`,再在 `finally` 中调用 `stream.free()` 和 `recognizer.free()`。Main 收到结果后以 `finish(..., false)` 结算、移除监听和超时,不等待 Worker exit;随后 `active` 删除请求,推理记录转为终态。这证明结果完成与资源清理确认之间存在记账间隙,但尚未证明实际泄漏或清理耗时。
|
||||
|
||||
### OCR 的浏览器依赖
|
||||
|
||||
[OCR Worker](../../src/renderer/src/document-ocr-worker.ts) 显式选择 WASM provider、`canvas-native` 图像处理,当前 `ort.env.wasm.numThreads = 1`。PDF 路径使用 `pdfjs-dist`、`OffscreenCanvas` 和 `convertToBlob`。本次核对的依赖链为 `ppu-paddle-ocr/web/platform.web.js` 到 `ppu-ocv/canvas-web`;后者的 `platform/web.js` 使用 `createImageBitmap` 解码图片。包依赖以 [package-lock.json](../../package-lock.json) 为准,依赖升级后需重新核对此结论。
|
||||
|
||||
这些能力与浏览器执行环境有关。直接迁到 Node utilityProcess,需要另行验证图像解码、Canvas、PDF 渲染及打包资源路径,不能只替换进程创建 API 就宣称等价。
|
||||
|
||||
[DocumentOcrBroker](../../src/main/document-ocr-broker.ts) 最多保留 4 个 pending 请求,一次派发一个;总超时从派发时开始,不覆盖 Main 排队等待。其预算为 `min(10 分钟, max(单页预算, 60 秒 + 单页预算 × 页数))`。[Renderer bridge](../../src/renderer/src/document-ocr-bridge.ts) 另有串行 Promise 队列,Worker 就绪超时为 120 秒,就绪后按单页预算计时,并在进度消息到达时重置单页计时器。Main 的 60 秒启动预算与 Renderer 的 120 秒就绪等待未对齐,短文档冷启动时 Main 有机会先判超时。
|
||||
|
||||
[Main 初始化](../../src/main/index.ts) 将 Broker 绑定到 `mainWindow`;[Renderer 入口](../../src/renderer/src/main.tsx) 安装 bridge。Broker 派发前仅检查窗口销毁等条件,没有等待 bridge 就绪的握手,也没有在该派发链路中处理 bridge 就绪丢失。Renderer 内的 Worker `ready` 只确认模型初始化,不能证明 Main 发请求时 Renderer 已安装 IPC 监听。窗口重载、Renderer 崩溃及启动时监听尚未安装的影响应按 TC-3 复现。
|
||||
|
||||
来源请求可以取消 OCR;已派发请求取消时 Main 发送取消消息并立即结算,然后派发下一项,未等待 Renderer 清理确认。Renderer 对当前活动请求的取消会终止共享 Worker。管理页因此没有将该操作作为安全的单任务取消能力;手动释放也会拒绝活动或排队请求。
|
||||
|
||||
### Embedding 的共享执行与取消
|
||||
|
||||
[Main provider 工厂](../../src/main/index.ts) 按模型目录维护 `sharedEmbeddingBrokers` 及使用计数;[transport](../../src/main/knowledge/embedding-utility-transport.ts) 以 spawn 确认进程启动。[Granite 引擎](../../src/main/knowledge/granite-embedding-engine.ts) 缓存 `loadTask`,在首次请求时创建 tokenizer 和 WASM ONNX session。进程已启动不等于模型已加载;当前没有为模型加载完成提供独立服务状态。
|
||||
|
||||
[Broker](../../src/main/knowledge/embedding-inference-broker.ts) 默认请求超时为 120 秒,计时在 transport 就绪后建立 pending 时开始。调用方取消或超时会先返回错误,同时发送 cancel,保留 pending 和推理执行记录,等待结果、`CANCELLED` 或进程退出。保留容量避免把仍在计算的任务当作已释放。
|
||||
|
||||
[执行端](../../src/main/knowledge/embedding-inference-worker.ts) 收到 cancel 后设置 AbortSignal;Granite 在加载、输入构造和 `session.run` 前后检查取消,但没有将可抢占的取消句柄传入 `session.run`。若该调用一直不返回,取消检查和确认就无法完成。Broker 的 `cancelPending` 会清除原计时器,当前没有针对该 pending 的后续有界清理升级,因此持续挂起可一直占用在途容量。此为源码可达的条件风险,真实引擎挂起尚未复现。
|
||||
|
||||
`loadTask ??= this.loadFiles(...)` 也会缓存被拒绝的加载 Promise;同一引擎实例后续请求复用同一个失败,不能通过再次请求重新加载。重建进程会建立新引擎;是否增加实例内重试及其清理方式,按 TC-2 验证后确定。
|
||||
|
||||
## 架构建议与触发条件
|
||||
|
||||
ASR 继续按录音请求创建 Worker。对低频、间歇输入,每次释放模型能避免长期占用;本次没有冷启动与常驻成本数据,不能量化收益。只有连续流式识别成为真实需求,或高频请求测出重复加载已影响目标延迟,才评估常驻 utilityProcess。届时需比较加载成本、暖请求延迟、空闲工作集和取消时延,并重新设计音频流与识别器复用;单纯常驻不会把当前离线 API 变成流式识别。
|
||||
|
||||
OCR 保留 Web Worker。如果文档任务需要在主 UI 重载或不可用时继续执行,优先评估由 Main 持有的专用后台 Renderer 加 Worker,以保留浏览器图像/PDF 能力。该方案仍是候选,需要完成以下设计与实机验证后才可决定迁移:
|
||||
|
||||
- 保持现有 sandbox、context isolation、禁用 Node integration 的边界;通过专用 preload 暴露必要方法,并沿用可信 sender 与输入校验。当前 IPC 若限定主窗口 sender,需要明确新增宿主的受信身份及响应归属。
|
||||
- 分别确认后台 Renderer 的 IPC 监听就绪与 Worker 模型就绪;定义重载、崩溃、启动失败及应用退出时 pending、队列和模型的处置,防止静默丢请求或自动重放。
|
||||
- Main 持有后台宿主生命周期;管理 UI 关闭不销毁执行宿主,服务释放和应用退出才按约定清理。验证隐藏 Renderer 的调度、计时器和 Worker 执行,不根据窗口不可见推断运行不受影响。
|
||||
- 用实际 OS PID、Electron 进程指标和崩溃影响验证隔离程度。单独的 BrowserWindow/WebContents 不能直接作为独立 OS 进程或独立服务资源统计的证据;若与其他 Renderer 共享 PID,必须继续报告共享范围。
|
||||
|
||||
Embedding 保留共享 utilityProcess。查询与索引共享模型和进程的现有方式具备复用基础,优化先处理取消回收和失败重试。单请求取消继续尽量只影响该请求;进程终止属于服务级操作,必须说明其他使用方的结果。不得把每次请求超时直接等同于可以无条件杀死共享进程。
|
||||
|
||||
## 任务控制覆盖与缺口
|
||||
|
||||
[共享契约](../../src/shared/local-inference-contracts.ts)将服务状态与任务状态分开;[LocalInferenceService](../../src/main/local-inference-service.ts) 在 Main 内存登记请求,保存活动项及最近 100 条终态记录。任务的 `running` 当前包含排队、模型加载与执行,未提供统一细分阶段。该登记器是观测和控制入口,各引擎仍负责实际队列与清理。
|
||||
|
||||
| 控制维度 | 当前覆盖 | 后续评估和验收要求 |
|
||||
| --- | --- | --- |
|
||||
| 状态 | 任务有 `running/cancelling/completed/cancelled/failed`;服务另有启动、运行、停止、未知和错误状态;ASR 模型可用时服务仍为 unknown | 区分调用方已返回、执行仍活动、资源清理已确认;进程 spawn、模型 ready、请求完成不能互相替代。先增加必要证据,不预建完整阶段状态机 |
|
||||
| 所有权 | ASR 请求持有 Worker;OCR Renderer 持有共享 Worker;embedding 按目录共享 Broker 并引用计数 | 控制记录注明真实来源与宿主;缺失项目、会话关联时不猜测归属。产品 Task 的取消不能凭名称去停止整个推理服务 |
|
||||
| 取消 | ASR terminate;OCR 来源取消会终止当前 Worker;embedding 协作取消并保留执行记录 | 验证取消与自然完成竞态、重复取消及清理结果。单请求取消与共享服务停止分别定义影响,不用 UI 隐藏代替取消 |
|
||||
| 超时与容量 | ASR Worker 120 秒;OCR 两端预算与排队语义不同;embedding 默认请求 120 秒且保留取消后容量 | 分别说明排队、启动、推理、清理的计时起点和上限;调用方超时后仍在执行的工作需要有界处置,不得提前释放容量制造超额并发 |
|
||||
| 停止与清理 | 服务操作按 serviceId 串行,期间拒绝新增请求;停止校验当前活动 taskId 确认清单;向量停止等待 exit,重启不重放请求 | 清理失败保持可观察的活动/错误信息;窗口或宿主丢失需使受影响请求得到明确结果;ASR 和 OCR 的结果/清理间隙分别核对 |
|
||||
| 可观测性 | 有来源、状态、时间、可选模型和错误;向量有 PID 资源数据;管理 Modal 不显示任务历史 | 测量排队、加载、执行、取消请求到执行结束、清理确认的耗时与在途数;日志不记录音频、图片或向量原文,未知指标不补零 |
|
||||
|
||||
本文的覆盖要求用于后续问题复现和最小修复,不构成引入统一常驻服务框架、持久化执行副本或自动恢复协议的决定。范围为桌面本机引擎;远程 Agent、外部向量端点和未接入的 TTS 不因此获得同样控制能力。
|
||||
|
||||
## 资源观测口径
|
||||
|
||||
[LocalInferencePage](../../src/renderer/src/LocalInferencePage.tsx) 在启用、文档可见且 Modal 内容未被 hidden/inert 隔离时每 5 秒读取快照,跳过重叠请求,关闭时清理轮询。该刷新间隔不能用于证明短请求的峰值或清理完成时刻。
|
||||
|
||||
[向量资源采样器](../../src/main/inference-process-resources.ts) 按 transport 当前 PID 匹配 `app.getAppMetrics()` 的 Utility 条目,用累计 CPU 秒差除以单调时间间隔。100% 表示一个逻辑核心,可超过 100%;首次采样、PID/创建时间变化、间隔超过 15 秒或读数异常时重建基线。工作集从 KiB 转为字节,UI 显示 MiB。
|
||||
|
||||
向量工作集包含运行时、模型、临时缓冲区及并发使用方,不能当作模型权重大小或单任务内存。ASR Node Worker 和 OCR Web Worker 没有当前可独立归属的 OS 进程工作集;宿主进程总量不能直接填入某个服务行。未来 OCR 后台宿主也必须先核实 PID 与共享情况,再决定指标范围。显存当前未采集。
|
||||
|
||||
## 优化待办与验收
|
||||
|
||||
优先级表示建议处理顺序:P1 为执行正确性和回收问题,P2 为测量及有条件的架构选型。全部待办待开展;源码核对已完成,故障复现、修复和实机验收分别记录,不以文档完成替代实现完成。
|
||||
|
||||
| ID | 优先级与事项 | 依赖及验收条件 |
|
||||
| --- | --- | --- |
|
||||
| TC-1 | P1:embedding 取消/超时后的有界回收 | 先覆盖 `session.run` 延迟返回及不返回,观察 caller、pending、执行记录与容量。定义取消后的清理等待上限及升级条件,数值依据测量确定。普通取消不终止其他共享任务;确需服务级终止时明确受影响使用方及失败结果,等实际退出再释放容量,且不重放请求。模拟用于稳定触发条件,最终以真实引擎取消和停止路径补验 |
|
||||
| TC-2 | P1:embedding 加载失败重试 | 覆盖首次读模型或建 session 失败、随后恢复依赖的场景;选择清除失败缓存或显式重建引擎的最小方案。验收后续请求能够重新加载,并发请求不重复加载,失败中已创建的资源被清理,销毁后不再复活;不能把一次成功创建进程当作重试通过 |
|
||||
| TC-3 | P1:OCR 宿主就绪、丢失与超时预算 | 保留当前 Web Worker 即可先验证,无须等待迁移。覆盖监听安装前派发、主 Renderer 重载/崩溃、冷启动超过 Main 启动预算及排队取消。明确 bridge 就绪与 Worker ready 的区别;受影响请求有明确结果,队列能继续使用,没有静默丢失或重放;两端预算、起算点和取消后下一项派发与清理顺序一致 |
|
||||
| TC-4 | P1:ASR 结果与清理记账 | 覆盖结果先到、延迟退出、清理异常以及取消/超时与结果竞争。选择延后请求结算或单独保留清理记录的最小方案;验收资源仍活动时不会被报告为已释放,也不提前放开实际单请求容量。正常完成及异常路径均验证 Worker 最终退出 |
|
||||
| TC-5 | P2:统一测量记录与资源归属核验 | 可与 P1 复现并行,为 TC-6/TC-7 提供依据。记录冷/暖加载时间、端到端延迟、排队时长、取消至执行结束/资源回收时长、空闲与峰值工作集、CPU 时间和样本数;按 PID 对照 OS 工具,分别报告引擎、宿主与应用总量。重启后不沿用旧 PID 基线,不以 5 秒 UI 轮询代替专门测量 |
|
||||
| TC-6 | P2、条件待定:OCR 后台 Renderer 加 Worker | 依赖明确的 UI 独立执行需求、TC-3 生命周期结论及 TC-5 数据。候选验证需包含图片、多页 PDF、UI 重载时执行、后台宿主崩溃、空闲释放、应用退出、preload/IPC sender 边界和各支持平台打包资源。证实任务连续性及实际 PID 隔离后,再记录采用或保留现状的结论 |
|
||||
| TC-7 | P2、条件待定:ASR 常驻 utilityProcess | 依赖流式/高频场景成立,或 TC-5 证明重复加载达到需优化程度,并完成 TC-4。对比请求级 Worker 与候选的冷启动、暖请求、空闲内存和取消成本;若为流式,验证连续音频、结果提交和停止语义。没有测量或实际需求时继续保留请求级 Worker |
|
||||
|
||||
测量记录应包含 OS/架构、Electron 与依赖版本、模型版本、硬件、输入时长/页数/token 与批量大小、冷暖条件、请求频率和并发使用方。使用固定可重复输入,报告样本数、分布及异常,不填入未经测量的性能收益。后续验收覆盖受影响的 Windows、macOS、Linux 构建;尚未测的平台明确保留为待验。
|
||||
|
||||
本次证据为源码和已安装依赖入口检查。后续执行待办时,在所属功能进度文档记录命令、环境、真实路径结果及遗留项,并引用 TC 编号;若迁移条件未成立,记录保留现状的原因即可。
|
||||
@@ -12,8 +12,8 @@
|
||||
| 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
|
||||
| 相关基线 | [统一界面设计系统](../../UI-DESIGN.md)、[文档解析与本地 OCR](../features/document-processing/prd.md)、[本地文本向量模型与连接设计](../features/knowledge-base/local-embedding-technical-design.md)、[全双工实时语音交互设计](../features/full-duplex-voice/technical-design.md) |
|
||||
|
||||
本文定义“设置 → 平台功能”的二级页签结构,以及所有 GoodBuddy 托管本地模型共同使用的
|
||||
“模型下载源”设置。首期下载源为 ModelScope 和 Hugging Face,默认使用 ModelScope。
|
||||
本文定义“设置 → 平台功能”的二级页签结构,以及位于“设置 → 模型连接”、所有 GoodBuddy
|
||||
托管本地模型共同使用的“模型下载源”设置。首期下载源为 ModelScope 和 Hugging Face,默认使用 ModelScope。
|
||||
|
||||
模型下载源是用户明确选择的供应链路径。当前来源不可用或没有所选模型时,GoodBuddy
|
||||
必须明确失败并提供设置入口,**不能静默切换到另一个下载源。**
|
||||
@@ -24,8 +24,9 @@
|
||||
|
||||
1. “平台功能”保留现有一级设置分类,在分类内部增加共享 `PageTabs`:
|
||||
- **通用设置**
|
||||
- **魔法笔记**
|
||||
2. “通用设置”首个功能为“模型下载源”。
|
||||
- **远程项目**
|
||||
2. “模型下载源”位于“模型连接”页头,与模型类型页签同一行右对齐,在保存操作下方;
|
||||
显示当前来源和次按钮,点击后在弹窗中选择并确认保存。
|
||||
3. 首期提供两个互斥选项:
|
||||
- `modelscope`,用户文案为 **ModelScope**,默认值。
|
||||
- `hugging-face`,用户文案为 **Hugging Face**。
|
||||
@@ -47,9 +48,9 @@
|
||||
同一模型包的部分文件从 ModelScope 下载、部分文件从 Hugging Face 下载。
|
||||
8. 下载任务启动时冻结来源。任务运行期间切换全局设置,不改变、重启或迁移该任务;新的
|
||||
下载使用新来源。
|
||||
9. 当前来源失败时,不自动重试另一个来源。错误可以提供“前往通用设置更换下载源”,由
|
||||
用户明确选择。
|
||||
10. 现有魔法笔记设置只移动到“魔法笔记”页签,不改变保存、启用或评论行为。
|
||||
9. 当前来源失败时,不自动重试另一个来源。向量、语音和 OCR 的更换下载源入口直接打开
|
||||
“模型连接”页的下载源弹窗,由用户明确选择并确认。
|
||||
10. 魔法笔记设置由应用中心与笔记页共享,不在全局设置中重复提供入口。
|
||||
|
||||
---
|
||||
|
||||
@@ -59,10 +60,11 @@
|
||||
|
||||
“平台功能”现已使用两个二级页签:
|
||||
|
||||
- “通用设置”承载全局模型下载源。
|
||||
- “魔法笔记”承载显示入口、AI 评论方式和 AI 评论形式。
|
||||
- “通用设置”承载快捷唤起、会话与通知、工作目录设置。
|
||||
- “远程项目”承载远程项目开关与 Agent 包管理。
|
||||
|
||||
页签复用共享 `PageTabs`,默认打开“通用设置”,不会把当前页签写入应用设置。
|
||||
全局模型下载源由“模型连接”页头提供入口,布局和弹窗行为见第 7 节。
|
||||
|
||||
### 2.2 受管模型下载
|
||||
|
||||
@@ -99,7 +101,7 @@
|
||||
- 切换来源时不影响已安装、已选择或正在使用的模型。
|
||||
- 下载失败时知道实际使用了哪个来源。
|
||||
- 所选来源没有模型时得到明确说明,并可以主动前往设置切换。
|
||||
- 在“平台功能”中快速区分通用设置与魔法笔记。
|
||||
- 在“模型连接”页头打开模型下载源弹窗,选择后确认保存。
|
||||
|
||||
### 3.2 产品目标
|
||||
|
||||
@@ -163,13 +165,15 @@
|
||||
|
||||
```text
|
||||
设置
|
||||
├─ 模型连接
|
||||
│ └─ 页头:模型类型页签同行右侧的当前来源与“模型下载源”按钮
|
||||
│ └─ 模型下载源弹窗
|
||||
└─ 平台功能
|
||||
├─ 通用设置
|
||||
│ └─ 模型下载源
|
||||
└─ 魔法笔记
|
||||
├─ 显示魔法笔记入口
|
||||
├─ AI 评论方式
|
||||
└─ AI 评论形式
|
||||
│ └─ 快捷唤起、会话与通知、工作目录
|
||||
└─ 远程项目
|
||||
├─ 远程项目开关
|
||||
└─ Agent 包管理
|
||||
```
|
||||
|
||||
分类页只有一个一级标题“平台功能”。页签标题不重复渲染为第二个页面标题;每个面板内部
|
||||
@@ -186,7 +190,7 @@
|
||||
onChange={setActiveSection}
|
||||
tabs={[
|
||||
{ id: 'general', label: '通用设置' },
|
||||
{ id: 'magic-notes', label: '魔法笔记' }
|
||||
{ id: 'remote-projects', label: '远程项目' }
|
||||
]}
|
||||
value={activeSection}
|
||||
variant="segmented"
|
||||
@@ -214,7 +218,7 @@
|
||||
|
||||
未来新增页签时:
|
||||
|
||||
- 必须与“通用设置”和“魔法笔记”处于同级。
|
||||
- 必须与“通用设置”和“远程项目”处于同级。
|
||||
- 页签表示完整、独立的功能设置域。
|
||||
- 通用设置只放跨功能的全局行为,不成为任意设置的杂物区。
|
||||
- 页签超过可读数量时重新组织信息架构,不允许多行堆叠。
|
||||
@@ -222,17 +226,20 @@
|
||||
|
||||
---
|
||||
|
||||
## 7. 通用设置界面
|
||||
## 7. 模型下载源界面
|
||||
|
||||
### 7.1 布局
|
||||
|
||||
“模型连接”页头在保存操作下方,将模型类型页签与下载源控件放在同一行:页签居左,
|
||||
“当前来源:ModelScope”和“模型下载源”次按钮整体右对齐。窄窗口保持同行,空间不足时
|
||||
导航行可横向滚动。点击按钮打开弹窗;通用设置的工作目录卡片不再提供下载源控件。共享布局规则见
|
||||
[UI 设计规范](../../UI-DESIGN.md#138-设置中心)。
|
||||
|
||||
```text
|
||||
平台功能
|
||||
管理通用平台行为与可选工作区能力
|
||||
模型连接 [保存]
|
||||
[文本] [向量] [重排] [语音] 当前来源:ModelScope [模型下载源]
|
||||
|
||||
[ 通用设置 ] [ 魔法笔记 ]
|
||||
|
||||
┌ 模型下载源 ────────────────────────────────────────────┐
|
||||
┌ 模型下载源(弹窗)───────────────────────────────── [×]┐
|
||||
│ 选择 GoodBuddy 托管本地模型后续下载使用的平台。 │
|
||||
│ 已安装模型和 ZIP 导入不受影响。 │
|
||||
│ │
|
||||
@@ -241,19 +248,19 @@
|
||||
│ │
|
||||
│ ○ Hugging Face │
|
||||
│ 适合可以稳定访问 Hugging Face 的网络环境 │
|
||||
│ │
|
||||
│ [取消] [确认] │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
页面只保留一个视觉上最突出的任务。模型下载源保存属于即时设置,不增加与来源选项竞争的
|
||||
大型“保存全部”按钮。
|
||||
弹窗通过 Portal 挂载到 `document.body`,复用共享 Modal 样式、背景隔离和焦点管理。
|
||||
模型连接页头的保存操作与下载源弹窗的“确认”各自保存对应设置。
|
||||
|
||||
### 7.2 控件
|
||||
|
||||
来源选项包含说明和未来可能的不可用原因,因此使用语义化 Radio Group,而不是原生
|
||||
`select` 或 `SegmentedControl`:
|
||||
|
||||
- `fieldset` + `legend` 表达“模型下载源”。
|
||||
- 弹窗内使用 `fieldset` + 持久的屏幕阅读器 `legend` 表达“模型下载源”。
|
||||
- 每个选项使用 Radio 和整行可点击卡片。
|
||||
- 当前项同时显示选中 Radio、强调边框和选中背景。
|
||||
- 不能只用站点 Logo 或颜色表达选择。
|
||||
@@ -282,12 +289,16 @@
|
||||
|
||||
### 7.4 保存交互
|
||||
|
||||
- 用户选择另一个 Radio 后立即调用应用设置更新。
|
||||
- 保存期间禁用两个选项,保留最后确认值。
|
||||
- 成功后更新当前选择,并通过应用通知提示:
|
||||
- 打开弹窗时以已保存来源初始化草稿;选择 Radio 只修改草稿,不调用应用设置更新。
|
||||
- 点击“确认”才保存;草稿与已保存来源相同时直接关闭,不重复写入。
|
||||
- 取消、关闭按钮、Escape 或点击遮罩均丢弃草稿,已保存来源不变。
|
||||
- 保存期间禁用选项、确认、取消和关闭,阻止重复提交及遮罩、Escape 关闭。
|
||||
- 成功后更新页头当前来源并关闭弹窗,通过应用通知提示:
|
||||
`模型下载源已切换为 Hugging Face。`
|
||||
- 失败时恢复最后确认值,在 Radio Group 附近显示可重试错误。
|
||||
- 保存失败时保留弹窗和草稿,已保存来源不变;就地显示错误,允许再次点击“确认”重试。
|
||||
- 读取失败时显示错误和“重试”,禁用确认,不以默认值伪装为已保存来源。
|
||||
- 同一个失败不再同时显示页面横幅和全局错误通知。
|
||||
- 弹窗打开后聚焦关闭按钮,Tab 在弹窗内循环;关闭后焦点回到页头“模型下载源”按钮。
|
||||
|
||||
来源变化不触发:
|
||||
|
||||
@@ -304,28 +315,11 @@
|
||||
|
||||
---
|
||||
|
||||
## 8. 魔法笔记页签
|
||||
## 8. 魔法笔记设置边界
|
||||
|
||||
现有魔法笔记卡完整移动到“魔法笔记”面板:
|
||||
|
||||
- 显示魔法笔记入口。
|
||||
- AI 评论方式。
|
||||
- AI 评论形式。
|
||||
|
||||
保持以下行为不变:
|
||||
|
||||
- 开关继续使用共享 Switch 和 `role="switch"`。
|
||||
- 评论方式和形式继续使用 `SegmentedControl`。
|
||||
- 每项继续即时保存。
|
||||
- 保存失败保留最后确认设置。
|
||||
- `onMagicNotesEnabledChange` 继续更新应用导航入口。
|
||||
|
||||
页签重构不能:
|
||||
|
||||
- 重置现有设置。
|
||||
- 在切换页签时保存或改变值。
|
||||
- 因魔法笔记入口关闭而隐藏“魔法笔记”设置页签。
|
||||
- 把页签选择误当成启用开关。
|
||||
全局设置不再提供魔法笔记页签或跳转入口。应用中心与笔记页继续共享同一表单和配置,
|
||||
保留已有启用、待办数量显示、评论方式、评论形式及业务数据。共同设置的交互与同步规则见
|
||||
[应用导航 UI 设计](../features/application-tool-navigation/ui-design.md)。
|
||||
|
||||
---
|
||||
|
||||
@@ -645,7 +639,7 @@ resolveModelDownloadPackage(files, source)
|
||||
|
||||
### 12.1 来源状态
|
||||
|
||||
语音和 OCR 的当前模型详情各保留一个全局下载源标签。向量内置连接不重复显示平台功能中
|
||||
语音和 OCR 的当前模型详情各保留一个全局下载源标签。向量内置连接不重复显示模型连接页头中
|
||||
已经明确的全局来源,也不显示固定本地安装路径;下载仍使用已保存的全局来源。
|
||||
|
||||
所有模型管理界面都显示当前模型是否可下载、活动操作的进度与取消入口,以及已安装模型
|
||||
@@ -663,10 +657,12 @@ resolveModelDownloadPackage(files, source)
|
||||
|
||||
```text
|
||||
Hugging Face 暂不提供此模型的完整已验证文件。
|
||||
[前往通用设置]
|
||||
[模型下载源]
|
||||
```
|
||||
|
||||
不能显示可点击“下载”后才告诉用户缺少某个文件。
|
||||
向量、语音和 OCR 的“模型下载源”入口均导航到“模型连接”并直接打开同一弹窗。
|
||||
跨分类导航沿用设置中心的未保存修改确认,用户取消离开时保留原表单。
|
||||
|
||||
### 12.2 已安装模型
|
||||
|
||||
@@ -711,7 +707,7 @@ ZIP 是来源无关的离线迁移方式:
|
||||
- `正在从 {{source}} 下载`
|
||||
- `打开 {{source}} 模型仓库`
|
||||
- `当前下载源暂不提供此模型`
|
||||
- `前往通用设置更换下载源`
|
||||
- `模型下载源`(打开下载源弹窗)
|
||||
|
||||
不再写死:
|
||||
|
||||
@@ -757,7 +753,7 @@ ZIP 是来源无关的离线迁移方式:
|
||||
失败状态提供:
|
||||
|
||||
1. 重试当前来源。
|
||||
2. 前往“平台功能 → 通用设置”。
|
||||
2. 打开“模型连接”的模型下载源弹窗,选择并确认保存。
|
||||
3. 取消。
|
||||
|
||||
用户切换来源后需要重新点击下载。不能在设置保存后自动恢复上一失败任务。
|
||||
@@ -824,7 +820,7 @@ ModelScope 和 Hugging Face 都可能跳转到文件存储或 CDN。每个来源
|
||||
|
||||
| 设置 | 位置 | 作用 |
|
||||
| --- | --- | --- |
|
||||
| 模型下载源 | 平台功能 → 通用设置 | GoodBuddy 托管本地模型 |
|
||||
| 模型下载源 | 模型连接页头 → 模型下载源弹窗 | GoodBuddy 托管本地模型 |
|
||||
| 检查更新源 | 关于与更新 | GoodBuddy 版本检查和下载页 |
|
||||
|
||||
切换任一设置不得修改另一个设置。用户选择 ModelScope 不表示应用更新使用镜像节点;选择
|
||||
@@ -864,9 +860,9 @@ Ollama 的模型下载由用户和 Ollama 管理。GoodBuddy 的模型下载源
|
||||
|
||||
| 场景 | 行为 |
|
||||
| --- | --- |
|
||||
| 读取设置失败 | 显示平台功能阻塞错误,不猜测或写入来源 |
|
||||
| 保存来源失败 | 保留上一个确认值,显示就地错误 |
|
||||
| 当前来源无完整模型包 | 下载按钮禁用,显示前往通用设置 |
|
||||
| 读取设置失败 | 页头显示读取失败,弹窗提供就地重试并禁用确认,不猜测或写入来源 |
|
||||
| 保存来源失败 | 已保存来源不变,保留弹窗和草稿,显示就地错误并允许确认重试 |
|
||||
| 当前来源无完整模型包 | 下载按钮禁用,提供直接打开模型连接页下载源弹窗的入口 |
|
||||
| 当前来源网络不可用 | 当前下载失败或允许同来源重试,不请求另一来源 |
|
||||
| 当前来源返回 404 | 显示来源缺少固定文件,不请求另一来源 |
|
||||
| 重定向 Host 不受信任 | 立即失败并删除本次暂存 |
|
||||
@@ -894,15 +890,19 @@ Ollama 的模型下载由用户和 Ollama 管理。GoodBuddy 的模型下载源
|
||||
- 更高未知版本拒绝,不覆盖文件。
|
||||
- 并发设置更新保持完整 JSON。
|
||||
|
||||
### 17.2 平台功能界面
|
||||
### 17.2 设置界面
|
||||
|
||||
- 默认选中“通用设置”Tab。
|
||||
- Tab 使用 `tablist`、`tab`、`tabpanel` 和 `aria-selected`。
|
||||
- 方向键、Home、End 和焦点恢复正确。
|
||||
- 切换到魔法笔记不改变任何设置。
|
||||
- 切换平台功能页签不改变任何设置,全局设置不显示魔法笔记入口。
|
||||
- 模型连接页头的当前来源与次按钮位于模型类型页签同一行右侧、保存操作下方;通用设置不再显示下载源控件。
|
||||
- Radio Group 具有持久 Label。
|
||||
- 选择来源时保存一次,保存期间防止重复提交。
|
||||
- 保存失败恢复原值并保留可重试错误。
|
||||
- 选择 Radio 只更新草稿,确认后保存一次;未改变来源时确认不写入。
|
||||
- 取消、关闭、Escape 和遮罩关闭不改变已保存来源;保存期间禁止关闭及重复提交。
|
||||
- 保存失败保留草稿并允许确认重试;读取失败提供重试且禁用确认。
|
||||
- 弹窗使用 body Portal、焦点循环和关闭后焦点恢复。
|
||||
- 向量、语音和 OCR 的更换下载源入口直接打开模型连接页弹窗,跨分类时保留离开保护。
|
||||
- 窄窗口不隐藏页签或来源名称。
|
||||
|
||||
### 17.3 目录
|
||||
@@ -953,8 +953,8 @@ Ollama 的模型下载由用户和 Ollama 管理。GoodBuddy 的模型下载源
|
||||
- 新增 `ModelDownloadSource` Schema 和默认值。
|
||||
- 将应用设置版本升级并迁移旧版本。
|
||||
- 在“平台功能”中增加 `PageTabs`。
|
||||
- 增加“通用设置”与模型下载源 Radio Group。
|
||||
- 把现有魔法笔记卡移动到“魔法笔记”面板。
|
||||
- “通用设置”保留平台行为;模型下载源已移至模型连接页头,通过弹窗 Radio 草稿和确认保存。
|
||||
- 魔法笔记卡已迁入应用共同设置,全局页签已移除,见第 8 节。
|
||||
|
||||
### 阶段 2:共享目录和来源解析(已完成)
|
||||
|
||||
@@ -983,14 +983,16 @@ Ollama 的模型下载由用户和 Ollama 管理。GoodBuddy 的模型下载源
|
||||
|
||||
### 19.1 产品
|
||||
|
||||
- “平台功能”显示“通用设置”和“魔法笔记”两个可访问页签。
|
||||
- “平台功能”显示“通用设置”和“远程项目”两个可访问页签。
|
||||
- 默认打开“通用设置”。
|
||||
- “通用设置”提供 ModelScope 和 Hugging Face,默认 ModelScope。
|
||||
- “模型连接”页头在模型类型页签同行右侧、保存操作下方显示当前来源及“模型下载源”次按钮。
|
||||
- 下载源弹窗提供 ModelScope 和 Hugging Face,默认 ModelScope;Radio 只编辑草稿,确认才保存,取消不改变已保存来源。
|
||||
- 读取与保存失败均可就地重试;向量、语音和 OCR 的更换下载源入口直接打开该弹窗。
|
||||
- 来源选择说明影响范围,并明确已安装模型、ZIP、Ollama 和应用更新不受影响。
|
||||
- 切换来源不会下载、删除、切换或重新验证任何已安装模型。
|
||||
- 当前来源没有模型时,在点击下载前即可看到不可用原因。
|
||||
- 下载失败不会自动请求另一来源。
|
||||
- 魔法笔记的现有设置和入口行为保持不变。
|
||||
- 魔法笔记设置由应用中心与笔记页共享,全局设置不再提供笔记入口;已有配置值和业务数据保持不变。
|
||||
|
||||
### 19.2 数据与下载
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Runtime Storage And Background Reads
|
||||
|
||||
SQLite owns persisted task, event and conversation evidence. The desktop business
|
||||
connection is long-lived. WAL permits readers alongside a writer; synchronous SQL
|
||||
still blocks the thread that executes it. Memory caches are disposable derived data.
|
||||
|
||||
Startup storage upgrades run in `assistant-storage-worker` before the business
|
||||
connection opens. Runtime readers must not invoke initialization, migrations or
|
||||
recovery. `SubagentProgressStorage` caches event encoding state, not task statistics.
|
||||
|
||||
## Runtime Read Design
|
||||
|
||||
Execution statistics use one lazily started worker with a read-only connection.
|
||||
The worker performs the existing evidence query inside a short read transaction.
|
||||
Main supplies the current reply lease IDs and receives only scalar summaries.
|
||||
There is no worker pool or duplicate persisted summary. The worker is terminated
|
||||
when its owning database closes; errors reject reads, without a synchronous Main
|
||||
fallback. In-memory test databases use the synchronous query because independent
|
||||
connections cannot access the same private in-memory database.
|
||||
|
||||
Derived caches must specify scope, capacity, lifetime and invalidation. Statistics
|
||||
keep at most eight scopes for 30 seconds. SQLite `data_version` detects other
|
||||
connections' commits; `total_changes()` detects writes on the owning connection.
|
||||
Reads inside an existing transaction clear and bypass the cache, so rolled-back
|
||||
values cannot escape. Closing the connection clears its cache. Live leases bypass
|
||||
the cache because elapsed time changes without a database write.
|
||||
|
||||
Read transactions end before replies are posted. Long-lived read transactions
|
||||
would prevent WAL checkpoints from reclaiming old pages. Each reader has its own
|
||||
SQLite page cache; adding workers increases memory usage and requires evidence of
|
||||
parallel query demand. New consumers should reuse the same lifecycle and read-only
|
||||
rules, without introducing a general query protocol before a second use case exists.
|
||||
|
||||
## Renderer Scheduling
|
||||
|
||||
Hidden task panels use React `Activity` to preserve state while scheduling hidden
|
||||
render work below visible updates. Panel factories run inside that boundary.
|
||||
Terminal and browser panels retain their existing mounted effects and sessions.
|
||||
Statistics refresh only when the task center and document are visible. Results
|
||||
must remain scoped and late responses from a previous selection must be discarded.
|
||||
|
||||
The [task statistics design](../features/task-and-job/technical-design.md) owns the
|
||||
query contract and evidence rules. Tests and timings demonstrate only their fixture
|
||||
conditions; they do not establish a latency guarantee for arbitrary retained history.
|
||||
@@ -18,6 +18,11 @@
|
||||
- 先修新增数据路径,再修历史数据。历史转换与新写入尽量使用同一套转换函数。
|
||||
- 普通 schema 变更使用既有 `PRAGMA user_version` 和 SQLite 事务;
|
||||
已升级 schema 不允许旧客户端强行打开,不通过回写版本号支持降级。
|
||||
- 将结构升级、旧内容转换和空间回收分别判断。结构版本落后不代表需要整理历史;
|
||||
普通更新产生的空闲页也不是旧数据证据。只有明确的旧内容转换才允许检查空闲页并
|
||||
执行 `VACUUM`,后续新增结构迁移不自动继承全库回收。启动页按实际操作显示状态。
|
||||
- 同一次启动的重试可在内存中保留已确认的转换/回收决定;退出后重新根据数据库
|
||||
schema 和未转换内容判断,不为仅剩空闲页增加持久化标记或重复启动回收。
|
||||
- 大数据转换不得同步阻塞 Electron Main 或 Renderer。放到工作线程,在业务连接
|
||||
打开前处理,并使用应用的共享字体、主题与页面组件显示进度和可执行的失败提示。
|
||||
- 根据实测设置有界读取批次与缓存,批次提交后再报告进度。保持 SQLite 事务与
|
||||
@@ -40,6 +45,9 @@
|
||||
对象比较必须覆盖真实生产形状和 JSON 往返,尤其是可选字段值为 `undefined` 时与字段缺省
|
||||
的差异。再次修复已发布迁移产生的数据时,提高目标 schema,并覆盖已升级用户的入口;
|
||||
不能只处理尚未迁移的旧库。
|
||||
仅修改升级调度、不改变存储格式时不增加 schema。回归必须覆盖已完成历史转换的库
|
||||
在后续结构升级时仍有空闲页,确认不重新转换或 `VACUUM`,并验证真实 Worker、
|
||||
同次启动重试和下一次启动的区别。
|
||||
|
||||
### 真实旧库
|
||||
|
||||
@@ -71,5 +79,5 @@
|
||||
表示,并回收空间。具体规则见
|
||||
[执行记录存储与升级回收](../features/assistant-workbar/execution-history-storage.md),
|
||||
数据保留与真实旧库验证见[工作栏进度](../features/assistant-workbar/progress.md)。
|
||||
- 当前未发布源码:schema 35 同时整理旧快照和 schema 34 的重复工具块,工具字段按
|
||||
- Desktop 0.13.2:schema 35 同时整理旧快照和 schema 34 的重复工具块,工具字段按
|
||||
JSON 可保存的值比较,不因 `undefined` 被省略而误判变化。
|
||||
|
||||
@@ -10,7 +10,7 @@ Debian `loong64` 桌面,不代表 GoodBuddy 已将龙芯纳入正式发布支
|
||||
均为 LoongArch ELF。
|
||||
|
||||
预览版使用社区维护的 Electron 42.3.0 LoongArch 构建。GoodBuddy 正式版本
|
||||
当前使用 Electron 43.2.0,因此该预览包只用于移植和真机验证,不能作为正式
|
||||
当前使用 Electron 44.5.1,因此该预览包只用于移植和真机验证,不能作为正式
|
||||
发布基线。Electron 官方目前也不提供 Linux LoongArch 预编译包。
|
||||
|
||||
已完成的实际构建结果:
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
# Desktop 0.13.12 and Agent 0.13.3 Preparation
|
||||
|
||||
Prepared on 2026-09-23 on `main`. This record describes a local candidate, not a
|
||||
published release. Desktop notes are in `resources/release-notes.json`; Agent
|
||||
notes are in `resources/agent-release-notes/0.13.3.md`. Both are bilingual drafts
|
||||
awaiting approval with the exact candidate commit before tagging.
|
||||
|
||||
## Scope
|
||||
|
||||
- Desktop baseline: `v0.13.11`, including all nine subsequent commits through
|
||||
`83a48a4` and the reviewed working-tree changes.
|
||||
- Agent baseline: `agent-v0.13.2`. The Agent changes reclaim idle Runtimes and
|
||||
drain displaced Agents; the Desktop Continue path restores conversation
|
||||
history when reopening a Runtime.
|
||||
- Desktop package and both root lockfile versions are `0.13.12`.
|
||||
`agent-runtime-lock.json` is `0.13.3`. Agent package tooling derives the minimum
|
||||
Desktop version from `package.json`. Node, OpenCode, and Continue locks are
|
||||
unchanged at `24.19.0`, `1.18.29`, and `1.5.47`.
|
||||
- Both feature matrices were reviewed and updated for versions, Supervisor
|
||||
default-off behavior, implemented reviews/graphs/activity/incremental input,
|
||||
remaining supervision work, and release acceptance limits.
|
||||
|
||||
## Local Validation
|
||||
|
||||
`npm run release:notes:verify`, `npm run typecheck`, and `npm run lint` passed
|
||||
after the final source changes. Focused runs cover 35 distinct test files and
|
||||
690 passing tests; 403 tests were excluded by the IPC/App name filter.
|
||||
|
||||
| Group | Files | Passing Tests |
|
||||
| --- | ---: | ---: |
|
||||
| Supervisor, settings, bridge, and release-note suites passing in the initial run | 14 | 157 |
|
||||
| Database, migration, activity I/O, note storage, menu, and sidebar suites after fixes | 6 | 209 |
|
||||
| IPC/App filtered by `supervision\|Supervisor\|heartbeat\|application enable\|graph navigation` | 2 | 16 |
|
||||
| Agent CLI, daemon, Runtime backend, Desktop ACP, transport, and connection manager | 7 | 207 |
|
||||
| Usage, duration, workspace, Markdown, and Supervisor Electron layout | 6 | 101 |
|
||||
|
||||
The initial focused run had 25 failures: 22 synthetic legacy database fixtures
|
||||
retained schema-46 objects, two menu assertions assumed Supervisor was enabled,
|
||||
and one browser close retry left its earlier error visible. Fixtures now remove
|
||||
the new objects before simulating older versions, menu tests follow the saved
|
||||
enabled preference, and a successful browser close clears its scoped error.
|
||||
All affected suites passed on rerun. Production migration behavior was not
|
||||
changed to accommodate synthetic fixtures.
|
||||
|
||||
The Electron layout test uses production components with isolated fixtures,
|
||||
including 1440, 1024, and 390 pixel widths and light/dark themes. It is not a
|
||||
packaged-app probe or a real-user database acceptance test.
|
||||
|
||||
No real model requests were made during this preparation. Existing development
|
||||
evidence records eight requests on the shared Linux x64 Host for remote lifecycle
|
||||
checks and three for incremental review. The remote evidence does not repeat
|
||||
Continue real-Host validation, the complete signed installation flow, or the
|
||||
native architecture matrix. See
|
||||
[remote lifecycle evidence](../features/remote-host/technical-design.md#生命周期验证2026-09-23)
|
||||
and [incremental review evidence](../features/conversation-supervision/progress.md#2026-09-23-自动增量验证).
|
||||
|
||||
## Pending Release Work
|
||||
|
||||
The user requested no further full local suite. Full main-branch CI validation
|
||||
and its production build remain required before tagging; native Desktop and
|
||||
Agent packaging and publication verification remain pending. No local production
|
||||
build or package was run. No push, tag, remote synchronization, or publication
|
||||
was performed during preparation. Approval must identify the exact candidate
|
||||
commit and both bilingual note files before either release tag is created.
|
||||
|
||||
## Approved Candidate CI Attempt
|
||||
|
||||
The user approved `bf8ece05902509411454b27b742ebfec930005ad` and both bilingual
|
||||
note files. On 2026-09-23 that exact commit was fast-forwarded to `main` on
|
||||
`origin` and `github`; both remote refs were verified. GitHub SSH stalled, so
|
||||
the GitHub push used HTTPS with the existing GitHub CLI keyring credentials.
|
||||
No persistent Git configuration was changed. GitHub reported the account's
|
||||
existing pull-request-rule bypass for this requested direct push.
|
||||
|
||||
- [Desktop main CI](https://github.com/mesalogo/goodbuddy/actions/runs/35849362694)
|
||||
failed in the full suite: 4 files failed, 418 passed, 13 skipped; 10 tests
|
||||
failed, 4,997 passed, 43 skipped. Type checks, lint, and the production build
|
||||
did not execute after the test failure.
|
||||
- [Agent main CI](https://github.com/mesalogo/goodbuddy/actions/runs/35849362799)
|
||||
passed source validation and native Linux x64, Linux arm64, and Darwin arm64
|
||||
deterministic compound-package verification with ephemeral test signatures.
|
||||
- Neither release tag was created or pushed. No Desktop or Agent publication
|
||||
workflow was started, and no production release assets were verified.
|
||||
|
||||
The follow-up changes are limited to tests. Two IPC tests clear the startup
|
||||
settings-read count before measuring interactive request lookups. The settings
|
||||
failure UI test now expects Supervisor to remain disabled. OpenCode isolation
|
||||
tests prepare their real configuration dependencies in a dedicated temporary
|
||||
directory instead of relying on a prior local package preparation; connection
|
||||
failures now include their diagnostic detail. The large activity-list and
|
||||
multi-page navigation and external knowledge binding tests have explicit
|
||||
15-second budgets; the binding test also waits up to five seconds for its
|
||||
resulting heading.
|
||||
Its snapshot fixture now selects by library ID, and its instance list remains
|
||||
available during polling instead of assuming a fixed number of asynchronous
|
||||
reads. Both mocks reset between tests; the binding test waits for the
|
||||
selected-library read.
|
||||
These budgets address timeouts observed in the full CI run without removing
|
||||
assertions or changing product behavior.
|
||||
|
||||
The approved source and bilingual notes remain unchanged. These test changes
|
||||
need a revised candidate commit and approval before another release attempt;
|
||||
the failed commit must not be tagged. No local production build, package,
|
||||
external model request, or LoongArch build was performed.
|
||||
|
||||
Follow-up validation: the seven selected IPC/UI regression cases and all seven
|
||||
OpenCode isolation cases passed locally. Type checks and lint passed. Full
|
||||
remote CI and the production build still require a new approved commit; these
|
||||
focused results do not satisfy that release gate.
|
||||
|
||||
## Linux Executable Path Follow-up
|
||||
|
||||
On 2026-09-23, local `main` was fast-forwarded from `163e245` to
|
||||
`073aa45a30efe99fab7a92f8aee2a36087db9c2a` after inspecting the four intervening
|
||||
commits. They update only the community QR codes in the two README files.
|
||||
The user authorized retaining these changes, fixing CI, pushing both remotes,
|
||||
and publishing Desktop `v0.13.12` and Agent `agent-v0.13.3` after successful
|
||||
candidate CI without another approval round. No LoongArch preview is requested.
|
||||
|
||||
[Desktop CI 35854792610](https://github.com/mesalogo/goodbuddy/actions/runs/35854792610)
|
||||
reported 5,004 passing tests, three failures, and 43 skipped tests. All three
|
||||
failures were in the real OpenCode isolation test. It constructed
|
||||
`opencode-ai/bin/opencode` on Linux, but OpenCode 1.18.29 installs its npm
|
||||
executable as `bin/opencode.exe` on every platform. The test now reuses
|
||||
`resolveBundledRuntimePaths`, the existing production resolver, instead of
|
||||
constructing a platform-specific filename. Production Runtime code and both
|
||||
release-note files are unchanged.
|
||||
|
||||
Local follow-up validation passed all nine tests in
|
||||
`opencode-selection-isolation.test.ts` and `bundled-runtimes.test.ts`, including
|
||||
the existing Linux development-path assertion, plus full type checks and lint.
|
||||
Release-note and package/lockfile version validation passed. The isolation
|
||||
tests made two requests to their local mock endpoint and zero real model
|
||||
requests. No full local suite, production build, package, or real-Host check
|
||||
was run for this test-only fix; it changes neither deployed Agent code nor
|
||||
the Desktop-to-Agent production path. Successful Linux main-branch CI and
|
||||
its production build remain required before tagging.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Desktop 0.13.13 Preparation
|
||||
|
||||
Prepared on 2026-09-23 from `b16fc90`, relative to published Desktop `v0.13.12`.
|
||||
This is a Desktop-only release; Agent remains at published `0.13.3`.
|
||||
No LoongArch preview is requested.
|
||||
|
||||
## Scope and isolation
|
||||
|
||||
- `bf8ff7f` reduces repeated activity-history reads during streaming saves.
|
||||
- `b16fc90` restores compact supervision feedback controls and target names.
|
||||
- The candidate is prepared in a separate worktree. The user's uncommitted
|
||||
supervision timeout tests in the original worktree are explicitly excluded.
|
||||
- Desktop and both root lockfile versions are `0.13.13`. Both feature matrices
|
||||
and bilingual release notes were reviewed against this range.
|
||||
- Database schema and Agent/Runtime contracts are unchanged. The activity
|
||||
comparison cache belongs to Desktop persistence; it does not modify the
|
||||
deployed Agent, model bridge, or remote Runtime lifecycle.
|
||||
|
||||
## Validation
|
||||
|
||||
- Six focused files passed 192 tests: activity-history I/O, AssistantDatabase,
|
||||
storage upgrades, sidebar interactions, SupervisionCard, and the Supervisor
|
||||
Electron layout fixture.
|
||||
- Re-running `tests/supervisor-layout.electron.test.ts` with
|
||||
`GOODBUDDY_SUPERVISOR_SIDEBAR=1` passed its additional sidebar scenario:
|
||||
480, 300, and 200 pixel widths in both themes, no horizontal overflow,
|
||||
compact 34px buttons, visible keyboard focus, and no raw target ID in body text.
|
||||
- `npm run release:notes:verify`, `npm run typecheck`, and `npm run lint` passed.
|
||||
- Ten consecutive activity saves performed one full-history scan. WAL and SQL
|
||||
counters are not installed-application physical disk throughput measurements.
|
||||
- No real model requests, local full-suite rerun, production build, packaging,
|
||||
or packaged-app probe were performed. UI validation uses production
|
||||
components with isolated fixtures, not a real user database.
|
||||
|
||||
The candidate must pass complete main-branch CI and its production build before
|
||||
an unused annotated `v0.13.13` tag is pushed to both remotes. Native six-platform
|
||||
packaging and public GitHub/OSS verification remain subsequent release steps.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Desktop 0.13.14 / Agent 0.13.4 发布准备
|
||||
|
||||
后续状态:Desktop 标签流程 `36165805814` 在测试阶段失败,未进入构建或发布;
|
||||
Agent 流程 `36165805884` 成功且 0.13.4 已公开。两个历史标签均保留,
|
||||
桌面恢复版见 [0.13.15 准备记录](./release-preparation-0.13.15.md)。
|
||||
以下为原候选准备时的范围与证据,不代表其桌面发布成功。
|
||||
|
||||
## 范围与审批
|
||||
|
||||
- 用户确认从 0.13.13 升至 0.13.14,现有七个测试修改及三个验证辅助文件一起提交。
|
||||
- 不构建实验性 LoongArch 预览,不运行本地生产构建、打包或安装包启动探针。
|
||||
- 准备起点为 `ff746bdc1add6a12227ccb61a7794f654aaaa8ba`。
|
||||
- 最近可达稳定标签为 `v0.13.12`。已公开发布的 `v0.13.13` 指向
|
||||
`7756231`,其发布准备提交不在当前 main 祖先链上;两项产品修复已包含,
|
||||
0.13.13 的包内发布说明也已保留。需同时核对两个范围,不重复宣传已发布修复,
|
||||
不移动标签或重写历史。
|
||||
- 用户随后确认 Agent 也末位加一,目标为 0.13.4。已批准联合发布的双语说明,
|
||||
以及验证通过后一起提交本地候选;最终候选 SHA 仍须另行确认。
|
||||
- Agent 范围为 `agent-v0.13.3..HEAD`,生产改动来自 `7adae97`。
|
||||
- 用户批准提交并推送两个远端、等待 CI;标签仍待精确提交确认。
|
||||
- Fetch 确认两个远端 main 均为 `7756231b3bdfa148f1b2702e4ff3fc3bd0cb057e`。
|
||||
用户批准以普通合并保留双方历史,保留本次版本和已批准说明,
|
||||
补回历史发布文档;不强推、不改动已有标签。
|
||||
- 本地候选提交为 `d23af99`。普通合并解决后,仅新增或修改文档;
|
||||
产品源码、测试、依赖、版本文件及两份已批准说明与该候选完全一致。
|
||||
合并后 `v0.13.13` 恢复为可达发布基线,先前祖先链差异仅记录准备起点状态。
|
||||
|
||||
## 交付与数据边界
|
||||
|
||||
- 本轮联合准备 Desktop 0.13.14 和独立 Agent 0.13.4,不覆盖历史 Agent 包。
|
||||
- 桌面包含远程结果未知后显式发送新消息的恢复修复;Agent 完成确认、
|
||||
空闲回收失败处理和旧安装清理随 0.13.4 交付。
|
||||
- Agent 发布流程读取本次 package.json 作为最低 Desktop 版本,因此需先升级
|
||||
Desktop 至 0.13.14,再下载并更新 Host。Node、OpenCode、Continue 锁定版本不变。
|
||||
- 两个候选分别使用 `v0.13.14`、`agent-v0.13.4`,Agent 不替代 Desktop Latest。
|
||||
- 数据库从 schema 46 升至 47。升级前需完整备份用户数据,包括数据库和
|
||||
相邻 notes/ 目录;旧客户端不能打开升级后的数据库。
|
||||
- 已检查中英文功能矩阵,更新当前版本、监督回顾、待办能力及独立 Agent
|
||||
交付边界。其他平台和功能的既有未验收事项继续保留。
|
||||
|
||||
## 验证
|
||||
|
||||
- 首次 `npm test`:5,125 项通过、74 项跳过、2 项失败。失败均为版本已更新而
|
||||
说明尚未获批写入导致的当前版本缺失,未修改断言规避。
|
||||
- 联合说明写入后的 `npm test`:435 个文件通过、10 个文件跳过;
|
||||
**5,127 项通过、74 项跳过,0 失败**,耗时 685.49 秒。
|
||||
- `npm run typecheck`、`npm run lint`:首轮与联合候选复验均通过。
|
||||
- `npm run release:notes:verify`:联合说明写入后通过;Desktop 三处版本一致,
|
||||
当前说明仅一项,Agent lock 与独立双语标题一致。
|
||||
- 合并后复验:发布说明校验、9 项发布说明测试、完整 typecheck、lint、
|
||||
`git diff --check` 通过。因合并仅恢复文档,未重复运行完整测试和 Host 探针。
|
||||
- 当前源码 Linux x64 完成/历史恢复探针通过:两轮 Ask 均完成,Runtime 重建后
|
||||
保留会话历史,每轮结束及最终退出时所属进程数均为 0。
|
||||
- 隔离旧安装清理探针通过:真实 CLI 回收所属孤儿进程、删除对应旧 payload,
|
||||
保留存活旧 daemon、活动工作、历史及 current/candidate,重复清理无重复删除。
|
||||
- 本轮新增真实模型调用 **2 次**,模拟模型调用 0 次;清理探针模型调用 0 次。
|
||||
- 两个探针从当前源码重新生成临时测试脚本,仅操作共享 Linux x64 Host 的独立
|
||||
`/root/tmp/gb-lifecycle-*` 与 `/root/tmp/gb-upgrade-*` 测试目录,不替换共享安装。
|
||||
探针沿用历史测试身份版本,不是正式签名 0.13.4 包安装验收,也不代替
|
||||
Linux arm64、macOS arm64 原生 CI 或完整 Host 更新界面验证。
|
||||
- 复现命令沿用[远程 Host 技术设计](../features/remote-host/technical-design.md)
|
||||
中 lifecycle-host 和 upgrade-cleanup 脚本;本轮生命周期参数为
|
||||
`driver --completion-only`,使用真实模型而非 `--fake-model`。
|
||||
- 候选主分支 CI、生产构建、六平台原生打包及公开发布验证:尚未执行。
|
||||
@@ -0,0 +1,40 @@
|
||||
# Desktop 0.13.15 恢复版准备
|
||||
|
||||
## 发布范围
|
||||
|
||||
- 以已发布的 `v0.13.13` 为用户更新基线,完整承接未发布 `v0.13.14` 的产品变更。
|
||||
历史准备及 Host 验证见 [0.13.14 记录](./release-preparation-0.13.14.md)。
|
||||
- 0.13.14 标签流程 `36165805814` 在测试阶段失败,生产构建、打包与发布均未执行;
|
||||
GitHub Release API 返回 404。不可变标签保留,包内条目改为 0.13.15,
|
||||
不保留重复的未发布说明。
|
||||
- Agent 0.13.4 流程 `36165805884` 成功,三个包已公开,最低 Desktop 要求仍为
|
||||
0.13.14。恢复版满足该最低要求;不重发 Agent、不更改其签名目录或标签。
|
||||
- 用户批准 0.13.15 标题及升级提示调整,其余双语正文沿用已批准内容;
|
||||
要求先推送候选,等 CI 全量验证与生产构建成功后再打标签。
|
||||
- 不构建 LoongArch,不运行本地生产构建、打包或安装包启动探针。
|
||||
- 中英文功能矩阵已复核,同步恢复版本和 Agent 已发布状态,保留其他未验收边界。
|
||||
|
||||
## CI 修复
|
||||
|
||||
`cd27163` 仅改测试和质量规范,不改变产品、Agent、数据库或外部契约:
|
||||
|
||||
- 原生能力清单测试控制第二次响应,等待重试已开始再展开控件,并分别校验响应前后状态。
|
||||
- 浏览器关闭测试在同一个 `waitFor` 中检查两次调用、页签移除和错误清除。
|
||||
- 笔记布局测试显式扫描 fixture 依赖,使用可见的隔离 Electron 窗口执行原生输入,
|
||||
保留 sandbox/contextIsolation,明确检查超时、信号和退出码后才读取结果。
|
||||
|
||||
对应 CI 证据:标签流程有 1 项失败(原生能力按钮未找到);
|
||||
main 流程 `36164698647` 有 2 项失败(关闭错误残留、布局测试约 90 秒后缺少结果文件)。
|
||||
两次失败点不同,不能仅凭本地通过认定 CI 已修复。
|
||||
|
||||
## 验证
|
||||
|
||||
- 本地三个定向用例通过,Electron 布局约 3.46 秒,全部几何和交互断言保留。
|
||||
- 修复提交完整 typecheck、lint 通过。
|
||||
- 本轮没有重复本地全量测试,以新候选 Linux CI 的完整测试结果为准;
|
||||
先前 5,127 项本地通过仅为历史记录。
|
||||
- 无新真实模型调用,未改变 Agent 或 Runtime 生产源码,无需重复 Host 开发探针。
|
||||
- 恢复候选的说明校验、9 项发布说明测试和 `git diff --check` 通过;
|
||||
三处桌面版本一致。逐项比较确认只替换版本及已批准升级提示,
|
||||
0.13.14 无重复条目,Agent lock 仍为 0.13.4。
|
||||
- 主分支 CI、生产构建和标签发布结果待后续核对。
|
||||
@@ -0,0 +1,43 @@
|
||||
# Desktop 0.13.16 发布准备
|
||||
|
||||
日期:2026-09-26。当前为候选,不代表已发布。
|
||||
|
||||
## 范围与批准
|
||||
|
||||
- 用户确认 Desktop 0.13.16、Agent 保持 0.13.4、不构建 LoongArch 预览。
|
||||
- 基线为 `325b702934ba0f0c076f27e926a897267262ab55`,用户明确批准纳入新提交的
|
||||
Supervisor 界面修改。相对已发布 `v0.13.15` 包含:
|
||||
- `e6ac073`:监督回顾持续分批处理,保留主动暂停、单次模型超时和并发。
|
||||
- `4c7193e`:结构升级不自动回收正常空闲页,准确显示操作状态及保留同启动重试。
|
||||
- `325b702`:监督回顾空态、运行状态与活动阶段布局修复。
|
||||
- 用户已批准中英文三项问题修复及两项升级提示,写入 `resources/release-notes.json`;
|
||||
已发布版本说明保留。最终候选 SHA 需另行确认,主分支 CI 通过后才创建新标签。
|
||||
- `FEATURES.md` 和 `FEATURES.zh-CN.md` 已核对并更新当前版本、持续回顾、
|
||||
结构升级及界面行为,删除过时的执行预算暂停描述。
|
||||
|
||||
## 数据与 Agent 边界
|
||||
|
||||
相对 0.13.15 不提高 schema,仍为 47。不新增持久化迁移标记,不改写用户数据库。
|
||||
旧格式仍执行必要转换;普通结构升级不因空闲页执行全库 VACUUM。
|
||||
监督回顾仍只读、无工具,模型调用可能收费;取消固定时长暂停后可连续处理更多批次。
|
||||
|
||||
本轮没有 Agent、Runtime、模型桥、部署锁或远程协议修改;本机与远程历史共用桌面升级
|
||||
实现。监督通过桌面 Ask 编排,不改变 Host 运行路径,不要求 Agent 更新或新 Host 部署。
|
||||
本轮实际模型请求数为 0。
|
||||
|
||||
## 验证
|
||||
|
||||
在独立工作树准备,避免其他工作修改影响候选,复用相同锁定依赖,未复制本机私有环境文件。
|
||||
候选第一轮全量发现 `HeartbeatCenter.test.tsx` 的两种语言仍要求已移除的重复说明存在;
|
||||
改为断言说明不存在,保留范围、操作、标题及页面内容校验,专项 **42 项通过**。
|
||||
|
||||
最终候选完整测试:**5,128 通过、84 跳过、1 失败**。唯一失败为未修改的
|
||||
`mermaid-viewer.electron.test.ts` 子进程退出码 null;首次单独复跑被工具的 150 秒
|
||||
上限中断,随后按用例原有时间窗口复跑 **1 项通过**,覆盖九种显示场景和十次实际 PNG
|
||||
导出。未修改该用例、提高其自身超时或弱化断言,独立复跑不替代全量结果。
|
||||
`npm run typecheck`、`npm run lint`、`npm run release:notes:verify` 均通过。
|
||||
|
||||
先前迁移定向 47 项通过;最终开发期全量为 5,137 通过、74 跳过、两项 Electron
|
||||
失败,失败用例独立复跑两项通过。该记录不是本候选的全量或 CI 通过证明。
|
||||
|
||||
未运行本地生产构建、打包或安装包启动。主分支 CI、六目标原生打包和公开发布核验待完成。
|
||||
@@ -0,0 +1,238 @@
|
||||
# Desktop 0.15.1 与 Agent 0.15.0 发布准备及恢复
|
||||
|
||||
日期:2026-09-27 至 2026-09-28。原 Desktop 0.15.0 标签构建失败,
|
||||
用户已选择 Desktop 0.15.1 与 Agent 0.15.0 恢复发布;验证通过不代表已发布。
|
||||
|
||||
## 范围与批准
|
||||
|
||||
- 用户要求 Desktop 与 GoodBuddy Agent 同步升级至 `0.15.0`,明确不构建 LoongArch。
|
||||
- 源码基线:`0f8bf405db7ca21a85cc70009ad083eb837e1e03`。
|
||||
- Desktop 差异基线为已发布的 `v0.13.16`,Agent 为 `agent-v0.13.4`。
|
||||
- `v0.15.0` 已创建并推送,保持不可变;恢复使用 `v0.15.1`,
|
||||
`agent-v0.15.0` 尚未创建或推送。
|
||||
- 用户已批准两份完整双语说明,正式正文仅维护在
|
||||
[Desktop 发布资源](../../resources/release-notes.json) 的 `0.15.1` 条目与
|
||||
[Agent 发布资源](../../resources/agent-release-notes/0.15.0.md)。
|
||||
已逐条核对写入文本与批准草案一致,本记录不再复制正文。
|
||||
- `FEATURES.md` 与 `FEATURES.zh-CN.md` 已核对本轮能力、限制和候选版本。
|
||||
- 用户要求两张未跟踪宣传截图保留原位、不提交,打标签前另行处理;
|
||||
不移动、删除、暂存或更改忽略规则。
|
||||
- 2026-09-28 用户更新授权:当前工作区的宣传文档与素材可以一起提交、推送,
|
||||
后续候选由代理自行审核确认,不再逐次等待用户批准,继续完成双产品正式发布。
|
||||
三张新增候选图已读取检查,保留原始草稿属性,不冒充正式发布截图或对外媒体发文。
|
||||
发布说明正文保持不变,`releasedAt` 更新为实际准备发布日期 2026-09-28。
|
||||
|
||||
## 本地验证
|
||||
|
||||
- `npm run typecheck`:通过。
|
||||
- `npm run lint`:通过。
|
||||
- `npm run release:notes:verify`:通过。
|
||||
- 发布说明相关三个文件定向测试:16 项通过。
|
||||
- 首轮 `npm test`:5,313 通过、79 跳过、4 失败,耗时 697.61 秒。
|
||||
三项失败来自当时尚未批准写入的 0.15.0 Desktop/Agent 发布说明;
|
||||
另一项为 `magic-notes-layout.electron.test.ts` 的布尔断言。
|
||||
该布局用例单独复验 1 项通过,未修改源码或放宽断言。
|
||||
单独通过不替代全量结果。
|
||||
- 正式说明写入后的最终 `npm test`:**5,317 通过、79 跳过、0 失败**,
|
||||
453 个测试文件通过、11 个跳过,耗时 645.49 秒。随后再次执行完整
|
||||
`npm run typecheck` 与 `npm run lint`,均通过。
|
||||
- 最终验证期间出现并行宣传文档编辑及新增 `storygraph.png`,均不属于本次
|
||||
发布准备改动,不暂存或覆盖。打标签前仍须处理工作区洁净条件。
|
||||
- 修改文档的相对文件链接、版本一致性和 `git diff --check` 通过。
|
||||
- 不运行本地生产构建、打包或安装包启动。准确候选提交须经用户批准,
|
||||
然后双远端推送、主分支 CI、生产构建、原生发布与公开核验分别进行。
|
||||
|
||||
## Agent 当前源码实机验证
|
||||
|
||||
共享 Linux x64 Host 的 LAN/VPN SSH 端口均可达,实际通过 LAN 与既有固定
|
||||
Host Key、加密凭据连接。重新捆绑当前源码的 Agent CLI、daemon、model bridge
|
||||
与 Desktop 适配器,仅上传独立 `/root/tmp/gb-lifecycle-*` 测试目录,复用已安装且
|
||||
版本锁未变的 Node/OpenCode/Continue,不替换原安装,不构建正式包。
|
||||
|
||||
最终 OpenCode、Continue 各通过一次 Ask 和一次 Execute,无工具请求,
|
||||
验证回答、后续历史、空闲 Runtime 回收及测试停止后所属进程为 0。
|
||||
模型请求时读取所属进程的固定遥测字段,确认 `DO_NOT_TRACK=1`、
|
||||
`OTEL_SDK_DISABLED=true` 及三个 exporter 均为 `none`,不输出整个环境。
|
||||
这些结果补齐 `eb53c82` 的 Agent/model bridge 真实链路验证,
|
||||
但不证明后台没有其他网络流量,也不替代三目标正式包验收。
|
||||
|
||||
首轮 Continue 探针沿用了 OpenCode 原生历史持久化假设,未像 Desktop 一样传入
|
||||
`request.history`,第二轮历史断言失败;当前 Continue 冷启动源码明确要求传入
|
||||
保存历史。修正测试输入及会话 ID 断言后通过,未修改产品实现。
|
||||
|
||||
有完整计数的实机运行共 **8 次**真实模型 HTTP 请求:首次 OpenCode 2 次、
|
||||
首次 Continue 2 次、最终 Continue 2 次、最终 OpenCode 2 次。
|
||||
此前一次直接启动 Electron 的探针未捕获子进程输出,不能确认是否实际发出请求,
|
||||
该次上限 2 次,不纳入通过证据;因此不能把 8 次写成本轮精确总数,总量为 8–10 次。
|
||||
后续均使用等待退出并记录脱敏输出的启动器;本轮探针 Electron 进程已退出。
|
||||
本地诊断证据在临时 `opencode/release-015-host/`,不提交脚本、日志或凭据。
|
||||
|
||||
## 首次候选 CI 与测试修正
|
||||
|
||||
用户批准的首次候选 `5a6ba6c14f5c58f3da5e9afe281b18e0cbdb1ae8`
|
||||
已推送至 `origin/main` 和 `github/main`,未创建两个发布标签。
|
||||
|
||||
- [Agent CI 36329271991](https://github.com/mesalogo/goodbuddy/actions/runs/36329271991)
|
||||
源码验证及 Linux x64、Linux arm64、Darwin arm64 原生构建全部通过。
|
||||
- [Desktop CI 36329271967](https://github.com/mesalogo/goodbuddy/actions/runs/36329271967)
|
||||
为 5,342 通过、51 跳过、3 失败。两个 App 长历史场景超过默认 5 秒单用例时限;
|
||||
会话采集 Electron 场景等待 Escape 返回笔记列表超时。生产构建未运行。
|
||||
- 测试修正仅让原生键盘辅助函数显示窗口、获取焦点并等待 `document.hasFocus()`,
|
||||
然后发送真实输入;两个长历史场景采用仓库既有的 15 秒单用例时限。
|
||||
不修改产品实现、不跳过 Linux、不关闭沙箱、不删除或放宽数据和键盘断言。
|
||||
- Windows 聚焦复验三个失败场景通过,其中真实 Electron 覆盖完整 502 条历史、
|
||||
214px 窄栏键盘操作、浅深主题确认按钮、真实 IPC/SQLite 和重开保留。
|
||||
修正后首次全量复验被中断,不计为通过;随后完整重跑为 **5,317 通过、
|
||||
79 跳过、0 失败**,453 个文件通过、11 个跳过,耗时 646.48 秒。
|
||||
类型检查、lint 和发布说明校验均通过,新候选 Linux CI 仍需独立通过。
|
||||
- 本次只改测试和验证文档,不改变 Agent、远程 Runtime 或模型桥;
|
||||
沿用上方当前源码 Host 验证,不新增真实模型请求。
|
||||
- 新提交必须重新取得准确 SHA 批准,原发布说明正文保持不变。
|
||||
|
||||
## Linux 实机复现与最终同步修正
|
||||
|
||||
2026-09-28:第二次批准候选 `12594961fe1e814e89f55de1fbfe245310505115`
|
||||
的 [Desktop CI 36331853137](https://github.com/mesalogo/goodbuddy/actions/runs/36331853137)
|
||||
为 5,343 通过、51 跳过、2 失败。两个 App 长历史场景已通过;
|
||||
Escape 返回列表仍失败,活动历史迁移这次超过默认 5 秒时限,生产构建仍未执行。
|
||||
|
||||
按用户要求直接在共享 Linux x64 Host 测试准确候选源码。使用独立磁盘测试目录、
|
||||
锁定 Node 24.19.0 和 `npm ci`,补齐 Xvfb 与匹配的 X server 系统依赖;
|
||||
没有创建新账号,使用既有非 root 账号运行测试,保留 Electron 沙箱及其标准 helper 权限。
|
||||
不替换既有 Agent,不修改其安装或工作区,不执行生产构建或打包。
|
||||
|
||||
原测试在 Host 上稳定复现:缩窄窗口会按产品既有行为收起工作栏,隐藏面板仍有非零宽度。
|
||||
诊断显示 Escape 实际发给 `aria-expanded=false` 的工作栏开关,而不是笔记输入框。
|
||||
因此仅获取窗口焦点不够;测试改为等待窄视口及响应式渲染完成,按真实按钮打开工作栏,
|
||||
确认展开状态和目标输入框焦点后再发送键盘输入。临时诊断代码已移除。
|
||||
|
||||
迁移测试在 Host 上以原默认时限通过,保留 10,000 条记录、完整迁移、WAL 字节比、
|
||||
扫描次数与无变化写入断言;仅为这一个大数据场景采用已有的 15 秒单用例时限,
|
||||
不改数据库实现或全局时限。
|
||||
|
||||
- 最终 Linux Host 完整 `npm test`:**5,345 通过、51 跳过、0 失败**,
|
||||
449 个文件通过、15 个跳过,耗时 751.09 秒。
|
||||
- Windows 聚焦回归:3 项通过,包含真实 Electron 和迁移。
|
||||
- Windows 完整类型检查、lint、发布说明校验:全部通过。
|
||||
- Linux Electron 实测覆盖 502 条历史、218px 窄栏、Escape/Tab/Enter、
|
||||
浅深主题确认按钮、真实 IPC/SQLite 与重开保留;Windows 对应窄栏为约 214px。
|
||||
- 本阶段不新增真实模型请求,不改变产品代码或双语说明。
|
||||
新候选仍须重新批准 SHA 并通过自己的主分支 CI/生产构建,两个标签仍未创建。
|
||||
|
||||
## 后续 CI 焦点前提与确定性取消验证
|
||||
|
||||
`b8b6007` 的 [CI 36335076841](https://github.com/mesalogo/goodbuddy/actions/runs/36335076841)
|
||||
两次执行均为 5,344 通过、51 跳过、1 失败,失败点改为 `new title focus`,
|
||||
生产构建没有运行。该次失败未在 Host 的 10 次带诊断复验中重现,其中 5 次限制单 CPU;
|
||||
移除临时诊断后再测也通过。这些独立结果不替代 CI。
|
||||
|
||||
测试原先直到键盘阶段才显示窗口,前面的自动聚焦检查仍在隐藏窗口执行。
|
||||
现让测试窗口从开始即显示并确认焦点,保留标题自动聚焦原断言;
|
||||
超时增加实际焦点、标题禁用/可见/隔离状态和保存状态,避免只报告阶段名称。
|
||||
可见窗口版本在 Host 连续五次聚焦复验通过。
|
||||
|
||||
随后 Host 全量出现一个独立的 Continue 取消测试竞争:
|
||||
`setTimeout(0)` 没有保证取消发生在子进程启动前,空 mock 已被调用。
|
||||
用例改在既有启动环境回调处精确取消,仍验证回调执行、子进程未启动和临时配置删除,
|
||||
不更改 Continue 产品实现或取消机制。
|
||||
|
||||
- 最终 Host 完整测试:**5,345 通过、51 跳过、0 失败**,449 个文件通过、
|
||||
15 个跳过,耗时 743.34 秒。
|
||||
- Windows 可见窗口 Electron 回归及确定性取消回归通过。
|
||||
- 完整类型检查、lint 通过;双语说明正文不变,日期调整后另行验证发布资源。
|
||||
- 上述均为测试或验证文档调整,不新增真实模型调用,不改变 Agent 发布输入。
|
||||
正式发布仍要求当前候选自己的 CI/生产构建通过。
|
||||
|
||||
## 完整分支快照重新核对
|
||||
|
||||
`d9e1e42` 的 [Desktop CI 36340682594](https://github.com/mesalogo/goodbuddy/actions/runs/36340682594)
|
||||
已通过原生会话采集焦点测试,但为 5,367 通过、51 跳过、4 失败。
|
||||
核对祖先后确认准备期间另有四个并行提交进入分支:
|
||||
`ee1bf8a` 输入区控件样式、`8e60736` 流式会话稳定排序、
|
||||
`f35198d` 桌面通知开关、`d47f967` 监督结果继续讨论。
|
||||
它们已随用户授权的当前分支推送;前述 Host 的 5,345 项通过使用较早源码快照,
|
||||
不能作为包含这四项的新候选验证结果。
|
||||
|
||||
本轮已重新核对四个提交的实现和文档,补充两份功能矩阵与双语发布说明:
|
||||
通知开关默认开启且不影响应用内提示;流式更新不改变排序;
|
||||
监督结果可直接编辑问题并继续讨论,界面不再提供知识实体写回控件。
|
||||
Agent 产品源码及 Runtime 锁未因这四项改变,Agent
|
||||
[CI 36340682607](https://github.com/mesalogo/goodbuddy/actions/runs/36340682607)
|
||||
源码校验和三个原生构建全部通过。
|
||||
|
||||
补齐通知默认值的契约 fixture;Obsidian 多次真实子进程调用使用单用例 15 秒时限,
|
||||
避免前项超时后的在途调用污染下一项取消断言;笔记来源用例按面板查询控件,
|
||||
并等前次来源操作释放禁用状态后再发起下一次。
|
||||
这些修正不改变产品实现。Windows 聚焦回归 19 项通过,类型、lint 和说明校验通过。
|
||||
从 Git tree `1758ff58a6ff33fcf7ddaa3d35dd2fcc57cd1265` 导出完整快照,
|
||||
覆盖 Host 测试源码,而非只同步早期快照中的少数文件。
|
||||
该完整快照 `npm test` 为 **5,371 通过、51 跳过、0 失败**,
|
||||
451 个文件通过、15 个跳过,耗时 751.42 秒,包含全部四个并行提交。
|
||||
最终提交前再次核对源码、测试、依赖和发布资源与该已验证 tree 一致,
|
||||
仅追加本段验证结果;完整候选仍需自己的主分支 CI 与生产构建通过。
|
||||
|
||||
### 完整候选后的焦点同步修正
|
||||
|
||||
`5cdffee` 的 Agent [CI 36343184917](https://github.com/mesalogo/goodbuddy/actions/runs/36343184917)
|
||||
三个原生构建和源码校验通过;桌面
|
||||
[CI 36343184916](https://github.com/mesalogo/goodbuddy/actions/runs/36343184916)
|
||||
为 5,369 通过、51 跳过、2 失败,生产构建未执行。
|
||||
会话采集的焦点等待可被 `body` 后代文字误满足,菜单随后被真正的消息聚焦关闭;
|
||||
另一个原生 Runtime 控件用例在启动完成前打开失焦关闭的菜单。
|
||||
本轮收紧消息焦点的元素断言,并等待项目能力请求及启动渲染后再打开 Runtime 选项。
|
||||
不重试菜单、不跳过场景、不改产品实现。
|
||||
|
||||
完整冻结 tree `2ddc0da69b6bcb42d8272c44dd11e3e6904d0438` 在 Host 全量
|
||||
**5,371 通过、51 跳过、0 失败**,耗时 748.21 秒。
|
||||
Windows 原生 Runtime 聚焦用例、类型检查和 lint 通过。
|
||||
提交仅追加本段记录;仍须等待修正候选的主分支 CI 和生产构建。
|
||||
|
||||
`706726b` 的桌面 [CI 36345672365](https://github.com/mesalogo/goodbuddy/actions/runs/36345672365)
|
||||
已通过上述采集与 Runtime 用例,但另一个外部知识库测试失败:
|
||||
5,370 通过、51 跳过、1 失败,生产构建未执行。
|
||||
该 fixture 只在查询新库 ID 时返回包含已创建库的快照,普通轮询会返回创建前数据;
|
||||
同时错误地要求最后一次请求必须属于新库,忽略后台轮询。
|
||||
改为模拟已持久化的库列表,等待新库请求发生,保留原库与新增库选中、被排除库未选中的
|
||||
全部界面断言,不修改产品代码。Windows 完整 App 300 项通过,类型、lint 和说明校验通过。
|
||||
冻结 tree `42a92450ad7d9ebc50167c7946bff53cff2889e6` 的 Linux 全量为
|
||||
**5,371 通过、51 跳过、0 失败**,耗时 748.67 秒。
|
||||
|
||||
## Desktop 0.15.1 恢复
|
||||
|
||||
`65a21390debe0dd04871417a363a9d87a29339e6` 的主分支
|
||||
[CI 36347785076](https://github.com/mesalogo/goodbuddy/actions/runs/36347785076)
|
||||
验证与生产构建全部通过后,两个远端创建了不可变 `v0.15.0`。
|
||||
其正式 [CI 36348938269](https://github.com/mesalogo/goodbuddy/actions/runs/36348938269)
|
||||
两个 macOS 目标在 `codesign --verify --deep --strict` 失败,
|
||||
报 `invalid destination for symbolic link in bundle`;GitHub Release 未发布。
|
||||
|
||||
Host 隔离 fixture 经实际 DS 资源准备和 electron-builder 文件复制,
|
||||
复现相对 npm CLI 链接变成原构建目录的绝对链接。不是签名凭据或测试超时问题。
|
||||
用户明确选择 **Desktop 0.15.1 + Agent 0.15.0** 恢复,不移动或复用旧标签。
|
||||
修正仅让复制保留相对链接;新增回归移除原安装与暂存目录后验证最终链接仍可读。
|
||||
该路径不用于 Agent,远程 Runtime 与 Agent 源码未改,不增加真实模型调用。
|
||||
未发布 Desktop 0.15.0 的双语条目迁移到 0.15.1,保留全部功能说明;
|
||||
Agent 版本不变,其包元数据从新版 Desktop manifest 派生最低版本 0.15.1,
|
||||
两份说明和功能矩阵同步这个升级顺序。
|
||||
|
||||
原标签最终 Windows/Linux 四个目标成功,两个 macOS 失败,发布任务跳过,
|
||||
公开 Latest 仍为 `v0.13.16`。
|
||||
Windows 打包相关测试 45 通过、1 个 POSIX 链接用例跳过,类型、lint、
|
||||
发布说明校验通过。冻结 tree `99d3271174a68922fbf0a1a4cfbadbb85a6067c8`
|
||||
在 Linux Host 全量 **5,372 通过、51 跳过、0 失败**,耗时 745.88 秒;
|
||||
新增符号链接回归实际执行通过。提交仅追加验证记录,恢复候选 CI 与 macOS 签名待验证。
|
||||
|
||||
## 数据、兼容与验收边界
|
||||
|
||||
Desktop schema 48 新增魔法笔记会话来源元数据。升级前应备份完整用户数据,
|
||||
包括数据库及相邻 `notes/` 目录;旧客户端不能打开升级后的数据库。
|
||||
本次发布准备不修改数据库迁移实现,也不直接改写用户数据库。
|
||||
|
||||
Agent 发布工作流以候选 `package.json` 设置最低 Desktop 版本,因此
|
||||
Agent 0.15.0 包要求 Desktop 0.15.1;Node、OpenCode、Continue 锁不变。
|
||||
Agent 与 Desktop 独立发布,Agent 不替代 Desktop Latest。
|
||||
|
||||
原生客户端的远程 coordinator、托管标准 Node 交付整合、Continue/OpenCode
|
||||
Ask 内置 MCP 映射及完整跨平台安装包验收仍有缺口,见
|
||||
[原生客户端进度](../features/assistant-workbar/progress.md#2026-09-27-local-native-client-validation)。
|
||||
本次说明明确标为本地预览,不将这些未完成部分宣称为已提供能力。
|
||||
@@ -0,0 +1,48 @@
|
||||
# Desktop 0.15.2 与 Agent 0.15.0 发布准备
|
||||
|
||||
日期:2026-09-28。用户要求包含当前本地工作、推送双远端,等待候选 CI 和生产构建通过后打标签;
|
||||
确认同时发布 Agent 0.15.0,不构建 LoongArch。沿用此前自行审核候选和双语说明的授权。
|
||||
|
||||
## 发布范围
|
||||
|
||||
- Desktop 基线为已发布的 `v0.15.1`(`4e5239256d42c856bb1420d87c4a338f17867b80`)。
|
||||
[发布 CI 36368878429](https://github.com/mesalogo/goodbuddy/actions/runs/36368878429)
|
||||
六个原生目标和 GitHub/OSS 发布全部成功,0.15.1 的历史说明保留。
|
||||
- 新源码截止 `66f77b83c2b6ebe0018a6c5c2bc72e1b4c1e43b1`:
|
||||
renderer 退出诊断、笔记采集界面、双语网站及 Story Graph 只读工具与完整讨论草稿。
|
||||
- Agent 0.15.0 尚未发布,基线仍为 `agent-v0.13.4`。
|
||||
既有遥测和 Continue 更新检查修复保留,补充远程 Story Graph 工具;
|
||||
最低 Desktop 版本由当前 manifest 派生为 0.15.2。三个原生目标和 Runtime 锁不变。
|
||||
- 当前未提交的功能矩阵、主机/云环境设计和宣传文稿一并审核纳入。
|
||||
云平台适配与主机入口迁移明确为待实现规划,不进入发布功能说明。
|
||||
宣传稿保持草稿和原核查截止点,不代表已对外发布。
|
||||
- 正式双语正文仅维护在 `resources/release-notes.json` 的 0.15.2 条目和
|
||||
`resources/agent-release-notes/0.15.0.md`。两份 FEATURES 已对照实现与规划核对。
|
||||
|
||||
## 验证边界
|
||||
|
||||
Story Graph 已有开发期证据见
|
||||
[监督者验证进度](../features/conversation-supervision/progress.md#2026-09-28-story-graph-只读工具):
|
||||
真实 Linux Host 上的 OpenCode/Continue Ask、Execute 调用与禁用检查通过;
|
||||
推理为本机确定性响应,10 次请求含 2 次标题生成,外部付费请求 0 次。
|
||||
不将这些证据称为真实模型建议质量验证,也不宣称历史重建、跨范围事实合并或大库性能已验收。
|
||||
|
||||
本轮类型、lint、双语说明及网站静态校验通过。按当前源码重建隔离 Agent 探针,
|
||||
在共享 Linux x64 Host 经固定身份完成 OpenCode/Continue Ask、Execute、空闲进程回收、
|
||||
冷启动历史恢复和遥测关闭环境检查;每个 Runtime 2 次真实模型请求,共 **4 次**,
|
||||
没有重试或假模型调用,结束时归属进程均为 0。该补充检查不等于真实模型图谱建议质量验收。
|
||||
|
||||
准备期间另有 `0cdf042` 宣传稿提交,已核对只有三份文档变化,产品源码仍截止 `66f77b8`,
|
||||
一并纳入完整候选。用户随后中断本地全量步骤,明确要求直接推送等待 CI;
|
||||
本地全量不记为通过,由精确候选的主分支 CI 执行完整测试和生产构建,通过后才打标签。
|
||||
不运行本地生产构建、安装包打包或安装包启动;使用既有隔离 Host 目录,不更改生产 Agent。
|
||||
schema 保持 48;从旧版升级应备份完整数据库和相邻 `notes/`,不能用旧客户端打开升级后的库。
|
||||
|
||||
## 首次候选 CI
|
||||
|
||||
`76e781a` 已同步双远端。Agent [CI 36379713606](https://github.com/mesalogo/goodbuddy/actions/runs/36379713606)
|
||||
通过;Desktop [CI 36379713607](https://github.com/mesalogo/goodbuddy/actions/runs/36379713607)
|
||||
5,389 通过、51 跳过、1 失败,生产构建未执行,未打标签。
|
||||
失败的设置测试用固定五个服务器名称过滤按钮,却用新增 Story Graph 后的六项目录断言数量。
|
||||
同步把旧模拟列表与按钮身份断言改为复用正式目录,保留数量和原交互断言,并逐项验证目录按钮存在。
|
||||
最终完整 SettingsPanel 文件、类型、lint 和发布说明检查通过;全量继续交由新提交 CI。
|
||||
@@ -0,0 +1,36 @@
|
||||
# Desktop 0.15.3 发布准备
|
||||
|
||||
日期:2026-09-28。用户确认沿用双远端推送、精确提交 CI 和生产构建通过后自动打标签的流程;
|
||||
本次仅发布 Desktop 0.15.3,已发布 Agent 0.15.0 保持不变,不构建 LoongArch。
|
||||
|
||||
## 范围
|
||||
|
||||
- 已发布基线:`v0.15.2`,提交 `47ba54f47db4f22f69375310925eedab020b3cf0`。
|
||||
- 本轮已核对 15 个提交,截止 `86347af0b1cc8726c1166b3f9fa3d7f12a0cc5e3`。
|
||||
包含手动压缩、请求级工具授权、取消/失败状态、聊天/笔记重复工作、
|
||||
侧栏与工作栏布局、打包依赖精简。起始工作区干净。
|
||||
- Office 同会话实时编辑仅更新需求与验收设计,不计入交付能力。
|
||||
- `package.json`、lockfile 两处 Desktop 版本统一为 0.15.3。
|
||||
已发布 0.15.2 与 Agent 0.15.0 的正文保持不变;新双语说明单独追加。
|
||||
- 两份 FEATURES 对照实现更新手动压缩、状态反馈、搜索、箭头方向及发布状态。
|
||||
不用局部 Markdown 基准宣称整体渲染性能提升,不提前承诺跨平台安装包缩小比例。
|
||||
|
||||
## Agent 与验证边界
|
||||
|
||||
`src/agent-daemon`、`src/main/remote-agent`、Agent 和 Runtime 锁在本轮未变。
|
||||
授权修复属于 Desktop 的 `KnowledgeMcpGateway`:请求有效期间不再按固定十分钟撤销,
|
||||
取消和清理继续撤销;不修改远程工具通道或部署包,不需要重发 Agent 0.15.0。
|
||||
手动直连压缩仅用于本地文本会话,UI 与 IPC 均排除 SSH/远程直连入口。
|
||||
|
||||
开发期证据分别见[直连模型进度](../features/direct-model-agent/progress.md)、
|
||||
[导航进度](../features/application-tool-navigation/progress.md)和
|
||||
[性能基准](../roadmap/product-performance-experience-improvement-plan.md)。
|
||||
开发期全量曾报告侧栏和笔记 Electron 用例失败,不把这些历史结果计为本候选通过。
|
||||
本轮 `npm run typecheck`、`npm run lint`、`npm run release:notes:verify` 通过。
|
||||
网关、Obsidian 授权、桌面资源与 DS 打包、手动压缩真实 Electron 生产链路共 6 文件:
|
||||
**85 通过、1 跳过**。跳过的是 Windows 不适用的 POSIX 符号链接用例。
|
||||
压缩测试使用本机 HTTP fixture,不冒充外部模型调用;本轮没有新增付费模型请求。
|
||||
沿用用户确认的直接推送等待 CI 流程,完整测试及生产构建以精确候选 CI 为发布门槛。
|
||||
不进行本地生产构建、打包或安装包启动。
|
||||
|
||||
schema 保持 48;从更早版本升级前备份完整数据库及相邻 `notes/` 目录。
|
||||
@@ -0,0 +1,55 @@
|
||||
# Desktop 0.15.4 与 Agent 0.15.1 发布准备
|
||||
|
||||
日期:2026-09-29。用户确认两个补丁版本,提交并推送两个远端;
|
||||
候选 CI 和生产构建成功后自动打标签发布,不构建 LoongArch。
|
||||
|
||||
## 范围与说明
|
||||
|
||||
- Desktop 已发布基线为 `v0.15.3`(`3c9229ac7a57071fc955adb1095c499ffdfd006a`)。
|
||||
本轮源码截止 `9c88ca20f3c62ca29b9aca179dc191b460db37d8`,开始时工作区干净。
|
||||
- 三个提交包含系统任务 Token 统计、Runtime/模型分层和宣传稿归档。
|
||||
正式功能说明以实现为准;用途模型和旧全局 provider 移除仍为规划。
|
||||
- Agent 基线为已发布 `agent-v0.15.0`。本轮 Continue helper 识别首次历史封装、
|
||||
去除保存消息中的工作模式前缀,因此发布新 Agent 0.15.1,不覆盖旧产物。
|
||||
Node 24.19.0、OpenCode 1.18.29、Continue 1.5.47 和协议锁保持不变。
|
||||
- 正文仅维护在 Desktop 0.15.4 JSON 条目及 Agent 0.15.1 Markdown;
|
||||
已发布版本正文保留。FEATURES 中修正旧的简化选择描述,并同步用量、版本和限制。
|
||||
|
||||
## 数据与兼容
|
||||
|
||||
schema 49 在既有 SQLite 事务中迁移项目与会话选择,不引入额外恢复系统。
|
||||
已读迁移指南并核对合成 schema 48 数据迁移回归;本轮不改写用户原库。
|
||||
关闭应用后备份完整数据库及相邻 `notes/`,旧客户端不能打开升级后的库,
|
||||
降级需要恢复完整升级前备份。远程先升级 Desktop 0.15.4,再更新 Agent 0.15.1。
|
||||
统计无法补算历史未记录的调用或缺失的供应商 usage。
|
||||
|
||||
## 验证
|
||||
|
||||
- `npm run typecheck`、`npm run lint`、`npm run release:notes:verify` 通过。
|
||||
- 10 个聚焦文件、221 项通过:包含数据库迁移/用量分类、选择解析、选择器、
|
||||
Activity UI、后台用量记录和 Continue helper。
|
||||
- 当前源码隔离 Agent 在共享 Linux x64 Host 验证 OpenCode 与 Continue Ask、Execute、
|
||||
空闲进程回收及冷启动历史恢复。成功场景每个 Runtime 两次真实模型请求。
|
||||
OpenCode 原模型连接前三次尝试未完成,第三次脱敏诊断明确为上游 `ECONNRESET`;
|
||||
随后测试显式使用 Continue 已配置且成功的模型连接,未修改用户设置或产品回退规则。
|
||||
本轮共 7 次上游请求分发尝试,4 次成功、3 次未完成;不推断失败尝试的计费情况。
|
||||
所有探针结束时归属进程为 0,用户设置只在临时加密副本中读取,测试副本已清理。
|
||||
- 既有开发记录缺少完整 UI 目测及全仓通过结果,保留边界,不将局部通过冒充全量。
|
||||
完整测试与生产构建交由精确提交 CI;不执行本地生产构建、打包或安装包启动。
|
||||
|
||||
## 首轮候选 CI
|
||||
|
||||
`ed47e2c` 已同步两个远端。Agent `36580699297` 三平台验证通过。
|
||||
Desktop `36580699255` 为 5,472 通过、52 跳过、1 失败,生产构建未执行,
|
||||
尚未创建任何本轮发布标签。问答测试在 Runtime 就绪前点击仍禁用的发送按钮,
|
||||
没有产生首个请求;按现有异步同步机制等待按钮可用,保留全部问答与重载断言。
|
||||
该修正仅影响测试和发布记录,运行时源码、锁文件及双语发布说明不变。
|
||||
修正后完整 `App.test.tsx` 314 项通过,类型检查与 lint 再次通过。
|
||||
|
||||
## 发布前界面修正
|
||||
|
||||
用户要求移除会话 Runtime 按钮图标与名称间的圆点,并确认继续提交、推送和发布。
|
||||
删除专用覆盖标记 class 及其伪元素样式,保留菜单内来源提示和恢复默认操作,
|
||||
同步双语说明及选择设计。完整 App 与样式回归共 351 项通过,类型检查和 lint 通过。
|
||||
该改动仅在 Renderer,不影响 Agent 或桌面至 Agent 协议,无需新增真实模型请求。
|
||||
此前中断的测试不计为通过;本条结果来自重新执行。最终新提交仍须等待独立 CI。
|
||||
@@ -0,0 +1,74 @@
|
||||
# Desktop 0.15.5 发布准备
|
||||
|
||||
日期:2026-09-30。用户授权按新增改动确定版本、提交推送两个远端,
|
||||
CI 与生产构建通过后自动打标签发布;本轮不构建 LoongArch。
|
||||
|
||||
## 范围
|
||||
|
||||
已发布基线 `v0.15.4`:`a662b16f5e076f67788457653d29c0d2fbacdab0`。
|
||||
初始工作区干净,产品源码截止 `2d8e58e995c4ac2e3d885b028e588d49d06f05cf`。
|
||||
四个提交分别为取消轮次历史、执行清单浮层、智能心跳增量回顾与监督建议、
|
||||
笔记保存后退出编辑。双语正文维护在 `resources/release-notes.json`;
|
||||
已发布正文保留,两个 FEATURES 同步当前能力和剩余边界。
|
||||
|
||||
Agent 保持 0.15.1,不发布新标签或修改其历史说明。Agent daemon、协议实现和
|
||||
Runtime 锁没有变化;共享合同新增的是桌面监督配置/API,历史构建在 Renderer/Main
|
||||
进行,远程继续通过现有 history 字段传输。因桌面至 Agent 历史输入改变,
|
||||
仍对当前源码完成真实 Host 取消后续聊验证。
|
||||
|
||||
## 数据与行为边界
|
||||
|
||||
schema 50 在既有事务中增加心跳介入方式、建议状态和建议表;旧报告中仍 proposed
|
||||
的记忆迁为 pending 约定,不确认或删除原记录。已读迁移指南并核对 49→50 回归;
|
||||
没有读取或升级用户原库,也不声明大规模真实旧库性能已验证。
|
||||
升级前关闭应用,备份完整数据库及相邻 `notes/`;降级需恢复完整升级前备份。
|
||||
|
||||
手动回顾默认增量,显式重新整理不推进或重置共享进度。建议以本次结果中的依据为准,
|
||||
有候选才进行额外模型调用;项目与全局分别维护进度,会话级实时介入仍未实现。
|
||||
本地 OpenCode 的取消会话标记不等同于 Agent 实现;远程使用共享桌面历史输入与
|
||||
原有取消/会话恢复路径,不声称复用了本地 OpenCode 类。
|
||||
|
||||
## 验证记录
|
||||
|
||||
- 类型检查和 lint 通过。
|
||||
- 首轮定向 34 文件:1092 通过、1 跳过、1 失败。会话合并用例只等待 Runtime
|
||||
mock 调用便触发新会话,单独复跑通过;补充等待已发送消息可见,保留两次同批
|
||||
新建请求和最终会话数量断言。最终 34 文件全部通过,1093 项通过、1 项既有条件跳过;
|
||||
类型检查、lint、双语发布说明及差异检查再次通过。
|
||||
- 当前源码在共享 Linux x64 Host:OpenCode、Continue 各在 Ask 输出后主动取消,
|
||||
用生产 `buildRuntimeHistory` 生成中断历史,再以 Execute 发送无工具的新问题。
|
||||
两条路径均只回答新问题,未续写原列表;归属进程最终为 0。
|
||||
本轮恰好 4 次真实上游请求,全部 HTTP 200,无重试或假模型;用户设置只读取
|
||||
临时加密副本,未修改正式 Agent 安装和工作目录。
|
||||
- 智能心跳开发记录中的 4 次 DeepSeek 请求是该功能开发证据,不计入本轮新增调用。
|
||||
参见[实施记录](../features/conversation-supervision/progress.md)。
|
||||
- 不执行本地生产构建、打包或安装包启动。全仓验证和生产构建以最终提交的 CI
|
||||
为准,标签只能在该门槛通过后创建。
|
||||
|
||||
## 中断后扩展候选
|
||||
|
||||
用户新增四个提交并授权再次推送继续发布;源码范围扩至
|
||||
`6edfc0046b83150f2027167352000f161fb0dd0a`,恢复时工作区干净。
|
||||
新增 OpenCode 终端依赖缓存、短 MCP 名称与并行占用、工作区固定工具栏、
|
||||
笔记双栏布局及手动标签。说明和功能矩阵已同步,目标 schema 改为 51。
|
||||
标签支持多选 AND 筛选、重命名/合并/删除及桌面笔记工具字段,非自动分类;
|
||||
Agent daemon 和协议不变,Agent 仍为 0.15.1。
|
||||
|
||||
上一候选 `925d410` 的 Agent CI 通过,Desktop `36664224641` 为 5481 通过、
|
||||
55 跳过、3 失败,生产构建未执行,未创建标签。两个笔记分析用例的旧编辑按钮
|
||||
假设已由新提交修正;新源码真实 Electron 复验已通过。监督布局仍查找旧记忆建议
|
||||
标题及已移除的心跳运行审计,修正为监督建议区域和两条建议断言、审计不重复展示,
|
||||
保留页签、宽度、历史报告和导航检查。
|
||||
|
||||
扩展候选复验:15 文件、502 项通过,包含上述三个失败场景、笔记标签与布局、
|
||||
工作区滚动布局、schema 51 数据库、MCP 网关/名称及原生终端缓存测试;
|
||||
类型检查、lint、双语说明校验和差异检查通过。该轮测试使用隔离 fixture,
|
||||
没有新增付费模型调用;先前 4 次真实 Host 请求仅对应取消后续聊范围,
|
||||
不作为新增标签工具的完整远程 UI 实测声明。
|
||||
|
||||
`89fc100` 的 Agent CI 通过;Desktop `36707706849` 为 5507 通过、55 跳过、
|
||||
2 失败,前述三个失败场景均已通过。笔记滚动在请求完成回调中早于 DOM 提交,
|
||||
以立即执行动画帧的回归稳定复现后,改在布局 Effect 中定位已挂载记录。
|
||||
手动压缩独立复跑通过,夹具同步当前分层选择格式并等待目标会话历史显示;
|
||||
尚不将独立通过视为 Linux 全仓通过。修正后完整 App/笔记组件及真实 Electron
|
||||
导航/压缩共 450 项通过,类型检查和 lint 通过,无新增外部模型调用。
|
||||
@@ -0,0 +1,70 @@
|
||||
# Desktop 0.15.6 发布准备
|
||||
|
||||
日期:2026-10-01。用户授权提交当前未提交改动并继续双远端推送与发布,
|
||||
如 Agent 实际受影响可升补丁版本;另明确本轮仅标准六平台,不含 LoongArch。
|
||||
|
||||
## 候选范围
|
||||
|
||||
基线 `v0.15.5` 为 `3be55348a215f4343326b1950a69529247cbae6a`,
|
||||
六个新增提交截止 `9cc5dc124464a67ed93014c53cbc93f96764caa2`。
|
||||
初始未提交改动仅 `application-center.css` 一行选择器:
|
||||
侧栏底部仅对第二个直接子元素文字 span 禁止伸缩,避免影响其他 span。
|
||||
已核对并纳入本轮,未包含凭据、日志或临时产物。
|
||||
|
||||
新增内容包括磨砂外观、输入/缓存会话切换渲染优化、直接图片预览、设置与笔记布局、
|
||||
本地 MCP 恢复及不确定名称退役、诊断和执行统计索引。
|
||||
双语说明和功能矩阵按实现更新,不将已有开发记录中的全量失败记为成功。
|
||||
|
||||
Agent 保持 0.15.1:相对其发布标签,daemon 与锁文件无变化。
|
||||
本地 OpenCode 的 HTTP 注册恢复属于 `OpenCodeRuntime`,远端使用
|
||||
`RuntimeAcpBackend`/`AgentImageToolMcp`,不经过这条 HTTP 注册路径。
|
||||
没有据此声称远程恢复得到同样改进,也没有新发布 Agent 包。
|
||||
|
||||
## 数据与验证
|
||||
|
||||
已读数据库迁移指南。schema 52 在事务内增加任务事件部分索引,不转换消息正文,
|
||||
不执行 VACUUM;建立索引的耗时受历史规模影响,本轮未测真实大库升级。
|
||||
升级前关闭应用并备份数据库和相邻 `notes/`,降级需要完整升级前备份。
|
||||
|
||||
- 当前源码 17 个定向文件、886 项通过,包含真实 OpenCode + HTTP 网关合成服务的
|
||||
MCP 故障恢复和晚到初始化回归,数据库/统计、诊断、设置、App、图片附件和真实
|
||||
Electron 磨砂、笔记布局、工作栏设置布局。
|
||||
- `npm run typecheck`、`npm run lint` 通过;本轮新增付费模型调用 0 次。
|
||||
- 完整测试和生产构建等待精确候选 CI,不运行本地生产构建、打包或安装包探针。
|
||||
|
||||
## 首轮 CI
|
||||
|
||||
候选 `cc451f6` 已含用户未提交样式修正并同步两个远端。Agent `36743915140`
|
||||
通过,Desktop `36743915207` 为 5573 通过、57 跳过、1 失败,生产构建未执行。
|
||||
磨砂测试切换主题后立即读取样式,窗口按钮 color 正在过渡,导致深色关闭态与
|
||||
先前基线不同。测试改为等待当前 CSS Transition 完成,保留完整样式、几何、
|
||||
拖拽、对比度及原生交互断言,不修改产品外观或全局超时。
|
||||
修正后完整磨砂 Electron 回归、类型检查及 lint 通过;双语说明与产品源码不变。
|
||||
|
||||
`8201c9c` 的 CI `36746918608` 中磨砂回归通过,唯一失败为 161 条历史的多次
|
||||
热切换 App 用例超过默认 5 秒(5573 通过、57 跳过)。只将该用例预算设为 15 秒,
|
||||
保留消息数量、DOM 复用、零额外渲染、草稿、附件、展开状态和滚动位置的全部断言;
|
||||
不修改全局超时或将运行时性能要求替换为等待通过。
|
||||
调整后完整 App 319 项、类型检查及 lint 通过。
|
||||
|
||||
## 再次扩展候选
|
||||
|
||||
用户要求将当前未提交改动一起提交。新增 `0a02a17`(新建笔记入口)和
|
||||
`a3b41bf`(设备共享元数据),并纳入五份应用导航文档修改。
|
||||
设备共享需另行运行 ShareServer,首期面向本地未认证服务,仅注册设备及管理元数据;
|
||||
不执行远程能力,不传输或检索知识正文,设备 ID 不构成所有权认证。
|
||||
Agent 实现和锁不变,仍不升版。
|
||||
|
||||
扩展源码类型检查和 lint 已通过。用户随后明确要求停止本地测试、直接推送等 CI,
|
||||
进行中的定向测试已中断,不计为通过,也不沿用旧候选的全量结果。
|
||||
最终发布取决于包含新增提交和文档的精确候选 CI 与生产构建。
|
||||
|
||||
## 扩展候选焦点回归
|
||||
|
||||
`2c5300d` 的 Desktop CI `36753498640`:5580 通过、58 跳过、1 失败,
|
||||
生产构建未执行;Agent CI `36753498643` 通过。
|
||||
笔记导航用例在打开并取消带 `autoFocus` 标题输入框的新建表单后,
|
||||
才断言此前打开详情时的返回按钮焦点,混合了两个不同交互阶段。
|
||||
将原焦点断言移至打开新建表单之前,并断言新建标题输入框取得焦点;
|
||||
保留返回列表、滚动位置、搜索内容和原卡片焦点断言,不改产品代码或延长超时。
|
||||
按用户要求不运行本地测试,由新候选 CI 验证。
|
||||
@@ -0,0 +1,53 @@
|
||||
# Desktop 0.15.7 发布准备
|
||||
|
||||
日期:2026-10-01。基线为 `v0.15.6`,源码候选 `c8f327f`。
|
||||
检查时工作区干净,用户要求纳入的先前改动已由该提交包含,共 75 文件。
|
||||
本轮 Desktop 升至 0.15.7,Agent 保持 0.15.1,仅标准六平台,不含 LoongArch。
|
||||
|
||||
## 范围
|
||||
|
||||
- 流式 Markdown 分段复用、会话行与派生值缓存。默认 16ms 节奏不变。
|
||||
- 运行记录进入时读取、可见时 15 秒刷新及页头手动刷新,不是逐事件实时视图。
|
||||
- 本地自定义 MCP 发现与动态目录故障按服务隔离,取消和撤销仍生效。
|
||||
- 模型连接中的受管模型下载源弹窗,确认保存、取消不变及相关入口统一。
|
||||
- 设备共享默认关闭的测试入口,显式启用,关闭不撤销服务端记录。
|
||||
|
||||
网关修改只涉及本地自定义 MCP 发现方法;远程 Agent 的独立 MCP 实现与
|
||||
Story Graph 绑定不经过这些方法,Agent 源码、锁及包未变。边界与开发验证见
|
||||
[工作栏进度](../features/assistant-workbar/progress.md)。
|
||||
数据库仍为 schema 52,无新增迁移。
|
||||
|
||||
## 证据与发布门槛
|
||||
|
||||
本轮遵照用户要求,不运行本地测试、类型检查、lint 或生产构建,由新候选 CI 验证。
|
||||
已有开发会话记录不等于本候选全仓通过;网关会话的历史全仓失败也不掩盖。
|
||||
流式输入测量见 [组件测量记录](../../tests/support/streaming-input-results.md),
|
||||
不据此声称完整 App 或所有机器的相同比例提速。本轮外部模型调用为 0。
|
||||
|
||||
中英文 FEATURES 已核对设备共享默认值、下载源位置及实现边界,并补充本轮性能和活动行为。
|
||||
发布说明保留 schema 52 备份提醒,不修改历史已发布条目。
|
||||
双远端推送精确候选后,必须通过主分支全量验证与生产构建,并确认候选及双语说明后,
|
||||
才能创建不可变 `v0.15.7` 标签。
|
||||
|
||||
## 首轮 CI
|
||||
|
||||
`227507d` 的 Desktop CI `36872224138`:5613 通过、59 跳过、2 失败,
|
||||
生产构建未执行;Agent CI `36872224140` 通过。
|
||||
英文设置布局 fixture 在 640×420 窗口将控件居中到包含固定页头的整个滚动区,
|
||||
点击检测被较高的英文页头遮挡。测试共用滚动辅助函数改为扣除实际页头高度的
|
||||
可见区域,仍检查命中、溢出、文字宽度和原生键盘交互,不改产品布局。
|
||||
旧 outbox fixture 先建当前数据库,再降为 schema 23 并执行完整迁移,
|
||||
超过默认 5 秒;仅该用例设 15 秒,保留终态、尝试次数及附件清理断言。
|
||||
修复不修改发布说明或运行时代码,按用户要求继续交新候选 CI 验证。
|
||||
|
||||
## 第二轮 CI
|
||||
|
||||
`7cb74cc` 的 CI `36874700588`:首轮两项失败已通过,但仍有 5613 通过、
|
||||
59 跳过、2 失败,生产构建未执行。检查提交链确认先前提交期间并行加入了
|
||||
`5e77118`,移除文字输入框占位提示的问候语,App 单元测试已同步为两行,
|
||||
Electron 侧栏探针却仍要求三行,导致断言失败。探针同步为两行并检查首行键盘提示,
|
||||
保留平台快捷键、输入、清空、焦点、布局和搜索检查。
|
||||
Markdown 用例逐一比较超过 1500 个流式前缀,超过默认 5 秒;
|
||||
仅该用例改为 15 秒,保留全部 DOM 等价、前缀数及多段覆盖断言。
|
||||
用户明确授权连续修复、双远端推送和 CI 通过后的标签发布,不再中途确认。
|
||||
本轮仍不运行本地测试,以上修复须由下一次精确候选 CI 验证。
|
||||
@@ -0,0 +1,57 @@
|
||||
# Desktop 0.15.8 / Agent 0.15.2 发布准备
|
||||
|
||||
日期:2026-10-02。Desktop 基线 `v0.15.7`,Agent 基线 `agent-v0.15.1`;
|
||||
已提交源码固定为 `b1129cedec5e4890d82fcddc186b13467f466a6f`。
|
||||
仅纳入四个已提交功能修正及本次必要发布准备,不提交已有 Story Graph、
|
||||
会话监督设计或宣传文章等未提交文件。
|
||||
|
||||
## 范围
|
||||
|
||||
- `dd29f30`:保存会话图片到本机或 Host、设置中列出图像工具。
|
||||
- `f4d8fe7`:新记录取消、编辑焦点返回、笔记正文使用可用宽度。
|
||||
- `2e5c348`:缺失内置浏览器设置时默认启用,保留已保存配置。
|
||||
- `b1129ce`:缺失磨砂外观字段时默认开启,保留已保存配置。
|
||||
- 数据库仍为 schema 52;Desktop 标准六平台,无 LoongArch。
|
||||
- Agent 保存路径涉及守护进程、工具声明与桌面图像二进制回复,升至 0.15.2,
|
||||
最低 Desktop 0.15.8;协议主版本及 Runtime 锁版本不变。
|
||||
|
||||
## 当前源码共享 Host 验证
|
||||
|
||||
共享 Linux x64 Host 的现有 SSH 固定身份可达。在独立 `/root/tmp` 目录运行
|
||||
当前源码 AgentImageToolMcp,通过真实 SSH 传递工具消息,与 Desktop 的
|
||||
MainImageToolSession、ImageGenerationService、真实 SQLite 会话协作。
|
||||
图像为本轮生成的合法随机 PNG,196992 字节,超过单个 128 KiB 分块。
|
||||
|
||||
保存后的 Host 文件 SHA-256 与 Desktop 原始内容相同;默认覆盖拒绝、明确覆盖成功、
|
||||
其他会话图片 ID 拒绝、Ask 写入拒绝且无文件落盘。共 8 个回复帧,
|
||||
最大 174892 字节,低于 256 KiB 上限。外部模型调用 0 次。
|
||||
这是生产保存服务与 Agent MCP 的真实 Host 工具层验证,不冒充完整 App、
|
||||
模型自动选工具、安装包或三个 Host 架构验证。使用 SSH 流作为该探针传输;
|
||||
未改变用户工作区、产品配置或 Host 已安装 Agent。
|
||||
|
||||
临时探针最初因打包 Zod 初始化顺序、混入仅 Desktop 依赖而无法启动,
|
||||
修正探针依赖边界后验证通过;这些失败不计为产品模型调用或产品修复。
|
||||
探针和包不进入仓库。用户要求不跑本地测试套件,精确候选由 CI
|
||||
执行全量验证和生产构建后,才允许打 Desktop 和 Agent 不可变标签。
|
||||
|
||||
## 发布核对
|
||||
|
||||
中英文 FEATURES、Desktop 与 Agent 双语说明必须与上述实现边界一致。
|
||||
只暂存必要发布文件,保留工作区用户文件;双远端核验候选和标签一致。
|
||||
公开验证包括六平台 Desktop、三平台 Agent 签名目录、GitHub/OSS 同步及 Latest 分离。
|
||||
|
||||
## 首轮 CI 修正
|
||||
|
||||
`20317c2`:Agent CI `36960575058` 成功;Desktop `36960575157` 为
|
||||
5620 通过、59 跳过、4 失败,构建未执行。
|
||||
Obsidian 专项 IPC fixture 未安装浏览器服务,显式关闭无关浏览器能力;
|
||||
MCP 列表断言纳入新增图像卡片的数量和名称;覆盖层原生点击先将控件居中,
|
||||
避免磨砂顶栏覆盖 `nearest` 保留在顶部的控件,并补充命中遮挡诊断。
|
||||
保留所有业务、覆盖层和原生交互断言,不改变产品默认值、关闭沙箱或跳过测试。
|
||||
这些修正由新候选 CI 验证。
|
||||
|
||||
`5368e0e` 的 CI `36962139369` 中上述四项通过,余下新记录取消焦点用例失败,
|
||||
5623 通过、59 跳过、1 失败。测试用 `fireEvent.click` 点击取消不会像原生点击
|
||||
那样移动焦点,确认框记住的仍是先前的返回总览按钮。用例先等打开详情的焦点完成,
|
||||
明确聚焦取消按钮后再点击,并等继续编辑实际聚焦编辑器后进行下一步;
|
||||
保留放弃草稿后回到记录与未保存新记录的断言,不改产品焦点规则或延长超时。
|
||||
@@ -0,0 +1,56 @@
|
||||
# Desktop 0.15.9 / Agent 0.15.3 发布准备
|
||||
|
||||
日期:2026-10-03。Desktop 基线 `v0.15.8`,源码
|
||||
`50481deaa1f7d17184500bb6f5abb3e1a0c8bbd5`,37 个提交、163 个文件。
|
||||
检查时工作区干净,范围仅为已提交实现与必要发布准备。
|
||||
标准六平台 Desktop、三平台 Agent,不含 LoongArch。
|
||||
|
||||
## 范围与边界
|
||||
|
||||
- 监督共享时间线、故事归属与人工调整、经验抽取、故事回顾与心跳建议;
|
||||
平铺与时间螺旋、紧凑图谱控件及 WebGL 生命周期修复。
|
||||
- schema 53~56:合并既有进度,增加故事、经验及建议类型,保留历史结果。
|
||||
旧数据库副本验证和模型质量抽查见[监督进度](../features/conversation-supervision/progress.md),
|
||||
不将 schema 53 的单库耗时当作本轮完整迁移保证。
|
||||
- 会话外部存储、侧栏和消息窗口化、Markdown 缓存、只读 Worker、活动差量写入、
|
||||
远程事件 checkpoint 批次持久化;保留丢失焦点、滚动、去重与提交顺序检查。
|
||||
- Execute 配置工具不再重复弹原生确认,Ask 只读边界不变。
|
||||
- Electron 44.5.1;Node、OpenCode、Continue 锁不变。
|
||||
- Agent 虽无守护进程文件变更,但打包的 Story Graph 工具 schema 增加 story/experience,
|
||||
旧 Agent 不支持这些输入,因此升级 0.15.3,最低 Desktop 0.15.9。
|
||||
|
||||
性能范围及暂缓项见[性能计划](../roadmap/product-performance-experience-improvement-plan.md)。
|
||||
不把组件或特定机器的指标作为普遍提速保证;向量仍可全表扫描,Worker 失效会同步回退,
|
||||
窗口化的未挂载行不支持页内查找,快速滚动可能短暂显示占位。
|
||||
故事、经验、建议与输出重试有模型费用,自动结果须人工核对。
|
||||
|
||||
## 本轮真实 Host 验证
|
||||
|
||||
当前源码临时打包到隔离测试目录,不构建生产安装包。使用现有固定 Host 身份及
|
||||
凭据存储,通过生产 Agent Attach、模型桥及远程 ACP 路径执行 OpenCode 与 Continue
|
||||
各一次 Ask、一次 Execute。OpenCode 探针显式选择既有 Continue 模型配置,
|
||||
不修改产品设置,也不是产品静默降级。
|
||||
|
||||
共 **4 次真实提供方请求,4 次 HTTP 200**,无模拟推理、无自动请求重试。
|
||||
验证 Runtime 释放后重建、历史恢复和本轮进程清零。
|
||||
每次消费真实远程语义流,通过生产 `RemoteEventBatcher` 与
|
||||
`AssistantDatabase.appendRemoteTaskEventsOnce` 在 checkpoint 前落库;
|
||||
对完整已提交事件再次写入均返回已存在。四次请求分别记录:
|
||||
|
||||
| Runtime / 模式 | 批次 | 事件 | checkpoint |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| OpenCode Ask | 3 | 4 | 2 |
|
||||
| OpenCode Execute | 21 | 40 | 20 |
|
||||
| Continue Ask | 3 | 4 | 2 |
|
||||
| Continue Execute | 23 | 44 | 22 |
|
||||
|
||||
验证没有启动完整 Renderer,也不声称覆盖会话消息投影的所有恢复分支。
|
||||
Story Graph 新对象与来源语义由本轮源码测试及开发记录覆盖,以上四次请求不调用图谱工具。
|
||||
开发文档中的模型调用不计入本轮 4 次。临时探针的两次编译钩子定位失败均发生在请求之前。
|
||||
|
||||
## 发布门槛
|
||||
|
||||
按用户要求,不执行本地测试套件、lint、类型检查或生产打包;
|
||||
精确候选推送双远端后由 CI 做全量验证和生产构建,再打不可变双版本标签。
|
||||
中英文 FEATURES 需同步共享进度、已实现故事经验、成本和窗口化限制。
|
||||
公开核验 Desktop Latest、标准产物、Agent 签名目录、两个源及网站,保留任何并行未提交工作。
|
||||
@@ -10,6 +10,23 @@ from the authoritative GitHub, OSS, workflow, and Git state, then use the
|
||||
smallest existing idempotent rerun or a new immutable version as specified
|
||||
below.
|
||||
|
||||
## Optional PR Article
|
||||
|
||||
Product publicity is an optional companion to a release, not a release blocker
|
||||
or a replacement for the approved bilingual release notes. When requested,
|
||||
follow the [PR article workflow](../pr-articles/README.md) and choose either the
|
||||
[featured article](../pr-articles/README.md#重点稿模板) or
|
||||
[regular update](../pr-articles/README.md#常规与综合稿模板) template based on user value,
|
||||
not version size or commit count.
|
||||
|
||||
Prepare the draft and real screenshots against the candidate commit using the
|
||||
[isolated screenshot procedure](../pr-articles/README.md#截图执行步骤). This does
|
||||
not waive the build, packaging, or launch restrictions below. Keep missing
|
||||
materials in the article's review record without delaying the software release.
|
||||
Before external publication, verify the final version, download links, upgrade
|
||||
notes, and screenshot accuracy. Preparing materials does not authorize posting
|
||||
to a public channel; follow the user's explicit publication instruction.
|
||||
|
||||
## Release Packaging
|
||||
|
||||
- Unless the user explicitly requests it, never run a local production build,
|
||||
@@ -27,6 +44,11 @@ below.
|
||||
- `.github/workflows/packages.yml` is the canonical cross-platform packaging
|
||||
workflow. It validates and builds `out` once, then packages on six native
|
||||
runners: Windows, macOS, and Linux, each for x64 and arm64.
|
||||
- Linux source validation configures the installed Electron `chrome-sandbox`
|
||||
helper with root ownership and mode `4755`, rehydrates the Electron
|
||||
distribution without cache when the helper is missing, then runs the full
|
||||
test suite under Xvfb. Keep Electron sandboxing enabled; do not skip UI tests
|
||||
or add `--no-sandbox` to work around runner setup failures.
|
||||
- The experimental Linux LoongArch (`loong64`) preview is built separately
|
||||
through `build/loongarch-cross` by following
|
||||
`docs/development/loongarch-preview-build.md`. It is not part of
|
||||
@@ -62,7 +84,7 @@ below.
|
||||
Host.
|
||||
- Each independently published Linux x64, Linux arm64, or macOS arm64 `.gbagent` contains the
|
||||
Agent daemon, pinned Node, and the desktop-maintained compatible OpenCode
|
||||
Runtime. Build all three targets on native GitHub Actions runners from one
|
||||
and Continue Runtimes. Build all three targets on native GitHub Actions runners from one
|
||||
immutable `agent-v<agentVersion>` tag.
|
||||
- Keep the GoodBuddy Agent source, shared protocol/contracts, runtime lock,
|
||||
bundle tooling, and tests in this repository so a desktop commit identifies
|
||||
@@ -72,7 +94,7 @@ below.
|
||||
- `.github/workflows/agents.yml` is the branch and pull-request build
|
||||
verification workflow for GoodBuddy Agent. It must build Linux x64 and
|
||||
arm64 on native runners from the checked-out commit, acquire the locked
|
||||
official Node archive and locked OpenCode npm archive, verify every locked
|
||||
official Node archive and locked OpenCode and Continue npm archives, verify every locked
|
||||
digest, use only an ephemeral in-memory test signing identity, and verify the
|
||||
resulting compound package and deterministic archive. It must not read
|
||||
production signing secrets, publish installable release artifacts, or modify
|
||||
|
||||
@@ -21,8 +21,10 @@ diagnosis of their cause.
|
||||
- Native crashes and main-frame load failures use the existing bounded desktop
|
||||
diagnostics store (`desktop.renderer.gone`, `desktop.renderer.load-failed`,
|
||||
stage `renderer`). Persistent records retain fixed summaries rather than raw
|
||||
errors. Crash reason/exit code and React errors/component stacks go to their
|
||||
respective process consoles. There is no new diagnostic IPC.
|
||||
errors. Renderer exit records also retain Electron's enumerated `reason` and
|
||||
numeric `exitCode` when written, read back and exported; older records without
|
||||
these fields remain readable. The exit details still go to the Main console.
|
||||
React errors/component stacks remain console-only. There is no new diagnostic IPC.
|
||||
|
||||
The boundary does not catch module evaluation/startup failures before React
|
||||
mounts, event-handler exceptions, asynchronous rejections, a hung renderer, or
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
# 应用导航与系统工具入口
|
||||
|
||||
本功能整理 GoodBuddy 主侧栏的入口层级:系统入口保持固定,内置应用通过最底部应用中心
|
||||
访问,并可常驻主导航;系统工具由右侧工作栏 Tab 承载。应用商店是后续功能,首期不提供
|
||||
本功能整理 GoodBuddy 主侧栏的入口层级:系统入口保持固定,五个内置应用共用可调整的保存顺序,知识库始终启用并常驻;
|
||||
智能心跳、魔法笔记和本机推理监控可通过应用管理控制启用和常驻。底部“应用”向上展开轻量锚定菜单,列出已启用应用;底部“管理应用”打开既有“应用中心”管理 Modal。系统工具由右侧工作栏 Tab 承载。应用商店是后续功能,首期不提供
|
||||
商店入口、推荐位或安装流程。
|
||||
|
||||
设备共享作为第五项追加到原有应用顺序,测试阶段默认关闭,启用后可从菜单和管理 Modal 打开,不提供常驻
|
||||
开关;服务地址与设备名称在其页面保存。首期仅支持本地服务的设备注册与元数据目录,详见
|
||||
[设备共享](../device-sharing/README.md)。
|
||||
|
||||
当前分期见 [PRD 第 8 节](./prd.md#8-分期),实施步骤见[技术设计第 11 节](./technical-design.md#11-实施顺序)。
|
||||
本轮更新实施计划与范围,旧版功能要求、验收和配套设计仍需按新计划同步,此项列为实现前的第一步。
|
||||
本轮 A–D 已接入生产源码:应用菜单、应用管理 Modal、共享应用设置、持久化偏好及本机推理监控。菜单不按打开历史或常驻状态筛选,无模态遮罩;点击应用行关闭菜单,知识库、智能心跳和魔法笔记打开主内容区页面。本机推理监控从菜单、常驻入口或管理 Modal 打开时均使用独立 Modal,保留底层工作区。管理列表、菜单和侧栏按同一保存顺序排列,侧栏按启用和常驻过滤。知识库在管理 Modal 中提供“打开”和排序,不提供启用、常驻开关或通用空设置;原工作区配置及心跳单条计划 enabled 保持原义。验证范围及集成缺口见[实施进度](./progress.md)。
|
||||
|
||||
新增计划包括 [私有化市场与 YAML 交换](./prd.md#fr-15-私有化市场与-yaml-交换后续):
|
||||
通过市场 URL 获取应用,支持 `.yml` 导入导出,未来由 ShareServer 提供私有目录与分发。
|
||||
[本机模型管理入口](./prd.md#fr-16-本机模型管理入口新增计划项)可独立于商店交付,拟作为
|
||||
可常驻左侧的内置管理应用,区分受管进程、外部服务和进程内模型。两项均为需求记录,
|
||||
未实现,相关后续验收见 [User Stories](./user-stories.md#7-后续需求场景)。
|
||||
[本机模型管理入口](./prd.md#fr-16-本机模型管理入口新增计划项)已独立于商店接入,作为
|
||||
可常驻左侧的“本机推理监控”(英文 Local Inference Monitor)内置应用,直接查看语音、OCR、向量等本机服务状态与可用资源指标,不显示任务历史。向量服务显示独立进程的 CPU 和工作集内存;ASR、OCR 与宿主共享进程,逐行说明无法独立统计的原因。
|
||||
本轮已接入 `local-inference` 管理 Modal、类型化 IPC、真实请求登记及引擎管理;实际支持清单见
|
||||
[技术设计](./technical-design.md#111-本机推理任务与执行服务),市场仍为后续需求。
|
||||
相关验收见 [User Stories](./user-stories.md#7-后续需求场景)。
|
||||
|
||||
项目活动汇总独立于上述应用中心计划,范围见 [FR-14](./prd.md#fr-14-项目活动汇总)。
|
||||
计数规则由[活动逻辑](./logic-design.md#9-项目活动汇总)定义,入口与级联菜单见
|
||||
[活动界面](./ui-design.md#10-项目活动汇总),数据加载与会话跳转见
|
||||
[技术设计](./technical-design.md#12-项目活动汇总)。此处“活动”指当前运行或待处理会话,
|
||||
与用于审计的“运行记录”不同;已完成未读面板属于后续范围,本次未实现。
|
||||
[技术设计](./technical-design.md#12-项目活动汇总)。此处“活动”包括当前运行、待处理及本次客户端
|
||||
会话内已完成但未查看的会话;完成提示在实际进入聊天后清除,不累计历史完成总数。
|
||||
|
||||
## 文档导航
|
||||
|
||||
待实现的应用扩展见 [FR-17](./prd.md#fr-17-主机管理与云环境管理待实现):
|
||||
主机管理从设置中心迁入独立工作页面,云环境管理作为同级应用;业务定义由
|
||||
[远程主机功能文档](../remote-host/README.md)维护。该计划不包含已接入的设备共享元数据页面。
|
||||
|
||||
现有 `heartbeat` 入口承载监督者工作回顾与故事线图谱,可管理启用、常驻和排序。应用默认关闭;完整执行边界见[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行)。未来市场复用同一应用身份,不增加平级重复入口,目标定义见[监督者产品设计](../conversation-supervision/supervisor-prd.md)。
|
||||
|
||||
| 文档 | 权威职责 |
|
||||
| --- | --- |
|
||||
| [PRD](./prd.md) | 用户问题、产品边界、功能要求与产品验收 |
|
||||
@@ -28,14 +40,15 @@
|
||||
| [功能逻辑设计](./logic-design.md) | 分类规则、入口状态、优先级与失败结果 |
|
||||
| [UI 设计](./ui-design.md) | 侧栏信息架构、控件、文案、键盘和响应式行为 |
|
||||
| [技术设计](./technical-design.md) | 现有模块映射、状态契约、持久化和实施顺序 |
|
||||
| [实施进度](./progress.md) | 项目活动级联的生产实现与验证记录 |
|
||||
| [实施进度](./progress.md) | 应用中心、设置及项目活动级联的实现与验证记录 |
|
||||
| [应用中心交互 Demo](./navigation-demo.html) | 对比六个候选方案;配套 CSS 和 JS 仅服务原型 |
|
||||
|
||||
Demo 可直接在浏览器中打开,支持方案切换、浅深主题、添加、移除、排序和商店模拟。
|
||||
六个方案共用本次页面会话内的应用配置,刷新后恢复默认。商店、资源指标和工具内容均为
|
||||
演示数据,不执行真实安装或本机操作。窄屏上下排列侧栏与输入区,桌面展示完整应用布局。
|
||||
这些方案用于讨论最底部应用中心;其中商店页、商店应用安装与 F 方案属于未来交互参考,
|
||||
不纳入首期实现或验收。首期产品只提供内置应用;Demo 暂保留远期示例以便后续讨论。
|
||||
Demo 可直接在支持原生 dialog、popover 的现代浏览器中打开,无需依赖或构建。
|
||||
六个方案共用本次页面会话内的配置,刷新后全部恢复默认,不持久化或修改真实设置。
|
||||
A–E 仅展示内置应用,没有商店入口、推荐或安装操作;F 单独演示未来私有市场。
|
||||
模型服务、资源指标和工具内容均为演示数据,不执行真实安装、模型加载或进程操作。
|
||||
窄屏上下排列侧栏与主内容,应用页面隐藏输入区,对话页面显示输入区。
|
||||
Demo 的模型管理和 F 方案均使用模拟数据;正式本机推理监控 Modal 的交付范围以技术清单和实施进度为准,旧页面原型不作为 Modal 验收依据。
|
||||
底部输入区共用一个简化候选布局:附件、语音、Runtime、模式和发送位于主操作行,专家、
|
||||
知识范围及 Runtime 专属选项通过“选项”展开,非默认选择显示为可移除标签。可体验文件名
|
||||
附件、模拟语音、模板填入、原生命令选择、8 秒模拟回复、发送排队、立即插入、删除、停止,
|
||||
@@ -47,20 +60,38 @@ Demo 可直接在浏览器中打开,支持方案切换、浅深主题、添加
|
||||
进入会话层,并提供返回按钮。页面上方可切换“有待处理、仅运行中、仅当前项目运行、全部
|
||||
空闲”四种场景。项目与会话均为演示数据;该级联交互已接入正式产品活动入口,范围与
|
||||
验证记录见[实施进度](./progress.md)。
|
||||
按最新讨论,Demo 已移除左侧系统工具快捷区:文件、终端、浏览器和资源监控只在右侧
|
||||
工作栏 Tab 中演示,不列入应用中心。上方正式文档仍保留原设计,待方案确定后同步。
|
||||
Demo 已移除左侧系统工具快捷区:文件、终端、浏览器和资源监控只在右侧
|
||||
工作栏 Tab 中演示,不列入应用中心。旧原型仍直接打开整窗 Modal,其中使用可搜索应用列表。
|
||||
|
||||
Demo 待同步更正:本节仅描述旧原型,不是生产行为或验收依据。原型直接打开 Modal、知识库的启停设置,以及心跳默认启用和关闭仅隐藏入口的说明均已过时;原型的常驻排序也不代表生产中四个应用的共享顺序。生产规则见上述说明及 UI 设计。Demo 已同步“本机推理监控”名称及其默认不常驻状态。
|
||||
|
||||
六个方案共用“我的应用”区:通过 + 或“全部应用”选择常驻应用,编辑时可拖动或使用
|
||||
上下箭头排序、移除入口。未常驻的已安装应用仍可从“全部应用”打开。商店包含“项目管理”
|
||||
和“阅读空间”应用示例,支持仅安装或安装并常驻左侧菜单,常驻不表示后台运行。
|
||||
常驻应用在本次页面会话内跨方案保持一致;“恢复默认应用”重置安装与常驻配置。
|
||||
移除入口不会卸载应用或关闭当前页面,刷新后恢复默认。
|
||||
上下箭头排序、移除入口。内置应用默认全部启用;魔法笔记、知识库、智能心跳默认常驻,计划预览“本机推理监控”默认不常驻。
|
||||
取消常驻仅移除导航捷径,仍可从应用中心打开;关闭应用隐藏业务入口,应用中心保留设置与
|
||||
重新开启入口,并记住常驻偏好。当前应用页关闭后显示不可用状态,重新启用从应用中心卡片设置进入。
|
||||
知识库关闭时同步隐藏侧栏入口、会话选项中的知识范围和检索方式、已选知识标签;重新开启
|
||||
恢复原来的选择。以上操作不删除数据,也不表示停止后台任务或模型服务。
|
||||
“恢复默认应用”重置应用安装、启用、常驻和笔记设置,返回对话页;模型服务模拟状态保留至刷新。
|
||||
|
||||
魔法笔记提供应用中心“设置”、笔记页面“笔记设置”和全局设置演示入口,均打开同一设置
|
||||
面板,共用本次页面会话内的配置。面板沿用现有选项:显示未完成待办数量、AI 评论方式
|
||||
通用应用设置统一从应用中心卡片进入,应用页面和全局设置不提供重复入口,分别控制
|
||||
启用与常驻。魔法笔记面板另含显示未完成待办数量、AI 评论方式
|
||||
(即时/保存后自动/保存后手动)、AI 评论形式(长评 + 要点/长评/要点)。现有“显示
|
||||
魔法笔记入口”在原型中与“常驻左侧菜单”统一,不额外增加功能启停开关。侧栏待办数为
|
||||
固定的 3 项演示数据。修改即时生效于原型,不调用 AI 或写入真实应用设置。
|
||||
魔法笔记入口”不在原型中重复提供。正式迁移保留其业务启用值,常驻使用独立配置。启用、常驻、待办数量显示默认开启;
|
||||
评论方式默认即时,评论形式默认长评 + 要点。侧栏待办数为固定的 3 项演示数据。
|
||||
修改即时生效于原型,不调用 AI 或写入真实应用设置。
|
||||
|
||||
此 Demo 中的任务、进度、使用方与服务内存为旧方案模拟内容;正式 Modal 已收窄为服务状态列表,见 [FR-16](./prd.md#fr-16-本机模型管理入口新增计划项)。
|
||||
引擎和管理方式收进详情;受管进程模拟启动、停止、重启,进程内模型模拟加载与释放。
|
||||
服务停止或重启后任务标为中断,重新启动不自动恢复任务;任务级取消仍待实际引擎接入。
|
||||
影响使用方的操作先显示就地确认,取消保留原状态;隐藏或关闭管理应用不改变服务状态。
|
||||
|
||||
F 提供私有市场 URL、模拟获取目录、模拟不可达与重试,不发起网络请求或切换公共来源。
|
||||
目录包含项目管理和阅读空间示例,可模拟安装或安装并常驻。YAML 文本支持导入预览和导出
|
||||
预览,明确标为 mock schema,仅识别示例中的七个扁平字段,尚非通用 YAML 解析器。
|
||||
导入列出依赖、外部资源和同标识冲突:缺少依赖或资源时不允许安装;内置应用不允许覆盖;
|
||||
已安装扩展须显式确认更新定义,保留启用与常驻偏好。导入新应用在目录中以标识显示,原始名称
|
||||
保留在定义及导出预览中。修改输入会使旧预览失效,取消不修改安装状态。
|
||||
导出仅显示定义文本,不写文件,不包含凭据、会话、笔记、知识数据、程序包或模型权重。
|
||||
|
||||
| 方案 | 讨论重点 |
|
||||
| --- | --- |
|
||||
@@ -69,7 +100,7 @@ Demo 可直接在浏览器中打开,支持方案切换、浅深主题、添加
|
||||
| C:应用预览 | 用卡片说明应用用途 |
|
||||
| D:搜索启动台 | 搜索打开能力,支持 Ctrl / Command K、方向键与 Enter |
|
||||
| E:常驻管理 | 已常驻应用优先排列,可取消常驻或打开 |
|
||||
| F:商店发现(未来) | 默认展开模拟商店,不纳入首期 |
|
||||
| F:私有市场预览(未来) | URL 模拟获取、YAML 导入导出与显式模拟安装,不纳入首期 |
|
||||
|
||||
D 参考 [Raycast](https://www.raycast.com/) 的搜索启动与扩展发现方式。早期方案参考过
|
||||
Notion 收藏和 VS Code Profiles,当前版本已移除场景工具包与快捷区。
|
||||
@@ -81,7 +112,7 @@ Notion 收藏和 VS Code Profiles,当前版本已移除场景工具包与快
|
||||
| 术语 | 定义 |
|
||||
| --- | --- |
|
||||
| 系统入口 | GoodBuddy 工作流程或审计所必需的固定入口,用户不能卸载、隐藏或改变类型 |
|
||||
| 应用 | 在主内容区打开完整一级页面的功能,可显示在“当前对话”所在的主导航区 |
|
||||
| 应用 | 可显示在主导航区的功能;知识库、智能心跳和魔法笔记打开一级页面,本机推理监控打开保留底层工作区的 Modal |
|
||||
| 系统工具 | GoodBuddy 自带且不可卸载的辅助能力,包括文件、终端、浏览器和资源监控,由右侧助手工作栏 Tab 承载 |
|
||||
| 插件应用 | 未来由插件提供的一级页面入口 |
|
||||
| 插件工具 | 未来由插件提供的辅助面板或命令入口 |
|
||||
@@ -94,3 +125,4 @@ Notion 收藏和 VS Code Profiles,当前版本已移除场景工具包与快
|
||||
- [助手工作栏多终端页签 PRD](../assistant-workbar/terminal-tabs-prd.md)
|
||||
- [智能心跳 PRD](../smart-heartbeat/prd.md)
|
||||
- [知识库功能文档](../knowledge-base/README.md)
|
||||
- [文档解析入口与职责](../document-processing/ui-design.md#11-入口与职责):全局 OCR 配置、附件与知识文档操作的目标分工;不新增应用中心成员,本机推理监控不管理 HTTP OCR 服务。
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 设计中 |
|
||||
| 版本 | 0.2 |
|
||||
| 日期 | 2026-09-08 |
|
||||
| 状态 | 应用中心与本机推理监控已接入源码,整体验收待完成 |
|
||||
| 版本 | 0.4 |
|
||||
| 日期 | 2026-09-17 |
|
||||
| 关联 PRD | [应用导航与系统工具入口 PRD](./prd.md) |
|
||||
| 关联 User Stories | [User Stories](./user-stories.md) |
|
||||
|
||||
@@ -15,24 +15,34 @@
|
||||
1. “当前对话”“运行记录”和“设置”始终是系统入口。
|
||||
2. 运行记录不进入工具注册表、应用显隐设置或插件市场。
|
||||
3. 文件浏览器、终端、浏览器和资源监控始终是系统工具,不能卸载或被插件替换。
|
||||
4. 左下角只提供快捷入口,工具内容仍由助手工作栏承载。
|
||||
5. 隐藏应用不等于停用功能,也不删除、迁移或清理业务数据。
|
||||
6. 快捷入口不能扩大执行空间、文件、PTY、网络或浏览器权限。
|
||||
4. 左下角提供应用中心和独立固定设置入口;文件、终端、浏览器、资源监控只通过右侧工作栏 Tab 和“+”访问,不进入应用中心。
|
||||
5. 五个内置应用共享保存顺序。知识库始终启用并常驻,位置可调整;智能心跳、魔法笔记、本机推理监控可独立设置启用和常驻。设备共享在测试阶段默认关闭,提供启用开关,启用后可从应用菜单打开,不常驻侧栏。关闭设备共享时退出其页面,不清除目录或撤销已发布记录。取消常驻不关闭功能,关闭不删除数据或导航偏好。
|
||||
6. 入口不能扩大执行空间、文件、PTY、网络或浏览器权限。
|
||||
7. 应用中心始终可达,点击向上展开无模态遮罩的锚定菜单,只列已启用应用,不使用打开历史或常驻过滤。菜单底部“管理应用”打开既有 Modal,关闭的可选应用在其中仍可搜索、配置和重新开启。
|
||||
8. 所有入口和新请求的功能判断读取同一份保存成功的权威设置;保存中的目标值不提前生效。
|
||||
|
||||
## 2. 入口模型
|
||||
|
||||
```ts
|
||||
type NavigationContribution =
|
||||
| { type: 'application'; id: 'device-sharing'; source: 'builtin'; enabled: true; pinned: false }
|
||||
| {
|
||||
type: 'system-entry'
|
||||
id: 'chat' | 'activity' | 'settings'
|
||||
id: 'chat' | 'activity' | 'application-center' | 'settings'
|
||||
placement: 'primary-start' | 'primary-end' | 'footer'
|
||||
}
|
||||
| {
|
||||
type: 'application'
|
||||
id: 'magic-notes' | 'knowledge' | 'heartbeat'
|
||||
id: 'knowledge'
|
||||
source: 'builtin'
|
||||
visible: boolean
|
||||
alwaysShown: true
|
||||
}
|
||||
| {
|
||||
type: 'application'
|
||||
id: 'magic-notes' | 'heartbeat' | 'local-inference'
|
||||
source: 'builtin'
|
||||
enabled: boolean
|
||||
pinned: boolean
|
||||
}
|
||||
| {
|
||||
type: 'system-tool'
|
||||
@@ -41,7 +51,8 @@ type NavigationContribution =
|
||||
}
|
||||
```
|
||||
|
||||
该模型只覆盖首期入口。插件贡献模型随插件 Host、权限和生命周期另行设计。
|
||||
该模型是首期的逻辑投影,启用值来自各应用的唯一业务配置,常驻及顺序来自导航偏好。
|
||||
应用清单为魔法笔记、知识库、智能心跳、本机推理监控,打开无需安装。市场不加入本轮清单。
|
||||
|
||||
## 3. 主导航组合规则
|
||||
|
||||
@@ -49,35 +60,60 @@ type NavigationContribution =
|
||||
|
||||
```text
|
||||
当前对话
|
||||
可见的内置应用
|
||||
应用(按保存顺序;知识库始终显示,可选应用仅显示启用且常驻项)
|
||||
运行记录
|
||||
```
|
||||
|
||||
当前对话和运行记录不参与排序。首期内置应用默认顺序为魔法笔记、知识库、智能心跳;显隐
|
||||
设置只从中移除对应项,不改变固定入口的位置。
|
||||
当前对话、运行记录和设置不参与应用排序。默认应用顺序为知识库、智能心跳、魔法笔记、本机推理监控;魔法笔记默认常驻,本机推理监控默认不常驻。过滤可选项不留下空白。
|
||||
常驻、取消常驻和排序按设备保存,重启后恢复。排序只发生在应用区,不移动固定系统入口。
|
||||
管理列表、应用菜单和主导航共用完整应用顺序,分别按搜索、启用、启用且常驻过滤;过滤不删除顺序记录,重新开启或常驻时按保存顺序恢复。
|
||||
每个管理行均可排序,包括关闭或未常驻的应用。上下移动以完整保存顺序中的相邻应用为目标,拖动使用同一移动规则。搜索时禁用拖动,箭头仍按完整顺序移动,保留未命中应用及其相对顺序。
|
||||
|
||||
### 3.1 应用可见性与业务状态
|
||||
|
||||
| 导航可见 | 功能启用 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 是 | 是 | 显示入口并允许进入 |
|
||||
| 否 | 是 | 隐藏入口,后台和数据保持原状 |
|
||||
| 是 | 否 | 不显示不可用入口;设置页保留启用路径 |
|
||||
| 否 | 否 | 不显示入口,保留符合该功能自身规则的数据 |
|
||||
下表适用于智能心跳、魔法笔记和本机推理监控。知识库始终可用,管理 Modal 中提供“打开”和排序,保留原工作区实际配置,不增加启用、常驻开关或通用空设置页。心跳应用的默认值与执行条件见[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行)。
|
||||
|
||||
“功能启用”由各应用现有产品设置负责。例如魔法笔记启停仍使用原设置;导航偏好不能覆盖它。
|
||||
| 常驻偏好 | 功能启用 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| 是 | 是 | 主导航显示,可从主导航或应用中心打开 |
|
||||
| 否 | 是 | 主导航隐藏,仍可从应用中心打开,后台和数据保持原状 |
|
||||
| 是 | 否 | 主导航、应用菜单和业务入口隐藏,管理 Modal 保留设置,记住常驻及顺序 |
|
||||
| 否 | 否 | 主导航、应用菜单和业务入口隐藏,管理 Modal 保留设置,记住未常驻状态 |
|
||||
|
||||
管理 Modal 浏览和搜索包含关闭的可选应用,搜索不修改启用或常驻。轻量菜单仅显示已启用项,点击行先关闭菜单;本机推理监控打开独立 Modal,其余应用请求主内容区导航。管理 Modal 的“打开”和常驻入口遵守同一规则。打开已启用应用不自动常驻;
|
||||
取消当前应用的常驻不关闭当前页面或本机推理监控 Modal。关闭应用后不能从旧入口继续执行新的业务操作。
|
||||
停用时仍允许管理常驻偏好,界面说明重新开启后才会显示入口。
|
||||
|
||||
### 3.2 关闭应用与恢复
|
||||
|
||||
关闭与恢复适用于可选应用,知识库页面、会话控件及关联不受影响。智能心跳关闭后的调度、手动请求、侧栏与在途运行按[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行)处理,保留单条计划 enabled。
|
||||
|
||||
| 对象 | 关闭后的结果 | 重新开启后的结果 |
|
||||
| --- | --- | --- |
|
||||
| 当前已打开应用页或本机推理监控 Modal | 原位置显示“应用已关闭”的不可用状态;保留内容状态及草稿,不强制跳转。重新启用从应用中心对应行的设置进入,监控 Modal 需先关闭 | 恢复可用性;仍打开时恢复内容,已关闭或离开则不自动打开 |
|
||||
| 已在执行的请求及后台操作 | 沿用功能自身的取消、完成和清理生命周期;本次应用开关不新增取消策略 | 不自动恢复、中断后重跑或补发任务 |
|
||||
| 数据、常驻及顺序 | 保留原值,业务入口按启用状态过滤 | 按保存的常驻及顺序恢复入口,不把未常驻应用自动常驻 |
|
||||
|
||||
知识请求及单个知识库实例停用、配置修改或删除继续遵守知识库自身规则,没有应用级禁用判断。
|
||||
|
||||
### 3.3 魔法笔记设置迁移
|
||||
|
||||
魔法笔记设置迁入应用中心对应行的设置页;笔记页面和全局设置均不提供重复入口,
|
||||
编辑同一配置。启用、常驻、显示未完成待办数量分别控制功能、导航和徽标,不保留重复的
|
||||
“显示魔法笔记入口”开关。
|
||||
|
||||
未配置的魔法笔记、本机推理监控启用及魔法笔记相关布尔项默认开启;心跳应用默认关闭。魔法笔记和心跳默认常驻,本机推理监控默认不常驻;已启用应用仍可从应用菜单及管理 Modal 打开。已有保存的 `true` 和 `false` 均保留。
|
||||
AI 评论方式和评论形式继续为单选,沿用现有默认值“即时”和“长评 + 要点”,不能解释为
|
||||
全部模式同时启用。迁移保留既有单选值及业务数据。关闭数量显示只隐藏徽标,不修改待办。
|
||||
|
||||
## 4. 系统工具打开规则
|
||||
|
||||
### 4.1 通用流程
|
||||
|
||||
```text
|
||||
用户激活工具按钮
|
||||
-> 校验按钮仍可用
|
||||
-> 向助手工作栏发送打开意图
|
||||
-> 工作栏按应用实例策略查找或创建实例
|
||||
-> 展开工作栏
|
||||
-> 激活目标实例
|
||||
用户展开右侧工作栏
|
||||
-> 选择已有 Tab,或从“+”目录选择工具
|
||||
-> 已有 Tab 按实例 ID 激活;目录按单实例或多实例策略创建或激活
|
||||
-> 把焦点移入面板标题或首个主要控件
|
||||
```
|
||||
|
||||
@@ -85,31 +121,27 @@ type NavigationContribution =
|
||||
|
||||
### 4.2 决策表
|
||||
|
||||
| 工具 | 匹配实例存在 | 匹配实例不存在 | 上下文不可用 |
|
||||
| 工具 | 选择已有 Tab | 从“+”目录选择 | 上下文不可用 |
|
||||
| --- | --- | --- | --- |
|
||||
| 文件 | 激活不可关闭的单实例并保留其跟随或固定状态 | 仅在迁移或布局修复时补回 `workspace` | 打开面板并显示目录修复入口 |
|
||||
| 终端 | 激活当前执行空间最近的 `starting` 或 `running` 实例 | 创建一个绑定当前执行空间的实例 | 没有项目时使用本机 Home |
|
||||
| 浏览器 | 激活绑定当前 Conversation 的最近使用实例 | 创建绑定当前 Conversation 的新实例 | 激活最近使用实例;不存在时创建待绑定实例 |
|
||||
| 资源监控 | 激活 `resources` 单实例 | 创建本机 `resources` 单实例 | 部分指标不可用时显示原因并保留其余指标 |
|
||||
| 文件 | 激活不可关闭单实例并保留跟随或固定状态 | 聚焦 `workspace`;仅迁移或布局修复时补回 | 跟随模式无有效项目目录或默认工作区时显示修复入口 |
|
||||
| 终端 | 激活选中的确切实例,保留执行空间和历史状态 | 每次创建绑定当前执行空间的额外实例 | 新建时无项目使用本机 Home;SSH 失败保留远端目标与诊断 |
|
||||
| 浏览器 | 激活选中的确切实例,保留绑定目标和页面 | 每次创建绑定当前 Conversation 的额外实例 | 新建时无 Conversation 创建待绑定空实例 |
|
||||
| 资源监控 | 激活本机 `resources` 单实例 | 已存在则聚焦,否则创建 | 部分指标不可用时显示原因并保留其余指标 |
|
||||
|
||||
文件保持单一实例原有绑定。浏览器快捷入口按当前 Conversation 匹配最近使用实例,而工作栏
|
||||
“+”始终新建浏览器实例。固定目标失效时显示该目标的修复状态,不回退到当前项目、默认
|
||||
工作区或当前会话。
|
||||
|
||||
`closing`、`exited`、`interrupted` 和 `failed` 终端不属于可复用实例。当前执行空间只有这些
|
||||
终端时,快捷按钮创建新实例并保留原 Tab 的历史状态。用户需要额外的并行终端时使用工作栏
|
||||
“+”或终端面板中的既有创建命令。
|
||||
选择已有 Tab 不按当前项目或 Conversation 查找替代实例,也不重绑目标。文件的跟随模式
|
||||
仍按工作栏自身规则更新上下文;固定目标失效时显示该目标的修复状态,不静默回退。
|
||||
终端 `closing`、`exited`、`interrupted`、`failed` Tab 保留原状态,选择它们不自动重建。
|
||||
终端和浏览器通过“+”新建时不按目标去重,不恢复旧快捷按钮的最近匹配实例复用行为。
|
||||
|
||||
### 4.3 选中与状态
|
||||
|
||||
工具按钮满足以下条件时显示选中:助手工作栏已展开,且当前活动实例的 `appId` 与工具映射
|
||||
一致。只有实例存在但未激活时不显示选中。
|
||||
工作栏按确切实例 ID 表达 Tab 选中状态,同类型其他实例不能同时显示为选中。“+”是普通
|
||||
按钮,不是 Tab。主侧栏和应用中心不显示工具选中状态或工具徽标。
|
||||
|
||||
徽标优先级为:等待用户处理 > 错误 > 运行中 > 无徽标。同一按钮只显示一个视觉徽标,
|
||||
徽标优先级为:等待用户处理 > 错误 > 运行中 > 无徽标。同一 Tab 只显示一个视觉徽标,
|
||||
可访问名称包含完整状态。
|
||||
|
||||
资源监控首期不在后台采样,因此不显示负载告警徽标。只有资源面板是工作栏当前活动实例且
|
||||
工作栏已展开时,资源按钮才显示选中状态。
|
||||
资源监控首期不在后台采样,因此不显示负载告警徽标。后台事件不自动展开工作栏或切换 Tab。
|
||||
|
||||
### 4.4 资源采样状态
|
||||
|
||||
@@ -134,44 +166,74 @@ warming/loading/live/partial/error -> inactive
|
||||
|
||||
## 5. 设置保存规则
|
||||
|
||||
应用导航偏好与应用业务启停分开保存。Switch 每次切换立即提交完整目标集合;请求完成前禁用
|
||||
三个应用显隐 Switch,下一次操作基于最新确认结果提交,防止并发更新互相覆盖。
|
||||
应用导航偏好与业务启停使用独立字段,通过同一应用设置保存链路提交。常驻、排序、启用和
|
||||
魔法笔记设置更新串行执行;前一请求确认前,所有入口中会修改这组设置的控件暂不可操作。
|
||||
每次只提交本次变更,Main 在最新已保存设置上合并并原子写入;顺序提交完整目标顺序。
|
||||
持久化成功后由 Store 发布完整权威快照,应用中心、主导航、当前应用页、会话和所有设置入口同步采用,工具配置写入也走同一通知链路。每次重开中心先刷新并锁定修改;较新的设置事件使在途旧读取及旧保存响应失效,不能覆盖新快照。
|
||||
|
||||
保存失败时:
|
||||
|
||||
- 主导航和 Switch 恢复最后一次成功保存的偏好。
|
||||
- 设置行显示可重试错误。
|
||||
- 不回滚或修改各应用的业务状态。
|
||||
- 所有界面保留最后一次确认的配置,包括启用、常驻、顺序、徽标和会话入口;不提前运行关闭或恢复副作用。
|
||||
- 发起操作的设置行或排序区域显示就地可重试错误,保留焦点及编辑上下文,不重复显示同一错误通知。
|
||||
- 若响应中断导致保存结果未知,先重读权威设置再开放修改;不能把本地旧值当成回滚成功,也不自动重发变更。
|
||||
|
||||
## 6. 与需求的对应关系
|
||||
|
||||
| 逻辑 | PRD | User Stories |
|
||||
| --- | --- | --- |
|
||||
| 固定入口不变量 | FR-1、FR-2 | US-A1、US-A2 |
|
||||
| 应用显隐和失败恢复 | FR-3、FR-4、FR-11、FR-12 | US-B1、US-B2、US-B4、US-B5 |
|
||||
| 工具打开决策表 | FR-5 至 FR-10 | US-C1 至 US-C7 |
|
||||
| 应用常驻和失败恢复 | FR-3、FR-4、FR-11、FR-12 | US-B1、US-B2、US-B4、US-B5 |
|
||||
| 应用中心、常驻顺序 | FR-3、FR-4、FR-5、FR-11、FR-12;PRD 第 8 节 | US-B6、US-B7 |
|
||||
| 可选应用启用恢复、知识库固定可用 | FR-3、FR-4、FR-12;PRD 第 8 节补充要求 | US-B8 |
|
||||
| 魔法笔记迁移及共同配置 | FR-11、FR-12;PRD 第 8 节补充要求 | US-B9 |
|
||||
| 工具打开决策表 | FR-6 至 FR-10 | US-C1 至 US-C7 |
|
||||
| 资源采样状态 | FR-9 | US-C6 |
|
||||
| 无障碍与窄窗口 | FR-13 | US-D1、US-D2 |
|
||||
| 活动聚合、完成通知与查看清除 | FR-14 | US-E1、US-E2、US-E3 |
|
||||
| 后续私有市场与 YAML | FR-15 | US-F1、US-F2 |
|
||||
| 服务状态、资源可用性、服务生命周期 | FR-16 | US-F3、US-F5;US-F4 入口已移除 |
|
||||
|
||||
稳定 ID 保留原编号。US-B6 为应用中心,US-B7 为顺序,US-B8 为启用联动,US-B9 为笔记迁移;
|
||||
US-F3 为服务概览,US-F4 为已移除的任务取消入口,US-F5 为服务生命周期。PRD 旧 FR-5 至 FR-10 和
|
||||
User Stories 已同步上述入口规则,工具实例行为沿用右侧工作栏契约。
|
||||
|
||||
## 7. 逻辑完整性评估
|
||||
|
||||
首期规则已覆盖固定入口、应用显隐、保存失败、系统工具匹配与显式多实例差异、本机 Home、
|
||||
托管 SSH、无活动会话、目录失效、资源部分可用、采样停止、权限边界、键盘和窄窗口。其他
|
||||
工具面板内部的取消、超时、
|
||||
连接恢复和固定目标失效继续由助手工作栏负责,本功能不复制这些状态机。
|
||||
首期覆盖应用中心访问、启用与常驻组合、顺序恢复、默认值与旧值迁移、共同设置、保存失败、
|
||||
知识库固定入口、心跳应用启停、可选应用已打开页面恢复,以及右侧实例选择、本机 Home、托管 SSH、无会话、
|
||||
目录失效、资源部分可用与采样停止。工具内部取消、超时、连接恢复及业务后台生命周期由
|
||||
相应功能负责,本功能不复制状态机。底部入口打开轻量菜单,“管理应用”才进入与设置相同的整窗 Modal。实现与验证的剩余范围见[实施进度](./progress.md)。
|
||||
|
||||
## 8. 后续产品决策
|
||||
|
||||
- 插件工具上线后,快捷栏允许固定的最大数量及溢出规则。
|
||||
- 插件应用是否允许用户排序,以及固定系统入口之间允许的排序范围。
|
||||
后续市场 URL、YAML 交换、ShareServer 目录与分发、插件注册及安装不进入首期。其协议、
|
||||
扩展身份与安装生命周期由市场功能另行确定;内置应用常驻及排序不依赖这些决策。
|
||||
|
||||
这些决策不影响首期四个系统工具的固定入口和内置应用显隐实现。
|
||||
### 8.1 本机推理管理
|
||||
|
||||
FR-16 独立于市场排期,作为可常驻的管理 Modal。所有打开入口只改变 Modal 打开状态,不改变底层工作区、项目、会话或草稿;关闭时卸载 Modal,服务生命周期独立。US-F3 只有服务列表,展示名称、状态、引擎、模型及错误。共享服务只展示一次;资源按独立服务进程归属,CPU 预热与指标不可用分别给出原因,不填 `0`,不将宿主进程占用分摊给共享线程。采样与单位见[技术设计](./technical-design.md)。ASR 没有 Worker 就绪观测,显示按需运行且状态未知;活动请求不覆盖服务状态。OCR 状态来自 Renderer Worker,向量状态来自 Broker。
|
||||
|
||||
US-F4 的 Modal 单任务取消入口已移除。后端取消行为保留:Worker 确认或退出前仍计入活动任务和停止影响,即使调用方已收到取消或超时错误;任务先完成则保留真实完成结果,不通过终止共享服务模拟单任务取消。
|
||||
|
||||
| 服务管理方式 | 可提供操作 | 结果要求(US-F5) |
|
||||
| --- | --- | --- |
|
||||
| GoodBuddy 受管子进程 | 引擎已支持的启动、停止、重启 | 共用同一服务实例;操作成功由真实进程和服务就绪状态确认 |
|
||||
| 外部服务 | 在模型与连接设置中管理 | 不列入本机服务,不推断本机归属 |
|
||||
| 进程内模型 | 引擎实际支持的加载、释放 | 不显示虚构 PID 或进程启动、停止状态 |
|
||||
|
||||
停止、重启或释放均先打开紧凑的嵌套影响确认,列出任务和影响;无已登记活动任务时说明执行前再次核对。取消确认不改变运行状态。
|
||||
确认存在时禁止关闭底层管理 Modal;提交期间两层均不能关闭,重复操作被拒绝。未提交时 Escape 只关闭确认,确认关闭后才可用 Escape 关闭管理 Modal。焦点规则由 [UI 设计](./ui-design.md#91-本机推理管理)定义。
|
||||
实际操作失败关闭旧确认、显示错误并刷新快照;刷新期间或失败时禁用服务操作。再次操作重新取得当前任务清单并要求确认,避免重复提交旧清单。
|
||||
再次启动不重放已中断任务;关闭管理 Modal、关闭管理应用或取消常驻不停止服务,卸载模型文件为独立操作。
|
||||
当前向量服务允许确认影响后停止,并拒绝确认清单未包含的新任务;OCR 仅空闲时允许释放。
|
||||
ASR 按请求加载与释放,支持独立取消;TTS 尚无执行服务。具体适配边界以
|
||||
[技术清单](./technical-design.md#111-本机推理任务与执行服务)为准。活动请求仅在影响确认中展示,主列表不展示历史。应用退出清理沿用各引擎原有路径。
|
||||
|
||||
## 9. 项目活动汇总
|
||||
|
||||
对应 [FR-14](./prd.md#fr-14-项目活动汇总),按 Conversation ID 聚合,每条会话最多一行。
|
||||
数据来源为活动会话 ID、会话中的流式消息及待回答/待审批请求,以及全部可见的运行中或
|
||||
等待处理 Task;不能只取当前项目、当前搜索结果或每条会话的最新 Task。
|
||||
等待处理 Task,以及当前应用会话内的已完成未查看集合;不能只取当前项目、当前搜索结果或每条会话的最新 Task。
|
||||
|
||||
| 优先级(高到低) | 条件 | 汇总类别 |
|
||||
| --- | --- | --- |
|
||||
@@ -179,18 +241,30 @@ warming/loading/live/partial/error -> inactive
|
||||
| 待审批 | 消息有待审批请求 | 待处理 |
|
||||
| Task 等待处理 | Task 为 `waiting_approval`,尚无更具体的消息状态 | 待处理 |
|
||||
| 运行中 | 活动会话 ID、`streaming` 消息或运行中 Task | 运行中 |
|
||||
| 已完成 | 会话在已完成未查看集合中,且无更高优先级实时状态 | 已完成 |
|
||||
|
||||
同一会话只采用最高优先级状态;Task 的 `waiting_approval` 也可能对应提问,不能直接
|
||||
标成待审批。“待处理”和“运行中”数量互斥,全局总数为两者之和;项目计数使用相同结果。
|
||||
标成待审批。“待处理”“运行中”和“已完成”数量互斥,全局总数为三者之和;项目计数使用相同结果。
|
||||
项目归属优先取会话元数据,缺少会话时取 Task 元数据;无项目归属的活动只计入全局汇总。
|
||||
|
||||
历史排队项、仅待调度的 Task、终态 Task 和未读标记本身不构成活动。活动结束且没有其他
|
||||
运行或等待条件时移除该行,不能因历史任务或未读状态继续计数。
|
||||
历史排队项、仅待调度的 Task、终态 Task 和普通未读标记本身不构成活动。完成通知规则如下:
|
||||
|
||||
- 本地运行收到成功 `done`,或当前活动运行从持久化快照确认成功完成时,记录会话完成通知。
|
||||
- 后台顶层 Task 只有在本次应用会话中已观察到该任务,随后状态转为 `completed` 时才记录通知;
|
||||
初次载入或首次出现的已完成历史任务只建立基线,子任务不单独产生通知。
|
||||
- 失败、取消不产生完成通知。实时待处理或运行中状态优先;开始新运行时清除旧完成通知,
|
||||
已观察 Task 转为 `running` 或 `waiting_approval` 时也清除对应会话的旧通知。
|
||||
- 通知只保存在 Renderer 内存,不跨重载或重启恢复,也不从普通未读状态推导。
|
||||
- 仅当目标会话实际成为当前聊天、设置未打开且文档未隐藏时清除通知;普通侧栏导航和活动菜单
|
||||
导航采用同一查看条件。已在查看的会话完成后不保留通知,恢复可见聊天时也清除通知。
|
||||
- 展开活动项目、查看菜单、设置覆盖下保留挂载的聊天、隐藏文档以及取消设置离开都不算查看。
|
||||
|
||||
没有实时状态且没有已完成未查看通知时才移除活动行。
|
||||
|
||||
级联菜单先按项目归属聚合活动行,沿用本地、SSH Host、通道的稳定项目顺序;缺少项目
|
||||
元数据的归属组及“未归属项目”组放在已知项目之后。无归属组只用于访问这些会话,不增加
|
||||
项目计数。每个项目内先列待处理会话,再列运行中会话,同类别保留来源顺序。
|
||||
项目计数。每个项目内依次列待处理、运行中、已完成会话,同类别保留来源顺序。
|
||||
|
||||
展开项目只改变菜单层级,不导航。选中项目的活动全部结束后返回项目列表;全局活动结束后
|
||||
展开项目只改变菜单层级,不导航。选中项目的活动行全部移除后返回项目列表;全局无活动行后
|
||||
关闭菜单并隐藏摘要入口。后台更新保留仍存在的焦点项,删除焦点项时将焦点
|
||||
移至当前层可用项。关闭菜单后,新的活动只恢复摘要,不会自动重新打开菜单。
|
||||
|
||||
@@ -122,6 +122,27 @@ dialog[data-variant="grid"] #catalog-items { display: grid; grid-template-column
|
||||
.sidebar > * { flex-shrink: 0; }.sidebar .history { flex: 1 1 80px; min-height: 0; }.sidebar .shortcuts { flex-shrink: 1; min-height: 80px; }
|
||||
.sidebar .resident-apps { margin-top: 12px; min-height: 96px; flex-shrink: 1; display: flex; flex-direction: column; max-height: 240px; }.resident-apps .section-label { margin-bottom: 4px; flex-shrink: 0; }#resident-list { overflow: auto; min-height: 0; }.resident-row { display: flex; align-items: center; }.resident-row > button { display: flex; align-items: center; gap: 10px; padding: 8px 10px; flex: 1; min-width: 0; text-align: left; }.resident-row > button span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }.resident-row [aria-current="page"] { background: var(--accent-subtle); color: var(--accent); }.resident-row[draggable="true"] { cursor: grab; }.resident-row .edit-actions { flex-shrink: 0; }.all-apps { display: flex; justify-content: space-between; padding: 6px 10px; font-size: 11px; color: var(--text-secondary); flex-shrink: 0; }.empty-apps { padding: 8px 10px; font-size: 11px; color: var(--text-secondary); }.app-placement { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; }.app-placement button { font-size: 11px; padding: 6px 8px; }.app-placement button[aria-pressed="true"] { border: 1px solid var(--accent-selected); }.app-catalog-row { align-items: flex-start; }.app-catalog-row > svg { margin-top: 4px; }
|
||||
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
||||
.workspace { min-height: 0; }
|
||||
.workspace-header { flex: 0 0 auto; flex-wrap: wrap; gap: 8px; align-items: center; }
|
||||
#page-label { overflow-wrap: anywhere; }
|
||||
.conversation { min-height: 0; overflow: auto; }
|
||||
.conversation.app-page { justify-content: flex-start; padding: 24px; max-width: 960px; gap: 12px; }
|
||||
.app-page h1 { font-size: 24px; }
|
||||
.model-list { width: 100%; }
|
||||
.model-service { padding: 16px 0; border-bottom: 1px solid var(--border-default); }
|
||||
.model-service p, .model-service details { color: var(--text-secondary); font-size: 12px; }
|
||||
.model-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
|
||||
.model-actions button, .inline-confirm button { border: 1px solid var(--border-default); }
|
||||
.inline-confirm { padding: 12px; margin: 8px 0; background: var(--accent-subtle); border: 1px solid var(--accent-selected); border-radius: var(--radius-control); }
|
||||
#market-preview { padding: 12px 0; border-bottom: 1px solid var(--border-default); }
|
||||
#market-preview label { display: block; margin: 8px 0; }
|
||||
#market-preview textarea, #market-preview select { width: 100%; padding: 8px; font: inherit; background: var(--surface-subtle); color: var(--text-primary); border: 1px solid var(--border-default); border-radius: var(--radius-control); }
|
||||
#market-preview textarea { resize: vertical; }
|
||||
#market-preview p, #import-preview { font-size: 12px; overflow-wrap: anywhere; }
|
||||
#market-preview summary, .model-service summary { cursor: pointer; }
|
||||
.note-segments, .center-switch, .tool-tabs { flex: 0 0 auto; }
|
||||
.center-app-info { min-width: 0; overflow-wrap: anywhere; }
|
||||
dialog, dialog::backdrop { -webkit-app-region: no-drag; }
|
||||
.launcher-trigger { display: flex; align-items: center; gap: 8px; width: 100%; padding: 12px 8px; border: 1px solid var(--border-default); background: var(--surface-raised); margin-bottom: 12px; font-size: 12px; }.launcher-trigger kbd { margin-left: auto; font: 10px var(--font-family-ui); color: var(--text-secondary); white-space: nowrap; }
|
||||
[data-variant="launcher"] .shortcut-list { grid-template-columns: repeat(4, minmax(0, 1fr)); }[data-variant="launcher"] .shortcut-button { flex-direction: column; padding: 8px 0; }[data-variant="launcher"] .icon-tile { height: 24px; }[data-variant="launcher"] .store-link { border-top: 1px solid var(--border-default); border-radius: 0; }
|
||||
[data-variant="favorites"] .section-label { padding-left: 0; }[data-variant="favorites"] .shortcut-list { gap: 0; }[data-variant="favorites"] .shortcut-button { justify-content: flex-start; padding: 8px 12px; }[data-variant="favorites"] .item-name { font-size: 12px; }[data-variant="favorites"] .shortcut-item { display: flex; align-items: center; }[data-variant="favorites"] .edit-actions { margin-left: auto; }[data-variant="favorites"] .store-link { margin-top: 12px; border: 1px dashed var(--border-default); }.count { padding: 0 6px; color: var(--text-secondary); }
|
||||
@@ -130,4 +151,4 @@ dialog[data-variant="grid"] #catalog-items { display: grid; grid-template-column
|
||||
.catalog-pack { padding: 20px 0; border-bottom: 1px solid var(--border-default); }.catalog-pack h3 { margin: 8px 0; font-size: 18px; font-weight: 550; }.pack-art { display: grid; place-items: center; float: right; width: 56px; height: 56px; border: 1px solid var(--accent-selected); background: var(--accent-subtle); color: var(--accent); border-radius: var(--radius-card); }.pack-art svg { width: 28px; height: 28px; }.pack-includes { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0; }.pack-includes span { padding: 4px 8px; font-size: 11px; background: var(--surface-subtle); border-radius: var(--radius-control); }.pack-includes svg { width: 14px; height: 14px; }.catalog-pack > button { border: 1px solid var(--accent-selected); color: var(--accent); background: var(--accent-subtle); }
|
||||
dialog[data-mode="launch"] { width: min(640px, calc(100% - 32px)); }dialog[data-mode="launch"] .catalog-row:focus-within { background: var(--accent-subtle); }.catalog-row { padding-left: 8px; padding-right: 8px; }
|
||||
@media (max-width: 1080px) { body { padding: 16px; }.app { position: relative; }.workbar { position: absolute; right: 0; top: 52px; bottom: 0; width: min(320px, 100%); box-shadow: -8px 0 24px var(--border-default); }.lab-header { flex-wrap: wrap; gap: 12px; }.design-note > span:last-child { display: none; }.conversation { padding: 32px 24px; } }
|
||||
@media (max-width: 640px) { body { padding: 12px; }.lab-header > div:first-child { flex: 1; }.variants { order: 3; width: 100%; }.variants button { flex: 1; padding: 8px 4px; font-size: 12px; }.design-note { align-items: flex-start; gap: 12px; }.app { height: auto; min-height: 0; }.app .sidebar { width: 100%; flex: 0 0 auto; border: 0; padding-top: 12px; }.brand { padding-bottom: 12px; }.app .history { display: none; }.app .workspace { display: flex; min-height: 320px; border-top: 1px solid var(--border-default); }.app .workspace .composer { display: block; }.composer-area { margin: 12px 12px 4px; margin-top: auto; }.composer-toolbar { gap: 2px; }.composer-select select { max-width: 108px; font-size: 11px; }.composer-toolbar button { font-size: 11px; padding: 6px; }.composer-toolbar .composer-icon { width: 28px; }.composer-meta { flex-wrap: wrap; gap: 0; }.shortcut-list { max-height: 300px; overflow: auto; }.lab-footer { align-items: flex-start; }.lab-footer button { white-space: nowrap; }.workbar { top: 0; width: 100%; }.design-note p { font-size: 11px; } }
|
||||
@media (max-width: 640px) { body { padding: 12px; }.lab-header > div:first-child { flex: 1; }.variants { order: 3; width: 100%; }.variants button { flex: 1; padding: 8px 4px; font-size: 12px; }.design-note { align-items: flex-start; gap: 12px; }.app { height: auto; min-height: 0; }.app .sidebar { width: 100%; flex: 0 0 auto; border: 0; padding-top: 12px; }.brand { padding-bottom: 12px; }.app .history { display: none; }.app .workspace { display: flex; min-height: 320px; border-top: 1px solid var(--border-default); }.app .workspace .conversation { display: flex; overflow: visible; padding: 16px; }.app .workspace .composer { display: block; }.composer-area { margin: 12px 12px 4px; margin-top: auto; }.composer-toolbar { gap: 2px; }.composer-select select { max-width: 108px; font-size: 11px; }.composer-toolbar button { font-size: 11px; padding: 6px; }.composer-toolbar .composer-icon { width: 28px; }.composer-meta { flex-wrap: wrap; gap: 0; }.shortcut-list { max-height: 300px; overflow: auto; }.lab-footer { align-items: flex-start; }.lab-footer button { white-space: nowrap; }.workbar { top: 0; width: 100%; }.design-note p { font-size: 11px; } }
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
<button data-variant="cards" aria-pressed="false">C · 应用预览</button>
|
||||
<button data-variant="launcher" aria-pressed="false">D · 搜索启动台</button>
|
||||
<button data-variant="favorites" aria-pressed="false">E · 常驻管理</button>
|
||||
<button data-variant="scenes" aria-pressed="false">F · 商店发现</button>
|
||||
<button data-variant="scenes" aria-pressed="false">F · 私有市场预览</button>
|
||||
</div>
|
||||
<button id="theme">切换深色</button>
|
||||
</header>
|
||||
<div class="design-note"><span id="variant-label">A / 紧凑 Dock</span><p id="variant-description"></p><span>交互原型 · 商店与指标均为演示数据</span></div>
|
||||
<div class="design-note"><span id="variant-label">A / 紧凑 Dock</span><p id="variant-description"></p><span>交互原型 · 全部状态仅在内存中,刷新重置</span></div>
|
||||
<div class="project-lab"><label>项目活动演示 <select id="project-scenario"><option value="waiting">有待处理</option><option value="running">仅运行中</option><option value="current">仅当前项目运行</option><option value="idle">全部空闲</option></select></label><span>有活动时显示摘要,悬停或点击项目向右展开会话。</span></div>
|
||||
<main class="app" data-variant="dock">
|
||||
<aside class="sidebar" aria-label="主侧栏">
|
||||
@@ -63,8 +63,8 @@
|
||||
<div id="composer-options" popover aria-label="会话选项">
|
||||
<header><strong>会话选项</strong><button popovertarget="composer-options" popovertargetaction="hide" aria-label="关闭会话选项">×</button></header>
|
||||
<label class="option-row">专家<select id="composer-expert"><option value="">默认助手</option><option>产品顾问</option><option>代码审查专家</option></select></label>
|
||||
<fieldset><legend>知识范围</legend><label><input type="checkbox" data-knowledge="产品文档"> 产品文档</label><label><input type="checkbox" data-knowledge="团队知识"> 团队知识</label></fieldset>
|
||||
<label class="option-row">检索方式<select id="composer-retrieval"><option value="auto">自动检索</option><option value="always">每次检索</option></select></label>
|
||||
<fieldset data-knowledge-control><legend>知识范围</legend><label><input type="checkbox" data-knowledge="产品文档"> 产品文档</label><label><input type="checkbox" data-knowledge="团队知识"> 团队知识</label></fieldset>
|
||||
<label class="option-row" data-knowledge-control>检索方式<select id="composer-retrieval"><option value="auto">自动检索</option><option value="always">每次检索</option></select></label>
|
||||
<div id="runtime-options"><label class="option-row"><span id="agent-label">Agent</span><select id="composer-agent"><option value="">默认</option><option value="plan">Plan</option><option value="review">Review</option></select></label><label class="option-row">快捷操作<select id="composer-command"><option value="">无</option><option value="prompt">填入总结模板</option><option value="command">原生命令 /init</option></select></label></div>
|
||||
<p class="muted">非默认选项会显示在输入框上方。</p>
|
||||
</div>
|
||||
@@ -77,7 +77,16 @@
|
||||
</main>
|
||||
<footer class="lab-footer"><span id="design-tradeoff"></span><button id="reset">恢复默认应用</button></footer>
|
||||
<div class="reference-note">设计参考:<a href="https://www.raycast.com/" target="_blank" rel="noreferrer">Raycast · 搜索与扩展</a><a href="https://www.notion.com/help/intro-to-workspaces" target="_blank" rel="noreferrer">Notion · 收藏与折叠分区</a><a href="https://code.visualstudio.com/docs/configure/profiles" target="_blank" rel="noreferrer">VS Code · Profiles</a><span>借鉴交互思路,保留 GoodBuddy 的视觉与入口分类。</span></div>
|
||||
<dialog id="catalog" aria-labelledby="catalog-title"><button id="back-to-apps" hidden>‹ 返回应用中心</button><header><div><span class="eyebrow">GOODBUDDY COLLECTION</span><h2 id="catalog-title">应用中心</h2></div><button id="close-catalog" aria-label="关闭">×</button></header><p class="muted" id="catalog-description"></p><div class="center-switch" role="group" aria-label="应用中心视图"><button data-catalog="apps">我的应用</button><button data-catalog="store">商店</button></div><input id="search" type="search" aria-label="搜索应用" placeholder="搜索应用…"><div id="catalog-items"></div><section id="app-settings" aria-label="魔法笔记设置" hidden></section><footer>常驻只表示菜单入口,不表示后台运行。安装与内容均为演示。</footer></dialog>
|
||||
<dialog id="catalog" aria-labelledby="catalog-title"><button id="back-to-apps" hidden>‹ 返回应用中心</button><header><div><span class="eyebrow">GOODBUDDY COLLECTION</span><h2 id="catalog-title">应用中心</h2></div><button id="close-catalog" aria-label="关闭">×</button></header><p class="muted" id="catalog-description"></p><div class="center-switch" role="group" aria-label="应用中心视图" hidden><button data-catalog="apps">我的应用</button><button data-catalog="store">未来私有市场</button></div><section id="market-preview" hidden>
|
||||
<label>私有市场 URL<input id="market-url" type="url" value="https://market.example.invalid" aria-describedby="market-status"></label><button id="market-fetch">模拟获取目录</button><button id="market-fail">模拟不可达</button><p id="market-status" role="status">未来预览,不发送网络请求;来源仅用于市场。</p>
|
||||
<details><summary>YAML 导入 / 导出预览(mock schema)</summary><p class="muted">仅支持下列扁平示例字段,非正式 Schema。支持 .yml / .yaml 的文本预览;不包含凭据或业务数据,不替代程序包或模型权重。</p><label>应用定义<textarea id="yaml-input" rows="9" spellcheck="false">schema: mock-v1
|
||||
id: reading
|
||||
name: 阅读空间
|
||||
version: 1.0
|
||||
entry: main
|
||||
dependencies: builtin:knowledge
|
||||
resources: bundled</textarea></label><button id="yaml-preview">预览导入</button><label>导出应用<select id="yaml-app"></select></label><button id="yaml-export">预览导出 YAML</button><pre id="yaml-output" tabindex="0" hidden></pre><div id="import-preview" hidden></div></details>
|
||||
</section><input id="search" type="search" aria-label="搜索应用" placeholder="搜索应用…"><div id="catalog-items"></div><section id="app-settings" aria-label="应用设置" hidden></section><footer>取消常驻仅隐藏捷径;关闭应用隐藏业务入口,保留数据与后台状态。此处全部为模拟。</footer></dialog>
|
||||
<div id="toast" role="status" aria-live="polite"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -14,6 +14,7 @@ const items = [
|
||||
{ id: 'notes', name: '魔法笔记', icon: 'note', detail: '记录想法、整理待办', summary: '把刚才的灵感留在这里', builtin: true },
|
||||
{ id: 'knowledge', name: '知识库', icon: 'book', detail: '整理资料、检索知识', summary: '下一次提问,从已有知识开始', builtin: true },
|
||||
{ id: 'heartbeat', name: '智能心跳', icon: 'activity', detail: '管理定时任务与主动提醒', summary: '让例行任务按计划发生', builtin: true },
|
||||
{ id: 'models', name: '本机推理监控', icon: 'layers', detail: '语音、OCR、向量任务统一管理 · 计划预览', summary: '查看本机推理任务、使用方和资源占用', builtin: true },
|
||||
{ id: 'projects', name: '项目管理', icon: 'layers', detail: '跟踪项目、任务与进度', summary: '把一个想法变成可跟进的项目', builtin: false },
|
||||
{ id: 'reading', name: '阅读空间', icon: 'book', detail: '集中阅读、批注与摘录', summary: '给长文和深度阅读留一个空间', builtin: false }
|
||||
];
|
||||
@@ -23,7 +24,7 @@ const variants = {
|
||||
cards: ['C / 应用预览', '入口展示内置应用图标,展开后用卡片说明每个应用的用途。', '适合通过内容理解应用,卡片不代表后台持续运行。'],
|
||||
launcher: ['D / 搜索启动台', '最底部就是搜索入口,也可以按 Ctrl / ⌘ K 打开。', '输入名称,用方向键与 Enter 打开已安装应用。'],
|
||||
favorites: ['E / 常驻管理', '从底部管理哪些应用常驻菜单,未常驻应用也可直接打开。', '常驻应用优先排列;入口显隐不影响安装和运行状态。'],
|
||||
scenes: ['F / 商店发现', '底部入口强调应用发现,展开后查看商店应用与安装位置。', '先模拟安装,再决定是否常驻;我的应用始终可切换访问。']
|
||||
scenes: ['F / 私有市场预览', '未来实验:私有市场 URL、模拟目录获取与 YAML 交换。', '不联网、不写入文件;mock schema 不代表正式协议。']
|
||||
};
|
||||
const $ = selector => document.querySelector(selector);
|
||||
const demoProjects = [
|
||||
@@ -134,7 +135,20 @@ window.addEventListener('scroll', () => { if ($('#project-activity').matches(':p
|
||||
renderProjects();
|
||||
let variant = 'dock';
|
||||
let catalogMode = 'apps';
|
||||
let residentApps = ['notes', 'knowledge'];
|
||||
let residentApps = items.filter(item => item.builtin && item.id !== 'models').map(item => item.id);
|
||||
let enabledApps = new Set(items.filter(item => item.builtin).map(item => item.id));
|
||||
let currentPage = '当前对话';
|
||||
let settingsApp = 'notes';
|
||||
let marketReady = false;
|
||||
let importDraft = null;
|
||||
const definitions = new Map();
|
||||
const modelServices = [
|
||||
{ id: 'ocr', name: 'OCR 文字识别', purpose: '文档入库 #12 · 正在识别第 8 / 24 页', engine: 'PaddleOCR worker', management: '受管进程', running: true, usage: '420 MB', users: '知识库 / 文档入库任务 #12' },
|
||||
{ id: 'speech', name: '语音识别', purpose: '语音输入 #18 · 正在转写', engine: '本机语音识别引擎', management: '受管进程', running: true, usage: '310 MB', users: '当前会话 / 语音输入 #18' },
|
||||
{ id: 'tts', name: '语音合成', purpose: '回复朗读 #19 · 正在合成', engine: '本机语音合成引擎', management: '进程内', running: true, usage: '180 MB', users: '当前会话 / 回复朗读 #19' },
|
||||
{ id: 'embedding', name: '向量生成', purpose: '文档向量化 #7 · 已处理 32 / 80 个片段', engine: 'BGE-small / ONNX Runtime', management: '进程内', running: true, usage: '128 MB', users: '知识库 / 文档向量化任务 #7' }
|
||||
];
|
||||
let modelConfirmation = null;
|
||||
let editingApps = false;
|
||||
let draggedApp = '';
|
||||
let installed = new Set(items.filter(item => item.builtin).map(item => item.id));
|
||||
@@ -143,23 +157,28 @@ let settingsOpen = false;
|
||||
const noteSettings = { showCount: true, mode: 'immediate', format: 'combined' };
|
||||
const noteModes = { immediate: '即时', 'after-save-auto': '保存后自动', 'after-save-manual': '保存后手动' };
|
||||
const noteFormats = { combined: '长评 + 要点', narrative: '长评', structured: '要点' };
|
||||
function renderNoteSettings() {
|
||||
$('#app-settings').innerHTML = `<div class="note-setting-row"><div><strong>常驻左侧菜单</strong><p>对应“显示魔法笔记入口”,移除后仍可从应用中心打开。</p></div><button class="demo-switch" role="switch" aria-label="常驻左侧菜单" aria-checked="${residentApps.includes('notes')}" data-note-toggle="resident"></button></div>
|
||||
function renderAppSettings() {
|
||||
const item = items.find(entry => entry.id === settingsApp);
|
||||
$('#app-settings').innerHTML = `<div class="note-setting-row"><div><strong>启用应用</strong><p>关闭后隐藏业务入口,保留常驻偏好、数据和后台状态。可在此重新开启。</p></div><button class="demo-switch" role="switch" aria-label="启用${item.name}" aria-checked="${enabledApps.has(item.id)}" data-app-toggle="enabled"></button></div>
|
||||
<div class="note-setting-row"><div><strong>常驻左侧菜单</strong><p>仅控制主导航捷径;应用关闭时记住此偏好。</p></div><button class="demo-switch" role="switch" aria-label="常驻左侧菜单" aria-checked="${residentApps.includes(item.id)}" data-app-toggle="pinned"></button></div>`;
|
||||
if (settingsApp === 'notes') $('#app-settings').innerHTML += `
|
||||
<div class="note-setting-row"><div><strong>显示未完成待办数量</strong><p>在左侧入口显示数量,超过 99 项显示 99+。本 Demo 使用 3 项示例。</p></div><button class="demo-switch" role="switch" aria-label="显示未完成待办数量" aria-checked="${noteSettings.showCount}" data-note-toggle="showCount"></button></div>
|
||||
<fieldset class="note-setting-options"><legend>AI 评论方式</legend><div class="note-segments" role="group" aria-label="AI 评论方式">${Object.entries(noteModes).map(([value, label]) => `<button data-note-mode="${value}" aria-pressed="${noteSettings.mode === value}">${label}</button>`).join('')}</div><p>即时:按回车并停止输入 5 秒后评论未保存草稿。保存后自动:保存后评论。保存后手动:点击 AI 分析后评论。</p></fieldset>
|
||||
<fieldset class="note-setting-options"><legend>AI 评论形式</legend><div class="note-segments" role="group" aria-label="AI 评论形式">${Object.entries(noteFormats).map(([value, label]) => `<button data-note-format="${value}" aria-pressed="${noteSettings.format === value}">${label}</button>`).join('')}</div><p>默认同时生成流式长评和结构化要点,也可只保留其中一种。</p></fieldset>
|
||||
<div class="note-settings-preview"><span class="eyebrow">当前配置预览</span><p>${noteModes[noteSettings.mode]} · ${noteFormats[noteSettings.format]}</p><small>选择后即时保留于本次 Demo 会话,不调用 AI、不修改真实设置。</small></div>`;
|
||||
if ($('#note-settings-summary')) $('#note-settings-summary').textContent = `当前设置:${noteModes[noteSettings.mode]} · ${noteFormats[noteSettings.format]}。待办数量${noteSettings.showCount ? '显示' : '隐藏'}。`;
|
||||
}
|
||||
function showNoteSettings() {
|
||||
function showAppSettings(id = 'notes') {
|
||||
settingsApp = id;
|
||||
const item = items.find(entry => entry.id === id);
|
||||
if (!$('#catalog').open) { catalogMode = 'apps'; $('#search').value = ''; }
|
||||
settingsOpen = true;
|
||||
$('#catalog').dataset.variant = variant;
|
||||
$('#catalog-title').textContent = '魔法笔记设置';
|
||||
$('#catalog-description').textContent = '调整入口与 AI 评论行为。应用中心和笔记页面共享这份演示配置。';
|
||||
$('#catalog-title').textContent = `${item.name}设置`;
|
||||
$('#catalog-description').textContent = '应用中心、应用页面和全局设置共用本次演示配置。';
|
||||
$('#back-to-apps').hidden = false; $('#app-settings').hidden = false;
|
||||
for (const selector of ['.center-switch', '#search', '#catalog-items']) $(selector).hidden = true;
|
||||
renderNoteSettings();
|
||||
for (const selector of ['.center-switch', '#search', '#catalog-items', '#market-preview']) $(selector).hidden = true;
|
||||
renderAppSettings();
|
||||
if (!$('#catalog').open) $('#catalog').showModal();
|
||||
$('#back-to-apps').focus();
|
||||
}
|
||||
@@ -169,54 +188,73 @@ function notify(text) {
|
||||
}
|
||||
function renderApps() {
|
||||
$('#edit-apps').textContent = editingApps ? '完成' : '编辑';
|
||||
$('#resident-list').innerHTML = residentApps.map((id, index) => {
|
||||
$('#resident-list').innerHTML = residentApps.filter(id => enabledApps.has(id) && (variant === 'scenes' || items.find(item => item.id === id).builtin)).map(id => {
|
||||
const index = residentApps.indexOf(id);
|
||||
const item = items.find(entry => entry.id === id);
|
||||
return `<div class="resident-row" data-resident="${id}" draggable="${editingApps}"><button data-open="${id}" ${$('#page-label').textContent === item.name ? 'aria-current="page"' : ''}>${icon(item.icon)}<span>${item.name}</span>${id === 'notes' && noteSettings.showCount ? '<small class="note-count" aria-label="3 项未完成待办(示例)">3</small>' : ''}</button>${editingApps ? `<div class="edit-actions"><button data-move="${id}" data-direction="-1" aria-label="上移${item.name}" ${index === 0 ? 'disabled' : ''}>↑</button><button data-pin="${id}" aria-label="移除${item.name}">×</button><button data-move="${id}" data-direction="1" aria-label="下移${item.name}" ${index === residentApps.length - 1 ? 'disabled' : ''}>↓</button></div>` : ''}</div>`;
|
||||
}).join('') || '<p class="empty-apps">点击 +,选择常驻应用。</p>';
|
||||
const subtitles = { dock: '', grid: `${installed.size} 个已安装应用`, cards: '笔记 · 知识 · 自动化', launcher: 'Ctrl / ⌘ K', favorites: `${residentApps.length} 个应用常驻菜单`, scenes: '发现你的下一个好应用' };
|
||||
const subtitles = { dock: '', grid: '内置应用', cards: '笔记 · 知识 · 自动化 · 模型', launcher: 'Ctrl / ⌘ K', favorites: '管理常驻偏好', scenes: '未来私有市场 · 模拟' };
|
||||
$('#center-entry').innerHTML = `<button id="open-center" aria-haspopup="dialog"><span class="brand-mark">${variant === 'launcher' ? icon('search') : 'GB'}</span><span><strong>${variant === 'launcher' ? '搜索应用…' : '应用中心'}</strong>${subtitles[variant] ? `<small>${subtitles[variant]}</small>` : ''}</span><span class="entry-arrow">${variant === 'scenes' ? '↗' : '⌃'}</span></button>`;
|
||||
}
|
||||
function renderCatalog() {
|
||||
const query = $('#search').value.trim().toLowerCase();
|
||||
const matches = items.filter(item => (catalogMode === 'store' ? !item.builtin : installed.has(item.id)) && `${item.name}${item.detail}`.toLowerCase().includes(query));
|
||||
const matches = items.filter(item => (catalogMode === 'store' ? marketReady && !item.builtin : installed.has(item.id) && (variant === 'scenes' || item.builtin)) && `${item.name}${item.detail}`.toLowerCase().includes(query));
|
||||
if (variant === 'favorites') matches.sort((a, b) => Number(residentApps.includes(b.id)) - Number(residentApps.includes(a.id)));
|
||||
$('#catalog-items').innerHTML = matches.map(item => {
|
||||
const ready = installed.has(item.id); const pinned = residentApps.includes(item.id);
|
||||
return `<article class="center-app">${icon(item.icon)}<div class="center-app-info"><strong>${item.name}</strong><p>${item.builtin ? '内置应用' : '商店应用 · 示例'}</p><p class="app-detail">${variant === 'cards' ? item.summary : item.detail}</p></div><div class="center-actions">${ready ? `<button data-open="${item.id}">打开</button>${item.id === 'notes' ? '<button data-note-settings aria-label="魔法笔记设置">设置</button>' : ''}<button data-pin="${item.id}" aria-pressed="${pinned}">${pinned ? '取消常驻' : '常驻左侧'}</button>` : `<button data-install="${item.id}">模拟安装</button><button data-install-pin="${item.id}">安装并常驻</button>`}</div></article>`;
|
||||
}).join('') || '<p class="empty-apps">没有匹配的应用。可以换个关键词,或切换到商店。</p>';
|
||||
return `<article class="center-app">${icon(item.icon)}<div class="center-app-info"><strong>${item.name}</strong><p>${item.builtin ? '内置应用' : '私有市场 · 示例'}${item.id === 'models' ? ' · 计划预览' : ''}${ready ? enabledApps.has(item.id) ? ' · 已启用' : ' · 已关闭' : ''}</p><p class="app-detail">${variant === 'cards' ? item.summary : item.detail}</p></div><div class="center-actions">${ready ? `${enabledApps.has(item.id) ? `<button data-open="${item.id}">打开</button>` : ''}<button data-app-settings="${item.id}">${enabledApps.has(item.id) ? '设置' : '设置 / 重新开启'}</button><button data-pin="${item.id}" aria-pressed="${pinned}">${pinned ? '取消常驻' : '常驻左侧'}</button>` : `<button data-install="${item.id}">模拟安装</button><button data-install-pin="${item.id}">模拟安装并常驻</button>`}</div></article>`;
|
||||
}).join('') || `<p class="empty-apps">${catalogMode === 'store' && !marketReady ? '请先模拟获取私有市场目录。' : '没有匹配的应用,请换个关键词。'}</p>`;
|
||||
$('#yaml-app').innerHTML = items.filter(item => installed.has(item.id)).map(item => `<option value="${item.id}">${item.name}</option>`).join('');
|
||||
document.querySelectorAll('.center-switch button').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.catalog === catalogMode)));
|
||||
}
|
||||
function openCatalog(mode = 'apps') {
|
||||
importDraft = null; $('#import-preview').hidden = true;
|
||||
if (variant !== 'scenes') mode = 'apps';
|
||||
settingsOpen = false; $('#back-to-apps').hidden = true; $('#app-settings').hidden = true;
|
||||
for (const selector of ['.center-switch', '#search', '#catalog-items']) $(selector).hidden = false;
|
||||
$('.center-switch').hidden = variant !== 'scenes';
|
||||
$('#market-preview').hidden = mode !== 'store';
|
||||
catalogMode = mode; $('#catalog').dataset.variant = variant;
|
||||
$('#catalog-title').textContent = mode === 'store' ? '发现新应用' : '应用中心';
|
||||
$('#catalog-description').textContent = mode === 'store' ? '安装你需要的应用,自主决定是否常驻左侧菜单。' : '打开应用,或把常用应用留在左侧。系统工具请使用右侧工作栏。';
|
||||
$('#catalog-title').textContent = mode === 'store' ? '私有市场 · 未来预览' : '应用中心';
|
||||
$('#catalog-description').textContent = mode === 'store' ? '目录和安装均为模拟,未接入 ShareServer。导入不会启动模型或任务。' : '内置应用无需安装;启用与常驻分别设置。系统工具请使用右侧工作栏。';
|
||||
$('#search').value = ''; renderCatalog();
|
||||
if (!$('#catalog').open) $('#catalog').showModal();
|
||||
$('#search').focus();
|
||||
}
|
||||
function openPage(name) {
|
||||
if ($('#catalog').open) $('#catalog').close();
|
||||
function openPage(name, keepSettings = false) {
|
||||
currentPage = name;
|
||||
modelConfirmation = null;
|
||||
if ($('#catalog').open && !keepSettings) $('#catalog').close();
|
||||
$('#page-label').textContent = name;
|
||||
$('#page-content').replaceChildren();
|
||||
const title = document.createElement('h1'); title.textContent = name;
|
||||
const description = document.createElement('p'); description.className = 'muted'; description.textContent = '主内容区页面预览。右侧工作栏保持当前工具状态。';
|
||||
$('#page-content').append(title, description);
|
||||
$('#page-actions').innerHTML = name === '魔法笔记' ? `<button data-note-settings>${icon('settings')} 笔记设置</button>` : '';
|
||||
const app = items.find(item => item.name === name);
|
||||
$('.composer-area').hidden = Boolean(app) || name === '设置' || name === '运行记录';
|
||||
$('#page-content').classList.toggle('app-page', $('.composer-area').hidden);
|
||||
for (const panel of ['#composer-options', '#context-details']) if ($(panel).matches(':popover-open')) $(panel).hidePopover();
|
||||
$('#page-actions').replaceChildren();
|
||||
if (app && !enabledApps.has(app.id)) {
|
||||
description.textContent = '此应用已关闭,当前页面不可用。已有数据和选择保留;此操作不停止后台任务或模型服务。';
|
||||
renderApps(); return;
|
||||
}
|
||||
if (app?.id === 'models') renderModels();
|
||||
if (name === '魔法笔记' || name === '设置') {
|
||||
const summary = document.createElement('p'); summary.id = 'note-settings-summary'; summary.className = 'muted'; $('#page-content').append(summary);
|
||||
if (name === '设置') { const button = document.createElement('button'); button.dataset.noteSettings = ''; button.textContent = '魔法笔记设置'; $('#page-content').append(button); }
|
||||
renderNoteSettings();
|
||||
renderAppSettings();
|
||||
}
|
||||
document.querySelectorAll('.navigation button').forEach(button => button.classList.toggle('selected', button.dataset.page === name));
|
||||
renderApps(); notify(`已打开${name}`);
|
||||
renderApps(); if (!keepSettings) notify(`已打开${name}`);
|
||||
}
|
||||
function selectVariant(name) {
|
||||
variant = name; $('.app').dataset.variant = name;
|
||||
document.querySelectorAll('button[data-variant]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.variant === name)));
|
||||
[$('#variant-label').textContent, $('#variant-description').textContent, $('#design-tradeoff').textContent] = variants[name];
|
||||
history.replaceState(null, '', `#${name}`); renderApps();
|
||||
if ($('#catalog').open) openCatalog();
|
||||
const pageApp = items.find(item => item.name === currentPage);
|
||||
if (name !== 'scenes' && pageApp && !pageApp.builtin) openPage('当前对话');
|
||||
}
|
||||
function selectTool(id) {
|
||||
const content = {
|
||||
@@ -228,6 +266,76 @@ function selectTool(id) {
|
||||
$('#tool-content').innerHTML = content[id]; $('#tool-content').setAttribute('aria-labelledby', `tab-${id}`);
|
||||
document.querySelectorAll('[data-tool]').forEach(button => { button.setAttribute('aria-selected', String(button.dataset.tool === id)); button.tabIndex = button.dataset.tool === id ? 0 : -1; });
|
||||
}
|
||||
function renderModels() {
|
||||
$('#page-content').innerHTML = `<h1>本机推理监控</h1><p class="muted">统一查看语音、OCR、向量等本机推理任务与执行服务。任务进度、引擎及占用均为模拟数据。</p><h2>推理任务与服务</h2><p class="muted">下方操作作用于执行服务;启动或加载服务不会自动恢复已中断的任务。</p><div class="model-list">${modelServices.map(service => {
|
||||
const external = service.management === '外部管理';
|
||||
const active = service.management === '进程内' ? '已加载' : '运行中';
|
||||
const actions = external ? ['config'] : service.management === '进程内' ? [service.running ? 'release' : 'load'] : service.running ? ['stop', 'restart'] : ['start'];
|
||||
return `<article class="model-service"><strong>${service.name}</strong><p>${service.interrupted ? '任务已中断,需由原应用重新发起' : service.purpose}</p><p>执行服务:${service.running ? active : '未运行'} · 内存:${service.running ? service.usage : '已释放(模拟)'}</p><p>使用方:${service.interrupted ? '暂无活动使用方' : service.users}</p><div class="model-actions">${actions.map(action => `<button data-model="${service.id}" data-model-action="${action}">${modelActionLabels[action]}</button>`).join('')}</div>${modelConfirmation?.id === service.id ? `<div class="inline-confirm" role="group" aria-label="推理服务操作确认"><p>${modelActionLabels[modelConfirmation.action]} ${service.name} 将影响 ${service.interrupted ? '该执行服务,当前没有活动任务' : service.users}。仅模拟状态变化。</p><button data-model-cancel>取消</button><button data-model-confirm>确认${modelActionLabels[modelConfirmation.action]}</button></div>` : ''}<details><summary>引擎与错误详情</summary><p>${service.engine} · ${service.management}</p><p>最近错误:无(示例)。操作仅更新本页内存状态。</p></details></article>`;
|
||||
}).join('')}</div>`;
|
||||
}
|
||||
const modelActionLabels = { start: '启动', stop: '停止', restart: '重启', load: '加载', release: '释放', config: '连接配置' };
|
||||
function applyModelAction(id, action) {
|
||||
const service = modelServices.find(entry => entry.id === id);
|
||||
service.running = !['stop', 'release'].includes(action);
|
||||
if (['stop', 'release', 'restart'].includes(action)) service.interrupted = true;
|
||||
modelConfirmation = null;
|
||||
renderModels();
|
||||
$(`[data-model="${id}"]`)?.focus();
|
||||
notify(`已模拟${modelActionLabels[action]} ${service.name}`);
|
||||
}
|
||||
function previewImport() {
|
||||
importDraft = null;
|
||||
const preview = $('#import-preview'); preview.hidden = false; preview.replaceChildren();
|
||||
const values = {};
|
||||
const fields = ['schema', 'id', 'name', 'version', 'entry', 'dependencies', 'resources'];
|
||||
for (const line of $('#yaml-input').value.split(/\r?\n/).filter(line => line.trim() && !line.trim().startsWith('#'))) {
|
||||
const match = line.match(/^([a-z]+):\s*(.+)$/);
|
||||
if (!match || !fields.includes(match[1]) || Object.hasOwn(values, match[1])) { preview.textContent = '仅支持示例中的七个扁平字段,不接受重复或未知字段;请修正后重新预览。'; return; }
|
||||
values[match[1]] = match[2].trim();
|
||||
}
|
||||
if (fields.some(field => !values[field]) || values.schema !== 'mock-v1' || !/^[a-z][a-z0-9-]*$/.test(values.id) || values.entry !== 'main') { preview.textContent = '请填写完整 mock-v1 示例,id 使用小写字母、数字或连字符,entry 为 main。'; return; }
|
||||
const existing = items.find(item => item.id === values.id);
|
||||
const dependencies = values.dependencies === 'none' ? [] : values.dependencies.split(',').map(value => value.trim());
|
||||
const missing = dependencies.filter(value => !value.startsWith('builtin:') || !installed.has(value.slice(8)) || !enabledApps.has(value.slice(8)));
|
||||
const blocked = existing?.builtin || missing.length || values.resources !== 'bundled';
|
||||
const conflict = installed.has(values.id);
|
||||
const text = document.createElement('p');
|
||||
text.textContent = `${values.name} · ${values.id} · v${values.version} · 主内容区入口。依赖:${values.dependencies}。${missing.length ? `缺失或关闭:${missing.join('、')}。` : '依赖已具备(模拟)。'}资源:${values.resources}。${values.resources !== 'bundled' ? '需补充外部资源,不能安装为可用。' : ''}${existing?.builtin ? '与内置应用冲突,不允许覆盖。' : conflict ? '同标识冲突:需显式更新定义,保留启用与常驻偏好。' : '没有同标识冲突。'}`;
|
||||
preview.append(text);
|
||||
const cancel = document.createElement('button'); cancel.textContent = '取消'; cancel.onclick = () => { importDraft = null; preview.hidden = true; $('#yaml-preview').focus(); }; preview.append(cancel);
|
||||
if (!blocked) {
|
||||
importDraft = values;
|
||||
const confirm = document.createElement('button'); confirm.id = 'import-confirm'; confirm.textContent = conflict ? '确认模拟更新定义' : '确认模拟安装';
|
||||
confirm.onclick = () => {
|
||||
if (!importDraft) return;
|
||||
const draft = importDraft;
|
||||
const item = items.find(entry => entry.id === draft.id);
|
||||
if (!item) items.push({ id: draft.id, name: draft.id, icon: 'layers', detail: 'YAML 导入 · 未来示例', summary: '导入的应用定义预览', builtin: false });
|
||||
// Imported text stays in textContent/textarea; catalog markup uses the validated identifier.
|
||||
definitions.set(draft.id, { ...draft });
|
||||
if (!installed.has(draft.id)) { installed.add(draft.id); enabledApps.add(draft.id); }
|
||||
importDraft = null; preview.hidden = true; renderCatalog(); renderApps(); $('#yaml-preview').focus(); notify('已模拟安装 / 更新定义,未启动模型或任务');
|
||||
};
|
||||
preview.append(confirm);
|
||||
}
|
||||
}
|
||||
$('#market-fetch').onclick = () => {
|
||||
const valid = /^https?:\/\/[^\s/]+(?:\/[^\s]*)?$/.test($('#market-url').value.trim());
|
||||
marketReady = valid;
|
||||
$('#market-status').textContent = valid ? `已模拟获取目录:${$('#market-url').value.trim()},无网络请求。` : '请输入 http(s) 市场 URL 后重试。';
|
||||
renderCatalog();
|
||||
};
|
||||
$('#market-fail').onclick = () => { marketReady = false; $('#market-status').textContent = '模拟来源不可达,请点击模拟获取目录重试。未切换公共市场,已安装应用仍可使用。'; renderCatalog(); };
|
||||
$('#market-url').oninput = () => { marketReady = false; $('#market-status').textContent = '来源已修改,请重新模拟获取目录。'; renderCatalog(); };
|
||||
$('#yaml-preview').onclick = previewImport;
|
||||
$('#yaml-input').oninput = () => { importDraft = null; $('#import-preview').hidden = true; };
|
||||
$('#yaml-export').onclick = () => {
|
||||
const item = items.find(entry => entry.id === $('#yaml-app').value);
|
||||
const definition = definitions.get(item.id) || { schema: 'mock-v1', id: item.id, name: item.name, version: '1.0', entry: 'main', dependencies: 'none', resources: 'bundled' };
|
||||
$('#yaml-output').hidden = false;
|
||||
$('#yaml-output').textContent = '# Mock schema only; no credentials or business data\n' + Object.entries(definition).map(([key, value]) => `${key}: ${value}`).join('\n');
|
||||
};
|
||||
document.addEventListener('click', event => {
|
||||
const button = event.target.closest('button'); if (!button) return;
|
||||
const data = button.dataset;
|
||||
@@ -246,13 +354,35 @@ document.addEventListener('click', event => {
|
||||
if (data.page) openPage(data.page);
|
||||
if (data.open) openPage(items.find(item => item.id === data.open).name);
|
||||
if (data.tool) selectTool(data.tool);
|
||||
if (button.hasAttribute('data-note-settings')) showNoteSettings();
|
||||
if (data.appSettings) showAppSettings(data.appSettings);
|
||||
if (data.appToggle) {
|
||||
const id = settingsApp;
|
||||
if (data.appToggle === 'enabled') enabledApps.has(id) ? enabledApps.delete(id) : enabledApps.add(id);
|
||||
else residentApps = residentApps.includes(id) ? residentApps.filter(entry => entry !== id) : [...residentApps, id];
|
||||
renderApps(); renderComposer();
|
||||
if (currentPage === items.find(item => item.id === id).name) {
|
||||
openPage(currentPage, true);
|
||||
}
|
||||
renderAppSettings(); $(`[data-app-toggle="${data.appToggle}"]`).focus();
|
||||
}
|
||||
if (data.modelAction) {
|
||||
if (data.modelAction === 'config') {
|
||||
const row = button.closest('.model-service');
|
||||
let config = row.querySelector('.model-config');
|
||||
if (!config) { config = document.createElement('p'); config.className = 'model-config'; config.textContent = '连接配置预览:Ollama · http://localhost:11434 · 外部管理。仅展示示例,不连接或保存配置。'; row.append(config); }
|
||||
} else {
|
||||
const service = modelServices.find(entry => entry.id === data.model);
|
||||
if (service.running && service.users) { modelConfirmation = { id: data.model, action: data.modelAction }; renderModels(); $('[data-model-cancel]').focus(); }
|
||||
else applyModelAction(data.model, data.modelAction);
|
||||
}
|
||||
}
|
||||
if (button.hasAttribute('data-model-cancel')) { const id = modelConfirmation.id; modelConfirmation = null; renderModels(); $(`[data-model="${id}"]`).focus(); }
|
||||
if (button.hasAttribute('data-model-confirm') && modelConfirmation) applyModelAction(modelConfirmation.id, modelConfirmation.action);
|
||||
if (data.noteToggle || data.noteMode || data.noteFormat) {
|
||||
if (data.noteToggle === 'resident') residentApps = residentApps.includes('notes') ? residentApps.filter(id => id !== 'notes') : [...residentApps, 'notes'];
|
||||
if (data.noteToggle === 'showCount') noteSettings.showCount = !noteSettings.showCount;
|
||||
if (data.noteMode) noteSettings.mode = data.noteMode;
|
||||
if (data.noteFormat) noteSettings.format = data.noteFormat;
|
||||
renderApps(); renderNoteSettings();
|
||||
renderApps(); renderAppSettings();
|
||||
const selector = data.noteToggle ? `[data-note-toggle="${data.noteToggle}"]` : data.noteMode ? `[data-note-mode="${data.noteMode}"]` : `[data-note-format="${data.noteFormat}"]`;
|
||||
$(selector).focus();
|
||||
}
|
||||
@@ -260,7 +390,7 @@ document.addEventListener('click', event => {
|
||||
if (button.id === 'edit-apps') { editingApps = !editingApps; renderApps(); $('#edit-apps').focus(); }
|
||||
if (data.pin || data.install || data.installPin) {
|
||||
const id = data.pin || data.install || data.installPin;
|
||||
if (data.install || data.installPin) installed.add(id);
|
||||
if (data.install || data.installPin) { importDraft = null; $('#import-preview').hidden = true; installed.add(id); enabledApps.add(id); notify('已模拟安装,未启动任务或模型'); }
|
||||
if (data.pin || data.installPin) residentApps = residentApps.includes(id) ? residentApps.filter(entry => entry !== id) : [...residentApps, id];
|
||||
renderApps();
|
||||
if ($('#catalog').open) { renderCatalog(); ($(`#catalog [data-pin="${id}"]`) || $('#search')).focus(); }
|
||||
@@ -282,7 +412,15 @@ $('#resident-list').addEventListener('drop', event => {
|
||||
$('#resident-list').addEventListener('dragend', () => { draggedApp = ''; });
|
||||
$('#search').addEventListener('input', renderCatalog);
|
||||
$('#close-catalog').onclick = () => $('#catalog').close();
|
||||
$('#back-to-apps').onclick = () => { const query = $('#search').value; openCatalog(catalogMode); $('#search').value = query; renderCatalog(); ($('#catalog [data-note-settings]') || $('#search')).focus(); };
|
||||
$('#back-to-apps').onclick = () => { const query = $('#search').value; openCatalog(catalogMode); $('#search').value = query; renderCatalog(); ($(`#catalog [data-app-settings="${settingsApp}"]`) || $('#search')).focus(); };
|
||||
$('#app-settings').addEventListener('keydown', event => {
|
||||
const group = event.target.closest('.note-segments');
|
||||
if (!group || !['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
|
||||
event.preventDefault();
|
||||
const buttons = [...group.querySelectorAll('button')];
|
||||
const index = buttons.indexOf(event.target);
|
||||
buttons[event.key === 'Home' ? 0 : event.key === 'End' ? buttons.length - 1 : (index + (event.key === 'ArrowRight' ? 1 : -1) + buttons.length) % buttons.length].click();
|
||||
});
|
||||
function toggleWorkbar(visible) { $('.workbar').hidden = !visible; $('#toggle-workbar').setAttribute('aria-expanded', String(visible)); }
|
||||
$('#toggle-workbar').onclick = () => toggleWorkbar($('.workbar').hidden);
|
||||
$('#close-tool').onclick = () => { toggleWorkbar(false); $('#toggle-workbar').focus(); };
|
||||
@@ -300,7 +438,7 @@ document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Enter' && document.activeElement === $('#search')) { event.preventDefault(); actions[0].click(); }
|
||||
});
|
||||
$('#theme').onclick = () => { const dark = document.documentElement.dataset.theme !== 'dark'; document.documentElement.dataset.theme = dark ? 'dark' : 'light'; $('#theme').textContent = dark ? '切换浅色' : '切换深色'; };
|
||||
$('#reset').onclick = () => { installed = new Set(items.filter(item => item.builtin).map(item => item.id)); residentApps = ['notes', 'knowledge']; editingApps = false; renderApps(); notify('应用安装与常驻配置已恢复默认'); };
|
||||
$('#reset').onclick = () => { installed = new Set(items.filter(item => item.builtin).map(item => item.id)); residentApps = [...installed].filter(id => id !== 'models'); enabledApps = new Set(installed); Object.assign(noteSettings, { showCount: true, mode: 'immediate', format: 'combined' }); editingApps = false; definitions.clear(); importDraft = null; $('#import-preview').hidden = true; renderApps(); renderComposer(); openPage('当前对话'); notify('应用启用、常驻和笔记设置已恢复默认'); };
|
||||
let demoRunning = false;
|
||||
let demoRecording = false;
|
||||
let demoAttachments = [];
|
||||
@@ -317,8 +455,11 @@ function renderComposer() {
|
||||
const select = $(`#${id}`);
|
||||
if (select.value) addTag(select.selectedOptions[0].textContent, () => { select.value = ''; });
|
||||
}
|
||||
document.querySelectorAll('[data-knowledge]:checked').forEach(input => addTag(input.dataset.knowledge, () => { input.checked = false; }));
|
||||
if ($('#composer-retrieval').value === 'always') addTag('每次检索', () => { $('#composer-retrieval').value = 'auto'; });
|
||||
document.querySelectorAll('[data-knowledge-control]').forEach(element => { element.hidden = !enabledApps.has('knowledge'); });
|
||||
if (enabledApps.has('knowledge')) {
|
||||
document.querySelectorAll('[data-knowledge]:checked').forEach(input => addTag(input.dataset.knowledge, () => { input.checked = false; }));
|
||||
if ($('#composer-retrieval').value === 'always') addTag('每次检索', () => { $('#composer-retrieval').value = 'auto'; });
|
||||
}
|
||||
tags.hidden = !tags.childElementCount;
|
||||
$('#stop').hidden = !demoRunning;
|
||||
$('#send').disabled = !$('#message').value.trim() && $('#composer-command').value !== 'command';
|
||||
|
||||
@@ -4,20 +4,19 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 设计中 |
|
||||
| 版本 | 0.2 |
|
||||
| 日期 | 2026-09-08 |
|
||||
| 状态 | 应用中心与本机推理监控已接入源码,整体验收待完成 |
|
||||
| 版本 | 0.4 |
|
||||
| 日期 | 2026-09-17 |
|
||||
| 适用产品 | GoodBuddy 桌面端 |
|
||||
| 关联设计 | [统一界面设计系统](../../../UI-DESIGN.md)、[助手工作栏 PRD](../assistant-workbar/prd.md) |
|
||||
|
||||
## 1. 背景
|
||||
|
||||
计划修订说明:当前实施范围以[第 8 节](#8-分期)为准。本文其余章节仍包含 v0.2 的
|
||||
左下角系统工具方案;启动实现前须同步功能要求、验收及配套设计,旧快捷工具条不再列入实施。
|
||||
本版已同步应用中心方案,分期见[第 8 节](#8-分期)。左下角用于访问和管理应用,系统工具保留在右侧工作栏。
|
||||
|
||||
主侧栏当前把“当前对话”“魔法笔记”“知识库”“智能心跳”和“运行记录”放在同一导航组,
|
||||
底部长期显示“GB 本地工作区”和平台架构。底部信息不能反映用户可以执行的操作,文件、终端
|
||||
和浏览器则需要先找到右侧助手工作栏后才能打开。
|
||||
原主侧栏把“当前对话”“魔法笔记”“知识库”“智能心跳”和“运行记录”放在同一导航组,
|
||||
底部显示“GB 本地工作区”和平台架构。当前已将底部改为应用中心及独立设置入口,文件、终端
|
||||
和浏览器继续从右侧助手工作栏打开。
|
||||
|
||||
后续插件可能提供完整页面,也可能只提供辅助面板或命令。若不先区分系统入口、应用和工具,
|
||||
插件接入后会同时争用主导航、侧栏底部和助手工作栏,用户也无法判断入口会切换页面还是打开
|
||||
@@ -25,16 +24,17 @@
|
||||
|
||||
## 2. 产品定义
|
||||
|
||||
### 2.1 三类入口
|
||||
### 2.1 入口分类
|
||||
|
||||
| 类型 | 首期成员 | 默认位置 | 用户控制范围 |
|
||||
| --- | --- | --- | --- |
|
||||
| 系统入口 | 当前对话、运行记录、设置 | 主导航固定位置或侧栏底部 | 不可隐藏、卸载或改变类型 |
|
||||
| 应用 | 魔法笔记、知识库、智能心跳 | 主导航应用区 | 可控制是否显示;隐藏不删除数据 |
|
||||
| 系统工具 | 文件浏览器、终端、浏览器、资源监控 | 侧栏底部快捷工具栏 | 首期固定提供,不可卸载 |
|
||||
| 始终显示的内置应用 | 知识库 | 主导航应用区及应用中心 | 始终启用并常驻,可排序,不提供启用或常驻开关;保留工作区实际配置 |
|
||||
| 可选内置应用 | 智能心跳、魔法笔记、本机推理监控 | 应用中心及主导航常驻区 | 独立控制启用、常驻与排序;保留数据 |
|
||||
| 系统工具 | 文件浏览器、终端、浏览器、资源监控 | 右侧助手工作栏 Tab 和“+”目录 | 自带且不可卸载,按工作栏规则管理实例 |
|
||||
|
||||
运行记录是系统审计页面,不属于工具。文件浏览器、终端、浏览器和资源监控是系统工具;它们
|
||||
的快捷入口位于左下角,实际界面继续由右侧助手工作栏承载。
|
||||
的入口与实际界面均由右侧助手工作栏承载,不列入应用中心。
|
||||
|
||||
### 2.2 后续插件类型
|
||||
|
||||
@@ -43,10 +43,10 @@
|
||||
|
||||
## 3. 目标
|
||||
|
||||
- 用户从侧栏底部一次点击即可打开文件、终端、浏览器或资源监控。
|
||||
- 主导航只承载完整页面,辅助工具不占用一级页面位置。
|
||||
- 用户从侧栏底部进入应用中心,查找、打开和配置内置应用。
|
||||
- 主导航承载应用入口,本机推理监控打开 Modal,其余内置应用打开完整页面;辅助工具由工作栏承载。
|
||||
- 运行记录始终可发现,不受应用显示设置和插件状态影响。
|
||||
- 内置应用可以从一个设置位置控制主导航可见性,隐藏后仍可恢复。
|
||||
- 五个内置应用均支持排序;智能心跳、魔法笔记和本机推理监控可独立设置启用与常驻,知识库始终显示,设备共享在测试阶段默认关闭,可启用但不常驻侧栏。未常驻的已启用应用仍可从应用菜单打开。
|
||||
- 为插件应用和插件工具保留明确的入口契约,不提前建设插件安装能力。
|
||||
|
||||
## 4. 非目标
|
||||
@@ -56,7 +56,7 @@
|
||||
- 不把文件树、终端或浏览器重新实现到左侧栏。
|
||||
- 不改变助手工作栏的面板实例、停靠、执行空间和安全边界。
|
||||
- 不允许隐藏运行记录、当前对话或设置。
|
||||
- 不因隐藏应用而删除魔法笔记、知识库或智能心跳数据。
|
||||
- 不因隐藏可选应用而删除业务数据。
|
||||
- 不把“显示在侧边栏”解释为停止智能心跳计划或后台任务。
|
||||
- 不提供任意本机程序启动器。
|
||||
- 首期资源监控不采集远程主机、GPU、显存或网络吞吐,不提供后台告警和历史持久化。
|
||||
@@ -75,42 +75,43 @@
|
||||
|
||||
### FR-3 内置应用区
|
||||
|
||||
魔法笔记、知识库和智能心跳显示在当前对话与运行记录之间。应用入口打开对应一级页面,保留
|
||||
现有徽标、范围和页面状态。用户可以在设置中控制其主导航可见性。
|
||||
主导航在当前对话之后按共享保存顺序显示应用,知识库始终显示,可选应用仅显示已启用且常驻项;运行记录位于应用区之后。五个应用均可排序。本机推理监控入口打开独立 Modal,其余应用打开对应一级页面,保留
|
||||
现有徽标、范围和页面状态。管理 Modal 列出全部内置应用,知识库只提供“打开”和排序。
|
||||
|
||||
### FR-4 隐藏应用
|
||||
|
||||
隐藏应用只移除主导航入口,不删除数据、不修改业务启停状态,也不终止正在进行的操作。显隐
|
||||
Switch 更新成功后主导航立即生效;更新失败时恢复原状态并允许重试。用户可从设置恢复入口。
|
||||
取消常驻只移除主导航捷径,不删除数据、不修改启用状态,也不终止正在进行的操作。
|
||||
关闭可选应用则同步移除业务入口,保留常驻偏好;知识库不受这些偏好影响。心跳关闭后的执行边界见[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行)。
|
||||
配置保存成功后各入口使用同一已确认状态,失败时保持原状态并允许重试。
|
||||
|
||||
### FR-5 系统工具快捷栏
|
||||
### FR-5 应用中心入口
|
||||
|
||||
侧栏底部提供文件浏览器、终端、浏览器和资源监控四个图标按钮,并保留独立设置按钮。每个
|
||||
按钮具有可见工具提示和可访问名称。首期系统工具顺序固定为文件、终端、浏览器、资源监控。
|
||||
侧栏底部提供“应用”(英文 `Apps`)和独立“设置”入口,两个按钮均显示文字。点击“应用”向上展开锚定按钮的轻量菜单,无模态遮罩。菜单列出已启用应用,不按是否打开过、历史记录或常驻状态筛选;点击应用行关闭菜单,本机推理监控打开 Modal,其余应用在主内容区打开页面。
|
||||
菜单底部“管理应用”关闭菜单并打开既有管理 Modal,在其中浏览、搜索全部内置应用,为已关闭的可选应用提供设置与重新开启入口。无搜索结果时显示空状态并可清除搜索。
|
||||
首期不展示商店、安装、推荐或系统工具清单。入口具有可访问名称,Escape 关闭后恢复触发位置的焦点。
|
||||
|
||||
### FR-6 文件浏览器入口
|
||||
|
||||
点击文件按钮时展开助手工作栏,并聚焦默认存在且不可关闭的“工作区”单实例。该实例保持
|
||||
在右侧选择“工作区”Tab 时聚焦默认存在且不可关闭的单实例。该实例保持
|
||||
“跟随当前上下文”或“固定到指定对象”状态;迁移或布局修复发现实例缺失时补回,并默认跟随
|
||||
当前项目。没有项目时使用已配置的默认工作区。跟随上下文所需的路径不可用时,面板显示修复
|
||||
入口;已有有效固定目标不受当前项目和默认工作区状态影响。
|
||||
入口;已有有效固定目标不受当前项目和默认工作区状态影响。固定目标失效时修复该目标,不静默回退到其他目录。
|
||||
|
||||
### FR-7 终端入口
|
||||
|
||||
点击终端按钮时展开助手工作栏。当前执行空间有正在启动或运行中的终端时聚焦最近使用的实例;
|
||||
没有时创建一个绑定当前执行空间的新终端,已退出、已中断或失败的历史 Tab 保持不变。没有
|
||||
项目时当前执行空间为本机 Home。创建额外并行终端继续使用工作栏现有的“+”目录入口。
|
||||
选择已有终端 Tab 仅激活该实例,保持目标、会话和历史状态。通过工作栏“+”选择终端时
|
||||
创建绑定当前执行空间的新实例,不按目标复用已有实例;无项目时使用本机 Home。
|
||||
远程项目的新实例绑定其托管 SSH 执行空间和权威目录。创建失败保留当前主页面与其他实例。
|
||||
|
||||
### FR-8 浏览器入口
|
||||
|
||||
点击浏览器按钮时展开助手工作栏,并优先聚焦绑定当前 Conversation 的最近使用浏览器实例;
|
||||
没有匹配实例时创建一个。没有活动 Conversation 时聚焦最近使用的浏览器实例;完全不存在时
|
||||
创建待绑定空实例。该快捷入口不会因连续点击创建重复实例;用户通过工作栏“+”选择浏览器时
|
||||
则始终创建新实例。入口不打开系统默认浏览器,也不创建旁路 WebView。
|
||||
选择已有浏览器 Tab 保留其绑定目标、页面和导航状态;通过工作栏“+”选择浏览器时创建新实例,
|
||||
有活动 Conversation 时绑定该会话,否则创建待绑定空实例。同一 Conversation 的实例共享隔离
|
||||
登录上下文,页面与导航状态独立。入口不打开系统默认浏览器,也不创建旁路 WebView。
|
||||
|
||||
### FR-9 资源监控入口
|
||||
|
||||
点击资源监控按钮时展开助手工作栏,并打开或聚焦单实例的本机“资源监控”面板。面板显示
|
||||
通过右侧工作栏“+”打开或聚焦单实例的本机“资源监控”,已有面板直接选择其 Tab。面板显示
|
||||
整机 CPU 使用率、内存使用量与总量、磁盘读写速率,以及 GoodBuddy 桌面进程的 CPU、内存
|
||||
和进程数摘要。指标不可用时显示具体项目和原因,不能显示为 `0`。
|
||||
|
||||
@@ -119,38 +120,43 @@ Renderer 内存中最多保留最近 5 分钟。任一条件不满足时停止
|
||||
也不据此产生侧栏告警。首次采样及停止后恢复的首次采样只建立 CPU 和磁盘 IO 速率基线,
|
||||
不显示伪造的 `0` 值。
|
||||
|
||||
### FR-10 快捷入口状态
|
||||
### FR-10 工作栏入口状态
|
||||
|
||||
工具面板已激活时,对应按钮显示选中状态;面板存在但未激活时保持普通状态。运行中、错误或
|
||||
工具面板已激活时,对应 Tab 显示选中状态;面板存在但未激活时保持普通状态。运行中、错误或
|
||||
等待用户处理可以显示徽标,但必须有可访问文字,且不得自动抢占当前面板。
|
||||
|
||||
### FR-11 应用导航设置
|
||||
|
||||
设置页提供“应用与工具”管理入口。首期“应用”区域列出三个内置应用并提供“显示在侧边栏”
|
||||
Switch,切换后立即持久化。系统工具区域列出四项内置固定能力,不提供虚假的卸载或停用按钮。
|
||||
管理 Modal 的五个应用行均提供上下移动和拖动排序,不受启用或常驻状态限制;管理列表、菜单和侧栏共用一份顺序。箭头边界按完整顺序计算,搜索过滤不丢失隐藏项。智能心跳、魔法笔记和本机推理监控另提供应用设置、常驻管理;知识库提供“打开”和排序,不添加通用空设置页或开关。设备共享提供启用设置、打开和排序,不提供常驻。原工作区配置入口及心跳单条计划 enabled 保持不变。设置中“启用应用”与“常驻左侧菜单”分别保存,
|
||||
修改即时提交;请求期间串行处理并禁用会修改同一配置的控件,失败就地反馈。
|
||||
魔法笔记设置统一从应用中心对应行进入,笔记页与全局设置不再提供重复入口。持久化成功后的设置变更同步覆盖工具配置写入;每次重开中心先刷新并锁定修改,较新的确认快照优先于迟到的读取结果。
|
||||
|
||||
### FR-12 偏好持久化
|
||||
|
||||
应用可见性按设备持久化。升级时保留当前行为:已有可用内置应用继续显示;魔法笔记原有功能
|
||||
启停设置保持原意,不由新的导航可见性覆盖。
|
||||
五个应用的顺序及可选应用启用、常驻按设备持久化,重启后恢复。默认顺序为知识库、智能心跳、魔法笔记、本机推理监控、设备共享。心跳和测试阶段的设备共享默认关闭,其余应用默认启用;心跳和魔法笔记默认常驻,本机推理监控默认不常驻,设备共享不提供常驻开关;
|
||||
魔法笔记待办数量默认显示,评论方式与形式沿用现有默认值。迁移保留已保存的启用、常驻和排序选择,
|
||||
旧魔法笔记入口开关绑定业务启用值,迁为“启用应用”并保留原值;常驻使用独立配置,
|
||||
只有已发布的独立显隐偏好才映射到常驻,不能由常驻推导业务启停。旧可选应用顺序补入知识库、智能心跳默认位置并保留已有相对顺序,具体补齐规则见[技术设计](./technical-design.md#43-持久化与-ipc)。`heartbeatEnabled` 的缺失值与保存值按[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行)处理;不读取未发布草案中的 `knowledgeEnabled`。
|
||||
|
||||
### FR-13 窄窗口行为
|
||||
|
||||
窄窗口下主侧栏仍按现有抽屉方式显示。系统工具按钮保持单行且不与设置按钮重叠;空间不足时
|
||||
收起文字和非必要分隔,不隐藏四个系统工具或设置。
|
||||
窄窗口下主侧栏仍按现有抽屉方式显示,应用中心和设置保持可操作且不重叠。应用列表可滚动,
|
||||
搜索、设置及键盘排序不依赖拖拽;工具沿用右侧工作栏响应式规则,不搬到左侧。
|
||||
|
||||
### FR-14 项目活动汇总
|
||||
|
||||
用户在任一项目中都能查看跨项目的运行中与待处理会话,并从活动列表直接进入所选会话。
|
||||
汇总入口仅在有运行中或待处理会话时显示在项目选择器下方,无活动时整行隐藏。点击后先显示有活动会话的项目,
|
||||
用户在任一项目中都能查看跨项目的待处理、运行中与已完成未查看会话,并从活动列表直接进入所选会话。
|
||||
汇总入口仅在有上述活动行时显示在项目选择器下方,无活动行时整行隐藏。点击后先显示有活动会话的项目,
|
||||
悬停或点击项目展开会话子菜单,点击会话进入该条对话。项目菜单保留原有分组与顺序,并显示
|
||||
各项目的活动数量;级联菜单内的项目顺序不随待处理或运行中数量变化。
|
||||
各项目的活动数量;级联菜单内的项目顺序不随各类活动数量变化。
|
||||
汇总不受当前项目、搜索条件或最近历史条数限制。计数与排除规则见
|
||||
[活动逻辑](./logic-design.md#9-项目活动汇总),交互见[活动界面](./ui-design.md#10-项目活动汇总)。
|
||||
|
||||
验收覆盖跨项目后台运行、同会话多任务去重、提问与审批优先、较早的仍在运行或等待处理的
|
||||
会话可见,以及从带搜索条件的页面或有未保存修改的设置页跳转。结束后仅有未读标记的
|
||||
会话不保留在活动汇总中;已完成未读面板不属于本项交付。
|
||||
会话可见,以及从带搜索条件的页面或有未保存修改的设置页跳转。成功完成后保留“已完成”
|
||||
通知,直到用户实际查看该会话;从活动菜单或普通侧栏进入均可清除。设置覆盖下的聊天及
|
||||
隐藏文档不算查看。完成通知仅保留在当前应用会话内,初始任务历史不补发通知,失败和取消
|
||||
不产生完成通知;普通未读标记本身不构成活动。
|
||||
|
||||
## 6. 权限与安全
|
||||
|
||||
@@ -167,49 +173,46 @@ Switch,切换后立即持久化。系统工具区域列出四项内置固定
|
||||
- 主导航能明确区分固定系统入口和可配置内置应用。
|
||||
- 运行记录在所有应用显隐组合下保持可见并可打开。
|
||||
- 左下角不再用“本地工作区”与系统架构占据主要空间。
|
||||
- 文件、终端、浏览器和资源监控均可从左下角打开正确的工作栏面板,且不产生第二套内容界面。
|
||||
- 连续点击系统工具不会无意创建重复的浏览器或终端实例;工作区始终复用不可关闭的固定实例。
|
||||
用户通过工作栏“+”可以显式创建额外浏览器或终端实例。
|
||||
- 应用菜单只列已启用应用,点击行关闭菜单;本机推理监控打开保留底层工作区的 Modal,其余应用进入主内容区。“管理应用”打开可搜索的既有 Modal。五个应用均可排序,知识库始终可用,四个可选应用可管理启用,其中设备共享不提供常驻,首期无商店入口。
|
||||
- 系统工具仅从右侧 Tab 与“+”访问;选择 Tab 不创建或改绑实例,“+”可显式创建额外浏览器或终端。
|
||||
- 资源监控以 2 秒间隔更新,保留不超过 150 个样本;面板不活动、工作栏收起或窗口隐藏、
|
||||
最小化后停止请求新样本。
|
||||
- CPU、内存、磁盘 IO 或 GoodBuddy 指标不可用时,页面区分部分可用与整体失败。
|
||||
- 隐藏内置应用不删除数据、不停止后台计划,并能从设置恢复;保存失败时入口保持原状。
|
||||
- 取消常驻不删除数据、不停止后台计划;关闭应用按统一规则联动业务入口,保存失败时各处保持原状。
|
||||
- 魔法笔记设置迁移保留已有选择,应用中心与笔记页共用同一配置。
|
||||
- 键盘、屏幕阅读器、浅色主题、深色主题和窄窗口均可完成相同操作。
|
||||
|
||||
## 8. 分期
|
||||
|
||||
### 首期
|
||||
|
||||
- 将侧栏最底部改为内置应用中心入口,设置按钮保持独立;具体列表、宫格或卡片样式从
|
||||
Demo 中收敛确定。
|
||||
- 应用中心提供魔法笔记、知识库、智能心跳的浏览、搜索与打开,不要求安装。
|
||||
- 支持应用常驻主导航、取消常驻和排序;未常驻应用仍可从应用中心打开,隐藏不删除数据或停止后台任务。
|
||||
- 应用中心和应用页面就近提供已有功能设置,共享真实设置来源;已有全局设置入口定位到同一界面。
|
||||
- 侧栏最底部应用中心打开向上锚定的轻量菜单,无模态遮罩;底部“管理应用”进入与设置相同的整窗 Modal 和可搜索应用列表,设置按钮保持独立。
|
||||
- 应用中心提供魔法笔记、知识库、智能心跳及本轮追加的本机推理监控入口,不要求安装。本机推理监控 Modal 与服务单独实现和验证。
|
||||
- 五个应用共享排序;支持智能心跳、魔法笔记、本机推理监控常驻主导航及取消常驻,知识库始终显示。设备共享仅从应用菜单和管理 Modal 进入,业务定义见[设备共享](../device-sharing/README.md)。已启用未常驻应用仍可从菜单打开,取消常驻不删除数据或停止后台任务。
|
||||
- 通用应用设置统一从应用中心对应行进入;应用页面保留实际业务配置,本机推理监控保留“模型与连接设置”。
|
||||
魔法笔记优先接入待办数量、AI 评论方式和评论形式,入口显示与常驻控制统一,避免重复开关。
|
||||
- 文件、终端、浏览器保留在右侧工作栏 Tab,不在左下角复制入口,也不纳入应用中心。
|
||||
- 原计划的本机资源监控仍作为系统工具交付项,在右侧增加单实例面板;与应用中心分别实现和验证。
|
||||
- 保持运行记录为固定系统入口。
|
||||
|
||||
首期验收需覆盖:无商店入口时可以访问全部内置应用;取消常驻后仍可重新打开和配置;
|
||||
首期验收需覆盖:无商店入口时可从菜单访问已启用应用,并从管理 Modal 恢复关闭的可选应用;取消常驻后仍可重新打开和配置;
|
||||
不同设置入口状态一致;常驻排序重启后保留;右侧工具切换不改变当前应用页面。
|
||||
本节是范围调整,Demo 交互通过不代表正式功能已经实现。
|
||||
|
||||
### 补充要求:魔法笔记迁移与入口联动
|
||||
|
||||
- 将现有魔法笔记设置迁入应用中心的应用设置界面,笔记页面内的设置入口访问同一份配置。
|
||||
魔法笔记可常驻主导航;原全局设置位置如保留入口,应定位到共同设置界面。
|
||||
- 将现有魔法笔记设置迁入应用中心对应行的设置界面,移除笔记页面内的重复入口。
|
||||
魔法笔记可常驻主导航;移除原全局设置入口,保留已有配置值和业务数据。
|
||||
- 魔法笔记相关布尔开关默认全部开启,包括入口显示(常驻)和未完成待办数量显示。
|
||||
AI 评论方式、评论形式是单选项,沿用现有默认值,不把“全部开启”解释为同时选中多个模式。
|
||||
新默认值适用于未配置状态,迁移时保留用户已明确保存的选择。
|
||||
- 应用关闭后的入口联动必须覆盖主导航和会话页面,不能只更新应用中心卡片或一个菜单。
|
||||
知识库关闭后,左侧知识库菜单及会话中的知识库入口按钮同步移除;重新开启后按配置恢复。
|
||||
- “取消常驻”只移除主导航捷径;“关闭应用”移除该应用在主导航、会话等业务界面的入口。
|
||||
应用中心保留管理和重新开启入口。原型中仅有常驻控制,正式设计需补齐这两种状态,避免混用。
|
||||
- 入口变化不删除已有笔记或知识数据。知识库已打开页面、会话已关联知识以及后台任务的处理,
|
||||
在详细逻辑同步时分别定义,不能仅凭隐藏按钮推断功能或后台行为已经停用。
|
||||
- 知识库侧栏入口始终存在且可调整位置;会话知识范围、检索方式、标签及请求行为继续遵守知识库自身规则。心跳入口按启用和常驻显示,单条计划 enabled 仍由心跳页面管理。
|
||||
- 可选应用“取消常驻”只移除主导航捷径;“关闭应用”移除该应用在业务界面的入口。
|
||||
应用中心保留管理和重新开启入口,重新开启沿用常驻偏好,不自动导航。
|
||||
- 入口变化不删除已有数据。关闭后已打开的可选应用页显示不可用状态及恢复设置入口。
|
||||
已发出的请求和后台任务沿用各功能的生命周期规则,关闭入口不新增强制取消或重放行为。
|
||||
|
||||
补充验收:从任一配置入口关闭知识库,主导航和当前会话按钮立即同步移除,切换会话、
|
||||
重启后也不重新出现;重新开启后恢复相应入口。验证魔法笔记未配置时的默认开启,以及
|
||||
补充验收:关闭全部可选应用后,知识库侧栏入口仍可用,知识会话入口及心跳单条计划状态不变;管理 Modal 的知识库行提供“打开”和排序,不能启停或取消常驻。心跳执行边界按[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行)验收。验证魔法笔记未配置时的默认开启,以及
|
||||
已有关闭选择在迁移后保留。设置保存失败时各入口保持原状态,不能出现互相矛盾的显示。
|
||||
|
||||
### 后续:应用商店与扩展
|
||||
@@ -227,12 +230,13 @@ Switch,切换后立即持久化。系统工具区域列出四项内置固定
|
||||
市场面向组织私有化部署。用户配置市场 URL 后即可获取该来源的应用目录与详情,并按需获取
|
||||
应用;需要认证的来源复用其登录流程。市场不可达时说明原因并允许重试,不影响已安装应用
|
||||
和内置应用使用,也不自动切换到公共市场。市场 URL 只影响应用市场,不改变模型、桌面更新
|
||||
或 Runtime 的下载源。
|
||||
或 Runtime 的下载源。市场 URL 按设备保存,重启后保留;认证失效时提示重新认证。
|
||||
|
||||
应用支持以 `.yml` 导入、导出,兼容 `.yaml` 扩展名,用于组织内部共享、备份和迁移。
|
||||
导出内容为应用定义及可迁移配置,包含格式版本、应用标识、应用版本、入口声明和依赖说明;
|
||||
凭据、会话、笔记及知识库业务数据不随应用定义导出。导入前展示应用摘要、缺失依赖和与已有
|
||||
应用的冲突;同标识应用由用户选择更新或取消,不静默覆盖本机配置。
|
||||
应用的冲突;同标识应用由用户选择更新或取消,不静默覆盖本机配置。成功后展示实际版本与可用状态;
|
||||
获取、校验或更新失败保留原已安装版本与配置,并显示可重试原因。
|
||||
|
||||
YAML 描述文件不能代替模型权重或插件程序包。有外部依赖时应明确资源引用;离线导入仅在
|
||||
所需资源已具备时可完成使用,否则列出需要补充的资源,不显示为可运行。导入应用不会自动
|
||||
@@ -245,19 +249,29 @@ YAML 描述文件不能代替模型权重或插件程序包。有外部依赖时
|
||||
|
||||
### FR-16 本机模型管理入口(新增计划项)
|
||||
|
||||
应用中心提供“本机模型”内置管理应用,可选择常驻左侧,打开主内容区管理页。该入口统一
|
||||
查看各功能使用的本机模型及服务状态,包括已接入的 OCR、向量、语音或推理引擎;具体支持
|
||||
清单需核对现有模型服务后确定,不因提供入口而假定全部引擎均已支持进程控制。
|
||||
应用中心提供“本机推理监控”内置管理应用,可选择常驻左侧。从常驻入口、应用菜单或管理 Modal 的“打开”均进入同一独立管理 Modal;来源菜单或管理 Modal 关闭,底层工作区、当前项目、会话及草稿保持不变。该入口统一
|
||||
查看本机语音识别、OCR、向量生成等服务的运行状态与可用资源指标。
|
||||
|
||||
页面展示模型名称、用途、所属引擎、管理方式、运行状态、占用情况及使用它的应用或任务。
|
||||
GoodBuddy 管理的进程提供启动、停止、重启和错误查看;外部启动的模型服务标明“外部管理”,
|
||||
提供连接状态与配置入口,未接入其管理接口时不提供停止或重启。进程内运行的模型按引擎实际
|
||||
能力提供加载与释放,不伪造独立进程。
|
||||
Modal 只有服务列表,显示名称、简短状态、引擎、模型、资源和错误,不显示任务历史、任务页签、单任务取消或外部连接列表。独立向量进程显示 CPU 与工作集内存,并标明进程范围;CPU 预热、采样失败和共享进程 worker 无法独立统计时说明原因,不能用零或整个应用进程占用代替服务指标。活动请求可能仍在排队或加载,不能据此宣称服务正在运行。ASR 当前仅显示按需运行且状态未知;OCR 和向量服务使用各自执行模块的状态。
|
||||
|
||||
停止或重启前展示正在使用该服务的任务和影响,由用户决定是否继续;取消时保留运行状态,
|
||||
失败时显示实际状态和原因。多个应用共用同一模型服务时展示同一个服务及其使用方,不重复
|
||||
启动进程。隐藏管理入口、关闭管理页面或取消应用常驻均不停止模型;卸载模型文件是独立操作。
|
||||
服务行保留实际支持的启动、停止、重启或模型加载、释放,工具栏保留刷新和模型设置入口。停止后重新启动不自动重放已中断任务。
|
||||
|
||||
资源监控仍是只读系统工具,本机模型管理负责模型生命周期,两者共享可用的状态来源。
|
||||
该项可独立于商店交付;当前只纳入计划,具体分期、任务使用期间的停止策略和各引擎管理能力
|
||||
在详细设计中确定。原首期资源监控的 GoodBuddy 桌面进程统计范围不因此扩大。
|
||||
停止、重启或释放前,在管理 Modal 上方打开紧凑影响确认,展示正在使用该服务的任务和影响;没有已登记活动任务时也说明执行前会再次核对。取消时保留运行状态,
|
||||
失败时关闭旧确认、显示原因并刷新状态,再次操作须重新确认当前影响。多个应用共用同一模型服务时只展示同一个服务,不重复
|
||||
启动进程。隐藏管理入口、关闭管理 Modal 或取消应用常驻均不停止模型;卸载模型文件是独立操作。
|
||||
确认打开时不能关闭底层管理 Modal;Escape 只关闭最上层,提交期间禁止关闭及重复执行。关闭后恢复可用入口焦点,具体规则见 [UI 设计](./ui-design.md#91-本机推理管理)。
|
||||
|
||||
资源监控仍是只读系统工具,本机推理监控聚焦本地服务状态。该项独立于商店接入生产源码,支持 OCR 空闲加载与释放、内置向量进程启动/停止/重启;TTS 显示不可用。后端保留请求登记及原有取消能力,供执行路径和服务影响校验使用。各引擎边界见
|
||||
[技术清单](./technical-design.md#111-本机推理任务与执行服务),验证范围见[实施进度](./progress.md)。
|
||||
原首期资源监控的 GoodBuddy 桌面进程统计范围不因此扩大。
|
||||
|
||||
### FR-17 主机管理与云环境管理(待实现)
|
||||
|
||||
2026-09-28 确认新增两个同级应用:“主机管理”和“云环境管理”。主机管理从设置中心迁入,
|
||||
两个应用均从应用菜单、管理应用和常驻入口打开主内容区独立页面,不嵌入管理 Modal。
|
||||
应用中心负责通用应用配置,业务操作在各自页面完成。
|
||||
|
||||
导航扩展须保留已有应用排序、常驻偏好和远程能力启用值;取消主机常驻不影响云环境使用
|
||||
主机能力。两个应用的能力依赖、迁移范围及产品验收由
|
||||
[主机与云环境 PRD](../remote-host/prd.md)和[功能逻辑](../remote-host/logic-design.md)定义,
|
||||
业务页面布局见[界面设计](../remote-host/ui-design.md)。该项尚未实现,不改变上文五应用的当前状态描述。
|
||||
|
||||
@@ -1,4 +1,417 @@
|
||||
# 项目活动级联实施进度
|
||||
# 应用导航实施进度
|
||||
|
||||
## 2026-10-01:设备共享入口
|
||||
|
||||
新增 `device-sharing`,启用后应用菜单及管理 Modal 均进入主内容区页面;管理行可排序。
|
||||
首期初始实现没有启用开关,同日后续改为测试阶段默认关闭、可手动启用,不提供常驻开关。
|
||||
旧顺序通过现有存储归一化追加新项,保留原设置;权威写入要求五项完整顺序。
|
||||
ApplicationCenter、ApplicationMenu、设置迁移以及 App 入口/排序回归已通过,真实 Electron
|
||||
从应用中心进入页面并完成后端流程。精确命令、完整范围及全仓测试记录见
|
||||
[设备共享实施进度](../device-sharing/progress.md)。下方历史四应用记录保留原验证时的范围。
|
||||
|
||||
## 2026-09-28: Composer shortcut initialization follow-up
|
||||
|
||||
The composer now retains successful shortcut-settings snapshots independently of
|
||||
the startup app-info request. A failed or pending app-info read no longer drops a
|
||||
successful shortcut save, and a late startup response cannot restore a disabled
|
||||
hint. Registration remains authoritative; no default shortcut is advertised when
|
||||
registration is unavailable. This preserves the existing [composer rule](../../../UI-DESIGN.md#131-聊天).
|
||||
|
||||
The failed/pending read regressions reproduced the missing hint before the fix.
|
||||
The reported screenshot's actual registration status and IPC result were not
|
||||
captured, so its specific runtime cause remains unconfirmed.
|
||||
|
||||
Validation: App, sidebar layout, main IPC, and shortcut-service suites passed
|
||||
(500 tests, one skipped); `npm run typecheck`, ESLint on the four changed source
|
||||
and test files, and `git diff --check` passed. The first broader run hit the
|
||||
60-second command limit; the rerun completed in 95 seconds. No full-repository
|
||||
suite, native Electron screenshot reproduction, packaging, or OS registration
|
||||
probe was run for this follow-up.
|
||||
|
||||
## 2026-09-28:新建对话与搜索共用一行
|
||||
|
||||
已按[会话搜索](./ui-design.md#会话搜索)接入收起与展开状态,移除列表上方的常驻搜索头部。
|
||||
沿用会话创建、延迟搜索和项目过滤接口。后续按用户反馈恢复默认按钮的聊天加号图标、原有
|
||||
字号字重和整体左对齐;搜索展开后保留居中的同一图标。快捷键说明移至聊天输入框原生占位文案,
|
||||
具体规则见[聊天 UI 规范](../../../UI-DESIGN.md#131-聊天)。
|
||||
|
||||
本次调整验证:`App.test.tsx` 与 `AppSidebarLayout.test.ts` 合计 316 项通过;最终长标签换行
|
||||
调整后,布局 5 项与 `magic-notes-capture.electron.test.ts` 1 项复验通过。Windows Electron
|
||||
在上述 6 种侧栏组合检查原字号字重、左对齐、图标居中、文字无溢出及搜索交互,并验证
|
||||
原生输入后占位提示隐藏、清空后恢复。App 回归覆盖中英文、生图提示、macOS 快捷键格式、
|
||||
全局快捷键缺省以及设置保存后的更新和停用。类型检查、修改文件 ESLint 与差异空白检查通过。
|
||||
未运行全仓测试、打包或 macOS/Linux 原生窗口验证。模型调用 0 次。
|
||||
|
||||
此前搜索展开改动的验证记录:
|
||||
|
||||
```text
|
||||
npm test -- src/renderer/src/App.test.tsx src/renderer/src/AppSidebarLayout.test.ts tests/magic-notes-capture.electron.test.ts
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
三文件合计 311 项通过。最终动画边界与文字宽度调整后,布局及 Electron 共 5 项、App 搜索
|
||||
交互 1 项复跑通过,typecheck 通过。Electron 复用现有 App、生产 Preload、IPC 和临时 SQLite
|
||||
测试环境,以英文界面检查浅深主题下 220px、420px 侧栏和短窗口抽屉,共 6 种组合;检查
|
||||
展开过程及结束后的右边缘、列表位置、完整按钮文字、原生鼠标与 Escape、自动聚焦、关闭
|
||||
焦点恢复、点击列表保留关键词及减少动态效果。中文 App 回归同时覆盖标题、正文、项目
|
||||
过滤和加号创建。模型调用 0 次。
|
||||
|
||||
修改文件的 ESLint 与 `git diff --check` 通过。全仓 ESLint 有 10 个错误,均来自本任务未修改
|
||||
的未跟踪 `sidebar-final-probe.ts` 和 `sidebar-final-run.mjs`。未跑全仓测试、打包程序或
|
||||
macOS/Linux 窗口验证。中文新增规则经 deai-writing 扫描无阻断项,并已人工核对。
|
||||
|
||||
## 2026-10-02:补齐五秒排序检查
|
||||
|
||||
会话列表已接入 `useConversationListOrder`,保留实时行数据,定时调整展示顺序。
|
||||
交互暂停与即时操作规则见[会话置顶与操作菜单](./ui-design.md#会话置顶与操作菜单)。
|
||||
本地和消息通道共用此 Renderer 路径,未修改 Agent 执行、同步或持久化。
|
||||
|
||||
验证命令:
|
||||
|
||||
```text
|
||||
npx vitest run src/renderer/src/use-conversation-list-order.test.tsx src/renderer/src/chat-conversation.test.ts src/renderer/src/App.test.tsx -t "useConversationListOrder|conversation display order|parallel streaming conversations|summary and detail order|sorts pinned conversations|merges persisted pin metadata|dedicated pin update"
|
||||
```
|
||||
|
||||
3 个文件、23 项通过,覆盖五秒边界、连续增量、交互暂停与恢复、立即增删与置顶、
|
||||
项目/搜索范围切换、计时器清理,以及 App 中本地/通道摘要更新的实际列表接线。
|
||||
新增 hook 与相关测试的定向 ESLint、`git diff --check` 通过。未运行 Electron 窗口验证。
|
||||
全仓检查未通过:typecheck 报并行修改中的 `App.tsx:5436` 缺少 `startTransition`;
|
||||
ESLint 报打包文件及现有会话视图缓存的 refs 错误;`npm test` 在 120 秒后超时。
|
||||
额外运行的草稿置顶失败测试未通过,实际显示持久化错误而非预期的置顶错误。
|
||||
|
||||
## 2026-09-28:并行会话输出时保持列表顺序
|
||||
|
||||
修复多个会话交替输出时,左侧列表随每个增量反复换位的问题。展示顺序统一使用消息创建
|
||||
时间,摘要查询补充只读 `latestMessageAt`。交互规则见[会话置顶与操作菜单](./ui-design.md#会话置顶与操作菜单),
|
||||
时间字段与合并边界见[会话列表读取与前端保留](../assistant-workbar/execution-history-storage.md#会话列表读取与前端保留)。
|
||||
本地及远程事件的更新时间写入、快照合并判定和增量保存保持原实现。
|
||||
|
||||
聚焦验证命令:
|
||||
|
||||
```text
|
||||
npx vitest run src/renderer/src/chat-conversation.test.ts src/renderer/src/App.test.tsx src/main/assistant/assistant-database.test.ts -t "conversation display order|parallel streaming conversations|summary and detail order|sorts pinned conversations|merges persisted pin metadata|lists lightweight history|persists only the changed conversation|preserves live .* across snapshot|loads only the opened history"
|
||||
```
|
||||
|
||||
结果:3 个文件、13 项通过。覆盖并行文字增量与完成后顺序、保存时间继续推进、本地和消息
|
||||
通道的摘要/详情刷新、新消息前移、置顶合并、同时间固定顺序、空会话回退、摘要附带实时
|
||||
消息及真实 SQLite 摘要时间。历史加载回归继续验证刷新不重复保存未变化的消息。
|
||||
修改过的 7 个 TypeScript 文件定向 ESLint 通过。中文文档按 deai-writing 自查清单复核。
|
||||
|
||||
本轮验证为 App 组件事件回归与真实 SQLite 测试,未进行 Electron 窗口或真实模型调用。
|
||||
改动仅涉及桌面展示及桌面 Main 的只读摘要,未改变 Agent 协议或桌面到 Agent 的执行路径,
|
||||
无需部署 Host。全仓 typecheck、ESLint 和 `git diff --check` 通过;全量测试运行十分钟后
|
||||
超时,期间报告配置摘要和 Agent 离线安装测试失败,未取得全仓测试通过结果。
|
||||
|
||||
2026-09-23 文档校正:当前 `heartbeat` 已作为可选应用管理,默认值与执行边界见[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行),设置与导航契约见[技术设计](./technical-design.md#4-应用导航偏好)。下方日期记录保留当时实现与验证结果;其中“心跳始终显示”“无应用级开关”和旧字段删除说明不再代表当前行为。本次仅核对源码并更新文档,未重新执行功能测试。
|
||||
|
||||
## 2026-09-18:窄幅单列应用列表
|
||||
|
||||
对应 FR-11、US-B6、US-B7、US-D1。管理概览改为紧凑单列横向列表,概览与详情共用 `640px` 最大宽度;左侧显示图标、名称、状态与可换行用途,右侧放置打开、可用的设置及成组排序箭头。行间使用弱分隔线,窄窗口操作换行。保留四应用保存顺序、箭头和拖动规则、知识库与智能心跳始终显示、本机推理监控默认不常驻。现行尺寸与交互见 [UI 设计](./ui-design.md#4-应用中心与共同设置),下方历史记录保留当时布局。
|
||||
|
||||
验证:ApplicationCenter、ApplicationMenu、WorkspacePrimitives 共 54 项测试通过;`npm run typecheck`、`npm run lint`、`git diff --check` 通过。全量 `npm test` 超过 60 秒未取得汇总,不记为通过。
|
||||
|
||||
Windows Electron fixture 重新编译当前组件、生产 CSS 与中文翻译,使用真实 ApplicationSettingsStore 及临时 IPC;浅深主题分别检查 1280、960、720、640、375px 目标窗口,实际 CSS 视口因缩放约多 1px。计算布局均为单列且无横向溢出,桌面 Modal 宽度为 640px,四行共八个箭头且均可拖动。原生键盘排序、磁盘顺序读取、详情返回及关闭后的焦点恢复通过。证据为临时目录 `application-rows-Hl3X55/result.json`。截图捕获持续报 `UnknownVizError`,禁用硬件加速后仍失败,因此未完成截图审阅;此 fixture 不作为完整 App、安装包或原生指针拖放验收。
|
||||
|
||||
中文改动按 deai-writing 自查清单复核。未修改模型、OCR、任务中心或 Agent 执行逻辑,未调用模型,未创建提交。
|
||||
|
||||
## 2026-09-18:收拢通用应用设置入口
|
||||
|
||||
对应 FR-11、US-B9。移除魔法笔记页面及本机推理监控停用状态的通用“应用设置”按钮,启用、常驻和笔记评论配置统一从应用中心卡片进入。保留管理应用、设置详情返回导航、模型与连接设置及原业务控件。删除仅用于页面快捷入口的 Context、跳转回调和初始详情参数;停用时仍保留页面挂载状态。现行入口规则见 [UI 设计](./ui-design.md#41-共同应用设置页),Demo 已移除页面、停用恢复及全局设置中的重复快捷入口。
|
||||
|
||||
验证:App 入口与状态同步定向测试 14 项通过;ApplicationCenter、ApplicationMenu、LocalInferencePage、SettingsPanel 共 128 项通过。首次包含完整 App 文件的运行出现两项既有任务审批失败,以及新增笔记测试未启用 fixture 的失败;修正 fixture 后相关定向测试通过。两项审批失败与上次记录一致,本轮未修改。类型检查、全仓 lint、Demo 语法及 diff 检查通过。本轮未运行全量测试或 Electron 窗口验收,未调用模型,未创建提交;保留已有四应用排序、监控名称和默认不常驻改动。
|
||||
|
||||
## 2026-09-18:四应用共享顺序
|
||||
|
||||
对应 FR-3、FR-11、FR-12、US-B7。四张管理卡片均提供上下移动和拖动,包含关闭、未常驻以及始终显示的应用;卡片、菜单、侧栏共用 `applicationNavigation.order`。知识库和智能心跳保留无开关的始终启用、常驻行为,位置可调整,标签改为“始终显示”/`Always shown`。当前对话、运行记录、设置仍保持系统位置。搜索中箭头按完整顺序移动,拖动禁用。保留此前 `260px` 自适应网格、本机推理监控名称及默认不常驻。
|
||||
|
||||
权威设置返回及写入 schema 接受四个 ID 各一次,拒绝重复、未知和缺失项。仅存储读取补齐旧可选应用顺序及部分默认值,保留已有顺序、显式布尔值和无关设置;规则见[持久化与 IPC](./technical-design.md#43-持久化与-ipc)。没有新增存储版本、应用启用字段或第二份导航状态。
|
||||
|
||||
验证:`npm test -- src/main/application-settings-store.test.ts src/shared/goodbuddy-config-contracts.test.ts src/main/goodbuddy-config-service.test.ts src/renderer/src/ApplicationCenter.test.tsx src/renderer/src/ApplicationMenu.test.tsx src/renderer/src/LocalInferencePage.test.tsx src/renderer/src/WorkspacePrimitives.test.tsx` 共 127 项通过;App 排序保存、菜单与侧栏同步、外部事件、重开及入口行为的 9 项定向测试通过。包含完整 `App.test.tsx` 的八文件运行共 390 项通过、2 项失败,失败项是任务中心审批测试在折叠列表中找不到任务行(`App.test.tsx` 的 scheduled approvals 场景),本次没有修改该行为。`npm run typecheck`、`npm run lint` 和 `git diff --check` 通过。全量 `npm test` 运行约七分钟仍未得到汇总,已停止本次测试进程;日志复现上述两项失败,不能宣称全量通过。
|
||||
|
||||
Windows Electron fixture 使用当前 ApplicationCenter、生产 CSS/中文翻译、真实 ApplicationSettingsStore 和临时 IPC,从旧两项顺序读取后,移动知识库以及关闭且未常驻的本机推理监控,并用新 Store 实例确认磁盘结果。浅深主题各检查 1280、960、640、375px 目标宽度,实际 CSS 视口因缩放多约 1px;均显示八个排序箭头、四个可拖动卡片,无横向溢出,列数分别为三、三、二、一。详情不含排序,返回搜索焦点和重开顺序通过。最终证据目录为临时 `all-app-order-t0hDya`;已查看桌面浅色及窄屏深色截图。此项是组件与存储验证,未验收完整生产 Main/Preload 启动或安装包,原生指针拖放由组件事件测试之外另行验收。
|
||||
|
||||
中文文档运行 deai-writing 扫描,无阻断项;对本次改动的复核候选人工检查,保留明确的产品状态与验收限制。未调用模型,未修改模型管理、GPU 指标或 Agent 执行路径,未创建提交。下方记录保留各次实施时的行为,旧“固定位置”“仅常驻项排序”规则已由本条及现行设计替代。
|
||||
|
||||
## 2026-09-18:卡片排序与自适应列数
|
||||
|
||||
对应 FR-11、US-B7、US-D1、US-D2。上下移动从设置详情移到常驻可选应用卡片,箭头名称及提示包含应用名;首尾、唯一常驻项和保存锁定时禁用对应操作。拖动也仅允许常驻可选应用,固定应用及未常驻项不参与排序。卡片改用 `auto-fit/minmax`,最小宽度 `260px`,Modal 最大宽度仍为 `960px`;启用、常驻及笔记设置继续使用原详情表单。保留本机推理监控名称及默认不常驻改动。
|
||||
|
||||
Windows Electron 组件 fixture 使用当前 ApplicationCenter、生产 CSS/中文 i18n、真实 ApplicationSettingsStore 及临时 IPC。浅深主题各检查 1280、960、640、375px 目标窗口宽度,实际 CSS 视口受缩放约多 1px;分别为三、三、二、一列,均无横向溢出。检查排序持久化、详情无排序及返回搜索焦点,并人工查看桌面浅色、窄屏深色截图。证据位于临时目录 `cards-order-fixture-tucKjv`。完整 App 配合已有 Main/Preload 的两次启动尝试未产生窗口,此次 fixture 不作为完整 App 或安装包验收。
|
||||
|
||||
`npm run typecheck`、`npm run lint`、`git diff --check` 通过;`npm test -- src/renderer/src/ApplicationCenter.test.tsx src/renderer/src/ApplicationMenu.test.tsx src/renderer/src/LocalInferencePage.test.tsx src/main/application-settings-store.test.ts` 共 66 项通过,覆盖固定/未常驻项、边界、保存锁定、搜索排序及设置入口。全量 `npm test` 在 60 秒后超时,未取得完整结果。中文改动按 deai-writing 自查清单人工复核。未调用模型、修改模型管理或 GPU 指标,未创建提交。
|
||||
|
||||
## 2026-09-18:本机推理监控默认不常驻
|
||||
|
||||
对应 FR-11、US-B 的默认值与迁移场景。共享导航默认值改为本机推理监控不常驻;新安装和旧设置缺失整个导航配置时采用此值,应用仍默认启用,可从应用菜单及管理 Modal 打开。默认可选顺序及知识库、智能心跳的固定入口规则保持不变。沿用存储版本 12,已有常驻 `true`/`false` 均保留;旧默认值与主动固定没有来源标记,无法区分,不强制取消已有常驻。Demo 初始状态和恢复默认同步。
|
||||
|
||||
验证:设置存储、应用中心、菜单、推理 Modal 共 62 项测试通过,App 入口及设置同步 8 项定向测试通过;`npm run typecheck`、`npm run lint`、Demo 的 `node --check` 和 `git diff --check` 通过。全量 `npm test` 在 60 秒后超时,未取得完整结果。中文改动按 deai-writing 自查清单人工复核。本次未启动 Electron 或调用模型,不涉及 GoodBuddy Agent 或远程执行路径;未创建提交,保留此前未提交的名称调整。
|
||||
|
||||
## 2026-09-18:统一名称为本机推理监控
|
||||
|
||||
对应 FR-16、US-F3。导航、应用中心、应用设置、Modal 标题及关闭按钮统一使用“本机推理监控”,英文应用名为 Local Inference Monitor。现行功能文档与 Demo 名称同步;`local-inference`、API、组件名及服务行为保持不变,下方历史记录保留当时称谓。
|
||||
|
||||
验证:应用菜单、应用中心、Modal 的 27 项测试及 App 入口、设置同步相关的 8 项测试通过;`npm run typecheck`、修改源码 ESLint、Demo 的 `node --check` 和 `git diff --check` 通过。Demo 被 ESLint 既有规则排除。中文改动按 deai-writing 自查清单人工复核,仅同步名称及本条验证事实。本次未运行全量测试或启动 Electron 验收窗口排版。
|
||||
|
||||
## 2026-09-18:独立推理进程资源采样
|
||||
|
||||
对应 FR-16、US-F3。向量 utility process 已按 PID 接入 Electron 真实 CPU 和工作集内存,服务行显示进程范围、PID、百分比及 MiB。ASR 为主进程线程,OCR 为渲染进程 Web Worker,分别显示不能独立统计的原因。采样口径及基线规则见[技术设计](./technical-design.md)。服务 Modal 继续保持单一列表。
|
||||
|
||||
本轮验证:资源采样器、Main 服务、向量 Broker、Modal 共 39 项定向测试通过;IPC 与 OCR Bridge 另有 130 项通过,共 169 项。类型检查、修改范围 ESLint、`git diff --check` 通过。全仓 lint 仅报告未修改的 story-graph Demo 的 22 个 `document` 错误;`npm test` 超过 60 秒未完成,不记为通过。中文功能文档 deai-writing 扫描阻断项为 0,复核项中的采样限制和验证边界按事实保留。
|
||||
|
||||
真实 Windows Electron 验证直接调用生产 `createEmbeddingUtilityTransport` 和采样器,使用临时 CPU 工作负载:PID 5892 的工作集从 57,769,984 字节变为 63,090,688 字节,区间 CPU 为 98.71%;退出后返回不可用,重启得到 PID 20532 并重新预热。进程刚 spawn 时指标可能尚未登记,返回等待数据。此验证没有加载推理模型,没有调用模型 API,也没有重新验收完整 App 窗口;macOS、Linux 尚未实机验证。改动仅涉及桌面本机服务监视,不影响 GoodBuddy Agent 或远程执行。
|
||||
|
||||
## 2026-09-18:本机推理服务监视简化
|
||||
|
||||
对应 FR-16、US-F3、US-F5;US-F4 的 Modal 单任务取消入口退役。`LocalInferencePage` 直接显示服务列表,移除任务页签、历史、外部连接和长说明,保留刷新、模型设置及受支持的服务操作。现行定义见 [UI 设计](./ui-design.md#91-本机推理管理)。
|
||||
|
||||
当时没有服务级 CPU/内存采样,统一显示“暂不可用”;这一限制已由上方资源采样实现修正。ASR 模型可用时返回 `unknown`,登记活动请求不再将状态覆盖为 `running`。OCR/向量仍使用各自执行模块的状态。操作失败关闭旧确认并刷新,重试时重新确认当前影响;后端任务清单校验和 OCR 忙碌保护保留。
|
||||
|
||||
验证结果:
|
||||
|
||||
- 定向运行 LocalInferencePage、Main 服务、向量 Broker、OCR Bridge 和 IPC,5 个文件共 165 项通过;App 的本机推理入口和停用恢复场景 4 项通过。
|
||||
- `npm run typecheck` 和修改范围 ESLint 通过。`npm run lint` 仍报告无关 `docs/features/story-graph/demo.js` 的 22 个 `document` 错误。
|
||||
- `npm test` 首次超过 120 秒,再次超过 600 秒,均未完成。第二次报告的 App 失败来自旧页签断言,已改为服务列表并定向复验通过;不将全量测试记为通过。
|
||||
- 中文功能文档按 deai-writing 扫描,阻断项为 0;复核项中的状态限制和操作清单按产品事实保留。
|
||||
|
||||
本轮未启动真实 Electron 或调用模型,未重新验证原生窗口排版。改动仅影响桌面本机服务监视,不涉及 GoodBuddy Agent 或远程执行路径。未创建提交,下方历史记录保留当时的 UI 与验证边界。
|
||||
|
||||
## 2026-09-17:应用卡片与紧凑详情
|
||||
|
||||
对应 US-B6 至 US-B9、US-F3 至 US-F5。已核对 `ApplicationCenter`、`LocalInferencePage`
|
||||
及其样式:管理改为桌面双列、窄窗口单列卡片,固定应用仅有打开;可选应用提供打开和设置,
|
||||
关闭时禁用打开。启用、常驻及上下移动位于最大 `640px` 的详情,卡片拖动排序保留。
|
||||
详情使用紧凑返回标题栏,进入及返回时滚动归顶。本机推理使用内容高度 Modal,列表状态与操作
|
||||
分区并响应式排列。现行规则见 [UI 设计](./ui-design.md#4-应用中心与共同设置)。
|
||||
|
||||
本轮实现验证结果由实现方提供,本次仅更新文档,未重跑产品测试:
|
||||
|
||||
- 59 项组件测试及 8 项选定 App 测试通过;`npm run typecheck`、修改范围 ESLint 和
|
||||
`git diff --check` 通过。
|
||||
- 真实 Electron 使用当前 Renderer、已有 Main/Preload、真实 IPC 和隔离配置,四种尺寸、浅深
|
||||
主题共 32 个视图无横向溢出;导航、焦点、Escape、背景隔离及详情滚动重置已检查。
|
||||
- 实机任务列表为空态;有任务及长名称由组件测试覆盖。模型调用为 0,未重跑全量测试,
|
||||
未创建提交;此前全量测试与全仓 lint 的失败记录仍保留,不将整体验收标为完成。
|
||||
|
||||
实机报告已核对:`C:/Users/jiang/AppData/Local/Temp/opencode/application-cards-review-cUiVAc/result.json`,
|
||||
截图位于同目录。下方记录保留各轮当时的布局及验证范围,旧单列管理行和旧 Modal 尺寸不作为现行定义。
|
||||
|
||||
## 2026-09-17:本机推理改为独立 Modal
|
||||
|
||||
对应 FR-3、FR-5、FR-16、US-B1、US-B6、US-F3 至 US-F5。已核对当前 `App.tsx`、
|
||||
`LocalInferencePage.tsx`、`local-inference.css` 和 `dialog-focus.ts`:常驻入口、应用菜单及管理
|
||||
Modal 的“打开”均打开独立本机推理 Modal,保留底层工作区;其他内置应用仍打开主内容区页面。
|
||||
停止、重启和释放使用嵌套紧凑影响确认,复用背景隔离、焦点循环与恢复,按当前层处理 Escape,
|
||||
操作提交期间禁止关闭及重复执行。具体行为见 [UI 设计](./ui-design.md#91-本机推理管理)。
|
||||
|
||||
以下结果由实现代理提供,本次文档更新未重新运行代码测试:
|
||||
|
||||
- 180 项定向测试通过。
|
||||
- `npm run typecheck` 与修改范围的 ESLint 通过。
|
||||
- 完整 App 测试运行超时,未取得完整通过结果。
|
||||
- 全仓 lint 仍有 `docs/features/story-graph/demo.js` 的既有 22 个 `document` 全局声明错误。
|
||||
|
||||
随后完成真实 Electron 验证:当前 Renderer 源码配合当日已有 Main/Preload 构建,使用隔离配置,
|
||||
未模拟 IPC。三个入口均保留同一知识库工作区节点;背景隔离、Escape、焦点恢复及窄窗口侧栏
|
||||
开关回退通过。使用已有本机资产完成 OCR 模型加载与确认释放,推理请求和模型下载均为零。
|
||||
浅深主题及窄窗口未出现横向溢出,释放确认框约为 480 × 182 CSS 像素,内边距 16px、按钮高 36px。
|
||||
修正窄窗口关闭按钮换行后再次通过实机检查和 43 项相关测试。证据位于临时目录
|
||||
`inference-modal-ui-8KJFgQ/result.json` 及同目录截图;未验证重新打包后的应用。
|
||||
|
||||
`npm test` 在 240 秒边界停止,未取得全量通过结果;独立复跑心跳数据库测试为 6 项通过、
|
||||
2 项失败,均为已有的 `duplicate column name: pinned`。下方旧页面截图不作为 Modal 验收证据。
|
||||
本轮已同步源码与文档,未修改 Demo,未创建提交。
|
||||
|
||||
下方保留各轮当时的实现与验证记录,旧主内容区页面及无 props 描述不作为当前 Modal 的行为定义。
|
||||
|
||||
## 2026-09-17:生产界面密度与响应式复核
|
||||
|
||||
对应 US-B5 至 US-B9、US-F3。已审阅 `abcd-production-08rUQA` 中 A、B、D 的原生产截图。
|
||||
应用管理从全宽纵向块改为最大 `960px` 的内容高度 Modal:名称旁标明固定/可选状态,桌面操作
|
||||
同行,窄屏自然换行且排序箭头成组。共同设置统一标签、说明和开关对齐;本机推理改为弱分隔列表。
|
||||
菜单只调整共享字体和图标层级。展示规则见 [UI 设计](./ui-design.md#4-应用中心与共同设置)。
|
||||
|
||||
最终源码验证:
|
||||
|
||||
- 应用中心、应用菜单、本机推理、设置面板和共享 UI 的 5 个定向文件共 148 项通过。
|
||||
- `npm run typecheck`、改动 Renderer 文件的定向 ESLint、`git diff --check` 通过。
|
||||
- `npm run build:bundle` 成功;最后一处布局修正后,`npx electron-vite build --logLevel error` 再次成功。
|
||||
- 全仓 `npm run lint` 仍有 `docs/features/story-graph/demo.js` 的 22 个 `document` 全局声明错误,未改动该文件。
|
||||
- 完整 `npm test` 用时 `648.80s`:4,454 项通过、67 项跳过、2 项失败。失败仍为心跳数据库
|
||||
迁移的 `duplicate column name: pinned`,位于未改动的 `assistant-database.ts:9988`;全量测试
|
||||
未通过。完整日志位于临时目录 `abcd-polish-tests-VxXZ34/test.log`。
|
||||
|
||||
真实 Electron 使用仓库 package 入口、当前生产 Main/Preload/App 和独立用户目录,未替换 IPC。
|
||||
最终截图与测量位于临时目录 `abcd-production-m9I4Vy`:`1200×775` 下四行约 `75px` 高,管理
|
||||
内容区 `scrollHeight` 与 `clientHeight` 均为 `420px`,操作同行;`520×740`、`375×667` 下
|
||||
管理、共同设置和推理内容无横向溢出。已审阅桌面与窄屏浅深色截图、375px 管理和设置截图。
|
||||
管理行按钮为 `34px`/`36px`,Switch 标签点击区高 `36px`,评论选项保留共享紧凑分段控件。
|
||||
生产验证覆盖菜单 End/Escape、Modal 焦点保持、页签方向键,以及 Space 切换常驻后重载 Renderer
|
||||
确认保存。运行报告为同目录 `result.json`,截图为 `A-menu*.png`、`B-manage*.png`、
|
||||
`C-settings*.png`、`D-inference*.png` 和 `D-tasks.png`。
|
||||
|
||||
此次为界面调整,未改后端生命周期;隔离目录没有安装模型,模型请求为 0,仅检查真实服务快照与任务空态。
|
||||
日志仍有既有字体 CSP 拦截,未捕获 JavaScript 异常。未验证模型启停、安装包或整个项目的功能验收。
|
||||
工作区原有 Runtime 与 story-graph 修改保留,未创建提交。
|
||||
|
||||
## 2026-09-17:轻量应用菜单与两个固定应用
|
||||
|
||||
对应 FR-3、FR-5、FR-11、FR-12、US-B5 至 US-B8、US-D1/US-D2。当前生产源码的底部应用中心向上展开轻量锚定菜单,无模态遮罩,只列已启用应用,不按打开历史或常驻状态筛选。点击应用行关闭菜单并打开主内容区;底部“管理应用”打开既有管理 Modal。
|
||||
|
||||
[ApplicationMenu](../../../src/renderer/src/ApplicationMenu.tsx) 与 [AnchoredMenu](../../../src/renderer/src/AnchoredMenu.tsx) 承担菜单及键盘、定位;[App](../../../src/renderer/src/App.tsx) 固定依次显示知识库、智能心跳,再显示已启用且常驻的可选应用。[管理 Modal](../../../src/renderer/src/ApplicationCenter.tsx) 中两个固定应用仅提供“打开”,不提供启用、常驻、排序或通用空设置;原工作区配置及心跳单条计划 enabled 不变。
|
||||
|
||||
[设置契约](../../../src/shared/application-settings-contracts.ts) 的可选偏好仅包含 `magic-notes`、`local-inference`。未发布草案中的 `knowledgeEnabled`、`heartbeatEnabled` 已删除,不增加迁移或兼容读取。[Store](../../../src/main/application-settings-store.ts) 持久化后发布权威快照的链路保留,包含配置工具写入;重开刷新期间锁定修改,较新快照使迟到的旧读取和保存响应失效。
|
||||
|
||||
本节收录本轮提供的最新验证结果;此次文档编辑未重新运行产品测试或模型请求:
|
||||
|
||||
- `npm run typecheck` 通过,`npx eslint src` 通过。
|
||||
- 完整 `npm test` 已结束:4,448 项通过、67 项跳过、2 项失败。两项仍为心跳数据库迁移的 `duplicate column name: pinned`,相关迁移未改动;全量测试未通过。
|
||||
- 全仓 `npm run lint` 仍有 `docs/features/story-graph/demo.js` 的 22 个 `document` 全局声明错误,属于无关文档 Demo;全仓 lint 未通过。
|
||||
- Electron fixture 共 8 个主题/尺寸场景通过,覆盖原生键盘、焦点、菜单几何、原生视图遮挡及实际 `ApplicationSettingsStore` 持久化。此证据验证组件 fixture 和真实设置存储,不是完整生产 App 的端到端验收,也不是安装包验收。
|
||||
|
||||
完整生产 App 补充验证:`npm run build:bundle` 成功后,以隔离用户目录启动仓库 package 入口,
|
||||
使用实际 `out/main/index.js`、生产 Preload 和 App,未替换 IPC handlers。已验证上拉菜单、
|
||||
“管理应用”Modal、两个固定应用仅有“打开”、笔记常驻切换,以及刷新 Renderer 和磁盘配置的
|
||||
持久化结果;本机推理页通过真实注册 IPC 取得 ASR、OCR、向量和 TTS 状态。已审阅菜单与管理截图。
|
||||
证据位于临时目录 `abcd-production-08rUQA` 的 `result.json`、`A-menu.png`、`B-manage.png`、
|
||||
`D-inference.png` 和 `runtime.log`。生产启动日志有字体被 CSP 阻止的消息,未捕获 JavaScript 异常。
|
||||
此轮未重启整个应用验证持久化;隔离目录没有安装模型,未执行模型启停或模型请求。
|
||||
|
||||
A–D 已接入源码并通过上述生产 App 冒烟检查;不将全量测试、安装包及整个计划标为通过。
|
||||
D 的真实 ASR、OCR、向量引擎证据及最小 Main 接线限制保留在下方原记录中,本次状态读取不扩大
|
||||
其模型操作验证范围。本机资源监控仍待实现。Demo 的直接 Modal 和知识库/心跳可选设置已过时,本轮未编辑 Demo 文件。
|
||||
|
||||
下方为历史记录,保留当时实现、测试结果及限制,旧应用级开关和旧入口描述不作为现行行为定义。
|
||||
|
||||
## 2026-09-16:知识库固定入口与设置同步更正
|
||||
|
||||
本节更正下方早期记录的现行行为,历史实现和验证记录原文保留,不作为最新修订已重新通过验证的证据。
|
||||
A–D 的应用中心 Modal、可选应用共同设置、常驻排序及本机推理均已接入生产源码,整体验收边界仍见各次记录。
|
||||
|
||||
本次文档核对的源码证据:
|
||||
|
||||
- [共享设置契约](../../../src/shared/application-settings-contracts.ts) 将可编辑 ID 限为魔法笔记、智能心跳、本机推理;知识库不参与启用、常驻或排序。版本 12 为未发布分支格式,`knowledgeEnabled` 已移除,无兼容旧草案要求。
|
||||
- [应用中心](../../../src/renderer/src/ApplicationCenter.tsx) 的知识库行只有“打开”;[App](../../../src/renderer/src/App.tsx) 在首个可选槽位后插入固定知识库,再过滤可选应用。知识库始终显示,原工作区实际配置保留;旧的禁用知识库请求守卫说明已从现行设计中删除。
|
||||
- [设置 Store](../../../src/main/application-settings-store.ts) 持久化成功后发布 `onChanged`;[IPC](../../../src/main/ipc.ts) 转发完整快照,覆盖[配置工具服务](../../../src/main/goodbuddy-config-service.ts) 的写入。App 重开中心时刷新并锁定修改,较新的事件使在途旧读取和保存响应失效。
|
||||
- [向量 Broker](../../../src/main/knowledge/embedding-inference-broker.ts) 区分调用方返回与 Worker 执行结束;取消或超时后,执行记录在 Worker 确认或退出前仍占用在途名额并计入活动任务和服务停止影响。
|
||||
|
||||
本次仅更新文档并核对源码、链接和文本一致性,未运行产品测试或实机验证,也未发起模型请求。
|
||||
README 已标明 Demo 中知识库启停、常驻及通用设置描述待独立同步;本次未修改 Demo 文件。
|
||||
|
||||
## 2026-09-16:D 本机推理生产接入
|
||||
|
||||
对应 FR-16、US-F3 至 US-F5。`LocalInferencePage` 默认导出、无 props,已与中心代理的
|
||||
`local-inference` 页面入口对接,未修改 App 生产入口或应用启用契约。真实支持范围见
|
||||
[技术清单](./technical-design.md#111-本机推理任务与执行服务)。ASR、OCR、向量生产请求均登记
|
||||
生命周期,服务操作和任务取消独立。内置向量 Broker 按模型目录共享;OCR 直接控制持有模型的
|
||||
Renderer Worker。没有新增任务持久化、虚构进度、资源值或 PID。
|
||||
|
||||
验证证据:
|
||||
|
||||
- 原始 Vitest 配置运行 App、IPC、应用中心、设置存储、推理服务、ASR、OCR、向量 Broker、
|
||||
Worker 和引擎,共 12 个文件、452 项通过。完整 App 测试使用真实页面模块,没有 resolve 替身。
|
||||
- 收尾新增 OCR 加载失败恢复用例后,11 个定向文件 202 项通过;加上已通过的完整 App 251 项,
|
||||
当前覆盖合计 453 项。最终 `npm run typecheck`、D 源码及测试定向 ESLint、`git diff --check`
|
||||
均通过;diff 检查仅有工作区已有换行符提示。验证生成的临时 bundle 已清理。
|
||||
- Windows Electron 隔离用户目录,加载当前页面与生产 CSS、生产 Preload、实际引擎模块,
|
||||
通过临时最小 Main IPC 接线运行已安装的 SenseVoice、PaddleOCR、Granite 模型,未修改模型配置。
|
||||
最终一轮:3 次向量请求(含完成/取消竞态)、1 次 ASR 静音采样请求、1 次 OCR 图片请求。
|
||||
向量返回 384 维;UI 停止、启动后再次推理成功;隐藏窗口保持服务运行;OCR 识别 `HELLO`,
|
||||
UI 加载与释放成功;1080px 和 520px 窗口无横向溢出。未调用外部付费模型。
|
||||
- 实机取消竞态中执行方自然完成,管理页正确保留完成结果;取消确认和共享进程不被终止的分支
|
||||
另由 Broker 回归测试验证。停止失败、启动失败重试、新任务影响清单变化由定向测试覆盖。
|
||||
- 真实向量验证发现浏览器 ONNX `/wasm` 入口无法 fetch 本地资源;Main 改为 Node 条件入口后
|
||||
实机推理通过,具体执行错误可回传任务列表。
|
||||
|
||||
限制与阻塞:本轮不是打包安装包验收,也未驱动完整生产 App 的所有入口与原生浏览器视图。
|
||||
实机测试 Main 使用最小接线,完整生产 IPC 注册和 App 接入由上述原始测试及类型检查覆盖。
|
||||
全量 `npm test` 在 200 秒预算内未结束;已复现心跳迁移两项 `duplicate column name: pinned`,
|
||||
独立重跑仍失败,和应用设置 v12 期望值无关,未修改该数据库迁移。全仓 lint 仍被并行工作目录
|
||||
`docs/features/story-graph/demo.js` 的 22 个 `document` 全局声明错误阻塞。不将全量测试或 lint
|
||||
标为通过。D 不修改 GoodBuddy Agent 或远程执行链路,无远程主机验证要求;没有创建提交。
|
||||
|
||||
本节取代下方早期记录中“缺少 LocalInferencePage”的当前阻塞描述,早期验证范围仍保留供追溯。
|
||||
|
||||
## 2026-09-16:应用中心、共同设置与知识联动
|
||||
|
||||
对应 US-A2、US-B5 至 US-B9。当前源码接入左下角应用中心和独立设置按钮;中心使用设置 Modal
|
||||
壳层,包含搜索、打开、常驻、拖动及上下移动。四个稳定 ID 为 `magic-notes`、`knowledge`、
|
||||
`heartbeat`、`local-inference`。应用启用、常驻和顺序独立持久化,固定入口及右侧工具不参与排序。
|
||||
|
||||
`ApplicationSettingsStore` 升为版本 12,保留已有 `false` 和评论单选值,为缺失项补默认值。
|
||||
笔记页与中心使用 `ApplicationSettingsView`;全局笔记入口已移除。保存期间共用锁,失败后读取
|
||||
确认值,无法确认时锁定修改并允许重新读取。关闭当前应用保留页面挂载及草稿,显示恢复入口。
|
||||
|
||||
知识关闭后移除会话知识控件与摘要,保留各会话关联;Main 交互请求入口在授予知识能力及
|
||||
检索前裁剪新请求范围,不取消已开始的请求。队列派发重新经过同一入口,本机和托管 SSH
|
||||
分支消费同一个请求对象。未修改 Skills、MCP、专家或本机推理页面与后端实现。
|
||||
|
||||
验证记录:
|
||||
|
||||
- 合并定向运行 App、IPC、配置与 Renderer 共 10 个 suite,599 项全部通过;随后新增本机推理
|
||||
主内容区入口用例,完整 App suite 251 项通过,合计覆盖 600 项;
|
||||
App 仍使用下述临时页面契约替身,其余生产代码未替换。
|
||||
- `ApplicationCenter.test.tsx`、设置、笔记、更新设置、共享界面与浏览器遮挡及配置契约的
|
||||
定向回归共 227 项通过;随后增加的保存失败、缺失笔记配置回归与配置契约共 39 项通过。
|
||||
- `ipc.test.ts` 和中心回归共 127 项通过,包含新请求禁用、在飞请求继续及重新开启的范围恢复。
|
||||
- `App.test.tsx` 共 251 项通过;因独立负责的 `LocalInferencePage.tsx` 尚未到位,使用临时
|
||||
Vitest resolve 插件提供无 props 空组件,仅用于验证 App 壳层,未替代或写入生产页面。
|
||||
- Windows Electron 使用当前中心组件、生产 CSS、真实 `ApplicationSettingsStore` 与临时
|
||||
最小 IPC 桥接验证常驻、排序、启用及重新读取持久化结果。浅深主题各验证 `1200 × 850` 和
|
||||
`680 × 560` 窗口,无水平溢出;搜索初始焦点、Escape、关闭焦点恢复及背景隔离通过。
|
||||
已审阅窄浅色和宽深色截图;证据位于本轮临时目录的 `application-center-*.png`。
|
||||
- 全量 `npm test` 的 JSON 报告为 4,153 项通过、4 项失败、67 项跳过,另有 suite 加载失败。
|
||||
其中应用配置契约旧快照已修正并定向通过;其余失败包括心跳旧库迁移两项、工作栏终端标题一项。
|
||||
终端标题用例随后单独复跑通过,未修改其实现。
|
||||
App suite 因缺少 `LocalInferencePage` 无法在原始配置下加载,不能将全量检查记为通过。
|
||||
- `npm run typecheck` 当前仅报告缺少 `./LocalInferencePage`;`npm run lint` 当前仅报告
|
||||
并行工作目录 `docs/features/story-graph/demo.js` 的 22 个 `document` 全局声明错误。
|
||||
本轮修改的生产源文件定向 Lint 通过。
|
||||
|
||||
剩余集成验证:接入另一实现负责的默认导出 `LocalInferencePage` 后重跑原始 App 测试与类型
|
||||
检查;完整应用的原生浏览器遮挡和共享 Linux Host 真实请求尚未实测。本轮真实模型调用为 0。
|
||||
本节不宣称本机推理后端、管理页或整个计划已交付,也没有生成提交。
|
||||
|
||||
## 2026-09-16:设置改为大 Modal
|
||||
|
||||
对应 [US-A2](./user-stories.md#us-a2-从固定入口进入设置)。设置入口保留底层工作区,关闭后卸载
|
||||
设置内容;布局、关闭与分类直达规则见 [设置中心 UI 规范](../../../UI-DESIGN.md#138-设置中心)。
|
||||
|
||||
`App.test.tsx`、`SettingsPanel.test.tsx`、`WorkspacePrimitives.test.tsx` 和
|
||||
`browser-viewport-occlusion.test.ts` 定向回归共 389 项通过。`npm run lint` 通过。
|
||||
全量 `npm test` 完成时为 4,354 项通过、9 项失败、67 项跳过;失败涉及本地 Runtime 复用超时、
|
||||
心跳旧库迁移、远程恢复及 ProjectSwitcher。共享工作区存在并行修改,最终 `npm run typecheck`
|
||||
报错位于非本次修改的 `use-unviewed-completions.test.tsx`,未将全量检查记为通过。
|
||||
|
||||
Windows Electron 使用临时用户目录、当前 Renderer 源码和已有 Main/Preload 输出,验证窗口
|
||||
尺寸 `1440 × 960`、`1024 × 700`、`680 × 560`(系统缩放使实际 CSS 高宽略有偏差)。
|
||||
浅色与深色检查通过:整窗背景隔离、左右分栏、无内容横向溢出、遮罩点击不关闭、原生 Tab 与
|
||||
Escape、关闭后保留原知识库组件。截图保存在本轮临时验证目录;没有运行打包构建,也未单独
|
||||
驱动原生 `WebContentsView`,其遮挡规则通过共享检测测试验证。本次未修改 GoodBuddy Agent
|
||||
或桌面到 Agent 的执行路径,未额外发起真实模型请求。
|
||||
|
||||
## 2026-09-16:已完成保留至查看
|
||||
|
||||
对应 [FR-14](./prd.md#fr-14-项目活动汇总)、[US-E3](./user-stories.md#us-e3-完成后保留至查看)。
|
||||
已核对当前工作区 `use-unviewed-completions.ts`、`conversation-activity.ts` 和 `App.tsx` 的
|
||||
完成通知接入:Renderer 会话内集合、后台 Task 转换、本地及持久化活动运行成功标记、
|
||||
三类计数,以及聊天、设置和文档可见性条件。行为定义见[活动逻辑](./logic-design.md#9-项目活动汇总)。
|
||||
|
||||
活动汇总、完成通知 Hook、项目活动组件、项目选择器及 App 活动集成定向回归共 77 项通过。
|
||||
覆盖前台完成、后台完成、持久化完成补齐、失败和取消、普通会话入口及级联菜单进入、
|
||||
设置覆盖聊天、窗口隐藏、历史完成不提醒、查看后刷新不重复提示和下一轮完成。
|
||||
实际聊天详情加载完成后才清除提示。最终 `npm run typecheck` 与 `npm run lint` 通过。
|
||||
|
||||
全量 `npm test` 用 900 秒预算运行,762.20 秒结束:4,365 项通过、3 项失败、67 项跳过。
|
||||
失败为 `agent-package.test.ts` 离线依赖清单安装内部 60 秒超时,以及
|
||||
`heartbeat-database.test.ts` 两项迁移的 `duplicate column name: pinned`。
|
||||
共享工作区存在并行修改,此项未改动这些文件,不将全量检查记为通过。
|
||||
|
||||
Windows Electron 43.2.0 / Chromium 150 的当前组件 fixture 验证浅深主题、1280×800 和
|
||||
375×667、混合状态及仅完成状态,共 8 个场景、112 项检查通过,8 张 PNG 已审阅。
|
||||
检查包含完成图标、三类计数、原生方向键与 Space、进入回调及移除后的空闲隐藏。
|
||||
证据为临时目录 `activity-completion-0916-results.json` 和 `activity-completion-0916-review.md`。
|
||||
此项使用受控活动数据,未验证完整 Main/Preload 真实模型端到端;模型及知识库请求为 0。
|
||||
本次只修改 Renderer 展示与状态跟踪,本地和远程均消费已有事件/Task 更新,不改变 Agent 执行路径。
|
||||
|
||||
## 2026-09-14:菜单独立高度修正
|
||||
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 设计中 |
|
||||
| 版本 | 0.2 |
|
||||
| 日期 | 2026-09-08 |
|
||||
| 状态 | 应用中心与本机推理监控已接入源码,整体验收待完成 |
|
||||
| 版本 | 0.4 |
|
||||
| 日期 | 2026-09-17 |
|
||||
| 关联 PRD | [应用导航与系统工具入口 PRD](./prd.md) |
|
||||
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
|
||||
| UI 设计 | [UI 设计](./ui-design.md) |
|
||||
|
||||
## 1. 现状
|
||||
|
||||
主页面没有 URL Router,由 `App.tsx` 中的 `WorkspaceView` 和 keep-alive 状态切换:
|
||||
主页面没有 URL Router,由 `App.tsx` 中的 `WorkspaceView` 和 keep-alive 状态切换;该类型也包含由独立状态打开 Modal 的入口:
|
||||
|
||||
```ts
|
||||
type WorkspaceView =
|
||||
@@ -21,6 +21,8 @@ type WorkspaceView =
|
||||
| 'magic-notes'
|
||||
| 'knowledge'
|
||||
| 'heartbeat'
|
||||
| 'local-inference'
|
||||
| 'device-sharing'
|
||||
| 'activity'
|
||||
| 'settings'
|
||||
```
|
||||
@@ -28,90 +30,72 @@ type WorkspaceView =
|
||||
文件、浏览器和终端已经由 `RightAssistantSidebar`、`WorkbarShell` 与 `workbar-contracts.ts`
|
||||
承载。工作区是不可关闭的单实例应用,浏览器和终端是多实例应用。浏览器多实例的跨进程
|
||||
实现以[浏览器多实例技术设计](../assistant-workbar/browser-tabs-technical-design.md)为准。代码中尚无资源指标采集和资源监控
|
||||
面板。现有侧栏底部 `user-card` 整行打开设置,并显示“本地工作区”和平台架构。
|
||||
面板。侧栏底部已替换为应用中心与独立设置按钮。
|
||||
|
||||
本次改动增加入口编排、从主侧栏到工作栏的命令通道,以及本机资源监控的 Main/Preload/
|
||||
Renderer 最小实现。文件、终端和浏览器后端保持不变。
|
||||
应用中心、五应用共享排序、可选应用常驻及启用联动、共同设置页和本机推理监控已接入生产源码;本机资源监控的
|
||||
Main/Preload/Renderer 实现仍待交付。系统工具只使用右侧工作栏已有 Tab 和“+”链路。
|
||||
行为由[功能逻辑](./logic-design.md)定义,验证范围见[实施进度](./progress.md)。
|
||||
|
||||
## 2. 模块边界
|
||||
|
||||
| 模块 | 责任 |
|
||||
| --- | --- |
|
||||
| `App.tsx` | 组合主导航、系统工具栏、设置入口和页面离开检查 |
|
||||
| `SidebarSystemTools`(新增) | 渲染四个系统工具按钮并发送打开意图 |
|
||||
| `RightAssistantSidebar.tsx` | 接收打开意图,调用工作栏实例创建或激活逻辑 |
|
||||
| `App.tsx` | 组合主导航、应用中心、固定设置,持有确认配置及页面离开检查 |
|
||||
| `ApplicationMenu.tsx` / `AnchoredMenu.tsx` | 底部向上锚定的已启用应用菜单;关闭后导航,或打开既有管理 Modal;共享几何定位与键盘行为 |
|
||||
| `ApplicationCenter.tsx` | 浏览、搜索内置清单,调用既有页面导航及共同设置入口,编辑常驻和顺序;复用设置 Modal 样式和焦点工具 |
|
||||
| `ApplicationSettingsView` | 位于 `ApplicationCenter.tsx`,由应用中心对应行进入的应用配置表单 |
|
||||
| `RightAssistantSidebar.tsx` | 保留右侧工具 Tab 和“+”目录调用链路 |
|
||||
| `WorkbarShell.tsx` | 继续负责实例策略、Tab、目录和焦点行为 |
|
||||
| `workbar-contracts.ts` | 继续作为工作栏应用 ID 和实例布局的权威契约 |
|
||||
| `resource-monitor-contracts.ts`(新增) | 定义本机资源快照、可用性和错误码 |
|
||||
| `resource-monitor-service.ts`(新增) | 在 Main 中采集并规范化本机与 GoodBuddy 指标 |
|
||||
| `ResourceMonitorPanel.tsx`(新增) | 按需轮询、保留有界历史并渲染资源面板 |
|
||||
| `application-settings-contracts.ts` | 增加应用导航偏好 schema 和默认值 |
|
||||
| `application-settings-store.ts` | 迁移并原子保存应用导航偏好 |
|
||||
| `PlatformFeaturesSettingsSection` 或独立设置 Section | 编辑“显示在侧边栏”偏好 |
|
||||
| `application-settings-contracts.ts` | 定义常驻、顺序及应用启用字段;保留笔记既有配置键 |
|
||||
| `application-settings-store.ts` | 迁移默认值,串行合并更新并原子保存,返回完整确认快照 |
|
||||
| `PlatformFeaturesSettingsSection` | 移除全局笔记设置页签、跳转入口及专用回调,保留通用设置和远程项目 |
|
||||
|
||||
不要在 `SidebarSystemTools` 中读取文件、创建 PTY、操作浏览器或采集系统指标;该组件只发送
|
||||
有界的工具 ID。
|
||||
应用中心只编排应用导航和配置,不读取文件、创建 PTY、操作浏览器或采集系统指标。
|
||||
不增加左侧工具组件、左侧工具状态镜像或主侧栏到工作栏的专用命令通道。
|
||||
|
||||
## 3. Renderer 契约
|
||||
|
||||
### 3.1 系统工具 ID
|
||||
### 3.1 内置应用清单
|
||||
|
||||
```ts
|
||||
export const sidebarSystemToolIds = [
|
||||
'workspace',
|
||||
'terminal',
|
||||
'browser',
|
||||
'resources'
|
||||
] as const
|
||||
type BuiltInApplicationId = 'magic-notes' | 'knowledge' | 'heartbeat' | 'local-inference' | 'device-sharing'
|
||||
|
||||
export type SidebarSystemToolId = (typeof sidebarSystemToolIds)[number]
|
||||
```
|
||||
|
||||
该类型可以放在 Renderer 内共享模块。首期不暴露 IPC,因为动作只编排已有工作栏 Renderer
|
||||
状态;实际能力调用继续走现有 Preload API。
|
||||
|
||||
### 3.2 打开意图
|
||||
|
||||
`App` 向 `RightAssistantSidebar` 传入单调递增的请求或命令对象:
|
||||
|
||||
```ts
|
||||
type WorkbarOpenIntent = {
|
||||
requestId: number
|
||||
appId: 'workspace' | 'terminal' | 'browser' | 'resources'
|
||||
reason: 'sidebar-system-tool'
|
||||
context?: { conversationId?: string; projectId?: string }
|
||||
type BuiltInApplicationDefinition = {
|
||||
id: BuiltInApplicationId
|
||||
source: 'builtin'
|
||||
titleKey: string
|
||||
descriptionKey: string
|
||||
view: BuiltInApplicationId
|
||||
}
|
||||
```
|
||||
|
||||
`requestId` 使连续点击同一工具仍能被消费。不要仅设置 `assistantSidebarTab`,因为用户可能已
|
||||
关闭对应实例,且终端需要按执行空间寻找实例。
|
||||
全部应用 ID 在 `application-settings-contracts.ts` 的 `BuiltInApplicationId` 定义;`EditableApplicationId` 包含 `magic-notes`、`heartbeat`、`local-inference`。Renderer 的 `applicationDefinitions` 映射图标、国际化键和可选应用启用键,知识库始终可用。
|
||||
`App.tsx` 默认导入 `LocalInferencePage`,传入 `onClose`、`enabled` 和 `restoreFocus`。`local-inference` 保留为入口 ID,`setView` 在提交工作区切换前将其转为 `localInferenceOpen = true` 并返回,不更新 `view` 或 KeepAlive 缓存;组件按该独立状态挂载及卸载。
|
||||
静态清单复用既有应用页面和国际化键。管理 Modal 的搜索只过滤该清单,包含未常驻和关闭的应用,不请求
|
||||
远程目录。启用、常驻和排序从确认配置派生,不在清单中持久化第二份状态。
|
||||
|
||||
建议由 `RightAssistantSidebar` 在消费意图后通过现有 `WorkbarShell` 控制接口完成。控制器必须
|
||||
接收上下文和调用原因,不能只按 `appId` 查找第一个实例:
|
||||
### 3.2 应用与设置导航
|
||||
|
||||
```ts
|
||||
type WorkbarController = {
|
||||
openOrActivate(intent: WorkbarOpenIntent): Promise<WorkbarTabInstance>
|
||||
}
|
||||
```
|
||||
应用中心调用既有 `navigateFromSidebar` 或同一页面导航函数,沿用未保存离开检查和焦点
|
||||
策略。打开前检查确认的启用值;未常驻但已启用的应用不附带常驻写入。本机推理监控从常驻入口、菜单及管理 Modal 均经 `setView('local-inference')` 打开独立 Modal,保留底层工作区与设置草稿;其他应用继续页面导航。
|
||||
`ApplicationMenu` 按五个应用共用的持久化顺序派生清单,仅按启用过滤,不读取常驻或打开历史。应用行先关闭菜单再导航;底部“管理应用”关闭菜单、恢复锚点焦点并打开既有 `ApplicationCenter` Modal。设备共享使用默认 `false` 的 `deviceSharingEnabled`,启用后进入主内容区页面,无常驻字段;迁移与关闭规则见[设备共享技术设计](../device-sharing/technical-design.md#当前首期实现)。
|
||||
应用中心对应行的设置操作仅接受 `EditableApplicationId`;应用页和全局设置不提供重复入口。知识库的管理行提供“打开”和排序,原工作区实际配置保留,不添加通用空设置页。
|
||||
共同页复用设置 Modal 容器、配置读取及更新方法,不建立独立配置存储或重复表单。
|
||||
|
||||
若当前 `WorkbarShell` 没有命令式入口,先增加窄的回调或受控请求 Props,不引入全局事件总线。
|
||||
### 3.3 工作栏选择与创建
|
||||
|
||||
工作区意图始终激活注册表中的不可关闭单实例;仅在迁移或布局损坏时补回。浏览器意图按
|
||||
`conversationId` 激活最近使用的匹配实例,没有匹配实例时创建一个。工作栏“+”不走该去重
|
||||
路径,选择浏览器时始终创建新实例。
|
||||
已有 Tab 仅按实例 ID 激活,保留目标和历史状态。不得按当前项目、执行空间或 Conversation
|
||||
查找最近匹配实例替代用户选择,也不在选中时自动重建退出或失败终端。
|
||||
|
||||
### 3.3 终端匹配
|
||||
|
||||
工作栏“+”目录选择终端时每次创建新实例;侧栏系统工具按钮执行“进入当前终端”,采用:
|
||||
|
||||
1. 解析当前项目对应的 `ExecutionSpaceSelection`。
|
||||
2. 在现有终端实例中按目标 identity 查找 `starting` 或 `running` 状态。
|
||||
3. 存在多个匹配实例时激活最近使用的实例。
|
||||
4. 没有匹配实例时调用现有终端创建流程。
|
||||
|
||||
`closing`、`exited`、`interrupted` 和 `failed` 实例保留为历史 Tab,不阻止快捷按钮创建新终端。
|
||||
工作栏“+”继续按原规则创建额外终端,不受快捷按钮去重影响。
|
||||
“+”选择终端每次调用既有创建流程,解析当前 `ExecutionSpaceSelection`,绑定本机或托管
|
||||
SSH 项目的权威目录;无项目时使用本机 Home。选择浏览器每次创建额外实例,绑定当前
|
||||
Conversation,无 Conversation 时创建待绑定空实例。同一 Conversation 的浏览器页面独立,
|
||||
登录上下文共享规则继续由工作栏负责。文件按单实例聚焦,缺失时仅由布局修复补回。
|
||||
选择现有文件 Tab 保留跟随或固定模式,固定目标失效时不切换目标。
|
||||
|
||||
### 3.4 资源监控工作栏应用
|
||||
|
||||
@@ -125,8 +109,8 @@ type WorkbarController = {
|
||||
}
|
||||
```
|
||||
|
||||
该应用不加入升级后的默认打开实例。侧栏资源按钮与工作栏“+”目录都通过既有单实例规则创建
|
||||
或聚焦它。
|
||||
该应用不加入升级后的默认打开实例,仅通过右侧已有 Tab 或“+”目录访问,使用既有单实例
|
||||
规则创建或聚焦。左侧应用清单不包含 `resources`。
|
||||
|
||||
## 4. 应用导航偏好
|
||||
|
||||
@@ -135,31 +119,35 @@ type WorkbarController = {
|
||||
在 `ApplicationSettings` 增加独立字段:
|
||||
|
||||
```ts
|
||||
type BuiltInApplicationId = 'magic-notes' | 'knowledge' | 'heartbeat'
|
||||
|
||||
type ApplicationNavigationSettings = {
|
||||
hiddenBuiltInApplicationIds: BuiltInApplicationId[]
|
||||
order: BuiltInApplicationId[]
|
||||
pinned: Record<EditableApplicationId, boolean>
|
||||
}
|
||||
```
|
||||
|
||||
默认值为空数组,保持升级前所有可用入口可见。ID 数组可以避免为每个应用增加一个顶层布尔
|
||||
字段;Zod schema 必须去重、限制数量并拒绝未知内置
|
||||
ID。
|
||||
|
||||
首期不保存排序,因为内置应用顺序固定。插件偏好在插件系统确定稳定 identity 和卸载保留
|
||||
规则后另行扩展,不提前混入当前 schema。
|
||||
`applicationNavigation` 保存五个应用的完整顺序和三个可选应用的常驻布尔值。默认顺序为
|
||||
`knowledge`、`heartbeat`、`magic-notes`、`local-inference`、`device-sharing`;心跳和魔法笔记常驻默认 `true`,本机推理监控默认 `false`,设备共享无常驻开关。
|
||||
权威返回 schema 和更新 schema 均要求五个 ID 各出现一次,拒绝重复、未知和缺失 ID;`pinned` 包含三个可选应用,缺失 `heartbeat` 时补 `true`,显式布尔值保留。Renderer 直接读取该顺序,再按各入口的可见性过滤。知识库始终启用、常驻,无可写开关键。
|
||||
按[排序规则](./logic-design.md#3-主导航组合规则)提交完整顺序;不持久化可推导的可见 ID 列表。
|
||||
首期不预建插件字段,也不为未发布的旧隐藏 ID 草案增加迁移读取器。
|
||||
|
||||
### 4.2 与现有开关组合
|
||||
|
||||
入口可见条件为:
|
||||
可选应用侧栏入口可见条件如下,知识库按保存位置始终显示:
|
||||
|
||||
```ts
|
||||
const visible = featureEnabled && !hiddenBuiltInApplicationIds.includes(id)
|
||||
const visible = featureEnabled && applicationNavigation.pinned[id]
|
||||
```
|
||||
|
||||
`magicNotesEnabled` 继续控制功能启停。知识库和智能心跳若未来增加业务启停开关,也按同一组合
|
||||
规则处理。功能停用时设置页保留显隐偏好但禁用显隐 Switch,并提供原功能设置入口。导航设置
|
||||
不能写回现有开关。
|
||||
`magicNotesEnabled` 保留为笔记启用的唯一权威键,`localInferenceEnabled` 为本机推理监控应用启用键,默认 `true`,不在导航配置再复制 enabled map。`heartbeatEnabled` 控制心跳与监督者应用;Renderer 仅将 `settings?.heartbeatEnabled === true` 视为启用,设置未加载时也不开放入口。默认值及运行边界见[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行),Main 与侧栏接入见[监督者运行路径](../conversation-supervision/technical-design.md#2-运行路径)。关闭可选应用时保留常驻控件和恢复入口。
|
||||
|
||||
笔记配置使用 `magicNotesShowIncompleteTodoCount`、`magicNoteCommentMode`、
|
||||
`magicNoteCommentFormat` 和 `magicNoteCanvasPageCount`,应用中心与笔记页使用同一 schema 和更新链路。
|
||||
画布发送页数默认 1、范围 1~8,具体选择规则见[画布分析契约](../magic-notes/technical-design.md#canvas-analysis-and-comments)。未配置的笔记
|
||||
布尔值(含 `magicNotesEnabled`)默认 `true`;迁移必须用字段是否存在区分缺失与显式 `false`,
|
||||
不能用 `value || true`。已有单选值保留,缺失时分别使用 `immediate` 和 `combined`。
|
||||
若旧“显示入口”实际绑定 `magicNotesEnabled`,保留其启用含义和原值;独立常驻使用新字段,
|
||||
不得直接把旧业务启用键改名为 pinned。只为确认已发布的独立显隐偏好增加对应迁移。
|
||||
|
||||
### 4.3 持久化与 IPC
|
||||
|
||||
@@ -173,61 +161,77 @@ Renderer
|
||||
-> application-settings.json
|
||||
```
|
||||
|
||||
更新继续由 Main 校验可信 sender、Zod 输入和原子写入。设置版本迁移只补充默认空数组,不
|
||||
修改已有 `magicNotesEnabled` 或其他平台设置。
|
||||
Main 继续校验可信 sender、Zod 输入,复用 `ApplicationSettingsStore` 的更新队列,读取最新
|
||||
确认值后合并本次 patch、原子写入并返回完整 `ApplicationSettings`。写入失败不发布目标值。
|
||||
存储版本 12 补充可选应用导航和启用字段,为缺失字段补默认值;保留已有
|
||||
`true`、`false`、单选值和无关设置,不迁移或清理笔记、知识及会话业务数据。
|
||||
新安装和升级时缺失整个 `applicationNavigation` 均使用共享默认值,本机推理监控启用但不常驻;本次不增加存储版本。已保存的常驻值均保留,无法区分旧默认 `true` 与用户主动固定,不强制改为 `false`。
|
||||
|
||||
仅存储读取允许补齐旧数据:缺失的 `knowledge`、`heartbeat` 按此顺序前置,再保留已有 ID 的相对顺序,最后按默认顺序追加缺失项。旧 `[local-inference, magic-notes]` 因此变为 `[knowledge, heartbeat, local-inference, magic-notes, device-sharing]`;旧完整四项顺序保留并在末尾追加 `device-sharing`。缺失或空顺序采用默认顺序,部分 `pinned` 映射仅补缺失键,显式 `false` 保留。重复或未知 ID 仍按现有损坏文件处理规则处理,不静默去重或丢弃。版本 11 迁移时写入归一化结果;版本 12 读取时归一化,下一次设置保存时写入完整结果。IPC 和配置工具写入不接受这些部分数据。
|
||||
|
||||
版本 12 属于未发布的分支格式,原草案 `knowledgeEnabled` 已移除,不增加兼容读取器。`heartbeatEnabled` 是当前应用启用字段,由 Store 归一化缺失值并保留显式保存值。
|
||||
|
||||
## 5. 侧栏底部重构
|
||||
|
||||
现有 `user-card` 是一个覆盖整行的设置按钮。实现时替换为容器和相邻独立按钮:
|
||||
原 `user-card` 整行设置按钮已替换为容器和相邻独立按钮:
|
||||
|
||||
```tsx
|
||||
<div className="sidebar-footer">
|
||||
<SidebarSystemTools onOpenTool={openSystemTool} />
|
||||
<button aria-label={t('navigation.settings')} ...>
|
||||
<div className="sidebar-footer sidebar-footer--applications">
|
||||
<button className="nav-item" onClick={toggleApplicationMenu} aria-haspopup="menu" aria-expanded={applicationMenuOpen} ...>
|
||||
<Grid2X2 aria-hidden="true" />
|
||||
<span>{t('applications.menuLabel')}</span>
|
||||
<ChevronUp aria-hidden="true" />
|
||||
</button>
|
||||
<div className="sidebar-footer__divider" aria-hidden="true" />
|
||||
<button className="nav-item" aria-label={t('navigation.settings')} ...>
|
||||
<Settings aria-hidden="true" />
|
||||
<span>{t('navigation.settings')}</span>
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
不能在设置按钮内部嵌套工具按钮。设置按钮继续调用 `navigateFromSidebar('settings', trigger)`,
|
||||
保留设置预加载、焦点恢复和未保存离开确认。
|
||||
“应用”(`applications.menuLabel`:中文“应用”、英文 `Apps`)和设置为横向等高独立按钮,使用 `nav-item` 样式和弱竖分隔线;“管理应用”菜单项使用 `LayoutGrid` 图标,管理 Modal 标题保持“应用中心” / `Application Center`。设置按钮继续调用 `navigateFromSidebar('settings', trigger)`,
|
||||
保留设置预加载、焦点恢复和未保存离开确认。该调用只打开设置 Modal,不更新当前工作区或
|
||||
KeepAlive 缓存;`settingsOpen` 独立管理打开状态。关闭后卸载设置内容,下次按入口指定的分类
|
||||
和消息通道重新挂载,避免缓存上次分类覆盖本次直达目标。底层工作区继续挂载,普通关闭不跳回聊天。
|
||||
|
||||
系统工具按钮调用统一 `openSystemTool`:
|
||||
应用菜单使用 `AnchoredMenu` 的 body Portal,向上锚定,无模态遮罩或背景隔离;`menu` 语义纳入共享浏览器 viewport 相交遮挡。管理 Modal 及共同设置沿用原 Portal、焦点隔离、恢复及 Escape 机制。
|
||||
不在主侧栏复制工具入口。工作栏仍由窗口右上方固定开关展开和关闭,关闭后焦点返回该开关。
|
||||
|
||||
1. 设置工作栏打开意图。
|
||||
2. 展开工作栏。
|
||||
3. 由工作栏创建或激活实例。
|
||||
4. 按现有策略把焦点移入目标面板。
|
||||
## 6. 应用可用性与知识请求
|
||||
|
||||
工作栏仍由窗口右上方的固定开关关闭,关闭后的焦点返回该开关。
|
||||
Renderer 从同一确认快照派生可选应用主导航、中心状态和页面可用性,依赖只包含相应
|
||||
字段。keep-alive 页挂载不代表应用仍启用;禁用后在原位置渲染不可用状态,
|
||||
保留页面草稿及业务数据,阻止新的页面操作。重新启用不触发导航或业务重放。
|
||||
|
||||
## 6. 工作栏状态与快捷按钮
|
||||
`ApplicationAvailability` 隐藏并设置业务内容为 inert,保留挂载的页面实例;重新启用从
|
||||
应用中心对应行的设置进入。笔记页面直接消费确认的评论配置,
|
||||
不因修改评论方式而重建编辑器。
|
||||
|
||||
`RightAssistantSidebar` 应向 `App` 暴露当前活动实例的 `appId` 和必要的有界状态摘要,或由
|
||||
现有 `onTabChange` 扩展得到。主侧栏不读取终端输出、文件内容、URL 或活动详情。
|
||||
知识库不经过 `ApplicationAvailability`;会话知识按钮、范围、检索方式和标签继续遵守原知识库规则。心跳页面受应用可用性控制,单条计划 enabled 保持不变。
|
||||
请求构建没有应用级禁用知识库的裁剪或守卫;本机、托管 SSH 和排队请求沿用原知识上下文链路。
|
||||
单个知识库实例的配置、停用、删除及取消清理仍由知识库功能负责。
|
||||
|
||||
```ts
|
||||
type SidebarSystemToolState = {
|
||||
activeToolId?: SidebarSystemToolId
|
||||
statusByToolId?: Partial<Record<SidebarSystemToolId, 'attention' | 'error' | 'running'>>
|
||||
}
|
||||
```
|
||||
## 7. 串行保存与确认快照
|
||||
|
||||
首期可以只实现 `activeToolId`,没有可靠聚合来源时不显示状态徽标。不能根据局部事件猜测错误
|
||||
或运行状态。
|
||||
中心与应用页的启用、常驻、排序及笔记配置共用一个 Renderer 更新入口及 pending
|
||||
状态。任一提交未完成时,所有入口中修改这组设置的控件暂时禁用;下一次操作基于最新确认
|
||||
快照生成 patch,不提交整个过期 `ApplicationSettings` 覆盖无关字段。Main 队列仍负责串行合并。
|
||||
|
||||
## 7. 应用显隐更新
|
||||
|
||||
应用显隐 Switch 复用平台功能设置的即时更新模式。请求开始后禁用三个应用显隐 Switch,只
|
||||
允许一个完整目标集合处于提交中。Main 返回成功结果后更新 App 中的平台设置快照,主导航
|
||||
随之变化。失败时恢复控件原值,在对应设置行显示可重试错误;主导航继续读取最后一次成功
|
||||
保存的设置。
|
||||
`ApplicationSettingsStore.update` 在持久化成功后通过 `onChanged` 发布完整快照;IPC 转发
|
||||
`applicationSettingsChanged`,Preload 提供 `updates.onSettingsChanged`。工具配置服务调用同一 Store,
|
||||
因此配置工具写入也同步界面,保存失败不发事件。`App` 收到事件后推进读取修订号并替换快照,
|
||||
迟到的旧读取及旧保存响应不能覆盖新状态。每次重开中心都刷新配置,刷新期间锁定修改,失败后
|
||||
保留锁和重试入口;控件不先乐观生效。
|
||||
明确保存失败时保留最后确认值,就地显示可重试错误与编辑上下文。若 IPC 响应中断而写入
|
||||
结果未知,通过既有设置读取重新取得权威快照再解锁;读取失败继续显示错误并允许重试读取,
|
||||
不自动重发变更或写回旧配置。无须引入持久化回滚日志或独立导航快照文件。
|
||||
|
||||
## 8. 插件兼容边界
|
||||
|
||||
首期不增加插件注册表、插件 schema 或插件持久化字段,只保留系统 ID 不向插件开放的产品边界。
|
||||
插件市场设计需要单独确定 Host、权限、签名、生命周期和稳定 identity;不得直接复用 DeepSeek
|
||||
Harness 的 npm 插件市场。
|
||||
首期不增加市场 URL、YAML 导入导出、ShareServer API、插件注册表或安装服务。FR-15 的
|
||||
私有市场协议、YAML Schema、资源分发与扩展生命周期另行设计;系统 ID 不向插件开放。
|
||||
不得直接复用 DeepSeek Harness 的 npm 插件市场。后续依赖见第 11 节,首期不预建占位界面。
|
||||
|
||||
## 9. 本机资源监控
|
||||
|
||||
@@ -333,23 +337,28 @@ resourceMonitor.getLocalSnapshot(input: {
|
||||
|
||||
### 10.1 Renderer 测试
|
||||
|
||||
- 固定入口顺序不受三个应用显隐组合影响。
|
||||
- 运行记录和设置没有显隐控件。
|
||||
- 四个系统工具按钮具有名称、工具提示、键盘行为和选中状态。
|
||||
- 文件复用不可关闭单实例;浏览器和终端按当前上下文聚焦匹配实例,工作栏“+”允许显式多实例。
|
||||
- 工具按钮展开工作栏但不改变当前主页面。
|
||||
- 显隐更新成功后主导航立即变化,失败时 Switch 和主导航恢复原状态。
|
||||
- 窄窗口下工具按钮不重叠,焦点恢复正确。
|
||||
- 固定入口不受应用启用、常驻和顺序组合影响,应用中心及设置始终可达;左侧没有重复系统工具。
|
||||
- US-B6:底部菜单向上锚定且无遮罩,只列已启用应用,不受常驻或打开历史影响;点击行关闭菜单并导航,“管理应用”进入既有可搜索 Modal,包含关闭及未常驻应用。
|
||||
- US-F3/US-F5:三个入口均打开本机推理监控 Modal 并保留底层工作区;覆盖嵌套确认的焦点循环、逐层 Escape、提交期间禁止关闭与重复执行、关闭后的入口及后备焦点恢复,以及停用内容后的键盘焦点。
|
||||
- US-B7:常驻、取消常驻、键盘及拖动排序成功后同步,重新开启及重启保留原偏好和顺序。
|
||||
- US-B8:知识库始终启用且常驻,对应行提供打开与排序,不提供启用或常驻开关;心跳应用启停按监督者逻辑验收,单条计划 enabled 不变;可选应用关闭后的不可用页保留草稿,重新开启不自动导航或重放。
|
||||
- US-B9:应用中心对应行进入笔记表单,管理启用、常驻、待办数量、评论方式及形式;笔记页和全局设置无重复入口。
|
||||
- US-B5:所有入口共享 pending,保存失败保留相同确认值;重开刷新期间锁定修改,较新事件使旧读取失效;结果未知时重读,不自动重发。
|
||||
- 文件复用不可关闭单实例;选择任一已有终端或浏览器 Tab 保留确切目标,“+”每次创建额外实例。
|
||||
- 已退出、失败的终端 Tab 不因选中而重建;工具切换不改变主页面,后台状态不抢占 Tab。
|
||||
- 窄窗口、键盘排序、共享 Switch、Tab 语义及浮层焦点恢复正确;中心和设置不被原生浏览器遮挡。
|
||||
- 资源面板只在可见活动时轮询,跳过重叠请求并把历史限制为 150 项。
|
||||
- 窗口隐藏、最小化和恢复时停止轮询并重新建立区间基线。
|
||||
- 资源部分可用、全部失败和重新进入活动状态均显示正确结果。
|
||||
|
||||
### 10.2 Main 与契约测试
|
||||
|
||||
- 新设置字段默认值、合法 ID、重复 ID、数量上限和未知 ID 校验。
|
||||
- 旧版本设置迁移后默认显示所有既有应用。
|
||||
- 更新导航偏好不修改魔法笔记启停等无关字段。
|
||||
- 原子写入失败时保留最后一次有效设置。
|
||||
- 顺序包含五个应用 ID 各一次;公开写入拒绝重复、缺失、未知 ID 及无效常驻字段;旧存储顺序按迁移规则补齐,不为未发布的 `knowledgeEnabled` 添加迁移。
|
||||
- 未配置的心跳应用默认关闭,其余应用启用及笔记布尔项默认开启,本机推理监控默认不常驻;已保存的启用、常驻值和评论单选值经迁移及重启保留。
|
||||
- 更新常驻或顺序不修改启用、计划或业务数据;笔记既有配置键保持语义。
|
||||
- 串行 patch 在 Main 最新快照上合并,原子写入失败不发布目标状态;响应丢失后重读实际结果。
|
||||
- Store 持久化后才发布变更,工具配置写入使用同一通知链路;知识库不受可选应用偏好影响。
|
||||
- 保存启用失败不会改变业务判断;会话关联和历史知识引用不因入口隐藏被清空。
|
||||
- 资源快照拒绝 NaN、Infinity、负数字节数、越界百分比和未知错误码。
|
||||
- 采集超时、权限不足和磁盘 IO 不支持时返回有界部分结果。
|
||||
- 超时采集尚未实际结束时保持单飞,不累积新的后台采集任务。
|
||||
@@ -369,22 +378,33 @@ resourceMonitor.getLocalSnapshot(input: {
|
||||
- 浅色、深色、`220px` 与 `420px` 侧栏宽度。
|
||||
- Windows、macOS 和 Linux 的真实 CPU、内存与磁盘 IO 采集;不支持项显示准确原因。
|
||||
|
||||
FR-16 单独覆盖 US-F3 概览去重、未知指标、US-F4 单任务取消与完成竞态、US-F5 服务
|
||||
实际启动/停止/重启或加载/释放、使用方确认、外部管理限制及失败真实状态;用接入的真实引擎
|
||||
验证共享服务不被单任务取消终止,重启不重放。设计文档检查或模拟服务不算生产路径验证。
|
||||
|
||||
## 11. 实施顺序
|
||||
|
||||
实施范围以 [PRD 分期](./prd.md#8-分期)为准。本节替代旧实施顺序;前文关于四个左侧
|
||||
系统工具按钮、仅持久化隐藏 ID 的契约需在实现前同步,不能直接按旧规格开发。
|
||||
实施范围以 [PRD 分期](./prd.md#8-分期)及最新评审决定为准,逻辑映射见
|
||||
[需求对应](./logic-design.md#6-与需求的对应关系)。既有 FR/US 编号保持稳定;新增故事使用
|
||||
US-B6 应用中心、US-B7 顺序、US-B8 启用联动、US-B9 笔记迁移,以及 US-F3 概览、
|
||||
US-F4 取消、US-F5 服务生命周期。PRD、User Stories、配套设计与根 UI 规范已同步入口及持久化规则。
|
||||
|
||||
1. 同步 PRD 功能要求、User Stories、逻辑、UI 与技术契约,收敛首期应用中心样式。
|
||||
以下为实施阶段划分,应用中心及本机推理监控已接入源码,验证状态以进度记录为准。
|
||||
|
||||
1. 底部入口使用向上锚定的轻量菜单,通过“管理应用”进入既有整窗 Modal;核对已发布配置的迁移来源。
|
||||
2. 接入现有内置应用页面与最底部应用中心入口,保留独立设置和固定运行记录入口。
|
||||
3. 为常驻显隐和排序设计最小持久化契约,保留已发布的相关设置与用户数据,按保存结果更新导航。
|
||||
4. 接入应用设置的共同界面与既有设置更新路径,先验证魔法笔记多入口状态一致性。
|
||||
5. 验证右侧现有文件、终端、浏览器 Tab;移除计划中的左下角工具快捷条和专用于该快捷条的意图接口工作。
|
||||
3. 实现常驻、完整顺序及启用契约,迁移时保留明确旧值,通过串行保存与确认快照同步所有界面。
|
||||
4. 迁移魔法笔记共同设置,保留知识库始终可用及原工作区配置、心跳单条计划 enabled,验证可选应用关闭和重新开启。
|
||||
5. 验证右侧现有文件、终端、浏览器 Tab 的确切目标与“+”额外实例行为,不实现左侧重复工具条。
|
||||
6. 单独实现资源快照契约、Main 采集服务、Preload API 与右侧 `resources` 单实例面板。
|
||||
7. 验证键盘、焦点、浅深主题、窄窗口、设置失败、重启持久化和跨平台行为。
|
||||
|
||||
首期使用内置应用清单,不依赖远程商店、插件注册或安装服务。商店目录、推荐、安装与
|
||||
更新机制在后续独立设计中确定;Demo 中的模拟商店不接入首期产品界面。
|
||||
|
||||
本轮 `local-inference` 已接入管理 Modal、类型化 IPC 与真实引擎管理,支持范围见 11.1;
|
||||
实机和自动化验证范围见[实施进度](./progress.md)。
|
||||
|
||||
后续设计输入见 [FR-15](./prd.md#fr-15-私有化市场与-yaml-交换后续) 与
|
||||
[FR-16](./prd.md#fr-16-本机模型管理入口新增计划项):
|
||||
|
||||
@@ -396,11 +416,50 @@ resourceMonitor.getLocalSnapshot(input: {
|
||||
市场来源配置不覆盖现有模型、应用更新或 Runtime 下载源设置。
|
||||
- ShareServer 的市场发布与分发实现由其独立仓库承接,本仓库先记录依赖,不将桌面需求
|
||||
记录标为服务端已实现。
|
||||
- 本机模型管理实施前盘点现有 OCR、向量、语音及推理服务的生命周期接口,优先复用其
|
||||
Main 服务。只为实际支持的操作提供 typed IPC;区分受管子进程、外部服务和进程内模型,
|
||||
不把直连模型 HTTP 请求等同于独立模型进程。
|
||||
- 模型管理的详细逻辑需确定共享服务的使用方识别、忙碌时停止与重启、退出应用时的释放
|
||||
行为,再接入管理页面并验证实际引擎。该工作单独排期,不依赖市场上线。
|
||||
|
||||
### 11.1 本机推理任务与执行服务
|
||||
|
||||
ASR、OCR 与内置向量的跨功能执行边界、任务控制缺口及有条件的优化方向,见[本机专用推理架构与任务控制评估](../../architecture/local-inference-task-control.md)。该评估区分源码现状、建议与待办,未将常驻化或后台宿主迁移列为已采用实现。
|
||||
|
||||
`LocalInferencePage` 默认导出,接收必需的 `onClose` 及可选的 `enabled`、`restoreFocus`,通过 `createPortal(..., document.body)` 渲染管理 Modal,复用 `PageHeader` 和共享
|
||||
`activateModalFocus`/`trapTabFocus`。`ImpactConfirmation` 使用独立 Portal 和紧凑确认样式;共享焦点工具隔离下层 Modal,并在关闭时恢复可用触发元素或指定后备焦点。`operation` ref 防止重复执行,`busy` 和 `control` 控制关闭与 Escape,交互细节见 [UI 设计](./ui-design.md#91-本机推理管理)。`local-inference-contracts.ts` 定义快照、操作与取消输入;Main 注册四个
|
||||
`local-inference:*` 通道,复用可信 sender 校验和 Zod 输入校验。Preload 只暴露读取、服务操作、
|
||||
任务取消及打开既有设置的方法,不接收 PID、命令或模型路径。
|
||||
|
||||
| 引擎 | 生产接入与可用操作 | 明确限制 |
|
||||
| --- | --- | --- |
|
||||
| ASR / sherpa-onnx | `SpeechTranscriptionService` 登记真实请求;执行路径保留独立 Worker 请求取消 | 每次请求加载、结束释放;未提供 Worker 就绪观测,模型可用时状态为 unknown,不从活动请求推断 running |
|
||||
| OCR / PaddleOCR | `DocumentOcrBroker` 登记文档请求;`document-ocr-bridge` 提供实际 Worker 状态、加载及空闲释放 | 有活动或排队请求时拒绝释放;不把终止共享 Worker 暴露为单任务取消 |
|
||||
| 内置向量 / Granite | 按模型目录复用引用计数 Broker;启动、停止、重启;取消消息等待 Worker 结果或取消确认 | 启动确认进程 spawn,模型在首个请求时加载;不把进程启动称为模型已加载 |
|
||||
| TTS | 显示当前没有执行服务 | 不提供虚假任务或控制 |
|
||||
| 外部向量连接 | 快照契约保留配置,Modal 不显示;通过既有模型设置管理 | 本页不主动探测,不推断本机归属,不控制外部进程 |
|
||||
|
||||
`LocalInferenceService` 在 Main 内存中登记真实 ASR、OCR、向量请求,保留活动请求和最近
|
||||
100 条终态记录。只保存能力来源、时间、模型(执行路径有提供时)、状态和错误,不保存音频、
|
||||
图片或向量输入文本。运行阶段包括排队、模型加载及执行,没有统一阶段进度;来源未提供项目或
|
||||
会话对象时不补造对象关联。Modal 仅使用活动请求生成影响确认,不展示任务历史或单任务取消。
|
||||
|
||||
向量执行端通过 `utilityProcess.fork` 创建独立进程。每个 transport 持有自己的资源采样器,按当前 `child.pid` 从 `app.getAppMetrics()` 匹配 Utility 条目。服务快照的 `resources.scope` 为 `service-process`,携带 PID、采样时间及可用指标;无进程或无法采集时为 `unavailable` 并给出原因。Broker 清除退出的 transport,重启使用新采样器,不复用旧进程指标。
|
||||
|
||||
Modal 可见且启用时每 5 秒读取一次快照,跳过重叠请求。CPU 使用累计 CPU 秒数之差除以单调时钟间隔,100% 表示一个逻辑核心,可超过 100%;首次读取、PID 或创建时间变化、采样间隔超过 15 秒、计数回退及采样失败后重新建立基线。15 秒阈值为定时器延迟和跳过重叠请求留出余量。工作集由 Electron 的 KiB 转为契约中的字节,界面显示 MiB。缺少 CPU 时间或内存读数时只标记该指标不可用,不补零。数值为整个向量服务进程的占用,包含运行时和并发使用方,不是单任务或模型权重大小。
|
||||
|
||||
ASR 使用主进程中的 `node:worker_threads`;OCR 使用渲染进程中的 Web Worker,两者均没有独立进程工作集,逐行标明无法独立统计 CPU/内存。没有将主进程或整个 Renderer 的读数归属给服务。显存未采集,本机资源监控工作栏应用仍未实现。
|
||||
|
||||
服务操作按 serviceId 串行,操作期间拒绝新增该服务请求;停止前校验所有当前活动 taskId
|
||||
都在用户确认清单内,新增使用方要求刷新后重新确认。向量停止等待实际 child exit;失败保留
|
||||
可观测状态和错误。Renderer 操作失败时关闭旧确认并重新读取快照,刷新期间或失败时禁用服务操作;再次操作须重新确认当前活动请求。重启不重放中断请求。任务取消与服务停止分开,完成竞态保留执行方结果。
|
||||
|
||||
向量 Broker 将调用方 Promise 与 Worker 执行记录分开。调用方取消或超时可先返回错误,
|
||||
执行记录在 Worker 确认、返回结果或进程退出前仍为活动项,保留在途名额并计入服务停止影响;
|
||||
停止等待超时也不能提前移除记录。请求取消与自然完成竞争时采用执行方结果。
|
||||
|
||||
OCR 模型可用性由 Main 校验,Worker 运行状态由实际持有它的 Renderer 模块提供。手动加载
|
||||
同样复用既有 Worker,加载超时恢复为可重试错误;空闲 60 秒自动释放。Modal 启用、文档可见且内容未被 hidden/inert 隔离时每 5 秒刷新,跳过未结束的请求;嵌套确认期间暂停新快照读取。
|
||||
关闭 Modal 时卸载组件并清理轮询,窗口隐藏、关闭管理应用或取消常驻不会触发服务停止。退出整个进程继续
|
||||
沿用原有应用清理路径。模型文件卸载仍在模型设置中独立进行。
|
||||
|
||||
向量 Main 引擎使用 `onnxruntime-web` 的 Node 条件入口,避免浏览器 `/wasm` 入口通过 fetch
|
||||
加载本地文件失败。执行方的具体错误随任务返回;Renderer 不把失败展示为零指标或恢复成功。
|
||||
|
||||
## 12. 项目活动汇总
|
||||
|
||||
@@ -408,8 +467,17 @@ resourceMonitor.getLocalSnapshot(input: {
|
||||
派生状态,界面行为见[活动界面](./ui-design.md#10-项目活动汇总)。
|
||||
|
||||
- `conversation-activity.ts` 从 `App.tsx` 的会话、`activeConversationIds`、全部可见
|
||||
`assistantTasks` 和项目元数据派生会话行、全局及项目计数,不新增持久化活动副本。
|
||||
`assistantTasks`、`completedConversationIds` 和项目元数据派生会话行、全局及项目计数,不新增持久化活动副本。
|
||||
`ProjectActivity` 在有活动时渲染汇总与项目、会话级联菜单,`ProjectSwitcher` 复用项目计数。
|
||||
- `use-unviewed-completions.ts` 在 Renderer 内存维护完成集合和上一轮 Task 状态;已观察到的
|
||||
顶层 Task 转为 `completed` 时加入集合,初始历史不生成通知。`App` 的本地 `done` 事件和
|
||||
活动运行持久化快照的成功终态调用 `markConversationCompleted`,失败、取消不调用。
|
||||
新运行及 Task 恢复运行或等待处理时清除旧通知。具体优先级和通知生命周期以活动逻辑为准。
|
||||
- `App` 仅在 `view === 'chat' && !settingsOpen && !applicationCenterOpen` 时向 Hook 传入当前会话 ID;Hook 监听
|
||||
`visibilitychange`,文档不为 `hidden` 时才按当前会话清除通知。这使普通侧栏、活动菜单
|
||||
导航和可见性恢复共用查看规则,KeepAlive 挂载本身不代表查看。集合不写入 SQLite 或设置。
|
||||
- 聚合状态新增 `completed`,全局和项目计数包含 `attention`、`running`、`completed`,
|
||||
同一 Conversation ID 只按最高优先级计数;活动行为空才隐藏摘要。
|
||||
- `App` 向 `ProjectActivity` 传入项目元数据、活动行、侧栏可见状态与既有精确跳转回调。
|
||||
菜单按项目 ID 分组并记忆化派生集合;排序仅影响展示,不改聚合状态或持久化数据。
|
||||
- 两层菜单在同一 body Portal 内相接,桌面会话层使用独立 fixed 定位,不参与项目层高度计算。
|
||||
@@ -425,13 +493,15 @@ resourceMonitor.getLocalSnapshot(input: {
|
||||
- `AssistantDatabase.listTasks()` 保留最近历史窗口(默认 100 条),额外包含窗口外当前
|
||||
运行或等待处理的可见 Task;`activeVisibleTaskSelect` 与 `toTask` 的有效状态保持一致,
|
||||
待调度运行规范化为 `queued`,不因旧 Task 状态被误纳入实时集合。
|
||||
- `listConversations()` 保留最近 100 条活动会话,同时包含上述 Task 关联的较早活动会话,
|
||||
以及含 `streaming` 消息的较早活动会话。两类列表去重后仍按原时间顺序返回,结果允许超过
|
||||
历史窗口大小,避免后台工作被最近记录挤出。
|
||||
- `listConversations()` 的范围与排序遵守
|
||||
[会话列表读取与前端保留](../assistant-workbar/execution-history-storage.md#会话列表读取与前端保留),
|
||||
包含上述 Task 关联及含 `streaming` 消息的较早活动会话,避免后台工作被最近记录挤出。
|
||||
`listTasks()` 仍按原时间顺序返回;列表结果允许超过历史窗口大小。
|
||||
- `openActivityConversation` 按确切 Conversation ID 导航;内存中缺失时刷新持久化会话
|
||||
列表并合并,再查找同一 ID。刷新失败或会话已不存在时显示通知,不回退到项目最新会话。
|
||||
设置离开检查通过后才提交项目、会话及界面清理状态。
|
||||
- 远程恢复成功提示沿用独立的自动消失计时器;活动汇总更新不重置或替代该计时器。
|
||||
|
||||
验证记录见[项目活动级联实施进度](./progress.md)。本次只改 Renderer 展示与交互,远程
|
||||
Runtime、Agent 协议和桌面到 Agent 的执行路径未改变。
|
||||
完成通知的定向验证应覆盖本地成功、持久化活动运行成功、后台 Task 状态转换、初始历史、
|
||||
失败和取消、实时状态优先、三类计数、普通侧栏查看、设置覆盖、隐藏文档与恢复可见、无活动行隐藏。
|
||||
验证记录见[项目活动级联实施进度](./progress.md);源码核对不能替代真实 Electron 路径验证。
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 设计中 |
|
||||
| 版本 | 0.2 |
|
||||
| 日期 | 2026-09-08 |
|
||||
| 状态 | 应用中心与本机推理监控已接入源码,整体验收待完成 |
|
||||
| 版本 | 0.4 |
|
||||
| 日期 | 2026-09-17 |
|
||||
| 关联 PRD | [应用导航与系统工具入口 PRD](./prd.md) |
|
||||
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
|
||||
| 全局规范 | [GoodBuddy 统一界面设计系统](../../../UI-DESIGN.md) |
|
||||
|
||||
本文定义入口、设置和轻量资源监控界面。文件、终端和浏览器的内容布局继续遵守助手工作栏
|
||||
文档。
|
||||
本文定义已接入的应用中心、共同设置和本机推理监控界面,以及待实现的右侧资源监控界面。
|
||||
行为以[功能逻辑](./logic-design.md)为准,需求保留 FR-1 至 FR-16 与既有 US 编号。
|
||||
根 UI 规范已同步应用中心与工具入口规则;共享控件、主题、设置 Modal 与工作栏布局遵守根规范。
|
||||
|
||||
## 1. 侧栏信息架构
|
||||
|
||||
@@ -20,114 +21,151 @@
|
||||
品牌
|
||||
项目选择
|
||||
项目活动汇总(仅有活动时显示)
|
||||
新对话
|
||||
搜索
|
||||
[新对话] [搜索图标]
|
||||
|
||||
当前对话
|
||||
魔法笔记 3
|
||||
知识库
|
||||
智能心跳 1
|
||||
应用(按保存顺序;知识库始终显示,可选应用仅显示启用且常驻项)
|
||||
运行记录
|
||||
|
||||
最近对话
|
||||
...
|
||||
|
||||
────────────────────────
|
||||
[文件] [终端] [浏览器] [资源] [设置]
|
||||
[应用 v] | [设置]
|
||||
```
|
||||
|
||||
“当前对话”和“运行记录”属于固定系统入口。三个内置应用位于二者之间。侧栏底部不再显示
|
||||
“本地工作区”和 `platform · arch`;排障信息移至关于或诊断页面。
|
||||
“当前对话”和“运行记录”属于固定系统入口,常驻应用位于二者之间。首期应用清单为魔法笔记、
|
||||
知识库、智能心跳及本轮追加的本机推理监控,应用中心与设置始终可见。侧栏底部不再显示“本地工作区”和 `platform · arch`;
|
||||
排障信息移至关于或诊断页面。左侧不再提供文件、终端、浏览器、资源监控的重复入口。
|
||||
|
||||
## 2. 底部系统工具栏
|
||||
### 会话搜索
|
||||
|
||||
底部使用连续平整表面和顶部分隔线,不做成账户卡片或嵌套卡片。按钮顺序固定:
|
||||
新建对话与搜索共用原新建对话位置的一行。默认显示 `MessageSquarePlus` 图标与完整“新建对话”文字,整体左对齐,沿用原有 13px、600 字重和 12px 左内边距,右侧为搜索图标;
|
||||
点击搜索后,输入框从右向左展开,新建对话只显示居中的同一图标,保留“新建对话”可访问名称与悬停提示。
|
||||
最窄侧栏中的长标签允许在固定行高内换行,保留字号和图标。搜索右边缘和整行高度保持不变,列表不因展开或关闭而上下移动。宽度动画使用
|
||||
`--motion-normal`,减少动态效果时立即切换;沿用桌面侧栏调宽和窄窗口抽屉尺寸。
|
||||
通道项目继续不提供新对话按钮,搜索展开后使用整行宽度。
|
||||
控件行与新建对话按钮高 `44px`;搜索框本体高 `36px`,上下各留 `4px`,垂直居中。保留 `2px` 焦点外框与 `2px` 外间距,聚焦后外沿与新建对话按钮上下对齐。
|
||||
|
||||
| 位置 | 图标 | 可见工具提示 | 点击结果 |
|
||||
展开时聚焦输入框。X 始终可用,X 或控件行内的 Escape 清空关键词并收起输入框,
|
||||
焦点返回搜索图标。失焦、点击会话列表保持搜索状态和关键词;新建对话图标继续调用原新对话操作。
|
||||
无结果区域的“清除搜索”只清空关键词,保留展开状态。项目活动跳转仍沿用既有清空搜索规则。
|
||||
|
||||
## 2. 底部应用中心入口
|
||||
|
||||
底部使用连续平整表面和顶部分隔线,“应用”在前,“设置”靠右,二者为横向排列的等宽、等高 `nav-item` 独立按钮,内容各自居中,竖分隔线位于底部入口区域的正中间:
|
||||
|
||||
| 位置 | 图标 | 可见文字 | 点击结果 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `FolderOpen` | 文件 | 聚焦不可关闭的工作区面板 |
|
||||
| 2 | `SquareTerminal` | 终端 | 打开或聚焦当前执行空间终端 |
|
||||
| 3 | `Globe` | 浏览器 | 聚焦当前 Conversation 的匹配浏览器实例 |
|
||||
| 4 | `Activity` | 资源监控 | 打开或聚焦本机资源监控面板 |
|
||||
| 末位 | `Settings` | 设置 | 打开设置页面 |
|
||||
| 1 | `Grid2X2` + `ChevronUp` | 应用 / Apps | 向上展开已启用应用菜单,底部“管理应用”进入管理 Modal |
|
||||
| 末位 | `Settings` | 设置 / Settings | 在当前工作区上打开设置 Modal |
|
||||
|
||||
图标使用项目现有 Lucide 库。每个按钮采用统一的 `34px × 34px` 点击区域,内部图标建议
|
||||
`16px`。工具栏使用 `8px` 内边距和 `4px` 间隔;设置按钮通过自动空白靠右,不添加第二条
|
||||
视觉分隔线。
|
||||
图标使用项目现有 Lucide 库。两个按钮均保留可见文字,沿用主导航的高度、间距和焦点样式;
|
||||
“应用”文字后显示向上箭头,与菜单展开方向一致。竖分隔线宽 1px、高 `--space-6`,使用 `--border-default`,与上方横分隔线颜色一致。不得把整个容器做成设置按钮。
|
||||
|
||||
### 2.1 状态
|
||||
|
||||
- 默认:透明背景,图标使用次级文字色。
|
||||
- 悬停:使用弱表面色,不能强于当前选中状态。
|
||||
- 选中:使用强调选中背景,并提高图标对比度。
|
||||
- 展开:应用中心按钮显示展开反馈,不能当作当前应用的选中状态。
|
||||
- 键盘焦点:显示统一焦点环,不能被侧栏边缘裁切。
|
||||
- 处理中:创建或激活请求未完成时暂时禁用对应按钮,保留尺寸并提供进行中名称。
|
||||
- 徽标:只用于等待处理、错误或活动状态,不显示无意义的常驻绿色圆点。
|
||||
- 加载:读取应用配置时保留入口,中心内显示加载状态;失败提供重试,不用默认值冒充已保存配置。
|
||||
|
||||
选中状态表达“对应工作栏面板正在显示”,不表达功能已安装或权限已授予。
|
||||
应用中心入口不承载工具运行徽标。全部可选应用关闭时菜单仍显示知识库及“管理应用”,可进入 Modal 重新开启可选应用。
|
||||
|
||||
## 3. 主导航应用区
|
||||
|
||||
应用入口沿用现有 `nav-item` 行,不使用应用卡片。图标、名称和徽标保持单行:
|
||||
|
||||
```text
|
||||
[图标] 魔法笔记 3
|
||||
[图标] 知识库
|
||||
[图标] 智能心跳 1
|
||||
[图标] 魔法笔记 3
|
||||
[图标] 本机推理监控
|
||||
```
|
||||
|
||||
隐藏某个应用后,其余应用向上补位。运行记录始终位于最后一个可见应用之后,不因应用显隐
|
||||
改变样式或归类为工具。
|
||||
取消常驻或关闭可选应用后不留下空白。知识库始终启用并显示;心跳按启用和常驻显示,位置遵守共享应用顺序。运行记录始终位于应用区之后。
|
||||
五个应用均在管理行上通过拖动及“上移”“下移”按钮排序;首尾无目标时禁用对应按钮。两种方式提交完整应用顺序,保存成功后更新,重启保留。当前对话、运行记录和底部系统入口不参与排序。
|
||||
|
||||
设置页打开期间,主侧栏显示最后一次成功保存的导航状态。Switch 更新成功后导航立即变化,
|
||||
失败时保持原导航。
|
||||
取消常驻不关闭当前页面或本机推理监控 Modal。重新常驻按已保存顺序恢复,关闭应用保留常驻与顺序偏好。
|
||||
主侧栏始终读取最后确认配置,不展示尚未保存成功的排序或开关目标值。
|
||||
|
||||
## 4. 应用与工具设置
|
||||
## 4. 应用中心与共同设置
|
||||
|
||||
设置中心新增“应用与工具”分类,采用 `standard` PageShell:
|
||||
设备共享追加到默认顺序末尾,测试阶段默认关闭。管理行显示“可选”及启用状态,提供打开、
|
||||
设置与排序按钮;设置内提供启用 Switch,不提供常驻开关。关闭时禁用“打开”,应用菜单只在
|
||||
启用时显示该项,点击后在主内容区打开[设备共享页面](../device-sharing/ui-design.md#当前首期页面)。
|
||||
|
||||
底部点击打开轻量菜单,锚定按钮向上展开,无模态遮罩、背景隔离或焦点循环。菜单按共享保存顺序列出已启用应用,不按打开历史或常驻状态过滤。点击应用行关闭菜单,本机推理监控打开独立 Modal,知识库、智能心跳和魔法笔记在主内容区打开页面。分隔线下的“管理应用”关闭菜单并打开既有管理 Modal。
|
||||
|
||||
菜单通过共享 `AnchoredMenu` 挂载到 body,宽度最多 `280px`,距窗口边缘至少 `16px`,与按钮上缘间隔 `8px`;短窗口限制高度并内部滚动,滚动、窗口和内容尺寸变化时重算位置。原生浏览器相交遮挡复用共享机制。
|
||||
|
||||
“管理应用”菜单项使用 `LayoutGrid` 图标。管理 Modal 标题仍为“应用中心”(英文 `Application Center`),沿用设置 Modal 的表面、遮罩和焦点规则,提供全部内置应用的名称及用途搜索、打开,以及可选应用的常驻和设置:
|
||||
|
||||
应用管理始终采用紧凑单列横向列表,从上到下对应保存顺序;行间使用 `--border-subtle` 弱分隔线,不加卡片外框。概览与设置详情共用 `640px` 最大宽度,窄窗口保留两侧各 `16px` 外边距,高度随内容增长并受窗口限制。
|
||||
每行左侧显示图标、名称、状态和简短用途,右侧对齐打开、可用的设置及上下移动操作;空间不足时操作组换到下一行并保持右对齐,组内也可换行,上下箭头保持成组。名称旁区分
|
||||
“始终显示”(英文 `Always shown`)和“可选 · 已启用/已关闭”。名称与用途可自然换行,不截断用途说明;正文与控件均不可横向溢出。
|
||||
|
||||
```text
|
||||
应用与工具
|
||||
管理主导航中的应用。系统工具由 GoodBuddy 提供并固定在侧栏底部。
|
||||
|
||||
应用
|
||||
魔法笔记 笔记、待办和评论 [显示在侧边栏 开]
|
||||
知识库 管理和检索知识 [显示在侧边栏 开]
|
||||
智能心跳 查看计划、建议和运行历史 [显示在侧边栏 开]
|
||||
|
||||
系统工具
|
||||
文件 打开当前工作区 内置
|
||||
终端 进入当前执行空间 内置
|
||||
浏览器 打开 GoodBuddy 受控浏览器 内置
|
||||
资源监控 查看本机 CPU、内存和磁盘 IO 内置
|
||||
|
||||
系统入口
|
||||
当前对话、运行记录和设置位置固定,不参与上述配置。
|
||||
应用中心
|
||||
[搜索内置应用]
|
||||
知识库 始终显示 [打开] [上移] [下移]
|
||||
智能心跳 可选 · 已关闭 [打开(禁用)] [设置] [上移] [下移]
|
||||
魔法笔记 可选 · 已启用 [打开] [设置] [上移] [下移]
|
||||
本机推理监控 可选 · 已启用 [打开] [设置] [上移] [下移]
|
||||
```
|
||||
|
||||
应用显隐使用共享 Switch,标签必须是“显示在侧边栏”,不能缩写为“启用”。切换时立即保存,
|
||||
请求期间禁用三个应用显隐 Switch;失败后恢复原值,并在对应设置行显示可重试错误。系统工具
|
||||
使用紧凑只读列表,不渲染禁用的卸载、停用或固定按钮。
|
||||
已启用但未常驻的应用可以直接打开,打开不自动常驻。关闭的应用仍能被搜索和配置,
|
||||
保留禁用的“打开”按钮,可通过“设置”重新开启。启用、常驻放在设置详情中;每行右侧提供成组的上下箭头,与打开、设置操作按空间换行。箭头使用共享图标按钮,名称和提示包含方向及应用名;目标取完整保存顺序中的相邻应用,仅首尾无目标或修改锁定时禁用对应箭头。无搜索词且未锁定修改时,所有行均支持拖动排序,包括关闭或未常驻的应用;搜索时禁用拖动,箭头仍按完整保存顺序操作,未命中的应用保留在顺序中。无搜索结果显示清除搜索入口。
|
||||
首期不展示商店页签、推荐、安装按钮、市场 URL 或 YAML 操作,系统工具也不在此列出。
|
||||
|
||||
功能已停用时,对应应用行显示“功能已停用”和设置入口,保留显隐偏好但暂时禁用 Switch。
|
||||
### 4.1 共同应用设置页
|
||||
|
||||
若某应用自身支持业务启停,应在该应用原有设置中保留“启用功能”开关,并用说明区分:
|
||||
智能心跳、魔法笔记、本机推理监控的通用应用设置统一从应用中心对应行的“设置”进入;应用页面不再提供重复快捷入口。本机推理监控保留“模型与连接设置”。知识库在管理 Modal 中提供“打开”和排序,不提供启用、常驻开关或通用空设置;原工作区实际配置入口保留。
|
||||
共同页遵守[设置中心 UI 规范](../../../UI-DESIGN.md#138-设置中心),不为每个入口建立一套设置。
|
||||
全局设置移除魔法笔记页签及跳转入口。旧“显示魔法笔记入口”绑定业务启用值,迁为“启用应用”并保留原值;
|
||||
“常驻左侧菜单”使用独立配置,只有已发布的独立显隐偏好才映射到常驻。
|
||||
|
||||
- “显示在侧边栏”只控制入口。
|
||||
- “启用功能”控制该功能是否运行。
|
||||
| 控件 | 表达含义 |
|
||||
| --- | --- |
|
||||
| 启用应用 | 控制业务可用性及主导航、会话等业务入口 |
|
||||
| 常驻左侧菜单 | 仅控制主导航捷径,关闭应用时保留该偏好,并说明开启后才显示 |
|
||||
| 显示未完成待办数量 | 仅控制魔法笔记导航徽标 |
|
||||
| AI 评论方式 | 即时/保存后自动/保存后手动,单选 |
|
||||
| AI 评论形式 | 长评 + 要点/长评/要点,单选 |
|
||||
|
||||
二元配置使用共享 Switch 和 `role="switch"`;评论选项使用共享单选控件。本机推理监控
|
||||
默认启用但不常驻;心跳应用默认关闭,常驻偏好默认开启,其余未配置布尔项默认开启。评论默认值沿用既有值,已有保存的选择保留。启用与常驻不可合并。
|
||||
设置详情最大宽度 `640px`,紧凑标题栏包含返回图标、应用名称、用途及关闭按钮。进入详情和
|
||||
返回列表时正文滚动位置重置到顶部;进入详情聚焦关闭按钮,返回后聚焦搜索框。
|
||||
共同设置表单使用共享 Switch,相关字段分组,组间使用弱分隔线;说明沿用正文字号。
|
||||
评论分段控件按内容占宽,窄窗口中标签与选项上下排列;详情不提供排序控件。
|
||||
智能心跳单条计划 CRUD 及 enabled 仍由其业务页面负责,应用级开关及执行条件见[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行)。
|
||||
|
||||
所有入口共享保存中状态。串行保存期间禁用相关启用、常驻、排序及笔记配置控件,保留
|
||||
最后确认值;持久化成功后的设置事件同步中心、侧栏、会话、应用页及其他设置入口,包括工具配置写入。重开中心刷新期间锁定修改,迟到的旧读取不能覆盖更新快照。失败在发起操作
|
||||
附近显示可重试错误并保留焦点;结果未知时显示“正在确认保存结果”,重读成功后再开放修改。
|
||||
|
||||
### 4.2 关闭后的页面与会话
|
||||
|
||||
关闭当前可选应用成功后,原页面或本机推理监控 Modal 显示“应用已关闭”,重新启用通过应用中心对应行的设置完成;本机推理监控需先关闭 Modal。保留草稿和内容状态,
|
||||
暂停该页面的新业务操作。重新开启后恢复可用性,不自动跳到已离开的应用页或重放任务。
|
||||
|
||||
知识库不显示应用关闭状态;会话知识控件、历史引用和工作区配置遵守原功能规则。心跳关闭时的侧栏行为见[监督者 UI 设计](../conversation-supervision/ui-design.md#会话侧栏)。可选应用的已有请求和后台任务继续按功能生命周期展示,详细边界见[关闭应用与恢复](./logic-design.md#32-关闭应用与恢复)。
|
||||
|
||||
## 5. 助手工作栏反馈
|
||||
|
||||
点击系统工具后,右侧工作栏按现有布局回流打开。主内容页面保持不变,侧栏不渲染工具内容。
|
||||
系统工具仅从右侧工作栏已有 Tab 或“+”目录进入,工作栏按现有布局回流,主内容页面保持不变。
|
||||
|
||||
- 文件:激活默认存在且不可关闭的“工作区”Tab,焦点进入目录区域或错误修复入口。
|
||||
- 终端:激活匹配终端,焦点进入终端输入区;新建期间保留按钮宽度和进度状态。
|
||||
- 浏览器:激活绑定当前 Conversation 的最近使用“浏览器”Tab;没有匹配实例时创建一个,
|
||||
没有 Conversation 时按产品规则复用最近实例或进入待绑定空状态。
|
||||
- 终端:选择已有 Tab 激活该确切终端并保留目标与历史状态;“+”每次创建额外终端,按当前
|
||||
项目选择本机或托管 SSH,无项目时使用本机 Home。新建期间显示进度,失败保留诊断。
|
||||
- 浏览器:选择已有 Tab 保留其 Conversation、页面及导航状态;“+”每次创建额外浏览器,
|
||||
无活动 Conversation 时新建待绑定空实例。不会按当前会话改选其他 Tab 或重绑已有实例。
|
||||
- 资源监控:激活“资源监控”Tab,焦点进入指标摘要;首个样本加载后开始绘制历史。
|
||||
|
||||
后台事件可以更新徽标,不能自动展开工作栏或切换当前 Tab。由用户点击快捷按钮触发的打开
|
||||
属于显式操作,可以展开工作栏并切换 Tab。
|
||||
文件和资源监控在“+”中按单实例规则聚焦或创建,终端和浏览器不按目标去重。
|
||||
后台事件可以更新 Tab 徽标,不能自动展开工作栏或切换当前 Tab。
|
||||
|
||||
### 5.1 资源监控面板
|
||||
|
||||
@@ -167,7 +205,10 @@ CPU 4% · 内存 620 MB · 桌面进程 7
|
||||
|
||||
| 对象 | 文案 |
|
||||
| --- | --- |
|
||||
| 设置分类 | 应用与工具 |
|
||||
| 底部入口 | 应用 / Apps |
|
||||
| 管理 Modal 标题 | 应用中心 / Application Center |
|
||||
| 菜单底部管理入口 | 管理应用 |
|
||||
| 应用设置开关 | 启用应用 |
|
||||
| 文件系统工具 | 文件 |
|
||||
| 终端系统工具 | 终端 |
|
||||
| 浏览器系统工具 | 浏览器 |
|
||||
@@ -175,7 +216,8 @@ CPU 4% · 内存 620 MB · 桌面进程 7
|
||||
| 资源系统工具 | 资源监控 |
|
||||
| 资源范围 | 本机 |
|
||||
| 指标缺失 | 不可用 |
|
||||
| 应用显隐开关 | 显示在侧边栏 |
|
||||
| 应用常驻开关 | 常驻左侧菜单 |
|
||||
| 关闭的应用页 | 应用已关闭 |
|
||||
| 内置来源 | 内置 |
|
||||
|
||||
不再使用“本地工作区”作为侧栏底部身份名称。项目和默认工作区的路径继续由项目选择器、页面
|
||||
@@ -183,33 +225,89 @@ CPU 4% · 内存 620 MB · 桌面进程 7
|
||||
|
||||
## 7. 键盘和无障碍
|
||||
|
||||
- 底部按钮均为独立 `button`,不能嵌套在整行设置按钮中。
|
||||
- Tab 顺序为文件、终端、浏览器、资源监控、设置。
|
||||
- 底部应用中心和设置均为独立 `button`,Tab 顺序与视觉顺序一致,不嵌套按钮。
|
||||
- 图标按钮通过 `aria-label` 提供名称,工具提示不能作为唯一可访问名称。
|
||||
- 工作栏打开后按现有焦点策略聚焦目标面板;用户通过固定工作栏开关关闭时,焦点返回该开关。
|
||||
- 选中按钮使用 `aria-pressed` 或等价的可访问状态,不能只依靠背景色。
|
||||
- 徽标的数量或状态合并进按钮可访问名称,例如“终端,1 个等待处理”。
|
||||
- 应用显隐 Switch 支持 Space 切换,保存失败后恢复原值并保留焦点。
|
||||
- 应用中心按钮提供 `aria-haspopup="menu"`、`aria-expanded` 和控制关系;菜单使用 `menu`/`menuitem`,打开后聚焦首个可用项,上下方向键循环选择,Home、End 到首尾,Enter、Space 激活。
|
||||
- Escape 关闭菜单并恢复入口焦点;Tab 关闭后继续正常焦点顺序,点击外部、焦点离开或再次点击入口也关闭。菜单不隔离背景,不增加遮罩。
|
||||
- “管理应用”打开的 Modal 使用共享 Portal、整窗遮罩、原生浏览器遮挡、背景隔离和焦点循环,初始焦点进入搜索。行操作与详情控件按正常 Tab 顺序访问,保留共享可见焦点环,不伪装成单个菜单项;详情切换焦点见共同应用设置页。
|
||||
- 工作栏使用 `tablist`、`tab`、`tabpanel`、`aria-selected`、游标焦点和方向键、Home、End。
|
||||
“+”使用普通按钮语义,同类型 Tab 仍按实例独立标记。
|
||||
- 徽标数量或状态合并进可访问名称;Switch 支持 Space,失败保留焦点。排序按钮名称包含
|
||||
应用名,操作结果通过 polite 状态区播报;拖动不是唯一排序方式。
|
||||
|
||||
## 8. 响应式
|
||||
|
||||
- 侧栏宽度 `220px` 至 `420px` 时,底部按钮尺寸不随宽度缩放。
|
||||
- 窄窗口继续使用现有侧栏抽屉;工具栏不转为多行。
|
||||
- 空间不足时缩小设置按钮前的弹性空白,不隐藏四个系统工具和设置。
|
||||
- 侧栏宽度 `220px` 至 `420px` 时,应用中心与设置均可操作,不随宽度缩小点击区域。
|
||||
- 窄窗口继续使用现有侧栏抽屉,底部入口不重叠;应用菜单向上展开并限制窗口边界、内部滚动。管理 Modal 的长名称可换行,搜索、设置与重新开启入口始终可达。
|
||||
- 右侧 Tab 单行横向滚动,“+”持续可见;工作栏保持停靠回流,不遮罩主页面。
|
||||
- 工具提示不得超出窗口;靠近底边时显示在按钮上方。
|
||||
- 粗指针设备不依赖悬停,按钮的可访问名称和选中反馈保持有效。
|
||||
|
||||
### 会话置顶与操作菜单
|
||||
|
||||
会话行的“更多会话操作”提供“置顶会话/取消置顶”,本地与消息通道会话均可使用。
|
||||
置顶会话显示带“已置顶”可访问名称的图标;当前项目和搜索过滤仍生效,过滤后的列表先显示
|
||||
置顶会话,两组内各按最新消息的创建时间倒序;时间相同按会话 ID 固定顺序,无消息时使用
|
||||
会话更新时间。新增消息在每 5 秒的排序检查时可以前移会话,仅顺序变化时调整位置;同一消息的文字、推理、工具进度和完成状态更新不
|
||||
改变排序。有消息的会话重命名或修改设置也不前移。摘要刷新、打开详情和重新启动沿用同一
|
||||
规则,行右侧仍显示实际更新时间。取消置顶恢复消息时间排序,不改变会话更新时间。
|
||||
置顶状态重启后保留;保存成功后更新图标和顺序,失败保留原状态并显示应用错误通知。
|
||||
存储尚未就绪或置顶请求进行中时禁用置顶操作。
|
||||
|
||||
消息内容、标题与运行状态实时更新。鼠标停留在列表内、焦点在列表内、滚动尚未停止
|
||||
200ms 或会话操作菜单打开时,跳过自动排序;操作结束后在下一次 5 秒检查时调整。
|
||||
新建、删除、置顶和取消置顶立即反映到列表中,不提前应用其他会话待处理的位置变化。
|
||||
切换项目或搜索范围立即显示对应列表;排序计时器不因消息增量而重新计时。
|
||||
|
||||
操作菜单通过 body Portal 浮在会话列表上方,不撑开会话行。菜单锚定更多按钮,默认位于
|
||||
按钮下方,随滚动、窗口及菜单尺寸变化重新定位,距窗口边缘至少 `8px`;空间不足时限制
|
||||
宽高并允许菜单内部滚动。使用共享表面色、边框、圆角与浮层阴影。
|
||||
|
||||
入口提供 `aria-haspopup="menu"`、展开状态和控制关系;菜单使用 `menu`/`menuitem`。
|
||||
打开后聚焦首个可用按钮,上下方向键循环移动,Home/End 到首尾;Escape 关闭并返回
|
||||
触发按钮,点击外部或焦点离开时关闭。本地会话继续提供分支、重命名和删除确认,复制与
|
||||
导出也适用于消息通道会话。持久化与刷新规则见
|
||||
[会话置顶存储](../assistant-workbar/execution-history-storage.md#会话置顶)。
|
||||
|
||||
## 9. 未来插件入口
|
||||
|
||||
插件市场交付后另行定义插件区域、固定数量、溢出和权限界面。系统入口与四个系统工具的位置
|
||||
保持保留,不向插件开放。
|
||||
FR-15、US-F1/US-F2 的市场 URL、YAML 导入导出及 ShareServer 分发在后续设计中定义。
|
||||
首期中心只浏览本地内置清单,不显示未来市场的空壳界面。
|
||||
|
||||
### 9.1 本机推理管理
|
||||
|
||||
FR-16 的 `local-inference` 入口已纳入本轮应用清单,可常驻主导航。从常驻入口、应用菜单和管理 Modal 的“打开”均进入同一本机推理监控 Modal,关闭来源菜单或管理 Modal,保留底层工作区、项目、会话和草稿。其他内置应用仍打开主内容区页面。
|
||||
Modal 与真实执行模块已接入;支持清单及验证范围分别见技术设计和实施进度。
|
||||
复用 `custom-task-dialog` 的整窗 body Portal、遮罩、背景隔离及浏览器遮挡机制。主 Modal 最大宽度 `960px`,高度随内容增长,最大高度为 `min(720px, 100dvh - 2 × --space-6)`;紧凑标题栏和关闭按钮固定在顶部,正文独立滚动。直接显示服务列表,没有页签或任务历史。工具栏显示“每 5 秒刷新 · CPU 100% = 一个逻辑核心”、模型与连接设置、刷新状态,窄屏可换行。服务行显示“服务进程 PID … · CPU …% · 工作集 … MiB”,预热时显示“正在建立采样基线”;共享进程的 worker 在该行显示无法独立统计的原因。
|
||||
初始焦点进入“关闭本机推理监控”,Tab/Shift+Tab 在当前 Modal 内循环。没有影响确认且没有操作提交时,关闭按钮或 Escape 关闭 Modal;点击遮罩不关闭。关闭后优先恢复仍可用的原触发元素,入口已移除时回到应用中心,窄侧栏已隐藏时回到侧栏开关。
|
||||
服务采用弱分隔列表;名称使用区块标题字号,状态及引擎元数据使用
|
||||
辅助字号,状态与名称同组并与操作区分开,原因与操作说明使用正文字号。桌面操作独立位于右侧;
|
||||
窗口宽度不超过 `720px` 时移到正文下方并左对齐,保留完整状态和原因。
|
||||
|
||||
- US-F3 每行显示服务名、简短状态、引擎、模型及错误。ASR 可用时显示“按需运行(状态未知)”;OCR 和向量使用执行模块的状态。TTS 显示不可用,外部连接不列入本机服务。
|
||||
- US-F4 不再提供单任务取消入口;活动任务仅在服务操作的影响确认中出现。
|
||||
- US-F5 服务行右侧保留实际支持的启动、停止、重启或模型加载、释放。
|
||||
- 停止、重启、释放前打开最大宽度 `480px` 的紧凑嵌套确认,列出正在使用的任务与影响;无已登记任务时说明执行前再次核对。初始焦点进入“保留运行状态”,最终危险按钮写明“确认停止服务/重启服务/释放模型”;取消保留运行状态。
|
||||
确认使用独立 body Portal,隔离底层管理 Modal,Tab/Shift+Tab 只在确认中循环;Escape 只关闭确认,关闭后恢复原服务操作按钮,按钮不可用时回到管理 Modal。确认存在时禁用主 Modal 关闭按钮;提交期间禁用确认和取消按钮,两层 Escape 均不能关闭,也不能重复执行。失败关闭旧确认,在服务列表显示错误并刷新;再次操作须重新确认当前影响。成功只关闭确认。
|
||||
操作失败呈现实际状态和可重试错误,无法确认则显示未知。服务恢复不自动重放任务。
|
||||
关闭 Modal、关闭管理应用或取消常驻不停止服务,模型文件卸载另行操作。
|
||||
|
||||
服务列表提供首次加载、无服务、加载失败重试及保留旧快照的刷新失败状态;刷新期间或失败时禁用服务操作。
|
||||
OCR 忙碌或模型不可用时禁用加载与释放,并在执行模块再次检查。
|
||||
文档解析与 HTTP OCR 的配置归属见[文档处理入口设计](../document-processing/ui-design.md#11-入口与职责),
|
||||
不因接入 HTTP 而扩展本机服务列表。“模型与连接设置”现直达 `model`,OCR 服务行的
|
||||
“文档解析设置”直达 `document-parsing`,关闭设置返回原工作区;实施与验证见
|
||||
[文档处理进度](../document-processing/progress.md#2026-09-19-实现与验证)。
|
||||
页面不显示无来源的进度条或资源数值。US-B6 至 US-B9 的应用中心、顺序、启用联动、笔记共同设置映射见
|
||||
[需求对应](./logic-design.md#6-与需求的对应关系)。
|
||||
|
||||
## 10. 项目活动汇总
|
||||
|
||||
对应 [FR-14](./prd.md#fr-14-项目活动汇总),状态与计数遵守
|
||||
[活动逻辑](./logic-design.md#9-项目活动汇总)。
|
||||
|
||||
- 全局汇总按钮仅在有活动时显示在项目选择器下方,紧凑显示待处理与运行中数量,空闲时整行隐藏,
|
||||
- 全局汇总按钮仅在有活动行时显示在项目选择器下方,紧凑显示待处理、运行中与已完成数量,无活动行时整行隐藏,
|
||||
右侧显示展开箭头,与下方新建会话保留 `8px` 间距。可访问名称明确数量属于“全项目活动”。
|
||||
- 项目菜单在项目行显示同口径数量,保留“本地项目 / 远程项目 / 远程通道”分组和 SSH Host
|
||||
层级,不因活动状态重排项目。数量为零时不显示标记,连接状态仍独立显示。
|
||||
@@ -219,10 +317,11 @@ CPU 4% · 内存 620 MB · 桌面进程 7
|
||||
相接,各自拥有边框、圆角与阴影;会话层对齐所选项目行,底部空间不足时仅上移会话层,
|
||||
不改变项目层的位置或高度。指针离开项目行进入会话层时不收起。展开项目不切换当前会话。
|
||||
- 会话层顶部显示项目名,每行显示完整可换行的会话标题与明确状态文字,待处理会话排在
|
||||
运行中会话之前。项目行保留子菜单箭头,会话行不显示箭头。不展开 Task、Job 或 Run 列表。
|
||||
运行中会话之前,已完成未查看会话排在最后并显示“已完成”。项目行保留子菜单箭头,会话行不显示箭头。不展开 Task、Job 或 Run 列表。
|
||||
- 点击会话先关闭菜单并恢复入口焦点,再请求进入该条会话;允许离开当前页面后,
|
||||
选择所属项目、清除搜索和该会话未读标记、清除任务选择与会话操作菜单,窄窗口同时关闭侧栏。
|
||||
设置页沿用未保存修改的离开检查,取消离开时保留原页面。
|
||||
设置页沿用未保存修改的离开检查,取消离开时保留原页面和完成通知。完成通知在目标聊天
|
||||
实际可见后清除,从普通侧栏进入也相同;设置覆盖下的聊天或隐藏文档不触发清除。
|
||||
- 菜单挂载到 body,使用 `menu`、`menuitem`、`aria-haspopup`、`aria-expanded` 与控制关系,
|
||||
不隔离背景、不增加遮罩。原生浏览器相交遮挡沿用共享机制。
|
||||
- 打开后焦点进入首个项目;上下方向键循环选择当前层项目或会话,Home、End 到首尾,
|
||||
@@ -231,4 +330,4 @@ CPU 4% · 内存 620 MB · 桌面进程 7
|
||||
顺序;点击外部、焦点离开、再次点击入口或隐藏侧栏也关闭菜单。
|
||||
- 单面板宽度为 `264px`,窗口小于 `620px` 或项目菜单两侧均无法容纳子菜单时,就地进入会话层并显示“返回项目列表”,
|
||||
触控无需悬停。浮层距窗口边缘至少 `16px`,每层最大高度为 `480px`,短窗口进一步限制
|
||||
高度并独立滚动。活动全部结束时,关闭已打开菜单并隐藏摘要。
|
||||
高度并独立滚动。成功完成但尚未查看的会话继续保留;所有活动行移除后,关闭已打开菜单并隐藏摘要。
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 设计中 |
|
||||
| 版本 | 0.2 |
|
||||
| 日期 | 2026-09-08 |
|
||||
| 状态 | 应用中心与本机推理监控已接入源码,整体验收待完成 |
|
||||
| 版本 | 0.4 |
|
||||
| 日期 | 2026-09-17 |
|
||||
| 关联 PRD | [应用导航与系统工具入口 PRD](./prd.md) |
|
||||
| 关联逻辑 | [功能逻辑设计](./logic-design.md) |
|
||||
|
||||
@@ -29,47 +29,50 @@
|
||||
|
||||
验收:
|
||||
|
||||
- Given 用户隐藏任意内置应用
|
||||
- Given 用户隐藏任意可选内置应用
|
||||
- When 用户查看主导航
|
||||
- Then “运行记录”仍显示在应用区之后
|
||||
- And 设置中没有隐藏、卸载或固定运行记录的选项
|
||||
|
||||
### US-A2 从固定入口进入设置
|
||||
|
||||
作为用户,我希望设置入口始终位于侧栏底部,以便快捷工具状态变化时仍能进入配置。
|
||||
作为用户,我希望设置入口始终位于侧栏底部,以便应用状态变化时仍能进入配置。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 任意主页面和任意侧栏宽度
|
||||
- When 用户查看侧栏底部
|
||||
- Then 设置按钮保持可见且点击区域不与工具按钮重叠
|
||||
- Then “设置”与“应用”为始终显示文字的独立按钮,点击区域不重叠
|
||||
- And 点击后继续遵守未保存内容的离开确认
|
||||
|
||||
## 3. Epic B:管理应用入口
|
||||
|
||||
本节的启用、常驻和共同设置适用于智能心跳、魔法笔记、本机推理监控;五个应用均可排序,知识库始终启用并常驻。设备共享在测试阶段默认关闭,提供启用开关,不提供常驻开关;启用后可从应用菜单和管理 Modal 打开,关闭后退出当前页面并保留目录及发布记录,旧设置缺失时默认关闭,明确启用值在重启后保留。业务验收见[设备共享首期需求](../device-sharing/prd.md#当前首期本地元数据)。心跳的默认关闭及执行边界按[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行)验收,保留既有故事 ID。
|
||||
|
||||
### US-B1 识别完整应用
|
||||
|
||||
作为用户,我希望魔法笔记、知识库和智能心跳显示在主导航,以便知道它们会打开完整页面。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 对应功能已启用且应用设置为显示
|
||||
- Given 始终显示的应用,或已启用且常驻的可选应用
|
||||
- When 用户打开主侧栏
|
||||
- Then 应用位于“当前对话”和“运行记录”之间
|
||||
- And 点击应用在主内容区打开对应一级页面
|
||||
- And 原有待办或建议徽标继续显示
|
||||
- And 点击知识库、智能心跳或魔法笔记在主内容区打开对应一级页面;本机推理监控打开保留底层工作区的 Modal,验收见 US-F3
|
||||
- And 原有待办或建议徽标按已保存的显示设置继续显示
|
||||
- And 当前对话之后按保存顺序显示应用;知识库始终显示,可调整位置,无启用或常驻开关;心跳按启用及常驻过滤
|
||||
|
||||
### US-B2 隐藏低频应用
|
||||
### US-B2 取消低频应用常驻
|
||||
|
||||
作为用户,我希望隐藏不常用的内置应用,以便缩短主导航,但保留已有数据。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 应用已有内容
|
||||
- When 用户关闭该应用的“显示在侧边栏”Switch
|
||||
- Then 主导航不再显示该应用
|
||||
- Given 应用已启用且已有内容
|
||||
- When 用户关闭该应用的“常驻左侧菜单”Switch 并保存成功
|
||||
- Then 主导航不再显示该应用,当前已打开页面保持不变
|
||||
- And 应用数据、计划和业务启停状态不变
|
||||
- And 用户可从同一设置位置恢复显示
|
||||
- And 用户可从应用中心打开该应用,或从同一设置位置恢复常驻
|
||||
|
||||
### US-B4 保留功能启停语义
|
||||
|
||||
@@ -78,35 +81,133 @@
|
||||
验收:
|
||||
|
||||
- Given 魔法笔记功能已启用
|
||||
- When 用户只隐藏其侧栏入口
|
||||
- When 用户只取消其侧栏常驻并保存成功
|
||||
- Then 笔记及待办处理能力继续按原设置工作
|
||||
- And 再次显示时恢复原页面和数据
|
||||
- And 重新常驻只恢复入口,不自动导航;再次打开时保留原页面状态和数据
|
||||
|
||||
### US-B5 恢复显隐保存失败
|
||||
### US-B5 保持设置保存一致
|
||||
|
||||
作为用户,我希望入口设置保存失败时继续使用原导航,以免界面状态与实际设置不一致。
|
||||
作为用户,我希望应用设置保存期间和失败后各入口保持一致,以免界面与实际配置不符。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 应用显隐更新尚未完成
|
||||
- When 用户切换“显示在侧边栏”Switch
|
||||
- Then 三个应用显隐 Switch 在请求期间暂时不可操作
|
||||
- And 更新成功后主导航立即变化
|
||||
- And 更新失败时 Switch 和主导航恢复原状态并显示可重试错误
|
||||
- Given 配置已加载且没有待确认的更新
|
||||
- When 用户修改启用、常驻、顺序或魔法笔记配置
|
||||
- Then 更新串行保存,在请求确认前,应用中心、应用页及全局设置中修改这组配置的所有控件均暂不可操作
|
||||
- And 各处保留最后确认值,不提前改变导航、业务可用性、徽标或会话入口
|
||||
- When 保存成功
|
||||
- Then 各入口同时采用确认配置,后续修改基于该配置提交
|
||||
- And 工具配置写入持久化成功后也同步各入口;较新的确认快照不被迟到的读取或保存响应覆盖
|
||||
- When 关闭后重新打开应用中心
|
||||
- Then 刷新最新配置期间锁定修改,读取失败保留重试入口,不允许用旧快照提交
|
||||
- When 保存明确失败
|
||||
- Then 各入口仍使用相同的最后确认配置,操作处显示可重试错误,保留焦点和编辑上下文,不重复通知
|
||||
- Given 响应中断,无法确认保存结果
|
||||
- When 用户查看设置
|
||||
- Then 显示正在确认保存结果,重读权威配置成功后才恢复修改;读取失败可重试读取,不自动重发变更或写回旧值
|
||||
|
||||
### US-B6 从应用中心访问内置应用
|
||||
|
||||
作为用户,我希望从固定应用中心找到应用,以便未常驻或已关闭的应用仍有访问和恢复入口。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 用户位于任意一级页面,包括全部可选应用均已关闭的情况
|
||||
- When 点击侧栏底部应用中心
|
||||
- Then 向上打开轻量锚定菜单,无模态遮罩;按保存顺序列出知识库和已启用的可选应用,不按打开历史或常驻筛选
|
||||
- When 点击菜单中的应用行
|
||||
- Then 菜单关闭;知识库、智能心跳和魔法笔记经既有离开检查在主内容区打开,本机推理监控打开保留底层工作区的 Modal,不自动常驻
|
||||
- When 点击菜单底部“管理应用”
|
||||
- Then 菜单关闭并打开既有管理 Modal,可按名称或用途搜索全部内置应用,包含未常驻及已关闭的可选应用
|
||||
- And 知识库行提供“打开”和排序,没有通用设置、启用或常驻开关;心跳行可管理启用和常驻,单条计划 enabled 不变
|
||||
- And 首期没有商店、推荐、安装、市场 URL、YAML 操作或系统工具入口
|
||||
- Given 应用已启用但未常驻
|
||||
- When 用户选择打开
|
||||
- Then 本机推理监控打开 Modal,底层内容无需离开;其余应用经既有未保存内容离开检查进入页面,不自动常驻
|
||||
- Given 应用已关闭
|
||||
- When 用户在管理 Modal 中找到该应用
|
||||
- Then 显示关闭状态,保留禁用的“打开”按钮,可通过“设置”进入共同设置重新开启
|
||||
- Given 搜索无匹配结果
|
||||
- Then 显示无结果及清除搜索入口,搜索不修改启用和常驻
|
||||
- Given 配置正在加载或读取失败
|
||||
- Then 中心保留加载状态或重试入口,不用默认值冒充已保存配置
|
||||
|
||||
### US-B7 保存常驻及应用顺序
|
||||
|
||||
作为用户,我希望常驻和顺序按设备保存,以便重启后仍使用熟悉的导航。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 首次使用且未配置导航偏好
|
||||
- Then 默认顺序为知识库、智能心跳、魔法笔记、本机推理监控、设备共享;心跳默认关闭且保留默认常驻偏好,魔法笔记默认常驻,本机推理监控默认启用但不常驻,仍可从应用菜单打开;设备共享默认关闭,启用后只在应用菜单和管理 Modal 提供入口;侧栏只按启用与常驻过滤可选项
|
||||
- When 用户修改常驻,或通过拖动、键盘可操作的上移和下移按钮排序并保存成功
|
||||
- Then 管理列表从上到下、菜单和侧栏按同一保存顺序显示,知识库和智能心跳可移动;当前对话、运行记录及底部系统入口位置不变,重启后恢复保存结果
|
||||
- And 每行均提供上下移动,以完整保存顺序中的相邻应用为目标;未常驻及关闭应用仍可排序,设置详情不提供排序控件
|
||||
- And 搜索时箭头仍按完整顺序移动,保留未命中应用,拖动禁用;清除搜索后显示完整保存结果
|
||||
- Given 旧设置仅保存本机推理监控、魔法笔记的顺序
|
||||
- When 升级后读取设置
|
||||
- Then 知识库、智能心跳补在前面,两个可选应用保留原有相对顺序和常驻值,设备共享追加到末尾
|
||||
- Given 应用已关闭或取消常驻
|
||||
- When 用户重新开启或常驻
|
||||
- Then 按保存顺序恢复入口;关闭或取消常驻不删除顺序,已关闭应用仍可管理常驻偏好
|
||||
- Given 保存失败
|
||||
- Then 按 US-B5 保留原常驻和顺序,在操作处提供重试并保留键盘焦点
|
||||
- Given 没有可见的可选应用或仅有一个
|
||||
- Then 固定入口和应用中心仍可用,没有移动目标的排序按钮禁用,不产生空白占位项
|
||||
|
||||
### US-B8 独立控制启用与常驻
|
||||
|
||||
作为用户,我希望关闭应用时保留数据和导航偏好,并能明确知道哪些业务操作已不可用。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 应用已启用
|
||||
- When 仅取消常驻并保存成功
|
||||
- Then 业务能力和后台操作保持原状,仍可从应用中心打开
|
||||
- Given 知识库已有会话关联,智能心跳已有启用或暂停的计划
|
||||
- When 用户关闭全部可选应用或修改其常驻与顺序
|
||||
- Then 知识库始终启用,侧栏位置稳定,页面可打开;知识控件及关联保持可用,请求遵守知识库自身规则,心跳单条计划 enabled 原值不变
|
||||
- And 心跳应用关闭后显示不可用状态,可从应用中心设置重新开启;新运行、在途运行及侧栏查询按[应用启停与执行](../conversation-supervision/logic-design.md#应用启停与执行)验收
|
||||
- Given 关闭的应用页面或本机推理监控 Modal 仍打开
|
||||
- Then 原位置显示“应用已关闭”,保留草稿及页面状态,阻止新业务操作而不强制跳转;用户从应用中心对应行的设置重新启用,监控 Modal 需先关闭
|
||||
- When 用户重新开启并保存成功
|
||||
- Then 仍停留的页面恢复可用;已离开时不自动导航,不重放或补发旧请求和任务
|
||||
- And 保留原常驻及顺序,未常驻应用不自动常驻
|
||||
- Given 启用变更保存失败
|
||||
- Then 按 US-B5 保留原业务状态和入口,不执行关闭或恢复副作用
|
||||
|
||||
### US-B9 迁移魔法笔记共同设置
|
||||
|
||||
作为魔法笔记用户,我希望在应用中心统一管理笔记设置,并在升级后保留已有选择。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 用户从应用中心的魔法笔记行进入设置
|
||||
- Then 显示笔记设置表单;笔记页面不提供重复入口,也不保留“显示魔法笔记入口”开关
|
||||
- And 全局设置不再显示魔法笔记页签或跳转入口,已有配置值和业务数据保持不变
|
||||
- And 启用、常驻、显示未完成待办数量分别控制业务、导航和徽标;隐藏数量不修改待办
|
||||
- Given 升级前已有明确保存的布尔值和评论选择
|
||||
- When 迁移配置并重启
|
||||
- Then 保留已有 `true`、`false`、评论单选值及业务数据;缺失导航配置时,本机推理监控默认不常驻,魔法笔记默认常驻;未配置的应用启用和笔记相关布尔项默认 `true`
|
||||
- And 旧入口开关若实际控制笔记启用,保留其启用含义和原值,不将其改作常驻
|
||||
- Given AI 评论方式或评论形式未配置
|
||||
- Then 分别默认“即时”和“长评 + 要点”,均为单选,不同时启用所有模式
|
||||
- When 任一入口修改配置并保存成功
|
||||
- Then 应用中心、主导航、笔记页、会话及其他设置入口同步采用确认值;保存中和失败均遵守 US-B5
|
||||
|
||||
## 4. Epic C:使用系统工具
|
||||
|
||||
### US-C1 一键查看文件
|
||||
|
||||
作为本地工作用户,我希望点击左下角文件按钮后直接查看当前工作区文件。
|
||||
作为本地工作用户,我希望从右侧工作栏查看文件,并保留工作区面板的目标模式。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 当前项目有有效根目录
|
||||
- When 用户点击文件按钮
|
||||
- Then 助手工作栏展开
|
||||
- And 聚焦默认存在且不可关闭的工作区单实例
|
||||
- And 文件按钮通过选中状态表明该面板正在显示
|
||||
- When 用户展开右侧工作栏并选择已有工作区 Tab,或从“+”选择文件
|
||||
- Then 聚焦默认存在且不可关闭的工作区单实例,不创建重复实例;仅迁移或布局修复时补回缺失实例
|
||||
- And 保留跟随或固定模式,跟随模式继续按工作栏规则更新上下文
|
||||
- And 仅该实例 Tab 显示选中状态,焦点进入目录区域
|
||||
|
||||
### US-C2 处理无效工作区
|
||||
|
||||
@@ -116,67 +217,62 @@
|
||||
|
||||
- Given 工作区面板处于跟随模式
|
||||
- And 当前项目和默认工作区均不可用
|
||||
- When 用户点击文件按钮
|
||||
- When 用户在右侧工作栏选择工作区 Tab 或从“+”选择文件
|
||||
- Then 工作区面板仍打开
|
||||
- And 面板说明目录不可用并提供选择或修复目录的入口
|
||||
- And 应用不静默改用其他目录
|
||||
- Given 工作区面板固定的目标已失效,即使当前项目或默认目录可用
|
||||
- When 用户选择工作区 Tab
|
||||
- Then 保留固定目标并显示该目标的修复状态,不回退到其他目录或切换为跟随模式
|
||||
|
||||
### US-C3 快速进入终端
|
||||
|
||||
作为本地工作用户,我希望点击终端按钮后进入当前执行空间的终端。
|
||||
作为本地工作用户,我希望选择确切终端继续查看,并能通过“+”创建额外终端。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 已有终端 Tab,包括绑定其他执行空间的终端
|
||||
- When 用户选择其中一个 Tab
|
||||
- Then 仅激活该确切实例,保留执行空间、目录及历史,不按当前项目查找最近匹配实例或重新绑定
|
||||
- Given 已选 Tab 正在关闭、已退出、已中断或失败
|
||||
- Then 保留其真实状态,不因选中而重建
|
||||
- Given 当前执行空间已有正在启动或运行中的终端
|
||||
- When 用户点击终端按钮
|
||||
- Then 工作栏聚焦最近使用的匹配终端,不创建重复实例
|
||||
- Given 当前执行空间没有终端
|
||||
- When 用户点击终端按钮
|
||||
- Then 创建一个绑定该执行空间的新终端
|
||||
- When 用户从右侧工作栏“+”选择终端
|
||||
- Then 每次创建绑定当前执行空间的额外实例,不复用或按目标去重
|
||||
- Given 当前没有项目
|
||||
- When 用户点击终端按钮
|
||||
- Then 创建或聚焦使用本机 Home 的终端
|
||||
- Given 当前执行空间只有已退出、已中断或失败的终端
|
||||
- When 用户点击终端按钮
|
||||
- Then 创建新终端并保留原终端的历史状态
|
||||
- When 用户从“+”选择终端
|
||||
- Then 创建使用本机 Home 的新终端,已有终端保持原目标和历史
|
||||
|
||||
### US-C4 使用远程项目终端
|
||||
|
||||
作为远程项目用户,我希望同一个终端按钮进入远程项目目录,以免误在本机执行命令。
|
||||
作为远程项目用户,我希望新建终端绑定远程项目目录,以免误在本机执行命令。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 当前项目绑定托管 SSH 执行空间
|
||||
- When 用户点击终端按钮
|
||||
- Then 创建或聚焦绑定该远程项目及其权威目录的终端
|
||||
- When 用户从右侧工作栏“+”选择终端
|
||||
- Then 创建绑定该远程项目及其权威目录的额外终端
|
||||
- And 面板持续显示远程主机和工作目录
|
||||
- And 连接失败时保留重连与诊断信息
|
||||
- And 连接失败时保留远端目标、重连与诊断信息,不回退为本机终端
|
||||
- When 切换当前项目后重新选择该终端 Tab
|
||||
- Then 仍激活原远程实例,不重绑到新项目
|
||||
|
||||
### US-C5 打开受控浏览器
|
||||
|
||||
作为用户,我希望浏览器按钮打开 GoodBuddy 受控浏览器,以便查看与 Agent 相同的会话。
|
||||
作为用户,我希望从右侧工作栏使用 GoodBuddy 受控浏览器,并按需创建独立页面。
|
||||
|
||||
验收:
|
||||
|
||||
- Given 浏览器面板跟随当前上下文且当前会话已有浏览器会话
|
||||
- When 用户点击浏览器按钮
|
||||
- Then 工作栏聚焦绑定当前 Conversation 的最近使用浏览器实例
|
||||
- And 不打开系统默认浏览器或无意创建重复实例
|
||||
- Given 现有浏览器实例都绑定到其他 Conversation
|
||||
- When 用户点击浏览器按钮
|
||||
- Then 工作栏创建并聚焦绑定当前 Conversation 的新浏览器实例
|
||||
- Given 当前没有活动 Conversation
|
||||
- And 已存在浏览器实例
|
||||
- When 用户点击浏览器按钮
|
||||
- Then 工作栏聚焦最近使用的浏览器实例并保持其目标
|
||||
- Given 当前没有活动 Conversation
|
||||
- And 不存在浏览器实例
|
||||
- When 用户点击浏览器按钮
|
||||
- Then 创建待选择目标的空实例
|
||||
- Given 用户需要同一 Conversation 的另一个独立页面
|
||||
- Given 已有浏览器 Tab,当前 Conversation 与其目标不同或没有活动 Conversation
|
||||
- When 用户选择该 Tab
|
||||
- Then 激活该确切实例,保留绑定目标、页面及导航状态,不重绑、重建或改选最近匹配实例
|
||||
- Given 当前有活动 Conversation,无论是否已有匹配实例
|
||||
- When 用户从工作栏“+”选择浏览器
|
||||
- Then 每次创建拥有独立页面和导航状态的新实例
|
||||
- And 同一 Conversation 内共享隔离登录上下文
|
||||
- Then 每次创建绑定当前 Conversation 的额外实例,页面和导航状态独立,同一 Conversation 内共享隔离登录上下文
|
||||
- Given 当前没有活动 Conversation,无论是否已有浏览器实例
|
||||
- When 用户从“+”选择浏览器
|
||||
- Then 创建待选择目标的空实例,不复用已有实例
|
||||
- And 以上操作均使用 GoodBuddy 受控浏览器,不打开系统默认浏览器
|
||||
|
||||
### US-C6 查看本机资源
|
||||
|
||||
@@ -186,17 +282,26 @@
|
||||
验收:
|
||||
|
||||
- Given 资源监控面板尚未打开
|
||||
- When 用户点击左下角资源监控按钮
|
||||
- When 用户从右侧工作栏“+”选择资源监控
|
||||
- Then 助手工作栏创建并激活单实例资源监控面板
|
||||
- And 页面显示整机 CPU、内存、磁盘读写及 GoodBuddy 桌面进程摘要
|
||||
- And 页面每 2 秒更新一次,并用图表保留最近 5 分钟
|
||||
- And 可见活动期间每 2 秒采样一次,前次请求未结束时跳过新请求,图表最多保留 150 个真实时间样本,连续采样约 5 分钟
|
||||
- Given 资源监控实例已存在
|
||||
- When 用户选择其 Tab 或再次从“+”选择资源监控
|
||||
- Then 激活同一本机单实例,不新建重复面板
|
||||
- When 用户切换到其他工作栏 Tab、收起工作栏、隐藏窗口或最小化窗口
|
||||
- Then 资源监控停止请求新样本
|
||||
- When 用户再次显示窗口并返回资源监控
|
||||
- Then 首次采样建立区间指标基线,下一次采样开始显示 CPU 和磁盘 IO 速率
|
||||
- And 内存等瞬时指标可立即显示,基线未建立时不显示虚构零值
|
||||
- Given 某项指标在当前平台不可用
|
||||
- When 页面收到部分结果
|
||||
- Then 该指标显示“不可用”及原因,其他指标继续更新
|
||||
- Given 本次采样全部失败或超时
|
||||
- Then 显示失败原因和重试入口,保留已有历史并标明最后更新时间,不把旧值当作实时数据
|
||||
- And 失败或暂停采样期间保留真实时间缺口,不补零或伪造中间样本
|
||||
- When 可见活动期间自动重试或用户手动重试成功
|
||||
- Then 按真实采样结果恢复更新,较晚返回的失活前旧请求不覆盖当前结果
|
||||
|
||||
### US-C7 从任意应用打开工具
|
||||
|
||||
@@ -205,35 +310,51 @@
|
||||
验收:
|
||||
|
||||
- Given 用户位于任意一级页面
|
||||
- When 用户点击文件、终端、浏览器或资源监控
|
||||
- When 用户展开右侧工作栏,选择已有工具 Tab 或通过“+”选择文件、终端、浏览器或资源监控
|
||||
- Then 主页面保持不变
|
||||
- And 右侧工作栏打开对应面板
|
||||
- And 工具不会覆盖主内容或伪装成一级页面
|
||||
- And 创建失败时工作栏保持展开并显示对应错误,主页面和其他实例不被清空
|
||||
- And 后台事件不自动展开工作栏或切换当前 Tab,左侧和应用中心不复制工具入口或选中状态
|
||||
|
||||
## 5. Epic D:可访问性和布局
|
||||
|
||||
### US-D1 使用键盘打开系统工具
|
||||
### US-D1 使用键盘管理应用与访问工具
|
||||
|
||||
作为键盘用户,我希望按顺序聚焦并打开左下角工具,以便不依赖鼠标。
|
||||
作为键盘用户,我希望浏览和管理应用,并通过右侧工作栏访问工具,以便不依赖鼠标。
|
||||
|
||||
验收:
|
||||
|
||||
- 每个系统工具和设置都是独立按钮
|
||||
- Tab 顺序与视觉顺序一致
|
||||
- Enter 和 Space 均可激活按钮
|
||||
- 每个纯图标按钮都有可访问名称和工具提示
|
||||
- 焦点样式不被侧栏边界裁切
|
||||
- Given 用户使用键盘访问侧栏底部
|
||||
- Then 应用中心和设置是独立按钮,Tab 顺序与视觉顺序一致,Enter 和 Space 可激活,图标按钮有可访问名称和工具提示
|
||||
- When 打开应用中心
|
||||
- Then 入口提供菜单展开状态和控制关系,焦点进入首个可用菜单项;上下方向键循环移动,Home、End 到首尾,Enter、Space 激活
|
||||
- When 按 Escape 关闭菜单
|
||||
- Then 焦点返回应用中心入口;Tab 关闭菜单后继续正常焦点顺序,点击外部或焦点离开也关闭
|
||||
- When 选择“管理应用”
|
||||
- Then 关闭菜单并进入既有 Modal,焦点进入搜索;每行的打开、设置、排序及详情中的启用、常驻控件按正常 Tab 顺序可达,不伪装成单个菜单项;进入详情及返回时滚动归顶,返回后聚焦搜索
|
||||
- When 通过键盘修改常驻或顺序
|
||||
- Then Switch 使用共享开关语义并支持 Space,上移和下移按钮名称包含应用名,结果通过 polite 状态区播报,失败保留焦点
|
||||
- Given 用户进入右侧工作栏
|
||||
- Then Tab 选择、游标焦点、方向键及 Home、End 由工作栏负责,使用 `tablist`、`tab`、`tabpanel` 和 `aria-selected`,按确切实例表达选中状态
|
||||
- And “+”为普通按钮,工具状态纳入 Tab 可访问名称;激活后焦点进入面板,关闭工作栏后返回固定工作栏开关
|
||||
- And 焦点环不被边界裁切,应用中心及设置沿用共享浮层和原生浏览器遮挡处理
|
||||
|
||||
### US-D2 在窄窗口使用快捷栏
|
||||
### US-D2 在窄窗口访问应用中心与工作栏
|
||||
|
||||
作为小屏用户,我希望侧栏抽屉中的工具按钮保持可操作。
|
||||
作为小屏用户,我希望应用管理和工具实例在窄窗口中仍可操作。
|
||||
|
||||
验收:
|
||||
|
||||
- 工具按钮保持单行且不重叠
|
||||
- 不因宽度不足隐藏文件、终端、浏览器、资源监控或设置
|
||||
- 触控点击区域符合统一界面规范
|
||||
- 关闭侧栏抽屉后焦点返回原触发按钮
|
||||
- Given 侧栏宽度为 `220px` 至 `420px`,或窄窗口使用侧栏抽屉
|
||||
- Then 底部应用中心和设置不重叠,不因宽度不足隐藏入口或缩小点击区域
|
||||
- When 打开应用菜单或通过“管理应用”进入 Modal
|
||||
- Then 菜单向上锚定且限制在窗口内,可内部滚动;Modal 中长名称可换行,搜索、常驻、排序、设置及重新开启入口均可达
|
||||
- And 触控不依赖悬停,工具提示保持在窗口内,点击区域符合统一规范
|
||||
- When 关闭侧栏抽屉
|
||||
- Then 焦点返回原触发按钮
|
||||
- Given 右侧工作栏有多个工具实例
|
||||
- Then Tab 单行横向滚动且“+”持续可见,工作栏停靠回流,不遮罩主页面;布局和 Tab 交互仍由工作栏负责
|
||||
|
||||
## 6. Epic E:项目活动级联
|
||||
|
||||
@@ -249,6 +370,9 @@
|
||||
- Then 进入该确切 Conversation ID,清除搜索、任务选择和会话菜单,窄窗口关闭侧栏
|
||||
- Given 设置页有未保存修改
|
||||
- Then 会话跳转仍需通过既有离开检查,取消离开保留设置草稿和原项目
|
||||
- Given 用户选择的确切 Conversation ID 未在内存中或已被删除
|
||||
- When 请求进入该会话
|
||||
- Then 刷新持久化会话列表后仍只查找同一 ID;刷新失败或会话不存在时显示通知并保留原页面和选择,不回退到项目最新会话或其他会话
|
||||
|
||||
### US-E2 键盘、窄窗口与实时结束
|
||||
|
||||
@@ -259,14 +383,34 @@
|
||||
- Then 点击项目后仅显示会话层,可通过返回按钮回到项目列表;长列表可滚动到最后一项
|
||||
- Given 鼠标从项目行移向相接的会话面板
|
||||
- Then 子菜单保持打开,点击会话正常导航
|
||||
- Given 当前项目的活动全部结束
|
||||
- Given 当前项目已无待处理、运行中或已完成未查看会话
|
||||
- Then 返回项目层并恢复可用焦点;全局无活动时整行隐藏摘要并关闭菜单,新活动到来时只恢复摘要
|
||||
- When 点击外部、焦点离开、按 Tab 或隐藏侧栏
|
||||
- Then 菜单关闭,后台新活动不自动重新展开
|
||||
|
||||
### US-E3 完成后保留至查看
|
||||
|
||||
对应 [FR-14](./prd.md#fr-14-项目活动汇总),具体状态规则见[活动逻辑](./logic-design.md#9-项目活动汇总)。
|
||||
|
||||
- Given 用户正在其他会话或应用中工作
|
||||
- When 本次应用会话内的运行成功完成,或已观察到的后台任务转为完成
|
||||
- Then 活动会话保留为“已完成”,全局与项目的已完成计数同步更新
|
||||
- When 从活动菜单或普通侧栏实际进入该会话的可见聊天
|
||||
- Then 清除其完成通知;没有其他活动行时隐藏摘要并关闭菜单
|
||||
- Given 聊天被设置覆盖或文档隐藏
|
||||
- When 对应运行完成
|
||||
- Then 完成通知保留,直到设置关闭且目标聊天在可见文档中显示;取消设置离开不清除通知
|
||||
- Given 目标聊天已可见且未被设置覆盖
|
||||
- When 运行成功完成
|
||||
- Then 不保留该会话的完成通知
|
||||
- Given 初始加载已完成任务历史,或任务失败、取消
|
||||
- Then 不产生新的完成通知;重载或重启也不恢复上一应用会话的通知
|
||||
- Given 同一会话仍有待处理或运行中工作
|
||||
- Then 按实时状态优先展示,只计入一个类别
|
||||
|
||||
## 7. 后续需求场景
|
||||
|
||||
本节对应新增计划,不计入应用中心首期完成条件。
|
||||
US-F1、US-F2 为后续市场计划;US-F3 至 US-F5 的本机推理监控已接入源码,当前支持边界见技术设计,验证范围见实施进度。
|
||||
|
||||
### US-F1 连接组织应用市场
|
||||
|
||||
@@ -275,33 +419,85 @@
|
||||
- Given 组织已部署提供应用市场的 ShareServer
|
||||
- When 用户配置市场 URL,并在来源要求时完成登录
|
||||
- Then 可以浏览该来源的应用目录、详情及可获取版本
|
||||
- Given 市场暂时不可达
|
||||
- Then 显示失败原因与重试入口,已安装和内置应用仍可打开,不切换公共市场
|
||||
- And 市场 URL 按设备保存,重启后恢复;认证信息使用受保护凭据存储,不作为可导出配置
|
||||
- Given 市场不可达、URL 无效、登录过期或认证失败
|
||||
- When 用户访问该来源
|
||||
- Then 显示具体原因及修改地址、重新登录或重试入口,已安装和内置应用仍可打开,不静默切换公共市场或其他来源
|
||||
- Given 用户修改市场来源或开始获取应用资源
|
||||
- Then 市场配置不改变模型、应用更新及 Runtime 等独立下载源;已开始的下载保持原来源,失败不静默切源
|
||||
|
||||
### US-F2 导入导出应用定义
|
||||
|
||||
对应 FR-15。
|
||||
|
||||
- Given 用户需要在私有部署间迁移应用
|
||||
- When 导出应用为 `.yml` 并在另一台设备导入
|
||||
- When 导出应用为 YAML,并在另一台设备选择 `.yml` 或 `.yaml` 文件导入
|
||||
- Then 预览应用身份、版本、可迁移配置和依赖,凭据与业务数据不包含在导出文件中
|
||||
- When 用户确认有效定义且所需依赖已满足,导入成功
|
||||
- Then 应用目录显示导入的身份、版本和配置,可按其启用状态打开或配置,不自动启动模型或任务
|
||||
- Given 同标识应用已存在
|
||||
- Then 用户可选择更新或取消,取消后原应用与配置保持不变
|
||||
- When 用户查看导入预览
|
||||
- Then 可选择更新或取消,取消后原应用与配置保持不变
|
||||
- When 更新成功
|
||||
- Then 显示已确认的新定义和配置
|
||||
- When 更新失败
|
||||
- Then 显示具体原因及重试入口,保留原应用定义、版本和配置,不留下部分更新
|
||||
- Given 文件格式无效、版本不支持或离线环境缺失程序包或模型资源
|
||||
- Then 显示具体问题与补充要求,不将应用误报为可运行,也不自动启动模型或任务
|
||||
|
||||
### US-F3 管理本机模型服务
|
||||
### US-F3 查看本机推理服务与资源
|
||||
|
||||
对应 [FR-16](./prd.md#fr-16-本机模型管理入口新增计划项)。
|
||||
|
||||
- Given 用户使用依赖本机模型的应用
|
||||
- When 从应用中心或常驻菜单打开“本机模型”
|
||||
- Then 可查看已接入模型的状态、管理方式及使用方,两处入口访问同一页面
|
||||
- Given 多个应用共用一个 GoodBuddy 受管模型服务
|
||||
- Then 展示同一服务及使用方,启动操作不重复创建进程
|
||||
- When 用户停止或重启仍有任务使用的受管服务
|
||||
- Then 先展示任务影响供用户决定,取消后服务保持运行;操作失败显示原因与实际状态
|
||||
- Given 模型服务由外部程序启动,且未接入其管理接口
|
||||
- Then 展示“外部管理”与连接配置,不提供停止或重启
|
||||
- When 用户关闭管理页或取消常驻
|
||||
- Then 模型运行状态不变
|
||||
- When 从常驻入口、应用菜单或管理 Modal 的“打开”进入“本机推理监控”
|
||||
- Then 均打开同一管理 Modal,来源菜单或管理 Modal 关闭,底层工作区、当前项目、会话和未提交草稿保持不变
|
||||
- And 直接显示服务列表,不出现任务页签、任务历史或外部连接;知识库、智能心跳和魔法笔记仍打开主内容区页面
|
||||
- And 初始焦点进入关闭按钮,背景不可交互,Tab/Shift+Tab 在 Modal 内循环;标题与关闭按钮保持可见,正文独立滚动
|
||||
- Given 没有影响确认且没有操作提交
|
||||
- When 点击关闭按钮或按 Escape
|
||||
- Then 关闭并卸载管理 Modal,底层工作区继续显示,模型服务不停止;点击遮罩不关闭
|
||||
- And 焦点恢复到仍可用的原触发元素;原入口已移除时回到应用中心,窄侧栏已隐藏时回到侧栏开关
|
||||
- When 查看服务列表
|
||||
- Then 显示服务名、模型、引擎、简短状态及错误;共享服务只展示一次
|
||||
- Given 向量服务独立进程正在运行
|
||||
- Then 显示带 PID 和进程范围的 CPU、工作集内存;CPU 首次采样显示预热状态
|
||||
- Given 服务 worker 与宿主共享进程,或进程已退出、采样失败
|
||||
- Then 在服务行显示不能独立统计或当前不可用的原因,不展示虚构零值或借用整个应用进程指标
|
||||
- Given ASR 有活动请求但没有 Worker 就绪观测
|
||||
- Then 显示“按需运行(状态未知)”,不推断服务正在运行
|
||||
|
||||
### US-F4 单任务取消入口(已移除)
|
||||
|
||||
保留故事 ID。FR-16 已收窄为服务监视,Modal 不再提供任务取消按钮。执行路径的原有取消行为和活动请求登记继续用于服务影响校验,见[逻辑设计](./logic-design.md#81-本机推理管理)。
|
||||
|
||||
### US-F5 管理实际执行服务生命周期
|
||||
|
||||
对应 FR-16;当前引擎支持清单见[技术设计](./technical-design.md#111-本机推理任务与执行服务)。
|
||||
ASR 取消独立 Worker,向量停止核对所有活动请求,OCR 有活动或排队请求时拒绝释放。
|
||||
运行阶段包括排队、加载和执行;当前未接入统一进度及来源项目对象关联。资源指标按服务进程统计,见 US-F3。
|
||||
|
||||
- Given 多个应用共用一个由 GoodBuddy 受管子进程承载的模型服务
|
||||
- When 用户启动、停止或重启服务
|
||||
- Then 仅提供引擎实际支持的操作,按同一服务串行处理;重复请求不重复创建进程或并发执行同一服务的重启
|
||||
- And 以真实进程退出和服务就绪确认结果,不将命令已发送当作操作成功
|
||||
- Given 用户准备停止、重启或释放服务或模型
|
||||
- When 用户发起操作
|
||||
- Then 在管理 Modal 上方打开紧凑影响确认,列出使用方、任务及影响;无已登记活动任务时说明执行前再次核对,取消后不发送操作指令且运行状态不变
|
||||
- And 初始焦点进入“保留运行状态”,Tab/Shift+Tab 在确认内循环,底层管理 Modal 不可交互;Escape 只关闭确认并恢复服务操作按钮焦点,按钮不可用时回到管理 Modal
|
||||
- When 确认提交尚未完成
|
||||
- Then 禁止重复执行,确认和取消按钮均禁用,两层 Modal 都不能通过关闭按钮或 Escape 关闭
|
||||
- When 操作成功
|
||||
- Then 只关闭影响确认,管理 Modal 保持打开
|
||||
- Given 执行前出现确认清单未包含的新任务
|
||||
- Then 拒绝执行,关闭旧确认并显示错误,自动刷新状态;刷新期间或失败时禁用服务操作,再次发起操作须确认新的影响清单
|
||||
- Given 停止失败,或重启时停止成功但启动失败
|
||||
- Then 显示实际状态、原因和可重试错误,不假报已停止或已恢复;无法确认时显示未知
|
||||
- When 服务再次启动或重启成功
|
||||
- Then 已中断任务不自动重放,由来源应用显式重新发起
|
||||
- Given 模型连接由外部程序管理
|
||||
- Then 在既有模型与连接设置中查看,不将其列入本机服务或提供停止、重启
|
||||
- Given 模型在进程内运行
|
||||
- Then 仅提供引擎实际支持的加载、释放,不显示虚构 PID、进程数或进程启动停止状态
|
||||
- When 用户关闭管理 Modal、关闭管理应用或取消常驻
|
||||
- Then 模型服务运行状态不变,取消常驻不关闭当前 Modal,卸载模型文件为独立操作
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# 助手工作栏
|
||||
|
||||
助手工作栏统一承载本机与远程执行空间、Task、成果、文件、浏览器和终端等持续工作界面。
|
||||
助手工作栏统一承载本机与远程执行空间、Task、成果、文件、浏览器、终端和笔记等持续工作界面。
|
||||
|
||||
“笔记”为应用级、可关闭的单实例 Tab,新布局默认不打开。它提供搜索、浏览和快速追加文字,
|
||||
与主页面魔法笔记共用存储;会话采集、来源跳转与草稿规则由
|
||||
[魔法笔记文档](../magic-notes/README.md)维护。该应用已接入并通过真实 Electron 验收;
|
||||
全仓验证未全绿,聚焦复跑与执行时失败见[笔记验证进度](../magic-notes/progress.md)。
|
||||
|
||||
## 文档导航
|
||||
|
||||
@@ -10,9 +15,10 @@
|
||||
| [多终端页签 PRD](./terminal-tabs-prd.md) | 工作栏应用的固定、可关闭、单实例和多实例策略,以及多终端生命周期 |
|
||||
| [浏览器多实例技术设计](./browser-tabs-technical-design.md) | 浏览器 Tab 身份、Conversation 绑定、IPC、BrowserService 和 MCP 路由 |
|
||||
| [实现与验证进度](./progress.md) | 工作区与 Runtime 相关改动的实现事实、验证证据与剩余阻塞 |
|
||||
| [执行记录存储与升级回收](./execution-history-storage.md) | 子任务过程差量存储、远程去重、旧库转换、空间回收及升级界面 |
|
||||
| [执行记录存储与升级回收](./execution-history-storage.md) | 子任务差量存储、远程去重、旧库转换、会话置顶、摘要与完整历史按需读取、内存引用释放 |
|
||||
| [Runtime 交互边界](./runtime-interactions.md) | 原生权限、结构化问答、后台拒绝、终端交互、并行与输出、消息请求状态及资源生命周期规则 |
|
||||
| [Runtime 进程复用技术设计](./runtime-process-reuse-technical-design.md) | 跨 Runtime 资源调研、锁定协议证据、进程与会话拆分、远端模型桥、实施顺序和性能验收 |
|
||||
| [Runtime 原生客户端启动方案](./runtime-native-client-proposal.md) | Local terminal/DS Web shortcut requirements, startup rules, current integration, validation boundaries and remaining remote/resource work |
|
||||
| [Runtime 执行清单技术设计](./runtime-checklist-technical-design.md) | OC/CN 原生清单提取、远程 Agent 传递复用、消息恢复与真实部署验收;尚未实现 |
|
||||
|
||||
当前尚未拆出独立 User Stories、功能逻辑和 UI 设计文档;产品场景与验收保留在现有 PRD,
|
||||
|
||||
@@ -81,12 +81,14 @@ Record<ConversationId, Record<BrowserTabId, BrowserLiveState>>
|
||||
|
||||
`BrowserViewport` 统一观察 body Portal 的插入、移除和可见属性变化,以及浮层尺寸、
|
||||
窗口尺寸、滚动和过渡结束。`browser-viewport-occlusion.ts` 按共享语义识别
|
||||
`aria-modal="true"`、dialog、alertdialog、menu 和应用通知:可见应用级 Modal 始终释放
|
||||
`aria-modal="true"`、dialog、alertdialog、menu、tooltip 和应用通知:可见应用级 Modal 始终释放
|
||||
viewport,其余浮层只在与浏览器区域相交时释放。隐藏节点和隐藏祖先不阻塞浏览器。
|
||||
最后一个遮挡消失后,只有当前活动实例恢复同一 viewport 租约;网页、历史和页面状态不变。
|
||||
终端关闭确认复用此机制,不再维护单独的隐藏条件。页面无需逐个登记弹窗,也不能靠提高
|
||||
CSS `z-index` 覆盖原生子视图。此逻辑仅涉及 Desktop Renderer 的显示租约,不改变
|
||||
Runtime、MCP 或远端 Agent 的请求、导航与生命周期。
|
||||
同行帮助仍使用 `role="tooltip"`;相交判定使用其内容矩形,不使用铺满窗口的
|
||||
`FloatingPortal` 宿主。真实 App 的相交与非相交验证见[浮层审查修复记录](../../quality/overlay-review-2026-09-27.md)。
|
||||
|
||||
浏览器应用声明 `visibleAcrossContextSwitches: true`,打开的 Tab 跨项目、会话切换继续显示。
|
||||
切换活动 Conversation 不改变当前选中的浏览器或其 `targetRef.conversationId`;用户在会话 B
|
||||
@@ -153,9 +155,38 @@ Conversation 归属,不新建租约或把页面重新归属到子级对话。
|
||||
单次工具取消信号、Tab 使用租约信号和请求信号,取消一次工具调用不终止后续调用。
|
||||
|
||||
该阶段 MCP transport、endpoint 和 token 生命周期不变,只扩展服务端 capability 内容和内部
|
||||
路由。工具调用使用当前请求绑定的 Tab,只有上述显式导航恢复会更换绑定。
|
||||
路由。工具调用使用当前请求绑定的 Tab;显式导航恢复和下述点击开窗会更换绑定。
|
||||
|
||||
### 6.2 Agent 多 Tab 阶段
|
||||
### 6.2 Page-created tabs
|
||||
|
||||
`target="_blank"` and `window.open` create managed `WebContentsView` tabs through Electron's
|
||||
`setWindowOpenHandler.createWindow`. Chromium retains the original request and opener relationship,
|
||||
including a blank window followed by a script-assigned location. These tabs share the Conversation
|
||||
partition, proxy and permission restrictions. HTTP(S) URL checks remain in the session and filtering
|
||||
proxy; `about:blank` is permitted only as the initial empty popup. Unsupported URL schemes remain denied.
|
||||
|
||||
BrowserService reserves capacity before creating the native view, assigns a new Tab/workbar identity,
|
||||
and owns initialization, the Driver and cleanup. Initialization failure or Conversation disposal releases
|
||||
the popup. Closing the opener leaves an already created popup alive. A popup's `window.close()` releases
|
||||
its service slot and usage leases; Renderer removes its workbar instance and returns to the opener when
|
||||
it is still available. No separate unmanaged BrowserWindow is created.
|
||||
|
||||
Live state includes `openerTabId`. A popup from the visible browser is selected even when the active chat
|
||||
has switched to a different Conversation. Other workbar panels and viewport occlusion rules retain their
|
||||
existing behavior. The existing workbar capacity notification still applies when all 32 slots are occupied.
|
||||
|
||||
During `browser_click`, the service collects popup registrations from that Tab and waits for their
|
||||
initialization. The result identifies the last created popup. BrowserModelTools binds subsequent tools to
|
||||
that Tab and asks the model to obtain new references with `browser_snapshot`; the MCP gateway also replaces
|
||||
its capability binding and usage lease. Switching visible tabs does not alter this binding. Closing the
|
||||
popup retains the existing explicit `browser_navigate` recovery behavior. A page may still be loading
|
||||
when the click returns. Popups requested asynchronously after the click has completed are displayed as
|
||||
managed tabs, but are not attributed to that completed tool call.
|
||||
|
||||
The remote ACP Runtime exposes its separately scoped image MCP server, not this desktop browser gateway;
|
||||
this change does not add browser access to the deployed GoodBuddy Agent.
|
||||
|
||||
### 6.3 Agent 多 Tab 阶段
|
||||
|
||||
只有明确需要 Agent 管理多个页面时,才增加:
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"date": "2026-09-17",
|
||||
"status": "passed-with-counting-limitation",
|
||||
"sourceBase": "3639566e1b52296c6a9bc409bcfcecb86c905e30",
|
||||
"source": "Working tree including consecutive-request cancellation and terminal snapshot fixes",
|
||||
"environment": {
|
||||
"platform": "Windows x64",
|
||||
"runtime": "OpenCode",
|
||||
"mode": "ask",
|
||||
"model": "Existing configured default text model",
|
||||
"upstreamProtocol": "http",
|
||||
"fullProductionApp": true,
|
||||
"isolatedProfile": true,
|
||||
"nativeInput": true
|
||||
},
|
||||
"checks": {
|
||||
"twoQueuedRequestsCompleteAndPersist": true,
|
||||
"cancelAfterExternalDispatch": true,
|
||||
"cancelToResendMs": 25,
|
||||
"resendCompletesAndPersists": true,
|
||||
"cancellationPersists": true,
|
||||
"stopAbsentAfterBothScenarios": true,
|
||||
"sendEnabledWithUnsentProbe": true,
|
||||
"terminalScreenshotsReviewed": true
|
||||
},
|
||||
"requests": {
|
||||
"instrumentedExternalDispatches": 4,
|
||||
"http200": 3,
|
||||
"cancelledBeforeResponseHeaders": 1,
|
||||
"observedRetries": 0,
|
||||
"toolExecutions": 0,
|
||||
"initialUninstrumentedCompletedGenerations": 2,
|
||||
"totalUiSubmissions": 6,
|
||||
"totalExternalDispatches": null
|
||||
},
|
||||
"limitations": [
|
||||
"Initial Main counting hook missed child-process HTTP traffic; total dispatches across attempts are unknown.",
|
||||
"Successful rerun used a transparent Main relay to the unchanged configured HTTP upstream for dispatch counting.",
|
||||
"Seven tool definitions remained exposed by production Ask, with zero observed tool executions.",
|
||||
"Upstream proxy retries and cancelled request outcome at the upstream are not observable.",
|
||||
"No deterministic stale-event ordering, remote Host, or restart verification in this live run."
|
||||
],
|
||||
"validation": {
|
||||
"focusedTestsPassed": 362,
|
||||
"typecheck": "passed",
|
||||
"productionBundle": "passed"
|
||||
},
|
||||
"cleanup": {
|
||||
"ownedProcessesStopped": true,
|
||||
"isolatedProfileRemoved": true,
|
||||
"temporaryBuildRemoved": true,
|
||||
"sensitiveCopiesRemoved": true,
|
||||
"originalSettingsUnchanged": true,
|
||||
"targetSourceHashesUnchanged": true
|
||||
},
|
||||
"localArtifacts": "consecutive-ui-0917/{final-evidence.json,scenarios.json,report.json,normal-terminal.png,cancel-resend-terminal.png} under the approved temporary directory"
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
处理速度;处理时间取决于历史数据量与磁盘速度,不承诺固定几分钟。用户可以退出,
|
||||
下次启动重新检查并继续转换未完成的数据;完成后回收空闲页并进入应用。失败时留在
|
||||
该页提供重试与退出,磁盘空间不足会明确说明,不删除聊天来腾出空间。
|
||||
以上转换和回收只针对仍需转换的旧格式数据;普通结构升级使用独立状态,不宣称正在
|
||||
清理子任务历史,也不会仅因数据库有空闲页而执行全库回收。
|
||||
|
||||
## 存储规则
|
||||
|
||||
@@ -33,8 +35,80 @@
|
||||
的缓存引用,不影响其他会话。若事务随后回滚或仍收到该任务的后续事件,按已提交记录重建。
|
||||
释放引用不等于进程 RSS 立即下降,垃圾回收和 Runtime 自身会话缓存另按生命周期回收。
|
||||
|
||||
## 活动历史连续保存
|
||||
|
||||
活动历史在桌面 `AssistantDatabase` 中按变化记录写入,保留记录顺序和重复 ID 的独立位置。
|
||||
连续保存使用最近一次成功提交的记录作为比较基准,不再逐次读取整份历史。关闭数据库、
|
||||
清空数据或检测到其他 SQLite 连接提交后重新读取;事务失败不更新比较缓存,重试仍基于已提交内容。
|
||||
本次优化不改变数据库 schema 或完整快照 IPC,也不改变 Agent、Runtime 和远程模型桥。
|
||||
本机与远程任务的桌面活动记录共用此保存路径,Host 不需要更新。
|
||||
|
||||
WAL 增长与 SQL 全历史扫描次数的定向验证见 [BUILD.md](../../../BUILD.md);
|
||||
这些指标不等于安装后应用的物理磁盘吞吐测量。
|
||||
|
||||
## 会话列表读取与前端保留
|
||||
|
||||
列表读取与数据库存储是两个不同层次。`messages.metadata_json` 继续保留完整过程,
|
||||
本次读取优化不修改 schema、迁移或用户数据:
|
||||
|
||||
- Renderer 启动及焦点、会话、队列刷新使用 `conversations.listSummaries(detailIds)`。
|
||||
普通未打开会话只返回标题、项目、置顶状态、上下文设置和消息数量/首条角色/最新消息创建时间,不解析或跨 IPC
|
||||
传输正文、工具、子任务及图片操作元数据。
|
||||
- 当前会话、现有 KeepAlive 视图,以及 Main 仍持有请求、数据库中仍有活动 Task 或
|
||||
streaming 消息的会话返回完整消息。列表包含全部 active 会话,不限制数量,
|
||||
Main 按置顶优先、组内更新时间倒序返回;Renderer 使用下述展示排序。仅置顶不会加载完整历史。
|
||||
- 打开历史通过 `conversations.get(id)` 读取全部消息,不使用会截断历史的分页上限。
|
||||
加载时明确显示进度;失败保留会话与输入,提供原地重试。发送、队列派发和压缩上下文
|
||||
必须先确认完整历史,发送时不按消息条数截断;复制与导出临时读取完整内容,不把未打开会话留入视图缓存。
|
||||
- 搜索由 Main 在 SQLite 中读取标题与正文,不读取过程元数据,保持原来的 Unicode
|
||||
大小写匹配和字面子串规则。范围包含全部 active 会话;前端同时匹配尚未
|
||||
落库的消息。搜索状态只保留命中的 ID,不额外持有整份历史对象。
|
||||
- KeepAlive 淘汰后,在现有保存周期中释放已经确认落库且空闲的详情及持久化比较引用。
|
||||
未保存消息、正在执行的会话、待答/待审批内容不被淘汰。读取中的请求按会话合并;
|
||||
晚到摘要不能覆盖刚打开的历史或新到的后台审批,摘要附带的实时新增消息仍增量保存。
|
||||
- `messageSummary` 仅为只读列表标记,不进入持久化 header。保存继续使用已有逐消息
|
||||
增量接口,空消息数组不代表删除历史。原有完整 `list()` 与数据库 `getConversation()`
|
||||
接口保留供其他调用方使用。
|
||||
- 图片状态通过同一合并函数处理。本地更新与 Main 图片操作按原有时间优先级合并,
|
||||
未变化消息返回原对象,避免每次列表刷新后把无变化的整个历史当作脏消息再次保存。
|
||||
- 展示顺序由 Renderer 的 `sortConversationsForDisplay` 统一派生,规则见
|
||||
[会话列表排序](../application-tool-navigation/ui-design.md#会话置顶与操作菜单)。完整消息取
|
||||
`createdAt` 最大值;摘要的 `latestMessageAt` 在已有聚合查询中取 `MAX(created_at)`,
|
||||
与摘要上附带的实时消息取最大值。该字段仅用于桌面只读投影,不增加数据库列或持久化副本。
|
||||
`updatedAt`/`updated_at` 继续随流式事件更新,原有快照合并、终态优先与逐消息保存规则不变。
|
||||
`useConversationListOrder` 在内存中保留展示 ID 顺序,以单个 5 秒计时器应用自动排序,
|
||||
并按上述交互规则暂缓重排;行数据始终使用最新会话对象,不延迟保存或同步。
|
||||
- 本机和托管 SSH 的 Renderer/Main 读取、持久化与恢复投影共用以上实现。Agent 的
|
||||
transcript、原生 Session、权限及模型桥协议未改变;完整列表、摘要和详情都附带
|
||||
Main 当前恢复请求的问题投影。
|
||||
|
||||
此优化不等于工具折叠内容的延迟渲染,也不保证操作系统立即归还全部 Private Commit。
|
||||
实际测量与适用边界见[实现与验证进度](./progress.md)。
|
||||
|
||||
## 会话置顶
|
||||
|
||||
界面行为见[会话置顶与操作菜单](../application-tool-navigation/ui-design.md#会话置顶与操作菜单)。
|
||||
`conversations.pinned` 是桌面数据库维护的会话属性,本地和消息通道会话共用,默认未置顶。
|
||||
完整快照与摘要均返回 `pinned`,旧快照缺省按未置顶处理。写入只通过
|
||||
`conversations.setPinned({ conversationId, pinned })`,经 Preload、可信发送方校验和严格
|
||||
Schema 校验后更新活动会话,再发布会话变更通知;不存在的会话报错。该写入不修改时间戳、
|
||||
消息或其他会话字段,普通自动保存 header 排除 `pinned`,不会覆盖置顶状态。
|
||||
|
||||
尚未落库的本地草稿先等待保存队列并保存,再设置置顶;消息通道会话直接调用专用接口。
|
||||
Renderer 合并时独立接收 Main 的置顶属性,即使本地正文更新时间更晚也不忽略它。
|
||||
请求修订号和进行中标记防止较早发出的列表/详情响应覆盖刚更新的置顶状态。
|
||||
此属性不修改 Runtime 或远端 Agent 协议。
|
||||
|
||||
## 旧库升级
|
||||
|
||||
启动检查只根据旧 schema 或仍未转换的笔记正文决定是否进入升级流程;正常写入、更新
|
||||
与删除产生的 SQLite 空闲页不是旧格式证据。迁移后新增数据使用当前格式,不因正常使用
|
||||
再次迁移。进入升级流程后仍分别判断结构、笔记转换与空间回收,不把旧 schema 当作
|
||||
回收许可。回收只用于实际存在旧子任务事件(schema < 35)、旧活动记录(schema < 44)
|
||||
或旧笔记正文的转换;例如 schema 45/46 升到当前结构不会自动转换历史或 `VACUUM`。
|
||||
同一次启动在内存中保留这一决定,只有原本需要回收的流程可重试压缩;若正文已转换、仅压缩中断,退出后
|
||||
重开不单凭空闲页重新进入迁移,剩余页由 SQLite 后续写入复用。不新增持久化迁移标记。
|
||||
|
||||
桌面在打开业务数据库之前启动独立 Node 工作线程,使用同一套编码和还原函数转换旧库。
|
||||
按任务有序读取,每批最多 32 条,提交后报告进度;SQLite 保留事务原子性及正常 WAL
|
||||
自动 checkpoint,每个任务结束时截断 WAL。读取缓存为 64 MiB,避免 1 MiB 级历史
|
||||
@@ -45,8 +119,10 @@
|
||||
不建立额外的恢复状态表。最后运行 SQLite `VACUUM` 回收已释放的空间,而非
|
||||
在转换前复制整份膨胀内容。回收仍需要额外可用磁盘空间,失败保留已提交转换并允许重试。
|
||||
|
||||
当前源码的业务初始化随后将数据库升级为 schema 35;schema 34 用户也会执行本次整理,
|
||||
成功后后续启动直接跳过。继续复用 schema 34 的子任务事件部分索引和同一差量表示。
|
||||
子任务整理对应 schema 35;schema 34 用户也会执行该整理,成功后后续启动直接跳过。
|
||||
继续复用 schema 34 的子任务事件部分索引和同一差量表示。业务初始化随后在事务中
|
||||
执行后续结构迁移,其中 schema 36 新增 `pinned INTEGER NOT NULL DEFAULT 0 CHECK(pinned IN (0, 1))`,
|
||||
已有会话默认未置顶,保留原消息与时间戳;失败回滚本次字段迁移。
|
||||
低于该 schema 的旧版客户端不能重新打开已经升级的数据库。需要回退客户端时,必须使用
|
||||
升级前备份,不要修改 `user_version` 强行绕过版本检查。
|
||||
|
||||
@@ -56,6 +132,8 @@
|
||||
- 工具 fixture 必须含生产对象会携带的 `undefined` 可选字段,并经过实际 JSON 写入/重读。
|
||||
同时验证旧完整事件和 schema 34 差量事件的整理、混合重试及无变化记录不被重写。
|
||||
- 验证远程重复事件、冲突事件,以及消息更新失败时事件和过程缓存的事务回滚。
|
||||
- 覆盖已完成历史转换、但正常聊天更新产生空闲页的后续结构升级:无转换进度、无
|
||||
`VACUUM`,会话、笔记、任务事件与完整性不变;验证真实 Worker 和同次启动重试。
|
||||
- 在真实旧库的已核验备份副本上运行 portable 的正常启动入口,核对事件逐条等价、
|
||||
其他用户表内容、数据库完整性和实际文件大小,不能只统计空闲页或只测试内存 fixture。
|
||||
- 同时验证真实本地 Runtime 和共享 Linux Host 的桌面持久化路径。真实场景必须让主代理
|
||||
|
||||
@@ -86,6 +86,7 @@ Runtime
|
||||
资源监控
|
||||
成果
|
||||
文档
|
||||
笔记
|
||||
```
|
||||
|
||||
- 应用不得根据当前项目、会话、Runtime、主机或探测结果无提示地增删能力目录项。
|
||||
@@ -96,17 +97,20 @@ Runtime
|
||||
Conversation、Job、Run 或心跳事项。
|
||||
- 当前能力、连接、数据和空状态可以动态变化。
|
||||
- 能力不可用时,目录项或已打开面板显示原因、影响和可执行的配置或切换入口,不能只通过隐藏表示。
|
||||
- 笔记遵循魔法笔记的业务启用设置,启用时提供应用级单实例 Tab,禁用前处理草稿,禁用后移除入口和实例;取消主侧栏常驻不影响工作栏可用性。采集与来源规则见[魔法笔记逻辑设计](../magic-notes/logic-design.md)。
|
||||
- 用户可以在设置中调整目录顺序;恢复默认布局恢复标准目录与默认打开面板,不强制打开全部能力。
|
||||
- 浏览器、终端及其他声明为多实例的能力可以创建多个实例,每个实例拥有独立身份和范围绑定。
|
||||
|
||||
### 3.2 当前上下文只提供默认值
|
||||
|
||||
任务中心只有一个工作栏实例,默认跟随当前项目,不跟随当前 Conversation。用户可以在同一
|
||||
实例内显式切换到全局任务或全部项目,但不能为不同项目打开多个任务中心 Tab。其他可绑定
|
||||
任务中心只有一个工作栏实例,任务列表固定跟随当前项目,不随当前 Conversation 筛选;顶部
|
||||
统计跟随当前 Conversation。不提供全局或所有项目入口。其他可绑定
|
||||
目标的能力使用当前会话、项目、Runtime 和主机帮助新面板实例初始定位,但这些上下文不是
|
||||
使用门槛:
|
||||
|
||||
- 监督默认选择当前会话,用户可以改选其他 Conversation、Task 或实验对象。
|
||||
|
||||
当前监督卡片已支持 Conversation/Task 跟随与固定,绑定保存在 tasks 实例的 `targetRef`;绑定只影响监督卡片,任务列表继续跟随当前项目。实现范围与验证见[监督者进度](../conversation-supervision/progress.md)。实验对象仍属后续工作。
|
||||
- Runtime 默认跟随当前会话或 Task;执行事件可以查看,但 Job/Run 不作为独立选择对象。
|
||||
- 终端创建时固定到当前项目执行空间,由项目配置自动决定本机或托管 SSH;没有当前项目时
|
||||
使用本机 Home。
|
||||
@@ -217,8 +221,8 @@ Runtime
|
||||
- 应用注册表分别声明实例策略、默认打开、必须存在、是否可关闭和是否允许排序。`single` 或
|
||||
`multiple` 只控制实例数量;`required` 控制布局修复,不能兼作关闭或排序策略。
|
||||
- 在工作栏“+”目录中重复选择单实例应用时聚焦已有实例;选择浏览器、终端等多实例应用时
|
||||
每次创建新实例。主侧栏系统工具快捷入口按当前上下文聚焦匹配实例,不能因连续点击无意
|
||||
创建重复实例。
|
||||
每次创建新实例。选择已有 Tab 只激活该实例,保留其目标与历史。主侧栏应用中心不重复
|
||||
提供文件、终端、浏览器或资源监控入口。
|
||||
- 支持方向键、Home、End、Enter、Space、可关闭面板的关闭命令和正确焦点恢复。不可关闭
|
||||
实例不渲染关闭按钮,也不响应关闭快捷命令。
|
||||
- 徽标显示未解决数量、等待审批或失败状态,并同时提供文字或可访问名称。
|
||||
@@ -230,7 +234,7 @@ Runtime
|
||||
从当前聊天右栏迁移时,Task Center 和工作区作为默认打开、单实例且不可关闭的基础应用保留;
|
||||
浏览器保留一个默认打开实例,但注册为可关闭的多实例应用;成果保持默认打开、单实例且可
|
||||
关闭;终端不默认打开,由用户通过“+”创建,并从首期开始支持多实例。Tab 必须单行横向滚动,
|
||||
不能自动隐藏或缩写到不可辨认,“+”不能随滚动消失。Task Center 继续作为 Task 的现有
|
||||
不能自动隐藏或缩写到不可辨认。“+”固定在滚动区外的行末,溢出时页签区两端提供固定滚动箭头,箭头不覆盖“+”。Task Center 继续作为 Task 的现有
|
||||
入口;审批定位到所属 Task 或 Runtime。智能心跳不作为工作栏页签,其报告、建议、历史和
|
||||
完整配置统一归属“智能心跳”菜单入口。当前阶段不新增独立自动化中心。
|
||||
|
||||
@@ -260,25 +264,26 @@ Runtime
|
||||
Task Center 保留为工作栏中的稳定入口,并在现有基础上适度完善:
|
||||
|
||||
- 只索引 Task;每个 Task 只关联一条 Conversation,一条 Conversation 可以关联多个 Task。
|
||||
- 显示名称、关联 Conversation、Global 或 Project 范围、Ask/Execute、状态、最近进展、
|
||||
真实活动时间和需要关注信息。
|
||||
- 顶部显示当前会话消息总数和 Agent 累计回复时间;列表显示本项目任务的名称、关联 Conversation、
|
||||
所属项目、Ask/Execute、状态、最近进展、真实活动时间、累计执行时间和需要关注信息。
|
||||
- 点击 Task 打开其关联 Conversation 并定位 Task。
|
||||
- 完整消息留在 Conversation;工具、Subagent、审批、错误和成果按 Task 关联到 Runtime、
|
||||
活动记录和成果查看器中,不在窄栏复制,也不呈现 Job/Run 树。
|
||||
- 智能心跳的报告、建议、历史和配置不进入任务中心。
|
||||
|
||||
任务中心是应用级单例,默认存在且不可关闭。它的数据范围默认跟随当前项目,并在标题或范围
|
||||
控件中明确显示项目;用户可以切换到“全局任务”或“全部项目”,该选择只改变同一实例的列表
|
||||
范围,不创建第二个任务中心 Tab。切换回“跟随当前项目”后,项目变化立即刷新列表。后台状态
|
||||
任务中心是应用级单例,默认存在且不可关闭。当前项目是唯一范围,标题明确显示项目;切换
|
||||
项目刷新任务和审批,同项目切换会话只更新顶部统计。无项目时提示选择项目。后台状态
|
||||
可以更新徽标和排序,但不能抢占用户当前工作。
|
||||
|
||||
当前实现已按活动项目过滤 Scheduled Task 和审批,并提供“当前项目”“全局任务”“全部项目”
|
||||
范围控件。
|
||||
回复时间按每轮请求区间累加,包含轮内工具和审批等待、不计轮间空闲;任务用时汇总历次执行
|
||||
请求,不按父任务生命周期计算。缺失历史有提示,远程缺少可靠证据时不虚构时间。
|
||||
项目任务标题右侧先显示“+”新建按钮,再显示筛选图标;状态筛选默认收起、默认全部。
|
||||
|
||||
主侧栏最近会话为关联 Task 提供轻量入口:父会话行只显示行首展开按钮,Task 子项使用
|
||||
任务图标和本地化摘要,展开层级只到 Task;点击 Task 子项打开同一 Conversation 并定位。
|
||||
新建定制任务使用 Modal 选择当前或新
|
||||
Conversation,默认 Execute,并持续显示 Runtime、Project、工作目录、工具和审批摘要。
|
||||
新建定制任务使用共享 Modal,默认立即执行和当前 Conversation,可选本项目其他可用会话或
|
||||
新会话,以及单次定时、每日或每周。内容必填,名称可留空从内容生成;工作模式使用关联
|
||||
会话的执行时配置,未设置时使用项目默认值,不在创建表单另设模式。
|
||||
|
||||
详细产品边界以 [Task Center PRD](../task-and-job/task-center-prd.md) 和
|
||||
[Task 与 Job 统一领域模型](../task-and-job/task-and-job-model.md) 为准。
|
||||
@@ -388,7 +393,7 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
|
||||
|
||||
当前项目目录的文件浏览与 Git 交互遵循以下规则:
|
||||
|
||||
- FR-W1:顶部同一行放置“文件 / Git 工作区”视图切换和右侧刷新;非 Git 目录仍保留刷新。
|
||||
- FR-W1:顶部同一行放置“文件 / Git 工作区”视图切换和右侧刷新;非 Git 目录仍显示选中的“文件”入口和刷新,仅隐藏“Git 工作区”。
|
||||
下一行只提供当前视图的工具:文件视图提供新建文件、新建目录,Git 视图提供分支、Fetch 和更改显示方式,
|
||||
不在 Git 视图重复目录导航或新建文件操作。操作使用带名称和提示的紧凑图标按钮。
|
||||
文件视图在根目录不显示孤立的文件夹图标或空路径行;进入子目录后提供“项目目录名 / 子目录”
|
||||
@@ -396,6 +401,7 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
|
||||
长目录名单行省略,完整路径通过提示查看;多级路径在导航内部横向滚动,不撑宽工作栏。
|
||||
选中目录时在该目录创建,选中文件时在其父目录创建,未选择时在当前浏览目录创建。
|
||||
无可用目录时隐藏无关操作。
|
||||
文件列表独立纵向滚动;顶部视图切换、刷新、新建操作和子目录路径导航保持可见,不随列表滚走。
|
||||
- 使用共享 `SegmentedControl` 切换“文件”和“Git 工作区”,默认显示文件;
|
||||
选择只在当前面板挂载期间保存在内存中,不持久化。未提交更改包含当前项目的全部
|
||||
Git 更改,不按 AI、用户或其他工具的修改来源筛选。
|
||||
@@ -416,8 +422,9 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
|
||||
不自动 force、reset、discard 或 stash,不因浏览分支而自动 Fetch。
|
||||
分支触发器复用共享模型选择按钮,带分支图标和展开箭头;Fetch 使用共享次按钮,二者在
|
||||
紧凑工作栏内保持同一行,长分支名省略并提供完整名称提示。
|
||||
展开的分支选择面板不超过 360px 或工作栏可用宽度;搜索使用共享表单标签、输入框与主题样式,
|
||||
分支列表独立限高滚动,创建按钮以紧凑次操作放在列表下方。Escape 收起面板并将焦点返回分支触发器。
|
||||
分支选择面板浮在内容上方,展开时不挤占变更列表和提交历史;宽度不超过 360px,并限制在窗口内。
|
||||
搜索使用共享表单标签、输入框与主题样式,分支列表独立限高滚动,创建按钮以紧凑次操作放在列表下方。
|
||||
点击面板外或焦点移出时收起;Escape 收起面板并将焦点返回分支触发器。
|
||||
- FR-W5:未提交更改支持列表和目录树切换;两种视图均可打开单文件 Diff。
|
||||
- FR-W6:Git 工作区底部提供默认收起的提交历史区,独立滚动,可调整高度。
|
||||
历史标题默认停在面板底部,首次展开向上占用当前 Git 内容区的一半高度;收起后标题回到底部。
|
||||
@@ -441,6 +448,7 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
|
||||
- 文件预览提供复制项目相对路径、刷新,以及仅本机项目可用的“使用默认应用打开”。
|
||||
刷新失败保留此前内容并显示错误;Markdown 默认渲染预览,可切换源码。
|
||||
- Diff 提供刷新、复制项目相对路径和“查看当前文件”;删除项不显示查看当前文件入口。
|
||||
文件路径与操作按钮位于分隔线上方,空间不足时换行;只有未暂存差异时省略“工作树更改”标题,同时存在已暂存差异时保留分区标题。
|
||||
- 手动刷新或任务结束更新 Git 状态时,同步重读根目录、当前浏览目录和已展开目录并去重,保留目录展开状态,
|
||||
刷新失败保留已有目录列表;重新展开目录时重读。目录读取错误在对应目录旁显示,
|
||||
可就地重试。正在查看的单文件 Diff 同时重读,旧请求不得覆盖新结果。
|
||||
@@ -457,6 +465,12 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
|
||||
|
||||
### 7.7 浏览器
|
||||
|
||||
Page requests made with `target="_blank"` or `window.open` open managed browser tabs in the same
|
||||
Conversation. A popup from the visible page becomes visible in the workbar; closing it from page script
|
||||
removes that popup tab. When an Agent click opens a new tab, subsequent browser tools follow that page
|
||||
and obtain fresh element references. Detailed lifecycle and routing rules are defined in
|
||||
[Page-created tabs](./browser-tabs-technical-design.md#62-page-created-tabs).
|
||||
|
||||
浏览器是可关闭的多实例应用。升级后的默认布局保留一个浏览器实例;用户从“+”目录选择
|
||||
浏览器时每次创建新的 Browser Tab。每个实例必须显示可区分的标题和绑定 Conversation,
|
||||
不能让多个工作栏 Tab 只是同一页面的重复控制入口。
|
||||
@@ -475,7 +489,7 @@ Renderer 不接收任意系统 PID 控制能力。控制动作引用 Main 签发
|
||||
|
||||
新建或重新创建的空白标签页尚无已提交 URL 时禁用刷新;只输入地址草稿不会启用刷新。
|
||||
首次导航期间仍可停止加载。操作错误仅在当前面板本次打开期间显示,切换面板、关闭当前
|
||||
页签或收起工作栏后清除,已离开面板的异步失败不得重新显示旧提示。
|
||||
页签或收起工作栏后清除;关闭失败后重试成功也清除原错误,已离开面板的异步失败不得重新显示旧提示。
|
||||
|
||||
同一 Conversation 下的 Browser Tab 共享隔离存储和代理上下文,但每个 Tab 拥有独立页面、
|
||||
导航状态、操作队列和元素引用空间。模型请求开始时绑定一个确定的 Browser Tab;用户切换
|
||||
@@ -644,7 +658,7 @@ type WorkbarPanelInstance = {
|
||||
|
||||
### 9.1 设置入口
|
||||
|
||||
设置中心“平台功能”提供独立“远程项目(技术预览)”页签及开关,默认关闭。关闭时不显示
|
||||
设置中心“平台功能”提供独立“远程项目”页签及开关,默认关闭。关闭时不显示
|
||||
“主机与远程执行”分类、托管 SSH 项目创建选项和已保存远程项目,Main IPC 同时拒绝新的
|
||||
SSH/远程项目操作;已有 Host、项目和凭据保持不变。若当前项目已经是远程项目,则切回
|
||||
项目列表中的第一个普通
|
||||
@@ -652,6 +666,10 @@ SSH/远程项目操作;已有 Host、项目和凭据保持不变。若当前
|
||||
|
||||
启用后,设置中心显示“主机与远程执行”分类,管理:
|
||||
|
||||
> 上述入口为当前实现。2026-09-28 已确认后续迁入应用中心的“主机管理”,并新增同级
|
||||
> “云环境管理”;范围、能力依赖及验收见[主机与云环境 PRD](../remote-host/prd.md)。
|
||||
> 本节保留现有行为,入口迁移尚未实现。
|
||||
|
||||
- 主机名称、地址、端口和用户名。
|
||||
- 认证方式和凭据配置状态。
|
||||
- Host Key 算法与 SHA-256 指纹。
|
||||
@@ -964,12 +982,14 @@ todo/plan,本次不为其增加工具。也不增加 plan 工作模式、计
|
||||
布局遵循 [UI-DESIGN](../../../UI-DESIGN.md#610-应用顶栏与全局操作)。
|
||||
|
||||
默认折叠为一行“执行清单 · 已完成 m/n”,有进行中条目时附简短摘要;展开后按原生顺序
|
||||
显示内容、状态及原生提供的优先级。行内状态使用图标和“待处理 / 进行中 / 已完成 / 已取消”文字,
|
||||
在摘要按钮旁的浮层显示内容、状态及原生提供的优先级,不推移或压缩下方消息流。
|
||||
行内状态使用图标和“待处理 / 进行中 / 已完成 / 已取消”文字,
|
||||
不用可勾选 Checkbox,也不根据位置推测优先级或进行中状态。请求结束后的摘要附真实
|
||||
请求结果,例如“已取消”,不改变条目状态。后台更新不强制展开、不抢焦点。
|
||||
|
||||
展开按钮提供 `aria-expanded` 和 `aria-controls`,支持 Enter/Space,折叠后焦点留在
|
||||
按钮;实时区域只播报摘要变化。复用全局颜色、间距和字体令牌,浅深主题均可读。窄窗口
|
||||
展开按钮提供 `aria-expanded` 和 `aria-controls`,支持 Enter/Space,展开后焦点进入清单。
|
||||
Escape 收起并返回按钮;点击外部或焦点移出也收起。实时区域只播报摘要变化。
|
||||
复用全局颜色、间距和字体令牌,浅深主题均可读。窄窗口
|
||||
摘要截短、展开内容换行并有界滚动,保留完成计数、请求结果与输入区;200% 缩放时仍可
|
||||
操作。没有清单或收到有效空清单时整行隐藏,历史内容仍按消息归属查看。
|
||||
|
||||
@@ -1057,7 +1077,8 @@ todo/plan,本次不为其增加工具。也不增加 plan 工作模式、计
|
||||
- 建立实例级跟随、固定和失效目标语义。
|
||||
- 保留现有任务中心、工作区、浏览器和成果行为;附件与知识库继续由 Composer 管理。
|
||||
- 将 Task Center 明确为 Task 的单例索引,并补齐范围、状态、最近进展、需要关注和直接打开 Conversation。
|
||||
- 审批在所属任务或 Runtime 中持续可见,不新增独立审批面板。
|
||||
- 审批在所属任务或 Runtime 中持续可见;任务中心只为未关联任务的审批保留按需显示的处理
|
||||
区域,具体规则见 [Task Center 交互](../task-and-job/task-center-prd.md#5-交互)。
|
||||
- 智能心跳菜单入口承接完整配置和范围后,再从任务中心移除重复表单;不得移除任务中心本身。
|
||||
|
||||
### 阶段 1:成果与 Runtime 可观测性
|
||||
|
||||
@@ -1,5 +1,633 @@
|
||||
# 工作栏实现与验证进度
|
||||
|
||||
## 2026-10-01:自定义 MCP 失败中断回复
|
||||
|
||||
已修复共用网关将单个上游发现失败升级为整轮失败的问题,行为见
|
||||
[本地 MCP 初始化失败恢复](./runtime-process-reuse-technical-design.md#41-本地-mcp-初始化失败恢复)。
|
||||
正常服务的连接继续复用,失败服务不进入工具列表,动态刷新失败也按服务隔离;
|
||||
取消和授权撤销继续生效。初次失败的服务在下一请求重试,原有连接超时未改。
|
||||
|
||||
验证使用真实 HTTP 上游和网关,覆盖部分失败、无可用工具、非法分页和超量目录、
|
||||
动态刷新失败后恢复、取消及撤销。Continue 使用真实网关和模拟 Host;新增实际 OpenCode
|
||||
二进制用例在自定义 MCP 返回 HTTP 503 后完成 Execute 回复,单次本地合成模型调用,
|
||||
外部模型调用 0 次。网关与 Continue 定向测试 65 项通过;OpenCode 生命周期 4 项通过。
|
||||
`npm run typecheck`、四个修改源码文件的 ESLint 通过。
|
||||
|
||||
完整 `npm test`:5,565 通过、89 跳过、4 失败。失败为 Agent 大依赖清单安装超时、
|
||||
Magic Notes 采集的 `memo is not defined`,以及终态探针和手动压缩的界面等待超时。
|
||||
全仓 lint 在并行修改的 `App.tsx` 性能测量代码处报告渲染纯度及不可变性错误;
|
||||
本次未修改这些文件,不将完整验证记为通过。
|
||||
远程 Agent 的独立 MCP 实现及 Story Graph 绑定不经过改动方法,本次无需远程 Host 验证。
|
||||
尚未发布或替换已安装应用。
|
||||
|
||||
## 2026-10-01:本地 MCP 晚到初始化覆盖新连接
|
||||
|
||||
根因修复见[本地 MCP 初始化失败恢复](./runtime-process-reuse-technical-design.md#41-本地-mcp-初始化失败恢复)。
|
||||
已核对随包 OpenCode 1.18.29:`MCP.add` 的初始化不随客户端取消而中止,完成后写入同名状态,
|
||||
并关闭该名称下已有的连接;`disconnect` 只处理当时已登记的客户端。旧实现在 `mcp.add`
|
||||
超时或取消后仍复用同一名称,前一轮的晚到初始化(授权已撤销,结果为 `failed`)会覆盖下一轮
|
||||
刚建立的连接。现在响应不确定的名称作废,后续改用唯一替代名称。
|
||||
|
||||
验证:新增真实 OpenCode+实际网关用例,通过本地代理挂起第一次 MCP `initialize`,
|
||||
使第一轮在 3 秒控制超时后结束,第二轮建立连接后再放行挂起的初始化。修复前该用例
|
||||
复现为第二轮连接状态从 `connected` 变为 `failed`;修复后第二轮连接保持 `connected`
|
||||
且请求完成。`opencode-runtime`、`opencode-runtime-lifecycle`、`knowledge-mcp-gateway`
|
||||
三个测试文件 180 项通过;`npm run typecheck` 通过;相关文件 ESLint 通过。模型为本地合成
|
||||
服务,外部模型调用 0 次。9 月 30 日那次失败的原始时序没有底层日志,未确认就是这一竞态;
|
||||
本次修复的是已复现的同类失败路径。尚未发布或替换已安装应用。
|
||||
|
||||
|
||||
## 2026-09-30:本地 MCP 连接诊断与恢复
|
||||
|
||||
已实现[本地 MCP 初始化失败恢复](./runtime-process-reuse-technical-design.md#41-本地-mcp-初始化失败恢复)。
|
||||
常驻 HTTP 网关和请求级授权保留;内置及自定义 MCP 对明确的暂时性传输故障最多重试一次,
|
||||
必须先成功清理失败连接。SDK 返回错误和抛出错误均有覆盖,未知失败、HTTP/API 拒绝和
|
||||
鉴权错误不重试。界面保留脱敏错误详情,两端失败诊断通过令牌摘要关联。
|
||||
网关初始化异常也会回收未完成会话。
|
||||
|
||||
最终定向验证:`npx vitest run` 指定 `opencode-runtime`、`opencode-runtime-lifecycle`、
|
||||
`opencode-runtime-permissions`、`knowledge-mcp-gateway`、`knowledge-mcp-gateway.obsidian`、
|
||||
`create-runtime`、`desktop-diagnostics`、`feedback-service` 八个测试文件,222 项全部通过。
|
||||
`npm run typecheck` 和 `npm run lint` 通过。真实 OpenCode 与实际网关的恢复用例在连接成功后
|
||||
注入响应丢失,验证断开先于重连、授权不变、请求完成和网关继续监听;该用例每轮产生一次
|
||||
本地合成模型请求,外部模型调用为 0。另有真实 HTTP 初始化连续失败后的会话回收测试。
|
||||
|
||||
全量 `npm test` 在 600 秒后超时,未取得汇总;超时前报告 `SettingsPanel.test.tsx` 中
|
||||
中英文磨砂玻璃开关两个用例失败。这些文件存在并行修改,未判定失败归因,全量验证不记为通过。
|
||||
本次未发布或替换已安装应用。远程 Agent 的 ACP 和 Agent MCP 连接路径未修改,
|
||||
不将本地测试结果作为远程恢复验证。
|
||||
|
||||
## 2026-09-30:OpenCode 终端启动准备
|
||||
|
||||
原先每次“在终端中打开” OpenCode 都会把随包插件依赖目录(约 3,600 个文件、52 MB)
|
||||
复制到临时目录,Windows 本机每次约 2.2–2.4 秒。现在该目录只复制一次到用户数据下的
|
||||
`terminal/opencode-config`,按 `.goodbuddy-ready.json` 判断是否需要刷新;Skills、Ask
|
||||
插件和模型配置仍按每次启动单独生成,关闭时清理。
|
||||
|
||||
Windows 本机实测启动准备:首次 2.9 秒,之后 19–44 ms。OpenCode 自身 TUI 从进程启动
|
||||
到显示输入框约 2.9–3.3 秒,由 OpenCode 程序决定,本次未改变。验证:原生终端单元测试
|
||||
5 项通过;已安装 OpenCode 的 Ask/Execute 真实 PTY 测试 2 项通过(合成模型,真实模型
|
||||
调用 0 次)。远程 Agent 使用独立的 OpenCode 启动路径,不受本次修改影响。
|
||||
|
||||
## 2026-09-28:消息取消与失败展示
|
||||
|
||||
消息终态按[Runtime 交互边界](./runtime-interactions.md)保存和展示。取消使用结构化
|
||||
`terminalStatus`,保持已有回答;终止提示不再补入正文。“重新编辑”只回填并聚焦输入框,
|
||||
没有可回填文字时隐藏入口。旧快照只对精确重复的形态隐藏状态副本,保留原正文。
|
||||
深色主题已移除覆盖失败提示颜色的通用规则;检索和压缩记录不再屏蔽终态的无障碍角色。
|
||||
|
||||
Windows 本地验证:
|
||||
|
||||
- 本次修复完成时 `npm run typecheck`、`npm run lint`、`git diff --check` 通过。
|
||||
后续并行的应用入口修改新增 `App.test.tsx:14430` 的 `getByRole(..., { exact: true })`,
|
||||
最后一次类型检查报 TS2769;该选项不属于 `ByRoleOptions`。未修改这处他人新增代码,
|
||||
当前组合工作区不能记为类型检查通过。最后一次 lint 和 diff 检查通过。
|
||||
- `npm test`:5,367 通过、84 跳过、1 失败。失败项为
|
||||
`tests/magic-notes-layout.electron.test.ts` 的原生拖拽测试,90 秒超时;单独复跑仍超时。
|
||||
该文件未修改,本次检查不记为全量通过。
|
||||
- 相关五文件复跑为 447 通过、14 失败;14 项均在并行入口改名期间查找“应用”按钮失败。
|
||||
入口源码更新后,单独复跑 `src/renderer/src/App.test.tsx` 的 304 项全部通过。
|
||||
其余四文件(数据库、时间线、清单和终态 Electron 探针)的 157 项在上述复跑中通过。
|
||||
- `tests/terminal-status-probe.electron.test.ts` 通过真实 App、Preload、生产 IPC、
|
||||
直连 Runtime 和 SQLite 验证空回答取消、部分回答取消、HTTP 400 失败;检查提示唯一性、
|
||||
浅深主题 computed style、重新编辑回填与焦点,以及数据库重开和页面恢复。
|
||||
每轮使用 3 次本地确定性 HTTP 请求,外部模型调用 0 次。探针在结束时关闭 Runtime、
|
||||
HTTP 服务和窗口,并删除本轮独立临时目录。
|
||||
- 数据库回归覆盖恢复事件事务回滚、失败与取消重放、空正文和已有回答保留、重开后元数据。
|
||||
App 和时间线回归覆盖新事件结构化判定、旧消息精确去重、正常正文保留、无效按钮隐藏、
|
||||
活动及工具终态。
|
||||
|
||||
本次未连接远程主机,未创建远程资源。远端事件仍通过既有公共事件进入桌面的
|
||||
`reduceRecoveredAgentEvent`,本次仅修改其消息投影与元数据保存,不改变 Agent、SSH
|
||||
传输、取消执行、ACK 或重连控制;本地 SQLite 恢复测试覆盖该修改。未验证 Linux/macOS
|
||||
原生界面,不把本地探针记为远端取消生命周期验收。
|
||||
|
||||
## 2026-09-28:DS Web 资源符号链接与 macOS 发布恢复
|
||||
|
||||
Desktop `v0.15.0` 原生构建中,两个 macOS 目标均因包外符号链接未通过签名校验。
|
||||
Linux Host 隔离 fixture 调用实际 DS 资源准备及 electron-builder 文件复制后,
|
||||
确认相对 CLI 链接变成构建目录的绝对路径。复制现使用 `verbatimSymlinks`,
|
||||
保留 npm 相对链接;回归移除原始安装和暂存目录后,仍须能读取最终包内链接目标。
|
||||
Windows 打包聚焦回归 45 项通过,1 项 POSIX 符号链接测试不适用于 Windows;
|
||||
类型、lint 和 0.15.1 双语说明校验通过。完整冻结快照在 Linux Host 全量
|
||||
5,372 通过、51 跳过,新增符号链接用例实际执行通过;macOS 原生签名仍待发布 CI。
|
||||
这只改变 Desktop 的本地 DS Web 资源准备,不改变 Agent 的独立 Runtime 包装、
|
||||
模型桥或启动控制,模型调用 0 次。恢复版本及最终证据见
|
||||
[发布恢复记录](../../development/release-preparation-0.15.0.md)。
|
||||
|
||||
## 2026-09-27:窄栏添加入口
|
||||
|
||||
“+”已移出页签滚动容器;左右箭头和溢出测量使用扣除添加按钮宽度后的页签区域。
|
||||
Windows Electron 回归覆盖 `300px` 工作栏、默认四页签和 16 个含长标题的页签,验证
|
||||
原生鼠标打开应用目录、创建浏览器实例、Escape 返回、箭头滚动及 Home/End 激活。
|
||||
窄栏扩到 `900px` 后,默认页签的溢出箭头消失,缩回后恢复。
|
||||
中英文、浅深主题均通过,模型调用 0 次。范围与命令见
|
||||
[窄容器布局修复记录](../../quality/responsive-controls-review-2026-09-27.md)。
|
||||
|
||||
## 2026-09-27 Continue Update Status
|
||||
|
||||
The shared Continue adapter now skips the update service before it emits the
|
||||
checking state; behavior is specified in the [native client design](./runtime-native-client-proposal.md#62-continue-与-opencode).
|
||||
The regression executes the patched method and verifies idle-only state when
|
||||
disabled, while retaining the original path when the flag is absent.
|
||||
|
||||
- Windows production coordinator/PTY and real DS browser validation completed:
|
||||
Continue made 2 supplier requests, OpenCode 1, and DS Web 2; all five returned
|
||||
HTTP 200 and the clients displayed the expected answers. Captured terminal
|
||||
output contained no `Checking for updates`. Original settings stayed byte-identical.
|
||||
- Current adapter source was bundled and uploaded through pinned-identity SSH to
|
||||
the shared Linux x64 Host, under a dedicated `/root/tmp/gb-update-check-*`
|
||||
directory. Continue 1.5.47 reached its initialized Ask TUI and displayed
|
||||
`Continue CLI`; 15 seconds of PTY capture contained no update-check text.
|
||||
This startup check made zero model calls and did not run the Agent protocol or
|
||||
remote model bridge. Agent helper imports this same adapter, but complete
|
||||
remote shortcut and real-model acceptance remain unfinished.
|
||||
- Adapter tests: 30 passed. Repository typecheck and lint passed. Full `npm test`
|
||||
reached the tool's 600-second limit without a final summary; it is not recorded
|
||||
as a passing full run.
|
||||
|
||||
Evidence is in the local temporary `opencode/dsh-017-verify` directory:
|
||||
`update-check-evidence.json`, `update-check-main-result.json`, and
|
||||
`remote-pty-run.cjs`. The local action/IPC harness uses jsdom and simulated
|
||||
Electron transport with real child clients. It does not reproduce or rule out
|
||||
full desktop-window freezing. The inherited TLS verification override and lack
|
||||
of a complete background-network capture also limit the evidence.
|
||||
|
||||
### Full production App follow-up
|
||||
|
||||
A fresh `npm run build` passed. The resulting production Main, Preload and
|
||||
Renderer were launched in an isolated Windows Electron profile. Native mouse
|
||||
input selected Continue and opened its terminal; native text input and Enter
|
||||
submitted a minimal request. The actual Continue output contained the expected
|
||||
joined answer and no `Checking for updates`. Main HTTP dispatch instrumentation
|
||||
recorded two additional provider requests, both HTTP 200: this fix's validation
|
||||
now totals seven external model requests. A subsequent no-submit probe copied
|
||||
initialized TUI content and typed input from the active xterm buffer.
|
||||
|
||||
Early driver attempts targeted a restored inactive terminal, giving an empty
|
||||
clipboard; selecting the newly opened terminal resolved this without product
|
||||
changes. Another attempt sent Enter before focus/input settled and dispatched
|
||||
zero requests. These harness failures are not product freeze fixes. The full App
|
||||
remained callable and accepted terminal input in the successful runs; the user's
|
||||
original freeze has not been reproduced or uniquely diagnosed. Captured PNGs
|
||||
have not been visually reviewed. The production TLS policy still enabled the
|
||||
verification override, despite the launcher's strict environment setting.
|
||||
|
||||
Local evidence: `full-app-model-events.json`, `full-app-calls.json`,
|
||||
`full-app-buffer.txt` and the `full-app*.cjs` drivers in the same temporary folder.
|
||||
The isolated profile was removed after checking that no probe-owned Electron or
|
||||
Node processes remained; the separate diagnostic evidence was retained.
|
||||
The full-suite rerun completed in 734 seconds: 5,298 passed,
|
||||
86 skipped and 3 failed tests, plus one failed suite setup. Failures were the
|
||||
OpenCode isolation fixture (`spawnSync npm.cmd EINVAL`), Magic Notes layout's
|
||||
90-second timeout, and two HeartbeatCenter tablist assertions. These files were
|
||||
not changed here; this is not an all-green suite. Original runtime settings were
|
||||
byte-identical after the full-App probes, and no probe-owned processes remained.
|
||||
Remote shortcut integration and full Agent/model acceptance remain outstanding.
|
||||
|
||||
### Failure triage follow-up
|
||||
|
||||
The OpenCode isolation and native Magic Notes layout files passed together through
|
||||
`npm test` (8 tests). The earlier full-suite supervisor invoked Vitest directly,
|
||||
without npm's launcher environment; its OpenCode setup error did not recur through
|
||||
the npm entry point. The layout timeout also did not recur. These focused results
|
||||
do not replace the failed full-suite result.
|
||||
|
||||
Both HeartbeatCenter language cases failed independently at that point. Source inspection
|
||||
shows the graph contains a separate, named PageTabs for events/entities/relations
|
||||
inside the page-level tabs; the older test assumes there can only be one tablist
|
||||
anywhere on the page. Neither that feature nor its test was changed for this
|
||||
Continue fix. No unrelated UI change or assertion relaxation was made.
|
||||
|
||||
A separate 2026-09-27 follow-up corrected the navigation scope assertions after
|
||||
checking the graph design. HeartbeatCenter, SupervisorWorkspace and shared tab
|
||||
tests passed together (96 tests); the full suite was not rerun. See the
|
||||
[validation record](../../quality/responsive-controls-review-2026-09-27.md#heartbeatcenter-断言修复).
|
||||
|
||||
The retained full-App log contains no matching unresponsive, crash, timeout or
|
||||
maximum-update-depth errors. This does not establish the cause of the reported
|
||||
freeze. This follow-up made zero external model requests (the fix-validation total
|
||||
remains seven); the OpenCode regression uses loopback model fixtures.
|
||||
|
||||
## 2026-09-27 Local Native Client Validation
|
||||
|
||||
Scope: [native client proposal](./runtime-native-client-proposal.md), FR-2/3/5/6/7/8,
|
||||
US-2/4/5/6/8, local portions of P1/P2/P3/P5. Source baseline:
|
||||
`55efca3ee1d8a491c4d9709888e302188cd988bd` plus the concurrent working tree.
|
||||
|
||||
- Fixed the coordinator's browser-open failure path: a ready DS service and its
|
||||
gateway remain available for owner-scoped reopen/stop. Startup failures still
|
||||
release their gateway. Focused regressions cover current credentials, work mode,
|
||||
Node selection, concurrent open, owner isolation and retry.
|
||||
The DS service also includes the coordinator configuration key in its own match
|
||||
hash; updated skill digests or launch settings cannot reuse an older instance.
|
||||
- DS Ask now maps built-in gateway read grants to the official MCP tool names.
|
||||
Custom MCP remains Execute-only, following existing Runtime behavior. The real
|
||||
DS process test loads an assigned directory containing `SKILL.md`, rather than
|
||||
only a synthetic loose Markdown skill file.
|
||||
- Custom standard Node uses the current local tool environment selection without
|
||||
a separate npm install. The managed Node fallback still has a bespoke pinned npm
|
||||
install and remains a delivery gap; it was not exercised by this real-model run.
|
||||
- The opt-in IPC integration test uses the production action component, preload
|
||||
methods, registered handlers, saved project/model stores, coordinator, official
|
||||
DS Web process, and real Continue/OpenCode PTYs. Electron transport is simulated
|
||||
by the existing IPC test harness and the action component runs in jsdom; this is
|
||||
not a full desktop-window or packaged-app acceptance result. DS Web itself was
|
||||
driven in real headless Edge. The hook retains credentials only in process
|
||||
memory; the temporary settings file stores opaque test-cipher identifiers.
|
||||
|
||||
| Client | Configured Model | Supplier HTTP Dispatches | Result |
|
||||
| --- | --- | ---: | --- |
|
||||
| DS Web 0.1.7-rc.2 | `deepseek-flash` | 2 | Workspace/model selected automatically; Web displayed `NATIVE_WEB_OK` |
|
||||
| Continue 1.5.47 | `gpt-6-astra` | 4 | Two prompts and two title requests; second transcript confirms the assistant response, with underscores consumed by Markdown rendering |
|
||||
| OpenCode 1.18.29 | `gpt-6-astra` | 1 | Native TUI displayed `NATIVE_OPENCODE_OK`; supplier response also contains it |
|
||||
| Total | | 7 | All HTTP 200; no further dispatches needed |
|
||||
|
||||
The first terminal assertion matched echoed prompt text and was rejected as
|
||||
evidence. The second prompt did not contain the expected joined answer. The
|
||||
OpenCode probe waits for the rendered TUI and uses bracketed paste before Enter.
|
||||
The initial six-dispatch bound was increased to eight to cover the remaining
|
||||
OpenCode path; seven were used in total. The host environment supplied
|
||||
`NODE_TLS_REJECT_UNAUTHORIZED=0`, so this run does not verify strict TLS.
|
||||
|
||||
Evidence under the approved temporary `opencode/dsh-017-verify` directory:
|
||||
`native-live-evidence.json`, `native-main-result.json`, `native-web.png`, and
|
||||
`native-{continue,opencode}-terminal.txt`. `native-live-run.cjs` composes the existing
|
||||
encrypted settings snapshot/safeStorage bootstrap with the production IPC test.
|
||||
Original `runtime-settings.json` remained byte-identical. The final test verifies
|
||||
DS stop/reopen/owner isolation and failed fetch after stop, closes both terminal
|
||||
sessions, and removes its temporary workspace/configuration. Owned live client
|
||||
processes were absent after completion.
|
||||
|
||||
Checks: official DS/coordinator focused tests passed (12) under standard Node 24.19.0;
|
||||
the opt-in UI/preload/IPC test passed both without provider calls and with the live
|
||||
hooks. Four installed Continue/OpenCode Ask/Execute tests passed with real clients
|
||||
and deterministic supplier responses; these made zero external supplier calls.
|
||||
Repository lint and Agent/Renderer TypeScript checks passed. Main typecheck is
|
||||
currently blocked by concurrent SSH
|
||||
test doubles missing `forwardLoopback`; no local native-client type errors remain.
|
||||
The full `npm test` run finished with 5,285 passed, 79 skipped and one failure:
|
||||
the desktop runtime-hook fixture lacked the new DS Web package tree. The fixture
|
||||
was corrected and all 44 build-release tests then passed in a focused rerun. The
|
||||
full run began before the final configuration-key regression and fixture updates;
|
||||
the final DS/coordinator and UI/preload/IPC paths were rerun separately and passed.
|
||||
This is not a claim of a subsequent all-green full-suite run. Full output is in
|
||||
`native-full-tests.log` beside the live evidence.
|
||||
|
||||
Remaining at this checkpoint: remote coordinator wiring and real Host acceptance belong to the
|
||||
concurrent remote implementation; Continue/OpenCode Ask built-in MCP mappings,
|
||||
managed standard Node distribution, full Electron/packaged UI acceptance and other
|
||||
platforms are not verified here. `ipc.ts` was not edited in this work.
|
||||
|
||||
2026-09-28 update: native Continue/OpenCode Ask now pass Main-bound read-tool
|
||||
names to native permission flags and execution hooks. The Story Graph work also
|
||||
adds Supervisor enablement to native-client reuse keys and gateway checks.
|
||||
The OpenCode generated plugin is executed by a focused test; packaged native UI
|
||||
acceptance remains separate. See [Story Graph validation](../conversation-supervision/progress.md#2026-09-28-story-graph-只读工具).
|
||||
|
||||
## 2026-09-26 结构升级与历史回收分离(未发布)
|
||||
|
||||
- schema 45/46 的回归先复现普通结构升级执行 `VACUUM`,修复后不转换历史、
|
||||
不回收正常空闲页,保留聊天、笔记、事件及 SQLite 完整性。
|
||||
- 启动决定在内存中分别保留笔记转换与回收需求,Worker 重试复用原决定;
|
||||
不增加 schema 或持久标记,不修改正常写入。规则见
|
||||
[旧库升级](./execution-history-storage.md#旧库升级)。
|
||||
- 真实 Node Worker 验证结构升级、旧笔记转换、取消与重试、文件大小和内容保留;
|
||||
启动调度回归验证允许/禁止回收的两种重试,中英文页面区分实际操作。
|
||||
六个定向测试文件 **47 项通过**,类型检查、lint 和 `git diff --check` 通过。
|
||||
- 完整 `npm test`:**5,138 通过、74 跳过、1 失败**。未修改的
|
||||
`tests/magic-notes-layout.electron.test.ts` 原生拖拽断言失败:首次索引列保持 168,
|
||||
单独复跑则 AI 列保持 280,均未达到拖动后的预期宽度。该 fixture 直接加载
|
||||
Magic Notes 和模拟 API,不运行本次存储升级路径。随后修正测试的原生输入同步:
|
||||
等待焦点、拖拽建立、宽度更新及结束,并明确左键按下状态,未弱化断言或改变产品
|
||||
布局;独立复跑及随后完整测试中的该用例通过。
|
||||
- 最终完整重跑:**5,137 通过、74 跳过、2 失败**,迁移相关及布局拖拽均通过。
|
||||
`inline-help.electron.test.ts` 超时,`overlay-layering.electron.test.ts` 子进程退出码
|
||||
为 null;这两个未修改的用例随后一起独立复跑 **2 项通过**,未提高超时或修改断言。
|
||||
最终 typecheck、lint、`git diff --check` 均通过。独立复跑不替代完整测试结果,
|
||||
不宣称本次全量或 CI 全绿。
|
||||
- 本机与托管 SSH 历史共用 Desktop 数据库升级路径;Agent、Runtime、模型桥和
|
||||
远程协议未变,不需要部署 Host 补丁。本次未调用模型,未改写用户数据库,
|
||||
尚无本次修复的 CI 或发布验证。
|
||||
|
||||
## 2026-09-21 重复历史迁移补修(Desktop 0.13.9 候选)
|
||||
|
||||
- 0.13.8 的检查仍在已有笔记时把 `freelist_count > 0` 当作迁移依据;正常聊天更新
|
||||
也会释放页。此前无笔记的回归提前返回,漏掉这一场景,不能证明该版本已修复。
|
||||
- 启动检查现在只依据旧 schema 或旧笔记 payload;空闲页只用于已进入迁移流程的
|
||||
压缩步骤。同一启动流程的 Worker 重试显式复用已确认的迁移决定,不持久化额外标记。
|
||||
若正文已转换但仅压缩中断,退出后重新启动不因剩余空闲页再次迁移,SQLite 可复用这些页。
|
||||
- 新回归先复现旧实现错误,再覆盖真实 SQLite 的旧笔记迁移、新建与编辑笔记、
|
||||
聊天释放页、连续三次重开、内容一致性和完整性;断言没有进度回调或 VACUUM。
|
||||
- 此次仅修改 Desktop 启动流程;新写入格式、schema、Agent 和远程协议不变。
|
||||
- 迁移、Magic Notes、数据库专项复跑 **135 项通过**(30 秒单测上限);此前一个
|
||||
listTasks 测试超时,独立复跑通过。加强后的迁移与笔记专项 **32 项通过**。
|
||||
全量测试此前被取消,候选全量验证和 CI 尚待完成。
|
||||
|
||||
## 2026-09-19 问答位置持久化
|
||||
|
||||
- Main 在收到问题时保存消息位置块,而不是等回答成功才追加;前台及重启恢复的实时待答
|
||||
快照、带来源的语义事件投影共用同一去重函数。答案写入不移动位置块,旧消息不补造历史
|
||||
顺序;规则见[Runtime 交互边界](./runtime-interactions.md#运行与失败)。未改变 schema。
|
||||
- `npm exec -- vitest run src/main/assistant/assistant-database.test.ts src/main/ipc.test.ts`
|
||||
通过 227 项,覆盖真实 SQLite 的问题/正文/工具交错、重复和重放、回答/跳过、完成及
|
||||
重新打开,以及三个设置重载入口的并行远程问题恢复。
|
||||
`npm exec -- tsc --noEmit -p tsconfig.node.json` 通过。
|
||||
- 共享 Linux x64 Host 使用已保存凭据与固定 Host Key,经 LAN 路由运行当前源码 Agent、
|
||||
Desktop Runtime 适配器和 Main SQLite 投影,使用真实 OpenCode `1.18.29`。首轮暴露实时
|
||||
问题没有语义来源的实际分支,使用 1 次真实文本模型请求后停止;补齐 Main 接收入口后,
|
||||
最终一轮 2 次请求,验证回答前位置块已经落库,回答后为 `tool → question → text`,
|
||||
完成重开后消息完全一致。合计真实文本模型调用 **3 次**,每次最多 1024 输出 token,
|
||||
每轮有限调用预算与 120 秒请求超时;图片模型调用 **0 次**。
|
||||
- 临时复验脚本为既有批准临时目录中的 `question-order-host-build.cjs`、
|
||||
`question-order-host-run.cjs`,最终日志为 `question-order-host-live.log`。
|
||||
两轮均停止测试 daemon,所属进程剩余 0,移除本轮专属 Host 目录及上传文件。
|
||||
该记录只覆盖 Main/Agent 路径,不代替 Renderer 视觉验收或最终全量验证。
|
||||
|
||||
## 2026-09-19 远程 CN 完整桌面验证
|
||||
|
||||
默认联合开发包已包含 OC/CN,并通过桌面导入、验签、传输至共享 Linux x64、安装及
|
||||
CN 项目创建。真实输入框发送产生清单更新,顶部和消息保存一致;退出重启后恢复完成
|
||||
与待办状态。修复实测发现的 CN 冷会话续发及重复序号问题,复验重启续发、点击停止和
|
||||
取消后再次发送通过。证据、调用口径及平台边界见
|
||||
[远程记录](../remote-host/runtime-checklist-validation.md#2026-09-19-联合包与完整桌面验收)。
|
||||
|
||||
任务与清单分别展开,容器高度有界,键盘可进入清单滚动区;状态图标不再影响文字
|
||||
基线。此前真实 Electron 视觉矩阵覆盖同时展开、长列表、窄窗口、浅深主题及 200% 缩放。
|
||||
本轮实际桌面截图进一步核对真实远程清单和取消状态。开发包未发布,非 x64 平台未实测。
|
||||
|
||||
远程 CN 真实长清单为 55 项、5,601 字符,展示与落库一致;清空后退出重启仍保持空数组。
|
||||
本轮 Agent gateway 账本记录 16 次模型请求,其中 15 次完成、1 次取消结果未确认。
|
||||
测试 Host 目录、所属进程及本地隔离 profile 已清理。全量 4707 passed、67 skipped、
|
||||
1 failed;本地 OC 压缩失败项与远程 ACP、项目选择、清单组件专项复验共 136 passed。
|
||||
最终 typecheck、lint 和差异检查通过。
|
||||
|
||||
## 2026-09-18 Remote Continue Checklist
|
||||
|
||||
CL-P1、CL-P3 的远程 CN 源码已接入受管包、HTTP 宿主到 ACP 的适配、模型桥、项目
|
||||
校验和聊天选择。CN 原生成功 Checklist 经既有 semantic transcript 传递统一清单,
|
||||
不增加数据库或重连协议。修复聊天页把远程 CN 选择恢复为 OC、隐藏 Continue 菜单,
|
||||
以及复合包验包默认读取 OC payload 的遗漏。
|
||||
|
||||
Linux x64 当前源码与开发包通过验包、prepare/commit、Agent bootstrap/health、CN
|
||||
激活、真实模型清单新增/清空、未 ACK 断线重放、取消和同对话 Ask 续发。本轮新增
|
||||
17 个外部模型 HTTP 请求;精确分项、临时探针、已清理的 Host 目录及剩余验收见
|
||||
[远程执行清单验证记录](../remote-host/runtime-checklist-validation.md#cn-部署与生命周期)。
|
||||
本阶段使用单 Runtime 开发包;后续联合包与完整桌面验证见上方 2026-09-19 记录。
|
||||
|
||||
远程相关测试 27 文件、374 项通过;最终项目选择、顶部组件和安装管理 41 项、App
|
||||
远程选择与继承 3 项、CN 复合包安装专项通过。typecheck 和 lint 通过。
|
||||
全量测试完成一轮:4665 passed、67 skipped、6 failed。其中 CN 新增包测试的文件名
|
||||
错误已修正并复验;其余 5 项为 ActivityPanel/activity-store 的引用复用断言,本轮未
|
||||
修改这些文件。最终 typecheck、lint 和专项结果见上述远程记录。
|
||||
并行工作区更新后,ActivityPanel/activity-store 的 39 项专项复验全部通过。
|
||||
|
||||
## 2026-09-18 Local Runtime Checklist Backend
|
||||
|
||||
CL-P1 and the backend portion of CL-P2 are implemented. Shared contracts use
|
||||
`runtime-checklist.ts`, `AgentEvent.type = checklist`, and message field
|
||||
`runtimeChecklist`. Local OC validates parent Session, submitted message identity,
|
||||
assistant parent identity and native tool arguments. CN extracts before truncation,
|
||||
commits successful calls once, excludes startup history and retains explicit clears.
|
||||
Message metadata serialization, reconstruction, remote replay deduplication and
|
||||
terminalization preserve the field without a database migration. Existing IPC
|
||||
transport already forwards these events. Renderer and `acp-remote-runtime` were
|
||||
outside this backend change; no shared Linux Host acceptance is claimed here.
|
||||
|
||||
The focused backend suite passed 275 tests across shared checklist, OC/CN Runtime,
|
||||
host adapter, permissions, real-binary lifecycle and assistant database tests.
|
||||
Coverage includes OC reused Sessions and stale/child updates, native event ordering,
|
||||
CN long lists and omitted success arguments, failure preservation, history dedupe,
|
||||
empty lists, database reopen and remote event replay/terminalization.
|
||||
`npm run typecheck` and `npm run lint` passed on the final validation run.
|
||||
The full `npm test` run completed with 4604 passed, 67 skipped and 9 failed:
|
||||
one Agent offline-package inventory test exceeded 60 seconds; eight failures were
|
||||
in concurrently changing Magic Notes renderer tests (including PDF `DOMMatrix`
|
||||
availability and canvas UI expectations). These files were not modified here.
|
||||
|
||||
Real model evidence on Windows, using production local adapters and isolated
|
||||
temporary profiles:
|
||||
|
||||
| Runtime | Version | Submitted tasks | Actual model HTTP POSTs | Observed result |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| CN | 1.5.47 | 1 | 3 | Two successful Checklist calls, two structured snapshots; A pending then completed, B pending |
|
||||
| OC investigation | 1.18.29 | 2 | 6 | Native tools succeeded; revealed todo.updated precedes running input, initially no projected snapshot |
|
||||
| OC corrected ordering | 1.18.29 | 1 | 3 | Two native updates projected; done retained |
|
||||
| OC final sortable message ID | 1.18.29 | 1 | 3 | Two native updates projected; assistant parent IDs matched; done retained |
|
||||
|
||||
Total: **15 actual external model HTTP POSTs**, all HTTP 200, **5 submitted tasks**.
|
||||
The unavailable old OC server probe and two isolated startup attempts dispatched
|
||||
zero model requests. Startup attempts were blocked by the probe's fetch wrapper
|
||||
not accepting Request objects; correcting that wrapper required no product change.
|
||||
The proxy allowed only the exact native checklist tool, with a per-run request cap.
|
||||
No business-file tools ran. Saved production settings were read through isolated
|
||||
credential handling; the existing explicit test connection was used in memory
|
||||
because its endpoint/model tuple differed from the saved connection. Credentials
|
||||
were not printed, original settings were unchanged, and isolated run data was removed.
|
||||
|
||||
The real probes verified adapter extraction and OC Runtime events, not desktop
|
||||
rendering. CN Runtime event forwarding and persistence were verified by tests.
|
||||
Long lists, clears, cancellation boundaries and history replay were tested with
|
||||
fixtures/database tests, not exhaustively exercised through real models. Temporary
|
||||
probe sources: `cn-todo-probe.ts` / `cn-todo-run.cjs` under the approved OpenCode
|
||||
temporary directory; each run printed its dispatch count and structured results.
|
||||
|
||||
## 2026-09-17 连续请求取消与停止状态
|
||||
|
||||
- 复现并修复 OpenCode 迟到的会话取消中断下一条请求:同一次运行复用一个有界 abort,
|
||||
等待取消结束后释放会话锁;取消失败时移除对应绑定。规则见
|
||||
[本机 OpenCode](./runtime-process-reuse-technical-design.md#4-本机-opencode)。
|
||||
- 复现并修复旧 streaming 快照覆盖本地终态后停止按钮残留。完成、取消两种回归在
|
||||
修复前失败,修复后通过;连续两条消息结束后按钮恢复。规则见
|
||||
[消息底部请求状态](./runtime-interactions.md#消息底部请求状态)。
|
||||
- App 257 项测试通过;最终 OpenCode Runtime、权限与真实 lifecycle 105 项通过。
|
||||
lifecycle 使用真实 OpenCode 二进制与 SDK、本地模型 fixture,验证延迟取消后同会话
|
||||
下一条正常完成及 SSE 回收。该轮外部真实模型调用 0 次;后续完整 App 实测见下文。
|
||||
- `npm test`:4454 项通过、67 项跳过、2 项失败;失败均为 heartbeat 迁移的
|
||||
`duplicate column name: pinned`。`npm run typecheck`、修改文件定向 ESLint、
|
||||
`git diff --check` 通过;全库 lint 因范围外 `docs/features/story-graph/demo.js`
|
||||
的 22 项 `document` 未定义错误失败。
|
||||
- 托管 SSH 经 `ManagedRemoteAcpRuntime` / `AgentOwnedAcpPrompt` 使用独立 ACP
|
||||
取消流程,不复用此 HTTP abort 实现;本次未改 Agent 或远程协议,未做共享 Host 实测。
|
||||
|
||||
### Real-model follow-up
|
||||
|
||||
Current-source production Main, Preload, and Renderer were built into dedicated
|
||||
temporary output and launched in an isolated Electron profile on Windows. Native
|
||||
input submitted two queued OpenCode Ask requests, then cancelled another request
|
||||
after external HTTP dispatch and clicked resend within 25 ms. Both queued answers
|
||||
and the resend completed and were persisted. The cancellation was also persisted.
|
||||
After each scenario, the real DOM had no Stop button; Send was enabled after
|
||||
entering an unsent readiness probe. Both terminal screenshots were reviewed.
|
||||
|
||||
The instrumented run dispatched exactly four external text-model HTTP requests:
|
||||
three returned HTTP 200 and one was cancelled before response headers. There were
|
||||
no observed retries or tool executions. A transparent Main relay forwarded to the
|
||||
unchanged configured HTTP upstream to count child-process requests; model answers
|
||||
and events were not simulated. An initial driver attempt completed two additional
|
||||
real generations before its counting hook was found not to cover child traffic.
|
||||
The total external dispatch count across both attempts is therefore unknown.
|
||||
|
||||
This verifies the observed full-App flows, not deterministic stale-event ordering,
|
||||
remote Hosts, or restart behavior. Isolated credentials, profile, build, and owned
|
||||
processes were cleaned up; original settings and target source hashes were unchanged.
|
||||
The follow-up focused suite passed all 362 tests, and `npm run typecheck` passed.
|
||||
Redacted evidence: [real-model report](./consecutive-requests-live-2026-09-17.json).
|
||||
|
||||
## 2026-09-15 Managed browser popups
|
||||
|
||||
Implemented [page-created tabs](./browser-tabs-technical-design.md#62-page-created-tabs): native
|
||||
Electron popup creation now registers a managed Tab, and click results update direct-tool and MCP
|
||||
bindings. Renderer selects a visible opener's popup across chat switches and removes script-closed
|
||||
popup instances. Existing worktree changes were retained.
|
||||
|
||||
Validation against the current source:
|
||||
|
||||
- `npx vitest run src/main/browser src/main/agent/knowledge-mcp-gateway.test.ts src/main/agent/model-tool-provider.test.ts src/renderer/src/RightAssistantSidebar.resize.test.tsx src/shared/contracts.test.ts`: 10 files, 232 tests passed.
|
||||
- `node build/run-browser-tabs-electron-e2e.cjs`: passed twice after making the test wait for the shown
|
||||
native view's animation frames before sending mouse input. Real Chromium verified `target=_blank`,
|
||||
`window.open(url)`, blank-then-location, script close, opener close, shared partition, unsupported URL
|
||||
denial, and subsequent direct/MCP snapshots on the popup. No fixed click delay was added to production.
|
||||
- `npm run typecheck` and `npm run lint`: passed. `git diff --check`: no whitespace errors.
|
||||
- `npm test`: 4319 passed, 67 skipped, 6 failed across 5 files. Failures were Agent offline dependency
|
||||
installation timeout, local Runtime reuse timeout, diagnostic-log writer expectations, two heartbeat
|
||||
migration fixtures with `duplicate column name: pinned`, and an App external knowledge binding test
|
||||
missing the `Remote handbook` heading. The full suite was not green; none of these failures exercise
|
||||
the popup registration or binding path.
|
||||
|
||||
The Electron test uses production BrowserService, CDP and MCP with a local HTTP fixture; the workbar
|
||||
selection/removal path is covered by the actual Renderer component test. A complete installed-App
|
||||
visual test and macOS/Linux native popup checks were not run. No external model calls were made for
|
||||
the popup validation. Remote ACP exposes only its scoped image MCP server (`imageMcpServers`), so the
|
||||
deployed Agent has no separate affected browser implementation and no remote Host change was needed.
|
||||
|
||||
## 2026-09-15 会话置顶与浮动操作菜单
|
||||
|
||||
已核对当前源码差异:置顶经专用 IPC 写入 SQLite,schema 36 迁移保留已有会话;会话操作
|
||||
改为 body Portal 菜单。规则分别见[会话置顶](./execution-history-storage.md#会话置顶)和
|
||||
[菜单界面](../application-tool-navigation/ui-design.md#会话置顶与操作菜单)。
|
||||
|
||||
本次任务提供的已验证聚焦测试结果为 backend **135 项通过**、renderer **283 项通过**。
|
||||
本轮仅更新文档,未重新运行测试;任务未提供这两组的完整命令或原始日志路径。
|
||||
已核对 `assistant-database.test.ts` 与 `App.test.tsx` 中相关回归,覆盖持久化与重开、
|
||||
schema 35 升级、最近 100 条之外的置顶、自动保存隔离、草稿先保存、通道会话、失败保留
|
||||
状态、旧刷新竞态,以及 Portal、窗口边界、键盘与外部关闭。
|
||||
|
||||
原生 Electron 视觉与完整应用交互尚未验证,不能据组件测试声明实际菜单排版、原生浏览器
|
||||
遮挡或跨主题/窗口尺寸视觉验收通过;本轮也未运行全量测试、类型检查、lint 或构建。
|
||||
|
||||
## 2026-09-15 工作栏标签标题国际化修复
|
||||
|
||||
实现与兼容规则见[多终端页签 PRD 第 10 节](./terminal-tabs-prd.md#10-实例session-与持久化)。
|
||||
任务中心、工作区、成果和带默认编号的终端标题由 Sidebar 按当前语言派生;重命名清除默认
|
||||
编号。浏览器标题保持原行为,未修改工作栏壳层。本次只涉及桌面标题显示与布局描述,不改变
|
||||
本机或远程 PTY、Runtime、Agent 协议或桌面到 Agent 的执行路径。
|
||||
|
||||
聚焦验证:
|
||||
|
||||
- `npx vitest run src/renderer/src/RightAssistantSidebar.resize.test.tsx src/shared/workbar-contracts.test.ts src/renderer/src/WorkbarShell.test.tsx`:3 个文件、74 项通过。
|
||||
- Sidebar 新增 6 项行为回归,覆盖中英切换且不改写布局、默认终端编号持久化与跨语言恢复、
|
||||
重命名后恢复,以及中文默认名、英文默认名和自定义名三种无标记旧终端。测试使用真实
|
||||
Sidebar、WorkbarShell 和布局 Schema,终端面板使用测试替身,未启动 Shell。
|
||||
- `npx eslint src/renderer/src/RightAssistantSidebar.tsx src/renderer/src/RightAssistantSidebar.resize.test.tsx src/shared/workbar-contracts.ts`:通过。
|
||||
- 首轮测试有一处新增浏览器文案断言错误,修正为现有“未绑定会话”后通过。按用户要求未运行
|
||||
全量测试、全量 typecheck、全量 lint 或构建;本轮未进行 Electron 实机验证。
|
||||
|
||||
## 2026-09-15 Desktop 0.13.6 发布准备
|
||||
|
||||
用户批准两项性能修复的中英文更新说明,指定 Desktop 0.13.6,不发布 Agent 或
|
||||
LoongArch 预览,并明确排除工作区另外四个应用导航设计/演示文件。
|
||||
`package.json` 与 lockfile 的两处根版本一致,更新说明校验通过。
|
||||
双语功能矩阵已核对并补充按需历史行为;Agent 保持 0.13.0。
|
||||
|
||||
用户随后要求直接交由 CI 发布,本地独立候选的全量验收命令被取消,未记为通过;
|
||||
开发阶段的完整测试及实测结果保留在下一节。候选发布不再进行本地生产构建或
|
||||
打包,主分支 CI 与六平台标签工作流仍为发布验收入口。最终提交、标签与公开
|
||||
产物状态须以远端实际结果核对,不以版本号变更视为已发布。
|
||||
|
||||
## 2026-09-15 会话列表与图片合并性能修复
|
||||
|
||||
本轮针对用户报告的 0.13.5 卡顿与高内存,不修改正在运行的正式应用或数据库。
|
||||
只读检查发现最近会话正文约 1.14 MiB,消息 metadata 约 119.3 MiB;`f8d0741`
|
||||
的图片合并还会重建无变化消息,使焦点刷新后再次保存历史。实现规则见
|
||||
[会话列表读取与前端保留](./execution-history-storage.md#会话列表读取与前端保留)。
|
||||
|
||||
已实现并验证:
|
||||
|
||||
- 图片合并的三个分支共用保留引用的函数。1,600 条无图片历史引用不变;App 使用
|
||||
JSON 往返形状刷新后,保存的消息数为 0,较新的图片操作、成果关联和流式正文仍保留。
|
||||
- 启动、焦点/队列刷新、活动跳转与自定义任务入口均使用摘要或按 ID 读取。
|
||||
搜索保留中文、Unicode 大小写和字面 `%_` 匹配,包含仍显示但已落到最近 100 条以外的
|
||||
会话;真实 SQLite 中 501 条历史仍可完整取回,header-only 保存不删除消息。
|
||||
- 打开、复制、导出、发送及压缩上下文保留完整历史语义。详情读取失败可原地重试;
|
||||
旧摘要不能覆盖晚到的后台审批或刚加载的历史。搜索只保存命中 ID,复制/导出不把
|
||||
未打开详情留入缓存,空闲详情在已确认保存后随现有 KeepAlive 生命周期释放。
|
||||
- 三路 simplify 审查覆盖全部本轮源码,修正了两处残留全量读取、搜索保留大对象、
|
||||
较早会话搜索遗漏及后台审批竞态。保留已发布完整列表接口,无 schema/Agent 协议迁移。
|
||||
- 定向 App、SQLite、IPC/恢复及图片合并测试:**443 通过**。最终 `npm test`:
|
||||
**4,301 通过、67 跳过、0 失败**,约 736.46 秒。`npm run typecheck` 和所有本轮
|
||||
源码文件的 ESLint 通过。全仓 `npm run lint` 仍被本轮范围外的
|
||||
`application-tool-navigation/navigation-demo.js:360` 的 `no-unused-expressions`
|
||||
阻塞,未修改该用户编辑;不能记为全仓 lint 通过。
|
||||
- 以 `f47049c40d757a38296d06d281b87fb6a5fe1c8c` 和本轮 14 个源码/测试文件冻结
|
||||
before/after,两个生产 Electron 构建均成功;验证后哈希核对无源码漂移。
|
||||
- 隔离生产 Electron 的 100 会话/1,600 消息试验:首次摘要读取 0 条消息,仅当前会话
|
||||
详情读取 16 条;搜索第 100 个会话的正文不预载其 metadata,点击后再读取完整 16 条。
|
||||
已检查实际 UI 截图,不是只调用数据库或 Mock。
|
||||
- 另一个隔离 App 正常 UI 打开未加载的旧会话并发送,Main 请求携带原有 2 条历史;
|
||||
**1 次真实直连文本调用**,HTTP 200,最终完整保存准确标记 `HISTORY_RENDERER_7391`。
|
||||
初始等待使用错误 DOM class 超时,随后以保存结果核对成功,未重复发送。
|
||||
- Linux x64 共享 Host 在本轮独立目录构建、安装并激活当前源码 Agent/OpenCode,
|
||||
真实 SQLite 摘要测试通过;**2 次真实无工具文本调用**均完成且 delivered=1,
|
||||
第二轮复用同一原生 Session 并准确记住首轮标记。测试 Agent 停止后独立 `/proc`
|
||||
检查无该目录所属进程。此检查验证当前源码部署与 Session 续接,不冒充远端 UI
|
||||
断线恢复全流程;桌面恢复问题投影由本轮真实 SQLite IPC 回归覆盖。
|
||||
|
||||
本轮真实模型调用共 **3 次文本、0 次图片**,无工具请求,不新增图片生成费用。
|
||||
准备阶段的错误 fixture provider/图片默认字段、独立 profile 缺少原安全存储的
|
||||
加密上下文、Host 源码包漏带 LICENSE 均只修验证脚本;失败记录保留,不计为产品修复。
|
||||
所有脚本与原始证据位于忽略目录
|
||||
`.agent-resources/conversation-performance-20260915/run-KX7bIz/`。
|
||||
|
||||
### 完整 App 配对内存测量
|
||||
|
||||
最终采用三组顺序执行的 before/after 冷启动,共六个独立 profile。每组使用相同
|
||||
100 会话/1,600 消息,正文 1.20 MiB、完整 reasoning metadata 119.69 MiB;
|
||||
执行三次焦点刷新、搜索未打开正文并导航 11 次。使用直连 Runtime 的无网络配置,
|
||||
不启动 OC/DSH、不调用模型;测试与构建已完成后再开始这组测量。
|
||||
|
||||
下表是各三次结果的中位数,内存为 Windows Private Commit,单位 MiB:
|
||||
|
||||
| 指标 | before | after |
|
||||
| --- | ---: | ---: |
|
||||
| 初次传入 Renderer 的完整消息数 | 1,600 | 16 |
|
||||
| 三次无变化刷新保存的消息数 | 4,800 | 0 |
|
||||
| Main 焦点列表读取耗时(ms,不是端到端帧延迟) | 207.28 | 8.05 |
|
||||
| 启动稳定点 App / Renderer | 803.42 / 284.77 | 682.36 / 155.73 |
|
||||
| 三次刷新后 App / Renderer | 1,640.43 / 703.91 | 661.34 / 154.16 |
|
||||
| 导航及缓存释放后 App / Renderer | 895.27 / 557.09 | 574.65 / 248.95 |
|
||||
| App 采样峰值 | 1,788.71 | 694.35 |
|
||||
|
||||
每次 10 个进程样本,定时器间隔 5 秒,另有阶段采样,不代表连续峰值。
|
||||
本数据集采样峰值中位数下降 **61.18%**;before 三次峰值分别为
|
||||
2,506.29 / 1,788.71 / 1,766.39,after 为 694.35 / 693.61 / 719.27。
|
||||
它测的是这两项历史读取/重复保存修复,不含重型 Runtime 的原生内存,不能推广为
|
||||
正式用户整个 App 必然下降相同比例,也不是长时间空闲泄漏证明。
|
||||
|
||||
六个最终 profile 都保留 1,600 条消息,按 ID/角色/状态/正文/完整 reasoning
|
||||
计算的 SHA-256 一致,`quick_check` 均通过;六个 App 均正常退出(code 0)。
|
||||
汇总见 `summary.json`,单轮为 `before-2` 至 `after-4` 的 report/acceptance。
|
||||
|
||||
保留但不纳入三组对照的早期 `before-1`/`after-1`:after 的驱动在清除搜索后未等待
|
||||
延迟筛选收敛,导航失败;退出时还记录 `PostQueuedCompletionStatus` 无效句柄。
|
||||
后续为两个版本都补上可见列表等待,并等本轮采样子进程完成后再正常退出;
|
||||
未唯一定位早期退出错误,也未据此修改产品代码。最终六轮完成后的监督脚本仍因
|
||||
向已退出 CDP 调用 browser close 返回错误;随后不附带 CDP 独立关闭命名浏览器成功。
|
||||
OS 按本轮目录核对无遗留 Electron/Node 进程,原始失败日志未覆盖。
|
||||
|
||||
文档检查覆盖本轮六份文档、81 个相对文件链接,无缺失;未逐一校验所有历史锚点。
|
||||
修改未提交、未推送、未发布。
|
||||
|
||||
## 2026-09-15 Runtime 执行清单设计(未实现)
|
||||
|
||||
对应 FR-CL1–FR-CL4。用户本轮明确只完善设计与进展文档,不改代码;
|
||||
@@ -961,13 +1589,38 @@ src/agent-daemon/runtime-composition.test.ts src/main/ipc.test.ts` 为 179 项
|
||||
测试应用与具名浏览器会话已退出,所属 Electron/Node 进程及 fixture 监听端口均为 0;
|
||||
临时截图和隔离 profile 保留在本机测试目录,不进入仓库。
|
||||
|
||||
## 2026-09-16 任务卡内审批
|
||||
|
||||
- 任务中心按 [Task Center 交互规则](../task-and-job/task-center-prd.md#5-交互) 展示卡内审批,
|
||||
未关联审批保留处理入口;没有审批时不渲染空审批区域。
|
||||
- 当前定时运行从队列 `taskId` 记录到 `ActiveRun`,审批事件再写入消息的临时审批数据;
|
||||
历史消息使用明确的 `message.task.id`。Sidebar 按任务 ID 匹配当前范围内顶层任务,
|
||||
并核对会话 ID 和项目 ID,不按会话、标题、时间或计划猜测运行归属。
|
||||
- `RightAssistantSidebar.resize.test.tsx` 的 54 项测试通过,覆盖项目范围、状态筛选、
|
||||
卡内处理、无审批、普通会话审批及关联不一致。App 使用 `-t "approval|scheduled instructions"`
|
||||
定向运行的 14 项测试通过,验证历史与当前定时审批的映射、允许/拒绝接口调用及成功移除。
|
||||
- `npm run typecheck`、`npm run lint` 通过。本次仅修改 Renderer 展示与临时关联数据,
|
||||
本机和远程审批继续使用原有接口;未改 Main、Agent 或存储协议。未进行真实 Electron
|
||||
窗口和模型调用验收;按本次要求未运行全量测试及无关数据库迁移测试。
|
||||
|
||||
## 2026-09-16 任务中心范围简化
|
||||
|
||||
- 任务中心仅保留“当前项目”和“所有项目”;无项目任务在后者中标注“未绑定项目”。具体规则见
|
||||
[Task Center 范围](../task-and-job/task-center-prd.md#4-范围)。
|
||||
- Renderer 将旧布局中的 `global` 恢复为 `all-projects` 并保存新值;共享解析契约、后端与任务
|
||||
数据不变。App 仍只向任务中心传入 schedule 来源的顶层任务。
|
||||
- `npx vitest run src/renderer/src/RightAssistantSidebar.resize.test.tsx src/shared/workbar-contracts.test.ts`
|
||||
通过 60 项;`npm run typecheck` 和 `npm run lint` 通过。未进行真实 Electron 窗口验收。
|
||||
- `npm test` 在 200 秒后超时,未取得整库结果;单独复跑 `heartbeat-database.test.ts` 为 6 项通过、
|
||||
2 项失败,均在未修改的数据库迁移代码中报 `duplicate column name: pinned`。
|
||||
|
||||
## 2026-09-10 固定基础页签与浏览器多实例
|
||||
|
||||
- 工作栏注册表已分别声明实例策略、默认上下文、默认打开、必须存在、可关闭和排序属性。
|
||||
任务中心与工作区保持默认存在且不可关闭;浏览器改为当前 Conversation 上下文的多实例。
|
||||
布局加载按 `required` 补回缺失的基础页签,并修复失效活动实例。
|
||||
- 任务中心增加“当前项目”“全局任务”“全部项目”范围,默认跟随活动项目;任务和审批按
|
||||
Conversation 所属项目过滤,范围模式随工作栏布局持久化。
|
||||
Conversation 所属项目过滤,范围模式随工作栏布局持久化。范围选项已于 2026-09-16 简化,见上方记录。
|
||||
- 浏览器服务已拆分 Conversation Context 与 Browser Tab。每个 Tab 拥有独立页面、Driver、
|
||||
操作队列、状态和元素引用空间,同一 Conversation 的 Tab 共享 partition、代理和 Cookie。
|
||||
- Renderer 使用工作栏实例 UUID 原子创建或恢复 Browser Tab,并以 UUID viewport token 防止
|
||||
|
||||
@@ -1,16 +1,38 @@
|
||||
# Runtime 执行清单技术设计
|
||||
|
||||
状态:设计阶段,尚未实现。需求、场景与顶部布局由 [PRD](./prd.md#131-runtime-执行清单)
|
||||
状态(2026-09-19):本地与远程 OC/CN 提取、统一事件、消息保存和顶部展示已实现。
|
||||
默认开发组包同时携带 OC/CN,远程 CN 已通过 Windows 桌面到 Linux x64 的真实模型
|
||||
更新、取消、重启恢复及续发验证。验证范围与调用数见
|
||||
[远程记录](../remote-host/runtime-checklist-validation.md#2026-09-19-联合包与完整桌面验收)。
|
||||
需求、场景与顶部布局由 [PRD](./prd.md#131-runtime-执行清单)
|
||||
维护,替换、隔离和终态规则由 [Runtime 交互边界](./runtime-interactions.md#runtime-执行清单)
|
||||
维护。研究证据与交付状态见[进度](./progress.md#2026-09-15-runtime-执行清单设计未实现)。
|
||||
|
||||
## 1. 原生数据与提取位置
|
||||
|
||||
Implemented local behavior:
|
||||
|
||||
- OC sends a native-sortable `messageID` with both prompt and command submissions.
|
||||
Only assistant messages whose `parentID` matches that request can own checklist
|
||||
calls. Parent Session identity is checked independently of child-session routing.
|
||||
OC 1.18.29 emits `pending`, `todo.updated`, then `running` with complete arguments.
|
||||
The adapter retains native snapshots only while an owned call is active and
|
||||
submits a snapshot once its validated items match that call's original input.
|
||||
Calls and pending snapshots are request-local; no `session.todo` polling is used.
|
||||
- CN extracts the exact `Checklist.checklist` argument in the patched native start
|
||||
callback and in native history before display truncation. Candidates survive
|
||||
success frames without input. A completed call commits once per call ID; failed
|
||||
calls and startup history do not commit. Ordinary tool events retain their limits.
|
||||
- CN accepts blank lines and `- [ ]`, `- [x]`, `- [X]` entries with nonempty content.
|
||||
Empty or whitespace-only strings clear the list. Other nonempty lines invalidate
|
||||
the entire update. Native 1.5.47 accepts a string without Markdown validation;
|
||||
these are GoodBuddy's explicit projection rules, not additional native guarantees.
|
||||
|
||||
| Runtime / 路径 | 已确认格式与设计接入 |
|
||||
| --- | --- |
|
||||
| 本地 OC 1.18.29 | `todo.updated` 与 `session.todo` 返回完整条目列表,字段为 `content/status/priority`,无 item ID;以原生更新事件驱动本轮整份替换 |
|
||||
| 本地 CN 1.5.47 | 精确原生工具 `Checklist` 的参数 `checklist` 是 Markdown 勾选列表,`- [ ]` / `- [x]` 表示未完成/完成;按一次成功调用提交完整替换 |
|
||||
| 远程 OC/CN | 先核对实际部署版本、原生输出与 Agent 传递结果;只有格式已确认且来源归属明确的原生 plan entries 或精确清单工具参数可以映射,当前尚无本功能远程实测 |
|
||||
| 远程 OC/CN | OC 1.18.29 通过原生 todo.updated,CN 1.5.47 通过成功 Checklist 参数进入 Agent transcript;Linux x64 清单与重放已实测,CN 同时验证取消后续发,见[远程记录](../remote-host/runtime-checklist-validation.md) |
|
||||
|
||||
`opencode-runtime.ts` 当前将普通工具 input 经 `boundedToolDetail(..., 4_000)` 转成
|
||||
展示字符串。接入应在 SDK 原始事件层消费 `todo.updated`;如需核对 `todowrite` 参数,
|
||||
@@ -24,23 +46,29 @@ CN 的提取位置在 `continue-host-adapter.ts`:宿主产生工具事件、HT
|
||||
流转。超出 4,000 字符的清单需要独立验证完整性,但不扩大所有普通工具输入的展示上限。
|
||||
|
||||
Markdown 解析只适用于已确认的 CN `Checklist.checklist`,不扫描助手正文或其他工具
|
||||
输出。空数组在统一事件中明确表示清除;CN 原生如何表达空清单,以及空白、大小写和
|
||||
非条目行的实际接受规则,实施时核对后固定解析规则。未确认或无效输入保留旧清单,
|
||||
不能误当清空。
|
||||
输出。空数组在统一事件中明确表示清除;CN 空字符串或仅空白的字符串表示清空。
|
||||
解析规则见上文;无效输入保留旧清单,不能误当清空。
|
||||
|
||||
## 2. 统一事件与消息归属
|
||||
|
||||
拟在现有 Runtime 事件联合中增加完整清单事件,承载 `requestId`、条目数组和原生来源;
|
||||
条目包含 `content`、`status` 及可选 `priority`。最终命名随实现确定,不设计条目级增删
|
||||
协议或稳定 item ID。空数组必须经过 schema、IPC、消息投影和保存链路,不能被“无内容”
|
||||
过滤丢弃。
|
||||
The shared contract is `src/shared/runtime-checklist.ts`: `runtimeChecklistSchema`
|
||||
and `RuntimeChecklist { source: 'opencode' | 'continue', items }`. Each item has
|
||||
`content`, `status: pending | in_progress | completed | cancelled`, and optional
|
||||
`priority: low | medium | high`. `AgentEvent` carries
|
||||
`{ type: 'checklist', requestId, checklist }`; conversation messages carry optional
|
||||
`runtimeChecklist`. Empty arrays are valid replacements. Existing IPC event
|
||||
transport forwards them without a separate channel or tool-input parsing.
|
||||
|
||||
Main 使用现有请求到助手消息的映射投影清单,Renderer 顶部读取该消息的最新有效状态。
|
||||
本地 Renderer 按请求映射更新助手消息并沿既有保存入口落库;远程 Main 使用既有事务
|
||||
投影更新消息。顶部读取所属请求消息的最新有效状态。
|
||||
同一请求若同时观察到原生清单事件与其工具记录,只以前者更新清单,后者保留活动;不能
|
||||
提交两次替换。原生父 Session、请求开始/结束边界需共同校验,子 Session 或旧请求事件
|
||||
不得通过当前活动请求映射到新轮。
|
||||
|
||||
优先扩展现有消息 metadata、Runtime 事件和数据库投影,历史清单与所属助手消息一起保存。
|
||||
The existing message metadata serializer and every database message reconstruction
|
||||
retain `runtimeChecklist`, including remote event projection and terminalization.
|
||||
No SQLite schema migration or separate checklist store is needed.
|
||||
历史清单与所属助手消息一起保存。
|
||||
远端沿用事件提交、消息投影和 ACK 的既有事务及去重规则;相关存储边界见
|
||||
[执行记录存储](./execution-history-storage.md)。实施前核对本地会话与远程任务实际落库
|
||||
字段,选择能复用的最小表示。不新建清单数据库、独立快照系统、恢复日志或持久化服务。
|
||||
@@ -48,16 +76,16 @@ Main 使用现有请求到助手消息的映射投影清单,Renderer 顶部读
|
||||
|
||||
## 3. 远程 Agent 与 ACP 复用
|
||||
|
||||
本次源码检查确认以下传递位置,属于设计依据,不是本功能远程验证结果:
|
||||
远程传递复用以下实现:
|
||||
|
||||
1. `AgentAcpConnection` 按原生 `sessionId` 分派 `sessionUpdate`;
|
||||
`AgentOwnedAcpPrompt` 校验 Session 后,以 `bindingId/operationId` 将通知写入
|
||||
既有 semantic transcript。
|
||||
2. `ProtocolRemoteRuntimeChannel` 已有 transcript 读取/ACK、身份核对,以及 ACP
|
||||
channel 恢复与重放;`AcpRemoteRuntime` 消费 `session-update` 并转换桌面事件。
|
||||
3. `AcpRemoteRuntime` 当前将 `plan.entries` 及 `plan_update` 的 items 转成 `status`
|
||||
字符串;markdown/URI 也只形成状态消息。工具事件按 `toolCallId` 合并 `rawInput`、
|
||||
`status` 和结果,再有界保留。清单提取应发生在结构丢失或截断前。
|
||||
3. OC 插件转发原生 `todo.updated`,CN 宿主将成功清单写入 `_meta.goodbuddyChecklist`;
|
||||
`AcpRemoteRuntime` 校验归属和结构后产生统一清单事件。标准 OC `plan.entries` 也有
|
||||
映射,但 OC 1.18.29 实测走插件事件。Markdown、URI 和普通状态文字不生成清单。
|
||||
|
||||
优先复用上述通知和 transcript,不另建远端清单通道。对实际 OC/CN 部署确认过的
|
||||
`plan.entries`,或已确认等价的 `plan_update` items,校验条目字段和状态后转成统一
|
||||
@@ -69,17 +97,27 @@ Main 使用现有请求到助手消息的映射投影清单,Renderer 顶部读
|
||||
真实格式证据,不能根据 ACP 类型定义推断 OC/CN 一定会发送。ACP 是协议,Runtime 身份
|
||||
仍为 OC 或 CN。
|
||||
|
||||
当前托管 SSH 产品及受管安装、校验、创建链路只支持 OC,详见
|
||||
[远程范围](../remote-host/technical-design.md#执行清单接入范围)。远程 CN 交付需补齐
|
||||
受管工件、安装与启动、项目选择/校验及原生事件接入,确认 Main 模型桥、请求归属、取消
|
||||
和已有恢复流程能用于该部署。不能只放开选择菜单或使用本地 CN 调远程模型来宣称完成。
|
||||
受管安装、校验、项目创建与聊天选择已接入 CN;Agent 在 Host 运行固定 CN HTTP 宿主,
|
||||
复用本地适配器和已有模型桥、请求归属、取消及恢复。开发包与 Linux 验证、联合分发和
|
||||
完整桌面验收的剩余范围见[远程记录](../remote-host/runtime-checklist-validation.md)。
|
||||
|
||||
重连按[既有 ACP 与断线规则](../remote-host/technical-design.md#acp-与断线)执行。
|
||||
已接受 Prompt 使用原 binding/operation 恢复,清单从已提交消息和其后的既有事件恢复;
|
||||
重复事件不重复展示。连接丢失不表示条目完成,不为恢复清单重新发送 Prompt、模型请求或
|
||||
工具调用,也不新增独立重连机制。
|
||||
|
||||
## 4. 实施与验收
|
||||
## 4. 顶部布局
|
||||
|
||||
`ConversationTaskStrip` 与 `RuntimeChecklistStrip` 共用顶部容器,任务在上、清单在下,
|
||||
各自展开和折叠。没有 Task 时清单仍可显示;远程会话也保留清单入口。总高度不超过
|
||||
45vh,Task 展开内容不超过 20vh 并内部滚动,标题不收缩。清单详情复用 `AnchoredMenu`
|
||||
的 `group` 语义和 `FloatingPortal`,宽度跟随摘要按钮并受视口约束,高度按可用空间限制,
|
||||
内部可用键盘滚动。展开不改变顶部容器或消息流的高度;点击外部、焦点移出或 Escape 收起。
|
||||
|
||||
状态文字与条目首行按文字基线对齐,状态图标单独居中。CN 不推断进行中或优先级。
|
||||
新轮先选择本轮消息,再读取清单,不回溯寻找旧轮非空清单。后台更新保持展开状态和焦点。
|
||||
|
||||
## 5. 实施与验收
|
||||
|
||||
| 阶段 | 必须完成的工作 | 对应需求 |
|
||||
| --- | --- | --- |
|
||||
@@ -88,7 +126,7 @@ Main 使用现有请求到助手消息的映射投影清单,Renderer 顶部读
|
||||
| CL-P3 | 确认远程 OC 原生格式,补齐远程 CN 真实部署与事件接入,复用 Agent 传递和恢复 | FR-CL1、FR-CL4 |
|
||||
| CL-P4 | 本地与共享 Linux 真实 Host 逐项验收,记录版本、结果、调用口径与剩余阻塞 | FR-CL1–FR-CL4 |
|
||||
|
||||
以下均为待执行验收,本次文档工作不运行模型或代码测试:
|
||||
以下为产品验收范围;已执行项和未覆盖项分别记录在进度与远程验证记录中:
|
||||
|
||||
- 本地 OC/CN 各经桌面正常发送路径产生并连续修改清单,检查新增、修改、重排、删除、
|
||||
重复文本、有效清空及超过 4,000 字符的完整内容;CN 成功才替换,失败保留前值。
|
||||
|
||||
@@ -29,6 +29,10 @@ Host 环境。2026-09-14 的已发布 Desktop `0.13.2` / Agent `0.11.25` 已包
|
||||
- 回答或跳过成功后,会话保留只读的“问题与回答”,显示原问题及对应的选项答案、
|
||||
自定义文本或“已跳过”。同一任务的多轮问答逐次保留,随本地会话保存,重新打开后仍可查看。
|
||||
提交失败时保留输入与重试入口,不生成已回答记录;取消未回答的问题不记为主动跳过。
|
||||
- 新消息在提问到达时,将只含 `questionId` 的问题位置块插入当时的消息块序列;
|
||||
待答和回答后的卡片使用同一位置,后续正文、工具输出不再排到问题前面。重复问题不追加
|
||||
或移动位置块。位置块不保存待答表单;旧会话没有位置块时保留原有问答展示,不推测或
|
||||
回填历史顺序。该变更复用既有消息元数据,不升级数据库 schema。
|
||||
- 提交响应晚于任务结束时仍保存已提交的问答,但不覆盖完成或失败状态;晚于下一轮提问时
|
||||
不清除新的待答卡片。旧版会话未保存的问题与答案无法从“已提交”状态文案恢复。
|
||||
- 待答队列仅保存在当前桌面会话内存中,不写入持久会话快照。刷新仍在运行的消息时保留整个
|
||||
@@ -38,6 +42,8 @@ Host 环境。2026-09-14 的已发布 Desktop `0.13.2` / Agent `0.11.25` 已包
|
||||
- 未发布的进程复用源码在 Desktop 重启后由 Main 并行附加同项目的存活任务,将待答
|
||||
列表和 Stop 所需的请求 ID 投影到实时 `activeRequest`,不保存该字段或重新发送任务。
|
||||
回答/跳过成功由 Main 写回原远程助理消息的既有问答历史,Renderer 不覆盖远程正文或终态。
|
||||
Main 在前台与恢复请求收到实时问题时立即保存位置块;实时待答快照没有语义事件来源,
|
||||
不伪造来源或追加 transcript。带来源的提问事件仍通过既有事务投影并按问题 ID 去重。
|
||||
Runtime 设置、定制和能力更新让旧请求在原配置上结束,已有问题仍可回答;新请求使用
|
||||
新配置。专家团队例外:既有专家调度器在设置替换时取消子任务,团队父请求也随之停止,
|
||||
不使用替换后的模型汇总旧结果。详细实现见
|
||||
@@ -92,6 +98,18 @@ DeepSeek Harness 按调用 ID 保留名称、输入和摘要,结束事件省
|
||||
正文、推理或工具进展会清除旧状态;子任务更新不覆盖父请求的重试提示。
|
||||
- 完成、失败、取消后保留真实终态,迟到的状态事件不能把请求恢复成处理中。底部状态点
|
||||
为静态,不凭请求尚未结束暗示仍有进展;会话列表闪点和发送、停止按钮保持原有行为。
|
||||
持久化快照中的同一条消息仍为生成中时,也不能覆盖本地已收到的终态,即使快照的
|
||||
会话更新时间更晚;否则连续发送或刷新会让已结束的请求重新显示停止按钮。
|
||||
- 失败和取消的终止提示只进入消息状态,不补写正文或文本块,已有回答、推理与工具输出
|
||||
保留。取消使用中性色和礼貌级 `status`,真实失败使用危险色和 `alert`;运行记录的
|
||||
取消结果标题为“任务已取消”。工具详情已表达同一失败时,不再复制到消息底部。
|
||||
- 最新失败或取消消息只有在上一条用户消息含可回填文字时显示“重新编辑”。点击只回填
|
||||
输入框并聚焦,由用户修改后自行发送;附件单独发送且无文字时不显示此按钮。
|
||||
- 实时事件和 Main 历史恢复均将事件的 `failed / cancelled` 写入消息元数据
|
||||
`terminalStatus`;既有 `state: error` 继续表示未成功终态,不改 SQLite 枚举。
|
||||
新事件不按提示文案识别取消。旧快照无此字段时,仅兼容已发布的精确取消标签。
|
||||
旧记录若正文与状态完全相同,且无文本块或唯一文本块位于末尾并等于该状态,展示时
|
||||
保留正文、隐藏重复状态;不修改持久数据,不删除正文中的同名句子或部分匹配内容。
|
||||
- Continue、DeepSeek Harness 和远端 ACP 保留其已接入的处理与工具事件,不伪造未接入
|
||||
的原生重试状态。直连模型的重试等待与请求开始文案见
|
||||
[直连模型 UI 设计](../direct-model-agent/ui-design.md#6-错误与通知);本次不改变任何
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
# Runtime 原生客户端启动方案
|
||||
|
||||
日期:2026-09-26
|
||||
Status: Local shortcut/coordinator paths and configured-model responses verified on Windows; remote integration and packaged Node delivery remain open.
|
||||
范围:Continue、OpenCode 的交互终端,以及 DeepSeek Harness(DS)的官方 Web 客户端。
|
||||
|
||||
本文集中维护该新增入口的需求、场景、行为规则、接入设计和验收计划。既有终端行为由
|
||||
[多终端页签 PRD](./terminal-tabs-prd.md)维护,既有 Runtime 权限和资源边界由
|
||||
[Runtime 交互边界](./runtime-interactions.md)维护;本文描述新增客户端路径,不宣称它已经具备后台 Runtime 的全部能力。
|
||||
|
||||
## 1. 方案选择
|
||||
|
||||
在聊天输入区“压缩上下文”左侧增加原生客户端入口。Continue 和 OpenCode 打开 GoodBuddy
|
||||
工作栏中的交互终端;DS 启动官方 Web 服务并在系统默认浏览器中打开。使用当前项目目录和
|
||||
当前模型,默认建立独立客户端会话,不自动复制 GoodBuddy 聊天历史。
|
||||
|
||||
| 当前 Runtime | 按钮文字 | 打开目标 | 本轮计划范围 |
|
||||
| --- | --- | --- | --- |
|
||||
| Continue | 在终端中打开 | 工作栏新终端页签,启动原生 CLI | 本地和托管 SSH 项目 |
|
||||
| OpenCode | 在终端中打开 | 工作栏新终端页签,启动原生 TUI | 本地和托管 SSH 项目 |
|
||||
| DeepSeek Harness | 在浏览器中打开 | 系统默认浏览器,访问官方 Web 应用 | 本地项目 |
|
||||
| 内置模型 | 不显示此快捷入口 | 无 | 保持现状 |
|
||||
|
||||
DS 采用官方 `@deepseek-ai/dsh` Web 应用,不开发新的 DS 终端前端,也不在首轮嵌入
|
||||
GoodBuddy 浏览器工作栏。远程 DS 不在本轮范围:当前共享远程启动契约只接受
|
||||
`opencode` 和 `continue`,不能通过快捷按钮绕过该范围。
|
||||
|
||||
## 2. 调研依据与证据范围
|
||||
|
||||
下表保留早期测试报告,作为技术选型线索。2026-09-26 新增的 DS 0.1.7-rc.2
|
||||
实测单列在表后;Continue、OpenCode 和远程交互终端本轮未复测。
|
||||
|
||||
| 历史报告项目 | 报告结果 | 尚需验证 |
|
||||
| --- | --- | --- |
|
||||
| Continue 1.5.47 | 标准 Node 与真实 PTY 可显示交互输入、配置和模型名称;直接用 Electron 启动失败 | 启动路径错误、当前模型注入、真实请求和权限 |
|
||||
| OpenCode 1.18.29 | 随包二进制可显示 TUI,响应终端尺寸变化 | 排除已有用户配置干扰,验证指定模型实际生效 |
|
||||
| DS Web 0.1.5-rc.3 | Node 24.19.0 启动;Edge 可访问页面及模型设置 | 项目自动选中、模型桥、真实对话、工作模式 |
|
||||
| DS Windows 依赖 | 首次缺少 Sharp 原生包,补装后可启动 | 正式资源中预先带齐各目标平台依赖 |
|
||||
| DS 工作目录 | 指定 cwd 后仍要求选择工作区 | 工作区创建及页面选中的完整路径 |
|
||||
| 终端相关回归 | 报告为 5 个文件、29 项通过 | 新入口尚无回归或端到端验收 |
|
||||
| 远程 Host | 两条网络路线 SSH 端口可达 | 登录后的当前源码、Runtime、模型桥真实验证 |
|
||||
|
||||
早期文档整理和依赖升级报告的真实模型调用为 0 次。本轮核查另发现临时目录
|
||||
`dsh-017-live` 的 7 次历史 dispatch 记录;本轮在独立 `dsh-017-verify` 目录新增
|
||||
9 次,两个计数分别保留。测试过程和结果以 [DS 实现进度](../deepseek-harness/progress.md#2026-09-26-真实模型与官方-web-验证)为准。
|
||||
|
||||
本轮直接检查了以下仓库材料:
|
||||
|
||||
- `src/main/terminal/terminal-session-manager.ts`:已有本地与 SSH 会话创建、窗口归属、事件转发和关闭管理,当前创建路径启动普通终端。
|
||||
- `src/shared/remote-runtime-launch-contracts.ts`:远程 Runtime 枚举包含 Continue 和 OpenCode。
|
||||
- `UI-DESIGN.md`:规定 Composer 元信息区、工作栏、通知、键盘及响应式规则。其聊天章节仍写远程只支持 OpenCode,与当前共享契约不一致;实施时需沿 UI 到执行路径核实并同步修正,不能单凭枚举认定远程交互已可用。
|
||||
|
||||
上游参考:[官方仓库](https://github.com/deepseek-ai/deepseek-harness)、
|
||||
[CLI 文档](https://github.com/deepseek-ai/deepseek-harness/blob/master/apps/cli/README.md)、
|
||||
[使用指南](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/user/guide/index.md)。
|
||||
这些链接随上游变化;实施时须为选定包版本补充对应源码 revision、启动参数和接口依据。
|
||||
历史测试版本是候选版本,不作为未经复测的最终发布版本。
|
||||
|
||||
2026-09-26 仓库内后台 DS Runtime 已升级至 `0.1.7-rc.2`,Cordis 升级至
|
||||
`4.0.4`,验证记录见 [DS 实现进度](../deepseek-harness/progress.md)。此版本来自
|
||||
npm `next` 标签;查询时 `latest` 仍为较旧的 `0.1.0-rc.6`。本轮另用官方
|
||||
`@deepseek-ai/dsh@0.1.7-rc.2` 验证 Web。Prepared Web resources and backend validation are recorded in section 9.1.
|
||||
|
||||
### 2.1 DS 0.1.7-rc.2 实测结论
|
||||
|
||||
Windows x64、标准 Node 24.19.0、Edge 154.0.4258.37 下,官方 Web 可承接当前
|
||||
工作区和已配置模型,完成页面真实对话。Main 使用 safeStorage 解密隔离副本中的模型
|
||||
配置,经现有 managed model bridge、AgentModelGateway 和 loopback proxy 转发。
|
||||
浏览器与 DS 只取得本地桥接信息,原始 `runtime-settings.json` 测前测后逐字节一致。
|
||||
|
||||
| 实测路径 | 结果 | 供应商 HTTP dispatch |
|
||||
| --- | --- | ---: |
|
||||
| 后台 Ask → `read` | 返回文件中未写入 prompt 的随机校验码,收到工具完成及用量事件 | 2 |
|
||||
| 同一后台会话切 Execute → `write` | 创建测试文件,内容校验通过 | 2 |
|
||||
| Execute → `pwsh` | PowerShell 读回该文件,结果正确 | 2 |
|
||||
| 官方 Web → 当前模型 → 文件读取 → 回答 | 页面返回另一随机校验码,刷新后保留结果 | 3 |
|
||||
| 合计 | 请求中的模型均为 `deepseek-flash`,HTTP 均为 200 | 9 |
|
||||
|
||||
`0.1.7-rc.2` 是 Harness/Web 包版本;`deepseek-flash` 是实际配置和请求中的模型 ID。
|
||||
Web 的 3 次请求包含 1 次未携带工具的请求,计数没有只按用户提交次数计算。
|
||||
后台使用当前源码的真实 ACP、Host、控制面和供应商流式响应,但 Host 在测试进程内组合;
|
||||
该结果不等于重新验收 GoodBuddy 完整 UI 或 Utility Process。Web 使用真实 CLI 子进程和
|
||||
浏览器自动化,未通过尚未实现的快捷按钮。测试继承了环境中的
|
||||
`NODE_TLS_REJECT_UNAUTHORIZED=0`,因此没有验证严格 TLS 证书校验。
|
||||
|
||||
工作区和模型自动准备已验证;官方 Web 到 GoodBuddy Ask/Execute 的工具执行适配、
|
||||
Skills/MCP 承接、取消和产品生命周期仍待实现与验证。此次可行性验证不要求完成按钮。
|
||||
|
||||
## 3. 产品需求
|
||||
|
||||
| ID | 要求 | 用户可见的验收结果 |
|
||||
| --- | --- | --- |
|
||||
| FR-1 | 在压缩按钮左侧提供 Runtime 对应入口 | 无历史、无压缩能力时仍能打开客户端 |
|
||||
| FR-2 | 按项目执行空间启动 | 本地使用本地目录,SSH 使用绑定 Host 与远端目录;目录失效时说明原因 |
|
||||
| FR-3 | 注入点击时选定模型 | 客户端首轮直接使用该模型,无需重复输入供应商密钥 |
|
||||
| FR-4 | 创建独立客户端会话 | 不修改原聊天历史,不把打开操作伪装成当前聊天的续接 |
|
||||
| FR-5 | 保持所选工作模式的执行边界 | Ask 的 AI 工具路径只读;Execute 使用相应账号已有权限 |
|
||||
| FR-6 | 管理启动、重开和关闭 | 不因收起工作栏或切换项目中断;关闭对应客户端资源时释放其桥接 |
|
||||
| FR-7 | 保留可处理的失败信息 | 启动、模型和远程错误可区分,允许修正后重试,不清空聊天草稿 |
|
||||
| FR-8 | 明确原生能力与 GoodBuddy 能力差异 | 已分配的 Skills、MCP 等逐项接入并验证;未接入项有明确说明 |
|
||||
| FR-9 | 交付可直接运行的客户端资源 | 用户无需自行运行 npx、补装 Sharp 或查找 Node |
|
||||
|
||||
聊天附件、知识范围、专家提示及历史不自动移入独立客户端。首轮不提供聊天双向同步、
|
||||
Task 关联恢复或客户端历史导入。原生客户端产生的历史由对应客户端保存,不能在 GoodBuddy
|
||||
活动记录中显示为已经同步的聊天内容。
|
||||
|
||||
FR-8 的实施清单须区分原生工具、GoodBuddy 分配的 Skills、自定义 MCP、内置 MCP、知识工具
|
||||
以及遵循当前工作模式授权的配置工具。配置应用不再额外要求 GoodBuddy 原生确认,Ask 仍只读。
|
||||
支持情况以真实调用为准;现有 DS 宿主插件能力不能
|
||||
直接算作 DS Web 能力。发现现有分配无法承接时,在实施评审中明确差异和处理方式,不能默默忽略。
|
||||
|
||||
## 4. 场景与状态规则
|
||||
|
||||
### 4.1 用户场景
|
||||
|
||||
| ID | 前提与动作 | 预期结果 | 需求 |
|
||||
| --- | --- | --- | --- |
|
||||
| US-1 | 本地项目选中 Continue 或 OpenCode,点击入口 | 展开工作栏,新建带 Runtime 与项目名称的终端,进入可输入的原生界面 | FR-1~4 |
|
||||
| US-2 | 本地项目选中 DS,首次点击入口 | 服务就绪后打开浏览器,当前项目已选中、模型可直接使用 | FR-2~4、9 |
|
||||
| US-3 | 在支持的 SSH 项目点击终端入口 | 使用已保存 Host 连接,在远端项目目录启动对应 Runtime | FR-2、3 |
|
||||
| US-4 | 启动期间连续点击 | 同一启动只创建一次;按钮显示启动中 | FR-6 |
|
||||
| US-5 | 已打开客户端后切换项目或模式 | 旧客户端保持原项目和启动模式;新操作使用新选择 | FR-5、6 |
|
||||
| US-6 | 启动失败或模型请求失败 | 保留原因与可操作的重试方式;聊天内容和草稿不变 | FR-7 |
|
||||
| US-7 | Ask 下从原生界面请求写文件、执行写命令或使用写入 MCP | AI 执行边界拒绝写入,不能通过客户端模式开关绕过 | FR-5、8 |
|
||||
| US-8 | 关闭终端、停止 DS 服务或退出 GoodBuddy | 对应进程和桥接释放,其他会话不受影响;原生历史保留 | FR-4、6 |
|
||||
|
||||
### 4.2 行为决策
|
||||
|
||||
启动参数按点击时的项目、执行空间、Runtime、模型和工作模式确定。界面后续切换不修改已运行
|
||||
客户端。这里只需保留进程运行所需的内存记录,不增加持久启动快照、恢复日志或数据库迁移。
|
||||
|
||||
Continue 和 OpenCode 启动成功后,每次新的点击创建独立终端。启动中的重复点击合并到同一
|
||||
进行中操作。终端会话数量沿用现有管理器规则,不新增一套数量控制。
|
||||
|
||||
DS 按项目、模型配置及工作模式匹配运行服务。配置相同且服务可用时重新打开原服务;配置变更后
|
||||
新建匹配实例,保留原实例直到用户停止或应用退出,避免在已有页面中静默改变模型和权限。
|
||||
同一匹配项并发启动只执行一次。服务已退出则清除内存记录,下一次点击重新启动。
|
||||
|
||||
| 事件 | 处理 | 关联场景 |
|
||||
| --- | --- | --- |
|
||||
| 启动中切换项目 | 操作仍属于原项目;完成时不抢占新项目的工作栏焦点 | US-4、5 |
|
||||
| 收起工作栏、切换页面 | 保留终端与其模型桥 | US-5 |
|
||||
| 关闭终端页签 | 停止该 PTY/远端进程,释放相应配置和桥接 | US-8 |
|
||||
| 关闭 DS 浏览器标签 | 不推断服务已停止;再次点击可重开 | US-2、8 |
|
||||
| 用户停止 DS 服务 | 停止该服务和桥接;浏览器页面失去连接;保留原生历史 | US-8 |
|
||||
| GoodBuddy 退出 | 清理自己创建的进程与桥接,不停止用户其他进程 | US-8 |
|
||||
| SSH 断开 | 显示断开;收束对应请求,重试时重建连接,不宣称原会话已恢复 | US-3、6 |
|
||||
| 系统浏览器打开失败 | 保留可重开的就绪服务,通知用户重试;不复制含令牌地址到普通日志 | US-6 |
|
||||
| 服务启动失败 | 清理本次创建的临时资源,再允许重试;不删除用户原生历史 | US-6 |
|
||||
|
||||
Ask 沿用现有 Runtime 按工具命令名判断的只读规则,不改变用户手动终端的已有权限。
|
||||
DS 后台的 `read` 和原生 `skill` 可在 Ask 执行;`write`、`edit`、`pwsh`/`bash`
|
||||
在 Ask 拒绝,在 Execute 放行,代理工具沿用现有分类。工具出现在模型请求目录中不等于
|
||||
获准执行,判断仍在 `tools/execute` 路径。Web 接入复用这些规则,不增加 Shell 文本分析、
|
||||
目录围栏、逐工具二次确认或新的权限等级。Execute 使用当前账号已有的文件、进程和网络权限。
|
||||
|
||||
官方 `dsh-plan-mode@0.1.7-rc.2` 的 Plan 只改变规划提示和状态,工具目录不随之变化;
|
||||
权限预设由另外的 sandbox/approval 组件处理。因此不能把 Plan 名称当成 Ask 实现,
|
||||
也不能把官方默认“工作区内修改”当成 GoodBuddy Execute。接入时将启动模式接到现有
|
||||
工具名规则,原生页面的模式选择保持同一语义。
|
||||
|
||||
这些规则覆盖普通启动、使用、关闭和已知失败。尚需技术验证的项目见第 7 节;它们不构成新增
|
||||
授权层级或通用恢复框架的理由。
|
||||
|
||||
## 5. 界面设计
|
||||
|
||||
输入区下方从左到右排列“在终端中打开/在浏览器中打开”“压缩上下文”和现有用量信息。
|
||||
两个按钮独立判断可用性。采用同一轻量操作组,避免两组绝对定位相互覆盖;窄输入区允许
|
||||
操作组换行,保留模式说明、用量信息和对称边距。稳定预留元信息高度的实现应与
|
||||
[统一 UI 规范](../../../UI-DESIGN.md)一起更新并验证。
|
||||
|
||||
按钮使用原生 `button`、共享主题令牌和焦点环,支持 Tab、Enter 与 Space。打开终端后焦点
|
||||
进入新终端;外部浏览器由系统处理焦点。启动期间保留按钮宽度并显示“正在打开…”,完成后
|
||||
恢复原文案。缺少项目目录时在相关位置提供选择目录的操作;其他异步失败进入全局通知视口。
|
||||
|
||||
DS 服务运行后,在入口旁提供轻量菜单“重新打开”“停止此服务”;操作指向当前匹配服务。
|
||||
若正在生成,停止前说明会中断该服务中的请求。输入区不常驻显示独立会话和服务生命周期说明。不在每次启动时增加确认弹窗。
|
||||
|
||||
按钮快捷入口根据所选 Runtime 显示,不改变工作栏“+”中的普通终端和浏览器入口。已有服务
|
||||
的停止入口应在切换回来后仍可找到。
|
||||
|
||||
## 6. 技术接入
|
||||
|
||||
### 6.1 进程职责
|
||||
|
||||
Renderer 仅提交当前会话或项目标识及选择标识。Main 重新解析项目、模型配置、工作模式与
|
||||
程序路径,通过共享 Zod 契约和显式 preload 方法完成调用。保留已有可信发送方校验,供应商
|
||||
密钥留在 Main 加密设置和模型请求路径中。
|
||||
|
||||
建议新增小型原生客户端启动服务,负责配置准备、启动去重和资源释放。终端会话仍由
|
||||
`TerminalSessionManager` 管理;DS 服务只需内存中的子进程、地址和桥接引用。不要新建平行
|
||||
终端管理器或通用进程恢复引擎。
|
||||
|
||||
共享接口应返回可供界面使用的启动状态、终端标识或 DS 服务标识。外部 URL 由 Main 打开,
|
||||
不允许 Renderer 提交任意程序、命令行、凭据或任意服务地址。错误保留组件、阶段和脱敏原因。
|
||||
|
||||
DS Web 随包依赖通过 `build/native-dsh-resources.cjs` 保留 npm 的嵌套布局。
|
||||
复制时原样保留相对符号链接,不能将 CLI 链接改写成构建机的绝对路径;
|
||||
经过 electron-builder 资源复制后,即使移除原始安装和暂存目录,链接仍须能解析到包内文件。
|
||||
这一交付约束覆盖 FR-9;不改变 Agent 的独立 Runtime 打包路径。
|
||||
|
||||
### 6.2 Continue 与 OpenCode
|
||||
|
||||
Continue 使用已解析的标准 Node 启动固定版本 CLI,不能直接假定 Electron 的
|
||||
`process.execPath` 等于 Node。OpenCode 使用已有平台对应可执行资源,启动交互 TUI。
|
||||
两者均复用现有配置生成机制,但单独验证原生交互模式可接受的参数、模型 ID、环境变量及
|
||||
Skills/MCP 配置,避免继承测试机器的全局默认模型。
|
||||
|
||||
本地 PTY 创建需接受 Main 生成的可执行路径、参数、环境和 cwd,不能先打开普通 Shell 再
|
||||
模拟键入一段含凭据的启动命令。退出 CLI 后是否保留终端输出沿用既有终端体验;其桥接应
|
||||
随 CLI 生命周期收束,不等到整个聊天结束。
|
||||
|
||||
Continue 的共享宿主适配器在 `GOODBUDDY_DISABLE_CONTINUE_UPDATES=1` 时,直接让更新服务保持 idle,并显示 `Continue CLI`,不进入 checking 状态。仅跳过底层版本查询仍会短暂显示 `Checking for updates`,因此服务入口和版本查询两处都需跳过。本地原生终端与 Agent Continue helper 使用同一适配器;已有缓存按补丁内容哈希重新生成。实测范围见[更新提示验证记录](./progress.md#2026-09-27-continue-update-status)。
|
||||
|
||||
模型桥作为客户端会话资源保持有效。复用前检查当前桥接是否依赖单轮请求、单一消息 ID
|
||||
或短时授权;必要时只提取所需的会话级资源接口。真实供应商密钥不写入 CLI 配置,客户端
|
||||
仅获得桥接所需的局部连接信息。
|
||||
|
||||
### 6.3 DS Web
|
||||
|
||||
使用固定版本的官方 Web 包,通过标准 Node 在回环地址启动。以下命令已在
|
||||
`@deepseek-ai/dsh@0.1.7-rc.2`、Windows x64、Node 24.19.0 下启动成功:
|
||||
|
||||
```text
|
||||
<managed-node> <resolved-dsh-cli> web --no-open --host 127.0.0.1 --port 0
|
||||
```
|
||||
|
||||
生产环境直接使用已准备的资源,不在用户点击时执行不固定版本的 `npx`。Web 资源与当前
|
||||
DeepSeek Harness 后台 Host 依赖分开管理,避免为了 Web UI 升级而隐式替换后台 Agent 库。
|
||||
沿用既有受管工具环境和资源分发机制,不新增下载体系。若需在线分发,按该功能文档规范
|
||||
明确下载源设置范围、固定版本、大小和 SHA-256;镜像与原地址使用相同字节内容。
|
||||
|
||||
使用 GoodBuddy 管理的独立 `DSH_HOME`,与用户已有 DS 安装隔离。原生历史和用户设置属于
|
||||
持久数据,服务退出只删除可重建启动配置。多个运行实例不得共享不支持并发访问的数据库;
|
||||
在验证官方存储行为后确定按项目及配置分目录的最小方案。
|
||||
|
||||
启动后经官方支持的配置/API 创建或查找项目工作区,初始化模型连接,再打开已选中目标
|
||||
工作区的页面。仅设置 cwd 或成功创建后台工作区都不足以完成该步骤。不得直接修改未经
|
||||
确认的 DS 私有数据库结构。
|
||||
|
||||
本轮验证使用 `$DSH_HOME/cordis.patch.yml` 配置 `llm-pi-ai` 的 `goodbuddy` provider,
|
||||
设置 `api: openai-completions`、本地桥 `baseURL`、`apiKeyEnv` 和模型列表;
|
||||
`agent-default-model` 指向同一 provider/model。环境变量中只放本地桥接标识。
|
||||
官方 CLI 的 `profile-boot` 代码将该 patch 作为用户配置层加载。
|
||||
|
||||
取得官方启动地址并建立认证 cookie 后,调用 `POST /api/workspace/create`,请求形态为:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "client-request",
|
||||
"rpcId": "<uuid>",
|
||||
"method": "workspace/create",
|
||||
"payload": { "args": { "request": { "path": "<project-directory>" } } }
|
||||
}
|
||||
```
|
||||
|
||||
该接口在首次运行返回 `created: true`,重开同一测试目录时返回 `created: false`。
|
||||
随后访问根页面,两次均自动选中 `workspace` 和 `deepseek-flash`,没有手动选择模型或
|
||||
再次输入供应商密钥。此证据覆盖单项目、独立 `DSH_HOME`;多项目共享服务的选中规则未测。
|
||||
首次页面保留官方“内测声明”的“继续”操作;测试通过原生按钮继续,没有伪造接受记录。
|
||||
官方默认权限显示“工作区内修改”,本轮只验证读取;权限适配按第 4.2 节另行接入。
|
||||
|
||||
Main 检查服务就绪后,使用 Electron `shell.openExternal` 打开官方规定的访问地址。
|
||||
如地址带认证令牌,沿用官方认证方式,不写入普通日志。浏览器只访问本机服务;模型请求
|
||||
通过 Main 中的桥接到达供应商。HTTP、流式响应、取消和错误映射都需实测。
|
||||
|
||||
### 6.4 远程 Continue / OpenCode
|
||||
|
||||
沿用 SSH Host 身份、凭据存储和 SSH PTY,并通过既有 GoodBuddy Agent 部署资源定位远端
|
||||
Runtime。远端配置与模型桥需要客户端生命周期,不能直接复用一次聊天请求结束即释放的
|
||||
桥接实例。远端使用已有模型桥协议及传输路径,是否需要补充生命周期接口由源码验证决定。
|
||||
|
||||
开发时必须在共享 Linux x64 Host 上运行当前改动,按
|
||||
[远程主机技术设计](../remote-host/technical-design.md)执行真实启动、模型请求、取消、
|
||||
断开和清理。端口可达不等于验证通过;若 Host 不可达,应记录阻塞,不能宣布远程功能完成。
|
||||
|
||||
远程 DS 将来若扩展,可评估远端 Web 加 SSH 本地端口转发;本轮不修改 DS 远程能力声明。
|
||||
|
||||
## 7. 实施顺序与技术验证
|
||||
|
||||
| 阶段 | 工作与应提交证据 | 通过条件 |
|
||||
| --- | --- | --- |
|
||||
| P1 原生配置验证 | 固定三个客户端版本;保存脱敏命令、环境与程序路径;隔离已有全局配置 | Continue/OpenCode 使用指定模型;DS 自动选中目标项目 |
|
||||
| P2 模型与模式 | 每个客户端发送最小真实请求;验证流式、取消、Ask 写入拒绝及 Execute 专用目录写入 | 请求确实经过 GoodBuddy 模型桥;客户端模式切换不能破坏 Ask 边界 |
|
||||
| P3 生命周期接入 | 配置、PTY、DS 进程和模型桥接入现有管理路径 | 重开、失败重试、关闭及退出能释放对应资源,原生历史保留 |
|
||||
| P4 远程验证 | 在共享 Host 部署当前代码,验证 Continue/OpenCode 的 SSH PTY 与模型桥 | 启动、请求、取消、断开和重试均有真实 Host 证据 |
|
||||
| P5 UI 与交付资源 | 接入按钮、通知、停止 DS 服务;完善平台资源与文档 | 从真实按钮到客户端完成对话,无手动补依赖步骤 |
|
||||
|
||||
P1、P2 是实施前段的实验任务,完成后继续交付完整入口,不将实验程序或不可用按钮作为最终功能。
|
||||
|
||||
DS 单项目工作区选中和当前模型桥协议已通过本轮验证。后续优先接入三个客户端的既有
|
||||
Ask/Execute 工具规则、Skills/MCP 配置及客户端生命周期,再完成快捷入口和交付资源。
|
||||
官方 DS 首次预览声明保留原生“继续”流程,模型配置提前准备。
|
||||
|
||||
## 8. 验收计划
|
||||
|
||||
| 验收面 | 检查内容 | 需求与场景 |
|
||||
| --- | --- | --- |
|
||||
| 实际使用 | 每个本地客户端从 GoodBuddy 按钮进入当前项目,使用当前模型完成一轮真实对话 | FR-1~4、US-1、2 |
|
||||
| 配置隔离 | 预置另一套全局模型后,仍使用当前选择;两个项目不串目录或配置 | FR-2、3、US-5 |
|
||||
| 工作模式 | Ask 尝试文件写入、Shell 写入和 MCP 写入均不执行;Execute 在专用测试目录成功写入 | FR-5、8、US-7 |
|
||||
| 能力复用 | 为声明支持的 Skills、MCP 各验证实际加载及最小调用;列出未承接能力 | FR-8 |
|
||||
| 生命周期 | 连点启动、切项目、隐藏工作栏、关闭页签、重开 DS、停止服务及退出 | FR-6、US-4、5、8 |
|
||||
| 已知失败 | Node/原生依赖缺失、失效目录、模型错误、浏览器打开失败、SSH 中断 | FR-7、9、US-6 |
|
||||
| 远程 | 当前源码在共享 Linux x64 Host 上完成 Continue/OpenCode 的 Ask/Execute、模型和断开场景 | FR-2、3、5、US-3 |
|
||||
| UI | 浅深色、键盘操作、窄输入区、无压缩按钮、空对话、200% 文字缩放 | FR-1、US-1、2 |
|
||||
| 平台资源 | 对实际交付的 Windows、macOS、Linux 架构逐项验证 Node、PTY、CLI 和 DS 原生依赖 | FR-9 |
|
||||
|
||||
完成源码后执行 `npm test`、`npm run typecheck`、`npm run lint`。修改生产构建资源时再按
|
||||
仓库开发规则执行构建验证。单元测试覆盖行为和回归,不能代替真实按钮、模型和远程 Host 路径。
|
||||
每份实测报告记录日期、源码 commit、包版本、平台、结果、脱敏证据位置及实际模型调用次数。
|
||||
|
||||
## 9. 文档交付状态
|
||||
|
||||
本轮完成 DS 后台真实模型与官方 Web 主流程验证,更新方案和
|
||||
[DS 进度证据](../deepseek-harness/progress.md#2026-09-26-真实模型与官方-web-验证)。
|
||||
P1 中 DS 单项目启动、模型预配置和工作区选中,以及 P2 中 DS 模型桥请求已实测。
|
||||
The DS Web backend implementation and its focused validation are recorded below. Current local shortcut evidence is recorded in [workbar progress](./progress.md#2026-09-27-local-native-client-validation); full Electron UI acceptance remains separate.
|
||||
后续入口交付事实写入工作栏 [progress.md](./progress.md),按 FR、US 和阶段编号关联本方案。
|
||||
|
||||
实施时同步更新工作栏产品范围、终端规范、Runtime 交互边界及根 UI 规范;DS Web 的版本、
|
||||
依赖与兼容性写入 [DeepSeek Harness 文档](../deepseek-harness/README.md),远程部署事实写入
|
||||
[远程主机文档](../remote-host/README.md)。保持各自职责,不复制多个相互冲突的支持矩阵。
|
||||
|
||||
### 9.1 DS Web Backend, 2026-09-26
|
||||
|
||||
`src/main/agent/native-dsh-web-client.ts` exports `NativeDshWebClientService`.
|
||||
Construct it with `rootDirectory`, an async `resolveLaunchEnvironment` returning
|
||||
`{ nodeExecutablePath, environment }`, and `resourcesPath` in packaged builds.
|
||||
`start({ projectId, workspace, profile, workMode, configurationKey?, skillDirectories?, mcpServers? })`
|
||||
returns `{ id, url }`. Main opens this URL with `shell.openExternal`; it must not
|
||||
log the token-bearing URL or pass it to Renderer. `stop(id)` and `dispose()` close
|
||||
the owned process tree and bridge. A browser-open failure leaves the service ready
|
||||
for another call. Matching concurrent starts reuse one instance; changed model
|
||||
credentials, project, mode, or capability assignments create a separate instance.
|
||||
|
||||
The service uses the existing managed model bridge, AgentModelGateway, and loopback
|
||||
proxy. Provider credentials stay in Main. Each matching configuration has its own
|
||||
persistent DSH_HOME. Shutdown removes launch configuration and the bridge ledger,
|
||||
while retaining native history and settings. Startup authenticates through the
|
||||
official launch URL and calls `workspace/create` before returning.
|
||||
|
||||
The host-level `tools/execute` hook permits native `read` and `skill` in Ask.
|
||||
Assigned MCP servers supply their already-classified `readOnlyTools` as raw MCP
|
||||
tool names; all other tool names are denied in Ask. Execute uses native full-access
|
||||
permissions with no additional approval. The native permission picker is hidden;
|
||||
changing Plan or creating another native session does not change the launch policy.
|
||||
Skill directories and stdio/streamable-HTTP MCP configurations use official plugins.
|
||||
Knowledge, built-in MCP, and GoodBuddy confirmation/configuration tools require a
|
||||
caller-owned MCP adapter and are not automatically exposed by this service.
|
||||
|
||||
`@deepseek-ai/dsh@0.1.7-rc.2` is a pinned build dependency. The existing packaging
|
||||
hook copies its installed dependency tree, including frontend assets and nested
|
||||
dependency versions, to `resources/runtimes/dsh` outside ASAR. Target Sharp and
|
||||
Koffi packages come from exact lockfile entries through the existing artifact
|
||||
preparation code. The Web package itself needs no click-time npm install. The
|
||||
coordinator now honors the current custom Node selection from the local tool
|
||||
environment. Its managed fallback still installs `node-<platform>-<arch>@22.22.0`
|
||||
through npm; this does not yet satisfy the prepared-resource delivery requirement
|
||||
in section 6.3. No new download setting was added.
|
||||
|
||||
Focused tests use standard Node 24.19.0 on Windows x64 and the prepared resource
|
||||
tree. They exercise Web startup, authentication, project creation, reuse,
|
||||
mode isolation, stop/restart, retained history, and real Web API tool execution.
|
||||
Deterministic model responses travel through the production model bridge: Ask
|
||||
rejects writes, Execute creates the expected file, assigned skills load, read-only
|
||||
MCP executes, and writing MCP is rejected before dispatch in Ask. These backend
|
||||
tests make zero external model-provider calls. This evidence does not claim a
|
||||
signed installer, other platforms, or the complete shortcut UI has been verified.
|
||||
|
||||
Validation: seven focused tests passed against the prepared resource tree;
|
||||
the copied Sharp 0.35.4 native module encoded a PNG successfully; Main TypeScript
|
||||
checking and `npm run build:bundle` passed. Full `npm run build`/typecheck and
|
||||
`npm run lint` encountered concurrent Renderer errors in App.test.tsx and
|
||||
RightAssistantSidebar.tsx; the backend files passed focused lint. Remote Agent
|
||||
launch paths are unchanged because this service supports local DS Web only.
|
||||
|
||||
### 9.2 Local Coordinator Integration
|
||||
|
||||
The production coordinator resolves the saved conversation/project and current
|
||||
settings for both `get` and `open`. Matching includes the selected model profile
|
||||
and credentials, work mode, capability assignments, knowledge scope, Obsidian
|
||||
settings and local Node selection. Ready services keep their MCP gateway after an
|
||||
OS browser-open error and can be reopened or explicitly stopped by their owner.
|
||||
The coordinator passes its configuration key into the service's existing match
|
||||
hash, so the inner DS reuse decision also observes updated skill digests and
|
||||
launch settings even when directory paths are unchanged.
|
||||
|
||||
For DS Ask, the built-in gateway's existing read grant supplies the allowed raw
|
||||
tool names; the Web adapter maps these through the official MCP naming contract.
|
||||
Custom MCP is exposed in Execute, matching the existing Runtime behavior; custom
|
||||
`readOnlyHint` annotations do not introduce a separate Ask permission rule.
|
||||
Assigned skill directories containing `SKILL.md` load through the official skill
|
||||
plugin. Continue/OpenCode native Ask now retain Main-bound endpoints and map the
|
||||
granted read-tool names through both native permissions and execution hooks.
|
||||
OpenCode prefixes names with the bound MCP server name; Continue uses raw names.
|
||||
Story Graph additionally rechecks Supervisor enablement on every call.
|
||||
|
||||
The coordinator remains local-only at this checkpoint. The remote implementation
|
||||
is being developed separately and must be connected and verified on the shared
|
||||
Host before remote shortcut support can be claimed. Current checks and exact
|
||||
supplier counts are maintained in the workbar progress record linked above.
|
||||
@@ -70,6 +70,9 @@ schema 35 的存储修复与本方案独立,不能回退或删历史来改善
|
||||
| Cordis | 4.0.2 | Host 根组件与 Agent scope 的生命周期 |
|
||||
| Continue CLI | 1.5.47 | 实际受管补丁入口的单 `M.session`、QuizService 和权限状态 |
|
||||
|
||||
本表保留进程复用探针执行时的版本。2026-09-26 DS 已升级为 `0.1.7-rc.2`,
|
||||
Cordis 为 `4.0.4`;当前依赖及升级回归见 [DS 实现进度](../deepseek-harness/progress.md)。
|
||||
|
||||
已通过的三个隔离探针:
|
||||
|
||||
1. **OpenCode HTTP**:调用当前生产 Server launcher,只有一次 spawn;同一个 SDK client
|
||||
@@ -159,6 +162,10 @@ SelectedRuntimeManager
|
||||
|
||||
会话绑定记录规范化绝对目录及原生 sessionId。同一 conversation 的请求继续串行,
|
||||
不同 conversation 并行,不引入覆盖整个 Server 的 Prompt 队列。
|
||||
取消或失败时,每次请求只发送一次会话级 abort,并在该调用结束后才释放会话锁,避免
|
||||
迟到的 abort 中断下一条请求。abort 使用既有控制请求超时;失败或超时后移除对应绑定,
|
||||
下一条请求创建新 Session。这也适用于配置外部 OpenCode HTTP Server 的路径;托管 SSH
|
||||
使用独立的 ACP 取消路径。
|
||||
现有 `snapshot: false` 必须保留。
|
||||
|
||||
`mcpMutationTail` 目前属于 Runtime 对象;共享后同一原生目录的变更必须由同一 owner
|
||||
@@ -170,6 +177,38 @@ SelectedRuntimeManager
|
||||
健康检查也读取 owner 的总 activeRequests,不能从一个空闲 adapter 判断整个 Server
|
||||
无任务后重启。异常退出使该 owner 的全部绑定失效,明确结算受影响请求,不重放已执行工具。
|
||||
|
||||
### 4.1 本地 MCP 初始化失败恢复
|
||||
|
||||
桌面 HTTP 工具网关保持监听,MCP 注册和授权仍按请求创建、清理。本地 OpenCode 的内置
|
||||
工具和自定义 MCP 共用连接逻辑:仅对明确的连接拒绝、连接重置、断管或传输超时,在
|
||||
一秒内成功断开失败连接后重试一次。SDK 返回的无 HTTP response 网络错误和抛出的网络
|
||||
错误采用同一判定;HTTP/API 错误、鉴权或协议错误、未知失败不自动重试。
|
||||
取消立即终止后续尝试。Prompt 和工具调用不重放。
|
||||
|
||||
自定义 MCP 上游发现由 `KnowledgeMcpGateway.getCustomMcpBindings` 按服务隔离失败。
|
||||
某个服务连接、列举或校验工具失败时,记录脱敏诊断并跳过该服务,保留正常服务的连接;
|
||||
全部失败时返回空的自定义工具列表,Continue 和内置 OpenCode 仍可开始回复。
|
||||
首次发现的结果在当前授权内复用,初次失败的服务在下一次请求创建授权时重试。
|
||||
动态刷新失败会清空该服务的工具列表,下次发现时再刷新,不阻断其他服务。
|
||||
请求取消和授权撤销仍终止操作,工具总数和名称冲突检查保留。发现仍等待连接尝试结束,
|
||||
因此连接超时可能延迟首个回复;失败隔离不等于后台加载。
|
||||
|
||||
OpenCode 1.18.x 的 `mcp.add` 在服务端独立执行:客户端超时或取消不会中止初始化,
|
||||
`disconnect` 也只清理当时已登记的连接。晚到的初始化完成后会按名称写入状态,并关闭同名
|
||||
的现有连接。因此,`mcp.add` 未得到 HTTP 响应(抛错、超时、取消或无 response 的网络错误),
|
||||
或断开未确认成功时,该名称即作废;同一槽位之后改用本客户端内唯一的替代名称
|
||||
(前缀加 8 位 base-36 序号,仍为 12 个字符)。槽位随请求结束释放,不因断开失败而长期占用。
|
||||
已得到响应的正常失败继续复用原名称。
|
||||
|
||||
界面显示有界、脱敏的失败详情。`runtime.mcp.failed` 诊断记录连接或断开阶段、失败类别、
|
||||
耗时、尝试次数、内置或自定义类型及可用的 HTTP 状态;通过本轮令牌的 SHA-256 摘要与
|
||||
网关鉴权、初始化和工具发现失败记录关联。日志不保存令牌、地址、请求正文或原始错误。
|
||||
网关初始化抛错时也清理未完成会话,防止失败会话累积。
|
||||
|
||||
此恢复逻辑属于本地 `OpenCodeRuntime`。远程 Agent 使用 `RuntimeAcpBackend` 和
|
||||
`AgentImageToolMcp`,不经过本地 MCP 注册或网关 HTTP 处理;本节不代表远程恢复能力。
|
||||
远程 Story Graph 通过网关的独立绑定调用,不经过上述自定义 MCP 发现方法。
|
||||
|
||||
## 5. DeepSeek Harness
|
||||
|
||||
### 5.1 共享 Host 的最小改法
|
||||
@@ -227,8 +266,9 @@ Agent 当前 Runtime installation + 兼容配置
|
||||
竞争读取同一 stdout。消息和 permission 按 sessionId 分派给 binding 的当前 operation。
|
||||
- `AgentAcpConnection` 统一连接级握手、stdio 解码与退出监听,Prompt
|
||||
生命周期仍留在 binding。同一 Session 串行,其他 Session 不等待它完成。
|
||||
- `closeAcpChannel`、取消和 release 只释放所属 binding/Session;最后一个 binding
|
||||
离开后空闲 60 秒关闭进程。关闭 Session 时等待原生 `closeSession` 和原始 Prompt
|
||||
- `closeAcpChannel`、取消和 release 只释放所属 binding/Session;以活动 operation
|
||||
判断共享进程是否空闲,最后一个任务结束即回收,不因历史 binding 保留进程。
|
||||
待答问题仍占用活动 operation。关闭 Session 时等待原生 `closeSession` 和原始 Prompt
|
||||
结算,不等待排在 backend 控制队列后面的完成回调。只有原生控制面不响应时才有界
|
||||
终止共享进程,并向其余请求报告中断,不声称这类故障可以逐 Session 隔离。
|
||||
- terminal 与物理进程状态分开:取消/失败的 Session 也可返回
|
||||
@@ -313,9 +353,10 @@ token、global dir、活动 Session 或权限/问题队列。设置替换及窗
|
||||
callback;会话释放才结束其可续读输出和工具资源,不在工具刚完成时删掉用户可查看结果。
|
||||
- 当前 50 个会话的 history 限制不是字节上限。先采堆与 retained size,再决定可从
|
||||
SQLite 重建的历史缓存是否使用字节约束和 LRU,不能先增加一套持久历史副本。
|
||||
- Renderer 最近 100 个会话整包 metadata 加载另做列表摘要、当前会话分页、展开后取
|
||||
详情的性能改造;本方案只记录依赖,不将这一尚未设计完成的 UI 改造算进进程修复交付。
|
||||
必须保持完整历史、搜索、重新打开和当前活动事件可用。
|
||||
- Renderer 整包 metadata 加载是独立于进程复用的性能工作。现有实现改为列表摘要、
|
||||
打开会话时读取完整历史,不截断已保存消息;搜索、复制、续聊、后台活动和缓存释放
|
||||
规则见[会话列表读取与前端保留](./execution-history-storage.md#会话列表读取与前端保留)。
|
||||
不将这项后续修改的收益计入原进程复用对照结果。
|
||||
- 旧 Runtime 磁盘缓存可单独评估版本清理策略,不在本次研究或进程 idle 时删除安装、
|
||||
用户工作区、数据库、备份或历史。
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ SSH 连接池,但尚不存在工作栏应用实例模型、终端 IPC、终端
|
||||
### 3.1 工作栏应用
|
||||
|
||||
工作栏应用是可以在助手工作栏中打开的一个能力,例如任务中心、工作区、浏览器、成果或
|
||||
终端。每项应用通过统一注册表声明:
|
||||
终端、笔记。每项应用通过统一注册表声明:
|
||||
|
||||
- 稳定应用 ID。
|
||||
- 用户可见名称、图标和说明。
|
||||
@@ -54,7 +54,8 @@ SSH 连接池,但尚不存在工作栏应用实例模型、终端 IPC、终端
|
||||
- 当前不可用时的原因和修复入口。
|
||||
|
||||
目录中的应用可发现性与实例是否已经打开相互独立。应用不可用时保留入口并说明原因,不以
|
||||
隐藏代替状态。
|
||||
隐藏代替状态。笔记遵循其业务启用设置:关闭魔法笔记时隐藏目录入口并移除对应 Tab,
|
||||
关闭前处理草稿,规则见[会话联动逻辑](../magic-notes/logic-design.md)。
|
||||
|
||||
注册表字段如下:
|
||||
|
||||
@@ -76,21 +77,23 @@ SSH 连接池,但尚不存在工作栏应用实例模型、终端 IPC、终端
|
||||
`defaultOpen`、`required` 和 `closable` 不互相推导。例如浏览器默认打开但不是必需实例;任务
|
||||
中心和工作区既默认打开又必须存在。`required` 不表示锁定顺序,顺序只由 `reorderable` 控制。
|
||||
|
||||
当前五个应用的 `visibleAcrossContextSwitches` 均为 `true`。该属性记录应用目录的显示策略,
|
||||
当前六个应用的 `visibleAcrossContextSwitches` 均为 `true`。该属性记录应用目录的显示策略,
|
||||
不改变内容归属,也不授予其他会话的 MCP 访问权限;当前没有使用 `false` 的应用,尚未实现
|
||||
按上下文隐藏 Tab 的策略。
|
||||
|
||||
浏览器在创建时绑定当前 Conversation;切换会话后仍保留原 Tab、选中状态和页面,导航操作
|
||||
继续使用原 `targetRef.conversationId`。此时新建浏览器才绑定新会话。默认或旧布局中尚未绑定
|
||||
的浏览器在首次使用时绑定活动会话,此后不再随会话切换改绑。终端同样保留创建时执行目标;
|
||||
任务中心、工作区和成果的内容继续跟随当前项目。
|
||||
任务中心、工作区和成果的内容继续跟随当前项目。笔记使用应用级范围,选择与草稿不随项目或
|
||||
会话切换,不接受 `targetRef`;重启仅恢复工作栏 Tab 描述,不恢复笔记选择和草稿。
|
||||
|
||||
### 3.2 工作栏 Tab 实例
|
||||
|
||||
工作栏 Tab 是应用的一次打开实例。每个实例具有独立 ID、标题、应用类型和状态。Tab 行只
|
||||
显示已经打开的实例,不把全部能力同时渲染为 Tab。
|
||||
|
||||
实例持久化字段只有 `id`、`appId`、`title` 和可选 `targetRef`。`targetRef` 使用公开引用:
|
||||
实例持久化字段为 `id`、`appId`、`title`,以及可选的 `targetRef`、`defaultTitleNumber`。
|
||||
默认标题编号的保存和兼容规则见第 10 节。`targetRef` 使用公开引用:
|
||||
终端接受 `local` 或 `projectId`,浏览器接受 `conversationId`。Main 签发的 Browser Tab ID、
|
||||
Terminal Session ID、元素引用、凭据、路径和运行状态不进入工作栏布局。
|
||||
|
||||
@@ -177,10 +180,14 @@ stdio fallback。首期远程终端随 SSH Channel 结束,不提供跨 SSH 或
|
||||
|
||||
- 已打开实例位于一个语义化 `tablist` 中。
|
||||
- 每个实例使用独立 `tab` 和 `tabpanel`。
|
||||
- “+”固定在 Tab 行末尾,但位于 `tablist` 之外。
|
||||
- Tab 过多时实例区域单行横向滚动,“+”保持可见;不换行、不自动隐藏实例。
|
||||
- “+”固定在 Tab 行末尾,位于 `tablist` 和页签滚动容器之外,占用独立宽度。
|
||||
- Tab 过多时实例区域单行横向滚动,不换行、不自动隐藏实例。“+”在 `300px` 工作栏内也保持可见和可点击。
|
||||
- 标签全部可见时隐藏左右滚动按钮;溢出时左右箭头透明浮动于页签区域两端,不占标签布局宽度,也不进入“+”的点击区域。鼠标移入标签栏时显示可滚动方向的箭头,移出后隐藏;键盘聚焦箭头时显示,无悬停能力的设备保持可滚动方向的箭头可见。悬停箭头时显示轻微底色。点击滚动约一屏,边界方向禁用并隐藏,且不拦截下方标签的指针操作。切换或新增激活标签时将其滚入可见区域。未激活标签使用低对比度灰蓝底色和细边框,激活标签保留亮底与强调色顶线。
|
||||
- 终端 Tab 默认使用递增标题,例如“终端 1”“终端 2”;状态栏持续显示项目、Host、
|
||||
工作目录和 Shell。用户可以重命名终端。
|
||||
- 任务中心、工作区、成果及新建终端的默认标题随界面语言切换,恢复布局时也使用当前语言。
|
||||
终端自定义名称保持原文;旧布局中的终端名称保持原值,即使看起来像“终端 1”。浏览器
|
||||
标题沿用现有行为,不在本次标题国际化范围内。
|
||||
- 当前 Tab 同时使用连续内容表面、选中边框、顶部强调线、图标和较高字重,不能只靠文字颜色。
|
||||
- Tab、关闭按钮和“+”入口沿用共享控件圆角;终端面板外框和终端顶部工具栏按钮使用直角,
|
||||
保持紧凑的停靠式终端界面。
|
||||
@@ -213,6 +220,7 @@ stdio fallback。首期远程终端随 SSH Channel 结束,不提供跨 SSH 或
|
||||
| 浏览器 | `multiple` | 当前 Conversation | 是,一个实例 | 否 | 是 | 是 | 每次创建新 Tab |
|
||||
| 成果 | `single` | 当前项目 | 是 | 否 | 是 | 是 | 聚焦已有 Tab |
|
||||
| 终端 | `multiple` | 当前项目 | 否 | 否 | 是 | 是 | 每次创建新 Tab |
|
||||
| 笔记 | `single` | 应用级 | 否 | 否 | 是 | 是 | 聚焦已有 Tab |
|
||||
|
||||
实例策略、默认状态和可关闭策略都是应用注册数据,不能分散硬编码在多个点击处理器中。
|
||||
|
||||
@@ -345,6 +353,7 @@ type WorkbarTabInstance = {
|
||||
id: string
|
||||
appId: WorkbarAppDefinition['id']
|
||||
title: string
|
||||
defaultTitleNumber?: number
|
||||
targetRef?:
|
||||
| { type: 'local' }
|
||||
| { type: 'project'; projectId: string }
|
||||
@@ -362,6 +371,8 @@ type WorkbarLayoutPreferences = {
|
||||
```
|
||||
|
||||
- 持久化工作栏打开状态、Tab 顺序、活动实例、应用 ID、自定义标题、公开目标引用和宽度比例。
|
||||
- `taskScope` 中的 `global` 仅保留旧布局解析兼容;UI 恢复时映射为 `all-projects`,此后只保存
|
||||
`current-project` 或 `all-projects`。范围规则见 [Task Center PRD](../task-and-job/task-center-prd.md#4-范围)。
|
||||
- 加载布局时根据 `required` 补回缺失实例,并拒绝重复的单实例应用;应用策略不写入实例
|
||||
数据,避免持久化状态覆盖注册表。
|
||||
- `reorderable` 已进入共享契约;当前 Tab 壳层尚未提供拖放或键盘重排,不能把声明属性视为
|
||||
@@ -373,6 +384,12 @@ type WorkbarLayoutPreferences = {
|
||||
Shell。
|
||||
- 运行期间切换项目不会改变已经创建的终端目标。用户需要新目标时通过“+”创建新终端。
|
||||
|
||||
新建终端保存正整数 `defaultTitleNumber`,沿用同一目标已有终端数量加一的编号规则。
|
||||
Sidebar 在渲染时按当前语言派生应用和默认终端标题,再交给 Tab 壳层与终端面板;切换语言
|
||||
不改写持久化标题。用户重命名时清除此字段并保存输入的 `title`。缺少标记的旧终端直接显示
|
||||
原 `title`,不通过名称模式推断默认标题。布局存储键保持 `goodbuddy.workbar-layout.v1`;
|
||||
旧版本使用严格 Schema,不能读取带新增字段的布局,本变更不提供降级兼容。
|
||||
|
||||
## 11. 生命周期与关闭语义
|
||||
|
||||
| 用户或系统动作 | Tab | Shell Session |
|
||||
@@ -548,7 +565,7 @@ Schema、假数据终端或不可用入口的中间产品。
|
||||
- [x] 连续选择浏览器会创建页面、导航状态和元素引用相互独立的浏览器 Tab。
|
||||
- [x] 连续选择终端会创建多个互相独立的终端 Tab。
|
||||
- [ ] 关闭可关闭实例、切换和重排实例后,“+”仍保持可用。
|
||||
- [ ] Tab 溢出时单行横向滚动,不隐藏“+”或把 Tab 换成下拉框。
|
||||
- [ ] Tab 溢出时单行横向滚动,页签区两端箭头固定,“+”在滚动区外持续可见,不把 Tab 换成下拉框。
|
||||
- [x] 关闭可关闭的活动 Tab 后激活正确的相邻 Tab 并恢复焦点。
|
||||
- [x] 当前 Tab 的背景、边框、顶部指示和字重清晰可辨;关闭按钮仅在悬停、焦点或无悬停
|
||||
设备上出现,且不引发布局位移。
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
电脑控制包含 GoodBuddy 对浏览器和客户端桌面能力的产品实现与平台适配状态。
|
||||
|
||||
内置浏览器 MCP 默认开启,用户可在设置中关闭。已保存的开关选择在重启和升级后保留。
|
||||
|
||||
## 文档导航
|
||||
|
||||
| 文档 | 权威职责 |
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 会话 HTML 渲染实现进度
|
||||
|
||||
2026-09-27 浮层审查修复:全屏 iframe 已纳入共享焦点循环,跨文档 Escape 通过受限
|
||||
Main/Preload 通知返回外层。真实 App 中的带链接及纯正文预览均已验证键盘进入和 Escape,
|
||||
带链接预览另验证正反向 Tab 返回;没有开放 sandbox 权限或执行预览脚本。
|
||||
实现与测试范围见[浮层审查修复记录](../../quality/overlay-review-2026-09-27.md)。
|
||||
|
||||
## 已实现
|
||||
|
||||
- [x] 应用级持久开关,默认开启,并提供旧设置迁移。
|
||||
|
||||
@@ -56,6 +56,17 @@ iframe 无法按内容自适应高度,因为 `sandbox=""` 不含 `allow-same-o
|
||||
嵌入预览完全相同的清理后文档,不额外放宽 Sandbox 或 CSP。切换回复时,源码面板与全屏
|
||||
预览按当前 source 派生开合状态,不使用会引发级联渲染的重置 effect。
|
||||
|
||||
全屏 iframe 提供 `tabIndex=0`,共享焦点候选包含 iframe。关闭按钮的正向 Tab 显式进入
|
||||
预览,支持没有链接的长文;iframe 后的焦点守卫将原生 Tab 返回到后续 Modal 控件或关闭
|
||||
按钮。守卫本身不作为焦点循环的首尾,通知关闭按钮仍参与循环。
|
||||
|
||||
iframe 内的 Escape 不会冒泡到父文档。`registerWindowIpcHandlers` 只在主窗口直接子
|
||||
`about:srcdoc` frame 获得焦点且收到 Escape keyDown 时,向主 Renderer 发送
|
||||
`window:preview-escape`。Preload 暴露无参数订阅 `app.onPreviewEscape`;全屏组件仅在
|
||||
自己的 iframe 当前聚焦且所属 Modal 未被 inert 隔离时关闭。组件卸载移除订阅,窗口 IPC
|
||||
释放时移除输入监听。此路径不执行预览脚本、不授予同源权限,也不暴露原始键盘事件或
|
||||
Electron 对象;iframe 的 Sandbox/CSP 保持原值。
|
||||
|
||||
## 验证范围
|
||||
|
||||
- 应用设置默认值、关闭持久化和版本 10 迁移。
|
||||
@@ -65,4 +76,5 @@ iframe 无法按内容自适应高度,因为 `sandbox=""` 不含 `allow-same-o
|
||||
- 源码按钮的 `aria-expanded` 切换,以及全屏预览的初始焦点、背景 inert、Escape 关闭和
|
||||
焦点恢复。
|
||||
- 主 Agent、子 Agent、用户、推理和流式状态边界。
|
||||
- 真实 Electron 中带链接与纯正文 iframe 的进入、正反向 Tab 返回和内部 Escape;通过生产 Main/Preload 事件订阅完成关闭。
|
||||
- 从设置 Switch 到当前会话重新渲染的应用级集成路径。
|
||||
|
||||
@@ -51,6 +51,11 @@ Main 从已保存图片 profile 的会话调用开关推导允许模型目录,
|
||||
|
||||
Main 仅在图片和会话引用持久化成功后发布完成事件。素材必须是当前会话可访问的附件或成果,沿用已有项目和会话边界;删除会话后的晚到结果不重建会话。
|
||||
|
||||
会话刷新共用保留对象引用的图片状态合并:无图片或图片状态未变化时,不重建消息并触发
|
||||
整段历史重新保存。未打开历史的读取与缓存释放遵循
|
||||
[会话列表读取与前端保留](../assistant-workbar/execution-history-storage.md#会话列表读取与前端保留),
|
||||
不改变图片操作的 Main 所有权、成果关联或时间优先级。
|
||||
|
||||
## 图片协议
|
||||
|
||||
沿用[图片技术设计](../image-generation/technical-design.md)的已有生成、编辑协议及素材限制。接收并校验有界内联图片,不下载 Provider 返回的图片 URL。沿用现有成果存储限制,实施时核对编码开销与超限反馈,不以配置检查成功代替真实生成验收。视频协议、下载、播放及交付计划均不属于本设计。
|
||||
@@ -66,7 +71,7 @@ Main 仅在图片和会话引用持久化成功后发布完成事件。素材必
|
||||
| GoodBuddy Agent 远程适用 Runtime | 复用桌面与 Agent 通信及工具桥接,将请求绑定原会话,由 Main 调用图片模型 | 逐一验证实际支持的远程 Runtime,覆盖真实 Host 工具往返、生成编辑、本地与远程双向切换、断连及成果归属 |
|
||||
| 无工具能力的聊天模型 | 说明自动调用限制,可使用已有直连图片工作流 | 不解析普通文字伪装工具调用,不新增表单;Ask 不提交 |
|
||||
|
||||
本地 OpenCode、Continue 和 DeepSeek Harness 经内部 MCP gateway 注入;直连 Model 使用内置工具分发。远程通过 `main-image-tool-session.ts`、Agent 协议和 `image-tool-mcp.ts` 回到同一 Main 服务。现有 GoodBuddy Agent 的受管远程 Runtime 为 OpenCode,项目校验和 Runtime 创建均沿用此范围;共享契约包含其他 Runtime 不表示存在其远程执行路径。保留会话在目录清空时也刷新 MCP,Continue 准备失败时释放本次分配的令牌。工具发现成功或 Skill 注入成功不能代替真实生成与编辑。
|
||||
本地 OpenCode、Continue 和 DeepSeek Harness 经内部 MCP gateway 注入;直连 Model 使用内置工具分发。远程通过 `main-image-tool-session.ts`、Agent 协议和 `image-tool-mcp.ts` 回到同一 Main 服务。当前受管远程 Runtime 包括 OpenCode 和 Continue;Continue 模型桥的会话 MCP 交付及 Ask 边界见[远程 Runtime](../remote-host/technical-design.md#runtime)。保留会话在目录清空时也刷新 MCP,Continue 准备失败时释放本次分配的令牌。工具发现、无计费测试接收端往返或 Skill 注入成功不能代替真实生成与编辑。
|
||||
|
||||
Main 已接收的请求在桌面仍运行时独立于远程连接继续;未确认是否接收的请求不自动重放。聊天回合停止等待与用户取消图片是不同动作,须验证现有取消传播不会误杀 Main 图片请求。
|
||||
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
# 会话监督
|
||||
# 监督者与会话监督
|
||||
|
||||
会话监督定义对长会话、工具执行和异常状态的观察、判断与介入边界。
|
||||
监督者统一工作回顾、会话监督与故事线图谱,以智能心跳作为定时唤醒机制。现有智能心跳页面承载监督者工作回顾入口,手动回顾、定时结果投影、来源原文、实体/关系确认、继续讨论、知识库预览提交和侧栏反馈已经接入。历史回顾定位到对应结果图谱;侧栏按会话或任务查询,并支持固定目标。同 scope 的跨 run 实体复用依赖模型显式选择 Main 提供的候选引用,Main 处理 UUID 和旧文本主键的精确映射,具体规则和验证边界见[实施进度](./progress.md)。
|
||||
|
||||
## 文档导航
|
||||
|
||||
主页面包含工作回顾、故事线图谱、自动监督、活动记录和设置。工作回顾阅读统一监督结果并选择历史,图谱定位同一结果;自动监督管理计划、监督建议和只读的旧报告。活动记录展示统一执行、进度、建议阶段及详情,可按计划 ID 筛选;设置只保留算法参数。当前布局以 [UI 设计](./ui-design.md#应用入口)为准。活动读取实际手动回顾及心跳执行,合并展示同一次心跳触发的监督回顾与建议阶段;状态规则、历史缺失和保留期限见[活动记录](./logic-design.md#活动记录)。
|
||||
|
||||
心跳的职责为“定时触发增量回顾+按配置介入(建议)”,不再先生成独立报告;手动“回顾”默认增量,历史重看改为显式“重新整理”。规则见[心跳职责与介入](./logic-design.md#心跳职责与介入目标设计),需求 FR-S12,验收 US-S32、US-S33,2026-09-30 已实现(主动介入除外),验证见[实施进度](./progress.md#2026-09-30-心跳职责收敛与监督建议)。
|
||||
|
||||
监督回顾按来源版本及正文位置增量处理,手动回顾与心跳共享进度,无变化时跳过模型;“重新整理”才重读所选区间。旧的进度、失败重试和删除边界见[自动增量与手动重看](./logic-design.md#自动增量与手动重看)。
|
||||
|
||||
| 文档 | 权威职责 |
|
||||
| --- | --- |
|
||||
| [会话监督 PRD](./prd.md) | 监督目标、证据、介入方式和产品验收 |
|
||||
| [监督者应用产品设计](./supervisor-prd.md) | 应用定位、心跳分工、知识库关系与目标范围 |
|
||||
| [逻辑设计](./logic-design.md) | 整体架构图、图谱对象关系图、状态规则与需求映射 |
|
||||
| [UI 设计](./ui-design.md) | 应用导航、工作回顾、侧栏反馈及页面状态 |
|
||||
| [User Stories](./user-stories.md) | 监督者与故事线图谱的用户操作和验收场景 |
|
||||
| [技术设计](./technical-design.md) | Main/Renderer 分层、数据模型、IPC、安全边界和实施顺序 |
|
||||
| [时态 Story Graph MCP 设计](./story-graph-mcp-design.md) | 只读工具、时间与版本语义、Runtime 接入,以及后续跨范围事实复用和记忆过渡合同 |
|
||||
| [回顾分块调度设计](./review-scheduling-design.md) | 第 0 节定义生产分页、批次持久化、续跑、当前设置及剩余边界;其余章节保留改造前基线、目标设计和原型证据 |
|
||||
| [故事线模型改造设计](./storyline-model-design.md) | 全局 › 项目 › 功能 › 子线索的故事层级、故事归属与经验抽取阶段、用户调整及实施顺序(目标设计) |
|
||||
| [实施进度](./progress.md) | 已验证实现、验证证据和当前边界 |
|
||||
| [故事线图谱设计](../story-graph/prd.md) | 图谱对象、环绕时间轴与实体交互 |
|
||||
|
||||
当前尚无独立 User Stories、功能逻辑、UI、技术设计和进度文档。
|
||||
生产入口已接通完整来源分页、叶子持久化、持续处理至完成、活动页主动暂停/继续及批次详情;设置可调页大小、批次正文/消息上限、单次响应容量、两个模型超时和共享并发。实际合同及限制见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界)。真实 DeepSeek 验证已覆盖一个完整的 27 条消息会话;较早的 129 条消息实验仍未完成。两次实验共 5 次付费请求,详情见进度。原始 AbortError 原因尚未由日志确定。
|
||||
|
||||
实施状态以代码和[实施进度](./progress.md)为准。逻辑待定项见[逻辑设计](./logic-design.md),架构边界见[技术设计](./technical-design.md)。现有心跳行为见[智能心跳](../smart-heartbeat/README.md),原型范围见[Story Graph 演示](../story-graph/README.md)。
|
||||
|
||||
2026-09-28:Agent 可按需通过三个只读 MCP 工具读取决定、演变和来源;监督者关闭后不暴露工具并拒绝旧会话调用。本地与远程接线及验证边界见[实施进度](./progress.md#2026-09-28-story-graph-只读工具)。当前 memory 读取、精确 scope 增量进度和候选身份保留;跨 scope 叶子复用、读取入口切换及 `as_of` 仍属后续能力。
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
# 监督者逻辑设计
|
||||
|
||||
日期:2026-09-23。状态:部分规则已接入产品,验证范围见[实施进度](./progress.md)。图中的连线表示逻辑依赖与数据流,不代表所有目标接口均已实现。
|
||||
|
||||
本文承接[监督者产品设计](./supervisor-prd.md)、[故事线图谱设计](../story-graph/prd.md)和[User Stories](./user-stories.md),定义触发、整理、展示与用户后续操作之间的关系。既有会话监督的证据和介入边界继续由[会话监督 PRD](./prd.md)约束。
|
||||
|
||||
## 整体逻辑架构
|
||||
|
||||
报告、监督叶子与导航整理共用监督专用 FIFO 并发池,普通聊天不入池;监督服务外层 run 仍串行。报告在排队前冻结超时,获槽后领取,实际模型请求前校验并刷新租约;报告提交或无变化后先释放槽位,再执行监督。监督超时在 run 创建时保存,节点获槽并解析 Runtime 后计时。阶段超时、租约、传输时限、后台检查与活动轮询各自独立,取值及生效规则见[当前设置合同](./review-scheduling-design.md#当前设置合同)。
|
||||
|
||||
完整回顾的生产接线已增加分页来源清单、逐批输出/offset 事务和导航节点。用户主动暂停后显示 `paused`,模型或保存失败显示 `failed`;两者均保留此前成功批次,可继续同一 run。回顾不按累计运行时长自动暂停。存在未读来源、覆盖空洞或未成功导航时不能标 `completed`。手动位置与自动 checkpoint 分开,自动位置只在最终发布事务推进。来源变更目前要求重新回顾,模型配置跨重启固定等差异见[生产接线与剩余边界](./review-scheduling-design.md#0-生产接线与剩余边界)。真实模型验证覆盖 27 条消息的完整会话,较大样本失败单独记录。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Entry["入口与触发"]
|
||||
App["监督者应用"]
|
||||
Scope["关注范围、时间区间与整理策略"]
|
||||
Manual["用户主动回顾"]
|
||||
Heartbeat["智能心跳:按计划唤醒"]
|
||||
end
|
||||
|
||||
subgraph Sources["已有内容与状态"]
|
||||
Conversation["会话记录"]
|
||||
Notes["魔法笔记"]
|
||||
Work["任务状态与执行记录"]
|
||||
Knowledge["现有知识库资料与检索引用"]
|
||||
end
|
||||
|
||||
subgraph Supervisor["监督者 Agent"]
|
||||
Collect["收集范围内的有界可见证据"]
|
||||
Review["理解进展与变化,判断是否需要整理"]
|
||||
Result["带来源的整理结果"]
|
||||
Quiet["记录检查结果,保持安静"]
|
||||
end
|
||||
|
||||
subgraph Outputs["结果与展示"]
|
||||
Recap["工作回顾:阶段决定、变化、未解决事项"]
|
||||
Feedback["监督反馈:聊天右侧展示"]
|
||||
Graph["故事线图谱:事件、实体、关系与演变"]
|
||||
end
|
||||
|
||||
subgraph Actions["用户后续操作"]
|
||||
Verify["核对来源、确认或纠正整理结果"]
|
||||
Continue["预览所选上下文后继续讨论"]
|
||||
Save["预览并确认新建或补充本地知识条目"]
|
||||
end
|
||||
|
||||
App --> Scope
|
||||
App --> Manual
|
||||
Scope --> Collect
|
||||
Manual --> Collect
|
||||
Heartbeat --> Collect
|
||||
Conversation --> Collect
|
||||
Notes --> Collect
|
||||
Work --> Collect
|
||||
Knowledge --> Collect
|
||||
Collect --> Review
|
||||
Review -->|需要整理| Result
|
||||
Review -->|无有效新变化| Quiet
|
||||
Result --> Recap
|
||||
Result --> Feedback
|
||||
Result --> Graph
|
||||
Recap -->|定位阶段| Graph
|
||||
Feedback -->|定位相关对象| Graph
|
||||
Recap --> Verify
|
||||
Graph --> Verify
|
||||
Verify -->|更新确认状态或用户修订| Result
|
||||
Graph --> Continue
|
||||
Continue --> Conversation
|
||||
Graph --> Save
|
||||
Save -->|仅写入可写的本地条目| Knowledge
|
||||
```
|
||||
|
||||
心跳负责唤醒,监督者负责判断;模型不因被称为 Agent 而自动获得工具权限。收集证据由应用按所选范围提供,现有心跳不具备的知识库或笔记接入属于后续工作。
|
||||
|
||||
工作回顾、监督反馈和图谱引用同一次整理的结论与来源。展示可以裁剪为阶段摘要、会话卡片或图形,但不能分别维护相互矛盾的结论。一次整理可以没有图谱变更,也可以有整理结果但无需主动通知。
|
||||
|
||||
结果定位以结果 ID 为准。历史选择不能回退到其他项目的最新图谱;侧栏需同时匹配目标来源和范围,没有匹配结果时显示空态。固定目标失效时保留目标并显示读取错误,用户可取消固定。跨 run 仅允许引用同 scope 的 Main 候选实体,模型局部 ID 和同名均不构成身份。模型候选引用与存储身份分离;历史文本主键按 Main 精确映射保留,格式错误的旧模型身份字段只产生新实体,不猜测合并对象。每次结果保留当次描述,自动更新不得改写已确认字段或其他结果;人工修改仅更新当前对象与明确选定的结果。
|
||||
|
||||
应用维护来源正文;监督者维护其产生的阶段总结、事件关联、实体描述与关系。图谱是这些对象的视图。图中“纠正”只更新整理内容或关系,修改来源正文仍需进入对应应用的编辑流程。
|
||||
|
||||
## 图谱对象关系
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Story["故事线"]
|
||||
Event["时间事件"]
|
||||
Change["事件与实体的关联:提出、补充、验证、修订"]
|
||||
Entity["知识实体"]
|
||||
Relation["知识关系:类型、理由、确认状态"]
|
||||
Summary["阶段总结"]
|
||||
Interval["时间区间与来源范围"]
|
||||
Reference["来源引用:所属对象、位置、可用版本信息"]
|
||||
Original["原内容:会话、笔记、本地资料或已保存外部片段"]
|
||||
|
||||
Story -->|组织| Event
|
||||
Story -->|关联,可跨故事共享| Entity
|
||||
Story -->|按阶段回顾| Summary
|
||||
Summary -->|限定覆盖范围| Interval
|
||||
Summary -->|归纳范围内事件| Event
|
||||
Summary -->|概括当时的认识| Entity
|
||||
Event -->|一个事件可影响多个实体| Change
|
||||
Change -->|一个实体可经历多个事件| Entity
|
||||
Entity -->|关系起点| Relation
|
||||
Relation -->|关系终点| Entity
|
||||
Event -->|原始依据| Reference
|
||||
Change -->|变化依据| Reference
|
||||
Relation -->|关联依据| Reference
|
||||
Entity -->|支撑资料| Reference
|
||||
Summary -->|结论依据| Reference
|
||||
Reference -->|定位,不复制正文| Original
|
||||
```
|
||||
|
||||
这里的关联对象用于说明关系自身也有类型和依据,不指定存储表。知识实体与来源为多对多关系;实体可以被多个故事引用,不能仅凭同名就合并。来源可用的版本信息沿用原内容能力,不要求为此复制完整来源历史。
|
||||
|
||||
外圈时间轴呈现事件及阶段标记,内部平铺实体;时间到实体的连线对应事件关联,实体间连线对应知识关系。时间轴上的同日聚合点只负责显示密集事件,不形成新的知识实体。
|
||||
|
||||
## 应用启停与执行
|
||||
|
||||
以下规则对应 FR-S1、FR-S4、FR-S5 和 US-S14,适用于监督者与智能心跳共用的 `heartbeat` 应用。
|
||||
|
||||
- 新安装及设置缺失 `heartbeatEnabled` 时按 `false` 处理;已明确保存的 `true`、`false` 均保留。
|
||||
- 关闭应用后不领取新的定时心跳运行,拒绝新的手动心跳和监督回顾请求,也不启动心跳完成后的自动监督回顾。侧栏不挂载监督面板,不发起该面板的查询。
|
||||
- 启用应用后,只有已保存且启用的心跳计划才参与自动调度;没有此类计划时不产生自动工作。启动或启用应用不自动创建、补种或启用计划,用户仍可主动发起回顾。
|
||||
- 关闭前已经领取并在执行的运行可以完成并保存结果,关闭应用不强制取消;若完成时应用已关闭,不再追加自动监督回顾。已有计划的 `enabled`、历史结果及导航偏好保留。
|
||||
- 应用开关不影响普通任务的现有定时调度。取消常驻只改变导航入口,不改变应用启用或计划状态。
|
||||
|
||||
## 状态与处理规则
|
||||
|
||||
下表包含目标规则;当前活动执行状态以本节后的“活动记录”为准,取消已接入,部分完成状态尚未接入。
|
||||
|
||||
| 规则 | 条件或状态 | 处理与用户可见结果 | 需求与场景 |
|
||||
| --- | --- | --- | --- |
|
||||
| L1 范围 | 手动或定时触发 | 按本次所选范围、时间和输入预算收集证据;启用应用不扩大来源范围 | FR-S3、S4;US-S01 |
|
||||
| L2 检查 | 没有有效新变化 | 记录检查结果,保留旧回顾,不重复通知;用户主动查看时仍可读取原结果 | FR-S4、S5;US-S03 |
|
||||
| L3 整理 | 产生新的阶段认识或关联 | 回顾、反馈和图谱引用同一整理结果;是否主动反馈按反馈策略决定 | FR-S2、S5;US-S02、S04 |
|
||||
| L4 失败 | 取消、失败或部分来源不可用 | 保留上次成功结果;显示当前运行状态、已覆盖与遗漏范围,支持重试;部分结果不标为完整 | FR-S6;US-S12 |
|
||||
| L5 纠正 | 用户修改描述或确认关系 | 后续自动整理提出补充或修订建议,不静默覆盖用户内容;移除关系可撤销且不删除来源 | FR-S6、S9、FR-G4;US-S06、S10 |
|
||||
| L6 历史 | 选择过去时刻或时间区间 | 使用当时已有的事件与认识;来源旧版不可恢复时提示,不以当前原文冒充旧版 | FR-S9、FR-G3、G5;US-S05、S07、S13 |
|
||||
| L7 目标 | 侧栏跟随或固定 | 跟随时显示当前会话,固定时保持所选目标;切换聊天不改变固定目标 | FR-S5;US-S25 |
|
||||
| L8 沉淀 | 用户选择写入知识库 | 先预览并确认,再新建或补充本地条目;已有条目提供打开入口,外部引用保持只读 | FR-S7、S8;US-S08、S09 |
|
||||
| L9 继续 | 用户选择继续讨论 | 预览并确认上下文后进入会话;新内容在后续整理时纳入,不立即作为已确认知识 | FR-S3、S5;US-S11 |
|
||||
| L10 展示 | 改变布局或输入方式 | 保留范围、回放时刻及选择;图形和文字列表访问相同对象与来源 | FR-G2、G5、G6;US-S15 |
|
||||
|
||||
整理运行状态与内容确认状态分开:一次运行成功不意味着自动归纳已经得到用户确认。手动重试创建新运行;心跳继续沿用原有运行领取和重试规则。
|
||||
|
||||
## 活动记录
|
||||
|
||||
对应 FR-S10、US-S26。监督运行在收集证据之前保存 `running`,结果事务成功后转为 `completed`;收集、模型、校验或保存失败转为 `failed`,保留错误及结束时间。重开应用将没有完成的监督运行标为失败,旧成功结果保持可读。
|
||||
|
||||
心跳活动按真实运行 ID 关联下游监督。报告阶段使用 `claimed / completed / failed / skipped / no_change`,活动将 `claimed` 显示为运行中。报告已结束而投影尚在执行时,整条活动仍为运行中;任一阶段失败,整条活动显示失败。两阶段都没有新输入时显示“无变化(未调用模型)”;任一阶段保存新结果且没有失败时显示完成。没有下游运行记录时明确显示“无执行记录”。
|
||||
|
||||
旧监督成功记录继续显示;旧心跳与监督之间未保存关联时分别呈现,不用时间接近猜测关系。旧心跳未保存的执行范围显示不可用,不从当前计划反推。此前未保存的手动运行与失败无法恢复。心跳报告沿用计划的历史保留和删除规则;监督结果独立保留,原心跳被清理后仍可读取监督记录。活动不是永久审计库。
|
||||
|
||||
同一时刻只允许一个监督回顾执行,包括停止清理阶段;不同 scope 也共用此限制。手动新建或继续遇到占用时立即提示,不排队;自动回顾等待占用释放,再检查未完成运行和增量来源。暂停或失败后可以新建回顾,也可继续有效的原 run。
|
||||
|
||||
取消立即持久化 `cancelled`,但执行与清理尚未停止时 UI 显示“正在取消”,此时仍不能开始另一个回顾。停止后显示“已取消”,保留上次成功结果和本次已保存批次,不发布部分结果、不推进自动 checkpoint。已取消运行不能手动继续,自动检查也不恢复它;后续自动检查可新建 run 处理尚未提交的来源。关联心跳活动显示监督取消状态,已保存的报告保持原状态。界面入口及提示关闭规则见[UI 设计](./ui-design.md#活动)。当前没有 `partial` 状态;实体和关系修改仍没有按时间追加的人工修改记录。
|
||||
|
||||
收集、叶子整理、导航合并和发布分别展示。阶段推进只依据服务记录,最终完成只依据结果事务;“六批已保存、剩余零来源”仍可处于合并失败或发布失败。继续保留成功叶子和导航节点,缺失阶段不得靠错误文本或来源数猜测。导航摘要允许省略事实数组,叶子缺少事实数组仍失败;导航不得生成替代叶子事实的非空数组。阶段及输出合同见[生产调度](./review-scheduling-design.md#已接入的调度与存储)。
|
||||
|
||||
### 自动增量与手动重看
|
||||
|
||||
自动心跳报告和下游监督分别记录已成功处理的来源版本及正文位置。首次自动运行只读取计划范围和回顾窗口内的来源;后续运行在同样边界内处理新增、修订或上次预算未覆盖的片段。项目列表顺序不改变自动进度;不同项目集合和 Global 分开处理。失败、未送入模型或结果事务未提交的输入保留待处理状态。
|
||||
|
||||
两个阶段都在调用模型前检查输入。没有待处理来源时记录 `no_change`,保留旧报告和图谱,不产生新结果或重复事件。心跳报告无变化仍进入受应用开关控制的下游检查,允许监督阶段重试之前的失败。已确认记忆、已有实体说明及最近摘要仅作为背景,不单独触发自动模型调用。
|
||||
|
||||
工作回顾的手动操作重新整理所选区间,沿用有界历史收集,不读取或推进自动进度。自动监督计划列表中的“立即运行”重新生成心跳报告,其下游监督仍按自动进度处理。删除报告或计划不重置来源进度;重看历史、删除报告和重建图谱是不同操作。当前没有清空或重建图谱功能。
|
||||
|
||||
回顾默认增量,重看历史改为显式重新整理。
|
||||
|
||||
### 心跳职责与介入(目标设计)
|
||||
|
||||
对应 FR-S4、FR-S12,US-S27、US-S32、US-S33。本节为已确认的目标规则,尚未实现;当前代码仍先生成心跳报告再执行监督整理,实际进度以[实施进度](./progress.md)为准。“回顾”默认增量,重看历史改为显式“重新整理”。
|
||||
|
||||
### 心跳职责与介入(目标设计)
|
||||
|
||||
对应 FR-S4、FR-S12,US-S27、US-S32、US-S33。本节为已确认的目标规则,尚未实现;当前代码仍先生成心跳报告再执行监督整理,实际进度以[实施进度](./progress.md)为准。
|
||||
|
||||
回顾整理只有一套流程,手动和自动共用:确定范围 → 分页采集来源 → 分批整理 → 汇总 → 发布到 Story Graph。心跳不参与整理本身,只在其外增加两件事:
|
||||
|
||||
1. **定时触发**:计划到期时代替用户发起一次增量回顾。
|
||||
2. **介入**:回顾发布后,按计划配置的介入方式处理本次图谱变化。
|
||||
|
||||
```text
|
||||
触发(点击“回顾” / 计划到期)
|
||||
↓
|
||||
变化检查:比较来源版本与已处理位置,不调用模型
|
||||
↓ 无变化 → 记录“无新变化”,结束
|
||||
↓ 有变化
|
||||
增量整理:只处理新增、修订和未完成片段,关联已有实体(唯一读取原文的阶段)
|
||||
↓
|
||||
发布:写入 Story Graph,推进增量进度
|
||||
↓ 仅心跳触发
|
||||
介入:读取本次发布的图谱变化,按计划配置生成建议
|
||||
```
|
||||
|
||||
| 规则 | 内容 |
|
||||
| --- | --- |
|
||||
| H1 原文只读一次 | 同一来源版本在一次触发中只由整理阶段读取;介入阶段的输入是本次图谱变化及其来源引用,不重新读取原文 |
|
||||
| H2 不再生成独立报告 | 取消“心跳报告”阶段及其独立增量进度;旧报告只读保留,不再新建 |
|
||||
| H3 手动默认增量 | “回顾”按钮与心跳整理行为一致,跳过已处理且未变化的来源;手动触发不生成建议 |
|
||||
重新整理放在更多中,确认后重新分析所选时间区间;不推进也不重置自动进度,不覆盖用户确认内容 |“重新整理”放在“更多”中,确认后重新分析所选时间区间;不推进也不重置自动进度,不覆盖用户确认内容 |
|
||||
| H5 介入方式 | 计划字段:`仅更新记忆`、`生成建议`(新计划默认)、`主动介入`(会话级实时,暂不实现,不显示为可选项) |
|
||||
| H6 建议来源 | 候选仅来自本次图谱变化:批次中的未解决事项、无明确取代关系的分歧(contrasts 关系)、由至少三处不同原始来源支持的未确认实体(候选约定)、被修订的实体(revised 变化)。长期未推进的判定尚未实现 |
|
||||
| H7 建议成本 | 先按规则挑选候选,不调用模型;存在候选时才发起一次输入有界的措辞调用,输入仅为候选及其引用 |
|
||||
| H8 建议可信 | 每条建议显示依据并可打开来源;用户忽略后,只有出现新证据才再次提出;确认候选约定或偏好后才作为长期背景 |
|
||||
| H9 频率与成本 | 提高频率只增加变化检查次数;模型调用次数由实际变化决定:无变化 0 次,有变化无候选仅整理,有候选再加一次建议调用 |
|
||||
| H10 失败隔离 | 建议阶段失败不回滚已发布的图谱与增量进度;活动显示“已更新,建议生成失败”,可单独重试建议 |
|
||||
|
||||
介入产物替代旧心跳的报告、记忆建议与行动建议:未解决事项可转任务,候选约定确认后进入长期背景,旧的报告阅读由按时间查看图谱变化替代。已有计划的范围、频率和启停状态保留,迁移后介入方式默认为“生成建议”;已确认记忆与待处理建议保留并逐步迁入,不补造来源。
|
||||
|
||||
计划 Modal 的新建草稿不产生持久化或执行副作用,显式保存时创建并启用;编辑沿用已有 enabled。保存失败保留草稿,提交期间禁止关闭,未保存关闭须确认。活动筛选只改变读取范围,不触发执行;同名同范围计划仍按 configId 区分,筛选在分页前应用,结果跳转继续按 resultId 定位。
|
||||
|
||||
规则冲突时,先满足所选范围与既有观察边界,再保留用户修订和来源事实,最后应用自动归纳和展示策略。新增证据与已确认结论冲突时显示待核对差异,不能自动断言旧观点失效。
|
||||
|
||||
## 完整性与待定项
|
||||
|
||||
FR-S11、US-S28 至 US-S31 的规则由[时态 Story Graph MCP 设计](./story-graph-mcp-design.md)统一定义:时间/确认与有效性见第 3 节,读取范围与分页见第 4 节,应用关闭与 Runtime 接入见第 6 节,跨 scope 复用与记忆过渡见第 7、8 节。D1 读取已接入,后两项仍待实现;Global/项目独立 checkpoint 和同 scope 候选身份保留。MCP 不接受 `as_of`,较晚生成的结果也不自动使旧决定失效。
|
||||
|
||||
本设计覆盖触发、证据输入、静默检查、正常整理、取消与失败、跨视图共享结果、用户纠正、知识库写入及历史回放。它补充产品逻辑,不代表接口、持久化或迁移方案已经完成。
|
||||
|
||||
仍待产品决策:事件触发与阶段识别条件、反馈频率与静默判定、实体合并与拆分的交互、时间轴比例和集中度含义。应用启停按[应用启停与执行](#应用启停与执行)处理;既有入口偏好、计划与运行记录的保留要求见 FR-S1、S2 和 US-S14。
|
||||
|
||||
来源旧版本和完整历史回放仍受原应用的保存能力限制。增量进度与同 scope 实体 ID 复用的实现见[技术设计](./technical-design.md#自动增量收集)。
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
## 1. 背景
|
||||
|
||||
本 PRD 继续定义会话监督能力。统一应用“监督者”提供工作回顾、故事线图谱、自动监督、活动记录与设置五个页签;实际能力及执行记录要求见[监督者应用产品设计](./supervisor-prd.md)。本文其余执行观察与介入目标仍按各节范围实施。
|
||||
|
||||
GoodBuddy 的魔法笔记已经提供一种有价值的交互:用户持续写作,AI 在右侧以长评、建议和
|
||||
警告进行评论,用户可以选择综合、扩展、润色、质疑或发散方向。该能力是内容分析,不是
|
||||
会话监督:
|
||||
@@ -252,7 +254,7 @@ type SupervisorDecision = {
|
||||
|
||||
### 13.1 工作栏监督栏目评论流
|
||||
|
||||
监督是助手工作栏中固定且始终可访问的栏目,不是只在聊天页面出现的附属面板。栏目默认
|
||||
监督应用启用后,助手工作栏提供监督栏目;关闭时不挂载或查询监督面板,见[应用启停与执行](./logic-design.md#应用启停与执行)。栏目默认
|
||||
跟随当前会话,用户也可以固定到其他普通 Conversation、Task 或 Experiment。
|
||||
切换页面不会改变固定目标;目标失效时必须显示修复状态,不能静默回到当前会话。
|
||||
|
||||
|
||||
@@ -0,0 +1,764 @@
|
||||
# 监督者实施进度
|
||||
|
||||
日期:2026-09-30。
|
||||
|
||||
当前记录以已验证生产行为为准。监督者尚未覆盖全部 user stories。
|
||||
|
||||
## 2026-10-02 长会话验收与 Agent 自主调用图谱
|
||||
|
||||
### 长会话验收
|
||||
|
||||
`npx jiti scripts/long-session-acceptance.mjs <live.sqlite> .env.deepseek.local <dir> <会话前缀> [failAfter]`:对本机数据库做在线备份,只在副本里归档其他会话、隐藏任务,使回顾只读取一条真实会话;首次运行在第 N 次提取时注入一次提供方失败(不消耗付费调用),再继续同一回顾;完成后用保存的批次片段逐条重建每条消息原文;最后再跑一次心跳和一次默认手动回顾,要求零模型调用。原库不修改。
|
||||
|
||||
第一次跑最长会话(389 条消息、约 21.7 万码点、16 天)时,继续回顾三次都失败:两次是模型返回无效 JSON,一次是引用了范围外的来源。原因是一个批次的模型输出不可用时整次回顾立即失败,只能靠反复手动“继续”碰运气。改动:
|
||||
|
||||
- 叶子批次输出不可用(无效 JSON、结构不符、引用不存在的来源或实体)时,在同一段待处理正文上缩小到 1/2、再到 1/4(不少于 500 字符)重试,最多 2 次;缩小只是先处理前一部分,其余正文留在待处理里,不跳过、不截断。
|
||||
- 导航合并输出不可用时,用相同输入再问 1 次。
|
||||
- 提供方错误、超时、取消和响应超出容量不重试,仍按原来的方式失败并保留已保存批次。
|
||||
|
||||
改后结果(DeepSeek `deepseek-flash`):
|
||||
|
||||
| 会话 | 首次运行 | 继续 | 覆盖 | 无变化重跑 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 97 条、6.0 万码点、4.9 天 | 第 4 次提取注入失败,已存 3 批 | 1 次完成,16 次调用 | 97/97 条精确重建,0 缺口、0 重叠 | 心跳、手动均 `no_change`,0 调用 |
|
||||
| 389 条、21.7 万码点、16.2 天 | 第 7 次提取注入失败,已存 5 批 | 1 次完成,61 次调用(30 提取、31 合并),约 32.5 分钟 | 389/389 条精确重建,0 缺口、0 重叠 | 心跳、手动均 `no_change`,0 调用 |
|
||||
|
||||
最长会话共 67 次调用、约 52.7 万输入和 54.8 万输出 Token,发布 237 个事件;失败前保存的批次在继续后全部保留未重算。改动前的同一会话运行(三次继续均失败)记录在 `sl9/long2`,改后中间版本(仅叶子重试,合并失败两次后第三次继续完成)在 `sl9/long3`。
|
||||
|
||||
边界:完整覆盖只说明每段原文都进入了成功批次,不代表模型没有遗漏或误读;验收只用了两条会话,没有测多项目全局回顾。
|
||||
|
||||
### Agent 自主调用
|
||||
|
||||
决定:普通对话不自动注入故事和经验,由 Agent 按需调用 Story Graph 工具。为此:
|
||||
|
||||
- 工具说明写明何时应主动调用(继续之前的工作、询问为什么这样决定、在可能有既有约束的领域开始新任务)、何时不调用(闲聊、与项目历史无关的通用问题),用 2~4 个关键词查询,查几次找不到就说明并按当前上下文回答。
|
||||
- 搜索改为按词匹配:空格和标点分词,中文按相邻两字切分;一条记录命中整句或至少一半的词即返回,按命中程度排序,故事和经验在同分时靠前。此前 Agent 用“监督者回顾调度”这类自然短语搜索,因为要求整句出现而 0 结果。
|
||||
- 搜索结果的预览先显示名称、标题、描述等可读字段,不再以 ID 开头。
|
||||
|
||||
验证(`scripts/story-graph-agent-deepseek.mjs`,DeepSeek 函数调用,只给生产工具定义,不提示要用工具):
|
||||
|
||||
| 轮次 | 判断正确 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 改前 | 4/5 | 通用编程题也去搜索;继续工作类问题搜了 9~22 次仍找不到内容 |
|
||||
| 改后 | 5/5 | 三个项目历史问题都主动调用;数组去重和问候都不调用 |
|
||||
|
||||
改后“继续回顾调度,按最后定的方案”能找到并引用 2026-10-02 的故事线模型设计;“导出功能有什么约束”能列出经验和来源。“为什么取消 300 秒自动暂停”仍在搜了十几次后达到对话轮数上限:这次取消发生在数据副本最后一次回顾之后,图谱里确实还没有这条决定,Agent 没能及时停止并说明“没有记录”。
|
||||
|
||||
## 2026-10-02 故事与经验质量抽查
|
||||
|
||||
数据:同一份 437 个事件的数据副本上已有的故事归属运行(`sl3/off1`、`sl3/cross2`)和经验抽取运行(`sl4/run1`、`sl4/run2`),以及本次改提示词后的新运行(`sl8/off3`,DeepSeek `deepseek-flash`,12 次调用,约 6.2 分钟)。一致性用“两次运行中同属一个功能的事件对”的重合度(Jaccard)衡量。
|
||||
|
||||
| 检查 | 结果 |
|
||||
| --- | --- |
|
||||
| 故事归属一致性(改前,off1 对 cross2) | 0.51 |
|
||||
| 主要不一致来源 | “提交与发布”吸收了 87 个事件,其中只有 40% 在另一次运行里属于发布;其余是各功能自己的提交、推送和验证 |
|
||||
| 改动 | 提示词明确:某个领域的提交、推送、测试和验证属于该领域;发布功能只放版本号、发布说明、标签、打包和发布 |
|
||||
| 改后(off3) | 发布功能 42 个事件;与 cross2 的一致性 0.74;暂不归类 55 个;再次整理 0 次调用 |
|
||||
| 暂不归类的事件 | 抽查 cross2 的 49 个:几乎都是问候、知识截止时间、SSL/认证报错、寿命和睡眠问答、案例咨询、权限挂载等一次性问题,归为“暂不归类”是合理的 |
|
||||
| 经验跨运行一致性 | run2 的 38 条里 23 条能在 run1 中找到形成依据重合(Jaccard ≥ 0.3)的对应经验,表述基本一致 |
|
||||
| 经验依据抽查(10 条) | 形成依据基本都对得上经验内容;后续“应用”较弱,抽到的 5 条应用里约 2 条和经验关系不大(例如“需求有歧义先确认”被记到“询问微信附件导入能力”) |
|
||||
|
||||
结论:功能边界在明确“提交归领域”后明显稳定;仍有约四分之一的事件对在两次运行间归属不同,主要发生在界面、笔记、帮助提示等相邻功能之间,可以通过手动合并修正。经验的形成依据可信,应用记录原先过宽。
|
||||
|
||||
随后收紧经验应用:模型给出的应用事件必须与该经验的某个形成事件共享实体,否则丢弃(同批其他内容保留)。在 off3 的故事上重新抽取(6 次调用,约 3.8 分钟):45 条经验,应用记录从此前的 53 条降到 6 条,抽查 6 条全部与经验内容相符;依据无来源 0、应用早于形成 0、再次抽取 0 次调用。代价是跨故事的经验连线变少(3 条),时间螺旋和“可用经验”建议能显示的跨故事复用也随之减少。`supervision-experiences.test.ts` 新增“无关知识的事件不记为应用”。
|
||||
|
||||
## 2026-10-02 时间螺旋显示经验节点
|
||||
|
||||
对应 [3D 概念](../story-graph/3d-concept.md) 中的桶外经验节点。
|
||||
|
||||
实现:
|
||||
|
||||
- `experienceLinks()`(`story-graph-3d-model.ts`):经验在当前层中,从最早形成依据所在的木片,连到之后第一个在另一片木片上的应用。只在同一片木片里用过的、应用不晚于形成的、依据不在当前层的经验不画。每层最多 6 条,按应用到的不同木片数排序。
|
||||
- 时间螺旋:经验画成桶外的菱形,高度在形成与应用两个时间之间,角度在两片木片之间;两段向外弯的弧线分别连到形成木片和应用木片的对应高度。只有选中的经验在画布上显示名称。提供“跨木片的经验”键盘入口(现为工具栏选择框);点击菱形或选择经验打开详情。
|
||||
|
||||
验证:
|
||||
|
||||
- `story-graph-3d-model.test.ts` 新增 1 项(只连到跨木片的后续应用、只出现一次、层外依据与倒序应用排除、上限)。相关组件测试 3 个文件 32 项通过,Web 类型检查、相关 ESLint 通过。
|
||||
- Electron 实测(生产组件,第 5 步数据副本的 46 个故事、38 条经验;1440 浅色、390 深色):无报错、无横向溢出;根层 3 条、goodbuddy 层 6 条跨木片经验;从列表选中和在画布上点中菱形都能打开经验。
|
||||
- 38 条经验中,在当前数据里能形成跨木片连线的只是少数,其余只在同一故事中形成或应用,不在螺旋上显示,仍可在“经验”页签中查看。
|
||||
|
||||
## 2026-10-02 故事线模型第 5 步:工作回顾按故事组织
|
||||
|
||||
对应[故事线模型改造设计](./storyline-model-design.md)第 5 步“工作回顾页”。
|
||||
|
||||
实现:
|
||||
|
||||
- 工作回顾在所选回顾结果的摘要之上新增“这段时间的故事”,按该结果的范围和时间区间读取已保存的故事和经验,不调用模型(`supervision-story-digest.ts`、`SupervisionStoryDigest.tsx`)。
|
||||
- 分组:推进的故事(区间内有事件、区间前也有)、新出现的故事(第一个事件在区间内)、已结束的故事(结束依据事件在区间内)、形成或用上的经验(最早形成依据在区间内,或区间内有应用)、这段时间没有新进展(进行中、区间前有事件、区间内没有;跨项目故事和已结束的故事不算)。功能把子线索的事件计入自己,子线索不重复列出。
|
||||
- 每组按事件数排序,默认显示 6 条,可展开其余;每条显示项目、事件数和最近一个事件。点击任一故事或经验打开图谱,并选中该对象。原有的工作总结、本次变化、未解决事项保留在下方,作为阅读入口。
|
||||
|
||||
验证:
|
||||
|
||||
- `supervision-story-digest.test.ts` 2 项(分组规则、子线索计入功能、跨项目与已结束故事不算无进展、经验形成与应用区分);`SupervisorWorkspace.test.tsx` 新增 1 项(按所选结果范围读取故事、各分组内容、点击进入图谱)。相关组件测试 4 个文件 73 项通过,Web 类型检查、相关 ESLint 通过。
|
||||
- Electron 实测(生产组件,第 5 步数据副本导出的 46 个故事、38 条经验和一条 7 天的全局回顾结果;1440 浅色、1024 深色、390 深色):无报错、无横向溢出、条目无文本溢出;宽屏 3 列、窄屏 1 列;展开后从 13 条到 26 条;点击条目切到图谱。该副本的数据从 09-19 开始,这条结果的区间正好覆盖全部数据,因此只出现“新出现的故事 19”“已结束的故事 1”“形成或用上的经验 33”,“推进的故事”和“没有新进展”两组没有在真实数据中出现,只由单元测试覆盖。
|
||||
|
||||
## 2026-10-02 故事线模型第 5 步:心跳故事建议与 MCP 故事、经验查询
|
||||
|
||||
对应[故事线模型改造设计](./storyline-model-design.md)第 5 步。工作回顾页按故事组织见上一节。
|
||||
|
||||
实现:
|
||||
|
||||
- 心跳建议新增两类,候选只由规则选出,不调用模型:
|
||||
- **暂无进展**(`stalled`):本次范围内进行中的功能或子线索(至少 3 个事件),最近事件距今达到“故事多少天没有新进展时提醒”(设置,默认 14 天)。只提示查看,不改变故事状态,不推断结束。每次心跳最多 3 条,按事件数从多到少;已待处理或依据未变的不计入名额,其余留到后续心跳。接受后转为暂停的后续任务。
|
||||
- **可用经验**(`experience`):本次回顾归入某故事的事件,与某条经验的形成事件共享实体,而该经验尚未在这个故事中形成或应用。出现在 5 个以上故事中的实体(如产品名)不作为依据。每条经验只指向匹配事件最多的一个故事,每次心跳最多 3 条,按依据数排序。接受即标记已参考。
|
||||
- 两类候选排在未决事项之前,不会被每次 20 条的上限挤掉;与其他建议一起只用一次模型调用措辞,输入不含来源原文和定位。
|
||||
- 建议记录 `story_id`、`experience_id`;“在图谱中查看”直接打开对应故事或经验。Schema 56 重建 `supervision_suggestions` 以扩展类型约束,已有行原样复制。
|
||||
- Story Graph MCP:`story_graph_search` 支持 `story`、`experience` 两种对象;`story_graph_get_context` 对故事返回其事件(功能包含子线索的事件),对经验返回形成与应用事件,再通过事件读取来源。经验带 `automatic` 或 `user_edited` 标记,工具说明要求把经验作为参考而非规则。查询范围规则不变:项目范围只返回本项目的故事,以及在本项目形成或应用过的经验。
|
||||
|
||||
验证:
|
||||
|
||||
- 新增 `supervision-story-suggestions.test.ts` 5 项:暂无进展的阈值、不改故事状态、不重复、转任务;每次最多 3 条且按事件数排序、其余在后续心跳补上;经验跨故事建议只出现一次、不建议给形成它的故事;MCP 故事与经验的搜索、上下文及项目范围隔离;Schema 55 升级到 56 保留已有建议。新增 `SupervisionSuggestionsPanel.test.tsx`:新类型标签与按故事、经验打开图谱。监督相关主进程 11 个文件 73 项、IPC 定向 21 项、组件 4 个文件 72 项通过;Node/Web/Agent 类型检查、相关 ESLint 通过。未运行全量测试。
|
||||
- 真实数据(第 4 步修正后运行的数据副本,`npx jiti scripts/story-suggestions-deepseek.mjs <db> .env.deepseek.local <dir> 7`,DeepSeek `deepseek-flash`;数据只有约 13 天,阈值用 7 天,“现在”取最后事件时间):
|
||||
|
||||
| 运行 | 规则候选 | 调用 | 结果 |
|
||||
| --- | --- | --- | --- |
|
||||
| 第 1 轮(故事候选排在未决事项之后) | 20 条未决事项,0 条故事候选 | 2 | 故事候选被 20 条上限挤掉,因此改为故事候选优先 |
|
||||
| 第 2 轮(暂无进展不限量) | 20 条暂无进展 | 2 | 13 天的数据里大量故事“停滞”,一次挤满上限,因此加每次 3 条的限制 |
|
||||
| 第 3 轮(经验按每个实体匹配) | 3 条暂无进展、17 条可用经验 | 2 | 同一经验指向 5~8 个故事,大多经由“监督者”这类通用实体匹配,因此改为排除通用实体、每条经验只指向一个故事、每次最多 3 条 |
|
||||
| 第 4 轮(最终规则) | 3 条暂无进展、3 条可用经验、14 条未决事项 | 2 | 每次请求 6 条故事候选,都不含来源定位;建议都带故事,经验建议都带经验 |
|
||||
|
||||
- 最终规则下的可用经验例如“长任务超时或中断先查取数与分批根因,不以延长超时或调并发修复”指向“回顾算法与并发设置”、“提交只纳入本次相关改动”指向“自动监督开关与设置”。是否真正适用未人工逐条核对。
|
||||
- 再次生成时没有重复任何待处理建议;只补上了上一次被 20 条上限挤掉的未决事项(1 次调用)。
|
||||
- MCP:全局范围搜到 6 个相关故事、9 条相关经验;一个功能的上下文返回 96 个事件(含子线索),一条经验返回 5 个依据事件。
|
||||
|
||||
边界:是否在普通对话中主动查询故事与经验,取决于 Agent 是否调用工具,未做额外注入。
|
||||
|
||||
## 2026-10-02 故事线模型第 4 步:经验抽取
|
||||
|
||||
对应[故事线模型改造设计](./storyline-model-design.md) SL-4、SL-5 和第 4 步。
|
||||
|
||||
实现:
|
||||
|
||||
- `supervision-experiences.ts`:新表 `supervision_experiences`(经验、适用条件、边界、状态、用户调整标记)、`supervision_experience_events`(`formed` 形成依据、`applied` 后续应用及结果)、`supervision_experience_seen`。Schema 55,升级后表为空,由下一次回顾从已有故事中抽取,不重读来源。
|
||||
- 在故事归属之后、同一回顾名额内执行。候选由规则选出,不调用模型:有尚未处理的决定、变化或里程碑事件,且事件数达到“故事参与经验整理的最少事件数”(设置,默认 5)的功能或子线索。小故事和只有讨论的进展直接标记已处理,不调用模型。
|
||||
- 候选故事按文本量分块,每块一次模型调用,输入是故事名、事件标题与截断描述以及已有经验,不含原文。回答中的每条形成依据必须是本次输入、且有来源的事件,否则整块不保存;早于经验形成时间的“应用”被丢弃(真实模型会出现这种引用),其余保留。同一表述的经验合并到已有经验。
|
||||
- 经验生成后即可读取,标记“自动归纳”。用户可以编辑、合并、删除,与故事调整共用撤销;编辑过的标记“已手动调整”;删除的经验不会因同一表述或相同依据再次生成。
|
||||
- 经验抽取失败不影响故事和已发布的回顾:活动记录显示“故事已整理,经验整理失败”,可与故事一起重新整理,下一次回顾也会补上。
|
||||
- 界面:图谱左侧新增“经验”页签;选中经验在右侧查看适用条件、边界、形成依据和后续应用(含所属故事与结果),依据事件可直接打开;编辑、合并、删除、撤销。设置新增“故事参与经验整理的最少事件数”。
|
||||
|
||||
验证:
|
||||
|
||||
- 新增 `supervision-experiences.test.ts` 4 项:规则候选、应用记录、无新进展零调用、小故事与纯讨论零调用、虚构依据整块拒绝、早于形成的应用被丢弃、编辑保护、删除后不再生成、合并与撤销。工作区新增经验页签测试。监督相关主进程测试 7 个文件 174 项、工作区/活动/设置组件测试 48 项通过;Node/Web 类型检查、相关 ESLint 通过。未运行全量测试。
|
||||
- 真实数据(第 2 步跨项目开启运行后的数据副本,46 个故事,`npx jiti scripts/experiences-deepseek.mjs`,DeepSeek `deepseek-flash`):
|
||||
|
||||
| 运行 | 候选故事 | 调用 / 耗时 | 经验 | 应用记录 | 跨故事经验 | 依据无来源 | 应用早于形成 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 首轮(应用早于形成时整块拒绝之前的规则) | 31 | 6 / 约 3.9 分钟 | 40 | 71 | 15 | 0 | 5 |
|
||||
| 修正后 | 31 | 6 / 约 2.7 分钟 | 38 | 53 | 9 | 0 | 0 |
|
||||
|
||||
- 两次再次抽取均为 0 次调用;所有请求都不含来源原文。
|
||||
- 修正后 38 条经验中 8 条只有 1 个形成依据。内容基本是这段时间真实形成的工作经验,例如“长材料回顾须完整分块,按成功批次保存进度并支持续作,禁止截断”“能力开关关闭时须阻断后台任务领取与执行,不能只隐藏入口”“提交只纳入本次相关改动,其他未完成改动留在工作区”。
|
||||
- 两次运行的经验条目不完全相同,未比较多次运行的一致性;也未逐条人工核对每条经验与依据是否对得上。
|
||||
|
||||
边界:经验尚未接入 MCP、心跳建议和时间螺旋视图(桶外经验节点),这些属于第 5 步和视图后续工作。
|
||||
|
||||
## 2026-10-02 故事线模型第 3 步:时间螺旋视图
|
||||
|
||||
对应[故事线模型改造设计](./storyline-model-design.md)第 3 步与 [3D 概念](../story-graph/3d-concept.md)。
|
||||
|
||||
实现:
|
||||
|
||||
- 图谱画布标题右侧新增“平铺 / 时间螺旋”切换。时间螺旋视图(`StoryGraph3D.tsx`)读取故事列表与图谱返回的 `attention`:高度为时间,半径为每圈的对话轮次(一圈一个半径,相邻圈差值受限),木片为故事起止,事件按屏幕距离聚合。层级为 全部 › 项目 › 功能 › 子线索,跨项目故事在根层;点击木片进入下一层并在右侧显示故事详情,单个事件选中后显示事件详情。斜俯视/侧视/俯视/自由视角,拖动、滚轮、方向键、数字键、Esc。
|
||||
- 计算部分在 `story-graph-3d-model.ts`(树、角度、圈长、半径、聚合),渲染只负责显示。圈长从候选档位中选最接近 8 圈且不超过 14 圈的一档。
|
||||
- Three.js `0.186.1` 作为依赖,组件通过 `lazy` 加载,只在打开时间螺旋时下载(生产构建中只出现在 `StoryGraph3D` 分块)。颜色取现有 `--graph-node-*` 令牌。WebGL 不可用时提示改用平铺视图。
|
||||
- 2026-10-02 布局调整:画布下方的图例段落和木片、经验列表移除,中间区域只保留图。图例改为工具栏“图例”按钮,点击后浮在画布右上角,可用 X 关闭;圈长(如“一圈 1 天 · 共 13.1 圈”)常驻画布左下角。木片和经验的键盘与读屏入口改为工具栏中的两个选择菜单(共享 `AnchoredMenu` 样式,每项显示名称和事件数、起止或来源→去向)。按钮按当前层级命名,如“项目与跨项目故事 · 6”“功能 · 15”“子线索 · 9”“经验 · 3”,不再使用“木片”这一内部比喻。菜单内边距 4px,菜单项 8px×12px、行间 2px,两个按钮间距 8px;1440 与 390 宽度下文字无裁切,菜单在窗口内。
|
||||
- 2026-10-03 图谱中间列不再滚动:平铺与时间螺旋都把标题栏以外的高度全部给图。平铺视图的图例、阅读说明移入标题栏“图例”浮层,可见数量常驻画布左下角;“事件浏览”改为单行(上一个、滑块、下一个、位置、当前事件)。图谱行最低 660px,以免平铺图文字小于 11px;更矮的窗口改为页面滚动。Electron 布局测试新增断言:中间列 `overflow: hidden` 且无溢出,图区一直延伸到列底部;1440×1200 下整页无滚动、底部留 33px,图高 625px。真实数据副本下时间螺旋在 700px 行高中填满,无列内滚动。
|
||||
- 2026-10-03 图谱页顶部的“图谱范围 · 全局”和时间行并入画布标题下方一行(范围 · 时间区间 · 生成时间 · 事件/实体数),刷新改为标题栏图标按钮;标题由“工作的来路”改为“事件与知识”。图谱行因此多出约 40px:1440×900 下整页不再滚动,图高 552px。布局测试新增断言:范围行位于标题下方,图谱上方不再有独立的范围栏。
|
||||
- 2026-10-03 修复“时间螺旋没有加载成功”:每次重建场景都复用同一个画布,而上一次释放时已对它调用 `forceContextLoss()`,该画布再也拿不到可用的 WebGL 上下文(Three.js 报 `Cannot read properties of null (reading 'precision')`)。开发模式的 StrictMode 双挂载、父组件刷新时传入新的 `attention` 数组都会触发重建,所以打开就失败。现在每次重建创建独立的绘图画布并在释放时移除,原画布只负责焦点和指针事件;`attention` 按内容保持稳定,刷新不再重建场景。复现脚本(StrictMode + 20 次父组件重渲染)修复前首屏即报错,修复后无报错。真实数据副本的 Electron 检查(1440 浅色、390 深色):画布外无段落和列表,图例浮层位于画布内,选择框逐层打开、视角切换、Esc 返回正常,无报错和横向溢出。
|
||||
|
||||
验证:
|
||||
|
||||
- `story-graph-3d-model.test.ts` 4 项(树与跨度、圈长档位、每圈一个半径且变化有界、聚合);工作区、活动、设置组件测试通过;Node/Web 类型检查、相关 ESLint 通过;`electron-vite build` 确认 Three.js 只在懒加载分块中。
|
||||
- Electron 实测(第 2 步跨项目运行的真实故事数据副本,生产组件):1440 浅色与 390 深色均无控制台错误、无横向溢出;画布非背景像素约 26%;根层 6 片(5 个项目、2 个跨项目故事中的果蝇神经仿真等),goodbuddy 15 个功能,监督者 9 条子线索;视角切换、Esc 返回、选中故事回调正常;旋转一帧约 1 ms。数据跨 13 天,一圈 1 天共 13.1 圈。
|
||||
|
||||
边界:时间切面(第 7 项)、展开视图(第 8 项)、原位分裂动画和桶外经验节点尚未移植;经验(W)要到第 4 步。Three.js 懒加载分块约 1.15 MB。
|
||||
|
||||
## 2026-10-02 故事线模型第 2 步:故事归属与用户调整
|
||||
|
||||
对应[故事线模型改造设计](./storyline-model-design.md) SL-1~SL-3、SL-5、SL-8 和第 2 步。
|
||||
|
||||
实现:
|
||||
|
||||
- `supervision-stories.ts`:新表 `supervision_stories`(功能、子线索、跨项目故事,状态、用户调整标记)、`supervision_event_stories`(主故事唯一,另可关联)、`supervision_story_assigned`、`supervision_story_edits`。Schema 54;升级后表为空,已有事件由下一次回顾归属,不重读来源。
|
||||
- 回顾发布后在同一回顾名额内执行故事归属:只取本范围中尚未归属的当前事件,按项目分块,每块一次模型调用,输入是事件标题、描述和本项目已有故事摘要,不含原文。没有待归属事件时不调用模型。项目来自事件来源;功能、子线索由模型提出;允许“暂不归类”。
|
||||
- Main 校验每个事件恰好分配一次、主故事属于本项目、子线索有上级功能、结束标记引用本次归入该故事的事件;任一项不符则整块不保存。功能事件数低于“子线索最少事件数”(设置,默认 20)时,子线索并入功能。
|
||||
- 跨项目关联关闭时,提示词禁止跨项目故事,也不提供已有跨项目故事;开启时项目依次处理,跨项目故事只作关联,不影响主故事的长度和事件数。
|
||||
- 用户调整:重命名、合并(同项目同层级)、移除(事件回到上级功能或变为暂不归类,不删除事件)、把事件移到同项目其他故事或暂不归类、撤销上一次调整。调整过的故事不会被自动整理改名或标记结束,移除的故事不会因同名再生成。
|
||||
- 故事归属失败不影响已发布的回顾:活动记录显示“回顾已保存,故事整理失败”并可重新整理;下一次回顾(含无变化)也会补上。
|
||||
- 界面:图谱左侧新增“故事”页签,按项目 › 功能 › 子线索、跨项目故事列出,显示暂不归类的事件数;选中故事在右侧查看起止、状态、事件,并可重命名、合并、移除、撤销;事件详情可改所属故事。设置新增“功能拆分子线索的最少事件数”。
|
||||
|
||||
验证:
|
||||
|
||||
- 新增 `supervision-stories.test.ts` 6 项:归属与无变化零调用、长时间空白后接回原故事、不完整/重复/虚构/证据不符的回答整块拒绝、小功能子线索并入、跨项目开关、调整保护与撤销、回顾内执行及失败后重试。`src/main/assistant` 全目录、Story Graph MCP 集成、监督相关 IPC、监督者工作区/活动/设置组件测试均通过;Node/Web 类型检查与相关 ESLint 通过。未运行全量测试。
|
||||
- 真实数据(第 1 步升级后的本机数据副本,`scripts/stories-deepseek.mjs`,DeepSeek `deepseek-flash`):
|
||||
- 跨项目关闭:437 个当前事件,11 次调用约 4.3 分钟;39 个故事(35 个功能、4 个子线索),396 个事件归入,41 个暂不归类;无事件有两个主故事、无主故事跨项目;11 个“已结束”均引用本故事内的事件;再次归属 0 次调用。
|
||||
- 跨项目开启:11 次调用约 6.4 分钟;44 个故事,其中 1 个跨项目故事关联 dgx 与阿里云-国内的 7 个事件;其余检查同上。
|
||||
|
||||
观察到的问题(首轮):
|
||||
|
||||
- 功能粒度不稳定:goodbuddy 的发布被拆成 0.13.10、0.13.11、0.13.12、0.13.13 多个功能,两次运行切分不同。
|
||||
- 监督者相关 77~85 个事件的功能只拆出 0~1 个子线索,模型很少主动建子线索。
|
||||
- goodbuddy 的 fly-brain 仿真与微信 ClawBot 的果蝇开源项目在开启跨项目后仍未关联。
|
||||
- 故事起止跨度可以从事件计算,但单个事件的跨度仍多为同一时刻(见第 1 步边界)。
|
||||
|
||||
针对上述问题的调整:
|
||||
|
||||
- 提示词写明粒度:功能是持续数周的模块、子系统或研究主题;发布、版本、单个缺陷、单日任务不是功能,同一项目的发布与版本同步归入一个功能,版本可作子线索;功能名不含版本号和日期;每个项目通常 3~12 个功能;功能达到“子线索最少事件数”后按子线索放置事件。
|
||||
- 新功能或跨项目故事的名字去掉版本号、日期后与已有或同批故事相同时,并入已有故事(确定性规则);被并入那一方的“已结束”标记不生效,避免一个版本结束让整条发布线结束。
|
||||
- 子线索拆分:每个项目归属完成后,自身事件数跨过“子线索最少事件数”下一个整数倍的功能(每项目最多 3 个)各做一次拆分调用,输入只有该功能名称、已有子线索和模型放置的事件标题与截断描述。只把事件从功能移到子线索;新子线索至少需要 max(3, 阈值/5) 个事件,否则不建;用户移动过的事件和用户调整过的功能不动;被用户移除的同名子线索不重建。功能没有跨过下一个整数倍时不再调用。
|
||||
- 跨项目开启时,提示词附带其他项目的功能名称、描述和事件数(最多 60 个,不含事件内容),已有跨项目故事附带它已关联的功能名称;回答可用 `links` 把本项目功能和其他项目功能挂到同一个跨项目故事,关联该功能的全部当前事件(只作关联,不改主故事)。关闭时不提供,且 `links` 会被整块拒绝。
|
||||
- 新故事键接受 `new_1_t1` 这类写法(真实模型在一次回答中这样写子线索键,原先整块被拒)。
|
||||
|
||||
调整后的真实数据(同一数据副本,DeepSeek `deepseek-flash`,每次各自从空故事开始):
|
||||
|
||||
| 运行 | 调用 | 功能 / 子线索 / 跨项目 | 归入 / 暂不归类 | 拆分移动 | 版本号命名的功能 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 首轮 关闭 | 11 | 35 / 4 / 0 | 396 / 41 | — | 有(0.13.10~0.13.13) |
|
||||
| 首轮 开启 | 11 | 35 / 8 / 1 | 407 / 30 | — | 有 |
|
||||
| 调整后 关闭 | 12(含 1 次拆分) | 19 / 23 / 0 | 372 / 65 | 18 | 0 |
|
||||
| 调整后 开启 | 12(含 1 次拆分) | 21 / 23 / 2 | 388 / 49 | 20 | 0 |
|
||||
|
||||
- 改善:goodbuddy 发布收敛为一个“发布与版本”/“提交与发布”功能,版本工作成为其子线索;监督者功能拆出 9~11 个子线索(回顾整理与图谱、回顾算法与并发设置、故事线模型与 3D 图谱、活动记录页、自动监督开关与设置等),界面、笔记与画布也拆出子线索;开启跨项目后“果蝇神经仿真”关联了 goodbuddy“本地果蝇神经仿真”与微信 ClawBot“果蝇神经开源项目调研”(16 个事件),另有“Agent 实例生命周期与回收”关联 goodbuddy 与 dgx(24 个事件)。两次运行都无事件有两个主故事、无主故事跨项目、无小功能子线索,再次归属 0 次调用。
|
||||
- 未改善或变差:暂不归类增多(关闭 41→65,开启 30→49),模型在功能变宽后把更多零散事件留空,未逐条核对是否合理。首轮的 dgx+阿里云跨项目故事这次未出现。子线索大多在归属调用中直接建出(关闭 18、开启 20 个事件由拆分调用移入子线索),拆分调用每次运行只触发 1 次。关闭、开启各只成功跑了一次,多次运行的切分是否一致未比较。
|
||||
- 单元测试:`supervision-stories.test.ts` 新增 3 项(版本名功能合并且不误结束、拆分调用的触发上限与用户事件保护及整块拒绝、跨项目功能列表与 `links` 关联及关闭时拒绝),共 9 项;与 `supervision-timeline.test.ts` 合计 13 项通过;Node 类型检查与相关 ESLint 通过。
|
||||
- 失败:另有两次运行中途失败、未完成。一次是开启跨项目时模型把“已结束”标记引用到未归入该故事的事件,整块被拒,导致整次归属失败(已在提示词中强调证据事件须归入同一故事,之后的开启运行通过);一次是上述 `new_1_t1` 键格式,已放宽校验并加单元测试,但放宽后未再用真实模型重跑。严格校验下单块被拒会让整次归属失败,仍需重试才能补上。
|
||||
|
||||
## 2026-10-02 故事线模型第 1 步:共享时间线
|
||||
|
||||
对应[故事线模型改造设计](./storyline-model-design.md) SL-6、SL-7 和第 1 步。
|
||||
|
||||
实现:
|
||||
|
||||
- 处理进度改为一条共享时间线:`review_checkpoints` 的监督阶段只用 `scope = 'timeline'`,项目、全局和多项目组合共用。同一来源版本在任一范围成功发布后,其他范围不再提取;范围只决定读取和展示哪些来源。旧的按范围进度在升级时合并,不重读已处理内容。
|
||||
- 事件新增 `started_at`、`ended_at`、`project_id`、`superseded_by`,来源新增 `source_key`、`source_revision`(`supervision-timeline.ts`)。起止时间和所属项目按事件引用的来源记录计算,不由模型给出;来源跨多个项目时 `project_id` 为空。同一来源版本的重叠区间被后来的结果重新提取时,旧结果中的事件标记为已被替代,图谱和 MCP 只读当前事件,历史结果快照不变。
|
||||
- 图谱按范围读取共享时间线上的当前事件、实体、关系和来源,并返回 `attention`:按小时统计的消息轮次与正文量,只表示投入集中程度。
|
||||
- 新增设置“跨项目关联”(`supervisionReview.crossProject`,默认关闭)。关闭时,每批只把本项目已有的实体作为候选,不同项目的内容不会合并为同一实体;开启后候选来自全部项目。运行开始时冻结到本次配置。
|
||||
- Story Graph MCP:全局范围可以读到各项目的当前事件;来源可从运行范围、全局或包含该来源项目的项目范围读取。
|
||||
- Schema 53。
|
||||
|
||||
验证:
|
||||
|
||||
- 新增 `supervision-timeline.test.ts`:项目回顾后全局只提取其他项目的来源,相同项目再次回顾不调用模型;起止时间和项目来自来源;重新整理与项目回顾重叠后当前事件不重复;跨项目开关决定候选;Schema 52 升级合并进度和重复事件。`src/main/assistant` 全目录 28 个文件 324 项、Story Graph MCP 集成、监督相关 IPC 15 项、设置组件均通过。Node/Web 类型检查、相关 ESLint、`git diff --check` 通过。未运行全量测试。
|
||||
- 真实数据(`scripts/timeline-deepseek.mjs`,用 SQLite 在线备份复制本机 2.2 GB 数据库,原库只读):升级 2.0 秒,`integrity_check` 为 ok。原有 423 个事件全部得到起止时间与项目;13 个两次回顾重复提取的事件被标记替代,当前 410 个(goodbuddy 358、默认项目 28、dgx 17、微信 ClawBot 5、阿里云-国内 2);全局图谱返回 67 个小时段、790 轮对话的密度。
|
||||
- 真实模型(DeepSeek,`deepseek-flash`):对 goodbuddy 最近 2 小时的 37 个来源做项目回顾,4 个批次加 3 次导航汇总,共 7 次请求,新增 27 个事件,起止时间都在区间内,未关联到其他项目的实体;再次项目回顾和同区间全局回顾均为无变化,0 次请求,全局重新提取的项目来源为 0。
|
||||
|
||||
边界:
|
||||
|
||||
- 这个 2 小时区间没有其他项目的消息,所以“全局只补其他项目”只在合成测试中验证过。
|
||||
- 模型通常让一个事件只引用一条消息,所以真实数据里的事件起止时间多数是同一时刻。按故事计算跨度要等第 2 步的故事归属。
|
||||
- 测试脚本把单次输出上限设为 8,192 token 时,一个批次的输出被截断,导致 JSON 无效。上限调到 32,768 后通过。产品路径不设这个参数,具体由 Runtime 的提供商配置决定。
|
||||
|
||||
## 2026-09-30 心跳职责收敛与监督建议
|
||||
|
||||
对应 FR-S4、FR-S12、US-S32、US-S33,规则见[心跳职责与介入](./logic-design.md#心跳职责与介入目标设计)。
|
||||
|
||||
实现:
|
||||
|
||||
- `HeartbeatService` 不再读取来源或调用模型。领取计划后立即完成自身运行,再通过 `review` 触发共享的 `SupervisorService.run({ trigger: 'heartbeat' })`;审查结果 `no_change` 时标记心跳为无变化。计划领取、单次补跑、单回顾排队和失败记录沿用原有机制。
|
||||
- 手动“回顾”默认增量:`supervisionRunRequestSchema` 新增可选 `reanalyze`,`isIncrementalReview` 决定是否读取、推进 `review_checkpoints`。只有 `reanalyze: true` 重读区间,且不推进也不重置共享进度。自动检查不再续跑用户主动暂停的回顾,改为新建运行处理未提交来源。
|
||||
- 计划新增 `intervention`(`suggest` / `memory`,默认 `suggest`)。`supervision-suggestions.ts` 从本次发布结果按规则挑选候选:批次未决事项、`revised` 实体变化、`contrasts` 关系、由至少三处不同原始来源支持的未确认实体。没有候选时不调用模型;有候选时 `supervision-suggester.ts` 只把候选文本交给一次建议调用,不读原文。待处理建议不重复,已处理建议仅在依据变化后再次提出。
|
||||
- 建议失败写入 `heartbeat_runs.suggestion_status/error`,图谱和进度保留;活动页显示“回顾已更新,建议生成失败”并可单独重试。
|
||||
- 接受未决事项生成暂停任务,接受候选约定确认为长期背景(记忆)并确认对应实体;分歧和修订标记已核对。
|
||||
- Schema 50:`heartbeat_configs.intervention`、`heartbeat_runs.suggestion_status/suggestion_error`、`supervision_suggestions` 表。升级时把旧心跳报告中仍为 `proposed` 的记忆迁入为待确认约定,不确认、不删除;旧报告只读保留。
|
||||
- 界面:工作回顾主按钮改为“回顾”,“更多 → 重新整理…”经确认后执行;计划 Modal 增加“介入方式”;自动监督页用“监督建议”替代成功率、趋势、记忆/行动建议和运行审计,建议可查看依据、打开会话、在图谱中查看;设置移除心跳报告超时;导航角标统计待处理建议。
|
||||
|
||||
验证:
|
||||
|
||||
- 定向单元与集成测试:`src/main/assistant`、`src/shared`、`src/preload` 共 589 项;`src/main/ipc.test.ts` 心跳与监督相关 29 项;Renderer 监督者、自动监督、活动、设置相关组件测试及 `App.test.tsx` 全文件均通过。新增覆盖:手动增量与重新整理不动进度、不续跑已暂停回顾、候选规则与去重、忽略后仅新证据重提、建议失败隔离、IPC 启停门控、Schema 49 升级迁移。
|
||||
- Electron 布局:`GOODBUDDY_SUPERVISOR_RECAP`、`AUTOMATIC_OVERVIEW`、`ACTIVITY`、`PLAN_MODAL` 场景通过,覆盖 1440/390px 下监督建议、介入方式字段、重新整理确认和工具栏两行布局,无横向溢出。
|
||||
- 真实模型(DeepSeek,`deepseek-flash`,隔离 SQLite,`scripts/heartbeat-deepseek.mjs`):首次心跳 1 次回顾调用 + 1 次建议调用(建议输入约 1,150 字符,不含原文),生成分歧、修订、未决事项三条建议且依据均指向本次来源;无变化心跳与默认手动回顾均 0 次调用;追加一条消息后只处理 1 个来源,复用全部 5 个既有实体,再调用 1 次回顾、1 次建议。共 4 次付费请求。
|
||||
- 未运行全量测试。
|
||||
|
||||
边界:主动介入(会话级实时)未实现,界面不显示;项目与全局仍各自维护处理进度(US-S31),已由 2026-10-02 的共享时间线取代。
|
||||
|
||||
## 2026-09-28 Story Graph 只读工具
|
||||
|
||||
对应 FR-S11、US-S28 至 US-S30 的 D1 读取实现。输入、分页与版本语义由[专项设计](./story-graph-mcp-design.md)负责。US-S31 的跨范围事实复用和 memory 读取切换仍待实现。
|
||||
|
||||
实现路径:
|
||||
|
||||
- [共享工具合同](../../../src/shared/story-graph-tools.ts)定义 `story_graph_search`、`story_graph_get_context`、`story_graph_read_source`。内置目录与能力设置支持 Model、OpenCode、Continue、DeepSeek Harness 分配;应用关闭时 Main 不签发图谱读取权限。
|
||||
- [只读 SQLite 投影](../../../src/main/assistant/story-graph-reader.ts)读取精确 scope 下的历史结果、当前对象、事件、变化、关系、来源和成功叶子描述。保留旧决定与修订依据,确认状态和有效性分别返回,未知有效性不按时间推断。没有新增 schema、业务写入或模型调用。
|
||||
- [Main gateway](../../../src/main/agent/knowledge-mcp-gateway.ts)在发现、读取前和返回前复核监督者开关及 Runtime 分配;IPC 将项目和 Runtime 固定到请求。Model 和 Harness 代理、本地 MCP、原生客户端共用此入口。Harness 的目录、scope schema 转换及 Host Ask 执行钩子均已接通。
|
||||
- 远程沿用 `ProtocolRemoteRuntimeChannel`、`MainImageToolSession`、`AgentImageToolMcp` 和 `RuntimeAcpBackend` 的现有工具通道。Ask 可以只有图谱描述,没有图像生成描述。OpenCode 根/子会话仅放行当前端点;Continue Ask 保留 Main 会话 MCP,并由原生只读工具名和 Main 双重校验。
|
||||
- 对象页为 10/50 项,正文页为 4,000/8,000 码点(默认/上限)。Context 用带位置的 JSON 片段续读长对象;游标绑定过滤、scope 和读取修订,变更后返回 `stale_cursor`。来源默认读保存片段;显式 current 通过 locator 读当前记录,version 仅接受可验证的现存版本。知识引用片段不声称是整份知识原文,来源迁出项目、删除、未知历史和响应过大均有明确结果。
|
||||
|
||||
定向验证:
|
||||
|
||||
```text
|
||||
npx vitest run src/main/assistant/story-graph-reader.test.ts src/agent-daemon/story-graph-integration.test.ts src/main/assistant/supervision-production.test.ts src/main/capabilities/capability-service.test.ts src/main/ipc.test.ts src/main/remote-agent/protocol-remote-runtime-channel.test.ts src/agent-daemon/runtime-acp-backend.test.ts src/agent-daemon/continue-acp-helper.test.ts src/agent-daemon/opencode-subagent-plugin.test.ts src/main/agent/native-terminal-client.test.ts
|
||||
```
|
||||
|
||||
10 文件、337 项通过,1 项既有条件测试跳过。覆盖真实 SQLite、HTTP MCP、Main/Agent 消息往返、协议准备和释放、四个 Runtime 的 IPC 项目绑定、Supervisor 关闭后不再读取、取消、来源删除/迁移、旧游标、Unicode 正文续页、超长响应及 A → B 修订后 C 仍为选项。冷启动审查发现 current 来源读取依赖运行回顾后才注册的 SQLite 函数,改为按现存 context 直接计算同一修订;新连接回归通过。最终读取相关 3 文件、28 项再次通过。
|
||||
|
||||
其他受影响路径也已定向验证:
|
||||
|
||||
- `continue-runtime.test.ts`、`opencode-runtime.test.ts`、`deepseek-harness-runtime.test.ts`、`native-client-coordinator.test.ts`、`protocol-remote-runtime-channel.test.ts`、`knowledge-mcp-gateway.test.ts`:6 文件、243 项通过。
|
||||
- `goodbuddy-harness-control-plane.test.ts`、`continue-host-adapter.test.ts` 及相关桥接/读取测试:6 文件、98 项通过。`model-tool-provider.test.ts`、能力设置及 Continue helper 组合:3 文件、92 项通过。
|
||||
- `npx vitest run src/main/agent/deepseek-harness-acp-e2e.test.ts -t "reads Story Graph"`:1 项通过、13 项按名称跳过。真实受控 Harness Host 经 ACP 和 Main 代理执行 Ask/Execute 读取,随后关闭监督者并确认工具移除;推理使用测试实现,没有外部模型请求。
|
||||
- `npx tsc --noEmit -p tsconfig.node.json`、`tsconfig.agent.json`、`tsconfig.web.json` 均通过;最终 Main 类型检查再次通过。本次变更的 TypeScript 源码和测试定向 ESLint 通过,`git diff --check` 通过。
|
||||
|
||||
### Linux Host 验证
|
||||
|
||||
[SSH 探针](../../../scripts/story-graph-host-probe.ts)从当前源码临时打包,在共享 Linux x64 Host 运行 `AgentImageToolMcp`、`AgentOwnedAcpPrompt` 和 `ContinueHostAdapter`,使用 Host 安装的 OpenCode 和 Continue(1.5.47)。桌面启动隔离内存 SQLite、Main gateway 和 `MainImageToolSession`,通过 SSH 搬运工具通道消息;用户数据库、模型凭据和正文库没有复制到 Host。Host 临时运行目录在结束时清理。
|
||||
|
||||
探针通过:HTTP 发现三个只读工具、搜索/上下文/来源读取及分页、`as_of` 拒绝、OpenCode Ask/Execute、Continue Ask/Execute 的实际工具调用,以及关闭后的发现和旧会话调用拦截。成功探针有 10 次本机确定性推理请求,其中 2 次为 Runtime 标题生成,桌面读取 10 次,付费调用 0 次。RPC blob framing 与 prompt 生命周期另由上述 `runtime-acp-backend` 和 `protocol-remote-runtime-channel` 测试覆盖;该 SSH 探针使用标准输入输出承载工具消息,没有执行安装包升级或完整桌面 UI 操作。
|
||||
|
||||
复跑时在仓库使用 esbuild 将脚本输出到临时目录,再通过已有 SSH 身份传至 Host 的 GoodBuddy 测试目录。桌面命令参数为:
|
||||
|
||||
```text
|
||||
node <local-probe.cjs> <ssh-host> <remote-probe.cjs> <host-opencode-path> <host-continue-cn.js-path>
|
||||
```
|
||||
|
||||
### 保留边界
|
||||
|
||||
没有执行全量测试、付费模型请求、提交、推送或发布。真实模型如何根据冲突证据形成最终建议、跨平台安装包和完整桌面 UI 仍未在本次验收;没有据此声明 US-S28 的建议质量已验证。查询目前同步扫描所选 scope 的已发布事实,大库延迟和运行中断粒度没有性能验收。`replaces`/`contradicts`、历史有效期、`as_of`、跨 scope 事实复用和 memory 消费切换均没有伪造实现;已有记忆与 checkpoint 保留。中文设计扫描无阻断项,复核项是时间、范围和证据限制,按技术含义保留。
|
||||
|
||||
## 已验证
|
||||
|
||||
### 2026-09-26 单次执行与取消集成复核
|
||||
|
||||
对应 FR-S4、FR-S6、FR-S10、US-S24、US-S26。复核当前 Main、SQLite、IPC、Preload 与 Renderer diff,确认手动新建/继续遇到占用立即拒绝,自动回顾等待;取消立即落库,执行位置保留至并行批次及 Runtime 清理结束。UI 在此期间显示正在取消,X 仅关闭提示;已取消运行不能继续,也不会被自动恢复。实现合同见[单次执行与取消](./technical-design.md#单次执行与取消),界面见[UI 设计](./ui-design.md)。
|
||||
|
||||
发现并修复一个可达问题:活动页等待整个 `resume` 请求结束才释放操作锁,继续运行后的暂停和取消按钮因此一直禁用。现在发出继续请求后释放提交锁,独立处理完成/失败,Main 仍保证唯一执行。新增回归在修复前因暂停按钮禁用而失败,修复后验证继续中的取消、停止提示,以及原继续请求返回时不清除后续取消操作的状态。
|
||||
|
||||
本轮验证:
|
||||
|
||||
- `npx vitest run src/renderer/src/SupervisorActivity.test.tsx src/renderer/src/SupervisorWorkspace.test.tsx`:38 项通过,包含上述新增回归、取消清理期间的状态、重新进入页面、手动重复提示及 X 不取消。
|
||||
- `npx vitest run src/main/assistant/supervision-review.test.ts src/preload/supervision-activity.test.ts src/main/ipc.test.ts -t "reserves before setup|persists cancellation|allows a new review after|cancels navigation|projects cancellation|wakes waiting automatic|production supervision IPC cancels|supervision"`:27 项通过、144 项按名称跳过;其中 Preload 文件未命中过滤,另用 `npx vitest run src/preload/supervision-activity.test.ts` 单独验证,1 项通过。
|
||||
- `npx eslint src/renderer/src/SupervisorActivity.tsx src/renderer/src/SupervisorActivity.test.tsx` 通过;本轮文件的 `git diff --check` 通过。中文新增内容按 `deai-writing` 清单人工审校,文档链接目标已核对。
|
||||
|
||||
上述测试使用隔离 SQLite、生产 handler/工厂及可控 Runtime、Renderer API 替身;本轮未运行 Electron 端到端、全量测试或类型检查,真实模型调用 0 次。用户提供的后端与前端实现代理测试通过记录作为先前证据,不并入本轮计数。远程不受影响的依据是 `ipc.ts` 生产工厂调用 `resolveRequestRuntime({ workMode: 'ask' })`,无项目、Runtime 选择或执行空间,解析直接返回桌面默认 Runtime;不进入 SSH/Agent 路径,未修改远程协议或取消实现。未运行远程实测,未改动其他工作的 Runtime/package 文件,未提交。
|
||||
|
||||
### 2026-09-26 移除回顾自动时间预算暂停
|
||||
|
||||
对应 FR-S4、FR-S6、FR-S10。生产服务移除提取循环和导航合并中的整次执行期限判断,持续处理至完成;已保存运行中的 `executionSeconds` 继续兼容读取,但不再控制暂停。设置页移除该控件,中英文帮助和活动配置改为持续处理说明。单次模型超时、共享并发、逐批保存、主动暂停及失败续跑保持原路径,具体合同见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界)。主动暂停仍中断在途请求,仅保留此前已保存批次。
|
||||
|
||||
`npx vitest run src/main/assistant/supervision-review.test.ts src/renderer/src/SupervisionReviewSettings.test.tsx`:13 项通过。受控时钟超过保存的执行预算后,三个叶子及两个导航合并仍完成并发布;另验证主动暂停后复用成功批次和原分块配置、中英文设置不再出现整理时长控件。测试使用隔离 SQLite 和可控摘要器,不代表真实模型长时间运行实测。
|
||||
|
||||
`npm run typecheck`、六个修改 TS/TSX 文件的定向 ESLint 和 `git diff --check` 通过。未运行全量测试,真实模型调用 0 次。核对 Main 的生产工厂接线,监督使用不携带项目的 `resolveRequestRuntime({ workMode: 'ask' })`;本次仅改变桌面回顾编排,不修改 Agent、远端 Runtime 或桌面到 Agent 协议。未提交或推送。
|
||||
|
||||
|
||||
### 2026-09-24 长摘要与内容区对齐
|
||||
|
||||
针对全局七天回顾的 `summary max 2000` 报错,代码确认导航输出 schema 的 `max(2000)`、服务层对叶子及导航的二次 2000 字符检查,以及模型提示词中的同值要求。另发现监督旧摘要通过 SQL `substr` 和 JavaScript `slice` 截断。以上监督路径限制已移除;生成内容和事实集合的合同见[生产调度合同](./review-scheduling-design.md#已接入的调度与存储)。未读取本次 16:03 失败的原始模型响应,不能据此声明该运行的具体失败节点。
|
||||
|
||||
生产工厂回归使用隔离 SQLite 和离线 Runtime 流,验证 14,000 字符的叶子及导航摘要完整进入合并输入、存储、历史读取,发布失败后以已保存节点恢复且不再请求模型,叶子长描述仍进入最终图谱。单次超过 100,000 字节的响应明确失败,保留续跑位置。共享契约另覆盖原字段及数组数量边界,仍拒绝导航事实和叶子缺字段;旧身份兼容、跨范围拒绝和六叶合并恢复测试继续通过。IPC 暂停/继续测试改用 3,600 字符摘要,并修正已有 `persistedId` 提示词断言为当前 `candidateRef` 合同。
|
||||
|
||||
布局采用共享阅读、dashboard 宽度,范围、工具栏、历史和结果的对齐规则见[UI 设计](./ui-design.md#应用入口)。真实 Electron 加载生产 React 组件,使用隔离 fixture 验证 2000、1440、1024、390px 的浅深主题,覆盖长摘要、活动、运行中、失败共 32 组,并保存 8 张正文末尾截图。无页面横向溢出,正文末尾保留,宽屏阶段行低于 40px,刷新紧邻筛选。另运行既有活动分支,验证原生键盘展开、完整错误滚动、六个叶子及 12 批分页。已人工查看宽窄屏、浅深主题、运行/失败、正文末尾及批次树截图。
|
||||
|
||||
截图分别保存在本机临时目录 `opencode/supervision-content-layout` 与 `opencode/supervision-content-activity`。复现命令为设置 `GOODBUDDY_SUPERVISOR_CONTENT_LAYOUT=1` 或 `GOODBUDDY_SUPERVISOR_ACTIVITY=1` 后运行 `npx vitest run tests/supervisor-layout.electron.test.ts`,可用 `GOODBUDDY_SUPERVISOR_ARTIFACTS` 指定保留目录。模型调用 0 次,未改写用户数据库、运行全量测试或提交。
|
||||
|
||||
验证记录:首轮服务、分块、历史和工作区 6 文件 68 项通过;补充共享契约、生产工厂、活动存储/preload、心跳中心及工作区共 8 文件 112 项通过。`npx vitest run src/main/ipc.test.ts -t 'supervis'` 为 17 项通过;修正测试等待的 TypeScript 类型后,暂停/继续单项再次通过。最终 `npm run typecheck`、本轮修改 TS/TSX/MJS 的定向 ESLint 和 `git diff --check` 通过。中文变更按 `deai-writing` 清单人工审校。
|
||||
|
||||
### 2026-09-24 活动阶段、批次层级与导航输出修复
|
||||
|
||||
对应 FR-S4、FR-S6、FR-S10、US-S26。活动记录改为紧凑运行摘要、实际阶段、恢复操作、折叠错误、按需批次层级和次要配置详情。阶段存入既有运行 JSON;叶子与导航分别校验,叶子缺数组仍失败。当前字段与缺失数据规则见[生产调度合同](./review-scheduling-design.md#已接入的调度与存储),布局见[活动设计](./ui-design.md#活动)。
|
||||
|
||||
只读检查 portable 原库的最新失败运行 `f6800335-5765-4163-9f55-a30c8ab18a94`,确认 32 个来源、剩余 0、6 个成功批次、4,515 字符和 1 个已保存导航节点。错误仅包含 `events / entities / entityChanges / relations` 四个缺失数组。旧代码把导航响应交给叶子 schema,且提示词同时要求“只返回摘要字段”和“事实数组必须为空”,合同矛盾已确认。原始失败响应和阶段没有保存,因此该次失败发生在导航解析仍属有数据支持的推断,不能声称取回了原响应。
|
||||
|
||||
使用 SQLite 一致性备份和关联笔记副本执行 `scripts/supervision-navigation-replay.ts`。仅私有副本初始化;第一次因缺少关联笔记停止,补齐后完成。生产工厂继续同一 run,复用已存导航节点,4 次离线响应完成剩余合并及发布,6 个叶子逐项未变。回放文本来自该 run 已保存的成功导航,只保留 summary、changeDigest、openItems,以验证缺数组的合法导航形状;它不验证新摘要质量,也不是原失败响应回放。原库只读,原失败 run 前后相同,付费模型调用 0 次。
|
||||
|
||||
本轮验证:
|
||||
|
||||
- `npx vitest run src/main/assistant/supervision-production.test.ts src/main/assistant/supervision-review.test.ts src/main/assistant/supervisor-service.test.ts src/main/assistant/supervision-activity.test.ts src/preload/supervision-activity.test.ts src/renderer/src/SupervisorActivity.test.tsx src/renderer/src/HeartbeatCenter.test.tsx src/renderer/src/SupervisorWorkspace.test.tsx`:8 文件、116 项通过。包含六叶零剩余后的合并失败与继续、发布失败后无模型继续、缺数组叶子拒绝、未知来源/实体拒绝、实际阶段呈现、旧阶段缺失、错误与配置折叠、树分页及读取失败重试。
|
||||
- `npm run typecheck` 和本轮变更 TS、TSX、MJS 文件的定向 ESLint 通过;未运行全量测试。
|
||||
- `GOODBUDDY_SUPERVISOR_ACTIVITY=1` 下运行 `npx vitest run tests/supervisor-layout.electron.test.ts`:1 项通过。真实 Electron 加载生产 React 组件,以多项目、多会话的隔离数据覆盖 1440×1000、390×1000 和浅深主题,分别截取运行摘要、错误展开、批次树,共 12 张截图。检查原生 Enter 展开、240px 错误滚动、全部六个已保存叶子及 12 批跨页读取,无横向溢出和控制台错误。失败运行摘要高度为桌面 366px、窄屏 476px,两主题一致;已人工查看截图。
|
||||
|
||||
私有数据库、回放报告及截图位于 `C:/Users/jiang/AppData/Local/Temp/opencode/activity-incident-20260924/`,截图测量文件为 `screenshots/review-activity-measurements.json`。收尾检查修正成功返回 coverage 沿用合并前阶段的问题,3 个生产服务测试文件共 33 项复验通过,活动与返回结果均使用发布阶段投影。`git diff --check` 通过,仅提示现存行尾转换。中文文档经 deai-writing 扫描与人工复核,本轮新增段落无阻断项;全文件扫描的 7 项阻断均在既有非本轮段落,未改动其内容。
|
||||
|
||||
历史缺失 phase 仍显示未记录;树只列成功批次,没有持久化的单任务失败/在途状态,项目和会话数量仅针对本页。收集与发布仍为同步 SQLite 阶段,不保证轮询可见。未修改 Agent、远程 Runtime、传输或凭据逻辑;原暂存及未暂存修改保留,未暂存、提交或替换 portable 构建。
|
||||
|
||||
### 2026-09-24 工作回顾与自动报告分区
|
||||
|
||||
对应 FR-S2、US-S22 至 US-S24。工作回顾仅阅读统一监督结果,历史选择和精确结果图谱入口移到正文前;手动新回顾控件保持内联,390px 下使用双列。结果常显生成时间、覆盖区间和范围,正文按原始段落及已有结构化字段展示,限制阅读宽度。新请求配置与运行状态不改写旧结果。自动报告、记忆与行动建议全部归入自动监督,保留展开、确认/忽略、带入对话、标记完成和每次追加 20 条;移除独立最近报告副本。五页签当前布局以 [UI 设计](./ui-design.md#应用入口)为准,下方各轮记录保留为历史证据。
|
||||
|
||||
- `npx vitest run src/renderer/src/HeartbeatCenter.test.tsx src/renderer/src/SupervisorWorkspace.test.tsx src/renderer/src/SupervisorActivity.test.tsx`:66 项通过。覆盖非空结果与空自动报告不矛盾、完整建议操作、长内容展开与分页保留、历史精确 ID、变更新请求范围不重标旧结果、运行中保留正文和活动入口。
|
||||
- `npx vitest run src/renderer/src/App.test.tsx -t 'graph navigation opens the pinned sidebar result|keeps heartbeat plans independent|saves Supervisor timeouts'`:3 项通过,284 项按名称跳过。校正已有设置保存测试的旧字段选择器,保留实际 App 接线验证。
|
||||
- `npm run typecheck` 与本轮修改的 Renderer、测试及 Electron driver 定向 ESLint 通过。中文 UI 文档经 deai-writing 扫描,无阻断项;复核项涉及既有状态与实现边界,保留事实限定。
|
||||
- 设置 `GOODBUDDY_SUPERVISOR_RECAP=1` 后运行 `npx vitest run tests/supervisor-layout.electron.test.ts`:1 项通过。现有生产组件 fixture 覆盖 1440×1100、390×1100 的工作回顾与自动监督,含两类结果均非空、自动报告为空、两者均空,共 12 个页面场景;另验证历史结果运行中保持可见及活动跳转。每页一处刷新、横向溢出及正文宽度断言通过。
|
||||
|
||||
截图与 `recap-measurements.json` 保存在 `C:/Users/jiang/AppData/Local/Temp/opencode/supervisor-recap/`。已人工查看桌面与窄屏正文、运行中的历史结果、自动报告展开、建议操作和独立空态截图。此次验证使用隔离 fixture,未读取用户数据库,模型调用 0 次,未运行全量测试;未修改 Main、Preload、数据库、Agent 或远程 Runtime。保留已有暂存和未暂存修改,未暂存或提交。
|
||||
|
||||
### 2026-09-24 自动监督概览归属修正
|
||||
|
||||
对应 FR-S2、US-S22 至 US-S24。当前状态、范围与刷新、成功率、记忆、洞察、行动指标、报告趋势和心跳运行审计已整体移到“自动监督”,与计划列表同页。当前状态下只保留计划列表的创建按钮和未配置说明,点击直接打开原 Modal。活动页只显示统一监督活动、进度、详情及结果入口,计划 ID 筛选、清除、分页和精确结果跳转沿用原路径;活动筛选不改变自动监督概览。当前归属见 [UI 设计](./ui-design.md#应用入口),下方旧方案的截图与测试记录保留为历史证据。
|
||||
|
||||
- `npx vitest run src/renderer/src/HeartbeatCenter.test.tsx src/renderer/src/SupervisorActivity.test.tsx`:48 项通过。覆盖空/非空时整块归属、唯一创建入口、独立刷新回调、原 Modal 的 X/footer/关闭保护、计划操作、审计分页、同名计划筛选和结果跳转。
|
||||
- `npx vitest run src/renderer/src/App.test.tsx -t 'keeps heartbeat plans independent of the active project'`:1 项通过,286 项按名称跳过,保留实际 App 的跨项目计划列表与暂停计划 Modal 保存验证。
|
||||
- `npm run typecheck`、本轮修改的 `HeartbeatCenter.tsx`、`HeartbeatCenter.test.tsx`、`supervisor-layout-driver.mjs` 定向 ESLint 通过;`git diff --check` 无空白错误,仅提示现存行尾转换。
|
||||
- 设置 `GOODBUDDY_SUPERVISOR_AUTOMATIC_OVERVIEW=1` 和产物目录后运行 `npx vitest run tests/supervisor-layout.electron.test.ts`:1 项通过,约 10 秒,仅运行本轮范围。现有生产组件 fixture 覆盖浅色 1440×1100、390×1100,空/非空计划与自动监督/活动共 8 个页面场景;检查整块归属、唯一创建按钮、横向溢出、原 Modal 打开关闭及精确计划活动筛选。
|
||||
|
||||
截图和 `automatic-overview-measurements.json` 位于 `C:/Users/jiang/AppData/Local/Temp/opencode/supervisor-automatic-overview-20260924/`。已查看桌面、窄屏空/非空计划、活动及滚动后的趋势和运行审计截图;桌面指标四列、窄屏单列,内容无横向裁切,活动页无重复心跳列表。本轮中文修改按 deai-writing 清单人工审校。未运行全量测试,模型调用 0 次,未读取用户数据库;未修改 Agent/远程 Runtime、Modal 实现或共享样式,保留已有暂存和未暂存修改,未暂存或提交。
|
||||
|
||||
### 2026-09-24 真实失败运行身份修复
|
||||
|
||||
只读定位到 `dist/GoodBuddy-windows-x64/data/assistant.sqlite` 中的运行 `db9902ab-22c8-4aae-86ae-7850bfc07b76`。Roaming 库不含该运行。原运行是全局手动回顾,范围为 2026-09-17 14:33:02 至 09-24 14:33:02,开始于 14:33:02,失败于 14:44:52(本机 UTC+8)。保存配置为分页 50、批次文本量 8000、消息数 20、执行预算 300 秒、模型超时 240 秒、并发 1;清单 32 个来源,成功批次 0。
|
||||
|
||||
原错误指向 `entities[0].persistedId` 的 UUID 校验。原始模型响应未保存,诊断文件没有该次响应,无法确认原错误值及 710 秒耗时的模型、排队、重试分解。只读候选检查发现 15 个旧实体均使用文本主键;隔离真实调用再次证明,即使模型选择提供的候选,旧主键仍会触发严格 UUID 校验。修复采用候选短引用、Main UUID 别名及旧键精确映射,详细规则见[技术设计](./technical-design.md)。
|
||||
|
||||
通过 SQLite 一致性备份复制数据库和关联笔记文件,仅隔离副本运行初始化及生产工厂 `createProductionSupervisorService`、`ModelAgentRuntime`、批次事务和结果保存。首次隔离启动因缺少笔记文件停止,付费调用 0 次;补齐副本后开始模型验证。使用 `.env.deepseek.local` 的 `deepseek-flash`,总计 3 次 HTTP 请求,无自动付费重试或模型回退:
|
||||
|
||||
| 验证 | 本机开始时间 | HTTP | 响应耗时 | 结果 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 原运行首叶提取 | 14:53:57 | 200 | 5.994 秒 | 首次暴露旧键校验失败,响应私存;修复后缓存复验并保存 |
|
||||
| 真实来源有界回顾 | 14:58:25 | 200 | 4.445 秒 | 2 个来源、402 字符、1 批,完成并发布 2 个实体 |
|
||||
| 同一来源候选复用 | 14:58:30 | 200 | 4.703 秒 | 完成并复用 2 个既有实体 |
|
||||
|
||||
缓存复验要求请求除 Runtime 当前时间外一致,没有新 HTTP。隔离副本中原失败 run 保存 1 批、3 个精确匹配的原文片段、490 字符及 3 个完整来源位置后主动暂停;32 个来源还剩 29 个,不冒充原全局回顾已完成。两个有界回顾分别完成结果发布,实体总数保持 15。手动回顾不写自动检查用的 `review_checkpoints`。只读复核原 run、来源清单及批次逐项未变;副本完整性检查通过,原有 1 个受保护实体保持不变。
|
||||
|
||||
私有产物在 `C:/Users/jiang/AppData/Local/Temp/opencode/supervision-incident-real-20260924/`:`incident.private.json`、`candidates.private.json`、3 份请求与 SSE 响应、`live-metrics.json`、`audit.json` 和副本 `assistant.sqlite`。原库只读打开,无初始化、迁移或业务写入;原失败状态及原有数据保留。验证脚本为 `scripts/supervision-incident-{inspect,launch,audit}.cjs` 与 `scripts/supervision-incident-live.ts`,重复付费启动受检查限制。
|
||||
|
||||
定向 7 文件共 47 项测试、`npm run typecheck`、本轮改动文件 ESLint 和 `git diff --check` 通过,后者仅提示现存行尾转换。回归包含真实旧键形状、严格 UUID 别名、空值和占位符的新事实保留、未知候选/跨 scope 拒绝、确认字段及图谱端点保护、失败后成功叶子不重算,以及中英文可展开错误详情。活动页显示本地化失败原因,技术错误保留在可选择文本的折叠详情;进度与设置不再直接展示 UTF-16、Unicode 或共享池术语。新增中文文档按 deai-writing 清单人工复核。未运行全量测试,没有提交、暂存或构建替换应用。此修复属于桌面监督身份边界,未修改 Agent 或远程 Runtime 执行实现;正在运行的 portable 和 D 盘安装版均需包含新源码的构建并重启后才会生效。
|
||||
|
||||
### 2026-09-24 计划 Modal 标题与操作区统一
|
||||
|
||||
对应 FR-S2、US-S22 至 US-S24。`HeartbeatSettings` 复用既有 `custom-task-dialog__header/content/actions` 和按钮样式,标题在左、带本地化可访问名称及工具提示的 X 在右,取消与保存位于右对齐 footer。采用共享 Grid 的固定标题、滚动正文、固定操作区,没有新增 Modal 框架或页面专属 CSS。共享规范见根目录 [表单 Modal](../../../UI-DESIGN.md#615-表单-modal),计划交互见[自动监督设置](./ui-design.md#自动监督设置)。
|
||||
|
||||
本轮验证:
|
||||
|
||||
- `npx vitest run src/renderer/src/HeartbeatCenter.test.tsx src/renderer/src/CustomTaskDialog.test.tsx`:50 项通过。覆盖中英文创建/编辑结构、X/取消/Escape/遮罩的干净关闭与未保存确认、Tab 循环、焦点恢复、提交锁定、失败草稿及暂停计划保存重试。
|
||||
- `npx vitest run src/renderer/src/App.test.tsx -t 'keeps heartbeat plans independent of the active project'`:1 项通过,286 项按名称跳过;验证实际 App 的暂停计划编辑提交、Portal 和背景隔离恢复。
|
||||
- `npm run typecheck` 通过;本轮修改的 Renderer、翻译和 Electron driver 文件定向 ESLint 通过;`git diff --check` 无空白错误。
|
||||
- `GOODBUDDY_SUPERVISOR_PLAN_MODAL=1`、`GOODBUDDY_SUPERVISOR_ARTIFACTS=C:/Users/jiang/AppData/Local/Temp/opencode/supervisor-plan-modal-standard` 下运行 `npx vitest run tests/supervisor-layout.electron.test.ts`:1 项通过,约 13 秒。创建/编辑 × 浅/深色 × 1440×1100、390×1100、390×480 共 12 个截图场景,测量无横向溢出、标题与底部按钮不随正文滚动,关闭及底部操作可命中;原生 Shift+Tab、Enter、Escape 验证焦点循环、取消确认和返回编辑。首次两次运行暴露测试驱动的 Enter 字符事件缺失及窗口焦点不稳定,补充字符事件和输入前显式聚焦后通过。
|
||||
|
||||
截图与 `plan-modal-measurements.json` 保存在上述产物目录。已查看宽窗口、390px 单列及短窗口的浅深主题创建/编辑截图,X 保持右上、取消与保存保持右下,短窗口只滚动正文。fixture 使用现有五页签和生产 React 组件,未读取用户数据库;模型调用 0 次。未运行全量测试,保留原暂存区与工作区改动,未提交或推送。
|
||||
|
||||
### 2026-09-24 监督者文案整理
|
||||
|
||||
设置帮助删除“提供商 RPM、TPM、Retry-After 控制及费用预算尚未实现”,保留执行软预算到期后保存正在处理的批次、暂停和从活动记录继续的说明。活动说明删除人工修改审计缺失提示;计划帮助改为选择每日或每周时间、保存并启用后运行。图谱说明实体状态以所选回顾结果为准,事实详情提示展开批次并对照来源核对模型内容。中英文同步,不承诺全局限流或完整语义识别。
|
||||
|
||||
已检查监督者设置、帮助、空态、错误展示及相关测试。真实运行错误、失败、超时、暂停和来源失效仍按原路径显示,保留恢复操作。根目录 UI-DESIGN 的既有监督者章节记录文案原则,UI 设计和用户故事同步;内部技术设计与验证记录仍保留事实边界。
|
||||
|
||||
本轮验证:
|
||||
|
||||
- `npx vitest run src/renderer/src/SupervisionReviewSettings.test.tsx src/renderer/src/SupervisorActivity.test.tsx src/renderer/src/HeartbeatCenter.test.tsx src/renderer/src/SupervisorWorkspace.test.tsx`:4 文件、57 项通过。新增中英文用例覆盖帮助文案、监督并发范围、来源核对提示,以及真实 HTTP 429 / Retry-After 错误、失败状态和继续入口的保留。
|
||||
- `npx eslint src/renderer/src/SupervisionReviewSettings.tsx src/renderer/src/SupervisionReviewSettings.test.tsx src/renderer/src/SupervisorActivity.test.tsx src/renderer/src/i18n/locales/zh-CN/heartbeat.ts src/renderer/src/i18n/locales/en-US/heartbeat.ts`:通过。`git diff --check` 通过,仅有现存行尾转换提示。
|
||||
- 中文 UI 文档扫描无阻断项,9 项复核提示均涉及实际行为边界,按事实保留;新增规则和技术记录另经人工审校。未运行全量测试、类型检查、Electron 或真实模型调用,本轮模型调用 0 次。
|
||||
|
||||
本轮仅修改监督者 Renderer、i18n、相关测试和文档;Main、Shared 只读核对。保留已有暂存及未暂存修改,未暂存、提交或推送。
|
||||
|
||||
### 2026-09-24 persistedId 并行修复记录
|
||||
|
||||
此段记录被上文真实旧库修复替代的早期方案。当时静态核对并行 Main 改动:新实体示例省略 `persistedId`,空字符串和 `null` 视为省略,其他格式错误值仍拒绝。该方案未处理真实候选中的旧文本主键,不能证明用户故障已解决。当前合同见[技术设计](./technical-design.md)。原始失败响应的具体坏值仍未知。
|
||||
|
||||
用户提供的并行验证结果为 29 项定向测试及另 1 项 IPC 测试通过;两次真实 DeepSeek 请求分别验证创建和复用,实体数保持 2,实体 ID 稳定。本轮仅记录并静态核对修复,没有复跑这些测试或模型请求;此证据不证明全部事实的语义识别,也不替代此前完整会话实验的范围与计数。
|
||||
|
||||
### 2026-09-24 五页签与计划 Modal 修正
|
||||
|
||||
对应 FR-S2、US-S22 至 US-S24。当前导航为工作回顾、故事线图谱、自动监督、活动记录、设置。计划列表移入自动监督,原有表单移入共享 Modal;设置只保留算法参数。保存失败保留草稿,未保存关闭确认,暂停计划编辑后仍暂停;计划执行记录按 configId 进入活动筛选,数据库在分页前过滤。报告、建议、概览、审计及精确结果跳转按[当前 UI 设计](./ui-design.md#应用入口)归属。
|
||||
|
||||
- 六文件定向组最终 61 项通过:HeartbeatCenter、SupervisorWorkspace、SupervisorActivity、SupervisionReviewSettings、Main supervision-activity、Preload supervision-activity。包含计划创建/编辑、失败重试、放弃确认、焦点循环与恢复、同名计划筛选、报告/审计分页,以及真实 SQLite 分页前过滤。新增换计划重置分页和忽略旧响应回归,筛选与清除保留键盘焦点。
|
||||
- App 筛选 3 项通过:跨项目计划列表与暂停计划 Modal 保存、设置保存后重开、精确结果跳转。IPC 筛选 2 项通过,验证活动 configId 经真实 handler/SQLite 过滤并拒绝空 ID;其余 App/IPC 测试未运行。
|
||||
- 最终 `npm run typecheck`、`npm run lint` 和 `git diff --check` 通过;按要求未运行全量测试。中文 UI 文档审校无阻断项,复核提示涉及执行限制和未实现边界,按事实保留。
|
||||
- Electron 生产组件 fixture 测试 1 项通过,约 66 秒,覆盖 1440/1024/390px、浅深主题、五页签归属、Modal 焦点与放弃、390×720 短窗口保存按钮命中、计划活动过滤和原生方向键。首次运行被终端 60 秒上限中断,延长后通过。
|
||||
|
||||
截图位于 `C:/Users/jiang/AppData/Local/Temp/opencode/supervisor-five-tabs/`,包括 `menu-plans-light-390.png`、`plan-modal-dark-390-short.png`、各页签浅深主题截图及 `measurements.json`。人工检查计划、Modal 和设置截图,无横向裁切;短窗口长表单内部滚动。fixture 复用实际生产组件与已有模拟数据,未读取用户数据库,未调用模型。此次不修改 Agent/远程 Runtime,无需远程执行验证。
|
||||
|
||||
开始检查时 HEAD 为 `7adae97`,监督改动仍在暂存区与工作区,未发现上轮计划提交;本轮保留其他 dirty 修改,未提交、amend 或推送。
|
||||
|
||||
### 2026-09-24 菜单归属调整
|
||||
|
||||
此段记录上轮四页签方案,已由上文五页签修正替代。当时设置包含自动监督计划、模型超时与并发、回顾算法三个纵向区块;报告与建议迁入工作回顾,运行概览和心跳审计迁入活动记录。以下保留当时验证证据,当前交互以[应用入口](./ui-design.md#应用入口)为准。
|
||||
|
||||
本轮验证:
|
||||
|
||||
- `npx vitest run src/renderer/src/HeartbeatCenter.test.tsx src/renderer/src/SupervisorWorkspace.test.tsx src/renderer/src/SupervisorActivity.test.tsx src/renderer/src/SupervisionReviewSettings.test.tsx`:50 项通过,覆盖双语菜单归属、设置无嵌套导航、计划操作、建议确认/忽略、报告及审计分页、页签切换保留展开状态、快捷按钮焦点、图谱来源与持久进度。
|
||||
- App 按测试名称筛选运行 4 项并通过:页面读取失败重试、侧栏结果跳转与 keepalive、计划独立于当前项目、模型及回顾参数保存后重开。其余 App 测试未运行。
|
||||
- `npm run typecheck`、本轮修改的 TS/TSX/MJS 文件 ESLint、`git diff --check` 通过;未运行全量测试。
|
||||
- `npx vitest run tests/supervisor-layout.electron.test.ts`:最终 1 项通过,约 51 秒。保留原图谱、异常状态、设置及活动的 1440/1024/390px 验证,新增非空菜单在 1440/390px 浅深主题的 12 个页面场景、区块归属、报告全宽、设置无范围徽标/刷新行和原生方向键导航检查。人工查看截图后修正了报告遗留双栏宽度,并补全 fixture 下次运行时间。一次复验受终端 60 秒限制中断,延长执行上限后通过。
|
||||
|
||||
截图及测量保存在 `C:/Users/jiang/AppData/Local/Temp/opencode/supervisor-menu-20260924/`,菜单截图以 `menu-` 开头,`measurements.json` 包含布局结果。Electron 使用生产 React 组件和隔离 fixture,未读取用户库、调用模型或验证真实模型产出。本轮模型调用 0 次,无提交或推送。
|
||||
|
||||
### 2026-09-24 完整会话生产验证与收尾
|
||||
|
||||
生产链路已通过一个独立完整会话的真实模型测试;较早的 129 条消息实验仍为未完成,不能据本次成功改写其状态。按用户要求,没有重发此前的两个请求,也没有重新计算其已保存批次。
|
||||
|
||||
只读选择器 `scripts/supervision-select-readonly.cjs` 在 `readOnly`、`query_only` 和读取事务下选择另一条完整会话,筛选条件为 21..40 条 user/assistant 消息、8,001..14,000 码点,并排除旧目标 ID。命中会话共 27 条消息、9,677 码点;选取的是整条会话,没有截取其头尾。仅该会话进入外部临时快照及隔离产品库,原用户库未初始化、迁移或写入。
|
||||
|
||||
首次请求输出有效 JSON,提供商以 `stop` 结束,但使用输入的 `locator.source` 作为引用,未使用带范围的 fragment ID。生产校验因此拒收。修正为:引用若是本节点提供的 source token,且只匹配一个输入片段,就映射到该片段 ID;同源多片歧义或未知来源仍拒绝。映射不扩展引用范围,不把片段升级为整条消息。针对唯一映射、歧义拒绝补了确定性测试。
|
||||
|
||||
原始 SSE 已保存在外部临时目录。继续测试时通过同一生产工厂、`SupervisorService` 和 `ModelAgentRuntime` 重放该响应,校验相同来源快照、运行配置及请求字节数;这是一次本地响应复核,没有新增付费请求。之后只为未调用的第二叶子和导航节点请求 DeepSeek。最终与每条原消息逐字比较,并检查码点区间连续性。
|
||||
|
||||
| 实测项 | 结果 |
|
||||
| --- | --- |
|
||||
| 完整覆盖 | 27 / 27 条消息,9,677 / 9,677 码点,27 个片段;重建差异及剩余来源均为 0 |
|
||||
| 生产产物 | 2 个叶子、1 个导航合并、1 个完整发布结果 |
|
||||
| 叶子记录 | 19 个事件、15 个实体、8 个变化、11 个关系,共 53 条;摘要未替换叶子 |
|
||||
| HTTP | 本会话共 3 次 HTTP 200:两个叶子和一个导航;另有一次缓存响应复核,HTTP 为 0 |
|
||||
| 本轮总付费请求 | 较早失败实验 2 次 + 本会话 3 次 = 5 次;无重复付费请求、无自动重试、无回退模型 |
|
||||
| 配置 | `deepseek-flash`;页大小 17;默认正文批次 8000、默认消息上限 20;模型超时 180 秒、软预算 600 秒、接纳并发 2 |
|
||||
| 耗时 | 首次 20,164ms,缓存复核及剩余执行 19,584ms;二者不含人工修正间隔 |
|
||||
| 手动隔离 | 自动 checkpoint 为 0;SQLite integrity_check 为 ok |
|
||||
|
||||
产物目录为系统临时目录 `opencode/review-production-complete-20260924`;来源快照在 `opencode/review-production-selected-20260924`。计数见 `metrics.json` 和只读审计入口,响应 `.private.sse` 仅供本机诊断,未进入仓库。模型输入包含完整选中正文,但未进行独立语义召回评估;“覆盖完成”不表示模型识别了每个事实。
|
||||
|
||||
最终确定性验证:12 文件定向组 136 项通过,覆盖服务/存储、迁移、设置、Preload 和页面;生产 IPC 筛选 22 项通过,其余 128 项未运行。完整 App 的设置保存及重开 1 项、AssistantDatabase 的监督历史筛选 4 项通过。Electron 使用生产组件,在 1440/1024/390px、浅深主题通过布局测试。首次视觉测试发现脚本仍只选中第一个设置表单,改为测量整个设置面板的可见控件后通过;两个表单同名保存按钮也已分别命名。
|
||||
|
||||
收尾新增用例确认 schema 46 已提交的 2,000 码点前缀不重读,只处理后续 1,300 码点;来源变化保留旧批次并要求新 run,后续自动检查不会永久卡在旧版本。来源上下文按已保存 messageId 和片段返回,避免同时间消息误定位或用当前正文冒充历史片段。`npm run typecheck`、本轮修改及新增 TS/TSX/CJS/MJS 文件的定向 ESLint 均通过。没有运行全量测试、提交或推送。
|
||||
|
||||
最终定向命令:
|
||||
|
||||
```text
|
||||
npx vitest run src/main/assistant/supervision-review.test.ts src/main/assistant/supervisor-service.test.ts src/main/assistant/supervision-history.test.ts src/main/assistant/supervision-activity.test.ts src/main/assistant/incremental-review.test.ts src/main/assistant/supervision-model-pool.test.ts src/main/application-settings-store.test.ts src/preload/supervision-activity.test.ts src/renderer/src/SupervisorActivity.test.tsx src/renderer/src/SupervisionReviewSettings.test.tsx src/renderer/src/HeartbeatCenter.test.tsx src/renderer/src/SupervisorWorkspace.test.tsx
|
||||
npx vitest run src/main/ipc.test.ts -t "supervision|heartbeat enabled gates|admits .*reports before claiming"
|
||||
npx vitest run src/renderer/src/App.test.tsx -t "saves Supervisor timeouts and concurrency"
|
||||
npx vitest run tests/supervisor-layout.electron.test.ts
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
### 2026-09-24 生产接线与真实模型失败
|
||||
|
||||
本节记录首轮生产接线及较大样本失败;最终补充证据见上一节。下方同日的原型、设计及旧 collector 记录是更早阶段证据。
|
||||
|
||||
已接线:Main 手动 IPC 与心跳回调共用 `createProductionSupervisorService`;schema 47 保存来源清单、配置、叶子和导航节点;稳定键分页、项目/会话轮转、Unicode 片段、逐批事务、失败/预算后继续、严格发布判定已进入生产服务。活动及 Preload 增加暂停、继续、分页批次详情;设置存储、App 回调和中英文表单已接通页大小、字符/消息批次上限及软预算。应用仍默认关闭。实现合同和未完成项见[生产接线与剩余边界](./review-scheduling-design.md#0-生产接线与剩余边界)。
|
||||
|
||||
确定性验证使用隔离 SQLite、生产 IPC 和可控 Runtime,先于真实请求执行。覆盖超过 20 会话/20 消息的 506 条来源、Unicode 精确重建、手动/自动位置隔离、失败后重开、冻结配置、事务回滚、暂停 signal 到 Runtime、成功批次不重算及历史结果来源。后续增加项目轮转、分页大小不改变语义块、发布失败后复用导航节点、Preload 方法和 UI 操作验证。
|
||||
|
||||
真实验证入口为 `scripts/supervision-production-live.cjs` / `.ts`,使用与 Main 相同的生产工厂及 `ModelAgentRuntime`,不使用 `review-algorithm.mjs`。输入为已有只读快照中的完整目标会话,截止时间为 2026-09-23 16:09:45.919 UTC,共 129 条消息;逐消息集合 SHA-256 与原探测清单一致。只将选中会话导入临时产品库,没有复制整份用户库,未打开或改写原库及用户设置。
|
||||
|
||||
| 实测项 | 结果 |
|
||||
| --- | --- |
|
||||
| 配置 | DeepSeek `.env` 中的 `deepseek-flash`;页大小 17、正文批次 16000、消息上限 50、并发接纳 2、模型超时 180 秒、软预算 600 秒 |
|
||||
| HTTP | 2 次,均 200;无付费重试、无模型回退、无工具 |
|
||||
| 结果 | 第 1 批校验并保存,第 2 次输出 JSON 无效;逻辑 run 为 failed,没有完整结果 |
|
||||
| 来源清单 | 129 条消息,54,979 个 Unicode 码点 |
|
||||
| 已持久化覆盖 | 16,000 码点;28 条完整消息及 1 条部分消息,共 29 个片段;101 个来源未完成 |
|
||||
| 已保留输出 | 10 个事件、9 个实体、9 个变化、9 个关系,共 37 条结构化记录 |
|
||||
| 完整发布 / 自动 checkpoint | 均为 0;此次为手动运行,失败批次没有推进位置 |
|
||||
| 耗时 | 56,775ms;HTTP 测量仅记录响应头延迟,不能当作完整模型耗时 |
|
||||
| 完整性 | 临时库 `PRAGMA integrity_check` 为 ok |
|
||||
|
||||
私有产物位于系统临时目录 `opencode/review-production-20260924`。`metrics.json` 与只读命令 `node scripts/supervision-production-audit.cjs <外部临时目录>/assistant.sqlite` 可核对上述计数;不在仓库保存正文或凭据。失败响应正文没有保存,尚不能确定 JSON 无效是格式问题还是输出截断;不补造原因。按用户要求未重发失败付费请求,**该 129 条消息样本没有完成真实模型全会话覆盖验收**。16000 字符测试上限也不能视为经过可靠性验证的推荐默认。
|
||||
|
||||
已完成的定向命令包括:
|
||||
|
||||
```text
|
||||
npx vitest run src/main/ipc.test.ts -t "production supervision IPC|collects supervision within the exact UI timeRange"
|
||||
npx vitest run src/main/assistant/supervision-review.test.ts src/main/assistant/incremental-review.test.ts src/main/assistant/supervision-history.test.ts src/main/assistant/supervision-model-pool.test.ts src/renderer/src/HeartbeatCenter.test.tsx src/renderer/src/SupervisionReviewSettings.test.tsx src/preload/supervision-activity.test.ts
|
||||
npx vitest run src/main/assistant/assistant-database.test.ts -t supervision
|
||||
```
|
||||
|
||||
前述 IPC 3 项、七文件 45 项及数据库历史 4 项通过。更早一次十文件定向运行 116 项通过,2 项因两个表单保存按钮同名失败;已分别命名并复查相关文件。类型检查、最终定向 lint 和收尾验证在本节后续记录。未运行全量测试、提交或推送,保留已有 dirty 改动。此次没有修改 Agent、远程 Runtime 协议或其取消实现;监督父 signal 使用既有 Runtime 接口,本轮不声称完成远程真实验证。
|
||||
|
||||
### 2026-09-24 DeepSeek 全目标会话隔离原型
|
||||
|
||||
使用 `.env.deepseek.local` 的 `DEEPSEEK_BASE_URL`、`DEEPSEEK_MODEL`、`DEEPSEEK_API_KEY`;配置模型及 12 次响应的实际模型均为 `deepseek-flash`。直接调用配置端点,没有 GPT 回退、自动付费重试或工具调用。原型复用 `SupervisionModelPool`,实验并发参数设为 2,实测峰值为 2;没有修改应用设置。单请求超时 180 秒,输出预留 16,384 token,12 次均以 `stop` 结束。
|
||||
|
||||
启动前核对旧探测的 PID、创建时间、启动记录及命令路径,终止已取消的全会话 GPT 探测。旧尝试记录 10 次已插桩 HTTP 及 2 次未插桩调用,不计入下表的 DeepSeek 12 次。原始消息复用 `review-full-20260924/sources.private.json`,核验冻结消息集合的 SHA-256 及逐消息重建;未沿用旧 GPT 事实或导航。该快照来自既有 `readOnly`、`query_only` 采集入口,本轮没有打开原库,也未初始化、迁移或写入原库。
|
||||
|
||||
| 测量 | 结果 |
|
||||
| --- | --- |
|
||||
| 完整目标范围 | 1 个会话,129 / 129 条 user/assistant 消息,8 页;截至 2026-09-23 16:09:45.919 UTC 的冻结快照 |
|
||||
| 正文覆盖 | 54,979 / 54,979 个 Unicode 码点;本样本 UTF-16 单元数相同;135 个片段、7 个叶子批次,每批正文不超过 8,000 UTF-16 单元 |
|
||||
| 来源核验 | 重建差异 0、覆盖空洞 0、引用码点区间错误 0 |
|
||||
| 持久化结果 | 354 条叶子事实、503 条引用;其中一个重复短引文保留 2 个明确标为歧义的候选位置,没有猜选唯一位置 |
|
||||
| 导航 | 18 组完整索引 354 条事实,每条一次;生成导航前后叶子内容散列一致 |
|
||||
| 真实调用 | 7 次叶子提取 + 2 次独立检查点选择 + 1 次导航 + 2 次语义评分,共 12 次 HTTP 200 |
|
||||
| 首批速度 | 并发两请求分别 20.376 / 9.236 秒,首轮 20.420 秒;先保存 16,000 码点并正确返回 partial |
|
||||
| 叶子速度 | 单请求 9.236..20.376 秒;七请求耗时之和 107.334 秒,含并发重叠,不能当作墙钟耗时 |
|
||||
| 全实验时间 | 四次执行的进程内耗时合计 111.955 秒;首次请求至末次响应约 333.869 秒,包含检查、修改及人工重启间隔;另两次离线验证合计约 38ms |
|
||||
| token | 提供商报告输入 188,377、输出 56,009;其中叶子提取为 34,771 / 35,852,其余用于评估及导航;未换算货币费用 |
|
||||
| 续跑与无变化 | 三个已保存叶子在校验失败后复用;失败批次的已有响应重新本地校验,未再次请求。最终重启复用七批,无变化检查 4.362ms、模型调用 0;独立离线验证 19.826ms、模型调用 0 |
|
||||
|
||||
目标会话在旧探测后继续增长,先前记录的 119、123 条与本轮 129 条属于不同读取时点,不能混为同一分母。本轮覆盖完整目标会话,不等于覆盖旧全局区间的 830 条消息、63 个会话,也未测试任务、记忆和知识引用的全量采集。
|
||||
|
||||
独立检查点选择请求只看到两半完整原文,没有看到叶子结果,也未注入指定引文。模型返回 125 个检查点,超过要求的每半 24..32 个;排除 2 个原消息中不存在的逐字引文后,评分采用 123 个。第二半的消息回执虽有 64 项,但 ID 集合不匹配,因此仅声称全部 129 条已提交给评估器,不以模型回执证明它逐条读懂。
|
||||
|
||||
| 独立检查类别 | 有效检查点 | 模型判为保留 | 遗漏 / 失真 |
|
||||
| --- | --- | --- | --- |
|
||||
| 决策 | 46 | 46 | 0 / 0 |
|
||||
| 修正 | 9 | 9 | 0 / 0 |
|
||||
| 否定约束 | 1 | 1 | 0 / 0 |
|
||||
| 未解决事项 | 5 | 5 | 0 / 0 |
|
||||
| 助手陈述归属 | 62 | 62 | 0 / 0 |
|
||||
|
||||
模型评分未报告不受引用支持的事实;离线核验 123 项均有原消息的引用,118 项在所引用叶子中保留完整逐字检查引文,另 5 项依赖语义比较。评估器报告导航短摘要遗漏 69 项叶子细节,相关叶子仍完整保留。评估独立性仅指先从原文选择检查点、再比较输出,提取与评分使用同一模型;缺少人工金标准,否定类仅一项,跨两半的修正关系没有单独审计。这些数字不构成全部事实召回率或“无语义遗漏”的证明。
|
||||
|
||||
实际出现两次本地校验失败:重复引文导致叶子拒收,以及评估输出违反检查点数量与引用合同。成功批次在失败后仍可读取;原型为重复引文保存全部候选定位,对无效评估引文明确排除并计数。原始响应保留在临时目录,三次缓存读取不产生 HTTP。确定性测试另外覆盖批次失败、截断、取消和预算暂停后的恢复,未刻意制造提供商失败来增加付费调用。
|
||||
|
||||
执行入口为 `scripts/review-deepseek.cjs` / `scripts/review-deepseek.mjs`,共享原型为 `scripts/review-algorithm.mjs`。私有正文、来源、模型响应及 SQLite 均位于系统临时目录;仓库仅包含脚本、合成测试和不含正文的测量记录。产物目录:`C:/Users/jiang/AppData/Local/Temp/opencode/review-deepseek-20260924`。可公开计数见 `metrics.json`、`coverage.json`;私有依据见 `batches.sqlite`、`facts.private.json`、`navigation.private.json`、`gold-*.validated.private.json`、`evaluation-*.private.json`。
|
||||
|
||||
复现命令如下。`first`、`finish`、`verify` 启动独立进程,须待上一次退出再运行下一条;`status` 仅读取计数。现有输出目录会复用已保存产物;更换输出目录重跑会新增最多 12 次付费调用。
|
||||
|
||||
```powershell
|
||||
$source = "$env:TEMP/opencode/review-full-20260924"
|
||||
$output = "$env:TEMP/opencode/review-deepseek-20260924"
|
||||
node scripts/review-deepseek.cjs first $source $output .env.deepseek.local 2
|
||||
node scripts/review-deepseek.cjs status $output
|
||||
node scripts/review-deepseek.cjs finish $source $output .env.deepseek.local 2
|
||||
node scripts/review-deepseek.cjs verify $source $output .env.deepseek.local 2
|
||||
node --test scripts/review-algorithm.test.mjs
|
||||
npx vitest run src/main/assistant/supervision-model-pool.test.ts
|
||||
npx eslint scripts/review-algorithm.mjs scripts/review-algorithm.test.mjs scripts/review-deepseek.cjs scripts/review-deepseek.mjs
|
||||
```
|
||||
|
||||
10 项原型测试、3 项共享池测试及上述定向 ESLint 通过;没有运行全量测试、提交或推送。测试覆盖分页同序键、Unicode 重建和引用位置、空洞、预算暂停、失败后重开、无变化零调用、来源版本变化、输出拒收、取消以及导航不删除冲突叶子。该原型未接生产 IPC/UI、自动 checkpoint、全局多项目调度或多层汇总;真实请求绕过产品 ModelRuntime。生产实现仍存在下节记录的采集遗漏,未来实现边界见[分块调度设计](./review-scheduling-design.md#12-隔离原型的设计结论)。
|
||||
|
||||
### 2026-09-24 模型阶段超时、并发与设计边界
|
||||
|
||||
- 本次静态核对当前新代码:报告 `heartbeatReportTimeoutSeconds`、监督 `supervisorOrganizeTimeoutSeconds` 已接入共享校验、设置存储、App/设置表单及 Main 摘要器;整数 30..600 秒、默认 240。报告在等待池前冻结值,监督在摘要器入池前读取值,均在获槽及 Runtime 解析后才计时。完整控制及未覆盖环节见[控制清单](./review-scheduling-design.md#当前超时与调度控制清单已实现)。
|
||||
- `supervisorModelConcurrency` 已接入同一设置链路,整数 1..4、默认 1;报告与监督共用 FIFO 池,普通聊天排除,降限不终止在途项。报告获槽后才领取,每次到期领取最多一条;租约为 `max(300, 报告超时 + 60)` 秒,实际请求开始前按运行 ID、owner、attempt、claimed 和未过期条件校验并刷新。报告提交或无变化后先释放槽位,再进入下游监督。尚无提供商全局并发、RPM、TPM 或监督层 Retry-After 控制。
|
||||
- 本轮协作提供的新增验证记录:24 项并发相关测试、33 项租约相关测试及 typecheck、lint 通过。本次文档修订仅核对实现,没有重新执行这些测试;记录未附精确过滤命令,不补造命令或与此前批次相加。
|
||||
- 实现代理报告:87 项测试、App/IPC 聚焦用例、另 19 项取消测试,以及 typecheck、lint 通过。本次文档修订未复跑这些命令,不将数量相加当作独立用例总数,也不推断全量测试已通过。
|
||||
- 实现代理报告的真实远程取消验证耗时 30.75 秒,清理后本次拥有的进程为 0。该证据验证相应 Runtime 取消/清理路径,不证明完整分页调度、父级取消、批次持久化或跨重启续跑已经实现。
|
||||
- 真实用户数据探测结果见下节。两次原始失败耗时与旧 240 秒时限一致,但缺少 abort 原因记录,尚不能证明证据规模是超时根因。
|
||||
- [回顾分块调度设计](./review-scheduling-design.md)已按用户授权加入完整分页、保留叶子事实/来源、成功批次及位置持久化、预算/失败后继续和严格完成判定。上述调度、存储合同、状态及 UI 仍待实现;当前有界单次成功不等于整个时间区间完整覆盖。
|
||||
|
||||
### 2026-09-24 真实会话覆盖与模型小样本
|
||||
|
||||
原始数据库以 `readOnly` 和 `query_only` 打开,只读事务内分析,未初始化或迁移。用户所述安装路径不存在;实际安装位于 `D:\Program Files (x86)\GoodBuddy`,数据位于用户配置目录。统计使用当前数据库内 9 月 16 至 23 日记录,并非还原该次运行时的数据库快照。
|
||||
|
||||
| 测量 | 结果 |
|
||||
| --- | --- |
|
||||
| 区间内会话消息 | 830 条、63 个会话、5 个项目 |
|
||||
| 手动采集器返回 | 202 条消息、70,491 个 UTF-16 单元 |
|
||||
| 实际进入当前摘要器 | 100 条消息、17,714 个 UTF-16 单元;覆盖 9 个会话 |
|
||||
| 目标会话 | 区间内 80 条中仅 20 条进入摘要器;正文覆盖 3,693 / 33,247,即 11.11% |
|
||||
| 离线完整分块审计 | 123 条消息分为 127 块,重建差异为 0;未对这些块执行模型提取 |
|
||||
| 只读采集耗时 | 手动 182ms、单批增量 375ms、整个探测 805ms |
|
||||
|
||||
整条消息遗漏发生在 SQL 数量限制及服务取前 100 项处;本样本进入摘要器的消息未再发生正文裁剪。100 条来源均缺少 `locator.messageId`,现有会话定位不能代替逐消息覆盖凭据。完整分页与消息定位仍为待实现项。
|
||||
|
||||
隔离模型实验使用两条已保存的真实样本,共 814 个 UTF-16 单元,经 `createDefaultModelRuntime` 调用 `gpt-6-astra`。3 次 HTTP 请求均为 200,未付费重试;两次提取耗时 34.665 / 35.413 秒,导航概述 6.703 秒,实验总耗时 76.813 秒。模型输入与原消息逐字一致,要求保留的两处引用及消息引用均核验通过,导航引用两项已保留事实;无变化复查耗时 5.99ms,模型请求为 0。原应用设置在完成时逐字节未变。
|
||||
|
||||
两处引文已在提示中明确指定,因此该实验仅验证小样本的引文保留、引用正确性与保留产物,不能证明自由提取完整性、全会话无失真或未来调度器性能。实验采用自定义单来源准入及直接模型 Runtime,未经过完整生产 IPC/UI。原库两次失败运行分别耗时 240.211 / 240.216 秒,监督结果仍为 0;样本成功不等于原问题已修复。
|
||||
|
||||
复核产物保存在本机临时目录 `opencode/review-fidelity-20260923-current`:`verification-current/coverage.json`、`live-resolved/live-metrics.json`、`verification-current/verification.json`。执行入口为私有临时脚本 `resume-supervision-probe.cjs` 和 `verify-supervision-probe.cjs`;核验退出码为 0。启动器等待曾超时,最终产物记录三次请求完成且进程随后退出,未因此重发请求。此前依赖加载失败均发生在请求发送前。会话正文及配置副本未加入仓库。
|
||||
|
||||
### 既有实现
|
||||
|
||||
- 共享监督者契约已接入 Main 服务,模型请求包含系统指令;证据总字符预算为 48,000,输出仍限制为 100KB,并校验本次证据集内的来源引用、输出局部实体 ID 和关系端点。手动回顾当前收集会话、任务、消息已有的知识引用和已确认记忆;记忆使用 `memory` 来源类型。
|
||||
- SQLite schema 43 包含监督运行、结果、来源、故事线、事件、实体、实体关联和关系表,并保存每次结果的实体与关系内容。来源保存本地 library/document/chunk 或外部 locator 元数据;持久化与升级规则见[技术设计](./technical-design.md)。
|
||||
- 初版监督者入口只有一个页面页头和四项顶层页签,原有计划、概览、建议及报告当时保留在设置页内部。2026-09-24 已按上文菜单调整迁移内容;手动运行仍通过 Supervisor 通道,未绑定自动计划时也可用。
|
||||
- 工作回顾显示结果自身冻结的 scope/time range。故事线视图使用真实数据库结果,外围事件按实际时间逆时针排列并保留缺口,事件实体连线和来源过滤来自 Main 查询投影,图形不可用时仍可通过事件/实体/关系列表操作。
|
||||
- `HeartbeatService` 在定时或手动心跳成功落库后调用同一个 `SupervisorService`,按心跳范围和回顾窗口自动保存监督结果;投影失败不会改写已经成功的心跳状态。
|
||||
- 监督图谱提供 Main/Preload IPC 的来源读取、实体确认/修订/撤销和关系确认/移除;关系移除保存 `revoked` 状态,不删除来源,后续自动 run 不恢复相同身份的关系。
|
||||
- 右侧助手栏已读取监督反馈,支持来源查看、继续讨论预览与确认发送,以及本地知识库实体写入预览和确认提交。
|
||||
- 本地会话来源可按会话 ID 和发生时间读取上下文;没有当前会话时,继续讨论操作明确禁用。
|
||||
- 应用中心已增加监督者开关,继续复用 `heartbeat` 导航 ID;关闭后已打开页面显示关闭状态并禁用工作区操作,不删除既有心跳计划或历史数据。导航显示同时遵守 enabled 和 pinned 语义。默认值及执行边界以[应用启停与执行](./logic-design.md#应用启停与执行)为准。
|
||||
|
||||
## 验证证据
|
||||
|
||||
### 2026-09-23 Desktop 0.13.13 侧栏布局回归
|
||||
|
||||
- 固定目标与刷新改用标题旁的共享图标按钮;目标显示会话/任务名称,缺失名称使用未命名文案。
|
||||
对应 US-S25,行为见[会话侧栏](./ui-design.md#会话侧栏),未改变监督运行或取消逻辑。
|
||||
- `RightAssistantSidebar.resize.test.tsx` 66 项和 `SupervisionCard.test.tsx` 4 项通过。
|
||||
`tests/supervisor-layout.electron.test.ts` 的常规工作区场景通过,额外设置
|
||||
`GOODBUDDY_SUPERVISOR_SIDEBAR=1` 后的侧栏场景也通过:480/300/200px、
|
||||
浅深主题、长标题、34px 按钮、键盘焦点与无横向溢出,共 6 种布局。
|
||||
- 完整 typecheck、lint 通过,真实模型调用 0 次。使用隔离组件 fixture,不代表真实
|
||||
用户数据库端到端验收;发布验证范围见[候选记录](../../development/release-preparation-0.13.13.md)。
|
||||
|
||||
### 既有实现验证
|
||||
|
||||
- `npx vitest run src/renderer/src/SupervisorWorkspace.test.tsx src/renderer/src/HeartbeatCenter.test.tsx`:17 个测试通过,覆盖统一页签、无计划手动回顾、加载状态、图谱真实连线与来源过滤。
|
||||
- `npx vitest run src/main/assistant/supervisor-service.test.ts src/main/assistant/assistant-database.test.ts src/main/application-settings-store.test.ts src/renderer/src/ApplicationCenter.test.tsx`:166 个测试通过。
|
||||
- `npm run typecheck`:通过。
|
||||
- `npm run lint`:通过。
|
||||
- `git diff --check`:通过;仅有现有文件的行尾转换提示。
|
||||
|
||||
### 2026-09-23 页面与视觉验证
|
||||
|
||||
- 生产页面按 [UI 设计](./ui-design.md) 使用三栏工作台、真实实体名称、逆时针事件环、实际关联连线、选中强调与来源详情。颜色复用现有图谱令牌,未修改全局主题。密集数据按批显示,列表保留全部返回记录;事件浏览不还原历史实体状态。
|
||||
- 设置只保留一个一级标题,缺少监督者 bridge 不再回退旧概览;无计划手动回顾、项目/时间范围、来源关联过滤均有组件回归。来源异步读取期间切换事件时,迟到正文不会出现在无关联事件下。
|
||||
- `npx vitest run tests/supervisor-layout.electron.test.ts src/renderer/src/SupervisorWorkspace.test.tsx src/renderer/src/HeartbeatCenter.test.tsx src/main/assistant/supervisor-service.test.ts src/main/assistant/assistant-database.test.ts`:134 项通过。Main 测试使用隔离数据库和注入摘要器;视觉入口渲染生产 React 组件,未读取真实用户数据或调用付费模型。
|
||||
- Electron 实测 1440、1024、390px 与浅深主题,页面、壳层和内容容器均无横向溢出;1440px 三栏实测为 216 / 863 / 280px。窄屏画布内部滚动保留可读字号,缩放后 SVG 文字不低于 11px;另验证 9 个容器临界宽度、长标题、80 个同时间事件、40 个实体、来源详情、设置表单、加载/空/错误/缺 bridge 状态。
|
||||
- 截图与 `measurements.json` 输出到本次指定的临时目录 `opencode/supervisor-visual`。代表文件为 `selected-source-1440.png`、`light-1024.png`、`light-390.png`、`dark-1440.png`、`dense-selected.png`、`selected-source-390.png`、`settings-plans-390.png`。可复用的验证和交互预览入口见 UI 设计末段。
|
||||
- 全量 `npm test`:416 个文件通过、9 个跳过、1 个失败;4902 项通过、67 项跳过、1 项失败。失败为 `local-runtime-reuse.test.ts` 的十项目 OpenCode 压缩断言(`compacted: false`),单独复跑该文件的 2 项均通过。本次未改动 Runtime 实现,未将这次全量运行记为全绿。
|
||||
- 页面收尾后重新执行 `npm run typecheck`、`npm run lint` 通过。App 已在上述全量测试中通过;一次包含 App 的额外定向命令超过终端 120 秒限制,随后缩小到上述 5 个相关文件完成验证。
|
||||
|
||||
## 当前边界
|
||||
|
||||
### 2026-09-24 算法设置文档补充
|
||||
|
||||
已静态核对共享设置合同、设置存储及隔离原型参数,补充[设置配置清单](./review-scheduling-design.md#4-设置配置清单)和 [UI 交互目标](./ui-design.md#算法配置设置目标)。本次仅更新文档;新增分页、分块、预算、续跑及额度控件均未实现,原型值不作为产品默认。未运行代码、测试、探测或模型调用,未修改应用配置和用户数据库。
|
||||
|
||||
### 2026-09-23 Main 与侧栏定向修复
|
||||
|
||||
- 已修复:继续讨论不再把数组写入要求字符串的 `serializedContexts`;请求省略 Runtime 时复用会话选择。真实 SQLite 入队后,生产队列解析器成功派发请求。
|
||||
- 已修复:每次结果独立分配实体和来源 UUID,映射事件、关系、实体变化及来源引用。两次重叠 run 和跨项目相同模型 ID 均有 SQLite 回归,用户修订的名称和说明保留,所有结果均可读取自己的来源及 locator。schema 41 升级并重复打开后保留旧记录和确认状态。
|
||||
- 已修复:知识库预览与提交均检查本地库及实体所属库;侧栏确认框展示实际返回内容,取消与写入失败后可重新预览。测试覆盖真实本地实体读写,外部只读及提交前归属变化由注入状态验证。
|
||||
- 定向验证:`npx vitest run src/main/assistant/assistant-database.test.ts src/main/ipc.test.ts src/renderer/src/RightAssistantSidebar.resize.test.tsx src/main/assistant/supervisor-service.test.ts -t 'supervision|shares bounded evidence'`,4 个文件中选中的 9 项通过,其余 289 项未运行。本轮未运行全量测试、typecheck、lint 或真实模型请求;前文历史验证不能代替本轮验证。
|
||||
- 当时未修复的双边时间范围及记忆背景语义已由下节定向修复覆盖。跨 run 实体识别由本轮候选身份实现补齐;旧实现已经覆盖或忽略的历史内容无法从现有记录自动还原。
|
||||
|
||||
### 2026-09-23 第 4 问题:双边时间范围
|
||||
|
||||
- 已接通真实 `supervisionRun` IPC handler → collector → SQLite → 摘要输入 → 结果保存。监督者显式传递请求时间区间,不再扩大到一小时;消息与会话在 LIMIT 前按闭区间筛选,任务按创建或完成时间筛选。所选项目复用存在且 active 的检查;普通 Heartbeat 不传明确区间时仍使用原下界行为。
|
||||
- 记忆证据使用真实 `updated_at`,locator 保留 `created_at`/`updated_at` 并标明当前背景;摘要指令禁止据此推断历史内容。任务的当前状态也不作为历史状态快照。实现规则与剩余容量边界见[技术设计](./technical-design.md)。
|
||||
- 定向验证:`npx vitest run src/main/ipc.test.ts src/main/assistant/heartbeat-database.test.ts -t 'collects supervision within the exact UI timeRange|builds one bounded snapshot across only the selected projects'`,2 个文件中选中的 3 项通过,133 项未运行。两项新 IPC 测试分别覆盖 global/projects:15 分钟精确范围与端点、25 条区间后消息及 21 个区间后会话不挤掉区间消息、旧任务期间完成、期内创建但期后完成的任务选用创建时间、记忆非采集时刻、archived 项目拒绝。已有 Heartbeat 项目快照用例通过。
|
||||
- 首两次定向运行因新增测试 fixture 缺少 `description`、消息 `state` 失败,补齐实际契约后通过。本轮所改 tracked 文件的 `git diff --check` 通过,仅有行尾转换提示。验证使用真实隔离 SQLite 与生产 IPC/collector,模型 Runtime 为测试替身;未运行 UI/Electron、全量测试、typecheck 或 lint,真实模型调用为 0。未改 Renderer/CSS、数据库 schema、远端 Agent/Runtime,不涉及独立远程执行逻辑;未提交。
|
||||
|
||||
### 2026-09-23 结果定位、目标过滤与候选身份
|
||||
|
||||
- 对应 US-S02、US-S05、US-S10、US-S13、US-S25:overview 返回稳定结果/故事线 ID;Main 校验 graph 请求归属,并按结果读取当次事件、对象与来源。历史选择和图谱共享结果定位,请求序号阻止迟到响应覆盖新选择。
|
||||
- 侧栏 overview 通过类型化 Conversation/Task 目标查询;SQLite 在 LIMIT 前同时校验匹配来源和项目范围。无匹配结果不展示全局第一条。卡片展示结果范围和时间区间,目标切换清空来源与预览并忽略旧请求。
|
||||
- tasks 工作栏实例新增可持久化的会话/任务监督绑定;固定、切换聊天、取消固定均连接到实际卡片查询,任务列表仍使用原项目范围。继续讨论预览展示真实发送目标,回调显式传会话 ID,Main 复用该会话配置。固定任务找不到关联会话时不启用发送。
|
||||
- 模型只能用 `persistedId` 引用 Main 提供的同 scope 候选 UUID;服务与保存事务分别校验。未显式引用候选的实体和所有来源继续分配新 UUID。SQLite 回归覆盖重复 run 复用、不同项目拒绝、裸模型 ID 不合并、确认/修订字段及关系理由保护、移除关系后不自动恢复。
|
||||
- schema 43 升级保存现有可定位的结果成员,后续 run 不改写旧结果内容;测试从 schema 42 升级并重复打开,比较历史图谱与来源,包括没有直接来源引用、但两端实体归属明确的关系。已有 schema 41 保留数据用例继续通过。缺失归属、曾被覆盖的描述仍无法重建。
|
||||
- 定向测试共覆盖 32 个不同用例,最终均通过。主命令为 `npx vitest run src/main/assistant/supervision-history.test.ts src/main/assistant/assistant-database.test.ts src/main/assistant/supervisor-service.test.ts src/main/ipc.test.ts src/renderer/src/SupervisorWorkspace.test.tsx src/renderer/src/SupervisionCard.test.tsx src/renderer/src/RightAssistantSidebar.resize.test.tsx src/renderer/src/HeartbeatCenter.test.tsx -t '[sS]upervis'`:29 项通过,2 项失败来自新增同步 IPC 拒绝断言误用 `.rejects`。修正后仅复跑 `src/main/ipc.test.ts src/shared/workbar-contracts.test.ts -t '[sS]upervis'`,5 项通过,包含新增工作栏绑定契约用例。期间修正了旧侧栏 fixture 缺少目标、迁移比较未忽略成员顺序和图谱按钮查询歧义。
|
||||
- 收尾增加同来源但项目范围不匹配的过滤断言,以及无直接来源引用的关系迁移断言;仅复跑 `src/main/assistant/supervision-history.test.ts src/main/assistant/assistant-database.test.ts -t supervision`,5 项通过,其余 103 项未运行。
|
||||
- `npm run typecheck` 运行一次,通过。对本轮涉及的 TS/TSX 执行一次定向 ESLint,0 error、1 条 ref 清理 warning;调整后仅复查该文件及新增契约测试,迁移收尾只复查数据库与对应测试,均为 0 error、0 warning。未重复检查整个 App。`git diff --check` 通过,仅有现有行尾转换提示。
|
||||
- 本轮未跑全量测试或 App 整文件,未启动 Electron 或调用真实模型;模型请求数为 0。验证包含生产 IPC/collector 与真实隔离 SQLite,以及真实 React 组件的异步交互测试,不能据此声称已完成真实模型或完整桌面端到端验收。未修改 Agent/远程执行协议或 Runtime 生命周期;此次改动在桌面 Main 的监督者输入、存储和查询层生效。全部 dirty 改动保留,未提交。
|
||||
|
||||
### 2026-09-23 侧栏结果直达图谱
|
||||
|
||||
- 对应 US-S25:SupervisionCard 有匹配结果时提供中英文“在图谱中查看 / View in graph”原生按钮。App 通过显式回调接收 resultId,遵守现有离页检查并打开 heartbeat 路由,向 HeartbeatCenter / SupervisorWorkspace 传递类型化导航请求;没有新增页面或 window 事件。沿用 schema 43 与现有 graph IPC,未改 Main、Preload 或存储。
|
||||
- 图谱页签、键盘焦点和历史结果选择共用现有页面状态;同一结果再次点击、不同结果切换及 keepalive 再进入均重新定位,固定监督目标保持。导航切换立即清空旧图谱和详情;overview、graph、来源及操作的迟到响应不会改写新选择。overview 没有返回指定结果时仍按该 ID 请求图谱;失败可重试原 ID,不回退默认最新结果。
|
||||
- 新增 7 个定向用例,最终均通过。首轮命令:`npx vitest run src/renderer/src/SupervisionCard.test.tsx src/renderer/src/SupervisorWorkspace.test.tsx src/renderer/src/HeartbeatCenter.test.tsx src/renderer/src/App.test.tsx -t 'graph navigation'`,3 项通过、2 项因测试使用旧文案失败。修正为实际“工作栏”“历史结果”文案并补充两项竞态测试后,仅运行 `src/renderer/src/SupervisorWorkspace.test.tsx src/renderer/src/App.test.tsx -t 'graph navigation'`,5 项通过、296 项未运行。请求状态收尾后仅复跑 SupervisorWorkspace 的同一筛选,4 项通过、11 项未运行。
|
||||
- 测试覆盖真实 React 组件与 App 路由接线:首次只请求侧栏结果、固定目标、同结果重复点击、不同结果、keepalive 页面实例不变、历史选择同步、页签方向键及焦点、中英文/空态入口、旧 overview/graph/来源迟到、指定 ID 不在 overview 和失败重试。IPC 在这些 UI 测试中为替身,未声称真实桌面端到端验收。
|
||||
- 本轮涉及的 10 个 TS/TSX 文件定向 ESLint 通过,0 error、0 warning。`npx tsc --noEmit -p tsconfig.node.json` 与 `npx tsc --noEmit -p tsconfig.web.json` 各一次有效源码检查通过;此前附加 `--incremental false` 的启动因 composite 配置报 TS6379,未执行源码检查。`git diff --check` 通过,仅有现有行尾提示。
|
||||
- 未运行 App 整文件、全量测试、全量 lint、Agent typecheck、Electron 或真实模型请求;模型调用为 0。此次仅修改桌面 Renderer 导航和异步展示,不影响 Agent/远程 Runtime。保留全部其他 dirty 改动,未提交。
|
||||
|
||||
### 2026-09-23 自动监督设置
|
||||
|
||||
- 对应 US-S22 至 US-S24:当时设置直接显示完整自动监督配置,并保留运行概览、建议、报告与记录次要面板;2026-09-24 已迁移这些面板并移除内部导航。界面统一中英文命名,保留内部 heartbeat 标识和已有内容。无计划时明确显示仅支持手动回顾,填写草稿不触发保存或运行。
|
||||
- 核对共享 recurrence 合同及实际调度器后,仅展示已支持的每日、每周指定时间;页面明确说明不支持分钟间隔。编辑保留原时区和暂停状态,计划列表显示范围、周期、时区、回顾窗口与保留期限,继续提供编辑、暂停、恢复、运行及确认删除。
|
||||
- 已核对 App、Preload、Main 和 HeartbeatService 的既有 CRUD 接线,本轮未修改调度器或创建默认计划。保留工作区原有默认关闭及 Main 启用检查改动。
|
||||
- 定向测试:`HeartbeatCenter.test.tsx` 23 项、`application-settings-store.test.ts` 50 项通过;`heartbeat-recurrence.test.ts`、`heartbeat-service.test.ts`、`heartbeat-database.test.ts` 合计 17 项通过;`ipc.test.ts -t 'heartbeat enabled gates'` 4 项通过、128 项未运行。覆盖中英文直接入口、每日/每周提交、多项目、无效窗口、失败保留草稿、时区及暂停状态、各项计划操作,以及默认关闭、无计划不运行和启停行为。首轮整文件运行发现原有顶层图谱测试未清理 DOM,补充文件级 cleanup 后通过。
|
||||
- `tests/supervisor-layout.electron.test.ts` 通过:生产 React 组件的自动监督设置在 1440、1024、390px 和浅深主题下测量无横向溢出,周频表单全部控件边界可达,默认配置页及未配置说明存在。该测试使用隔离 fixture;未将其表述为真实模型或用户数据库端到端验收。
|
||||
- `npm run typecheck` 通过;本轮 9 个 TS/TSX 文件及 Electron driver 的定向 ESLint 通过。按要求未运行全量测试,真实模型调用 0 次;没有 Agent 或远程 Runtime 改动,未提交或推送。
|
||||
|
||||
### 2026-09-23 活动页签
|
||||
|
||||
- 对应 FR-S10、US-S26:schema 45 复用运行表保存监督开始、完成和失败,并按真实心跳 ID 合并报告和下游监督。手动失败、报告完成但下游运行中/失败、回调失败、应用重开后的未完成记录均有真实 SQLite 测试。没有新的执行引擎或取消协议。
|
||||
- 类型化活动 IPC/Preload 已接入生产页面;关闭应用不查询,页签或路由离开停止刷新并忽略迟到响应。每页 50 条,串行轮询,读取失败可重试。历史结果通过 resultId 定位,保留现有自动监督设置。
|
||||
- `npx vitest run src/main/assistant/supervision-activity.test.ts src/main/assistant/supervisor-service.test.ts src/main/assistant/heartbeat-service.test.ts src/preload/supervision-activity.test.ts src/renderer/src/SupervisorActivity.test.tsx src/renderer/src/HeartbeatCenter.test.tsx src/renderer/src/SupervisorWorkspace.test.tsx`:7 个文件、58 项通过。
|
||||
- `npx vitest run src/main/assistant/supervision-activity.test.ts src/main/assistant/heartbeat-database.test.ts src/main/assistant/supervision-history.test.ts`:3 个文件、14 项通过,包含 schema 44 升级及重开保留。
|
||||
- `npx vitest run src/main/ipc.test.ts -t 'supervision|heartbeat enabled gates'`:8 项通过、124 项未运行。新增活动查询首次运行暴露 UNION 排序列缺少别名,修正后真实 SQLite 和 IPC 均通过。
|
||||
- `npx vitest run tests/supervisor-layout.electron.test.ts`:1 项通过,新增活动页 1440、1024、390px 浅深主题测量,长错误、元数据和操作无横向溢出,短窗口页签保持高度。测试使用生产组件和隔离 fixture,没有读取用户库。
|
||||
- `npm run typecheck` 通过;定向 ESLint 通过。未跑全量测试或真实模型,模型调用 0 次。没有修改 Agent/远程 Runtime;工作区其他改动保留,未提交或推送。
|
||||
- App 定向命令 `npx vitest run src/renderer/src/App.test.tsx -t 'Supervisor|graph navigation'`:5 项通过、281 项未运行。刷新互斥及焦点收尾后,`SupervisorActivity.test.tsx`、`HeartbeatCenter.test.tsx`、`supervisor-service.test.ts` 合计 31 项通过;22 个相关 TS/TSX/MJS 文件的定向 ESLint 为 0 error、0 warning。
|
||||
- 结束时间收尾:手动心跳使用真实报告完成时间,下游失败单独记录结束时间。`npx vitest run src/main/assistant/supervision-activity.test.ts src/main/assistant/heartbeat-service.test.ts src/main/assistant/heartbeat-database.test.ts src/main/assistant/supervision-history.test.ts`:4 个文件、21 项通过,新增受控时钟用例区分 10:00 开始、10:02 报告完成、10:03 下游失败。随后 Node typecheck 和这 3 个改动源码/测试文件的 ESLint 通过。
|
||||
- 已存在的手动失败无法补回;旧心跳没有冻结范围和关联 ID,保留缺失提示。心跳仍按原保留期限清理,监督结果独立保存。取消和人工修改时间序列审计未实现;`no_change` 的后续接入见下节。
|
||||
|
||||
### 2026-09-28 继续讨论正文与布局
|
||||
|
||||
侧栏与图谱来源详情直接显示可编辑的发送正文,移除隐藏上下文拼接及附带提示。目标会话独立分隔,底部复用共享右对齐操作区。失败保留草稿,取消不发送,目标切换继续使旧预览失效。Main 的来源筛选与发送契约未修改。
|
||||
|
||||
- `npx vitest run src/renderer/src/SupervisionDiscussion.test.tsx src/renderer/src/SupervisionCard.test.tsx`:10 项通过,覆盖正文可见、编辑后精确提交、删除引用后不补回、失败重试、取消、重复提交和目标切换。
|
||||
- `npx vitest run src/main/ipc.test.ts -t 'scopes supervision continuation|dispatches supervision continuation'`:2 项通过,使用真实 SQLite 与队列解析器核验来源范围及空附件发送。
|
||||
- `npx vitest run tests/supervisor-layout.electron.test.ts` 分别设置 `GOODBUDDY_SUPERVISOR_DISCUSSION=1`、`GOODBUDDY_SUPERVISOR_SIDEBAR=1` 运行通过。图谱覆盖 1440/390px,侧栏覆盖 480/300/200px,均含浅深主题;检查 16px 区块间距、8px 字段间距、目标分隔、右对齐及无横向溢出,侧栏另检查滚动后按钮可命中。截图与测量保存在临时目录 `opencode/discussion-feedback-graph`、`opencode/discussion-feedback-sidebar`。
|
||||
- Renderer 类型检查 `npx tsc --noEmit -p tsconfig.web.json`、8 个改动 TS/TSX/MJS 文件的定向 ESLint 与改动文件 `git diff --check` 通过。Electron 使用生产组件和模拟数据,未调用模型;未跑全套测试,未提交。
|
||||
|
||||
### 其他边界
|
||||
|
||||
- 新批次来源读取按 messageId 返回已保存片段及版本;旧历史缺少消息 ID 时保留会话/时间定位。本地知识引用通过现有 `KnowledgeService` 解析文档/分块,失效时显示不可用;外部引用只使用已保存 locator,不调用远端全库。
|
||||
- 继续讨论和知识库写入使用现有本地会话队列与知识库实体接口,并要求先预览、再由用户确认提交。侧栏支持结果直达图谱;继续讨论的侧栏与图谱入口均已提供可编辑的实际上下文正文,发送规则见 [UI 设计](./ui-design.md)。
|
||||
- 关系移除保留撤销状态及原始来源;界面没有提供恢复入口。
|
||||
- 候选集最多 100 个实体,复用依赖模型显式选择;不会自动合并历史重复实体或跨 scope 合并。结果级历史内容已保存,事件滑块仍不重建逐事件的实体演变。Experiment 和完整图谱回放尚未实现;批次暂停/继续已接通。
|
||||
|
||||
## 2026-09-23 自动增量验证
|
||||
|
||||
对应 FR-S4、FR-S10、US-S27。自动心跳报告与下游监督均已接入来源版本和处理位置;无变化跳过模型并保存 `no_change`。消息修订、任务状态、知识引用变化分别参与增量判定,已确认记忆只作背景。实现、预算和删除语义见[自动增量收集](./technical-design.md#自动增量收集)。
|
||||
|
||||
- schema 46 增加最小来源进度与消息版本,扩展已有心跳状态 CHECK。来源片段进度与对应结果原子保存,模型或事务失败不推进。未送入模型的来源及正文余段保留待处理资格。
|
||||
- `incremental-review.test.ts` 使用真实 SQLite 和生产服务,覆盖重开数据库、重复零调用、消息修订后复用实体 ID、105 个长来源分批读完、模型及结果事务失败、任务状态、范围排序、记忆背景、知识引用、报告保存失败和 schema 45 有数据升级。
|
||||
- 真实模型使用已有加密默认文本配置,经 `RuntimeSettingsStore` 和 Electron `safeStorage` 读取隔离副本,生产 `createDefaultModelRuntime`、`HeartbeatService`、`SupervisorService` 和 SQLite 执行。`tests/incremental-review-live.electron.test.ts` 于本日通过:首次自动运行 2 次 HTTP 请求,重复自动运行 0 次,修改消息后的监督回顾 1 次,再次监督检查 0 次,总计 **3 次真实文本请求**。工具与附件均为 0。
|
||||
- 真实输出通过结构、来源及实体引用校验,修改后的 Atlas 实体至少复用一个原持久化 ID;无变化检查前后图谱相同。测试检查原设置字节未变、外键与数据库完整性。隔离数据库、配置快照和加密 Local State 随测试目录删除,未改用户计划或数据库。
|
||||
- 实时测试命令:设置 `GB_REVIEW_LIVE_SETTINGS` 为已有运行时配置路径后,执行 `npx vitest run tests/incremental-review-live.electron.test.ts`。无该环境变量时跳过;每次测试最多 3 个真实请求,不记录凭据。
|
||||
- 最终聚焦验证:10 个服务、SQLite、Preload 和 Renderer 测试文件共 76 项通过;`npx vitest run src/main/ipc.test.ts -t 'supervision|heartbeat|application enable'` 另有 9 项通过,124 项按过滤条件跳过。`npm run typecheck`(Node、Agent、Web)和本次改动的 19 个 TypeScript 文件 scoped ESLint 均通过。
|
||||
|
||||
真实测试覆盖来源较短的单次自动回顾、重复检查及修改后的实体复用。长正文和大量来源预算由确定性 SQLite 回归验证;没有执行全量测试或真实用户数据库升级。事件驱动、清空或重建图谱和语义近似去重不在本次实现范围。
|
||||
- 知识正文条目与监督实体仍是两个模型,当前不会把监督实体当作知识正文条目。
|
||||
@@ -0,0 +1,354 @@
|
||||
# 监督回顾分块调度设计
|
||||
|
||||
日期:2026-09-24。状态:生产分块、持久化续跑、设置及活动入口已接线;27 条消息的完整会话通过真实模型验证,较早的 129 条消息实验仍未完成。当前实现以第 0 节为准,第 1 节保留改造前基线,第 2 至 11 节保留目标设计,第 12 节为早期隔离原型证据。
|
||||
|
||||
本文针对全局七天 `supervision:run` 出现 `AbortError` 的反馈,设计完整分页、证据分块、模型任务调度和逐层汇总。用户已明确要求主动暂停或失败后保留成功批次及位置,重启后可继续,不重复计算成功叶子。生产接线复用 `SupervisorService`、现有数据库及 `review_checkpoints`;不另建定时调度器。schema 47 的实现与目标差异见第 0 节。
|
||||
|
||||
产品范围仍由[监督者产品设计](./supervisor-prd.md)定义;本设计对应 FR-S4、FR-S6、FR-S10 及 US-S12、US-S26、US-S27。既有规则见[逻辑设计](./logic-design.md#自动增量与手动重看),实现架构见[技术设计](./technical-design.md),已验证事实见[实施进度](./progress.md)。第 0 节以当前代码为准,其余目标须逐项核对实现状态;大范围回顾仍需独立性能验证。
|
||||
|
||||
2026-09-26 修订:回顾持续分页、提取和汇总至完成,不再按整次运行的时间预算自动暂停。下文历史草案中的执行软预算、整次执行硬期限及按时间预算续跑方案已撤销;单次模型超时、并发限制、用户暂停和失败续跑仍保留。
|
||||
|
||||
## 0. 生产接线与剩余边界
|
||||
|
||||
Main 的手动 IPC 和心跳完成回调均使用 `createProductionSupervisorService`,该工厂连接 `SupervisorService`、`SupervisionReviewStore`、共享模型池及实际 ask Runtime。生产路径不再调用旧的 20 会话 × 20 消息 collector,也不使用旧服务分支的 100 条 / 48,000 字符裁剪。旧的有界服务注入方式仍供原有测试和实验脚本使用;心跳报告本身继续使用原报告预算。
|
||||
|
||||
### 已接入的调度与存储
|
||||
|
||||
schema 47 增加运行配置、来源清单、成功批次、导航节点四张表和一个实时来源视图。迁移只创建新结构,保留此前迁移、结果及用户数据。来源清单通过 `(project_id, conversation_id, sequence, source)` 稳定键分页,SQL 事务冻结范围内的来源身份、版本、长度及自动起点;清单不保存正文。项目和会话由来源页共同枚举,不另设全量 JavaScript 目录数组。正文随后按主键和有界 `substr` 读取。
|
||||
|
||||
派发按项目及项目内会话轮转,每个会话同一轮至多一个批次,会话内按 sequence/source 排序。批次跨数据库页累计;长消息未装入部分保留待处理。每片最多 8,000 UTF-16 单元、每批最多 50 片,均为封块条件,不是范围上限。位置采用零基、左闭右开的 Unicode 码点;消息 locator 保存 messageId、conversationId、projectId、role、sequence、source、revision、start、end、length。知识引用另保留原 locator 及所属消息,任务使用当前状态和区间内创建/完成时间。
|
||||
|
||||
成功叶子的输出、原文片段和连续 offset 在同一事务提交。失败或用户主动暂停保留此前批次。继续操作使用同一 run ID、时间范围和分块配置;重启后可继续。手动运行不读写自动 checkpoint。自动运行先恢复同 scope 的未完成 run;仅在全部来源连续覆盖、导航完成、最终发布事务成功时写入 `stage=supervisor` 的 checkpoint。空范围无需模型。
|
||||
|
||||
叶子输出仍使用现有事件、实体、变化和关系契约,并校验引用与候选身份。会话、项目、scope 使用二叉递归合并导航摘要,单子透传,总共 N 个叶子至多 N-1 次合并。已成功导航节点按孩子 ID 保存,发布失败后可复用。叶子、导航及最终结果的生成文本不设字段字符上限,事实数组不设独立数量上限;导航节点仍不得生成替代叶子的事件或实体。发布事务逐页读取叶子,保存既有历史/图谱结构,保持历史定位、来源及用户确认状态;完整叶子同时通过独立批次接口分页读取。
|
||||
|
||||
单次 Runtime 响应继续在接收过程中限制为 100,000 字节,防止响应缓冲持续增长。超过边界时停止接收,明确提示本批次未保存、已保存批次可继续使用;不裁剪后冒充成功结果。该边界与摘要字符数不同,不增加摘要长度设置。监督旧摘要读取、导航输入、保存和恢复均保留全文。身份格式、来源归属、字段类型与导航空事实数组的校验继续生效;原有数据库字段及已保存结果无需迁移。
|
||||
|
||||
导航使用独立的严格输出 schema,要求 `summary`、`changeDigest`、`openItems`;四个事实数组允许省略或为空,非空数组、错误类型和额外字段仍拒绝。验证通过后才在内部补空数组,以复用现有导航存储形状。叶子继续要求四个数组齐全,不将缺字段当作成功;来源、事件实体、变化实体和关系端点均校验。继续时还会核对已保存叶子的引用,导航不覆盖其独立事实。
|
||||
|
||||
`supervision:pause` 将父 signal 传入排队及在途模型;取消后的输出不提交。`supervision:resume` 恢复已保存 run;`supervision:batches` 默认每页 10 批、最多 20 批,返回完整叶子及来源。活动返回已保存批次、码点数、剩余来源、当前阶段、排队或执行中的模型任务数及已保存配置。提取和导航汇总持续运行至完成,不按累计运行时长暂停;用户暂停会中断在途请求,仅保留已保存批次。完成判定不依赖摘要措辞。
|
||||
|
||||
2026-09-26 已接入整次回顾 single-flight、`supervision:cancel` 及实时 `supervision:execution`。手动重复请求不排队,自动请求等待;已取消运行从自动未完成查询中排除,也不能手动继续。持久取消与实际停止分开呈现,准入保留至 Runtime 清理结束。完整生命周期见[单次执行与取消](./technical-design.md#单次执行与取消),第 1 节关于 `pending` 队列和缺少取消入口的描述仅为改造前基线。
|
||||
|
||||
当前阶段以 `phase` 写入既有 `supervision_review_runs.state_json`,取值为 `collecting / extracting / summarizing / saving`;无新表或迁移。它记录服务最后进入的阶段,失败和重启后保留;完成仍由 `supervision_runs.status` 的发布事务决定。活动的 `navigationNodes` 从已保存导航表计数,`inFlight` 仍来自进程内计数。旧运行没有 phase 时不回填推测值。清单和最终发布为同步事务,短阶段不保证被轮询看到,清单事务失败也不会留下部分阶段记录。批次接口只有成功记录,没有逐个失败/在途任务或项目、会话聚合状态;界面仅按分页返回的真实归属组织详情。
|
||||
|
||||
### 当前设置合同
|
||||
|
||||
| 字段 | 默认 | 整数范围 | 生效规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `supervisionReview.pageSize` | 50 | 1..200 | 每次来源元数据页条数;不改变最终集合或批次边界 |
|
||||
| `supervisionReview.batchCharacters` | 8000 | 1000..16000 | 每批新正文 UTF-16 单元,不含提示、候选及 JSON |
|
||||
| `supervisionReview.batchMessages` | 20 | 1..50 | 每批不同消息数;任务等无消息 ID 的来源单独计数 |
|
||||
| `supervisionReview.executionSeconds` | 300 | 30..3600 | 旧设置兼容字段,执行时忽略,设置页不再展示;恢复旧 run 同样不按此值暂停 |
|
||||
| `supervisionReview.crossProject` | 关闭 | 开关 | 创建回顾时冻结;关闭时每批只以本项目已有实体为候选,也不生成跨项目故事;开启后候选来自全部项目,故事归属可建立跨项目故事 |
|
||||
| `supervisionReview.storyThreadEvents` | 20 | 4..500 | 创建回顾时冻结;功能的事件数低于此值时,模型提出的子线索并入功能 |
|
||||
| `supervisorOrganizeTimeoutSeconds` | 240 | 30..600 | 创建回顾时冻结,节点获槽并解析 Runtime 后计时 |
|
||||
| `heartbeatReportTimeoutSeconds` | 240 | 30..600 | 维持报告发起时冻结及既有租约规则 |
|
||||
| `supervisorModelConcurrency` | 1 | 1..4 | 回顾保存接纳并发;共享池上限仍随应用设置实时变化,实际执行取较小值 |
|
||||
|
||||
应用仍默认关闭。除上述旧兼容字段外,参数由共享 Zod 合同、ApplicationSettingsStore、App 设置回调和 Renderer 表单保存;没有放置尚未生效的高级字段。
|
||||
|
||||
### 未完成与实测限制
|
||||
|
||||
- 真实验证覆盖一个 27 条消息的完整会话;较早的 129 条消息实验在第二次输出 JSON 无效后停止。两实验合计 5 次付费请求,没有重复付费或回退。范围及缓存复核说明见[进度](./progress.md#2026-09-24-完整会话生产验证与收尾)。
|
||||
- 来源被修改或删除时保留旧批次并明确失败,标记需要新建回顾,活动页停止提供无效的继续按钮;下一次自动检查可创建新 run,不会永久重复该失败。尚未实现仅使受影响叶子和父节点失效后重算。清单记录冻结版本,不保存全库正文快照。
|
||||
- 分块配置、超时、接纳并发及算法版本已保存。实际默认 Runtime、模型及候选背景仍在执行时解析;未实现跨重启模型/提示词版本固定,也未实现共享池排空后切换冻结并发的目标规则。
|
||||
- 元数据清单和最终发布仍是同步 SQLite 事务,缺少大库延迟验证及可中断来源读取。批次详情已分页,既有历史图谱查询仍返回整份结果;大图谱的响应大小尚未改造。
|
||||
- 没有 token/费用预算、提供商 RPM/TPM、Retry-After 冷却、执行硬期限、独立清理期限或逐层可配置超时。默认正文预算不保证任意模型上下文容量;超长输出、非法 JSON 和引用错误会失败,保留已成功批次。
|
||||
- 片段以码点边界切分,尚未增加句界优先和重叠背景。事实保留指保留模型已产出的结构化记录,不证明语义无遗漏;生产契约校验引用 token,不要求原型的逐字引文和 reviewedTokens 回执。提供过的 locator.source 仅在本节点唯一对应一个片段时可作为引用别名;歧义及未知来源拒绝。
|
||||
|
||||
## 1. 改造前代码事实
|
||||
|
||||
下列旧草案行号仅作定位参考,工作区代码更新可能使其偏移;以所列函数和下方本次核对的控制清单为准。
|
||||
|
||||
| 环节 | 当前行为与代码依据 | 对调度设计的影响 |
|
||||
| --- | --- | --- |
|
||||
| 手动入口 | [ipc.ts:7917](../../../src/main/ipc.ts#L7917) 校验应用启用和维护状态,等待 `SupervisorService.run()` 返回 | 当前没有提前返回 runId 的开始接口,也没有监督取消接口 |
|
||||
| 运行串行 | [supervisor-service.ts:124](../../../src/main/assistant/supervisor-service.ts#L124) 用一个 `pending` Promise 串行执行整个服务的请求,出队后才创建运行 | 当前不是按项目并发;排队时间不在摘要定时器内,也尚无排队活动记录 |
|
||||
| 单次摘要 | [supervisor-service.ts:135](../../../src/main/assistant/supervisor-service.ts#L135) 最多取 100 条证据,每条最多 8,000 个 JS 字符,总正文最多 48,000,再调用一次 `summarize` | 没有分块队列或递归汇总;48,000 不包括实体候选、系统指令、旧摘要和 JSON 开销 |
|
||||
| 模型超时 | [ipc.ts](../../../src/main/ipc.ts) 的监督摘要器读取 `supervisorOrganizeTimeoutSeconds`,默认 240 秒,超时带明确原因 abort;流结束检查 signal 和 `done`,finally 释放会话 | 新控制已实现,完整清单见下节;不能据此反推原始 AbortError |
|
||||
| 底层模型请求 | [model-runtime.ts:206](../../../src/main/agent/model-runtime.ts#L206) 默认请求超时十分钟;[createRequestSignal](../../../src/main/agent/model-runtime.ts#L584) 传播上层 signal 并设置底层超时;[retryModelStream](../../../src/main/agent/model-runtime.ts#L1891) 对部分网络失败重试 | 上层默认四分钟可以先到,当前已可配置;调度层不能再盲目叠加网络重试。注入 Runtime 的实际类型和配置仍须运行时核实 |
|
||||
| 手动采集 | [ipc.ts:7120](../../../src/main/ipc.ts#L7120) 传入精确时间范围;[buildHeartbeatInput](../../../src/main/assistant/assistant-database.ts#L8168) 在时间和范围过滤后最多取 20 个会话、每会话最后 20 条消息、100 个任务、100 条记忆 | SQL 层未入选的证据在服务分块前已经遗漏,单改 `summarize` 无法补齐 |
|
||||
| 自动采集 | [collectIncrementalReview](../../../src/main/assistant/assistant-database.ts#L8333) 按阶段、scope、来源版本和位置过滤,再按发生时间和来源 ID 排序;最多 100 个来源,各取后续 2,000 个 Unicode 字符 | 已有片段定位与进度能力;查询需要扩展运行内位置,避免提交前反复读到同一片段 |
|
||||
| 自动预算 | [ipc.ts:7115](../../../src/main/ipc.ts#L7115) 监督新证据预算 44,000 个 JS 字符;[heartbeat-service.ts:227](../../../src/main/assistant/heartbeat-service.ts#L227) 报告预算 12,000;[reviewBackground](../../../src/main/assistant/assistant-database.ts#L8419) 最多四条各 500 字符记忆,旧摘要最多 2,000 字符 | 自动路径与手动路径的证据集合、预算和游标语义不同 |
|
||||
| 输出及身份 | [supervisor-service.ts:71](../../../src/main/assistant/supervisor-service.ts#L71) 检查 100KB 输出与来源引用;[同文件:167](../../../src/main/assistant/supervisor-service.ts#L167) 校验候选身份;[共享输出契约](../../../src/shared/supervision-contracts.ts#L62) 限制每对象 20 个来源、40 个事件、40 个实体、80 个变化和 80 个关系 | 多个合法子输出拼接后不一定合法;汇总必须同时约束字节、数量与引用 |
|
||||
| 保存 | [saveSupervisionResult](../../../src/main/assistant/assistant-database.ts#L8531) 一个事务保存运行状态、结果、来源正文片段、图谱快照及进度,来源重新分配 UUID | 可复用一次结果事务;当前 `coverage_json` 只有证据条数,不能表达完整覆盖 |
|
||||
| 自动入口 | [heartbeat-service.ts:220](../../../src/main/assistant/heartbeat-service.ts#L220) 先处理报告,再经 [ipc.ts:1914](../../../src/main/ipc.ts#L1914) 调同一监督服务;报告 `no_change` 仍检查监督阶段 | 报告和监督是独立消费阶段,报告成功不能替监督推进游标 |
|
||||
| 活动 | [活动契约](../../../src/shared/supervision-contracts.ts#L27) 没有 `partial`、`cancelled` 或子任务计数;[数据库活动投影](../../../src/main/assistant/assistant-database.ts#L8501) 按 heartbeat run ID 合并;[SupervisorActivity](../../../src/renderer/src/SupervisorActivity.tsx#L21) 仅在激活时轮询 | 展示新进度需要明确扩展契约,不能只添加状态文案 |
|
||||
|
||||
初稿阶段没有检查真实运行日志或用户数据库。后续只读探测记录了两次约 240 秒的失败,但没有复现该次请求或取得明确 abort 原因,尚不能确定是四分钟定时器或证据规模导致超时。诊断记录应包含运行 ID、阶段、耗时、输入规模、错误类型和 abort 原因;不记录证据正文、模型凭据或完整提示词。
|
||||
|
||||
当前监督输出先累积字符串、再检查 100KB;它不像心跳摘要器那样在流中检查字节数。报告和监督均将池 signal 与局部超时 signal 合并;清除本地数据会取消池内等待和在途工作,IPC 释放时 dispose 池。该生命周期取消不等于已有用户取消入口或分块 run 父级取消。以上都是静态实现差异,不是本次真实日志结论。
|
||||
|
||||
### 是否截断丢数据
|
||||
|
||||
手动回顾会遗漏本次分析输入:SQL 的数量限制,以及服务的 `.slice(0, 100)`、单条和总字符裁剪均会排除内容;任务和记忆排在会话证据之后,还会竞争余量。未入选内容没有自动续读机制。原始消息并未因此从数据库删除,但结果不能代表七天全部记录,裁剪后的来源也不能冒充完整消息。
|
||||
|
||||
自动回顾的未读片段仍保留待处理资格。[supervisor-service.ts:174](../../../src/main/assistant/supervisor-service.ts#L174) 只将完整进入摘要输入的片段 checkpoint 交给保存事务,模型或保存失败不推进。这里的“完整”指采集片段,并非整条消息。来源若在后续执行前离开滚动窗口,就不会被自动补读,因此“不会误标已处理”不等于“未来必定覆盖”。
|
||||
|
||||
目标是不再静默裁剪,把单次上限改成分页、分块和暂缓派发的依据,直至冻结范围内全部项目、会话、消息及其他入选来源处理完毕。模型提取仍可能遗漏原文含义,但已产出的叶子事实、事件、冲突及来源必须完整保留、可分页读取。摘要只用于导航,不能替代叶子结果;“原文已处理”也不等于模型识别了全部事实。
|
||||
|
||||
### 当前超时与调度控制清单(已实现)
|
||||
|
||||
以下是当前代码事实,不是后文拟议的多层调度预算。设置暴露两个阶段超时和一个监督并发数。
|
||||
|
||||
| 控制 | 实际值、读取时点与范围 | 代码依据与边界 |
|
||||
| --- | --- | --- |
|
||||
| 心跳报告模型阶段 | `heartbeatReportTimeoutSeconds`,整数 30..600 秒,默认 240;`runNow` / `processDue` 在等待池之前读取并冻结,获得槽位后才领取数据库任务 | [共享设置](../../../src/shared/application-settings-contracts.ts)、[设置存储](../../../src/main/application-settings-store.ts)、[HeartbeatService](../../../src/main/assistant/heartbeat-service.ts);手动及自动均适用,排队期间修改设置不改变该次报告值;到期领取每次最多一条 |
|
||||
| 监督整理模型阶段 | `supervisorOrganizeTimeoutSeconds`,整数 30..600 秒,默认 240;出队、采集完成后,在摘要器开始时读取 | [IPC 摘要器](../../../src/main/ipc.ts);后续监督阶段可读到新值,正在执行阶段不变;不覆盖排队、采集、Runtime 解析、保存或 finally 清理 |
|
||||
| 监督模型并发 | `supervisorModelConcurrency`,整数 1..4,默认 1;报告与监督整理共用一个 Main 内 FIFO 池 | [SupervisionModelPool](../../../src/main/assistant/supervision-model-pool.ts)、[IPC 接线](../../../src/main/ipc.ts);普通聊天不入池。升限可派发等待项,降限不打断在途项,等占用降到新上限以下再派发;监督服务外层 run 仍串行 |
|
||||
| 报告计时与失败 | 获得槽位、领取、采集和 Runtime 解析完成后,在实际请求开始前续租并启动计时;timeout 原因优先于下层 abort,流结束检查 signal、`done` 及空输出;流中限制 100KB | [IPC 报告摘要器](../../../src/main/ipc.ts);失败不保存报告及其 checkpoint,finally 清 timer、释放会话;报告槽位覆盖领取与采集,但排队和这些步骤不消耗模型时限 |
|
||||
| 监督计时与失败 | 摘要器读取设置后等待池,获得槽位并解析 Runtime 后启动计时;保留明确 timeout 原因及非 timeout 的提供商错误,流结束检查 signal 和 `done` | [IPC 监督摘要器](../../../src/main/ipc.ts)、[SupervisorService](../../../src/main/assistant/supervisor-service.ts);排队不消耗模型时限,失败不保存结果及 checkpoint;100KB 仍在服务收齐后校验,无监督用户取消入口 |
|
||||
| 直连模型传输 | 产品默认固定 600 秒的独立请求超时,本页两个阶段超时设置不修改它;内部 `requestTimeoutMs` 可由 Runtime 构造配置 | [model-runtime.ts](../../../src/main/agent/model-runtime.ts) 的 `createRequestSignal` / `fetchWithTimeout`;传播上层 signal,不是整次回顾预算;其他 Runtime/提供商可能更早失败 |
|
||||
| 直连传输重试 | 最多 3 次重试,间隔 500 / 1,000 / 2,000ms,仅符合条件的网络错误且尚未有阻止重试的流事件时重试 | 同文件 `retryModelStream`;等待也受上层 signal 约束,不重新获得报告/监督阶段时限,不是用户设置或调度叶子重试 |
|
||||
| 报告领取租约 | `max(300, reportTimeoutSeconds + 60)` 秒,实际范围 300..660 秒;模型请求实际开始前按同一冻结值刷新 | [HeartbeatService](../../../src/main/assistant/heartbeat-service.ts) / [renewHeartbeatLease](../../../src/main/assistant/assistant-database.ts);刷新须匹配运行 ID、owner、attempt、claimed 状态且尚未过期,否则拒绝启动。300 是下限;数据库默认仍为 300 秒,租约不是取消定时器,不覆盖下游监督 |
|
||||
| 阶段交接 | 报告保存成功或 `no_change` 后释放报告槽位,再进入下游监督;finally 的重复释放幂等 | [HeartbeatService](../../../src/main/assistant/heartbeat-service.ts);默认并发 1 时不会持有报告槽位等待监督取得同一个槽位 |
|
||||
| 心跳失败重领 | 最多 3 次尝试,失败退避 60 秒、300 秒;过期租约按领取条件重领 | [AssistantDatabase](../../../src/main/assistant/assistant-database.ts) 的 `claimDueHeartbeats` / `failHeartbeatRun`;与 HTTP 重试及未来成功批次续跑不同 |
|
||||
| 后台 tick | 30 秒一次,启动时也检查;`heartbeatTickRunning` 防重入 | [ipc.ts](../../../src/main/ipc.ts) 的 `runDueHeartbeats`;是到期检查周期,不是阶段超时或分钟级计划设置 |
|
||||
| 计划迟到 | 到期超过 2 小时记为 skipped 并计算下次时间 | [claimDueHeartbeats](../../../src/main/assistant/assistant-database.ts);这是漏跑处理窗口,不是模型超时 |
|
||||
| 活动轮询 | 有运行中记录时读取结束 2 秒后刷新,否则 10 秒;串行,离页停止 | [UI 设计](./ui-design.md#活动);只读展示,不终止任务、不触发模型 |
|
||||
| 提供商限流 | 尚无跨聊天与监督的提供商全局并发、RPM 或 TPM 调度限制,也无监督层 Retry-After 冷却 | 当前池只限制监督工作;普通聊天仍可能与之竞争同一上游额度。已核对直连文本 [model-runtime.ts](../../../src/main/agent/model-runtime.ts) 的 HTTP 失败分支和 `retryModelStream`:网络错误重试不等于按 429 或 Retry-After 退避;其他 Runtime 的内部策略需分别核实 |
|
||||
|
||||
阶段超时以各自读取设置时的值为准;并发数更新则影响池的后续派发。两个时限不包含数据采集和保存,不能相加视为整次 run 的最大耗时;清理也可能使实际返回晚于计时值。当前 FIFO 无排队超时或持久化等待记录,也没有按项目公平调度。池的生命周期取消不证明完整分块调度及所有子任务清理已实现。原始错误没有关联日志证据,仍不能确定是否来自旧四分钟定时器、传输、提供商或其他 Runtime。
|
||||
|
||||
## 2. 执行单位与范围
|
||||
|
||||
一次 run 对应一个冻结的 scope、时间区间和最终结果。项目、会话和消息块只是 run 内部的分组,不能各自创建故事线或把全局请求改写成多个项目 scope,否则既有实体身份将被分散。Global 在启动时分页解析并保存符合条件的项目集合及目录读取边界;后续新增项目留给下一次运行。正文按读取时版本处理,不持有跨模型调用的数据库事务,也不建立全库来源快照;成功批次仍须保存事实及其对应原文片段和定位。
|
||||
|
||||
手动新回顾重新读取所选区间,使用该 run 的持久化位置,不读取或写入自动 checkpoint;继续操作恢复同一逻辑 run。自动模式从 `stage=supervisor` 的已提交位置开始。批次输出及其恢复位置一起持久化,自动消费 checkpoint 只在最终完整结果提交时推进。两种位置分开,避免其他自动运行跳过尚未发布的事实;同 scope 有未完成自动 run 时先恢复它,不重复创建相同工作。
|
||||
|
||||
采集器按项目轮转、项目内按会话轮转;会话内遵循消息 sequence,同序时用消息 ID,任务和知识引用有各自的来源顺序。每轮先给各组一个块,再给仍有余量的组第二个块。分页在范围与时间过滤后执行,使用稳定排序键,不再使用“最新 20 个会话 × 最后 20 条消息”的预筛选。项目、会话目录也须分页,不能先加载全库正文再切片。页内只读元数据及即将派发的正文。
|
||||
|
||||
自动查询的排序与现有实现不同,需定向验证旧 checkpoint 仍能复用。项目目录、会话目录、消息页和其他来源页均保存恢复位置、稳定排序边界及来源版本;内存只缓存有界在飞批次。预算、失败或退出不删除已提交批次。续跑使用原冻结区间,不让滚动窗口移动丢掉该 run 尚未读取的尾部;来源删除或版本不符必须显式处理,不能当作已覆盖。只有目录和全部来源页耗尽、无空洞且汇总提交成功,才完成该 run。
|
||||
|
||||
任务按其实际项目归组;无项目任务仅在 Global 的独立组内处理。知识引用按所属消息分组,保留项目、会话、引用位置及本地或外部 locator。当前手动 collector 未输出这些完整分组字段,自动知识引用 locator 也需补所属消息元数据;这是采集接口的拟议改动。
|
||||
|
||||
## 3. 消息分块与背景
|
||||
|
||||
优先将一个会话中连续的短消息合成一个叶子任务;项目或会话改变就结束该块。单条长消息按段落或句界切分,找不到适当边界时按字符位置切分,后半段必须留在队列中。结构化任务、引用 JSON 保留完整字段及对象身份。超长说明按字段分段,游标仍映射到采集器原始 body 的位置;若暂未实现这种映射,则整条装入一个符合预算的块,装不下时明确失败,避免以重新序列化后的下标写入旧 checkpoint。
|
||||
|
||||
每个原文片段在运行内用 `source + revision + start + end` 标识,另保存 `projectId`、`conversationId`、`messageId`、角色、发生时间和原始 locator。消息来源的业务 `sourceId` 继续是会话 ID,不能用它代替消息身份。范围统一采用零基、左闭右开的 Unicode 码点位置,与 SQLite `substr` 的位置规则转换;JS 字符预算按 UTF-16 code unit 计算。中文、emoji、代理对和组合字符需有测试,不能用 JS `slice` 的下标直接推进 SQLite 位置。
|
||||
|
||||
允许加入前一片尾部或前一轮对话作为理解背景。背景注明原来源及位置,与本块的新证据分开传递;重叠区域计入模型输入预算,但不计新增字符、不推进 checkpoint、不独立产生事件。若结论只引用背景而没有本轮新证据,不把它作为新增变化输出。范围外的旧上下文也须标为背景,不能据此创建区间内事件。
|
||||
|
||||
已有摘要、同 scope 实体说明和已确认记忆同样属于背景。记忆是当前内容,任务状态也是当前状态,不推断历史快照。只有这些背景改变时,自动运行仍是 `no_change`。长消息完整分析后的累计覆盖是新证据区间的并集,不是包含重叠后的输入长度之和。
|
||||
|
||||
## 4. 设置配置清单
|
||||
|
||||
本节是监督者算法配置的权威清单,对应 FR-S2、FR-S4、FR-S6、FR-S10。目标是在“监督者 > 设置”开放有实际调优用途的参数,普通项常显,高级项按需展开;交互见 [UI 设计](./ui-design.md#算法配置设置目标)。除明确标为已实现的三个字段外,均为规划,不代表已有设置合同或控件。未测参数的产品默认值与数值范围均为待定,下表只给设计校验规则;原型参数单独列出,不作为推荐默认值。
|
||||
|
||||
### 普通配置
|
||||
|
||||
| 设置 | 单位、默认值与状态 | 用途与约束 |
|
||||
| --- | --- | --- |
|
||||
| 消息每页条数 | 条/数据库页;待实现,默认值与范围待定 | 正整数;只控制一次数据库 fetch 的消息数,不是总接纳消息上限、每次模型调用消息数或活动列表页大小 |
|
||||
| 监督模型并发数 | `supervisorModelConcurrency`;已实现,整数 1..4,默认 1 | 报告和监督共用;未来叶子、会话、项目及全局汇总也入同一池,不将项目并发与叶子并发相乘;外层 run 仍串行 |
|
||||
| 心跳报告模型超时 | `heartbeatReportTimeoutSeconds`;已实现,整数 30..600 秒,默认 240 | 报告模型阶段;当前读取时点及租约规则见第 1 节 |
|
||||
| 监督整理模型超时 | `supervisorOrganizeTimeoutSeconds`;已实现,整数 30..600 秒,默认 240 | 当前单次整理;未来作为未单独覆盖的节点超时,仍须按层测量,不声称现值适合所有节点 |
|
||||
| 单次执行软预算 | 秒/次执行;待实现,默认值与范围待定 | 到期停止读页、接纳和派发;已在飞节点在各自期限及硬期限内结束,校验后保存批次,再暂停。继续同一 run 可重新获得一次执行预算 |
|
||||
|
||||
消息页必须按稳定键持续读取至冻结范围耗尽,可跨多次执行继续。增大或减小页大小只改变数据库读取批量,不改变来源资格、最终待覆盖消息集合或语义分块边界。块可跨数据库页,但不能跨本设计规定的会话/项目边界;一条消息也可拆成多个片段。页面耗尽、批次封块和执行预算耗尽是不同事件,不能用页数或单轮接纳上限代替完整覆盖判定。
|
||||
|
||||
### 高级配置
|
||||
|
||||
下列分组均待实现。计数、字符、token、字节和时间分别校验,不使用一个“批量大小”兼任多个含义;数值上限需通过内存与真实模型测量后写入共享合同。
|
||||
|
||||
| 分组 | 拟开放参数及单位 | 设计校验、依赖与到限行为 |
|
||||
| --- | --- | --- |
|
||||
| 来源分页 | 项目目录每页项目数、会话目录每页会话数、任务/引用等来源每页记录数 | 各为正整数,与消息每页条数独立;只改变 fetch 大小,不限制范围总量。正文读取还受内存接纳预算限制,不能一次装入多条无界长消息 |
|
||||
| 叶子分块 | 每批不同消息数、每批片段数、每批新增正文字符数(UTF-16 单元) | 各为正整数;任一到限即封块,未装入内容继续排队。拆分消息按消息 ID 去重计数,片段另计;字符切分必须保留完整 Unicode 码点 |
|
||||
| 上下文与重叠 | 相邻片段重叠字符数、旧摘要字符预算、记忆条数及单条字符预算 | 非负整数,重叠可为零;均计入完整输入预算,不计新增正文及覆盖。输入预算不足时优先缩减可选背景,不能通过缩减新证据后丢弃余段来满足预算 |
|
||||
| 模型输入 | 每节点完整输入 token 上限、模型上下文容量、无 tokenizer 时的保守估算系数 | 输入包含系统指令、元数据、JSON、实体候选、背景与重叠。上下文容量优先读取实际模型配置,手工值不得超过已知能力;估算必须标明,不能冒充实测 token |
|
||||
| 模型输出 | 叶子与汇总各自的输出预留 token、输出字节预算 | 输入上限加输出预留不得超过模型上下文;预留还受提供商输出上限约束。用户预算不得放宽生产输出合同;超限拆小任务或显式失败,不截断 JSON、事实或引用 |
|
||||
| 摘要卡 | 叶子/中间导航卡的目标 token、字节预算 | 卡片只用于导航,完整事实独立保存;须能在父节点输入预算内放入至少两个孩子及必要元数据,否则拒绝该组合 |
|
||||
| 实体候选 | 每组候选数、候选描述字符/token 预算 | 数量与正文均有界,UUID 和名称保留;合法孩子身份继承规则不变,缩小候选预算不授权按同名合并 |
|
||||
| 汇总结构 | 每次合并孩子数(扇入)、最大汇总深度 | 扇入至少两个,单子透传;层级必须严格收缩,触及深度限制显式失败,保留叶子。产品默认及上限待测 |
|
||||
| 内存接纳 | 待执行块数、每轮新增字符/token、采集与在飞内容合计字节预算 | 约束内存和本轮接纳量,包含元数据、背景、摘要卡;到限暂缓读取/接纳并持久化成功批次。不得当作整次 run 的消息总额 |
|
||||
| 来源与排队 | 单页来源读取超时、队列等待超时(秒) | 分开计时;读取超时不能变成空页,排队超时保留恢复记录。同步 SQLite 查询须验证真实中断能力;等待不消耗模型时限 |
|
||||
| 节点时限 | 叶子、会话汇总、项目汇总、跨项目汇总模型超时(秒,可继承监督整理值) | 包含 Runtime 内部重试,各自获槽并解析 Runtime 后开始;显式覆盖需校验实际 Runtime 能力,不能自动沿用现有字段的数值范围 |
|
||||
| 阶段预算 | 单项目处理、全局汇总与最终发布前的阶段预算(秒) | 与单节点超时分开;到限停止新增工作并保存可恢复状态,缺失项目不能用空摘要补齐;发布事务保持原子性 |
|
||||
| 执行硬期限与清理 | 本次执行硬期限、独立清理期限(秒) | 硬期限不得早于软预算;到期向在飞节点传播取消,拒绝迟到提交,保留此前批次。清理单独计时和报告,不承诺实际返回时间等于硬期限 |
|
||||
| 调用与用量 | 每次执行及逻辑 run 累计模型调用数、token 预算;费用预算仅在价格和用量可可靠换算时开放 | 派发前预留,完成后核算;逻辑调用与 HTTP 尝试分别计数,重试也计入用量。累计预算续跑不归零,到限暂停;费用不可计算时显示不可用,不猜价格 |
|
||||
| 失败与续跑 | 自动续跑开关、最大自动尝试次数、重试退避起始/上限(秒) | 仅失败或未提交节点可重试;成功批次复用。次数有界、退避上限不小于起始值;初版不新增节点网络重试,复用 Runtime 策略,后续根据失败证据再开放对应字段 |
|
||||
| 上游额度 | 同额度组并发数、RPM(请求/分钟)、TPM(token/分钟)、429 冷却等待预算(秒) | 额度组按实际提供商账号/连接/模型共享规则确定;生效并发取监督池与额度组限制的较小值。遵守 Retry-After,等待预算不足则暂停,不能提前重发;冷却不占模型槽或报告租约 |
|
||||
|
||||
上游额度设置必须标明实际纳入的请求路径。仅监督接线时标为“监督请求额度”,不能称为提供商全局限额;覆盖普通聊天及相关 Runtime 后才能称为应用内共享额度,其他进程或客户端的消耗仍不可控。跨连接共享同一账号额度时需归入同组,不能按连接各放一份完整额度;未知共享关系不猜测。未支持的 Runtime 不显示可保存却不生效的控件。
|
||||
|
||||
### 配置冻结与续跑
|
||||
|
||||
目标设计中,保存设置仅影响新建 run;创建时保存解析后的有效配置及算法/提示词/输出合同版本,整个 run(含排队、节点和跨重启继续)使用同一份配置。共享模型池不能为不同 run 各创建独立上限:不同并发配置的执行须等待当前报告及监督工作排空,再采用该执行冻结的值;恢复旧 run 同样使用其原值,不修改已保存的新设置。这是待实现的生效规则调整;第 1 节记录的当前阶段读取与并发即时影响后续派发行为保持事实描述,不提前改称已冻结整个 run。
|
||||
|
||||
默认“继续”沿用原配置,不能因应用设置已保存新值而重切剩余片段。性能字段(分页条数、并发、等待/执行时限、速率与退避)不改变已保存事实的有效性,但本设计仍只在新 run 使用新值,不隐式修改恢复中的配置。语义字段(消息/片段/字符分块、背景重叠、模型与提示词、候选选择、输入输出预算、汇总结构)会影响产物;需要采用新值时新建 run,不将旧叶子按新规则冒充同一版本结果。
|
||||
|
||||
恢复时复查来源 revision、算法/提示词/输出合同版本及依赖。来源变更使该来源叶子及依赖汇总失效,其他有效批次保留;仅汇总规则失效可保留可验证的叶子、重做受影响汇总,叶子语义或合同不兼容则相应叶子及上层都需重做。软件升级后无法按原版本继续时明确说明并提供重新回顾,禁止静默套用新版本。这里只要求最小有效配置与依赖记录,不新增通用历史版本执行引擎。
|
||||
|
||||
累计调用/token 预算、成功批次和恢复位置须随 run 保存;普通恢复不能重置累计额度,也不能自动删除未完成 run 的唯一输出。后续若增加显式调整预算的续跑操作,需单独定义其生效范围与记录;当前清单不将保存设置视为该操作。
|
||||
|
||||
### 固定正确性条件
|
||||
|
||||
以下不提供关闭开关或可降低的阈值:冻结范围内全部目录与来源页耗尽、同 revision 连续无空洞覆盖、来源及精确位置校验、叶子事实与 provenance 保留、输出结构/身份校验、取消后的迟到提交拦截,以及最终发布与自动 checkpoint 的原子提交。覆盖率不得配置成低于完整覆盖的“成功阈值”;任何预算都不能授权静默截断或将未读内容标为完成。批次持久化与恢复所需记录不是可选性能开关。
|
||||
|
||||
### 原型参数依据
|
||||
|
||||
静态核对 [review-algorithm.mjs](../../../scripts/review-algorithm.mjs) 和 [review-deepseek.mjs](../../../scripts/review-deepseek.mjs):`pages` 默认 17 条/页,`chunks` 默认 8,000 UTF-16 单元/叶子且跨页累计;没有单批消息数/片段数上限、句界切分或重叠参数。`extract` 默认宽度 2、调用上限 `Infinity`,DeepSeek 入口仅接受并发 1..2,首次执行最多两次叶子调用,实验总 HTTP 上限 12(包含评估),单请求 180 秒,输出 16,384 token、200,000 字节校验上限。这些是脚本值和实验边界,未验证为产品默认或合法范围;尤其不能用原型 200,000 字节替换生产 100KB 合同,也不能采用无限调用作为产品默认。
|
||||
|
||||
没有可用 tokenizer 时用保守字符估算,并将计数明确标为估算值。上线前按模型、中文长文本和大量实体测量输入 token、首字延迟、总耗时、输出大小及调用数,再调整参数。证据数量大但每条很短时,也要计入元数据和引用 token 开销。
|
||||
|
||||
一次执行接纳量不限制整个逻辑 run 的范围。内存片段、元数据和摘要卡总量应有上界;达到上界就持久化已完成批次并暂缓派发,不能删来源。现有全局 100 条/48,000 字符裁剪须改为每节点校验及分页结果。存储不足明确暂停/失败,保留已保存内容及未完成状态,不通过删除叶子事实满足容量限制。
|
||||
|
||||
### 未来超时与清理清单(均未实现)
|
||||
|
||||
以下是调度设计项,不是现有设置字段或已接通的分块取消能力。没有测量依据的数值保持未定。
|
||||
|
||||
| 层次 | 拟议范围和到期行为 | 参数状态 |
|
||||
| --- | --- | --- |
|
||||
| 叶子模型 | 单块执行含底层重试;超时停止该节点,保留其他成功批次,恢复只重试未成功部分 | 未定 |
|
||||
| 会话汇总 | 有界卡片合并;失败保留所有叶子事实及引用 | 未定 |
|
||||
| 项目汇总 | 单项目各合并节点及项目阶段预算;不得用失败项目的缺省摘要替代完整结果 | 未定 |
|
||||
| 跨项目汇总 | 全局合并节点及最终发布预算;缺任一项目时不能标完成 | 未定 |
|
||||
| 来源读取 | 项目/会话/消息分页、任务及引用读取;读取超时不是空页或无变化 | 未定;同步 SQLite 查询不能仅靠 JS timer 中断,实现时需验证 |
|
||||
| 排队等待 | 区分等待与执行时间;到期移出待执行队列,保留恢复记录,不创建假完成结果 | 未定 |
|
||||
| 批次接纳预算 | 块数、字符、token、内存到限后暂停接纳;成功输出及连续位置先落库 | 未定 |
|
||||
| 单次执行与整次 run | 软预算停止派发并保存进度,硬期限取消在飞调用;逻辑 run 可跨执行继续,累计调用/token 预算不重置 | 默认与范围待测;费用预算以可可靠换算为开放前提,不能以预算耗尽视为完成 |
|
||||
| 提供商速率与冷却 | 按上方额度组与实际接线范围限制并发、RPM、TPM;冷却不占执行槽,不领取报告租约,预算不足时保留待恢复工作 | 未实现;先核实各 Runtime 已有重试与错误信息,避免重复退避;未覆盖聊天时只称监督请求额度 |
|
||||
| 用户取消与进程关闭 | 父 signal 到全部节点,停止读页/派发/发布,保留已提交批次,忽略迟到完成 | 待接类型化 IPC 和状态;关闭应用开关仍按既有规则,非取消 |
|
||||
| 清理期限 | 清 timer、监听器和 controller 注册,等待在飞调用终止,释放临时会话,核验本次拥有的远程进程已退出 | 独立期限未定;不得误停共享或其他运行资源,清理失败单独记录 |
|
||||
|
||||
有效节点期限不得超过本次执行剩余硬期限;软预算只停止新增派发,不立即取消已在飞调用。暂停、错误和用户取消分别记录原因;自动恢复与重试按配置有界执行,不能形成无限付费循环。上述清单的设置交互等实现后再开放,目前算法控制 UI 只显示报告/监督两个超时及共享监督并发数。
|
||||
|
||||
## 5. 队列及汇总算法
|
||||
|
||||
```text
|
||||
run(request):
|
||||
create or resume durable run with frozen scope, time range, runtime and effective configuration
|
||||
wait for existing supervisor run queue and compatible shared pool configuration
|
||||
load successful batches and page/source positions; read next bounded page
|
||||
if automatic, all eligible pages exhausted and no new evidence: save no_change; return
|
||||
admit bounded leaf tasks, round-robin by project and conversation
|
||||
run admitted leaves in a shared worker pool
|
||||
validate and persist each successful batch with facts, provenance and resume positions
|
||||
if a task fails or cancellation arrives: stop; retain batches; mark incomplete
|
||||
continue paging all project/conversation/message/source groups until exhausted
|
||||
reduce within conversation, then project, then requested scope
|
||||
pass through singleton groups; reduce oversized groups in bounded rounds
|
||||
validate final output, identities, original references and coverage
|
||||
persist successful reduction nodes without replacing leaf facts
|
||||
if coverage is exhaustive and gap-free: publish result and automatic checkpoints atomically
|
||||
return completed only after publication; otherwise return resumable incomplete status
|
||||
```
|
||||
|
||||
采集与叶子执行可交错,但入队和在飞任务有界,不使用对全部来源的 `Promise.all`。每批完成后持久化,再接纳下一批,持续至全部来源及汇总完成。首次派发前用有界探测判断是否直通;穷尽范围后确实只有一个叶子时直接生成最终输出,通过校验后只调用一次模型。因预算只接纳一个叶子时仍是未完成 run。空范围及自动 `no_change` 必须在全部相关目录/来源页检查完毕后判定,不调用模型、不把预算未读误作无变化。
|
||||
|
||||
叶子有多个时输出受限摘要卡及结构化事实,Main 保存卡片 token 到原文片段的映射。会话只有一个卡片时透传,项目只有一个会话摘要时透传,全局只有一个项目摘要时透传。不得为了层级外观额外调用模型。边界必须标清项目和会话,跨项目汇总仍使用原请求 scope 的候选实体集。
|
||||
|
||||
每次汇总只读取符合输入预算的一组有界卡片、少量背景和所需实体候选,不重新附上所有原文、所有中间摘要或全部候选描述。若一个大组有 N 个卡片,划成至少两个孩子的组,单个余项透传;下一轮卡片数必须严格小于 N。以卡片数量作终止度量,同时限制每张输出卡大小和最大深度。输入预算连两张卡都放不下时,重新缩小叶子卡输出目标或明确失败;禁止用同一内容反复“再总结一次”形成不收敛循环。
|
||||
|
||||
24 个叶子使用二叉合并时至多产生 23 个合并节点;分组层级不额外生成单子节点调用。最终合并节点直接生成最终结果,不再调用独立的润色模型。小任务直通和此调用上界都应有确定性测试。上述次数按摘要器逻辑调用计,底层 HTTP 重试应单独统计。
|
||||
|
||||
单节点 JSON 继续遵守事件、实体、关系和引用上限,整个 run 的事实结果使用分页读取,不能把最终 40 个事件等单输出上限套在叶子事实总集上。归纳层可以合并导航条目,但必须保留被合并的叶子 ID、事实及冲突;不能以父摘要覆盖孩子。一个对象展开后超过 20 个来源时,以分页关联读取完整引用或显式拒绝该节点,不能截断。完整叶子事实及 provenance 的存储和查询合同属于本设计的必要实现项,不再作为可选后续工作。
|
||||
|
||||
## 6. 来源与实体合并
|
||||
|
||||
来源归属由 Main 管理。模型只返回本节点收到的引用 token;中间卡片 token 由 Main 展开为原始来源 token,校验引用属于本 run、对应版本与范围,并存在于已保存片段集合。每个成功批次持久化叶子事实、原始引用映射、版本、范围和有界原文片段,恢复时不依赖内存映射。中间摘要只作导航;父摘要未列出的事实仍能从叶子结果分页访问。覆盖依据成功片段,不以最终摘要引用数推算,不复制完整用户库。
|
||||
|
||||
重叠输入的同一原文区间只登记一次;不同版本或不同片段保持可区分。不同 run 的来源 UUID 继续重新分配,底层 `source`、revision、位置和业务 `sourceId` 用于追溯,不能拿结果内 UUID 当增量身份。引用去重与实体合并是两件事。
|
||||
|
||||
实体采用两类明确身份:已有实体用允许候选中的 `persistedId`,本轮新实体先用带叶子命名空间的局部 ID。多个叶子引用同一合法 `persistedId` 时,Main 合成一个身份并改写事件和关系端点,再汇总描述及冲突,最终输出中的 persistedId 保持唯一。父节点可继承孩子已校验的身份,Main 同时校验它属于本轮原始候选集;候选描述的筛选不应使已确认合法的孩子身份丢失。
|
||||
|
||||
新实体只在有界汇总中显式给出“这些局部 ID 指同一概念”的映射后合并,并校验 ID 均来自孩子输出;同名、相同裸模型 ID 或相似描述都不是自动合并规则。保留未能判断的独立实体,接受候选集有界带来的漏复用风险,不新增语义检索服务。合并后从实体到事件、变化、关系及来源的引用必须全部重写,去掉合并导致的重复关系,并校验自关系是否仍有意义。
|
||||
|
||||
实体候选始终来自原请求的同 scope 故事线。不得将项目分组变成跨 scope 身份复用,也不批量修复历史重复实体。保存事务再次检查候选归属和撤销状态,沿用 [assistant-database.ts:8564](../../../src/main/assistant/assistant-database.ts#L8564) 的保护;已确认、已修订字段及已撤销关系不被自动结果覆盖。候选在执行中被撤销时整次保存失败,游标保持原值,下一次重新读取候选。
|
||||
|
||||
## 7. 超时、取消和部分失败
|
||||
|
||||
一个 run 持有父 controller,节点持有独立的超时 controller;父取消传到全部在飞节点。取消后停止采集、派发和结果提交,清除定时器并释放临时 Runtime 会话。节点超时只触发本轮失败,不取消其他运行或共享 Runtime。节点采用独立 requestId/conversationId;冻结默认 Runtime 选择,不因证据来自某个项目就切换到该项目远程执行环境。
|
||||
|
||||
当前监督通过无 projectId 的 [resolveRequestRuntime](../../../src/main/ipc.ts#L1492) 获取默认 Runtime。此设计只改变桌面侧编排,不要求新增 Agent 调度协议。后续实现若改动共享 Runtime 的取消或释放逻辑,必须检查 `gbagent` 是否共用该实现,并按仓库要求验证实际受影响的远程路径,不能据桌面测试推断远端行为。
|
||||
|
||||
摘要器应在流式输出期间检查字节上限,并要求完成事件;超时、用户取消、关闭进程、提供商失败、非法 JSON 和引用错误保留可区分的原因。调度层初版不重试节点网络错误,复用 Runtime 已有重试;模型格式错误也不无限“修 JSON”。先用小块降低失败成本,再根据实际失败数据决定是否值得增加一次有界纠正调用。
|
||||
|
||||
取消需要 Main 注册与执行生命周期连接,并扩展类型化 Preload/IPC;只让 Renderer 忽略响应不构成取消。取消先于最终发布事务开始则拒绝发布,保留此前批次;仅最终完整结果已经同步提交时,取消才返回“已完成”。单个批次已提交不能使 run 完成。关闭监督应用继续遵循现有规则:停止新领取,允许已开始的运行结束,不等同于用户取消。
|
||||
|
||||
| 情况 | 本轮结果与进度 | 用户可见结果 |
|
||||
| --- | --- | --- |
|
||||
| 叶子或汇总失败 | 保留已持久化成功批次和恢复位置,不发布完整结果、不推进自动消费 checkpoint | run 失败但可恢复;仅重做失败/未提交节点,旧完整结果仍可读 |
|
||||
| 用户取消或执行硬期限到达 | 停止在飞调用和发布,保留已提交批次 | 显示取消或预算暂停及可恢复范围,不标 `completed`;具体新状态需扩展合同 |
|
||||
| 执行软预算到达 | 停止读页、接纳和派发,在飞节点在节点及硬期限内结束并保存成功批次 | 未完成时显示预算暂停,可继续;若已满足全部完成条件则可正常发布 |
|
||||
| 批次或发布事务失败 | 该事务回滚;此前成功批次不回滚 | 从最后持久化位置恢复;发布失败不重算已有合法叶子 |
|
||||
| 达到采集预算,已接纳任务全部成功 | 保存有限覆盖批次及位置,仍有未读内容 | 显示“未完成,可继续”,不以 `completed` 加提示代替真实状态 |
|
||||
| 有来源读取失败或运行中被删除 | 初版按本轮失败处理,不将读取失败当作没有新证据 | 显示来源不可用的数量和范围,后续重新查询 |
|
||||
| 自动无新来源 | 保存 `no_change`,无新结果或图谱事件 | 保留旧结果,模型调用数为零 |
|
||||
|
||||
成功批次持久化是用户已授权的续跑要求。最小记录包含 run 冻结范围、目录/页位置、来源 revision 与连续 offset、已校验叶子输出及引用、必要的汇总依赖和成功输出。输出与对应位置同事务提交,以 run 和来源区间等稳定身份防止重复保存;不持久化每次进度动画或完整提示词。崩溃前未提交的在飞批次允许重算,已提交成功批次必须复用。表结构及保留/清理实现仍待设计落地,不能声称现有 schema 46 已支持此能力。
|
||||
|
||||
## 8. 游标与覆盖提交
|
||||
|
||||
同一来源片段可能乱序完成。恢复位置只能是同一 revision 从已提交 offset 开始、成功持久化区间连续覆盖的最远位置,不能取最大 end 跨过空洞;空洞后的成功批次单独保留,恢复时跳过其已完成工作。背景、仅读取内容和失败片段不推进。自动消费 checkpoint 在完整发布事务中推进,现有 `MAX` 合并位置的调用方仍须满足连续覆盖条件。
|
||||
|
||||
同一来源读到不同 revision 时不拼接版本;标明该来源及依赖汇总需重新处理,保留其他未受影响的成功批次。若最后读取之后来源修改,提交的仍是确实分析过的旧 revision,下一次查询因版本不符处理新版本。已保存旧事实及片段保留版本标识,不冒充当前内容;不还原未保存的历史正文,也不引入全库快照。
|
||||
|
||||
建议扩展 `coverage_json`:是否读完冻结范围、已处理新字符和片段数、覆盖项目/会话数、是否仍有来源、停止原因及恢复引用。精确片段继续用 locator,恢复记录关联批次输出,不仅保存一个总计数。总数未知时显示“仍有未读内容”,不用动态发现数充当固定分母。结果读取合同需显式暴露 coverage;当前数据库查询了 coverage,但共享结果 schema 未返回它。
|
||||
|
||||
手动和自动均须跨重启继续同一未完成逻辑 run。Main 根据持久化 run ID 读取冻结范围、成功输出及位置,复查来源版本,恢复剩余页和未成功汇总;Renderer 不能自报已处理 offset。手动继续不污染自动位置;自动恢复先于创建同 scope 新 run。恢复后持续处理剩余工作,不按旧执行预算暂停,也不丢失既有进度。
|
||||
|
||||
持久化续跑及手动“继续”入口是本设计的验收要求,不能以建议缩小范围替代。恢复记录的保留期限、主动放弃后的清理规则和具体 schema 尚未确定;不得在 run 未完成时自动删除唯一成功输出或继续所需位置。显式放弃可以终止后续工作,但不能标为完整覆盖。
|
||||
|
||||
## 9. 心跳接线与重复调用
|
||||
|
||||
手动 `supervision:run` 和心跳完成回调继续进入同一个服务及同一运行内队列。外层按计划唤醒仍归 `HeartbeatService`,不能让各项目、会话和叶子再调用 `heartbeat.runNow`、创建计划或再次进入 `supervision:run`。队列等待中的自动请求到执行时重新查询 checkpoint,从而看到前一个 run 的已提交结果。
|
||||
|
||||
初版保留既有报告语义:每次心跳最多执行一次原报告阶段,再执行一次分块监督;报告含记忆和任务建议,不能直接用监督 summary 替代而悄悄删除这些产物。报告阶段无新证据时零次模型调用,仍允许监督补读或重试;报告已经提交而监督失败,下次报告仍按自己的 checkpoint 判定,无变化则不再付费。设置中的“立即运行”目前会主动重做报告,此行为不与自动无变化混淆。
|
||||
|
||||
因此本设计消除的是分块引入的重复报告调用,不声称将现有两类模型输出合为一次调用。一个叶子的自动首次运行在两阶段都有新输入时仍为“报告 1 次 + 监督 1 次”;多叶时为“报告至多 1 次 + 叶子数 + 必要汇总数”,另计底层重试。报告只读报告阶段的有界输入,不能宣称它覆盖监督处理的全部片段。
|
||||
|
||||
若后续决定让心跳只触发监督、报告从同一结构化结果投影,必须先定义记忆建议和任务建议的共同输出契约、各自成功语义及旧报告展示,另行修改 FR-S4 和 US-S27;本轮不把该产品调整藏在调度重构里。也不使用多阶段事务将两个结果强行绑定。
|
||||
|
||||
报告先获共享槽位再领取租约,并在实际模型请求开始前刷新;报告完成或无变化提交后释放槽位,再执行下游监督。领取与续租条件见当前控制清单。未来整次执行预算不能套到报告租约,也不能在报告完成前等待整棵监督树。现有 tick 防重入不替代共享池上限;监督过长仍可能延迟到期检查;移除自动时间预算暂停不改变 tick 的串行等待关系,不另建后台调度服务。
|
||||
|
||||
## 10. 活动与结果展示
|
||||
|
||||
沿用[现有活动设计](./ui-design.md)及[全局界面规范](../../../UI-DESIGN.md),活动仍是一条手动逻辑 run 或合并后的心跳 run。拟显示采集中、整理会话、汇总项目、生成回顾、保存中,以及已发现块数、成功块数、在飞数和新字符数。持久化成功批次提供可恢复进度,实时在飞数只存内存。发现未结束时显示“已处理 N 块,继续读取”,不用不可靠百分比。摘要、项目导航可展开分页叶子事实及完整来源。
|
||||
|
||||
计数在 `supervision:activity` 读取时与持久化运行信息合并,保持每页 50 条及串行轮询,不持久化每次计数更新。当前出队前没有 run 记录;未来排队/恢复操作需能定位逻辑 run。重启后从成功批次重建已完成计数,不恢复虚构在飞状态;待恢复记录不能被现有未完成记录处理逻辑误判为全部作废。
|
||||
|
||||
结束后以持久化状态为准:只有冻结范围穷尽、片段无空洞、必要汇总成功并发布结果,才能显示 `completed`。预算暂停或失败保留部分产物,显示“未完成,可继续”、实际覆盖和原因,不显示完成徽标。需要扩展活动/结果状态、coverage、恢复入口及中英文文案和 UI 测试;不能从摘要措辞猜状态。旧结果 coverage 缺失时显示“覆盖详情不可用”,不推断已读完。
|
||||
|
||||
取消按钮仅在 Main 接线可用时加入。拟增加暂停/未完成/可恢复及取消的明确状态语义,最终枚举和 schema 尚未实现;禁止用现有 `completed` 冒充部分成功。活动读取不调用模型,离页停止轮询,迟到响应不改写选择。旧完整结果与本次部分产物、失败原因分开展示,继续入口恢复原 run,不暗中重做全区间。
|
||||
|
||||
## 11. 验证方案与本次检查
|
||||
|
||||
实现时先用隔离 SQLite、生产采集与保存函数、可控摘要器和时钟验证调度规则,不修改用户数据库。以下生产端到端验收仍待执行;隔离原型已覆盖的子项及其限制见第 12 节和实施进度:
|
||||
|
||||
| 场景 | 必须验证的结果 |
|
||||
| --- | --- |
|
||||
| 全局七天、多项目、超过一页项目/会话、每会话超过 20 消息 | 过滤在分页前;逐页耗尽项目、会话、消息及其他来源,无静默裁剪;每个片段有来源定位 |
|
||||
| 一条超长消息及中文、emoji | 多片段覆盖连续、无意外缺口;Unicode 位置与 JS/token 预算分开;重叠不增加进度和事件 |
|
||||
| 小任务、自动无变化 | 小任务只有一次监督模型调用;背景变化单独触发时零次;不调用空的会话/项目汇总层 |
|
||||
| 24 叶子测试样本、多级汇总、超大候选及引用 | 报告及所有节点共用配置的监督池上限;每个节点满足预算;节点数严格收缩;二叉合并调用至多 23;引用展开超限显式处理,样本数不作为产品预算 |
|
||||
| 某叶失败、根汇总失败、保存失败及进程重启 | 已提交成功批次仍可读取;恢复不重新调用成功叶子;只重做未提交/失效依赖;完整发布与自动 checkpoint 原子提交 |
|
||||
| 预算结束、乱序完成及同源多个片段 | 批次输出及连续恢复位置原子提交;空洞后成功输出保留但不提前推进;同一冻结 run 续读尾部,绝不标 completed |
|
||||
| 叶子事实超过最终输出上限 | 所有已产出叶子事实和 provenance 分页可读;摘要遗漏、合并或缩短不删事实、冲突或来源 |
|
||||
| 来源修改、删除及窗口滑出 | 版本不混用;下一次处理新版本;窗口外未读不假装已完成,也不擅自扩大范围 |
|
||||
| 两叶同一 persistedId、同名新实体、关系端点变化 | 显式身份可复用;局部 ID 命名空间隔离;同名不自动合并;人工修订和撤销状态受保护 |
|
||||
| 节点超时、run 超时、取消及迟到完成 | signal 到达实际 Runtime,停止派发与提交,释放临时会话;原因可区分,其他请求不受影响 |
|
||||
| 报告与叶子/汇总竞争槽位、运行中调低并发、上游 429 | 共用监督池且不重入占槽;排队不消耗模型时限、不提前领取报告;未来冷却遵守提供商信息且不重复叠加 Runtime 重试;普通聊天与监督共享额度的行为按实际接线验证 |
|
||||
| 手动与自动先后入队、报告成功而监督失败 | 后续自动请求重读已提交位置;手动不改自动位置;每次心跳报告最多一次,监督重试不重复已成功且无变化的报告输入 |
|
||||
| 活动及继续入口 | 合并活动仍关联同一逻辑 run;部分覆盖不伪装完整;重启后从持久化输出重建进度;继续失败明确原因,手动恢复不改自动位置 |
|
||||
| 配置与分页 | 改变消息/目录页大小不改变最终入选集合和语义块;分别触发消息数、片段数、字符与 token 上限,余段仍被覆盖;跨字段无效组合不能保存 |
|
||||
| 配置生效与恢复 | 保存和恢复默认值只影响新 run;进行中与重启恢复沿用冻结配置;语义或来源版本失效只重做相应依赖,累计额度不被续跑重置;软预算与硬期限行为分开验证 |
|
||||
|
||||
实现后的定向测试应覆盖 `supervisor-service`、`incremental-review`、`supervision-activity`、相关 IPC 和 Renderer 场景,并检查所有参数上界及调用计数。真实模型性能验证另行执行,使用脱敏固定长文本,限制请求次数,记录不同输入规模的耗时和 token,不以模型替身测试宣称超时已解决。涉及 Runtime 生命周期变化时追加对应真实 Runtime 路径验证。
|
||||
|
||||
初稿只做静态核对。后续只读用户数据探测及 DeepSeek 隔离原型的测量、复现命令、范围和限制统一记录在[实施进度](./progress.md)。这些证据不证明生产分块调度已实现;原始 AbortError 仍缺乏明确 abort 原因的日志证据。
|
||||
|
||||
## 12. 隔离原型的设计结论
|
||||
|
||||
`scripts/review-algorithm.mjs` 实现了可执行的消息分页、码点分块、逐批 SQLite 保存、按已保存片段恢复、完整覆盖判断及叶子导航校验。`scripts/review-deepseek.mjs` 用配置中的 DeepSeek 完成整条目标会话验证;具体计数和耗时只在[实施进度](./progress.md#2026-09-24-deepseek-全目标会话隔离原型)维护。原型按已授权的续跑要求使用最小批次表,没有修改产品数据库。
|
||||
|
||||
来源覆盖、事实保留和短摘要须分别验收。覆盖以完整冻结消息集合及成功批次的区间并集判断;事实以精确来源引用和独立语义检查判断;导航必须索引全部叶子,生成前后核验叶子内容未变。真实实验中导航摘要确实省略了仍保留在叶子中的细节,因此产品查询和 UI 必须能访问叶子,不能只存最终 summary。模型的 reviewedTokens 回执仅参与结构校验,不能证明事实召回。
|
||||
|
||||
重复引文是已出现的定位歧义。原型保留同一已提交片段内全部精确匹配区间,标记 ambiguous 和候选数;这些位置都能重建原引文,但不冒充已确定的唯一发生位置,也不复制事实。生产接口需要携带该歧义或要求更长引文,禁止无提示地选第一次出现。来源中不存在的引文仍拒收。
|
||||
|
||||
成功叶子与覆盖区间一起提交,另一批校验失败不回滚已提交结果;重开时只处理缺失批次。测试脚本额外保存原始提供商响应,以便校验器修正后本地复核,不因此增加付费请求。该实验缓存不是新增的产品响应日志设计。无变化路径必须遍历所选批次并核验完整覆盖后返回零调用,不能因本轮预算耗尽就返回 no_change。
|
||||
|
||||
本次每叶 8,000 UTF-16 单元、并发 2、输出预留 16,384 token 只是在该模型和单会话样本上验证过的实验参数,不直接定为产品默认值。原型加载完整冻结来源,尚未满足生产有界内存采集;没有句界切分和上下文重叠,也未实现项目轮转、多层有界汇总、生产发布事务、自动 checkpoint 或继续 UI。导航关联保持空数组,跨项目身份和关系合并未作真实验证。第 11 节中的生产端到端场景仍需分别落实。
|
||||
@@ -0,0 +1,165 @@
|
||||
# 时态 Story Graph 内置 MCP 设计
|
||||
|
||||
日期:2026-09-28。状态:D1 只读工具及本地、远程接线已实现,验证记录见[实施进度](./progress.md#2026-09-28-story-graph-只读工具)。D2 跨范围复用、D3 记忆读取切换及历史重建仍未实现。
|
||||
|
||||
本文负责 FR-S11 的 MCP 工具契约、时间语义、范围复用和记忆过渡设计。产品要求见[监督者产品设计](./supervisor-prd.md),用户验收见 [US-S28 至 US-S31](./user-stories.md#us-s28-依据决定演变继续工作)。既有图谱对象与交互仍由[图谱 PRD](../story-graph/prd.md)定义。可执行输入以[共享工具合同](../../../src/shared/story-graph-tools.ts)为准,数据投影见 [Story Graph Reader](../../../src/main/assistant/story-graph-reader.ts);本文件同时保留后续阶段目标。
|
||||
|
||||
## 1. 目标与现状
|
||||
|
||||
监督者的主要产出是带时间、演变关系和来源的 Story Graph。工作回顾、侧栏和 Agent 上下文从同一组事实中组织内容。扁平记忆条目无法独立表达“当时决定了什么、后来为何修订、现在应采用什么”,因此不再作为目标架构的主要产物。
|
||||
|
||||
用户侧统一为监督者维护的工作记忆:包含长期偏好、决定、事件、未解决事项及其依据。“替代记忆”指统一概念与后续读取入口,保留已确认偏好、用户修订和已有数据。它不授权删除 `memory_items`、抹掉历史或给旧内容补造事件。
|
||||
|
||||
| 能力 | 当前代码事实 | 本设计目标 |
|
||||
| --- | --- | --- |
|
||||
| 图谱与来源 | 已有结果、事件、实体、关系、结果图谱快照及成功叶子;历史归属存在缺失边界 | 按对象、时间和相关性读取事实与演变,并返回来源版本和覆盖信息 |
|
||||
| MCP | [内置目录](../../../src/shared/builtin-mcp-servers.ts)和[能力合同](../../../src/shared/capability-contracts.ts)已有 `story-graph`,包含三个只读工具 | 按需读取,沿用应用启停及 Runtime 分配 |
|
||||
| 时间与关系 | [监督合同](../../../src/shared/supervision-contracts.ts)已有 `occurredAt`、确认状态和 `revised`;关系枚举没有明确替代/矛盾类型 | 分离发生、生成、记录时间;用有证据的关系表达替代和矛盾 |
|
||||
| 范围与身份 | checkpoint 按阶段、精确 scope、来源区分;候选实体只取同 scope | 同一来源版本的有效叶子事实可跨 scope 复用,范围仍控制可见性与解释 |
|
||||
| 记忆 | `reviewBackground` 仍读已确认记忆;最多四条、每条 500 字符,作为当前背景 | 逐步由统一查询承接,保留确认状态与适用范围 |
|
||||
| 历史重建 | 有结果快照,不具备完整来源版本及人工修改时间序列 | 首期只提供 `current`、`timeline`;`as_of` 留待历史重建能力具备后设计交付 |
|
||||
|
||||
现状依据还包括 [AssistantDatabase](../../../src/main/assistant/assistant-database.ts) 的 `saveReviewCheckpoints`、`listSupervisionCandidates`、`reviewBackground`,以及[生产分块调度](./review-scheduling-design.md#0-生产接线与剩余边界)。现有结果快照只证明某次回顾保存过哪些内容,不能据此声称能还原任意时刻的认识。
|
||||
|
||||
## 2. 对象与稳定身份
|
||||
|
||||
复用现有 SQLite、领域对象和来源定位。设计不要求图数据库、独立向量库、完整正文副本、新持久化日志或通用历史快照层。所需读取视图先从已有结果、叶子、关系和 locator 推导;无法推导的内容明确返回未知。
|
||||
|
||||
| 对象 | 概念合同 | 身份与关联规则 |
|
||||
| --- | --- | --- |
|
||||
| 故事线 `story_line` | `id`、主题、范围、事件与实体引用 | 沿用故事线 ID;不把项目名当 ID |
|
||||
| 事件 `event` | `id`、类型、描述、发生时间、实体及来源引用 | 决定、讨论、变化、里程碑是事件类型;一次回顾运行不是工作事件 |
|
||||
| 实体 `entity` | `id`、描述、确认状态、有效性、演变引用 | 对象 ID 与内容修订分离;同名或模型局部 ID 不证明同一实体 |
|
||||
| 变化 `entity_change` | 实体、事件、变化类型、依据、适用时间 | 指向具体对象版本;无独立主键时以已有所属对象和稳定关联键定位,不以页内下标造身份 |
|
||||
| 关系 `relation` | `id`、两端引用、类型、理由、来源、确认状态 | 替代/矛盾关系必须能定位两端主张及支持该关系的证据 |
|
||||
| 阶段总结 `summary` | 结果 ID、范围、区间、正文、事实引用 | 用于导航;检索与上下文仍能访问完整叶子事实 |
|
||||
| 来源引用 `source_reference` | 引用 ID、来源类型与原 ID、版本、locator、可用性 | 引用 ID 与原消息/任务 ID 分开;跨结果的引用 UUID 不等于新的原始来源 |
|
||||
|
||||
偏好、方案、决定涉及的概念沿用实体及事件表达,不为每个业务名词另建基础设施。业务上的“已决定”(`decided`)与“已完成”(`completed`)是不同状态,这里不声明新增任务状态枚举;运行 `completed` 仅表示整理发布成功。决定采用方案、计划执行、模型写出“已决定”均不能推出任务完成,完成须有任务记录或明确执行证据。
|
||||
|
||||
返回对象携带 `object_ref`(类型和稳定 ID)、`object_revision`、`result_id` 及适用 scope。版本可使用已有结果身份与可用修订信息表达;没有可靠对象版本时返回 `unknown`,不把 `updated_at` 伪装成完整版本链。旧文本主键及 Main 精确别名映射继续保留,`candidateRef` 只是一次模型输入内的候选引用,不作为 MCP 持久 ID。跨 scope 身份合并须有明确映射依据,不能因复用叶子事实而自动合并同名实体。
|
||||
|
||||
## 3. 时间、确认与有效性
|
||||
|
||||
| 维度 | 含义 | 禁止的推断 |
|
||||
| --- | --- | --- |
|
||||
| `event_time` | 原记录描述的事件发生时刻或区间;带精度、来源依据,未知可空 | 不以回顾时间填补未知发生时间;消息发送时间只有在确实表示该事件时才可采用 |
|
||||
| `review_generated_at` | 产生该归纳的回顾时间 | 较晚生成不表示事件较晚发生或旧决定已被替代 |
|
||||
| `recorded_at` | 对象或版本实际写入系统的时间 | 迟录、补录不改变事件先后,也不证明当时已知 |
|
||||
| `validity` | 该主张适用的区间及当前有效、被替代、存在争议或未知状态 | 不用确认时间、记录时间直接推导有效起止 |
|
||||
| `confirmation_state` | 自动归纳、已确认、用户修订、已撤销等内容审核状态 | 已确认不等于永久有效;未确认不等于已失效 |
|
||||
|
||||
`replaces` 表示有明确证据的新主张替代旧主张,记录方向、两端版本、适用范围、理由和来源。`contradicts` 表示两项主张存在证据支持的冲突,不自动决定谁正确。它们是目标关系语义,不能将当前 `contrasts` 或单纯 `revised` 无条件映射为这两类关系。新增选项、日期较新、措辞更肯定都不足以建立替代关系;没有证据时保留并列主张或待核对差异。
|
||||
|
||||
时间区间输入使用带时区的 ISO 8601 时间,查询归一化为 UTC。概念 `time_range` 使用闭区间 `[from, to]`,与现有监督范围保持一致;区间事件按相交判定。检索支持明确的 `time_basis`:默认 `event_time`,也可指定 `review_generated_at` 或 `recorded_at`。响应回显所用维度。未知时间不冒充命中区间,返回可计算的排除数量或 `unknown`,允许通过不限定时间的查询另行查找。
|
||||
|
||||
`timeline` 按发生时间和稳定 ID 排序,未知时间单列,展示目前保存的演变及缺口。它可以包含后来补录的旧事件,因而不等于“当时已知”。`current` 给出截至读取时、在已覆盖证据内可支持的当前理解;有冲突或缺口时保留并说明,不能仅挑最新摘要作为答案。
|
||||
|
||||
`as_of` 是未来能力,首期输入 schema 不接受该模式或参数,调用必须明确返回不支持,不能悄悄降级为 `current`。只有具备所需对象版本、人工修订记录以及可用来源版本/历史重建,并能验证不会泄漏当时未知的信息后,才另行定义其语义和验收。本文不批准为它新建持久化日志或快照体系。
|
||||
|
||||
## 4. 公共读取合同
|
||||
|
||||
三个工具都只读已有数据,不调用模型、不自动发起回顾、不更新 checkpoint、不确认或修订对象、不创建任务,也不写入记忆或知识库。检索采用已有数据的确定性过滤与相关性排序,不能在调用中借模型补齐缺失事实。生成面向用户的建议由调用方 Agent 完成,必须引用返回证据并遵守其范围与限制。
|
||||
|
||||
默认 scope 为调用会话的当前项目,由 Main 解析并冻结;没有当前项目时要求显式选择 scope,不默认为 Global。调用方可显式请求 Global 或指定项目集合,沿用已有范围与能力授权校验。分页继续使用首请求范围,切换 UI 当前项目不改变旧游标。对象与来源读取也要重新校验归属,稳定 ID 不能绕过 scope。
|
||||
|
||||
首期沿用现有精确 scope 结果归属,不把 Global 摘要冒充单项目结论,也不隐式把 Global 结果投影为项目上下文。项目查询缺少该 scope 数据时明确返回空结果及覆盖状态。未来跨 scope 复用只接纳能按来源项目和事实依据正确筛选的叶子,跨项目归纳仍保留真实范围。
|
||||
|
||||
| 响应部分 | 必须表达的信息 |
|
||||
| --- | --- |
|
||||
| 范围 | `requested_scope`、`resolved_scope`、每项原始 scope、所选时间区间和时间维度 |
|
||||
| 修订 | 对象/结果修订引用、来源版本、读取时间;区分已知版本与 `unknown` |
|
||||
| 覆盖 `coverage` | 已知处理范围、来源版本和片段区间、缺失或未处理部分、未纳入来源类型;缺少数据时为 `unknown`,不能用零代替未知 |
|
||||
| 分页 `page` | `returned_count`、`has_more`、`next_cursor`、本页是否完整及限制原因;集合数量不能计算时为 `unknown` |
|
||||
| 证据 | 来源引用与 locator、关系依据、确认与有效性、已知冲突和历史缺口 |
|
||||
|
||||
覆盖与分页独立:最后一页只证明匹配结果读完,不能证明所有原始消息已被整理;已处理全部入选正文也不证明模型识别了全部事实。旧 `coverage_json` 只有条数时,不能推导完整的时间、版本或语义覆盖。
|
||||
|
||||
列表及长字段都需有界分页,超过响应容量时提供可续读的位置,无法分页的响应明确失败。不得静默裁剪后标完整。游标绑定过滤条件、排序和可用结果修订;分页期间内容变化、无法维持同一读取版本时返回 `stale_cursor` 并要求重新查询,不混入新旧页。该规则可基于现有修订检查完成,不要求持久化查询快照。取消、读取失败与真实空页分别返回。
|
||||
|
||||
## 5. 工具定义
|
||||
|
||||
### `story_graph_search`
|
||||
|
||||
输入:`query`、可选 `scope`、`object_types`、`time_range`、`time_basis`、`page_size` 和 `cursor`。`object_types` 对应第 2 节的对象类型;省略时检索全部支持类型。默认 scope 及时间规则按第 3、4 节执行。对象页默认 10 项,上限 50 项。匹配为不区分大小写的连续文本匹配,标题、实体标签和摘要字段权重高于其他字段;同分按类型、稳定 ID 和结果 ID 排序。
|
||||
|
||||
输出相关对象页,包含匹配原因或命中字段、明确标为摘录的预览、稳定引用、修订、相关演变和来源入口,以及公共响应信息。过滤须先于分页,按相关性排序并以稳定 ID 消除同分歧义。不能只在最近若干摘要中筛选后声称查完历史,也不能将摘要中的命中替代叶子事实。
|
||||
|
||||
### `story_graph_get_context`
|
||||
|
||||
输入:`object_ref`、可选 `scope`、`mode: current | timeline`(默认 `current`)、可选时间范围与维度、页大小及游标。`current` 表达当前理解;时间过滤只限定证据集合,响应需标明“在所选证据范围内”,不能称作该区间终点时的历史状态。
|
||||
|
||||
输出对象当前描述、明确的决定/工作状态、偏好与适用范围、未解决事项、相关实体及关系、演变和证据引用。`timeline` 分页列出提出、补充、验证、修订、替代和冲突证据,保留旧决定及新修订;多组集合或长正文须各自提供续读位置,不能只对顶层对象分页。返回的是存储事实及其组织视图,不在工具内生成新建议。
|
||||
|
||||
D1 返回各结果快照、当前投影、事件、变化、关系、来源及成功叶子的独立描述。既有存储没有可靠的有效期和替代关系枚举,因此 `validity` 返回 `unknown`,保留 `revised`、`contrasts` 及原始理由供调用方核对。当前实体投影不会遮掉旧结果或叶子描述。`content` 是可拼接的 JSON 文本片段,每项附零基码点位置;按对象引用和结果 ID 拼接后解析。`content_page_size` 默认 4,000、上限 8,000 码点,游标可停在对象内部。完整响应超过 100,000 字节时明确失败,要求缩小页大小。
|
||||
|
||||
对象不存在、scope 不匹配、版本缺失或只有部分历史时分别说明。已存在的某个结果快照可作为具体版本读取依据,但不能作为任意 `as_of` 查询的替代品。
|
||||
|
||||
### `story_graph_read_source`
|
||||
|
||||
输入:`source_reference_id`、可选 `scope`、明确的读取种类 `snapshot | version | current`、所需 `source_version`(读取指定版本时必填)及片段页位置。默认 `snapshot`,读取该引用实际保存的证据片段;不默认换成当前原文。
|
||||
|
||||
输出正文页、`content_kind`、请求版本与实际返回版本、`source_location`、原来源 ID、已保存片段与整份来源的范围和长度、可用时间元数据,以及公共覆盖和分页信息。位置用零基、左闭右开的 Unicode 码点 `[start, end)`;消息 locator 尽量保留项目、会话、消息、顺序和角色,本地资料保留文档/分块,外部片段保留已有 locator。缺少位置时明确标记,不伪造链接。
|
||||
|
||||
`snapshot` 只声称返回当次实际保存的片段;`version` 仅在该来源支持并保留指定版本时成功;`current` 显式读取当前内容并报告与引用版本是否不同。历史版本不可用时返回 `historical_version_unavailable` 及可用读取选项,由调用方决定是否另读当前版本。来源删除或不可访问时返回对应可用性;已保存片段仍可读时准确标注其范围。读取完一个片段不能称整份原文已读完,超长内容必须可续页,无静默截断。
|
||||
|
||||
D1 来源页默认 4,000、上限 8,000 码点。`current` 通过既有 locator 精确读取消息、任务状态正文或保存的知识引用片段;不拉取外部知识库原文。`version` 仅在当前可读来源的修订恰好等于请求版本时返回该版正文。旧版正文没有保留时明确不可用;无 locator 的旧引用仍可读 snapshot。当前来源迁出所选项目时拒绝读取。已保存引用的范围与当前位置分别校验。
|
||||
|
||||
## 6. 内置 MCP 与 Runtime 接入
|
||||
|
||||
内置 MCP 列表提供 `Story Graph`,能力 ID 为 `story-graph`,默认分配给 Model、OpenCode、Continue 和 DeepSeek Harness;监督者应用默认关闭,开启应用后才可发现工具。工具描述说明适用于“继续上次工作”“此前如何决定”“方案为何改变”等请求。普通聊天不强制查询,也不把整份图谱自动注入每轮上下文;Agent 先搜索、再取相关对象上下文,在需要核对原话、处理冲突或详细引用时读取来源。
|
||||
|
||||
| 配置或目录 | 当前状态 | 目标映射 |
|
||||
| --- | --- | --- |
|
||||
| `heartbeatEnabled` | 监督者应用开关,新设置默认关闭 | 为 Story Graph 暴露和每次调用的必要条件;关闭后不列出三个工具,旧会话直接调用也拒绝 |
|
||||
| 内置能力 `enabled` 与 `assignments` | [CapabilityService](../../../src/main/capabilities/capability-service.ts)管理现有 MCP 开关与 Runtime 分配 | `story-graph` 沿用同一管理方式;应用关闭时不得由内置能力开关绕过,保留用户原分配 |
|
||||
| 内置工具目录 | 已有 Story Graph 条目 | 映射 `story_graph_search`、`story_graph_get_context`、`story_graph_read_source`,全部 `access: read`、`readOnlyHint: true` |
|
||||
| 监督整理参数 | 超时、并发、来源页与批次设置已存在 | 控制后台整理;不让一次 MCP 查询启动整理,也不以正文批次预算截断检索结果 |
|
||||
| 计划 `enabled` | 决定自动回顾是否到期执行 | 不决定已有图谱能否读取;应用开启且能力可用时,无计划也能查已有结果 |
|
||||
|
||||
Main 在工具发现和调用分发时共同检查应用启用、能力启用、Runtime 分配和 scope;只隐藏菜单不足以阻止旧会话调用。关闭后拒绝新的读取请求,已在途响应交付前复核状态,避免旧绑定继续返回数据。启用工具本身不产生回顾或数据写入。Ask 与 Execute 均可读取,同样没有写工具,也不借此扩大 Execute 权限。
|
||||
|
||||
本地 OpenCode、Continue 和原生客户端复用 [KnowledgeMcpGateway](../../../src/main/agent/knowledge-mcp-gateway.ts) 的 MCP 注册和分发;直连 Model 与 DeepSeek Harness Main 代理调用同一服务。远程 OpenCode/Continue 复用现有图像工具的受管 HTTP MCP 和二进制通道,`storyGraph` 描述独立于图像生成描述,Ask 可只携带图谱读取。请求仍由桌面 Main 解析范围和读取 SQLite,不把数据库、正文库或凭据复制到 Host。
|
||||
|
||||
远程发现每次向 Main 复核启用状态;调用复用同一个范围、分页及版本读取入口。OpenCode 根会话和子会话精确放行当前受管端点的图谱工具;Continue Ask 保留 Main 绑定的会话端点并只允许共享只读工具名。DeepSeek Harness 的 Main 目录、参数转换和 Host 执行钩子均接入这三个工具;其他普通内置 MCP 的 Harness 限制保留。当前远程 Runtime 支持 OpenCode/Continue,不新增远程 Harness。桥接不可用时明确失败。
|
||||
|
||||
远程使用须配套部署含此接线的 Desktop 与 Agent;旧 Agent 不识别新增的工具描述字段。本次没有发布或更新受管包,源码验证不代表已发布客户端具备该能力。
|
||||
|
||||
## 7. Global 与项目复用
|
||||
|
||||
当前 Global、单项目和不同项目集合分别维护 checkpoint 和故事线候选身份,项目集合排序归一化后才比较。项目 A 已处理某消息,不会使 Global 的相同消息自动成为已处理;Global 当前会按自身范围重新收集原始来源,不能描述为“只聚合已有项目图谱”。报告与监督也是独立阶段,不能互相推进位置。当前手动重看还会重新整理所选历史。
|
||||
|
||||
目标是同一来源版本的事实提取成功一次后,在 Global 和项目查询/整理中复用可用叶子事实,包括事件、实体变化、关系证据及来源位置,不只复用项目摘要。Global 可在已提取事实之上组织跨项目关系和摘要;范围变化本身不应造成相同正文再次提取。范围控制查询归属和综合判断,不再充当重复提取的唯一分界。
|
||||
|
||||
复用须核对原来源身份、版本、已覆盖片段及提取合同是否仍适用。仅部分成功时继续缺失部分;来源改版或用户明确要求重新分析时可产生新的提取结果,不能覆盖已确认内容。任务的当前状态、偏好背景和既有摘要仍须标明时间角色,不能作为跨 scope 复用时新发生的事件。共享事实的可见范围不能被 Global 摘要扩大。
|
||||
|
||||
这一目标尚未实现。本轮不批准具体数据迁移、跨 scope 主键重写、历史实体合并、checkpoint 转换或缓存表方案。实施前应从已有成功叶子和来源版本定位验证最小复用方案,列出旧数据的可复用程度与缺口;保留现有精确 scope 行为直到替代方案完成验证,不提前修改现有 checkpoint 含义。
|
||||
|
||||
## 8. 记忆过渡与分阶段交付
|
||||
|
||||
已确认的长期偏好必须继续可用,并保留 Global/项目适用范围。旧记忆只有更新时间时,只能显示为当前偏好背景和实际更新时间,不能补造“首次决定”“当时生效”或替代关系。新的图谱归纳与已确认偏好冲突时返回待核对差异,不以较新生成时间覆盖用户选择。
|
||||
|
||||
| 阶段 | 交付内容与退出条件 | 当前状态 |
|
||||
| --- | --- | --- |
|
||||
| D0 设计 | 记录工具、时间、范围、过渡与验收合同 | 已记录 |
|
||||
| D1 只读查询 | 基于现有数据实现三个工具及本地/远程接线、配置目录、关闭拦截和分页;缺失版本与覆盖返回未知,对应 US-S28 至 US-S30 | 已实现;验证范围见实施进度,当前 memory 读取继续保留 |
|
||||
| D2 事实复用 | 验证同一来源版本跨 scope 复用完整叶子;明确身份、版本变化与已有数据处理方案,达到 US-S31 的范围验收 | 待设计具体存储调整并实施;无已批准迁移方案 |
|
||||
| D3 概念与读取统一 | 让统一入口承接已确认偏好及图谱上下文;核对引用、范围、冲突和旧数据可读性后,才切换现有 memory 消费路径 | 待实现;不以删除旧记录作为退出条件 |
|
||||
| 未来历史重建 | 单独定义并验证 `as_of` 所需版本与当时可知性 | 不属于首期,不承诺交付时间 |
|
||||
|
||||
D1 可将现有已确认偏好作为明确标记的背景投影,原读取路径在过渡期仍工作。重复背景按原记忆身份识别,图谱未知历史不能由记忆更新时间补齐。具体迁移与旧入口退出时点须根据实现证据确定,本文没有要求立即删除旧 memory API、设置或 UI。
|
||||
|
||||
## 9. 验收映射
|
||||
|
||||
核心场景使用可核对的旧决定 A、后来明确修订为 B 的证据,以及“现在继续做应采用什么”的请求。工具返回 A、修订事件、替代依据与 B;Agent 基于它们推荐 B,并引用旧决定和修订来源。新增选项 C 但没有替代依据时,不得把 B 判为失效;决定 B 也不能被回答为 B 已执行完成。
|
||||
|
||||
| 验收 | 对应故事 | 通过条件 |
|
||||
| --- | --- | --- |
|
||||
| 决定演变 | US-S28 | 完成上述旧决定 → 有证据修订 → 当前建议及引用链;冲突没有证据解决时保持待核对 |
|
||||
| 时间 | US-S28、US-S29 | 迟录旧事件不变成新事件;确认与有效性分开;timeline 不声称当时已知;`as_of` 明确不支持 |
|
||||
| 分页与来源 | US-S29 | 匹配项、演变和长原文均可读到末页;覆盖未知与零命中可区分;游标失效不混页;旧版不可用不返回伪历史 |
|
||||
| 启停与只读 | US-S30 | 应用关闭后发现与直接调用均不可用;开启但无计划可读已有数据;工具自身模型调用与业务写入均为零 |
|
||||
| Runtime 一致性 | US-S30 | 本地及 GoodBuddy Agent 远程实际链路具有相同 scope、版本、分页、引用与关闭行为,不能以共享 schema 代替接线验收 |
|
||||
| 记忆及范围复用 | US-S31 | 确认偏好与已有引用保留;同版本成功叶子跨 scope 复用且能检索事实,不只返回摘要;历史缺口不补造 |
|
||||
|
||||
验证命令和证据集中记录在[实施进度](./progress.md#2026-09-28-story-graph-只读工具)。本次只运行定向测试、类型检查、限定文件 lint 和确定性 Host 探针,没有全量测试、付费模型请求或发布操作。真实模型如何根据冲突证据形成最终建议,仍需与工具读取正确性分开验收。
|
||||
@@ -0,0 +1,86 @@
|
||||
# 故事线模型改造设计
|
||||
|
||||
状态:2026-10-02 已确认的目标设计。第 1 步(共享时间线、事件起止与密度、跨项目开关)、第 2 步(故事归属与用户调整)、第 3 步(时间螺旋视图)、第 4 步(经验抽取)和第 5 步(心跳故事建议、MCP 故事与经验查询、工作回顾按故事组织)已实现,见[实施进度](./progress.md)。本文定义监督回顾如何产出下一版故事线(时间螺旋、分层故事木片、经验 W),是 [3D 故事线图谱概念](../story-graph/3d-concept.md) 的数据来源合同。现有回顾流程见[回顾分块调度设计](./review-scheduling-design.md),现有图谱工具见 [Story Graph MCP 设计](./story-graph-mcp-design.md)。
|
||||
|
||||
## 1. 结构总览
|
||||
|
||||
| 层 | 含义 | 来源 |
|
||||
| --- | --- | --- |
|
||||
| 全局(根) | 整条时间螺旋本身,即用户的全部经历;不是一片木片 | 所有来源 |
|
||||
| 项目木片 | 按会话所属项目划分;无项目归属的内容进入“未归属项目” | 会话归属,确定性,不调用模型 |
|
||||
| 跨项目故事 | 同时涉及多个项目或不属于任何项目的长期事情,例如长期偏好、学习方向 | 故事归属阶段 |
|
||||
| 功能木片 | 项目内的长期功能或主题 | 故事归属阶段 |
|
||||
| 子线索 | 事件量足够大的功能再拆分;小功能不拆 | 故事归属阶段 |
|
||||
| 经验 W | 从故事中形成、可复用到其他故事的经验,默认是全局对象 | 经验抽取阶段 |
|
||||
|
||||
全局不作为单独的“全局记忆”重复处理:它是根视图,读取同一套事件、故事和经验。查看某个项目时,只是以该项目为过滤条件。
|
||||
|
||||
## 2. 已确认的决定
|
||||
|
||||
| 编号 | 决定 |
|
||||
| --- | --- |
|
||||
| SL-1 | 层级为 全局 › 项目 › 功能 › 子线索;子线索仅在功能事件量达到阈值时出现,阈值可在设置中调整 |
|
||||
| SL-2 | 跨项目或无项目的长期事情,作为全局根下的“跨项目故事”,与项目木片并列 |
|
||||
| SL-3 | 每个事件只归属一个主故事,可另外关联其他故事;关联不影响木片的长度和密度统计 |
|
||||
| SL-4 | 经验自动生成即可供 Agent 读取,不设确认门槛;标记“自动归纳”,用户可以编辑、合并、删除 |
|
||||
| SL-5 | 用户对故事和经验的调整(改名、合并、拆分、移动事件、编辑、删除)受保护,后续回顾不覆盖;删除的对象不会因同一证据再次生成 |
|
||||
| SL-6 | 注意力密度按来源统计:时段内的消息轮次与正文量,不由模型判断;只表示投入集中程度 |
|
||||
| SL-7 | 每个来源版本只提取一次,项目与全局共用提取结果和增量进度 |
|
||||
| SL-8 | 设置提供“跨项目关联”,默认关闭。关闭时新内容只关联本项目已有的知识;开启后可关联其他项目中的同一件事,跨项目故事也只在开启时生成 |
|
||||
|
||||
## 3. 回顾流程改造
|
||||
|
||||
```text
|
||||
采集来源(按来源版本增量)
|
||||
↓
|
||||
批次提取:事件带开始/结束时间(取原始消息时间)、来源引用
|
||||
↓
|
||||
故事归属(新增,不读原文)
|
||||
· 项目:按会话归属确定
|
||||
· 功能/子线索/跨项目:召回候选故事 → 一次模型调用判断归入已有、新建或暂不归类
|
||||
↓
|
||||
经验抽取(新增,低频,不读原文)
|
||||
· 规则挑候选:有明确结果的决定、多个故事中重复出现的做法、说明原做法问题的修订
|
||||
· 有候选时一次模型调用:经验内容、形成依据、适用条件、边界
|
||||
· 记录后续应用及结果
|
||||
↓
|
||||
导航摘要 → 发布
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 故事跨回顾保持同一身份,沿用实体候选编号机制;长时间无事件的故事能被接回,不新建。
|
||||
- 故事状态(进行中、已结束)必须有内容依据;长期无事件只标“暂无进展”,不推断结束。
|
||||
- 允许“暂不归类”,不为凑层级强行归组;暂不归类的事件在项目下以淡色木片显示。
|
||||
- 经验的每条依据必须对应到具体事件和来源,否则不保存;没有候选时不调用模型。
|
||||
- 新增阶段失败时,已发布的提取结果和处理进度保留,可单独重试该阶段。
|
||||
|
||||
## 4. 用户调整
|
||||
|
||||
图谱是可编辑的结果,不需要逐条审批:
|
||||
|
||||
- 故事:改名、合并、拆分、调整层级、把事件移到其他故事。
|
||||
- 经验:编辑内容与适用条件、合并重复、删除。
|
||||
- 入口位于故事线图谱的详情面板与 3D 视图选中对象的详情中,共用同一组操作。
|
||||
- 每次调整可撤销,并记录在活动记录的修改记录中。
|
||||
|
||||
## 5. 展示与使用
|
||||
|
||||
- 故事线图谱新增 3D 视图,与现有平铺视图并列,几何、时间尺度、聚合规则沿用 [3D 概念](../story-graph/3d-concept.md);Three.js 仅在打开 3D 视图时加载,WebGL 不可用时使用平铺视图。
|
||||
- MCP 增加按故事、按经验查询;经验返回时附“自动归纳 / 用户编辑”标记和依据。
|
||||
- 心跳建议增加“故事长期无进展”和“已有经验可能适用于当前故事”。两者都由规则选出:前者按设置的天数判断,只提示不改状态;后者要求共享不过于通用的实体。每次心跳各最多 3 条,排在未决事项之前。
|
||||
- 工作回顾按故事组织:推进了哪些故事、新出现哪些故事、形成或应用了哪些经验。
|
||||
|
||||
## 6. 存储
|
||||
|
||||
新增故事表(含层级与父故事)、事件与故事的归属/关联、经验表、经验依据与应用记录、用户调整记录;对应一次数据库升级。已有回顾结果在升级后按新流程重建,原始来源不变。
|
||||
|
||||
## 7. 实施顺序与验收
|
||||
|
||||
| 步骤 | 内容 | 验收(真实数据副本 + `.env.deepseek.local` 中的 DeepSeek) |
|
||||
| --- | --- | --- |
|
||||
| 1 | 统一时间线(SL-7)、事件时间跨度与密度(SL-6) | 重叠回顾不产生重复事件;项目与全局不重复处理同一来源 |
|
||||
| 2 | 故事归属与用户调整(SL-1~SL-3、SL-5) | 多次增量回顾后故事不碎片化;间断后的事件接回原故事;调整不被覆盖 |
|
||||
| 3 | 3D 视图接入真实数据 | 长期数据下时间尺度与聚合正常 |
|
||||
| 4 | 经验抽取(SL-4) | 每条经验依据可追溯到原文;无候选时零额外调用 |
|
||||
| 5 | MCP、心跳建议、工作回顾页 | Agent 能按故事接手工作、按经验给出建议 |
|
||||
@@ -0,0 +1,66 @@
|
||||
# 监督者应用产品设计
|
||||
|
||||
日期:2026-09-23。状态:工作回顾、图谱、活动和设置已接入;其余目标能力以[实施进度](./progress.md)为准。
|
||||
|
||||
本文定义监督者应用的定位、功能边界与知识库关系。[会话监督 PRD](./prd.md)继续负责对执行过程的观察、证据与介入边界;本文将该能力纳入更完整的工作回顾应用。
|
||||
|
||||
## 产品定位
|
||||
|
||||
监督者是持续关注指定工作范围的智能 Agent。用户可以主动要求回顾,也可以通过智能心跳定期唤醒它。它根据可见记录判断是否需要反馈、整理阶段或更新知识关联,没有值得反馈的变化时保持安静。
|
||||
|
||||
| 名称 | 职责 |
|
||||
| --- | --- |
|
||||
| 监督者 | 应用名称,负责观察、回顾和解释工作进展 |
|
||||
| 智能心跳 | 按计划触发增量回顾,并按配置的介入方式生成建议;不单独读取原文或生成报告(FR-S12) |
|
||||
| 监督反馈 | 会话右侧的阶段回顾、问题提示与证据入口 |
|
||||
| 故事线图谱(Story Graph) | 呈现事件、知识实体及其随时间发展的关系 |
|
||||
|
||||
FR-S1:将现有智能心跳入口整合、升级为监督者,不增加另一个平级重复应用。当前通过应用中心管理;未来应用市场提供同一应用的发现与启用入口。市场建设不应成为监督者落地的前置条件。
|
||||
|
||||
应用默认关闭,用户显式启用后才接受新的回顾工作;关闭时保留计划和历史。默认值、已保存选择、自动运行条件及在途运行边界由[应用启停与执行](./logic-design.md#应用启停与执行)统一定义。
|
||||
|
||||
FR-S2:应用包含“工作回顾 / 故事线图谱 / 自动监督 / 活动记录 / 设置”五个主视图。工作回顾提供统一监督结果阅读、历史选择和手动新回顾,结果显示自身范围与日期;自动监督集中展示计划状态、范围、周期及下次执行时间,以及运行概览、指标、趋势、自动监督报告、待处理记忆与行动建议和心跳审计,创建和编辑使用同一份 Modal 表单;活动记录展示统一实际执行、进度、详情及结果入口,支持按计划 ID 筛选;设置只管理超时、并发、分页、分批及单次响应容量,不嵌套次级菜单。计划保存失败保留草稿,关闭未保存修改须确认;编辑已暂停计划保持暂停,创建须显式保存并启用。原有运行记录及应用、存储标识保留。
|
||||
|
||||
当前设置还支持分别调整报告与监督整理的模型超时,以及两者共用的监督并发数。并发设置不限制普通聊天,也不保证避免上游限流;排队不消耗模型阶段时限,降低并发不打断在途请求。取值及生效规则由[当前控制清单](./review-scheduling-design.md#当前超时与调度控制清单已实现)定义。
|
||||
|
||||
FR-S10:活动同时覆盖手动监督回顾、定时心跳及用户立即运行的心跳。每条记录显示触发方式、范围、开始与结束时间、运行状态、失败原因和已有结果入口。同一次心跳的报告和下游监督回顾合为一条活动,报告保存成功不能掩盖下游仍在运行、暂停或失败。活动读取不调用模型,关闭应用后不查询活动。新批次运行支持暂停及继续,保留成功批次;人工修改的时间序列审计尚未实现,历史缺失不得猜测补齐。
|
||||
|
||||
FR-S2:分页只控制数据库读取批量,不能限制最终处理范围。当前已开放来源页大小、每批正文/消息上限;这些配置在新建回顾时保存,继续沿用。单次响应容量按当前设置生效。回顾持续处理至完成,不按累计运行时长自动暂停。当前字段和未来高级参数的边界见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界),交互见 [UI 设计](./ui-design.md#算法配置设置目标)。完整覆盖、来源校验与事实保留不作为可关闭选项。
|
||||
|
||||
## 范围与触发
|
||||
|
||||
FR-S3:首次启用时选择项目、会话及可用来源,生成前显示范围和时间区间。应用启用不等于默认观察全部内容。已有心跳的 Global、单项目和多项目范围语义由[智能心跳 PRD](../smart-heartbeat/prd.md)定义,统一应用时保留。
|
||||
|
||||
FR-S4:支持手动回顾,并复用已有定时检查机制。自动报告和监督回顾分别只处理所选范围、回顾窗口内新增、修订或尚未处理的来源片段;没有待处理输入时跳过模型及结果生成。失败输入保留重试资格,预算遗漏不得记为已处理。手动回顾重新整理所选历史区间。具体规则见[自动增量与手动重看](./logic-design.md#自动增量与手动重看)。事件触发和阶段自动判定仍待设计。
|
||||
|
||||
回顾默认增量“回顾”默认增量,与心跳整理一致;重看历史改为“更多”中的显式“重新整理”。心跳不再先生成独立报告,同一来源版本在一次触发中只读取一次。规则见[心跳职责与介入](./logic-design.md#心跳职责与介入目标设计)。
|
||||
|
||||
FR-S12(已实现,主动介入除外):智能心跳的职责限定为**定时触发增量回顾**和**按计划配置介入**。介入方式为“仅更新记忆”“生成建议”(默认);会话级“主动介入”后续交付,当前不显示。建议只根据本次图谱变化产生,附来源依据,替代旧心跳报告、记忆建议和行动建议;无变化不调用模型,有变化而无建议候选时不发起建议调用。验收见 US-S32、US-S33。
|
||||
|
||||
FR-S5:会话右侧在跟随模式下展示当前会话的监督反馈;固定到 Conversation、Task 或 Experiment 时保持所选目标并显示其名称,切换聊天不改变监督目标。跨会话总结在完整应用中展示。反馈包含结论、来源和“在图谱中查看”定位入口。用户主动选择“发到当前会话”才加入聊天消息,同一结果不重复触发侧栏和全局通知。
|
||||
|
||||
FR-S6:监督者可提出整理与修改建议;已有会话监督的观察边界继续生效。自动更新图谱不能静默覆盖用户修改的描述、已确认的关系或已有知识库正文。取消整理后保留上次结果;失败说明受影响范围并允许重试,部分结果不得显示为完整回顾。
|
||||
|
||||
FR-S4、FR-S6、FR-S10 的验收要求:完整分页处理冻结范围内的入选来源;叶子事实和来源完整保留,摘要用于导航。用户主动暂停或失败后保留成功批次与位置,重启后继续,不重复计算仍有效的成功批次;未读完或汇总未成功不能标完成。该续跑要求已进入生产接线,当前实现、实测范围与剩余差异见[回顾分块调度](./review-scheduling-design.md#0-生产接线与剩余边界)。
|
||||
|
||||
## 与知识库的关系
|
||||
|
||||
FR-S7:知识库管理资料、索引、检索和可复用知识;监督者管理工作事件、阶段总结与演变关系。故事线图谱共用现有来源,引用知识库对象,不复制一套资料正文,也不取代已有知识库图谱。
|
||||
|
||||
一个知识实体可以关联多份资料,一份资料也可支撑多个实体。图谱的关系与阶段记录由监督者维护,原始内容由所属会话、笔记或知识库维护。新增功能的概念实体与现有图谱实体是否共用标识,需结合现有模型制定技术方案。
|
||||
|
||||
FR-S8:本地知识库提供双向定位:资料详情可进入相关故事线图谱,图谱详情可返回资料及引用位置。已有本地条目显示“打开知识库条目”;新归纳提供“新建条目”或“补充已有条目”,先预览内容和引用再保存。外部引用沿用现有能力,打开已保存的有界片段及可用定位信息,不承诺重新打开远端原文;缺少位置时明确提示。外部知识库保持只读;不得因图谱整合而增加对 Dify、FastGPT、RAGFlow 的写入或批量同步。
|
||||
|
||||
FR-S9:来源事实、自动归纳、待核对关联和用户确认内容分别标注。移除关联不删除原文,纠正操作可撤销。来源失效时显示不可用状态;回放历史时使用当时已有的归纳与依据,无法恢复旧版原文时明确提示,不能以当前版本冒充历史内容。
|
||||
|
||||
## 落地范围
|
||||
|
||||
FR-S11(D1 已实现,D2/D3 待实现):监督者以时态 Story Graph 为主要产出,统一用户侧工作记忆概念并保留已确认偏好与已有数据。Agent 在继续工作、查询此前决定及其修订时,按需使用只读的 `story_graph_search`、`story_graph_get_context`、`story_graph_read_source`,读取范围、时间、演变、覆盖及可引用来源;工具不调用模型或触发回顾,监督者关闭时不暴露且拒绝调用。本地和远程 Runtime 共用读取语义。后续目标为同一来源版本提取一次并跨 scope 复用叶子事实;当前精确 scope 独立处理及 memory 读取继续保留。`as_of` 仅在历史重建能力具备后另行交付。合同与过渡阶段由[专项设计](./story-graph-mcp-design.md)定义,验收见 US-S28 至 US-S31;本轮没有数据迁移。
|
||||
|
||||
触发、整理与结果共享规则,以及整体架构和图谱对象关系见[逻辑设计](./logic-design.md)。
|
||||
|
||||
首个完整流程为:启用监督者、选择范围、生成回顾、进入图谱、核对来源、携带选定上下文继续讨论。知识库双向定位与内容补充随后接入同一流程。图谱关系辅助检索需单独验证效果,不作为首版默认检索策略。
|
||||
|
||||
当前工作回顾和图谱读取生产结果,活动展示实际执行与无变化检查、暂停和继续入口,自动监督设置复用已有计划。完整图谱回放及应用市场尚未实现,验收证据见[实施进度](./progress.md)。
|
||||
|
||||
具体 UI 与验收见[UI 设计](./ui-design.md)和[User Stories](./user-stories.md)。图谱对象与操作由[故事线图谱设计](../story-graph/prd.md)定义。已实现的实体身份和增量处理方式见[技术设计](./technical-design.md),来源旧版本能力仍受原应用限制。
|
||||
@@ -0,0 +1,322 @@
|
||||
# 监督者技术设计
|
||||
|
||||
## 文档信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 生产分页、持久化批次、暂停/继续及设置已接线;27 条消息完整会话通过真实模型验证,较大样本仍未完成;完整历史回放未实现 |
|
||||
| 日期 | 2026-09-23 |
|
||||
| 产品设计 | [监督者应用产品设计](./supervisor-prd.md) |
|
||||
| 行为规则 | [监督者逻辑设计](./logic-design.md) |
|
||||
| 界面设计 | [监督者 UI 设计](./ui-design.md) |
|
||||
| 实施进度 | SQLite schema 50(心跳介入与监督建议见[心跳触发与建议](#心跳触发与建议2026-09-30));当前算法、设置和验收边界见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界) |
|
||||
|
||||
本文回答如何在现有 GoodBuddy 桌面端中实现监督者。它不改变产品范围,也不把模拟 Demo 当作生产数据模型。
|
||||
|
||||
FR-S11 的读取架构见[时态 Story Graph 内置 MCP 设计](./story-graph-mcp-design.md)。三个只读工具通过 `readStoryGraph` 投影现有 SQLite,Main 在发现、调用和返回前检查监督者启用及 Runtime 分配。本地 MCP、Model、Harness Main 代理和远程受管工具通道共用此入口;无 schema 迁移。该文独立定义对象/版本合同、时间语义、配置映射及后续跨 scope 复用和记忆过渡。当前 memory 背景读取、精确 scope checkpoint 与候选身份保留;结果快照不支持 `as_of` 历史重建。
|
||||
|
||||
生产入口由 `supervision-production.ts` 组装服务、SQLite、共享池和 ask Runtime。`supervision-review-store.ts` 保存冻结来源清单,按有界页和片段供 `SupervisorService` 派发;叶子及连续位置先提交,导航和完整结果之后发布。消息已有知识引用保留本地或外部 locator,已确认记忆通过独立背景输入提供。不会检索外部全库。详细存储、调度及当前限制统一见[分块调度第 0 节](./review-scheduling-design.md#0-生产接线与剩余边界)。
|
||||
|
||||
模型实体 `id` 只在单次输出内有效。Main 从同一 scope 的故事线提供最多 100 个未撤销实体候选,生产提示只暴露 `candidateRef`(如 `known_1`)、名称和说明。模型显式选择候选后,Main 转换为严格 UUID 类型的内部 `persistedId`。服务校验候选集成员和重复映射,保存事务再次按实体主键校验故事线归属及撤销状态。没有有效候选引用时分配新 UUID,不按名称或裸模型 ID 合并。来源仍按每次结果分配 UUID,保留原始 `source_id` 和 locator。
|
||||
|
||||
新实体省略 `candidateRef`,不要求模型生成数据库 ID。未知非空 `candidateRef`、冲突身份和候选集之外的 UUID 均拒绝。旧输出中的 `persistedId` 仅在 UUID 合法且属于候选集时复用;空值、占位符和格式错误值不能证明已有身份,保留为独立新实体。局部 ID 或同名不会触发合并。
|
||||
|
||||
真实旧库存在 `goodbuddy` 等文本实体主键。候选读取为这些键生成稳定 UUID 别名,并在 Main 内保留 `storageId` 到原键的映射;模型看不到这两个字段。别名使用 scope 和原键的 SHA-256 摘要构造 UUID v8,保存时按精确映射查询原实体,保留历史图谱引用和人工确认字段,不迁移或重写原主键。新实体仍由 Main 分配随机 UUID。此兼容仅处理已经存在的旧库键,模型返回同样的文本不能直接复用实体。实测见[真实失败运行修复](./progress.md#2026-09-24-真实失败运行身份修复)。
|
||||
|
||||
schema 42 为实体、实体变化和关系增加 `source_reference_ids_json`,保存映射后的来源 ID。升级只补列,旧记录、ID、确认状态及 locator 保留;此前未保存的引用使用空数组,已被覆盖或忽略的历史内容无法据此恢复。
|
||||
|
||||
schema 43 在结果上增加 `graph_snapshot_json`,保存当次实体名称、说明、确认状态、关系理由及本次来源引用。后续 run 只更新 `automatic` 的当前实体与关系,已确认、修订或移除状态受保护。历史结果读取自身内容;人工操作带 `resultId` 时先校验成员归属,在事务内更新当前对象及所选结果,其他结果不变。移除关系记录 `revoked`,防止后续相同端点和类型的自动关系恢复它。
|
||||
|
||||
升级从现有事件实体关联、实体变化和来源引用恢复结果成员,不猜测名称匹配;旧数据缺失的归属或已经覆盖的内容无法还原,原对象仍保留。新结果即使实体没有事件或来源,也会保存完整成员。结果内容与运行、事件、来源在同一事务提交。
|
||||
|
||||
`overview({ target? })` 返回稳定的 `id`、`storyLineId` 及来源 ID。指定 Conversation/Task 时,Main 读取目标实际项目,查询同时要求目标来源匹配、结果 scope 为 global 或包含该项目;LIMIT 在过滤之后。卡片展示结果真实范围,global 结果不会伪装成单会话摘要。`graph({ resultId, storyLineId? })` 校验二者归属,只返回该结果的事件、对象和来源;显式 ID 无效时不回退到最新结果。继续讨论按结果主键读取,并验证来源属于该结果。
|
||||
|
||||
工作回顾保存所选结果,切换或刷新使用请求序号忽略迟到图谱。侧栏按目标重建卡片,清空来源和预览,并忽略旧请求;工作栏 tasks 实例的 `targetRef` 可保存 conversation/task 固定目标,取消固定后恢复跟随当前选择。该绑定只控制监督卡片,不改变任务列表的项目范围。继续讨论预览显示实际目标名称和 ID,发送回调显式携带该会话 ID,由 Main 使用该会话的 Runtime 与项目。
|
||||
|
||||
侧栏通过 `onOpenSupervisionGraph(resultId)` 交给 App 导航;App 遵守现有离页检查,向 HeartbeatCenter / SupervisorWorkspace 传递类型化 `graphNavigation`,复用 `heartbeat` 路由及 keepalive 页面。每次点击创建新的导航请求,打开 graph 页签并更新历史选择共用的 resultId。overview 未包含该 ID 时仍直接请求其 graph;旧 overview、graph、来源和操作响应通过同一请求序号失效,不覆盖新结果。没有新增 window 事件、页面或持久化字段。
|
||||
|
||||
继续讨论预览将 `supervision:continue-context` 返回的 `prompt` 直接作为可编辑草稿。侧栏与图谱入口发送当前草稿,不重新拼接原始上下文。`supervision:continue` 沿用现有正文校验(去除首尾空白,最多 12,000 字符),使用空 `attachments` 入队,无附件请求省略 `serializedContexts`;请求未指定 Runtime 时使用会话保存的选择。会话已有的记忆和知识库配置仍按普通会话发送规则处理。知识库预览与提交均检查目标库存在且为本地可写库,更新实体还检查其实际所属库。侧栏确认框展示 Main 返回的实体字段、目标库和来源正文,取消或写入失败后可以重新预览。
|
||||
|
||||
监督来源清单将 UI 时间区间归一化为 UTC,先筛选再按稳定键分页。项目必须存在且 active;消息按闭区间筛选,任务按区间内创建或完成时间筛选。手动和自动监督共用该路径,自动清单另读取 supervisor checkpoint;手动心跳报告保留原有 collector。
|
||||
|
||||
已确认记忆作为当前背景,不参与新增正文覆盖。最多四条、每条 500 字符,时间来自真实更新时间;模型不能把背景引用为本批新增证据。旧结果继续保留原来源类型和 locator。监督正文上限现用于分批,余段继续处理;心跳报告的有界输入语义没有改变。
|
||||
|
||||
## 模型阶段超时与调度
|
||||
|
||||
监督整理超时由 run 创建时冻结;共享模型池仍采用实时设置上限。`supervision:pause` 取消该 run 的排队和在途工作,`supervision:resume` 继续已保存批次;监督整理输出在流中按 `supervisionReview.responseKiB` 检查响应容量,默认 1024 KiB,可在设置页调整为 100 至 16384 KiB。该容量每次请求读取当前设置,继续旧运行也采用新值;超限保留成功批次并提示调高后继续。生成摘要及事实内容不设独立短字符限制,监督旧摘要读取保留全文,具体边界见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界)。以下报告领取和租约规则继续适用。
|
||||
|
||||
手动和自动监督均在桌面 Main 调用生产工厂,Runtime 解析请求只有 `workMode: ask`,不携带项目、Runtime 选择或执行空间。`resolveRequestRuntime` 因而返回桌面默认 Runtime,不进入 SSH 项目或 `selectedRuntimes.getRuntime` 分支;回顾 scope 只筛选证据。摘要校验、合并和持久化在 Main 完成,single-flight、取消及 UI 控制不改变 `gbagent`、远端 Runtime 启动或桌面到 Agent 协议。
|
||||
|
||||
`ApplicationSettingsStore` 持久化报告和整理模型超时,均为 30..600 整数秒,默认 240。报告排队前冻结,监督创建 run 时冻结;计时从获槽及 Runtime 解析后开始,覆盖模型执行及其内部重试。流结束校验 signal 和完成事件,finally 释放临时会话,心跳报告保持原有 100KB 上限,监督整理使用上文的可调响应容量。采集、排队、保存和清理不计入模型超时。
|
||||
|
||||
`supervisorModelConcurrency` 为 1 至 4 的整数,默认 1。报告和监督整理共用 Main 内 `SupervisionModelPool` FIFO 池,普通聊天不入池;降低上限不终止在途请求。报告槽位从领取前持有至报告完成或无变化提交后,在下游监督开始前释放,避免并发 1 时相互等待。监督服务外层运行仍串行;该池不是完整分块调度器,也不提供跨聊天与监督的提供商全局并发、RPM、TPM 或 Retry-After 控制。
|
||||
|
||||
报告租约为 `max(300, reportTimeoutSeconds + 60)` 秒,300 是下限;每次到期领取最多一条。Runtime 解析后、模型请求开始前按冻结超时刷新租约,数据库要求运行 ID、owner、attempt、claimed 状态和未过期条件均匹配,否则拒绝启动模型。直连模型独立的 600 秒传输超时、网络重试、心跳重领及轮询不属于用户阶段时限。设置 UI 提供两个超时、共享并发及分页/分批/单次响应容量字段;整次回顾持续处理至完成,旧 `executionSeconds` 不再参与执行判断。
|
||||
|
||||
schema 47 的四张批次相关表使用外键随监督运行清理,未完成运行不新增自动过期删除规则。启动时原有 running 记录会标 failed,但已保存批次和配置保留,活动页仍可继续。发布逐页读取叶子并复用现有结果/来源/图谱写入,在同一事务更新完成状态和自动 checkpoint。
|
||||
|
||||
当前设置及冻结范围见[当前设置合同](./review-scheduling-design.md#当前设置合同)。未实现的高级参数继续保留在目标清单,不显示为可保存控件;没有兼容原型数据库的读取器。
|
||||
|
||||
### 单次执行与取消
|
||||
|
||||
对应 FR-S4、FR-S6、FR-S10;状态规则见[活动记录](./logic-design.md#活动记录)。同一个 `SupervisorService` 在异步配置读取前占用唯一执行位置,`run` 和 `resume` 共用准入。已有执行或停止清理时,手动新建及继续立即返回 `SUPERVISION_REVIEW_BUSY`;`trigger: heartbeat` 等待当前 Promise 结束,醒来后重新检查占用。等待中的自动请求尚未创建监督 run,不占模型池;心跳报告在进入下游监督前已释放自己的槽位。
|
||||
|
||||
`supervision:execution` 返回进程内 `{ active, runId?, stopping? }`,配置读取期间可有 `active: true` 而尚无 runId。`supervision:cancel({ runId })` 先在既有 `supervision_runs` 写入 `cancelled`、清除错误并保存取消时间,再向父 controller 传播 abort;重复取消保留原时间。该接口等待执行 Promise 结束,包含并行批次的 `allSettled` 和 Runtime `releaseConversation`,之后才释放准入。暂停只请求 abort,执行停止后保存 `paused`;取消优先于暂停。迟到模型输出不得保存叶子、导航或完整结果,成功批次保留,自动 checkpoint 不推进。发布是同步 SQLite 事务,已完成或无变化的运行拒绝取消,不撤销已发布结果;本次无 schema 迁移。
|
||||
|
||||
Preload 暴露类型化 `execution`、`cancel`,取消输入使用 UUID schema 并校验可信 sender。活动 UI 用实时执行状态覆盖持久记录的停止显示;工作回顾挂载期间每两秒读取执行状态,发起前再次查询,Main 仍作最终准入判断。`resume` IPC 直到运行结束才返回,Renderer 发出请求后释放提交锁并独立处理结果,确保继续中的回顾仍可暂停或取消。状态条 X 仅修改本地显示状态。
|
||||
|
||||
### 心跳触发与建议(2026-09-30)
|
||||
|
||||
对应 FR-S12,规则见[心跳职责与介入](./logic-design.md#心跳职责与介入目标设计)。`HeartbeatService` 只依赖 `HeartbeatActions { review, suggest? }`:领取后 `completeHeartbeatTrigger` 立即提交心跳自身运行(租约 5 分钟,仅覆盖交接),再调用 `review`,由 `ipc.ts` 转为 `supervisorService.run({ trigger: 'heartbeat' }, heartbeatRunId)`。审查失败写 `projection_status/error`,结果为 `no_change` 时标记心跳无变化。心跳不再占用模型池,也不读取 `heartbeatReportTimeoutSeconds`;该设置字段保留兼容,界面不显示。下文关于心跳报告阶段、报告租约与报告增量的描述均为历史行为。
|
||||
|
||||
`isIncrementalReview(request)` 统一判断:`trigger: heartbeat` 或未设 `reanalyze` 的手动请求读取并在发布事务中推进 `review_checkpoints`;`reanalyze: true` 从零读取且不写进度。`SupervisionReviewStore.unfinished` 只续跑 `failed/running` 的心跳运行,用户暂停的保持暂停。
|
||||
|
||||
计划 `intervention` 为 `suggest` 时,`deriveSuggestions` 调用 `SupervisionSuggestionStore.candidates(supervisionRunId)`:读取该运行叶子批次的 `openItems`(按原始来源映射到本结果 `supervision_sources`)、`revised` 实体变化、快照内 `contrasts` 关系,以及事件关联中不同原始来源数不少于 3 的 `automatic` 实体。指纹按类型与实体、关系或规范化文本生成;依据键使用原始来源类型、ID 与内容哈希,不用每次结果新生成的来源行 ID。每次最多 20 条。有候选时 `createProductionSuggestionPhraser` 发起一次无工具请求,共享监督模型池与整理超时;模型只返回 `ref/title/detail`,未知 ref 丢弃,缺失措辞回退规则文本。建议失败写 `suggestion_status = 'failed'` 与错误,`supervision:retry-suggestions` 只重跑建议。
|
||||
|
||||
IPC `supervision:suggestions`、`supervision:suggestion-action`、`supervision:retry-suggestions` 校验可信 sender 与 Zod 输入;监督者关闭时列表返回空、操作拒绝。接受未决事项创建暂停任务,接受候选约定确认迁入记忆或新建已确认记忆并确认实体。Schema 50 内容见[实施进度](./progress.md#2026-09-30-心跳职责收敛与监督建议)。
|
||||
|
||||
## 自动增量收集
|
||||
|
||||
|
||||
本节的 100 来源/2,000 码点批次查询现用于原心跳报告及旧服务测试;生产监督使用 schema 47 清单及批次位置,自动 checkpoint 只在完整发布时提交。已有 schema 46 指纹算法和 checkpoint 仍复用,不重置用户已处理位置。
|
||||
|
||||
对应 FR-S4、FR-S10 和 US-S27,行为定义见[自动增量与手动重看](./logic-design.md#自动增量与手动重看)。schema 46 在现有数据库增加 `review_checkpoints`,主键为阶段、规范化 scope 和来源标识,保存版本指纹、已处理位置和来源长度。来源标识区分消息 ID、任务 ID 和消息内知识引用位置,不使用消息时间戳作为身份。
|
||||
|
||||
消息正文写入时由 SQLite trigger 更新轻量 `review_revision`;范围、标题、角色和时间共同参与版本指纹。任务标题、状态、创建和完成时间,以及知识引用内容或定位变化分别使对应来源待处理。工具执行元数据更新不会把未修改的消息正文再次标为新增。删除来源时清理其进度。原始消息不复制到进度表;自动查询先排除已处理版本,再读取待处理片段,不重新载入每个会话的历史正文。
|
||||
|
||||
原心跳增量查询按来源时间和 ID 排序,每轮最多 100 个来源、每个最多 2,000 码点,正文预算 12,000 UTF-16 单元。该报告路径的余段留待后续检查,窗口外来源不自动补读。生产监督另用冻结清单和持久化批次,未读尾部不会因继续时的滚动窗口变化而消失;旧监督分支的 44,000/48,000 字符上限已退出生产接线。
|
||||
|
||||
只有实际送入模型、通过结果校验且成功落库的完整片段推进位置。心跳报告及其进度、监督结果及其进度分别在各自 SQLite 事务提交。监督服务串行执行,后续自动请求在前次保存后重新收集。报告成功而监督失败时,下一次自动检查跳过已成功的报告输入,但重试监督输入;无变化不创建报告、来源或图谱事件。
|
||||
|
||||
已有同 scope 故事线的 scope 表示和实体 ID 保留;比较项目集合时忽略排序。模型接收最近摘要和同 scope 最多 100 个未撤销实体的候选引用、名称、说明,显式选择候选才复用,身份转换按上文执行。不同范围不合并,已有重复实体不自动修复。知识引用继续以独立来源保存 locator;当前记忆和旧摘要不作为新事件。
|
||||
|
||||
迁移在事务中扩展心跳计划及运行表的状态 CHECK,保留列、索引、ID、计划、报告与引用;消息版本列使用默认值,无需遍历或重写旧正文。进度初始为空,首次自动运行从配置窗口开始。报告保留期限覆盖 `no_change` 运行,但不删除来源进度;删除报告不触发重新消费。
|
||||
|
||||
## 活动读取与执行持久化
|
||||
|
||||
对应 FR-S10、US-S26。schema 45 在既有 `supervision_runs` 增加可空 `heartbeat_run_id` 和唯一索引,在 `heartbeat_runs` 增加执行范围、下游投影状态、错误及结束时间字段。迁移保留原记录,不补造旧关联。监督服务通过 store 的 start/fail 接线记录执行,保存结果事务更新同一 run;不增加调度器。心跳在执行前记录冻结范围和投影进度,报告事务仍保持原有含义,下游失败单独保存。手动心跳在生产路径使用实际结束时间,活动等待下游结束后才显示整次执行的结束时间。
|
||||
|
||||
`supervision:activity` 沿用可信 sender 与 Zod 输入校验,接受 `limit`(1 至 100,默认 50)、`offset`(0 至 100000)及可选 `configId`。应用未启用时返回空列表且不访问活动表。未筛选时 SQL 合并心跳及未关联的监督记录;筛选时在 LIMIT/OFFSET 前按 `heartbeat_runs.config_id` 精确匹配,排除独立手动回顾,不依赖计划名称或范围。关联查询使用运行 ID;返回状态、阶段、范围、时间、错误、摘要和 resultId,不调用模型。保留规则和历史缺失见[活动记录](./logic-design.md#活动记录)。
|
||||
|
||||
Preload 暴露类型化 `supervision.activity()`。`overview({ resultId })` 支持按 ID 读取历史回顾,供活动跳转使用。Renderer 只在活动页签和 heartbeat 路由同时激活时串行定时读取,每次有界分页;effect 清理撤销定时器并使旧响应失效。App 关闭应用时卸载页面;读取没有执行副作用。该改动位于桌面 Main/Renderer,未修改 Agent 或远程 Runtime 协议。
|
||||
|
||||
活动进度现在包含既有运行 JSON 中的持久 `phase` 及导航表计数 `navigationNodes`。`SupervisionReviewStages` 将实际阶段与监督运行状态组合呈现;`SupervisionBatchDetails` 按既有 10 批分页结果组织项目、会话和批次,新增批数变化时刷新已打开页。导航和叶子使用不同的严格输出 schema,继续仍复用成功叶子与导航节点。字段语义、旧记录缺失和同步阶段限制由[生产调度合同](./review-scheduling-design.md#已接入的调度与存储)统一定义;未增加任务调度器、数据库表或远程协议。
|
||||
|
||||
## 1. 总体方案
|
||||
|
||||
监督者作为 Main 进程中的一个领域服务,复用现有智能心跳的计划、运行领取、租约、重试和有界模型调用。图谱是监督者整理结果的查询视图,知识库继续拥有资料正文、索引和检索。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
UI["Renderer\n监督者页面 / 图谱 / 侧栏"]
|
||||
IPC["Preload + IPC\n类型化输入输出"]
|
||||
SVC["SupervisorService\n范围、运行、结果、用户确认"]
|
||||
HB["HeartbeatService\n计划、领取、租约、重试"]
|
||||
EVIDENCE["EvidenceCollector\n按范围读取有界证据"]
|
||||
MODEL["BoundedSummarizer\n只读 JSON 输出"]
|
||||
STORE["AssistantDatabase\nSQLite 事务与查询"]
|
||||
KB["KnowledgeService\n本地资料、引用、检索"]
|
||||
CONV["会话 / 笔记 / 任务"]
|
||||
|
||||
UI --> IPC --> SVC
|
||||
HB --> SVC
|
||||
SVC --> EVIDENCE
|
||||
EVIDENCE --> CONV
|
||||
EVIDENCE --> KB
|
||||
SVC --> MODEL
|
||||
SVC --> STORE
|
||||
SVC --> UI
|
||||
SVC -->|确认后补充本地条目| KB
|
||||
```
|
||||
|
||||
职责边界如下:
|
||||
|
||||
| 模块 | 负责 | 不负责 |
|
||||
| --- | --- | --- |
|
||||
| `HeartbeatService` | 计划、到期运行、租约、重试、已有报告兼容 | 图谱实体合并、用户确认、知识库写入编排 |
|
||||
| `SupervisorService` | 触发统一、证据收集、整理结果、图谱查询和修订 | 自己实现调度器、复制来源正文 |
|
||||
| `EvidenceCollector` | 按冻结范围读取会话、笔记、任务、知识库引用 | 越过范围读取全部数据、调用工具 |
|
||||
| `SupervisorSummarizer` | 以有界输入调用文本模型并解析结构化输出 | 执行工具、直接修改知识库 |
|
||||
| `AssistantDatabase` | 事务、外键、分页、状态和来源定位持久化 | 生成模型内容 |
|
||||
| `KnowledgeService` | 资料和本地知识条目的维护、检索及引用 | 管理监督者事件和关系 |
|
||||
| Renderer | 展示和发起用户操作 | 访问 SQLite、决定权限或拼接 SQL |
|
||||
|
||||
## 2. 运行路径
|
||||
|
||||
应用启停遵守[逻辑规则](./logic-design.md#应用启停与执行)。`ApplicationSettingsStore` 对新设置及缺失的 `heartbeatEnabled` 使用 `false`,保留显式布尔值。Main 在心跳 tick 调用 `processDue()` 前、`heartbeatsRunNow` 与 `supervisionRun` IPC 入口、心跳完成后的监督回顾回调中读取已保存设置,只有值为 `true` 才继续。此检查不取消已领取运行,也不改变普通任务调度链路。Renderer 由 App 将同一启用状态传入 `RightAssistantSidebar`,控制监督面板挂载。
|
||||
|
||||
`HeartbeatCenter` 的五个顶层页签直接选择内容,无内部页签。overview 只由 `SupervisorWorkspace` 读取 `supervision.overview` 的统一结果,历史选择和图谱共享 resultId;新请求控件不改变所选结果的 scope/timeRange。`HeartbeatSections` 仅在 plans 输出内容,包含 `HeartbeatSettings`、范围与刷新、运行概览、指标、趋势、自动监督报告、记忆与行动建议及审计;activity 只渲染 `SupervisorActivity`,settings 只渲染两个算法参数表单。运行中的手动请求保留既有结果,活动入口清空计划筛选并聚焦 activity。计划列表放在当前状态下,复用唯一创建按钮及未配置说明。计划表单在原组件内通过 body Portal 显示,复用 `custom-task-dialog` 和 `activateModalFocus` / `trapTabFocus`,没有复制表单或嵌套 form。关闭确认、提交锁定及失败草稿由本地状态管理;保存继续调用 App 原有心跳 CRUD 回调。
|
||||
|
||||
计划“执行记录”将 `activityPlanId` 交给活动查询,并切换、聚焦 activity 页签。更换计划时清空旧列表、重置 offset 并清理旧轮询,保留筛选控件的键盘焦点;该筛选不传入自动监督概览,返回 plans 时仍显示全部计划的统计和心跳审计。报告展开和追加条数保存在同一组件内,切换顶层页签仍保留;图谱继续由 `SupervisorWorkspace` 持有结果选择,活动结果跳转保留精确 resultId。此次仅调整桌面 UI 和只读活动查询,不修改 schema、Agent 或远程 Runtime。
|
||||
|
||||
计划 CRUD 沿用 App → Preload → Main 接线。`heartbeatRecurrenceSchema` 与 `computeNextHeartbeatRun` 仅支持 daily/weekly;本轮不增加分钟间隔合同或调度分支。编辑草稿保存已有 `timezone` 和 `enabled`,新建仅在显式提交时使用本机时区创建启用计划。UI 命名统一为自动监督,内部 ID、翻译键、数据库及 IPC 名称保留。
|
||||
|
||||
手动回顾和心跳回顾必须经过同一个 `SupervisorService.run()`,只在触发来源上区分。这样可以保证两者使用相同的范围校验、输入预算、输出契约、结果状态和来源处理。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant T as 手动/智能心跳
|
||||
participant S as SupervisorService
|
||||
participant D as AssistantDatabase
|
||||
participant C as EvidenceCollector
|
||||
participant M as 文本模型
|
||||
participant R as Renderer
|
||||
|
||||
T->>S: run(scope, timeRange, trigger)
|
||||
S->>D: 创建 run,冻结范围与时间
|
||||
S->>C: collect(frozenScope)
|
||||
C-->>S: 有界证据 + source references
|
||||
S->>M: 结构化整理请求,禁止工具
|
||||
M-->>S: validated result
|
||||
S->>D: 事务写入 result、events、entities、relations、references
|
||||
D-->>S: completed
|
||||
S-->>R: 状态和定位信息
|
||||
```
|
||||
|
||||
运行状态与内容确认状态分开保存:
|
||||
|
||||
- 当前监督运行状态:`running`、`paused`、`cancelled`、`completed`、`failed`、`no_change`。心跳及合并活动的映射见[活动记录](./logic-design.md#活动记录)。
|
||||
- 内容状态:来源事实、自动归纳、待核对、用户确认、用户修订、已移除关系。
|
||||
- 运行成功只表示模型结果已按契约保存,不表示所有实体和关系已经被用户确认。
|
||||
- 失败保留上次完整结果和本次已保存批次;暂停不生成完整结果,继续入口恢复同一 run。
|
||||
|
||||
## 3. 数据模型
|
||||
|
||||
首版使用现有 `AssistantDatabase` 和同一 SQLite 数据库。新增表保持领域对象独立,但不复制会话、笔记或知识库正文。
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
supervision_runs ||--o{ supervision_results : produces
|
||||
supervision_results ||--o{ story_events : contains
|
||||
story_lines ||--o{ story_events : organizes
|
||||
story_lines }o--o{ knowledge_entities : references
|
||||
story_events }o--o{ knowledge_entities : changes
|
||||
knowledge_entities ||--o{ knowledge_relations : source
|
||||
knowledge_entities ||--o{ knowledge_relations : target
|
||||
supervision_results ||--o{ source_references : cites
|
||||
story_events ||--o{ source_references : cites
|
||||
knowledge_entities ||--o{ source_references : cites
|
||||
knowledge_relations ||--o{ source_references : cites
|
||||
```
|
||||
|
||||
建议的最小持久化对象:
|
||||
|
||||
| 对象 | 关键字段 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `supervision_runs` | `id`, `trigger`, `scope_json`, `time_range_json`, `status`, `error`, `started_at`, `completed_at` | 一次手动或心跳整理;保存冻结范围,不从当前配置回推历史 |
|
||||
| `supervision_results` | `id`, `run_id`, `summary`, `change_digest`, `coverage_json`, `graph_snapshot_json` | 工作回顾、监督反馈和图谱共同读取的结果版本 |
|
||||
| `story_lines` | `id`, `scope_json`, `title`, `created_at`, `updated_at` | 用户选择的工作故事线,不等同于 Project |
|
||||
| `story_events` | `id`, `story_line_id`, `result_id`, `occurred_at`, `title`, `description`, `event_type`, `confidence` | 时间轴上的事件;同日聚合只在查询或 UI 层处理 |
|
||||
| `knowledge_entities` | `id`, `canonical_label`, `description`, `state`, `confirmation_state`, `updated_at` | 可被多条故事线引用的持续认识,不与知识库文档一一对应 |
|
||||
| `entity_changes` | `event_id`, `entity_id`, `change_type`, `description`, `confirmation_state` | 表达提出、补充、验证和修订,避免覆盖实体正文 |
|
||||
| `knowledge_relations` | `id`, `from_entity_id`, `to_entity_id`, `relation_type`, `reason`, `confirmation_state` | 关系独立拥有状态和来源;移除关系不删除来源 |
|
||||
| `source_references` | `id`, `owner_type`, `owner_id`, `source_type`, `source_id`, `locator_json`, `availability` | 只保存来源标识和有界定位,不复制正文 |
|
||||
|
||||
外键和索引要求:
|
||||
|
||||
- 新增监督者对象使用 UUID;已有文本实体主键按上文兼容映射保留。实体、来源和关系通过稳定 ID 关联,不能只按名称合并。
|
||||
- `supervision_runs` 按 `created_at`、`scope` 查询;事件按 `story_line_id, occurred_at` 查询。
|
||||
- 关系对 `(from_entity_id, to_entity_id, relation_type)` 建唯一约束,允许同一实体对存在不同关系类型。
|
||||
- 删除故事线只删除其组织关系,不删除共享实体、来源或知识库资料;删除知识库资料后来源标为不可用。
|
||||
- 整理结果、实体变化、关系变化和来源引用在同一 SQLite 事务中提交,提交失败全部回滚。
|
||||
- 只保存整理结果自身的对象内容与有界来源,不建立原会话、记忆或知识库的完整历史快照和独立向量索引。
|
||||
|
||||
叶子模型输出使用严格共享契约,包含 `events`、`entities`、`entityChanges`、`relations`、`summary`、`changeDigest`、`openItems`,事实项保留 `sourceReferenceIds`。Main 校验数量、字符、枚举、ID 所属范围和引用存在性,拒绝未知字段后才保存。导航输出另用[导航合同](./review-scheduling-design.md#已接入的调度与存储),允许省略空事实数组;该规则不适用于叶子。
|
||||
|
||||
## 4. Main、IPC 与 Renderer
|
||||
|
||||
新增能力沿用现有边界:Renderer 只传业务输入,Main 重新校验项目、故事线、时间范围、来源和权限。
|
||||
|
||||
建议的 IPC 分组:
|
||||
|
||||
| 通道 | 用途 |
|
||||
| --- | --- |
|
||||
| `supervision:overview` | 工作回顾、运行状态、未解决事项和最近结果 |
|
||||
| `supervision:run` | 手动开始一次整理,完成后返回包含 runId 的结果 |
|
||||
| `supervision:activity` | 有界读取真实执行和阶段状态 |
|
||||
| `supervision:execution` | 读取唯一执行位置及停止清理状态 |
|
||||
| `supervision:pause` / `supervision:cancel` / `supervision:resume` | 暂停、终止或继续指定回顾;生命周期见上文 |
|
||||
| `supervision:graph` | 按故事线、时间区间和回放时刻分页读取图谱 |
|
||||
| `supervision:object` | 读取事件、实体、关系及来源详情 |
|
||||
| `supervision:confirm` | 确认、修订或移除自动关系和实体变化 |
|
||||
| `supervision:continue-context` | 生成继续讨论前的上下文预览 |
|
||||
| `supervision:knowledge-preview` | 预览新建或补充本地知识条目 |
|
||||
| `supervision:knowledge-commit` | 用户确认后调用知识库服务写入 |
|
||||
|
||||
IPC 返回图谱查询结果时使用分页和有界字段;详情中的来源正文复用现有会话、笔记和知识库读取路径。不要把完整图谱一次性发送给 Renderer,也不要把模型原始输出暴露为可执行内容。
|
||||
|
||||
侧栏反馈通过已有应用状态刷新机制读取结果。首版可以在手动运行完成后主动刷新,心跳完成后发布一个不含私人正文的变更通知,Renderer 再按权限读取摘要;不新增系统通知正文通道。
|
||||
|
||||
## 5. 安全与一致性
|
||||
|
||||
- 监督者模型调用固定为 `ask` 语义,输入来自 `EvidenceCollector` 的有界快照,工具授权始终拒绝。
|
||||
- `Global`、Project、多项目和固定 Conversation/Task/Experiment 目标在 Main 冻结;模型不能生成或改变范围。
|
||||
- 每个来源引用必须属于本次冻结范围,跨范围引用、伪造项目 ID 和不存在的来源 ID 直接使结果失败。
|
||||
- 心跳运行沿用现有 `heartbeat_runs` 的领取、租约和重试机制;监督运行按心跳运行 ID 关联,活动据此合并阶段。监督取消不撤销已保存的心跳报告,合并活动优先显示监督已取消状态。
|
||||
- 同一配置同时触发手动和定时运行时,沿用现有幂等键和运行领取规则;已完成结果不能重复写入。
|
||||
- 用户确认或修订后的实体、关系不被后续整理静默覆盖;新证据以补充或冲突待核对状态写入。
|
||||
- 外部知识库只读。图谱可以引用保存到本地的外部片段和定位信息,不调用远端写入、不承诺打开远端原文。
|
||||
- 来源正文仍由原应用拥有。监督者删除关系、故事线或运行记录时不得删除会话、笔记、任务或知识库资料。
|
||||
|
||||
## 6. 实施顺序
|
||||
|
||||
### 阶段 A:先把现有心跳变成统一运行入口
|
||||
|
||||
- 抽出 `HeartbeatSummarizer` 的有界输入和结构化输出校验,使 `SupervisorService` 可以复用。
|
||||
- 增加手动运行的统一服务入口,但先只生成现有回顾结果,不改变心跳页面行为。
|
||||
- 为运行状态、范围冻结、失败保留旧结果补充 Main 单元测试。
|
||||
|
||||
### 阶段 B:接入证据引用和阶段回顾
|
||||
|
||||
- 实现 `EvidenceCollector`,接入会话、任务、消息已有知识引用和记忆;魔法笔记仍未接入。
|
||||
- 建立 `supervision_runs`、`supervision_results` 和 `supervision_sources`,保存来源 locator,并通过 `KnowledgeService` 解析本地文档/分块。
|
||||
- 在现有智能心跳页面增加监督者工作回顾入口;不先改应用名称和导航 ID。
|
||||
|
||||
### 阶段 C:实现最小故事线图谱
|
||||
|
||||
- 建立事件、实体、实体变化和关系表及查询服务。
|
||||
- 先完成平铺环绕时间轴、节点详情、来源展开、回放和列表模式;圆形视图继续作为后续布局。
|
||||
- 同一实体通过稳定 ID 连接多个事件,禁止按每次事件复制实体。
|
||||
- 用真实整理结果替换 Demo 的模拟数据,并保留 Demo 作为视觉回归 fixture。
|
||||
|
||||
### 阶段 D:接入用户确认和知识库双向定位
|
||||
|
||||
- 增加关系确认、修订、撤销和实体拆分的最小交互。
|
||||
- 从图谱返回本地会话资料及引用位置;知识库文档定位依赖来源中存在文档/分块元数据。
|
||||
- 新建或补充本地条目必须经过预览和确认;外部知识库保持只读。
|
||||
|
||||
### 阶段 E:侧栏反馈与增量整理
|
||||
|
||||
- 将结果摘要投影到现有右侧助手栏,并沿用当前会话生命周期。
|
||||
- 心跳只负责触发,监督者根据新证据决定有无变化和是否反馈。
|
||||
- 在运行去重、用户修订保护和来源失效提示稳定后,再评估更复杂的跨目标固定策略、事件唤醒和自动阶段识别。
|
||||
|
||||
## 7. 验证策略
|
||||
|
||||
首版每层验证一条可追踪链路:
|
||||
|
||||
1. `EvidenceCollector` 测试范围裁剪、字符预算、来源定位和不可用来源。
|
||||
2. `SupervisorService` 测试模型输出解析、引用完整性、重复运行、取消、失败和部分结果。
|
||||
3. `AssistantDatabase` 测试迁移、事务回滚、外键、分页、关系移除不删来源。
|
||||
4. IPC 测试不可信输入、越权 Project/Source ID、确认状态和知识库写入前置预览。
|
||||
5. Renderer 测试从反馈进入图谱、点击实体高亮多个时间点、回放不显示未来结果,以及窄屏列表访问。
|
||||
6. 使用一组固定脱敏 fixture 验证“监督反馈 → 实体 → 多个事件 → 来源 → 继续讨论”的完整路径。
|
||||
|
||||
首个生产验收不要求模型自动发现所有关系。必须先证明来源可追溯、失败可解释、用户修订不会丢失,以及图谱与工作回顾读取同一结果。
|
||||
|
||||
## 8. 暂不实现的内容
|
||||
|
||||
- 独立的图数据库、向量图谱或新的知识库正文存储。
|
||||
- 让监督者拥有通用 Agent 工具执行能力。
|
||||
- 自动把所有聊天消息变成事件或节点。
|
||||
- 无依据的实体自动合并、旧观点自动失效和静默覆盖用户确认。
|
||||
- 应用市场建设作为监督者开发前置依赖。
|
||||
- 在没有增量和去重证据前实现事件触发、自动阶段识别和全量后台重算。
|
||||
|
||||
## 当前剩余差距
|
||||
|
||||
- 实体与关系 revoke 保存状态;界面没有 undo 或恢复入口。
|
||||
- 跨 run identity 依赖同 scope 候选 UUID 的显式引用,不自动合并历史重复实体;模型漏选候选时会创建新身份。
|
||||
- 结果图谱使用真实事件实体连线,侧栏已按固定目标过滤并支持结果直达图谱;逐事件状态回放和 Experiment 尚未实现。
|
||||
- 用户暂停已接到排队/在途 signal,并保留已保存批次;源版本变更后的局部重算、完整模型配置冻结及其他限制见分块调度第 0 节。
|
||||
- 监督知识实体与知识库正文条目仍是不同对象,知识正文只能通过现有条目定位和预览接口处理。
|
||||
@@ -0,0 +1,120 @@
|
||||
# 监督者 UI 设计
|
||||
|
||||
日期:2026-09-16。状态:目标设计,遵循根目录 [UI-DESIGN.md](../../../UI-DESIGN.md)。需求对应[监督者应用产品设计](./supervisor-prd.md) FR-S1 至 FR-S9。
|
||||
|
||||
## 应用入口
|
||||
|
||||
应用中心中的智能心跳入口升级为“监督者”,沿用既有启用、常驻与排序能力。未来应用市场使用同一应用身份。应用已启用时,停用单条自动心跳计划仍可手动回顾;关闭应用后,已打开页面显示关闭状态并禁用工作区操作。默认值及运行边界见[应用启停与执行](./logic-design.md#应用启停与执行)。
|
||||
|
||||
当前应用页的顶层导航只使用一套共享 PageTabs:“工作回顾 / 故事线图谱 / 智能心跳 / 活动记录 / 设置”(英文活动标签为 Activity records)。智能心跳独立管理心跳计划;设置只显示模型超时与并发、回顾算法,不含计划、报告、审计或内部页签,也不显示整页全局徽标或独立刷新行。计划范围仍在表单和每条计划内显示。整个页面只有一个一级标题;监督者 bridge 不可用时显示明确错误,设置仍可访问,不回退到旧心跳概览。共享页签保留方向键、Home/End、游标焦点和单一激活面板,窄窗口横向滚动。故事线图谱面板内另有分类页签,见[故事线图谱](#故事线图谱)。
|
||||
|
||||
工作回顾只读取统一监督结果。顶部紧凑排列范围、时间、发起与刷新控件,不重复显示“新回顾”标题和来源说明,不新增 Modal。历史结果选择与图谱入口位于正文之前,即使只有一条结果也显示选择器。新回顾工具栏、状态、历史选择和结果面板铺满监督者页面壳层的可用内容宽度,与页签左右对齐,不另设阅读宽度上限;结果内部按面板内边距排版,不再单独居中窄正文。正文使用“监督结果”标题,常显生成日期、真实范围和覆盖区间,历史结果不标为最新。工作总结按原有空行分段并保留换行;本次变化和未解决事项直接使用已有字段,不补写结论或截断正文。390px 下新请求字段与操作各占两列,结果元数据可换行。
|
||||
|
||||
新回顾运行中保留已显示结果及其日期、范围,范围、时间、发起和刷新控件保持可用;重复点击发起只重新显示当前运行提示,不排入新回顾。状态条提供活动记录入口,清除计划筛选并聚焦活动页签;右侧 X 只关闭提示,不暂停或取消执行,同一次运行的轮询不重新打开已关闭提示。范围、时间和活动计划筛选保留可访问名称,不再重复显示字段标题。工作回顾不读取历史心跳报告,也不显示自动报告空态、记忆与行动建议或第二行全局范围与刷新。无监督结果时,空态独占工具栏下方整行,标题与手动回顾入口水平居中;自动报告是否为空不影响此判断。运行中和暂停后使用独立状态条,提示当前状态及活动记录入口。
|
||||
|
||||
## 活动
|
||||
|
||||
文案遵循根目录 [监督者文案规则](../../../UI-DESIGN.md#134-智能心跳):不把内部路线图、交付状态或实现缺项展示给用户。活动页不常显重复说明;真实错误、失败和暂停状态仍展示,并保留恢复操作。事实详情提示展开批次并对照原文核对模型内容,不承诺完整语义识别;图谱说明实体状态以所选回顾结果为准。
|
||||
|
||||
对应 FR-S10、US-S26。活动按开始时间倒序,每页 50 条,提供较新/更早记录和刷新。列表保留触发方式、执行范围、开始/结束时间、实际状态、阶段状态、错误及已保存摘要。没有结果时不显示结果按钮;有监督结果时可分别进入该结果的工作回顾和故事线图谱,焦点移到目标页签。超过最近 20 条的结果仍按 ID 读取,不能回退最新结果。
|
||||
|
||||
活动页只展示统一监督活动、进度、详情及结果入口,不在列表下方重复显示心跳概览或心跳审计。计划“执行记录”打开此页并按精确 ID 筛选,筛选器显示所选计划并提供清除;监督查询在分页前过滤。同名计划仍按 ID 区分;没有匹配记录时显示筛选无结果,不回退全部数据。监督建议和历史心跳报告位于智能心跳。
|
||||
|
||||
“智能心跳”页依次展示范围与“刷新智能心跳”、“心跳计划”卡片、监督建议,以及只读的历史心跳报告。心跳计划卡片内不再嵌套第二层边框:标题行右侧显示运行状态点,下方一行说明文字与“创建心跳计划”按钮同行,其后直接是计划列表。说明文字按状态三选一:无计划显示“尚未配置心跳计划,目前仅支持手动回顾”,全部暂停显示暂停说明,否则显示频率帮助;不叠加第二个空态或创建入口,点击按钮打开现有 Modal。
|
||||
|
||||
加载、空记录和读取失败分开显示;读取失败保留已显示记录并提供就地重试。活动页面激活时,同轮读取活动和实时执行状态;有活动执行(包括停止清理)或运行中记录则在上次读取结束 2 秒后刷新,否则 10 秒后刷新,不重叠轮询。切换顶层页签、离开监督者或关闭应用后停止活动定时器,迟到响应不更新界面。错误后等待用户重试,不持续重发失败查询。
|
||||
|
||||
活动列表与工具栏铺满监督者页面壳层的可用内容宽度,与页签左右对齐,不另设仪表盘宽度上限。范围、计划筛选、清除筛选和刷新紧邻排列,空间不足时换行。列表使用共享表面、边框、状态颜色和按钮。每条运行首先显示类型、状态、开始时间和范围;恢复与事实操作紧随其后,再展示“收集来源 / 整理批次 / 合并摘要 / 发布回顾”。阶段使用编号圆点和连接线,宽窗口四项等宽,窄于 720px 时两列;当前及已完成阶段突出显示,不为每个阶段铺设独立色块卡片。阶段读取已保存位置和真实运行状态,剩余来源为零不表示发布成功。旧运行没有阶段字段时显示“未记录”,不猜测失败位置;已提交来源清单可确认收集结束。无变化运行不显示新结果跳转,后续无需执行的阶段单独标注。
|
||||
|
||||
阶段下突出显示已保存批数和剩余来源,运行中另显示合计的排队或在途数,不拆分未经接口提供的数量。已读字符数和已保存导航合并数放入默认折叠的“运行详情与配置”。字符进度按码点统计,界面不展示 Unicode 术语。恢复操作先于详情:运行中可暂停,失败或暂停后沿用同一运行继续;已记录合并、发布阶段时分别显示“继续合并摘要”“重试发布”。成功叶子保留。来源失效时显示新建回顾提示,不提供无效继续。原始错误默认折叠,展开后的完整文本最多占 240px 高度,可滚动、选择复制和键盘聚焦;错误与恢复操作位于批次树之前。结束时间、覆盖区间、报告状态和已有摘要收进“运行详情与配置”,已保存配置在其中再次折叠。
|
||||
|
||||
“已保留事实与来源”展开后才查询既有批次接口,每页 10 批,按本页持久记录组成项目、会话、批次层级。项目使用可用名称,会话优先使用保存来源的标题;只有已保存批次标记“已保存”,不创建未落库的待执行、在途或失败叶子,也不把本页数量当作项目总量。批次摘要收起时最多两行,展开后保留完整摘要、事件、实体、变化、关系及原文片段。来源显示消息 ID、码点范围及版本;长原文局部滚动。原生 details/summary 保留键盘展开和可见焦点,轮询不主动展开用户收起的会话。加载、空批次和读取失败分开显示,失败可重试;换页失败不显示上一页批次,切换 run 后旧响应失效。暂停状态为“未完成,可继续”,手动回顾主动暂停后在工作回顾页提示去活动继续,不显示完成通知。
|
||||
|
||||
活动页运行中同时提供“暂停回顾”和“取消回顾”,继续后的运行也可操作。停止期间禁用两项操作,并在运行标题和当前阶段显示“正在暂停”或“正在取消”;即使持久记录已经为已取消,只要执行尚未停止就继续显示“正在取消”。重新进入活动页通过实时执行状态恢复该提示。停止后显示“已取消”,保留事实详情,不显示继续按钮或失败恢复提示;任一回顾执行或停止清理期间,其他记录的继续按钮禁用。
|
||||
|
||||
## 智能心跳设置
|
||||
|
||||
没有自动运行、报告或关联建议时,只展示计划配置和已有计划,不显示零值统计、空趋势、建议、报告或审计区。手动回顾不计入心跳统计;产生自动运行或报告后展示对应结果区。
|
||||
|
||||
2026-09-23 当前已实现“模型超时与并发”表单:心跳报告与监督者整理分别设置,均为 30 至 600 的整数秒,默认 240;监督模型并发数为 1 至 4 的整数,默认 1。保存真实字段 `heartbeatReportTimeoutSeconds`、`supervisorOrganizeTimeoutSeconds`、`supervisorModelConcurrency`;无效输入不能提交,设置加载/保存中或锁定时禁用,失败保留草稿并提供重试。超时以阶段读取设置的时点为准,不改变已冻结的值;并发数影响后续派发,降低上限不打断在途请求。
|
||||
|
||||
并发说明明确报告和监督整理共用上限,排队不计入模型超时;普通聊天不受此设置限制,但仍与监督竞争上游额度。表单不宣称提供商全局并发、RPM、TPM 或 Retry-After 限流已实现。
|
||||
|
||||
模型超时不包含数据收集与保存,直连模型另有独立 600 秒传输超时。“回顾算法”表单提供“每次读取来源条数”“每批文本量”“每批消息数”和“单次响应容量”,显示默认值及合法范围,使用“保存回顾算法”独立提交。文本量内部仍按 UTF-16 单元计算,界面不要求用户理解编码单位。保存经 App 的现有设置回调进入共享设置存储,加载/保存/锁定时禁用,失败保留草稿。取值以[当前设置合同](./review-scheduling-design.md#当前设置合同)为准。帮助说明回顾持续处理至完成,每批保存进度,可从活动记录主动暂停并继续;已保存配置用“同时处理任务数”解释并发,说明实际并发还受当前监督设置限制。
|
||||
|
||||
对应 US-S22 至 US-S24。“智能心跳”先显示心跳计划列表,点击创建或编辑才打开 Modal;复用原表单,不保留页面内副本。表单展示名称、项目范围、每日或每周频率、指定时间、回顾窗口(1 至 720 小时)和历史保留(1 至 365 天);每周显示星期选择。时区常显,新建使用本机时区,编辑保留计划原时区。列表上方帮助说明选择每日或每周时间、保存并启用后自动运行。
|
||||
|
||||
无计划时显示“尚未配置心跳计划,目前仅支持手动回顾”;全部暂停时另行说明。加载或首次读取失败不显示未配置状态。打开页面、Modal 或填写字段均不创建计划,只有点击“保存并启用计划”才提交创建。已有计划显示范围、周期、时区、回顾窗口、保留期限和启停状态,保留编辑、暂停、恢复、立即运行、执行记录和确认删除;暂停时不显示待执行的下次时间,编辑保存不改变暂停状态。
|
||||
|
||||
Modal 按[共享表单 Modal 规则](../../../UI-DESIGN.md#615-表单-modal)复用 header、body、footer:标题左侧、带本地化名称和工具提示的 X 右侧;底部取消次按钮、保存主按钮整体右对齐。标题和操作区保持可见,仅表单内容滚动。表单使用独立字段容器,不嵌套设置表单;窄窗口表单单列,保留共享窗口安全边距。
|
||||
|
||||
沿用整窗 Portal、共享遮罩、背景 inert、焦点循环与关闭后焦点恢复;初始焦点进入名称。X、Escape、取消或点击遮罩关闭时,有未保存修改先确认;确认初始焦点为继续编辑,Escape 或 X 只返回编辑,点击遮罩不放弃修改。保存期间禁止关闭和重复提交,失败在表单内显示并保留草稿。
|
||||
|
||||
界面及可访问名称统一使用“智能心跳 / 心跳计划 / 回顾”;心跳负责定时触发回顾和按计划介入,回顾本身由监督者完成,保留内部 heartbeat 标识及已有计划名称、报告正文。表单继续使用共享控件和容器响应式布局,建议及审计无需先进入即可配置计划。
|
||||
|
||||
### 算法配置设置目标
|
||||
|
||||
以下高级分组为目标交互;已接通的四项算法控件以上节为准。其余参数引用[设置配置清单](./review-scheduling-design.md#4-设置配置清单),不在 UI 文档另设数值,不增加一级页面。
|
||||
|
||||
普通配置常显消息每页条数、监督并发、报告/整理模型超时和单次响应容量。高级配置按“来源与分块”“上下文与输出”“汇总与内存”“超时与续跑”“上游额度”分组。消息页字段的常显帮助为:“每次从数据库读取的消息数;会持续读完所选范围,不限制回顾总消息数,也不决定每次发给模型的消息数。”模型批次消息数、片段数和新增字符数分别标注,避免统称“批量大小”。
|
||||
|
||||
字段显示单位、已确定的默认值和合法范围;补充帮助说明调大后的内存、等待或模型用量影响。允许继承的节点超时显示“继承监督整理值”及解析后的秒数;依赖模型容量的输入/输出预算显示所用模型、容量及 token 是否为估算。跨字段校验覆盖输入与输出预留之和、扇入可容纳性、退避起始与上限;错误附在字段并关联可访问说明,提交时聚焦首个错误,Main 仍作权威校验。
|
||||
|
||||
规划中的“保存配置”持久化后只用于新建运行,常显“已开始和待继续的回顾沿用启动时配置”。“恢复本组默认值”只修改该组草稿,经保存才生效,不开始回顾、不修改已有计划;取消编辑恢复已保存值。运行详情展示本次实际配置,继续操作沿用它;想采用新算法配置时使用“重新回顾”,说明会重新计算。当前已实现字段仍按上文的阶段读取和共享池生效方式工作,整次 run 冻结接线完成后再统一调整提示。
|
||||
|
||||
初次加载、读取失败、编辑、保存中、保存失败和已保存分别呈现;读取失败不填假默认值,保存失败保留草稿并允许重试。保存成功使用应用通知,字段错误就地显示,不重复通知。窄窗口按单列排列,折叠标题可键盘操作,单位和关键生效说明不藏进悬浮帮助。
|
||||
|
||||
只在真实 Main/存储/运行路径接通后显示相应可编辑字段;尚未实现的配置留在设计文档,不放置永久禁用的假控件。已接通但因加载、保存或依赖缺失而临时不可编辑时说明实际原因。上游额度明确显示适用连接/模型和是否包含普通聊天;未实现跨路径共享时不得标为提供商全局限制。覆盖完整性、来源校验及事实保留不提供开关。
|
||||
|
||||
## 工作回顾
|
||||
|
||||
2026-09-30 已实现(规则见[心跳职责与介入](./logic-design.md#心跳职责与介入目标设计)):主按钮“回顾”执行增量整理,文案不再称“重新分析”;旁边“更多”菜单提供“重新整理…”,确认对话说明将重新分析所选区间并可能产生模型费用。计划 Modal 增加“介入方式”单选:“生成建议”“仅更新记忆”;主动介入上线前不显示。最近建议替代,每条建议显示依据、打开来源及转任务/确认/忽略。“最近建议”替代,每条建议显示依据、打开来源及转任务/确认/忽略。
|
||||
|
||||
当前页面提供全局或单项目范围,以及 1、7、30 天时间区间选择。没有自动计划也能手动回顾;成功结果显示自身冻结的范围、时间和未解决事项,可选择历史结果或进入图谱。来源筛选由本次回顾范围和时间限制;图谱详情只列出所选事件的关联来源,实体或关系列出相关事件的来源。独立的来源类型选择器尚未实现。
|
||||
|
||||
选择历史结果后,图谱读取该结果的成员和内容,标题显示其回顾区间和生成时间。切换时清空旧图谱和详情并显示加载状态,迟到请求不得替换新选择。确认、修订和移除反馈更新当前所选结果,后续自动回顾不改写旧结果。
|
||||
|
||||
手动“回顾当前进展”新建回顾,活动页“继续”恢复原 run;运行中再次发起按上文显示提示,停止清理结束后才能开始新回顾。失败保留上次结果、本次范围和成功批次;已取消回顾不能继续。手动操作不推进自动位置,删除报告也不重置自动位置。
|
||||
|
||||
## 会话侧栏
|
||||
|
||||
监督反馈标题右侧使用共享紧凑图标按钮固定/取消固定目标与刷新回顾;固定状态通过
|
||||
`aria-pressed` 和按钮高亮表达。目标正文显示会话或任务名称,名称缺失时使用“未命名会话”
|
||||
或“未命名任务”,原始 ID 仅作为悬停补充。无目标时禁用固定和刷新,读取中禁用刷新;
|
||||
窄侧栏中的长标题换行,按钮保留可见键盘焦点。
|
||||
|
||||
仅在应用已启用时挂载监督面板及固定目标控件;关闭或设置尚未加载时不挂载,也不发起面板查询。任务列表仍按原规则显示。
|
||||
|
||||
任务中心从上到下依次显示项目概览(含当前会话统计)、监督反馈、项目任务。
|
||||
|
||||
右侧通用助手工作栏中的监督卡片展示“监督反馈”,沿用现有[工作栏设计](../assistant-workbar/prd.md)的布局和执行边界。当前支持 Conversation/Task:有选中任务时跟随任务,否则跟随会话;“固定监督目标”保存当前目标,切换聊天后保持,取消固定恢复跟随。卡片显示目标、结果真实范围、回顾区间和摘要,可刷新并查看匹配来源;无目标或无匹配结果显示空态。Experiment 尚未接入。
|
||||
|
||||
有匹配结果时提供“在图谱中查看”,打开现有监督者页面的图谱页签并读取该结果;无匹配结果不显示入口。按钮保留原生键盘操作,进入后焦点落在图谱页签,继续使用共享 PageTabs 的方向键导航。固定目标不因跳转改变;再次点击相同或不同结果均重新定位,包括页面缓存后再次进入。结果不可用时显示错误并重试原 ID,不展示默认最新结果。
|
||||
|
||||
“继续讨论”直接准备上下文,不要求先查看来源。预览顶部单独显示目标会话,下方“发送内容”文本域直接填入实际回顾与来源正文,用户可编辑或删除其中任意内容。发送仅提交当前文本域内容,不再暗中拼接上下文,也不提供额外的展开区。只有点击“发送”才提交,取消不发送,失败保留整份草稿。固定任务使用其关联会话,无法确定发送会话时禁用。上下文仅包含所选来源关联的事件、实体、关系及原文快照,不带入整份全局回顾摘要;没有关联事件时只使用来源。切换目标使旧来源请求和预览失效。查看来源不自动跳转,用户点击“打开会话”才导航。侧栏不再显示实体写入或知识库选择器,现有知识库数据不受影响。
|
||||
|
||||
侧栏与图谱来源详情共用编辑区样式:目标区底部使用弱分隔线,区块间距为 `--space-4`,标签与文本域间距为 `--space-2`。文本域默认十行,可纵向调整大小;长内容在文本域内滚动。底部复用 `custom-task-dialog__actions`,取消在前、发送在后,整体右对齐。
|
||||
|
||||
## 故事线图谱
|
||||
|
||||
关联来源详情属于会话时,显示“打开会话”按钮,使用来源保存的会话 ID 跳转并切换到所属项目;不发送消息或启动回顾。另提供“继续讨论”,使用上述可编辑正文预览,点击发送后提交到来源会话并打开它。详情继续展示整理时保存的原文快照。会话已删除或读取失败时,沿用应用会话导航的提示;其他来源类型不显示此按钮。
|
||||
|
||||
左侧使用共享 PageTabs 切换“事件 / 实体 / 关系”,显示各类数量,一次只展示一类列表,无记录时显示本次回顾的分类空态。页签固定在顶部,列表占满剩余高度并独立滚动。切换页签不清除当前图谱选择;从画布、事件浏览或详情选择对象时同步切到对应页签。支持方向键、Home/End 和面板键盘聚焦。
|
||||
|
||||
使用 `supervisor` 页面壳层,最大内容宽度为 `1440px`。当前生产图谱左侧为紧凑的事件、实体和关系列表,中间为 SVG 画布,右侧为详情与来源。桌面列宽为 `216px minmax(0, 1fr) 280px`,画布获得主要空间。外圈事件按发生时间排序后等距逆时针排列,不表示实际时间间隔;起止之间保留缺口和方向箭头。内部显示真实实体名称,实线仅连接实际事件与实体,虚线仅连接实际实体关系。
|
||||
|
||||
高度:容器宽度不小于 861px 时,图谱这一行的高度等于窗口可见高度减去图谱顶部位置,并在底部保留约 24px 边距(不小于页面底部内边距),最低 660px(再矮平铺图的文字会小于 11px,这时改为页面滚动)。三栏时整页不再出现纵向滚动;左侧列表和右侧详情在这一行内独立滚动,滚动只影响自身,不带动页面。中间画布列不出现滚动条:标题栏和底部单行“事件浏览”栏之外的高度全部给图,平铺与时间螺旋一致。画布标题为“事件与知识”,下方一行小字依次显示图谱范围、时间区间、生成时间和事件/实体数,刷新为标题栏右侧图标按钮;图谱上方不再单独占一行显示范围与时间。没有图谱数据时,刷新按钮保留在页签顶部。图例默认收起,从标题栏“图例”按钮打开,浮在画布右上角,可用 X 关闭;可见事件与实体数量常驻画布左下角。两栏(861~1190px)时第一行按同样方式填满,详情换到下一行,保持自然高度并随页面滚动。单栏保持原有页面滚动和列表 320px 上限。窗口大小或图谱上方内容(提示、工具栏)变化时重新计算。
|
||||
|
||||
实体及其影响连线复用现有 `--graph-node-*-border` 令牌;颜色用于辅助辨认实体,不代表故事分类或确认状态。选中项加粗并突出连线,其余节点降低图形强调度但保留可读文字。详情分为名称与说明、图谱关联、确认操作和来源,来源正文按需读取。图例常显,读图说明可展开。
|
||||
|
||||
画布每批最多显示 8 个事件、6 个实体;同时间事件分开排布。列表保留全部返回记录和完整名称,选择实体会定位到真实关联事件所在批次,选择关系会优先显示两端实体。滑块明确命名“事件浏览”,只切换当前事件,不还原实体历史状态,也不宣称完整历史回放。当前接口返回单条故事线,不复制 Demo 中的三条模拟故事。后续视图规划见[图谱设计](../story-graph/prd.md)。
|
||||
|
||||
按实际内容容器宽度响应:不超过 `1190px` 时变为 `184px` 列表加画布,详情移到下方;不超过 `860px` 时变为单列,画布优先,列表限制高度并独立滚动。SVG 最小宽度为 `660px`,窄屏只在画布内部横向滚动,页面本身不横向溢出。范围、时间和操作可换行。节点、关系与事件均可从列表选择,画布节点支持 Enter、Space 和可见焦点。加载、无数据、读取失败和服务不可用各自呈现,错误提供可行的恢复说明或重试入口。
|
||||
|
||||
隔离视觉验证使用 `tests/support/supervisor-layout-regression.tsx` 渲染生产 React 组件;所有输入标为模拟 fixture,不读取用户数据库或调用模型。`tests/supervisor-layout.electron.test.ts` 启动 Vite 与 Electron,通过 `executeJavaScript` 测量布局、`capturePage` 截图,覆盖 1440、1024、390px、浅深主题、长名称、密集事件和异常状态。可通过 `GOODBUDDY_SUPERVISOR_ARTIFACTS` 保留截图;交互预览入口为 `node tests/support/supervisor-preview.mjs`。
|
||||
|
||||
设置 `GOODBUDDY_SUPERVISOR_CONTENT_LAYOUT=1` 验证工作回顾、空态、活动、待处理及失败场景,在 2000、1440、1024、390px 和浅深主题下共 40 个组合,检查全宽、空态居中及位于工具栏下方、阶段连接线和长内容溢出。
|
||||
|
||||
设置 `GOODBUDDY_SUPERVISOR_PLAN_MODAL=1` 可只验证五页签 fixture 中的计划 Modal:创建和编辑各覆盖浅深主题、1440×1100、390×1100、390×480,检查关闭按钮、底部按钮对齐、正文滚动、鼠标命中和原生键盘操作。截图及测量写入同一产物目录。
|
||||
|
||||
设置 `GOODBUDDY_SUPERVISOR_RECAP=1` 可验证本次工作回顾分区:1440×1100 和 390×1100 下覆盖监督结果与自动报告均有内容、仅有监督结果、两者均空,另检查历史结果运行中仍可阅读以及活动入口。该分支检查每页一处刷新、自动报告和建议的归属、正文段落与宽度,并截取自动页建议、展开报告和审计区块。
|
||||
@@ -0,0 +1,647 @@
|
||||
# 监督者 User Stories
|
||||
|
||||
日期:2026-09-22。本文定义监督者从应用入口到故事线图谱的用户场景、界面行为和验收条件。
|
||||
|
||||
当前代码已经交付智能心跳页面和运行链路;监督者工作回顾、故事线图谱、会话侧栏反馈和知识库双向定位仍是目标设计。因此每条故事标注了当前状态:
|
||||
|
||||
- **已实现**:当前生产界面或 Main/Preload 链路已有可验证行为。
|
||||
- **待实现**:产品目标,不能用现有智能心跳页面或 Story Graph Demo 代替验收。
|
||||
- **沿用**:已有能力在监督者统一入口中必须继续保留。
|
||||
|
||||
## 用户角色
|
||||
|
||||
| 角色 | 目标 |
|
||||
| --- | --- |
|
||||
| 工作使用者 | 回到工作现场,理解进展、决定和未解决事项,并继续工作 |
|
||||
| 知识整理者 | 核对来源,修正自动归纳,把可靠认识补充到知识库 |
|
||||
| 监督者配置者 | 决定观察范围、运行时间和反馈方式 |
|
||||
| 受限访问者 | 只能看到已授权范围内的会话、任务、笔记和知识库引用 |
|
||||
|
||||
## 入口与范围
|
||||
|
||||
### US-S01 启用后知道监督者观察什么
|
||||
|
||||
**状态:待实现。**
|
||||
|
||||
作为工作使用者,我希望在第一次使用监督者时明确它将观察哪些内容,避免启用应用后默认读取全部工作。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 用户第一次打开监督者 | 选择 Global 或一个、多个 Project,并选择可用来源和时间区间 | 页面在生成前显示完整范围、来源和时间区间 |
|
||||
| 用户没有选择来源 | 点击生成回顾 | 不开始整理,显示选择来源的入口和原因 |
|
||||
| 当前项目已打开 | 打开范围选择 | 当前项目可以作为建议值,但不能覆盖已保存范围 |
|
||||
| 用户切换当前项目 | 返回监督者 | 已保存的范围不改变 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 首次状态显示“选择关注范围”,不显示看似已有的空回顾。
|
||||
- 范围摘要持续显示在页面标题或范围徽标中。
|
||||
- Global、指定项目和来源选择使用持久标签;不能只用颜色表示范围。
|
||||
- 生成按钮在范围未完成时禁用,并把错误关联到对应字段。
|
||||
|
||||
### US-S14 从应用中心进入唯一的监督者
|
||||
|
||||
**状态:应用入口与启停已接入,完整验证范围见[实施进度](./progress.md)。**
|
||||
|
||||
作为用户,我希望监督者只有一个稳定入口,不需要在智能心跳、任务中心和设置中心之间寻找重复配置。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 用户打开应用中心 | 启用应用后选择智能心跳入口 | 进入监督者统一页面,未来显示为监督者名称 |
|
||||
| 用户已有智能心跳计划 | 应用入口升级 | 计划、运行记录、报告、建议和启用偏好仍可访问 |
|
||||
| 用户打开任务中心或设置中心 | 查找心跳配置 | 不出现第二套计划 CRUD;需要时只提供进入监督者的链接 |
|
||||
| 新安装或设置缺失应用启用值 | 启动应用 | 监督者关闭;已有显式开启或关闭选择保留 |
|
||||
| 应用已启用但没有已保存且启用的计划 | 等待自动调度 | 不自动创建计划或运行回顾 |
|
||||
| 已有心跳运行被领取并正在执行 | 关闭应用 | 该运行可完成并保存;不再接受新运行或追加自动监督回顾,侧栏不挂载或查询监督面板;普通任务定时调度不变 |
|
||||
|
||||
以上启停验收遵守[应用启停与执行](./logic-design.md#应用启停与执行)。
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 应用身份、排序和常驻行为保持一致,不能生成第二个平级应用。
|
||||
- 迁移期间如果仍显示“智能心跳”,页面应明确说明它是监督者的自动唤醒能力。
|
||||
- 旧数据为空时进入有意义的首次配置状态,而不是报错。
|
||||
|
||||
### US-S21 选择一个或多个项目作为观察范围
|
||||
|
||||
**状态:已实现。**
|
||||
|
||||
作为监督者配置者,我希望心跳计划明确属于 Global 或选定项目。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 用户编辑心跳计划 | 选择 Global | 计划显示 Global,不能同时选择项目 |
|
||||
| 用户选择指定项目 | 勾选一个或多个活动项目并保存 | 计划保存为一份多项目计划,不为每个项目复制 Run 或报告 |
|
||||
| 用户没有勾选项目 | 尝试保存指定项目计划 | 保存按钮不可用或显示字段错误 |
|
||||
| 项目已归档或删除 | 查看或保存旧计划 | 显示不可用项目;不能默默扩大或改写范围 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 使用分段控件选择 Global/指定项目,项目使用 Checkbox。
|
||||
- 计划卡片显示 Global 或完整项目摘要;项目过多时显示数量并保留可访问的完整名称。
|
||||
- 当前项目切换不会重新解释计划卡片的范围。
|
||||
|
||||
### US-S22 配置自动监督
|
||||
|
||||
**状态:已实现。**
|
||||
|
||||
作为监督者配置者,我希望配置回顾时间、周期、回顾窗口和保留期限。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 用户打开自动监督 | 查看计划列表并点击创建或编辑 | Modal 显示名称、项目范围、每日/每周频率、指定时间、时区、回顾窗口和保留期限;页面说明选择每日或每周时间、保存并启用后自动运行 |
|
||||
| 应用已启用但没有计划 | 打开自动监督或填写 Modal 字段 | 显示“尚未配置自动监督,目前仅支持手动回顾”,不自动保存、创建计划或发起运行 |
|
||||
| 用户修改计划草稿 | 保存失败或关闭 Modal | 失败保留输入并可重试;Escape、取消和遮罩关闭均先确认放弃,继续编辑保留草稿 |
|
||||
| 已有计划处于暂停状态 | 编辑并保存 | 更新计划字段,保持暂停;只有显式恢复才启用 |
|
||||
| 两条计划名称相同 | 点击其中一条的执行记录 | 打开统一活动记录并按所选计划的精确 ID 筛选;可清除筛选,不影响自动监督页的概览和审计 |
|
||||
| 用户完成配置 | 点击保存并启用计划 | 保存明确选择的频率、时间、范围和期限,参与既有调度 |
|
||||
| 用户编辑已有计划 | 修改字段并保存 | 计划卡片显示新配置、下次运行和上次状态 |
|
||||
| 已有计划暂停且时区与本机不同 | 编辑并保存 | 保留已暂停状态及原时区,不隐式恢复或改变执行时间 |
|
||||
| 用户开始编辑 | 点击取消编辑 | 草稿丢弃,恢复编辑前的计划值 |
|
||||
| 保存失败 | 提交表单 | 草稿保留,错误就地显示,用户可以修正后重试 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 表单标签始终可见,不能用 placeholder 代替。
|
||||
- 每次操作期间禁用重复提交和相互冲突的按钮。
|
||||
- 周期选择为 weekly 时才显示星期字段。
|
||||
- 新建使用本机时区,编辑保留原计划时区,表单与计划摘要常显时区;范围和状态不能只依赖颜色。
|
||||
|
||||
### US-S23 暂停、恢复或立即运行计划
|
||||
|
||||
**状态:已实现。**
|
||||
|
||||
作为用户,我希望临时暂停自动回顾,也能随时手动运行一次。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 计划正在运行 | 点击暂停 | 计划停止下一次自动运行,已有历史和结果保留 |
|
||||
| 计划已暂停 | 点击恢复 | 计划恢复并显示下一次运行时间 |
|
||||
| 计划可运行 | 点击立即运行 | 创建一条标记为手动触发的 Run,并在完成后刷新结果 |
|
||||
| Run 正在执行 | 再次点击运行或暂停 | 禁止重复操作,按钮显示进行中状态 |
|
||||
|
||||
## 工作回顾和监督反馈
|
||||
|
||||
### US-S02 隔几天回来能继续上次工作
|
||||
|
||||
**状态:待实现;当前智能心跳的运行概览和最新报告是可复用基础。**
|
||||
|
||||
作为工作使用者,我希望打开监督者就知道上次形成了什么决定、哪些内容改变、还有什么没有解决。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 已有成功的阶段回顾 | 打开工作回顾 | 默认显示最近成功结果、覆盖范围和生成时间 |
|
||||
| 回顾包含决定、变化和未解决事项 | 点击其中一项 | 展开依据,并提供“在图谱中查看”入口 |
|
||||
| 没有成功回顾 | 打开工作回顾 | 显示首次生成入口,不伪装成已有总结 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 页面顶部固定显示当前范围、来源和时间区间。
|
||||
- 工作回顾是监督者的主视图之一,与故事线图谱使用 PageTabs 切换。
|
||||
- 内容卡片显示“自动归纳”“待核对”或“用户确认”等状态。
|
||||
- 点击图谱入口时带入当前阶段、实体或事件的定位,不打开无选择的全局图谱。
|
||||
|
||||
### US-S24 手动回顾当前进展
|
||||
|
||||
**状态:已接入工作回顾;执行状态与取消规则见[活动记录](./logic-design.md#活动记录)。**
|
||||
|
||||
作为工作使用者,我希望主动检查当前范围,而不必等待下一次计划运行。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 范围有效且有来源 | 点击“回顾当前进展” | 页面显示整理中状态和活动入口,活动页提供暂停与取消 |
|
||||
| 整理完成并有新变化 | 返回结果 | 工作回顾、监督反馈和图谱引用同一次结果 |
|
||||
| 没有有效新变化 | 整理完成 | 显示检查时间和“暂无新进展”,不重复推送旧内容 |
|
||||
| 用户点击取消 | 整理仍在运行 | 清理期间显示正在取消,停止后显示已取消,保留上一次成功结果和成功批次 |
|
||||
| 回顾正在执行或停止清理 | 再次点击手动发起 | 显示当前运行提示,不新建或排队;关闭提示不取消执行 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 运行按钮在整理期间保持可用,重复点击按上表提示;取消停止后可以新建,已取消运行不能继续。
|
||||
- “无新进展”与“失败”使用不同状态和文案。
|
||||
- 结果必须显示本次冻结的范围,不能因用户后来切换项目而改变解释。
|
||||
|
||||
### US-S03 自动检查不过度打扰
|
||||
|
||||
**状态:待实现;当前智能心跳已实现计划运行、静默之外的私有结果保存和运行审计。**
|
||||
|
||||
作为用户,我希望定时检查有变化时才打扰我,没有新信息时保持安静。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 计划到期 | 智能心跳唤醒监督者 | 监督者只读取冻结范围内的有界内容 |
|
||||
| 没有有效新变化 | 运行完成 | 保存检查记录,不发送重复侧栏反馈或全局通知 |
|
||||
| 有新的阶段认识 | 运行完成 | 更新工作回顾和图谱,并按反馈设置显示监督反馈 |
|
||||
| 运行失败 | 运行结束 | 显示失败状态和重试入口,不把失败当作成功回顾 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 心跳负责唤醒,监督者负责判断是否需要反馈。
|
||||
- 自动结果不在系统通知中暴露私人正文;用户在应用内查看详情。
|
||||
- 同一结果不能同时作为重复的侧栏卡片和全局通知推送。
|
||||
|
||||
### US-S25 查看和处理监督反馈
|
||||
|
||||
**状态:Conversation/Task 过滤、固定/跟随、来源、确认发送和侧栏结果直达图谱已接入;Experiment 仍待实现。验证见[进度](./progress.md)。**
|
||||
|
||||
作为工作使用者,我希望在当前会话旁边看到与当前工作有关的简短反馈,并能进入完整回顾。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 监督栏处于跟随模式 | 用户切换会话或任务 | 面板只显示包含该目标来源且范围匹配的结果,同时标出结果真实范围;无结果显示空态 |
|
||||
| 旧目标请求仍在读取 | 用户切换目标 | 旧摘要、来源和预览不出现在新目标下 |
|
||||
| 监督栏固定到 Conversation、Task 或 Experiment | 用户切换聊天 | 面板继续显示固定目标,并显示目标名称 |
|
||||
| 反馈卡片有来源 | 点击“查看依据” | 展开来源摘要和可用定位信息 |
|
||||
| 用户点击“在图谱中查看” | 进入图谱 | 图谱定位到反馈对应阶段或实体 |
|
||||
| 用户点击“发到当前会话” | 确认发送 | 才将反馈作为消息加入会话;默认不自动发言 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 卡片显示目标、阶段、结论、未解决事项和来源。
|
||||
- 跨会话总结明确显示覆盖范围,不能伪装成当前会话观察。
|
||||
- 固定/跟随模式沿用通用助手工作栏的选择和返回行为。
|
||||
|
||||
## 故事线图谱
|
||||
|
||||
### US-S04 从时间事件查看发生了什么
|
||||
|
||||
**状态:待实现;当前 Demo 仅有模拟数据。**
|
||||
|
||||
作为工作使用者,我希望知道某个时间点发生了什么,以及它影响了哪些认识。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 图谱显示多个时间事件 | 点击外圈时间点 | 相关知识实体和实际连线高亮,其余内容降低显著度 |
|
||||
| 同一天事件过密 | 点击聚合点 | 显示事件数量和事件列表,可选择具体事件 |
|
||||
| 用户选择具体事件 | 查看右侧详情 | 显示事件摘要、所属故事、发生时间、影响实体和来源入口 |
|
||||
| 来源可用 | 点击来源 | 打开所属会话、笔记或知识库条目的对应位置 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 外圈时间轴从起点到终点逆时针排列,持续显示方向和当前时间范围。
|
||||
- 外圈保留缺口,不能让用户误以为时间循环。
|
||||
- 时间到实体的连线表示事件影响实体;它与实体之间的知识关系使用不同文字说明。
|
||||
|
||||
### US-S05 追溯同一个知识实体的变化
|
||||
|
||||
**状态:待实现。**
|
||||
|
||||
作为知识整理者,我希望看到一个认识如何被提出、补充、验证或修订。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 一个知识实体关联多个时间事件 | 点击内部实体 | 多个时间点和连线高亮,右侧先显示当前理解 |
|
||||
| 事件类型分别为提出、补充、验证或修订 | 展开演变 | 每次变化显示类型、时间、摘要和依据 |
|
||||
| 新方案只是增加选择 | 查看演变 | 旧方案不被自动标记为失效 |
|
||||
| 正在回放过去时刻 | 查看实体 | 只显示截至该时刻已形成的理解,不泄漏未来结论 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 一个实体只显示一个稳定节点,不能把每次变化复制成同名节点。
|
||||
- 用户能从当前理解切换到演变列表和来源列表。
|
||||
- 用户确认过的描述和关系显示确认状态,自动新增内容显示待核对状态。
|
||||
|
||||
### US-S06 查看知识关系为什么成立
|
||||
|
||||
**状态:待实现。**
|
||||
|
||||
作为知识整理者,我希望知道两个实体为什么被连接,而不是只看到一条线。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 图谱存在实体间关系 | 点击关系线 | 右侧显示关系类型、关联理由、确认状态和两端依据 |
|
||||
| 关系由系统推测 | 查看关系详情 | 明确标记“待核对” |
|
||||
| 用户确认关系 | 点击确认 | 关系进入用户确认状态,并保留确认操作记录 |
|
||||
| 用户认为关系错误 | 修改类型或移除 | 图谱更新,原始来源不删除,移除可撤销 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 关系类型必须有文字名称,颜色只能辅助区分。
|
||||
- 时间到实体的关系和跨故事实体关系在详情中分别说明。
|
||||
- 后续自动整理只能提出补充或修订建议,不能静默覆盖用户确认内容。
|
||||
|
||||
### US-S10 纠正错误关系而不删除来源
|
||||
|
||||
**状态:待实现。**
|
||||
|
||||
作为知识整理者,我希望修正系统错误合并的概念或错误关系,同时保留原始记录和撤销能力。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 自动归纳把两个概念错误连接 | 在关系详情中选择修改或移除 | 图谱立即反映修正,并显示待保存或已保存状态 |
|
||||
| 用户移除一条关系 | 查看来源或知识库 | 原始来源和知识库正文仍然存在 |
|
||||
| 用户完成修正 | 后续自动整理 | 系统可以提出新的证据或差异,但不能静默恢复被移除关系 |
|
||||
| 用户点击撤销 | 修正仍可撤销 | 关系恢复到修正前状态,来源不发生变化 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 破坏性关系操作使用明确确认,并说明只改变图谱组织。
|
||||
- 用户修改、系统归纳和来源事实显示不同状态。
|
||||
- 修正失败时保留当前详情和未提交意图,不能显示为已保存。
|
||||
|
||||
### US-S07 按时间段查看阶段总结
|
||||
|
||||
**状态:待实现。**
|
||||
|
||||
作为工作使用者,我希望复盘一段过程,而不是逐条翻阅原始记录。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 图谱有多个事件 | 拖选起止时间或使用时间范围控件 | 页面显示选中范围和阶段总结入口 |
|
||||
| 用户打开阶段总结 | 查看结果 | 总结只覆盖该范围,分别列出决定、变化和未解决事项 |
|
||||
| 用户拖动回放控制 | 回到过去时刻 | 图谱只显示当时已经发生的事件和已经形成的认识 |
|
||||
| 用户切换圆形/平铺 | 保留当前范围 | 回放时刻、选择和范围继续保留 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 平铺环绕视图是默认图谱视图;圆形视图用于观察故事聚合。
|
||||
- 回放状态必须持续显示“截至某时刻”,避免误读当前结论。
|
||||
- 时间轴密集时先聚合数量,展开后才能选择单个事件。
|
||||
|
||||
### US-S15 在列表、窄屏和键盘中完成同样操作
|
||||
|
||||
**状态:待实现。**
|
||||
|
||||
作为无法方便操作图形的用户,我希望通过文字列表和键盘完成相同的查看和追溯任务。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 用户使用窄屏 | 打开故事线图谱 | 故事列表收为选择入口,详情进入可返回的独立区域 |
|
||||
| 用户不使用鼠标 | 用 Tab、方向键和 Enter 选择对象 | 能选择时间事件、实体和关系,并查看相同详情 |
|
||||
| 用户缩放或平移图谱 | 返回详情或列表 | 当前选择不丢失 |
|
||||
| 图形无法加载 | 使用文字列表 | 仍能浏览对象、关系、来源和回到知识库的入口 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 焦点始终可见,弹出详情关闭后返回触发它的控件。
|
||||
- 文字列表和图形共享同一选择状态,不维护两份内容。
|
||||
- 缩放、平移和颜色不能成为获取信息的唯一方式。
|
||||
|
||||
## 来源与知识库
|
||||
|
||||
### US-S08 回到来源核对依据
|
||||
|
||||
**状态:待实现,沿用现有知识库的本地条目和外部片段边界。**
|
||||
|
||||
作为知识整理者,我希望从总结或图谱回到原始依据。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 引用的是本地知识库条目 | 点击来源 | 打开原条目及引用位置,并可返回图谱 |
|
||||
| 引用的是会话或魔法笔记 | 点击来源 | 打开所属内容,并显示可用的消息、条目或位置 |
|
||||
| 引用的是外部知识库片段 | 点击来源 | 打开已保存的有界片段和可用定位信息 |
|
||||
| 原来源已失效或没有位置 | 点击来源 | 明确显示不可用或缺少定位,不伪造远端原文跳转 |
|
||||
|
||||
### US-S09 把新认识整理进本地知识库
|
||||
|
||||
**状态:待实现。**
|
||||
|
||||
作为知识整理者,我希望把可靠的新认识放入现有知识库,而不是建立第二份正文存储。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 候选归纳尚无对应条目 | 选择“新建条目” | 显示正文预览、来源和目标位置,确认后才保存 |
|
||||
| 已有本地条目可以补充 | 选择“补充已有条目” | 显示新增内容与原内容的差异,确认后修改原条目 |
|
||||
| 用户查看的是外部只读资料 | 查找写入操作 | 不显示写回或远端同步按钮 |
|
||||
| 同一内容已被保存 | 再次整理 | 提示已有条目,避免重复创建 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 图谱保存实体、关系和来源定位,不复制知识库正文。
|
||||
- 保存失败保留编辑预览,不能把候选显示为已写入。
|
||||
- 现有资料使用“打开知识库条目”,已有条目不显示“存入知识库”。
|
||||
|
||||
## 建议、任务与继续工作
|
||||
|
||||
### US-S16 审核心跳产生的记忆建议
|
||||
|
||||
**状态:已实现,沿用智能心跳页面。**
|
||||
|
||||
作为知识整理者,我希望逐条确认或忽略记忆候选。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 存在待确认记忆 | 打开待处理建议 | 显示类型、内容、置信度和显著性 |
|
||||
| 内容较长 | 点击展开 | 显示完整内容,再次点击可以收起 |
|
||||
| 用户确认 | 点击确认记忆 | 状态变为已确认,计数和指标更新 |
|
||||
| 用户不接受 | 点击忽略 | 候选不再作为待处理项,不改写来源 |
|
||||
|
||||
### US-S17 处理后续任务建议
|
||||
|
||||
**状态:已实现,沿用智能心跳页面。**
|
||||
|
||||
作为工作使用者,我希望把回顾产生的行动建议带入对话或标记完成。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 存在未完成的后续任务 | 点击“带入对话处理” | 将任务交给当前会话处理入口,不自动执行工具 |
|
||||
| 用户完成任务 | 点击标记完成 | 任务状态更新,行动转化指标刷新 |
|
||||
| 用户不再需要任务 | 点击忽略建议 | 任务标记为取消或不再待处理 |
|
||||
| 任务已经完成或取消 | 再次打开建议 | 不显示需要处理的操作按钮 |
|
||||
|
||||
### US-S11 带着上下文继续讨论
|
||||
|
||||
**状态:待实现。**
|
||||
|
||||
作为工作使用者,我希望围绕已有知识继续讨论,而不用重新交代背景。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 用户选中知识实体或阶段总结 | 点击“继续讨论” | 显示将携带的摘要、时间范围和来源列表 |
|
||||
| 用户移除部分上下文并确认 | 进入会话 | 会话只携带剩余上下文,并明确来自监督者 |
|
||||
| 新讨论产生内容 | 下一次整理 | 新内容可以关联到原实体或提出待核对的新关系 |
|
||||
| 用户没有确认上下文 | 关闭预览 | 不创建会话消息,也不改变图谱确认状态 |
|
||||
|
||||
## 历史、失败与权限
|
||||
|
||||
### US-S18 查看报告和运行审计
|
||||
|
||||
**状态:已实现。**
|
||||
|
||||
作为监督者配置者,我希望区分报告内容和产生报告的运行记录。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 已有报告 | 打开报告与记录 | 按时间显示摘要、亮点和建议数量 |
|
||||
| 用户点击展开报告 | 展开一项 | 显示完整摘要和亮点,可再次收起 |
|
||||
| 用户查看运行记录 | 打开审计列表 | 显示手动/计划触发、时间、状态、尝试次数和错误 |
|
||||
| 记录超过首屏数量 | 点击加载更多 | 追加历史,不替换当前已展开内容 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 报告和 Run 审计是两个可区分的区域。
|
||||
- `claimed`、完成、失败和跳过使用文字和图标共同表达。
|
||||
- 失败信息直接显示在对应 Run 下,不能用空报告代替。
|
||||
|
||||
### US-S19 加载、刷新和首次空状态可区分
|
||||
|
||||
**状态:已实现;监督者页面必须沿用。**
|
||||
|
||||
作为用户,我希望知道页面是在加载、真的没有数据,还是读取失败。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 页面首次读取尚未完成 | 打开页面 | 显示加载状态和忙碌语义,不显示创建按钮造成误导 |
|
||||
| 首次读取失败 | 页面返回错误 | 显示错误原因和重试按钮 |
|
||||
| 已有数据显示但刷新失败 | 点击刷新后失败 | 保留旧数据,同时显示刷新失败提示和重试入口 |
|
||||
| 读取成功但没有数据 | 完成加载 | 显示首次配置或生成回顾的空状态 |
|
||||
|
||||
### US-S12 新一次失败不覆盖旧结果
|
||||
|
||||
**状态:待实现;当前智能心跳已有失败 Run 保留历史结果的基础行为。**
|
||||
|
||||
作为工作使用者,我希望一次模型、来源或网络失败不会抹掉上一次可用回顾。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 已有成功回顾 | 新一次整理失败 | 旧回顾继续可读,新运行显示失败原因和覆盖范围 |
|
||||
| 新一次整理被取消 | 用户查看工作回顾 | 显示已取消,不把部分内容标成完整回顾 |
|
||||
| 只有部分来源可用 | 整理完成 | 显示已覆盖和遗漏范围,结果标记为部分完成 |
|
||||
| 用户点击重试 | 重新运行 | 创建新的运行记录,不把旧失败记录改写为成功 |
|
||||
|
||||
### US-S13 历史回放不冒充当前来源
|
||||
|
||||
**状态:待实现。**
|
||||
|
||||
作为知识整理者,我希望回看过去时看到当时的认识,知道原始来源是否已经变化。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 来源后来被修改 | 回放旧阶段 | 仍显示当时保存的归纳和可用历史依据 |
|
||||
| 旧版原文无法恢复 | 点击来源 | 明确提示历史版本不可用,并显示当前可用信息的时间,不称其为旧原文 |
|
||||
| 当前结论与过去不同 | 回放过去 | 不把当前结论注入过去的图谱或阶段总结 |
|
||||
|
||||
### US-S20 遵守范围和访问边界
|
||||
|
||||
**状态:已实现于智能心跳输入与工具禁用边界;监督者目标行为待实现。**
|
||||
|
||||
作为受限访问者,我希望监督者只处理授权范围内的内容。
|
||||
|
||||
**Given / When / Then**
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 计划是 Global | 运行回顾 | 只读取允许访问的有界会话、任务和 Global 已确认记忆 |
|
||||
| 计划绑定项目 | 运行回顾 | 只读取选中项目内容及允许共享的 Global 记忆 |
|
||||
| 模型尝试调用工具 | 整理期间 | 调用被拒绝,运行失败原因可审计 |
|
||||
| 模型输出项目外的记忆或任务 | 校验输出 | 结果被拒绝,不写入越权内容 |
|
||||
|
||||
**界面行为**
|
||||
|
||||
- 页面显示本次运行的冻结范围。
|
||||
- 权限不足、来源不可用和没有新内容使用不同状态。
|
||||
- 监督者的 Agent 身份不自动获得工具执行、批准或切换 Execute 的能力。
|
||||
|
||||
## 活动记录
|
||||
|
||||
### US-S26 查看实际执行活动
|
||||
|
||||
对应 FR-S10,当前已接入。
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 用户手动开始监督回顾 | 打开活动 | 显示冻结范围、开始时间和运行中状态;失败保留错误,成功提供结果入口 |
|
||||
| 定时或立即运行心跳已保存报告 | 下游监督仍在运行或失败 | 一条活动分别显示阶段状态,整体不能显示完成 |
|
||||
| 活动包含较早的成功结果 | 点击查看回顾或在图谱中查看 | 打开该 resultId,超过最近结果列表仍正确定位 |
|
||||
| 活动读取失败 | 点击重试 | 保留可用记录并重新读取,不调用模型 |
|
||||
| 活动有在途请求 | 离开页签、页面或关闭应用 | 停止后续刷新,迟到响应不更新界面;关闭后不查询活动 |
|
||||
| 旧记录缺少范围或运行关联 | 查看活动 | 明确显示缺失,不根据当前计划或相近时间推断 |
|
||||
| 六个来源批次已保存,剩余来源为零 | 导航合并失败后打开活动 | 已记录的合并阶段显示失败,发布保持未完成;继续复用成功叶子和导航节点 |
|
||||
| 已保存批次跨多个项目和会话 | 展开事实并翻页 | 按本页真实归属分层,标明本页数量;原文可读,失败叶子不伪造为已保存 |
|
||||
| 原始错误是多行校验 JSON | 展开错误详情 | 默认折叠,展开后有界滚动,完整文本可选择复制;恢复操作和阶段仍在其前面 |
|
||||
|
||||
继续后的运行仍可暂停或取消;任一运行执行或停止清理期间,其他运行不能继续。重新进入页面仍显示实际停止状态。人工修改时间序列审计不属于当前已实现范围;实际状态及保留规则见[活动记录](./logic-design.md#活动记录)。
|
||||
|
||||
### US-S27 自动增量与历史重看
|
||||
|
||||
对应 FR-S4、FR-S10。
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 监督已成功处理窗口内来源 | 同范围再次自动检查且来源未修改 | 不调用模型,活动显示无变化,不新增图谱事件或建议 |
|
||||
| 消息正文、知识引用或任务状态有修改 | 再次自动检查 | 只处理修改版本及未读片段,结合已有摘要和实体说明,允许复用已有实体 ID |
|
||||
| 来源多于单次预算或正文较长 | 一次结果成功保存后再次检查 | 继续处理窗口内遗漏或剩余片段,已送入且保存成功的部分不重复处理 |
|
||||
| 模型、校验或保存失败 | 再次检查相同输入 | 失败阶段仍可重试;已成功的另一个阶段不重复付费 |
|
||||
| 来源已经自动处理 | 用户点击
|
||||
| 只有已确认记忆或背景摘要变化 | 自动检查 | 背景不单独触发模型调用 |
|
||||
|
||||
### US-S32 点击回顾只整理变化
|
||||
|
||||
**状态:已实现(2026-09-30)。** 对应 FR-S4、FR-S12,规则 H3、H4。
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 所选范围已有成功回顾,来源未变化 | 点击“回顾” | 不调用模型,活动显示“无新变化”,保留原结果 |
|
||||
| 有新增或修订的消息 | 点击“回顾” | 只处理变化部分,关联已有实体后发布;不生成建议 |
|
||||
| 用户需要重看一段历史 | 在“更多”中选择“重新整理”并确认 | 重新分析所选区间,保留用户确认内容,不改变自动进度 |
|
||||
| 首次对某范围回顾 | 点击“回顾” | 完整分批处理所选时间区间 |
|
||||
|
||||
### US-S33 心跳按配置给出建议
|
||||
|
||||
**状态:已实现(2026-09-30),主动介入除外。** 对应 FR-S12,规则 H1、H5 至 H10。
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 计划介入方式为“生成建议”,来源无变化 | 计划到期 | 不调用模型,不产生建议或通知 |
|
||||
| 有变化,但图谱变化中没有建议候选 | 计划到期 | 完成增量整理;不发起建议调用 |
|
||||
| 有变化,出现新的未解决事项或分歧 | 计划到期 | 整理后按候选生成建议,每条附依据并可打开来源;原文不重复读取 |
|
||||
| 介入方式为“仅更新记忆” | 计划到期 | 只更新图谱,不生成建议 |
|
||||
| 用户忽略一条建议 | 后续检查没有新证据 | 不再重复提出 |
|
||||
| 建议生成失败 | 查看活动 | 显示“已更新,建议生成失败”;图谱与进度保留,可单独重试建议 |
|
||||
| 旧计划升级 | 打开自动监督 | 范围、频率和启停状态不变,介入方式为“生成建议”;旧报告只读可查 |
|
||||
|
||||
|
||||
## 时态图谱与 Agent 读取
|
||||
|
||||
### US-S28 依据决定演变继续工作
|
||||
|
||||
**状态:D1 工具读取已实现;真实模型建议质量尚未验收。** 对应 FR-S11,读取合同见[时态 Story Graph MCP 设计](./story-graph-mcp-design.md),验证范围见[实施进度](./progress.md#2026-09-28-story-graph-只读工具)。
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 已保存旧决定 A,后来有明确证据将其修订为 B | 用户询问“继续上次工作,现在采用什么” | Agent 按需读取图谱,说明 A 到 B 的变化、推荐 B 的依据,并引用旧决定与修订来源 |
|
||||
| 新记录只是增加选项 C | Agent 查询当前理解 | B 不因 C 更新而自动失效;替代或矛盾关系必须有明确证据 |
|
||||
| 记录只表明决定采用 B,没有完成证据 | 用户询问执行进度 | 回答已决定且完成状态未知,不推断已完成 |
|
||||
| 已确认旧认识与新证据冲突 | Agent 组织当前建议 | 分别展示确认状态、有效性与待核对差异,不静默覆盖 |
|
||||
|
||||
### US-S29 分页核对时间与来源
|
||||
|
||||
**状态:D1 已实现,缺失历史版本明确返回不可用。** 对应 FR-S11、US-S13。
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 旧事件在较晚回顾中才生成并记录 | 按发生时间查询并查看 timeline | 区分事件、回顾生成和记录时间;不把补录当成新发生,也不声称当时已知 |
|
||||
| 搜索、演变或原文超过一页 | Agent 继续读取 | 每页说明范围、修订、覆盖与剩余页,能读取全部匹配内容,无静默截断 |
|
||||
| 旧记录只有证据条数,没有完整覆盖信息 | Agent 读完最后一页 | 返回覆盖未知,不称已覆盖全部原始工作记录 |
|
||||
| 引用版本原文已不可用或当前内容已修改 | 读取历史来源 | 返回实际保存片段或明确版本不可用;读取当前版本须显式选择并标注差异 |
|
||||
| 分页期间对象版本变化 | 继续使用旧游标 | 明确提示游标失效并重新查询,不混合新旧版本 |
|
||||
| 用户要求截至过去时刻的完整认识 | 调用 `as_of` | 明确不支持,不用 current 或 timeline 冒充历史重建 |
|
||||
|
||||
### US-S30 按需读取并遵守应用开关
|
||||
|
||||
**状态:D1 已实现;跨平台安装包验收未执行。** 对应 FR-S11、US-S14、US-S20。
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 会话属于当前项目 | Agent 未显式指定 scope 搜索 | 只查该项目;无项目时要求明确范围,不自动扩大为 Global |
|
||||
| 用户进行普通聊天 | 没有继续工作或历史依据需求 | 不强制每轮查询或注入完整图谱;需要核对原话时才进一步读来源 |
|
||||
| 监督者开启且内置能力已分配,没有自动计划 | 查询已有结果 | 可以读取;查询不触发模型、回顾或业务写入 |
|
||||
| 监督者关闭,旧会话仍缓存工具 | 列出工具或直接调用 | 不暴露工具,调用被拒绝;已有数据保留 |
|
||||
| 相同范围与证据分别通过本地和远程 Runtime 查询 | 翻页、读取版本或关闭监督者 | 返回相同业务语义与引用,远程实际桥接须通过验收 |
|
||||
|
||||
### US-S31 保留偏好并复用跨范围事实
|
||||
|
||||
**状态:待实现;当前仍独立处理精确 scope,读取现有已确认 memory。** 对应 FR-S11、US-S16、US-S27。
|
||||
|
||||
| Given | When | Then |
|
||||
| --- | --- | --- |
|
||||
| 用户已有已确认的 Global 或项目偏好 | 逐步切换统一工作记忆入口 | 原内容、确认状态、适用范围和引用保留,不删除旧数据 |
|
||||
| 旧偏好只有更新时间 | 查询演变 | 标记为当前背景和历史未知,不补造决定时间或替代关系 |
|
||||
| 项目 A 的来源版本已有成功叶子事实 | 目标复用能力交付后进行 Global 整理 | 复用该版本的事件、变化、关系和来源,不因 scope 改变重复提取相同正文,也不只复用摘要 |
|
||||
| 仅部分片段成功或原来源已改版 | 再次整理 | 识别未覆盖或变更部分,不把旧版本覆盖算作新版本完成 |
|
||||
| Global 结果含多个项目的归纳 | 从单项目查询 | 不把跨项目摘要伪装成项目结论,复用事实仍保留来源归属 |
|
||||
|
||||
## 验收优先级与依赖
|
||||
|
||||
首个可用版本应先打通以下路径:
|
||||
|
||||
> 应用入口 → 选择范围 → 手动回顾 → 工作回顾 → 选择知识实体 → 查看多个时间事件 → 打开来源 → 预览并继续讨论
|
||||
|
||||
建议实施顺序:
|
||||
|
||||
1. 顶层有工作回顾、故事线图谱、自动监督、活动记录、设置五项。自动监督保留计划 CRUD 与范围校验,创建和编辑使用同一 Modal 表单;运行概览、指标、趋势、自动监督报告、建议和心跳审计与计划列表同页,无计划时只有一处未配置说明和一个创建按钮。工作回顾只阅读统一监督结果,新请求的范围和运行状态不改变旧结果的日期、范围及正文;自动报告为空时不会在成功结果下显示“没有报告”。历史选择位于正文前,图谱打开所选结果的精确 ID。活动记录只保留统一执行、进度和详情。设置只显示算法参数,没有计划、报告、审计、第二套页签、整页范围徽标或独立刷新行。
|
||||
2. 增加监督者统一的工作回顾和共享结果模型。
|
||||
3. 接入最小故事线图谱:时间事件、知识实体、实体变化、来源和时间回放。
|
||||
4. 接入文字列表、窄屏和键盘操作,以及失败、取消和部分来源状态。
|
||||
5. 接入来源双向定位、继续讨论预览和本地知识库新建/补充。
|
||||
6. 侧栏监督反馈和自动增量已接入;事件驱动及语义层面的主动反馈判定仍待实现。
|
||||
|
||||
详细状态规则见[逻辑设计](./logic-design.md),页面结构和共享组件见[UI 设计](./ui-design.md),现有智能心跳的已实现范围见[智能心跳 PRD](../smart-heartbeat/prd.md)。
|
||||
@@ -16,3 +16,12 @@
|
||||
[Runtime 进程复用技术设计](../assistant-workbar/runtime-process-reuse-technical-design.md)
|
||||
统一维护;本 Runtime 的生命周期见[技术设计 §9](./technical-design.md#9-runtime-生命周期),
|
||||
未发布实现的验收状态见[工作栏进度](../assistant-workbar/progress.md#2026-09-14-runtime-进程复用实施中)。
|
||||
|
||||
The separate official Web client is pinned to `@deepseek-ai/dsh@0.1.7-rc.2`.
|
||||
Its Main service API, Ask/Execute policy, capability adapters, packaged resource
|
||||
layout, and Windows validation are documented in
|
||||
[Native Client Proposal, section 9.1](../assistant-workbar/runtime-native-client-proposal.md#91-ds-web-backend-2026-09-26).
|
||||
This Web resource tree is prepared separately from the background Harness Host.
|
||||
Current shortcut/coordinator behavior and configured-model evidence are tracked in
|
||||
[local native client progress](../assistant-workbar/progress.md#2026-09-27-local-native-client-validation),
|
||||
including the remaining managed Node delivery gap.
|
||||
|
||||
@@ -1,5 +1,91 @@
|
||||
# DeepSeek Harness 实现进度
|
||||
|
||||
## 2026-09-26 真实模型与官方 Web 验证
|
||||
|
||||
本轮验证 `0.1.7-rc.2` 后台对话/工具路径与同版本官方 Web 的本地接入可行性,关联
|
||||
[原生客户端方案](../assistant-workbar/runtime-native-client-proposal.md)的 FR-2、3、5,
|
||||
US-2 和 P1/P2。快捷按钮与完整客户端集成本轮未实现。
|
||||
|
||||
环境:Windows x64,标准 Node `24.19.0`,Edge `154.0.4258.37`,官方
|
||||
`@deepseek-ai/dsh@0.1.7-rc.2`,仓库 HEAD `51166136157a646530e1ad9f6a0242c1c6390e95`
|
||||
加当前未提交修改。源码哈希记录在本机临时报告 `dsh-017-verify/source.json`。
|
||||
供应商实际模型为当前 DS 配置中的 `deepseek-flash`,协议为 `openai-chat-completions`。
|
||||
|
||||
| 场景 | 实测结果 | HTTP dispatch |
|
||||
| --- | --- | ---: |
|
||||
| 后台 Ask | `read` 返回未出现在 prompt 中的随机校验码 | 2 |
|
||||
| 同会话切 Execute | `write` 创建文件,实际内容为 `DSH_EXECUTE_OK` | 2 |
|
||||
| Execute Shell | `pwsh` 执行 `Get-Content execute-marker.txt`,返回正确内容 | 2 |
|
||||
| 官方 Web 页面提交 | 当前模型读工作区文件并返回另一随机校验码;刷新仍保留回答 | 3 |
|
||||
| 本轮合计 | 9 次均请求 `deepseek-flash`,9 次均返回 HTTP 200 | 9 |
|
||||
|
||||
计数在 `AgentModelGateway` 向供应商执行 `fetch` 时记录,包含 Web 的 1 次无工具请求;
|
||||
4 次用户提交实际产生 9 次供应商请求。后台 3 轮均收到 `done`、工具 `completed`
|
||||
和每轮 2 个用量事件。调用经过现有 `createManagedModelBridge`、`AgentModelGateway`
|
||||
和 `ModelBridgeLoopbackProxy`,由隔离 Electron profile 中的 safeStorage 解密配置副本。
|
||||
供应商凭据未写入 Web 配置或报告;正式 `runtime-settings.json` 测前测后逐字节一致。
|
||||
环境继承 `NODE_TLS_REJECT_UNAUTHORIZED=0`,本轮不证明严格 TLS 证书校验可用。
|
||||
|
||||
官方 Web 以标准 Node、独立 `DSH_HOME` 和回环随机端口启动。通过官方 patch 配置 provider
|
||||
和默认模型,再调用 `workspace/create`;首次创建和已有目录复用均通过。根页面自动选中
|
||||
测试工作区和当前模型,无需手动填供应商密钥。保留并通过官方“内测声明”的“继续”按钮,
|
||||
真实页面输入、发送、回答、刷新及截图检查通过。配置/API 细节统一维护在方案第 6.3 节。
|
||||
|
||||
本轮驱动曾因缺少 `skillPackages` 默认值和临时目录动态模块解析失败退出,均为 0 次请求;
|
||||
修正驱动后后台成功。首次 Web `fill` 后发送按钮未启用,没有发出 Web 模型请求;改为键盘
|
||||
输入并仅重跑 Web 后成功,未重复后台调用。本轮 9 次 dispatch 逐次记录于同一 `calls.json`。
|
||||
|
||||
补查发现此前 `dsh-017-live/calls.json` 已有 7 次 HTTP 200 记录,其中后台 4 次、Web 3 次;
|
||||
对应历史报告记录模型 `deepseek-flash`、工作区与模型选中、文件结果及配置未改。本轮保留
|
||||
这些文件,未覆盖或重算为新增调用。该历史批次与本轮合计可核查 16 次,下面依赖升级阶段
|
||||
“0 次”仅指升级回归本身。
|
||||
|
||||
定向命令使用标准 Node 执行 `node_modules/vitest/vitest.mjs run`,目标为
|
||||
`src/main/agent/deepseek-harness-acp-e2e.test.ts`、
|
||||
`src/main/agent/goodbuddy-harness-control-plane.test.ts` 和
|
||||
`src/main/agent/deepseek-harness-runtime.test.ts`:3 个文件、52 项通过、3 项跳过,11.12 秒。
|
||||
3 个环境开关 live case 未在 Vitest 中启用;上表为独立真实模型驱动结果,不把跳过项算通过。
|
||||
确定性回归包含 Ask 写入拒绝和 Execute 工作区外写入,无需为验证重复调用真实模型。
|
||||
|
||||
本机证据位于临时 `dsh-017-verify/`:`calls.json`、`backend-result.json`、`result.json`、
|
||||
`main-result.json`、`source.json` 和 `web-conversation.png`。报告保留首次 Web 驱动失败,
|
||||
最终 Web 结果另见 `result.json`。桥接、worker、Web 和浏览器已关闭,进程检查未发现本轮
|
||||
目录所属的残留进程;临时 profile、测试工作区和 DS home 清理后仅保留脚本及脱敏证据。
|
||||
这些本机临时文件不作为仓库长期资源。
|
||||
|
||||
证据范围:后台运行真实 ACP/Host/控制面和供应商模型,Host 在测试进程内组合;Web 为
|
||||
官方 CLI 子进程与真实浏览器页面。未重新验收 GoodBuddy Composer/Utility Process,
|
||||
也未实现 Web 的 GoodBuddy Ask/Execute 适配。官方 Plan 只控制规划状态,默认
|
||||
“工作区内修改”也不等同于 Execute;后续复用现有工具命令名判断,不增加权限层级、
|
||||
目录限制或逐工具二次授权。Skills/MCP 承接、取消、产品生命周期及交付资源仍待验证。
|
||||
未改远程源码;DS 仍不支持 SSH execution space,本轮没有远程模型调用或 Linux Host 验证。
|
||||
|
||||
## 2026-09-26 依赖升级至 0.1.7-rc.2
|
||||
|
||||
23 个直接引用的 `@deepseek-ai/dsh-*` 包统一锁定为 `0.1.7-rc.2`,Cordis
|
||||
升级为上游要求的 `4.0.4`,同步更新依赖锁文件、Runtime 版本及打包测试夹具。
|
||||
版本取自 npm 发布列表与 `next` 标签;查询时 `latest` 仍指向 `0.1.0-rc.6`。
|
||||
|
||||
控制面改为监听 `agent/assistant-stream` 的 chunk 帧,以转发文本、推理和用量;
|
||||
工具结果从 message 层读取 `toolCallId` 和 `isError`。测试同步适配新版本的
|
||||
Agent 配置、Shell 执行和系统消息接口。
|
||||
|
||||
- DS 定向回归:152 项通过、5 项跳过。
|
||||
- 最终复跑 DS、Runtime discovery 和发布打包夹具:12 个文件通过、2 个跳过,
|
||||
170 项通过、5 项跳过,耗时 19 秒。
|
||||
- 扩大至打包夹具和设置面板:196 项通过、5 项跳过;设置面板套件因并行
|
||||
Obsidian 修改缺少 `obsidian-tools` 模块而未能加载。
|
||||
- `npm run smoke:deepseek-harness`:开发构建通过,真实 Electron Utility Host 返回 `ready`。
|
||||
- 升级后的首次类型检查与 lint 通过;后续类型检查受到并行 Obsidian 修改中的
|
||||
模块缺失、项目文件列表和类型错误阻塞,不能将当前整个工作区标为检查通过。
|
||||
- 全量测试两次运行分别在 200 秒、600 秒超时,期间出现 App、SupervisorActivity
|
||||
和 Electron UI 测试失败;未取得全量通过结果,未修改这些并行工作的文件。
|
||||
|
||||
本次依赖升级回归的真实供应商模型调用为 0 次;后续真实模型及 Web 实测见上节。上述升级验证不包含官方 Web 应用,
|
||||
“在浏览器中打开”入口仍按[原生客户端方案](../assistant-workbar/runtime-native-client-proposal.md)实施。
|
||||
远程启动契约和资源打包仅支持 Continue、OpenCode,`create-runtime.ts` 拒绝 SSH
|
||||
执行空间使用 DS,因此本次升级未改变远程 Runtime 路径,也未进行 Linux Host 验证。
|
||||
|
||||
## 2026-09-14 Windows 工作区路径修复
|
||||
|
||||
本机 Runtime 的 Host 启动与 `newSession.cwd` 已共用初始化时解析的 `realpath`,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
| 文档状态 | 实现与发布验收基线 |
|
||||
| 设计目标 | 将 DeepSeek Harness 作为 GoodBuddy 的第三个 Agent Runtime |
|
||||
| Runtime 标识 | `deepseek-harness` |
|
||||
| 当前依赖基线 | 实际使用的 `@deepseek-ai/dsh-*` 底层库,精确锁定 `0.1.2-rc.1` |
|
||||
| 当前依赖基线 | 实际使用的 `@deepseek-ai/dsh-*` 底层库,精确锁定 `0.1.7-rc.2`,Cordis 为 `4.0.4` |
|
||||
| 上游状态 | Developer Preview,允许出现破坏性变更 |
|
||||
| 上游许可证 | MIT |
|
||||
| GoodBuddy 目标平台 | Windows、macOS、Linux,x64 与 arm64 |
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
DeepSeek Harness 的底层库使用 Cordis 组合服务。GoodBuddy 不采用官方产品 profile,也不允许用户配置覆盖内部 Host 或控制服务;GoodBuddy 自行维护 Host、控制协议和生命周期,同时提供一个由 Main 管理、默认关闭的 npm 插件市场。用户显式开启后,市场只搜索带精确 `dsh-plugin` 关键字的公共 npm 包,不代表 GoodBuddy 审核、推荐或承诺兼容这些包。
|
||||
|
||||
用户明确安装并启用的插件以当前用户权限运行。Ask/Execute 只控制模型经过 `tools/execute` 发起的工具调用:Ask 只允许 Host 中真实注册的 `read`、`skill` 和 Main 管理的 Web Search/Fetch 代理,Execute 放行 Host 中全部已注册工具。插件不能用同名工具冒充 Ask 允许项。插件安装脚本和初始化代码不属于模型工具调用,不能由 Ask 限制,因此界面在安装前必须明确确认这一边界。
|
||||
用户明确安装并启用的插件以当前用户权限运行。Ask/Execute 只控制模型经过 `tools/execute` 发起的工具调用:Ask 只允许 Host 中真实注册的 `read`、`skill` 和 Main 管理的 Web Search/Fetch、Story Graph 只读代理,Execute 放行 Host 中全部已注册工具。插件不能用同名工具冒充 Ask 允许项。插件安装脚本和初始化代码不属于模型工具调用,不能由 Ask 限制,因此界面在安装前必须明确确认这一边界。
|
||||
|
||||
整体分成两个互相约束的部分:
|
||||
|
||||
@@ -145,7 +145,7 @@ Harness 子进程内控制面不能取代 Main 控制面,Main 控制面也不
|
||||
|
||||
Harness Session 只在当前 Runtime 进程生命周期内存在。释放 GoodBuddy 会话时必须同步释放对应 Harness Agent。
|
||||
固定 Host 显式加载 `dsh-session-projection` 的内存 SessionProjectionRegistry,这是
|
||||
`0.1.2-rc.1` AgentLoop 的必需依赖;不加载 Session persistence,也不新增磁盘会话副本。
|
||||
`0.1.7-rc.2` AgentLoop 的必需依赖;不加载 Session persistence,也不新增磁盘会话副本。
|
||||
该依赖与其余 DSH 依赖一起由 Vite 打入 Host,不能从 `app.asar.unpacked` 外部导入
|
||||
仅存在于 `app.asar` 的 npm 包。最终打包检查及 Windows CI 的真实 UtilityProcess
|
||||
启动探针见 [Runtime 资源与打包](../../../BUILD.md#runtime-资源)。
|
||||
@@ -368,7 +368,7 @@ GoodBuddy conversationId -> Harness sessionId + workspace + process generation
|
||||
|
||||
### 9.5 跨项目复用
|
||||
|
||||
锁定 0.1.2-rc.1 的文件和 Bash/PowerShell 工具已按原生 Session header.cwd 路由,
|
||||
锁定 0.1.7-rc.2 的文件和 Bash/PowerShell 工具已按原生 Session header.cwd 路由,
|
||||
Provider 的根 cwd 只是默认值。不为每工作区复制本地 Provider;
|
||||
Main 的 Web/MCP ToolProvider 按工作区保存,最后一个对应 Session 释放时 dispose。
|
||||
|
||||
@@ -386,11 +386,17 @@ Main 的 Web/MCP ToolProvider 按工作区保存,最后一个对应 Session
|
||||
| Ask | 只允许 Host Registry 中真实注册的 `read` 与 `skill`;其他 Host/插件工具一律拒绝 | 能力启用时注册 Main 的精确代理对象,无逐次审批 | 不注册 | 模型工具调用保持只读 |
|
||||
| Execute | 放行 Host 中全部已注册的内置与插件工具 | 能力启用时注册 Main 代理 | 按分配注册并经过既有 RuntimeAuthorizer | 不增加插件权限层,以当前用户权限运行 |
|
||||
|
||||
2026-09-28:内置 Story Graph 的三个读取工具在 Ask/Execute 均通过 Main 代理提供,
|
||||
Host 只接受 Main 注册的具体工具对象。Main 逐次检查监督者启用、Harness 分配及请求项目范围,
|
||||
不逐次审批。其他普通内置 MCP 仍不支持 Harness 分配;合同与证据见
|
||||
[Story Graph 设计](../conversation-supervision/story-graph-mcp-design.md)和
|
||||
[验证进度](../conversation-supervision/progress.md#2026-09-28-story-graph-只读工具)。
|
||||
|
||||
### 10.2 Ask 模式
|
||||
|
||||
- Harness Control Plane 在 `tools/execute` 分发边界识别当前 Session 和在途请求。
|
||||
- 采用所有权感知的只读允许列表,只接受 Registry 中真实的 `read`、`skill` 和 Main 注册的 Web 代理对象;`write`、`edit`、Shell、MCP 及任意新插件工具默认拒绝。只比较工具名不足以授权,插件注册同名工具仍会被拒绝。
|
||||
- Ask 不注册 Main 代理的 MCP 工具。
|
||||
- 采用所有权感知的只读允许列表,只接受 Registry 中真实的 `read`、`skill` 和 Main 注册的 Web/Story Graph 代理对象;`write`、`edit`、Shell、自定义 MCP 及任意新插件工具默认拒绝。只比较工具名不足以授权,插件注册同名工具仍会被拒绝。
|
||||
- Ask 不注册 Main 代理的自定义 MCP;Story Graph 使用共享只读合同,scope 的互斥分支转换为 Harness 支持的 `oneOf`,完整参数仍由 Main 的 Zod schema 校验。
|
||||
- Web Search/Fetch 的凭据、传输与结果限制保留在 Main;Utility 只看到有界 schema 和结果。
|
||||
- 只读不等于无限输出,读取仍受字节和工具结果上限控制。
|
||||
- 插件安装脚本和 Cordis 初始化生命周期不经过 `tools/execute`。Ask 不能把已启用第三方代码变成沙箱,也不能保证第三方代码没有启动副作用。
|
||||
@@ -405,7 +411,7 @@ Main 的 Web/MCP ToolProvider 按工作区保存,最后一个对应 Session
|
||||
`goodbuddy/tools/list` 按 Session 保存本次发现的工具定义;`goodbuddy/tools/call` 复用该定义
|
||||
校验参数与授权,不因调用健康工具而重新等待故障 MCP 的发现。发现重试留在清单刷新,
|
||||
Session 释放及 Runtime dispose 清除内存定义,不持久化第二份能力状态。
|
||||
- 所有工具调用仍作为活动事件记录;Ask 和 delegation 路径继续固定拒绝。
|
||||
- 所有工具调用仍作为活动事件记录;自定义 MCP 的 Ask 和 delegation 路径继续固定拒绝。
|
||||
|
||||
### 10.4 Runtime OS 沙箱
|
||||
|
||||
@@ -418,7 +424,7 @@ Main 的 Web/MCP ToolProvider 按工作区保存,最后一个对应 Session
|
||||
|
||||
- 使用环境变量白名单构造 utilityProcess 环境。
|
||||
- 不继承 `NODE_OPTIONS`、调试端口、任意 npm 配置、用户 `DSH_*` 覆盖或白名单之外的凭据。
|
||||
- `DSH_TELEMETRY_DISABLED=1` 必须固定设置。
|
||||
- `DSH_TELEMETRY_DISABLED=1` 必须固定设置。后台 Host 与独立 Web 启动器均叠加 `src/shared/runtime-privacy-environment.ts` 的通用遥测关闭配置,设置 `DO_NOT_TRACK=1`、`OTEL_SDK_DISABLED=true`,并关闭日志、指标和追踪 exporter。模型、MCP 和用户主动联网请求不受此配置限制。
|
||||
- Harness Home 指向 GoodBuddy 管理的隔离目录。
|
||||
- 不调用官方 `loadEnv` 或 `loadLayeredEnv`。
|
||||
- API Key 由 Main 从加密设置中解析。
|
||||
@@ -546,7 +552,7 @@ Runtime: GoodBuddy 内置 DeepSeek Harness
|
||||
模型配置: 跟随 GoodBuddy · 企业网关(qwen-plus)
|
||||
状态: 已就绪
|
||||
路径: <受控 Host 路径>
|
||||
版本: 0.1.2-rc.1
|
||||
版本: 0.1.7-rc.2
|
||||
执行权限: 当前用户权限
|
||||
|
||||
Host 始终由当前 GoodBuddy 版本提供,不存在自定义 Host 入口。
|
||||
@@ -689,11 +695,11 @@ Renderer 只接收公开 npm 元数据和受管插件状态。任何凭据、完
|
||||
- 二进制检测、版本解析和路径规范化。
|
||||
- ACP 握手、事件转换和请求关联。
|
||||
- 每个会话单请求、跨会话并行。
|
||||
- Ask 在工具分发边界只允许真实注册的 `read`、`skill` 与 Main Web 代理,并拒绝同名冒充和任意新插件工具;Execute 放行插件工具。
|
||||
- Ask 在工具分发边界只允许真实注册的 `read`、`skill` 与 Main Web/Story Graph 代理,并拒绝同名冒充和任意新插件工具;Execute 放行插件工具。
|
||||
- 握手只接受明确的 `execution.mode = 'host'`。
|
||||
- 未分配 Skill/MCP 不可见;分配后的 Skill catalog 可调用 `skill` 加载。
|
||||
- 原生能力快照只包含 Host/插件原生 Skills,不包含 GoodBuddy 分配的 Skills 或 MCP。
|
||||
- Ask 不注册 MCP 工具;Web 代理可用于 Ask 与 Execute;Execute 每轮刷新有界 MCP schema,并在调用前再次校验活动请求、模式、参数和 RuntimeAuthorizer 结果。
|
||||
- Ask 不注册自定义 MCP 工具;Web 和 Story Graph 代理可用于 Ask 与 Execute;Execute 每轮刷新有界自定义 MCP schema,并在调用前再次校验活动请求、模式、参数和 RuntimeAuthorizer 结果。
|
||||
- MCP URL、启动命令和凭据不进入 Utility 启动配置或协议结果。
|
||||
- 未知授权结果失败关闭。
|
||||
- 超时、取消、迟到帧和进程意外退出。
|
||||
@@ -760,7 +766,7 @@ npm run build
|
||||
|
||||
- `deepseek-harness` 可被保存、选择、检测和显示。
|
||||
- Runtime 详情卡内显示状态、路径、版本和当前用户执行权限。
|
||||
- Skills 与自定义 MCP 设置可把能力分配给 DeepSeek Harness;请求级 GoodBuddy 内置 MCP 当前不支持分配,并在内置 MCP 卡片中以置灰、未选择状态明确显示。布局、键盘语义、文案和保存回显通过真机检查。
|
||||
- 该阶段的 Skills 与自定义 MCP 设置可把能力分配给 DeepSeek Harness;当时请求级 GoodBuddy 内置 MCP 不支持分配,并在卡片中置灰。布局、键盘语义、文案和保存回显通过真机检查。2026-09-28 的 Story Graph 源码另接入 Harness 分配与 Main 只读代理,验证证据见上文专项记录。
|
||||
- DSH 市场初始关闭且不加载 npm 目录;显式开启后可以搜索、安装、启停、配置和移除插件,安装前只出现一次准确的当前用户权限确认。关闭市场后已有启用插件继续运行,重新开启后管理状态不变。
|
||||
- Ask 写入测试在 Runtime 边界失败。
|
||||
- Ask 可使用已启用的 Main Web Search/Fetch,且插件无法通过同名工具绕过所有权校验。
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# 设备发现与共享
|
||||
|
||||
本功能是 GoodBuddy 桌面端管理附近设备、本机共享和 ShareServer 连接的唯一入口。默认连接
|
||||
方式是局域网;网关由用户额外添加。功能不包含 ShareServer 的组织管理 Web 界面。
|
||||
设备共享在测试阶段默认关闭,先在“管理应用 → 设备共享 → 应用设置”启用,再从“应用 →
|
||||
设备共享”或管理列表的“打开”进入。当前首期连接本地未认证
|
||||
ShareServer 元数据服务,默认地址为 `http://127.0.0.1:8787`,可注册本机、浏览设备与发布目录、
|
||||
发布能力或知识的元数据并撤销本机 ID 的条目。设备 ID 只用于识别,不是所有权认证。
|
||||
|
||||
当前实现不执行远程能力,不读取、搜索或下载知识正文;来源模式和权限均为元数据声明。
|
||||
局域网发现、配对、组织网关和远程调用仍是后续设计,不是首期交付内容。
|
||||
|
||||
## 文档导航
|
||||
|
||||
@@ -9,12 +14,13 @@
|
||||
| --- | --- |
|
||||
| [PRD](./prd.md) | 用户问题、范围、功能需求和产品验收 |
|
||||
| [功能逻辑](./logic-design.md) | 不变量、状态、授权优先级和失败结果 |
|
||||
| [UI 设计](./ui-design.md) | 设置入口、四个页签、配对与调用交互 |
|
||||
| [UI 设计](./ui-design.md) | 当前应用入口、单页表单与列表,以及后续发现、配对界面 |
|
||||
| [技术设计](./technical-design.md) | Desktop 进程边界、持久化、IPC、网络和验证 |
|
||||
| [实施进度](./progress.md) | 首期实现、检查命令、真实 Electron 联调证据及未测范围 |
|
||||
| [共享网络总体设计](../../architecture/share-network-architecture.md) | 跨端身份、能力、授权、任务、更新和协议权威 |
|
||||
|
||||
当前没有单独的 User Stories:正常、失败、撤销、权限和兼容场景已直接进入 PRD 验收与逻辑
|
||||
决策表。功能尚未实施,因此不创建 `progress.md`。
|
||||
当前没有单独的 User Stories:首期验收见 PRD 的 `FR-M1` 至 `FR-M5`;后续场景仍在原有
|
||||
需求与逻辑决策表中保留。下列 Peer、配对、网关术语仅用于后续设计。
|
||||
|
||||
## 术语
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 设计中 |
|
||||
| 状态 | 本地元数据首期已接入;完整共享网络为后续设计 |
|
||||
| 版本 | 0.1 |
|
||||
| 日期 | 2026-09-03 |
|
||||
| 关联 PRD | [设备发现与共享 PRD](./prd.md) |
|
||||
@@ -12,6 +12,27 @@
|
||||
|
||||
本文负责桌面端状态、不变量和决策规则,不重复跨端协议定义。
|
||||
|
||||
## 当前首期规则
|
||||
|
||||
本节对应 [FR-M1 至 FR-M5](./prd.md#当前首期本地元数据)。后文发现、配对、授权和任务状态均为
|
||||
后续设计,不适用于本地未认证元数据服务。
|
||||
|
||||
应用启用状态默认关闭,缺失设置按关闭处理,明确保存的启用状态在重启后保留。启用只开放
|
||||
页面入口;打开页面才读取目录。关闭后菜单隐藏该项,当前页面返回对话;不清除设备设置、
|
||||
目录或已发布记录,不停止已有请求。该应用开关不代替后续发现、广播和发布的独立状态。
|
||||
|
||||
本机 ID 在首次读取设置时生成并原子保存;并发首次读取共用一次加载。名称与服务 URL 更新
|
||||
串行保存,不接受 Renderer 写入设备 ID。已存在设置无法读取或解析时直接报错,避免静默
|
||||
更换身份。切换服务保持本机 ID,但目标服务必须有本机注册记录才能发布。
|
||||
|
||||
页面初次加载读取真实目录,空列表、加载中和读取失败分别展示。刷新失败保留旧列表并标记
|
||||
可能过期,禁用发布和撤销;用户可修改配置或重试。每次请求固定使用开始时读取的服务地址。
|
||||
页面操作期间锁定重复提交。保存失败保留设置草稿,发布失败保留条目草稿。
|
||||
|
||||
发布状态只有 `published` 与 `revoked`。撤销入口匹配本机 ID,Main 再查询本机发布列表并确认
|
||||
条目 ID;这只是防止误操作,不是认证。撤销只改变元数据状态。能力类型发送三个 `false`
|
||||
权限;知识类型按独立选择发送。目录读取无需注册,注册记录不表示在线。
|
||||
|
||||
## 1. 核心不变量
|
||||
|
||||
### INV-1 连接方式独立
|
||||
|
||||
@@ -4,13 +4,39 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 设计中 |
|
||||
| 状态 | 本地元数据首期已接入;完整共享网络为后续设计 |
|
||||
| 版本 | 0.1 |
|
||||
| 日期 | 2026-09-03 |
|
||||
| 适用产品 | GoodBuddy Desktop |
|
||||
| 关联架构 | [共享网络总体设计](../../architecture/share-network-architecture.md) |
|
||||
| 关联文档 | [功能逻辑](./logic-design.md)、[UI 设计](./ui-design.md)、[技术设计](./technical-design.md) |
|
||||
|
||||
## 当前首期:本地元数据
|
||||
|
||||
2026-10-01 首期范围由 `FR-M1` 至 `FR-M5` 定义。后文 `FR-1` 至 `FR-14` 保留为后续共享网络
|
||||
设计,不能用来推断首期已有发现、认证、调用或内容传输能力。
|
||||
|
||||
- **FR-M1 入口与设置**:设备共享为可选应用,测试阶段默认关闭,应用中心提供启用开关;
|
||||
启用后才出现在应用菜单,关闭时退出当前设备共享页面。旧设置缺少开关时按关闭处理,已保存的
|
||||
明确启用值保留。关闭不清除目录、设备设置或撤销已发布记录。本机设备 ID 稳定保存,用户可
|
||||
修改服务 URL 和设备名称。设置保存后重启仍保留;新设备默认连接 `http://127.0.0.1:8787`。
|
||||
- **FR-M2 注册与浏览**:用户显式注册本机,随后查看服务实际返回的设备及发布列表。注册
|
||||
包含 ID、名称、平台、应用版本;时间来自服务记录,不从更新时间推断在线状态。
|
||||
- **FR-M3 元数据发布**:注册本机后可发布名称、描述、能力或知识类型、设备或服务器来源。
|
||||
知识的搜索、读取、下载三个标志独立选择。服务器来源不会上传、同步或托管内容;权限
|
||||
标志不提供内容访问实现。
|
||||
- **FR-M4 撤销与刷新**:只给匹配本机 ID 的已发布条目提供撤销入口,确认后更新服务记录并
|
||||
刷新目录,显示已撤销状态。该筛选不构成认证,服务本身没有所有权鉴权。
|
||||
- **FR-M5 失败恢复**:请求有超时,错误可见;失败保留用户输入,列表失败不伪装为空目录。
|
||||
修改服务 URL 后必须先保存,再向新服务注册或发布。既有应用设置、排序和常驻偏好保留。
|
||||
|
||||
验收顺序为启用应用、保存配置、注册本机、浏览返回列表、发布知识元数据、检查三个独立标志、撤销、
|
||||
刷新并重启读取设备 ID。网络请求由 Main 发出,Renderer 只调用显式 preload 方法。
|
||||
|
||||
## 后续设计
|
||||
|
||||
以下章节为完整共享网络计划,尚未实施。
|
||||
|
||||
## 1. 背景
|
||||
|
||||
GoodBuddy 的能力当前主要在本机执行,SSH 远程项目则要求用户预先配置具体 Host。个人和组织
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
# 设备共享实施进度
|
||||
|
||||
日期:2026-10-01。当前实现范围见 [README](./README.md)。
|
||||
|
||||
## 2026-10-01:测试阶段默认关闭
|
||||
|
||||
对应 FR-M1,新增持久化 `deviceSharingEnabled`,默认 `false`,旧设置缺少字段时补为关闭,
|
||||
明确保存的 `true` 保留。应用中心提供启用 Switch,不增加常驻;菜单仅显示已启用项。
|
||||
从应用中心或设置变更事件关闭时,App 退出设备共享页面并返回对话;设备设置、目录与已发布
|
||||
记录保留。中英文文案、功能列表和导航文档已同步。
|
||||
|
||||
本轮验证:设置存储、配置工具契约及服务、应用中心、菜单和共享页面共 6 文件 112 项通过;
|
||||
完整 `App.test.tsx` 321 项通过;设置面板、更新设置及笔记工作区 3 文件 246 项通过。
|
||||
`npm run typecheck` 与 `npm run lint` 通过。首次 App 回归发现关闭逻辑误触其他设置导航,
|
||||
限定为当前设备共享页后复跑通过;新增测试的查询参数类型错误也已修正。
|
||||
|
||||
设置存储回归覆盖版本 11/12 的字段缺失、显式开启/关闭、无关更新、事件与重读;App 回归
|
||||
覆盖默认隐藏、启用、菜单打开、UI 关闭和外部事件关闭,重新启用不自动返回该页。
|
||||
真实 Electron/ShareServer 用例 1 项通过,新增默认禁用“打开”、仅一个启用 Switch 的检查,
|
||||
启用后继续注册、发布、撤销、刷新及浅深主题 480px 检查。该 Electron 用例的应用设置由组件
|
||||
fixture 更新,持久化由真实设置存储测试覆盖,不据此声明完整生产 App 或安装包联调通过。
|
||||
本轮未运行全仓测试,未改动 Agent/Runtime 路径,无模型调用。
|
||||
|
||||
## 首期接入记录
|
||||
|
||||
`a3b41bf` 已接入应用入口、Main 服务、可信 IPC、preload 与页面,
|
||||
支持配置 ShareServer、注册设备、读取目录、发布/撤销元数据及本机 ID 持久化。
|
||||
设备 ID 不是认证凭据,权限字段不代表已实现远程操作;正文传输、能力调用、
|
||||
发现和配对仍不属于本期实现。
|
||||
|
||||
当前组合源码类型检查和 lint 通过。仓库包含服务、IPC、页面测试和需指定
|
||||
`GOODBUDDY_SHARING_SERVER_ROOT` 的真实 ShareServer/Electron 联调用例。
|
||||
本轮发布准备中的定向测试被用户中断,并要求直接推送交 CI 验证;
|
||||
不据此声明测试或真实服务联调通过。发布结果以最终候选 CI 为准,
|
||||
条件跳过的真实服务用例不能计入已验证能力。
|
||||
|
||||
## 2026-10-01:桌面实现轮次的独立验证
|
||||
|
||||
以下记录来自设备共享实现会话,独立于上文发布准备轮次;已实际运行,不作为发布 CI 或安装包
|
||||
验收的替代。对应 [FR-M1 至 FR-M5](./prd.md#当前首期本地元数据)。
|
||||
|
||||
| 检查 | 结果与覆盖范围 |
|
||||
| --- | --- |
|
||||
| 定向 7 文件 | 114 项通过:Main HTTP 契约、稳定 ID、设置重读、可信 IPC、超时与错误、页面发布/撤销、输入保留、旧设置迁移与应用排序 |
|
||||
| 完整 `App.test.tsx` | 320 项通过,含新增设备共享入口与既有设置 Modal 回归 |
|
||||
| `goodbuddy-config-contracts.test.ts` | 8 项通过;修正旧四项写入样例,保留重复、未知及缺失项拒绝断言 |
|
||||
| 真实 Electron/ShareServer | 1 项通过,使用相邻后端源码、独立端口和临时数据目录,真实窗口运行生产页面、preload、可信 IPC 和 Main 服务 |
|
||||
| 类型与静态检查 | `npm run typecheck`、`npm run lint`、`git diff --check` 通过 |
|
||||
|
||||
Electron 联调从应用中心打开页面,注册本机,发布知识元数据,服务返回确认
|
||||
`sourceMode=server`、`search=true/read=false/download=true`,随后撤销并刷新至
|
||||
`status=revoked`。注册、发布、撤销确认和刷新使用原生鼠标输入;重读磁盘保持设备 ID。
|
||||
浅深主题的 480px 视口均无横向溢出。首次窄窗口检查因系统缩放与预期 CSS 宽度不一致失败,
|
||||
沿用现有 Electron 测试的固定设备缩放设置后通过。该测试是组件组合,未启动完整生产 App,
|
||||
也未验证安装包、截图视觉效果或 macOS/Linux 窗口。
|
||||
|
||||
全仓 `npm test` 用时 760.82 秒:468 文件通过、13 文件跳过、2 文件失败;5,549 项通过、88 项
|
||||
跳过、2 项失败。设备共享真实 Electron 联调在这次全仓运行中也通过。两项失败分别为:
|
||||
|
||||
- 配置工具样例仍提交四项顺序。运行期间修正后独立复跑 8 项通过;随后与服务测试合跑
|
||||
11 项也通过,服务测试包含对尚不存在设置文件的并发首次读取。
|
||||
- `inline-help.electron.test.ts` 的 `Hover Escape closed modal: dark 360x320` 断言失败。
|
||||
未修改该文件或帮助组件,独立复跑 1 项通过,原因未确定;不把复跑结果写成全仓一次全绿。
|
||||
|
||||
完整日志位于本次工作机临时目录 `opencode/goodbuddy-sharing-full-tests.log`。没有再次执行整套
|
||||
全仓测试。本功能测试没有模型调用。中文设备共享文档经 `deai-writing` 扫描无阻断项,复核项
|
||||
中的权限边界、真实字段及测试列表按事实保留。
|
||||
|
||||
### 复现命令
|
||||
|
||||
在 GoodBuddy 仓库执行:
|
||||
|
||||
```powershell
|
||||
npx vitest run src/main/device-sharing-service.test.ts src/main/device-sharing-ipc.test.ts src/main/application-settings-store.test.ts src/renderer/src/DeviceSharingPage.test.tsx src/renderer/src/ApplicationCenter.test.tsx src/renderer/src/ApplicationMenu.test.tsx src/preload/preload-sandbox.test.ts
|
||||
npx vitest run src/renderer/src/App.test.tsx
|
||||
npx vitest run src/shared/goodbuddy-config-contracts.test.ts
|
||||
$env:GOODBUDDY_SHARING_SERVER_ROOT = (Resolve-Path ../goodbuddy-shareserver).Path
|
||||
npx vitest run tests/device-sharing.electron.test.ts
|
||||
npx vitest run tests/inline-help.electron.test.ts
|
||||
npm run typecheck
|
||||
npm run lint
|
||||
npm test
|
||||
```
|
||||
|
||||
跨仓 Electron 测试需要后端已安装依赖;未设置 `GOODBUDDY_SHARING_SERVER_ROOT` 时明确跳过。
|
||||
测试会关闭自己启动的服务并清理临时数据,不写后端源码。
|
||||
|
||||
### 手工体验
|
||||
|
||||
在 ShareServer 仓库运行 `npm run dev:api`,在 GoodBuddy 仓库运行 `npm run dev`。先从“应用 →
|
||||
管理应用 → 设备共享 → 应用设置”启用,再从应用菜单打开“设备共享”,默认地址为
|
||||
`http://127.0.0.1:8787`。先保存名称或地址修改,再注册本机、
|
||||
发布条目、检查目录、撤销并刷新。知识类型提供三个独立权限框;重启后可比较同一设备 ID。
|
||||
服务关闭时刷新应显示错误并保留旧列表,不应显示为空目录或推断设备离线。
|
||||
@@ -4,13 +4,47 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 设计中 |
|
||||
| 状态 | 本地元数据首期已接入;完整共享网络为后续设计 |
|
||||
| 版本 | 0.1 |
|
||||
| 日期 | 2026-09-03 |
|
||||
| 关联 PRD | [设备发现与共享 PRD](./prd.md) |
|
||||
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
|
||||
| 共享协议 | [共享网络总体设计](../../architecture/share-network-architecture.md) |
|
||||
|
||||
## 当前首期实现
|
||||
|
||||
本节实现 [FR-M1 至 FR-M5](./prd.md#当前首期本地元数据)。后文模块树、SQLite 表、共享协议包、
|
||||
局域网与网关协议都是后续设计,当前不创建这些基础设施。
|
||||
|
||||
`src/shared/device-sharing-contracts.ts` 定义严格输入和返回类型;`DesktopApi.sharing` 暴露
|
||||
`getSettings`、`saveSettings`、`registerDevice`、`getCatalog`、`publish`、`revoke` 六个方法。
|
||||
生产 preload 对应六个固定 IPC 通道,`device-sharing-ipc.ts` 对每次调用检查可信主窗口主帧,
|
||||
对变更输入进行 Zod 校验,不暴露通用网络入口。
|
||||
|
||||
`DeviceSharingService` 在 Main 内使用 `fetch`,每个请求含响应体读取采用 10 秒超时,非成功
|
||||
响应保留 HTTP 状态;响应再经 Zod 校验。请求只在页面读取或用户动作时发出,不启动后台发现。
|
||||
URL 接受 HTTP/HTTPS,禁止内嵌凭据、查询和片段。没有令牌或 Agent/Runtime 改动。
|
||||
|
||||
| 方法 | HTTP 请求 |
|
||||
| --- | --- |
|
||||
| 注册 | `POST /api/v1/sharing/devices/register`,字段为 `id,name,platform,appVersion` |
|
||||
| 设备列表 | `GET /api/v1/sharing/devices`,读取 `{devices:[]}` |
|
||||
| 发布列表 | `GET /api/v1/sharing/publications`,读取 `{publications:[]}` |
|
||||
| 发布 | `POST /api/v1/sharing/publications`,Main 生成条目 ID 并填入本机设备 ID |
|
||||
| 撤销 | 先按 `?deviceId=` 获取本机条目,再 `POST /api/v1/sharing/publications/:id/revoke` |
|
||||
|
||||
设备 ID、名称与服务 URL 存在用户数据目录的 `device-sharing-settings.json`,版本为 1;
|
||||
复用 `writeJsonFileAtomically`,加载 Promise 去重、保存串行。损坏设置报错而不重置 ID。
|
||||
此文件不保存目录副本或凭据。导航沿用已有存储归一化,把 `device-sharing` 追加到旧顺序,
|
||||
保留既有应用相对顺序与设置;新入口可排序,不增加常驻字段。应用设置中的
|
||||
`deviceSharingEnabled` 使用共享 schema 的 `false` 默认值补齐缺失字段,显式 `true` 保留;
|
||||
沿用现有版本与原子持久化、变更事件。更新 schema 将该字段设为可选,避免无关更新重置选择。
|
||||
菜单复用启用过滤,App 收到关闭设置时返回对话并卸载页面。共享服务和目录数据不受此开关修改。
|
||||
|
||||
服务测试使用真实 loopback HTTP,验证契约、独立权限、识别范围、持久化、错误和超时;
|
||||
Electron 测试通过环境变量选择相邻真实 ShareServer 源码,使用临时服务数据与桌面设置。
|
||||
具体命令和实测边界见[实施进度](./progress.md)。
|
||||
|
||||
## 1. 进程边界
|
||||
|
||||
```text
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 状态 | 设计中 |
|
||||
| 状态 | 本地元数据首期已接入;完整共享网络为后续设计 |
|
||||
| 版本 | 0.1 |
|
||||
| 日期 | 2026-09-03 |
|
||||
| 关联 PRD | [设备发现与共享 PRD](./prd.md) |
|
||||
@@ -14,6 +14,26 @@
|
||||
本文只定义桌面端界面。所有颜色、间距、字体、通知、Switch、PageTabs、焦点和响应式行为以
|
||||
全局设计系统为准。
|
||||
|
||||
## 当前首期页面
|
||||
|
||||
本节对应 [FR-M1 至 FR-M5](./prd.md#当前首期本地元数据),后文四页签、配对及调用界面为后续设计。
|
||||
|
||||
应用中心显示“可选”及启用状态,测试阶段默认关闭,此时“打开”禁用。应用设置复用共享
|
||||
Switch,常显测试状态和关闭后果,不提供常驻开关。启用后应用菜单显示入口,关闭时退出
|
||||
设备共享页面并返回对话。主内容区使用 `standard` PageShell、PageHeader 与全局范围徽标。
|
||||
标题下常显未认证、ID 识别用途和无执行/内容访问的边界。内容依次为本机设置、已注册设备、
|
||||
发布表单和发布目录。服务 URL 与设备名称可编辑,设备 ID 只读;注册、保存与刷新为次操作,
|
||||
“发布元数据”为唯一主按钮。
|
||||
|
||||
类型与来源使用短标签原生选择框;知识的搜索、读取、下载使用三个独立 Checkbox。服务器来源
|
||||
不上传或同步内容的说明常显。发布按钮附近显示已保存的目标服务及设备名称,尚未注册或
|
||||
配置未保存时给出操作提示。列表展示服务返回的名称、标识、平台/类型、更新时间和发布状态,
|
||||
不展示在线灯或执行按钮。
|
||||
|
||||
撤销使用共享危险入口和就地确认,说明保留记录且不删除本机内容。成功与操作失败通过应用
|
||||
通知反馈;目录加载失败在页面显示并由刷新按钮恢复。宽度收窄时按钮换行,长 URL 和 ID 换行;
|
||||
表单复用 `.field`,颜色与间距使用共享令牌。页面文案提供中英文。
|
||||
|
||||
## 1. 信息架构
|
||||
|
||||
设置中心新增一级分类“设备发现与共享”,使用 `standard` PageShell:
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
| --- | --- |
|
||||
| 直连模型 | Runtime provider 为 `model`、由 GoodBuddy 直接调用模型协议的文本模型 |
|
||||
| 进程执行 | GoodBuddy 在当前执行空间启动一个前台 Shell 命令并返回有界结果 |
|
||||
| 工作区搜索 | 通过安装包内置 ripgrep 搜索内容或列出文件,并返回紧凑结果 |
|
||||
| 工作区搜索 | 通过安装包内置 ripgrep 搜索内容或列出文件,接受原生参数并返回原生输出及退出码 |
|
||||
| 工作区补丁 | 通过 `*** Begin Patch` 格式新增、修改或删除工作区文本文件 |
|
||||
| 输出续读 | 通过 `output_read` 分页读取当前会话保留的进程或 Subagent 输出 |
|
||||
| 输出续读 | 通过 `output_read` 分页读取当前会话保留的搜索、进程或 Subagent 输出 |
|
||||
| 平台 Shell | Windows 上的 PowerShell,macOS/Linux 上的 Bash 或 POSIX Sh |
|
||||
| 编程 Subagent | 由直连模型临时委派、使用直连模型 Runtime 执行一个有界子任务的执行者 |
|
||||
| 父请求 | 发起进程调用或 Subagent 委派的当前直连模型请求 |
|
||||
@@ -43,7 +43,7 @@ Task、Job、Subjob、Run 和 Subagent 的对象关系以
|
||||
- OpenCode、Continue 和 DeepSeek Harness 继续使用各自原生执行与委派能力。
|
||||
- Ask 可搜索和分页读取工作区,但不允许补丁写入或进程执行;Subagent 若在 Ask 中使用,
|
||||
只能继承 Ask 的只读能力。
|
||||
- 进程和 Subagent 的长输出返回预览与续读位置,完整内容保留到所属会话或 Runtime 释放。
|
||||
- 搜索、进程和 Subagent 的长输出返回预览与续读位置,完整内容保留到所属会话或 Runtime 释放。
|
||||
Ask 和 Execute 均可续读当前会话已取得的输出,详见 [输出契约](./technical-design.md#43-输出边界)。
|
||||
- Execute 表示用户授权当前执行空间账号的完整能力,不增加第二套工具审批或权限档位。
|
||||
- 首版跨平台指桌面本机 Windows、macOS 和 Linux。托管 SSH 项目当前继续使用远端
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user